Merge "Refactor PagedTileLayout to not overflow screen"
This commit is contained in:
committed by
Android (Google) Code Review
commit
bf954233c6
@@ -19,7 +19,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tile_page"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="@dimen/notification_side_paddings"
|
||||
android:paddingEnd="@dimen/notification_side_paddings"
|
||||
android:clipChildren="false"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom">
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (c) 2017, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<!-- The number of rows in the QuickSettings -->
|
||||
<integer name="quick_settings_num_rows">2</integer>
|
||||
</resources>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (c) 2017, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<!-- The number of rows in the QuickSettings -->
|
||||
<integer name="quick_settings_num_rows">3</integer>
|
||||
</resources>
|
||||
@@ -107,7 +107,7 @@
|
||||
<integer name="quick_settings_num_columns">3</integer>
|
||||
|
||||
<!-- The number of rows in the QuickSettings -->
|
||||
<integer name="quick_settings_num_rows">1</integer>
|
||||
<integer name="quick_settings_max_rows">3</integer>
|
||||
|
||||
<!-- The number of columns that the top level tiles span in the QuickSettings -->
|
||||
<integer name="quick_settings_user_time_settings_tile_span">1</integer>
|
||||
|
||||
@@ -6,14 +6,12 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
@@ -41,14 +39,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
return t * t * t + 1.0f;
|
||||
};
|
||||
|
||||
|
||||
private final ArrayList<TileRecord> mTiles = new ArrayList<>();
|
||||
private final ArrayList<TilePage> mPages = new ArrayList<>();
|
||||
|
||||
private PageIndicator mPageIndicator;
|
||||
private float mPageIndicatorPosition;
|
||||
|
||||
private int mNumPages;
|
||||
private PageListener mPageListener;
|
||||
|
||||
private boolean mListening;
|
||||
@@ -56,6 +52,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
|
||||
private AnimatorSet mBounceAnimatorSet;
|
||||
private float mLastExpansion;
|
||||
private boolean mDistributeTiles = false;
|
||||
|
||||
public PagedTileLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -122,7 +119,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
|
||||
public void setPageIndicator(PageIndicator indicator) {
|
||||
mPageIndicator = indicator;
|
||||
mPageIndicator.setNumPages(mNumPages);
|
||||
mPageIndicator.setNumPages(mPages.size());
|
||||
mPageIndicator.setLocation(mPageIndicatorPosition);
|
||||
}
|
||||
|
||||
@@ -136,13 +133,15 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
@Override
|
||||
public void addTile(TileRecord tile) {
|
||||
mTiles.add(tile);
|
||||
postDistributeTiles();
|
||||
mDistributeTiles = true;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTile(TileRecord tile) {
|
||||
if (mTiles.remove(tile)) {
|
||||
postDistributeTiles();
|
||||
mDistributeTiles = true;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,44 +174,50 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
mPageListener = listener;
|
||||
}
|
||||
|
||||
private void postDistributeTiles() {
|
||||
removeCallbacks(mDistribute);
|
||||
post(mDistribute);
|
||||
}
|
||||
|
||||
private void distributeTiles() {
|
||||
emptyAndInflateOrRemovePages();
|
||||
|
||||
final int tileCount = mPages.get(0).maxTiles();
|
||||
if (DEBUG) Log.d(TAG, "Distributing tiles");
|
||||
final int NP = mPages.size();
|
||||
for (int i = 0; i < NP; i++) {
|
||||
mPages.get(i).removeAllViews();
|
||||
}
|
||||
int index = 0;
|
||||
final int NT = mTiles.size();
|
||||
for (int i = 0; i < NT; i++) {
|
||||
TileRecord tile = mTiles.get(i);
|
||||
if (mPages.get(index).isFull()) {
|
||||
if (++index == mPages.size()) {
|
||||
if (DEBUG) Log.d(TAG, "Adding page for "
|
||||
+ tile.tile.getClass().getSimpleName());
|
||||
mPages.add((TilePage) LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.qs_paged_page, this, false));
|
||||
}
|
||||
if (mPages.get(index).mRecords.size() == tileCount) index++;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
|
||||
+ index);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
|
||||
+ index);
|
||||
mPages.get(index).addTile(tile);
|
||||
}
|
||||
if (mNumPages != index + 1) {
|
||||
mNumPages = index + 1;
|
||||
while (mPages.size() > mNumPages) {
|
||||
mPages.remove(mPages.size() - 1);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
|
||||
mPageIndicator.setNumPages(mNumPages);
|
||||
setAdapter(mAdapter);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
setCurrentItem(0, false);
|
||||
}
|
||||
|
||||
private void emptyAndInflateOrRemovePages() {
|
||||
final int nTiles = mTiles.size();
|
||||
int numPages = nTiles / mPages.get(0).maxTiles();
|
||||
// Add one more not full page if needed
|
||||
numPages += (nTiles % mPages.get(0).maxTiles() == 0 ? 0 : 1);
|
||||
|
||||
final int NP = mPages.size();
|
||||
for (int i = 0; i < NP; i++) {
|
||||
mPages.get(i).removeAllViews();
|
||||
}
|
||||
if (NP == numPages) {
|
||||
return;
|
||||
}
|
||||
while (mPages.size() < numPages) {
|
||||
if (DEBUG) Log.d(TAG, "Adding page");
|
||||
mPages.add((TilePage) LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.qs_paged_page, this, false));
|
||||
}
|
||||
while (mPages.size() > numPages) {
|
||||
if (DEBUG) Log.d(TAG, "Removing page");
|
||||
mPages.remove(mPages.size() - 1);
|
||||
}
|
||||
mPageIndicator.setNumPages(mPages.size());
|
||||
setAdapter(mAdapter);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
setCurrentItem(0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,20 +227,39 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
setPadding(0, 0, 0,
|
||||
getContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.qs_paged_tile_layout_padding_bottom));
|
||||
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < mPages.size(); i++) {
|
||||
changed |= mPages.get(i).updateResources();
|
||||
}
|
||||
if (changed) {
|
||||
distributeTiles();
|
||||
mDistributeTiles = true;
|
||||
requestLayout();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
final int nTiles = mTiles.size();
|
||||
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
|
||||
|
||||
// Only change the pages if the number of rows or columns (from updateResources) has
|
||||
// changed or the tiles have changed
|
||||
if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) {
|
||||
mDistributeTiles = false;
|
||||
distributeTiles();
|
||||
}
|
||||
|
||||
final int nRows = mPages.get(0).mRows;
|
||||
for (int i = 0; i < mPages.size(); i++) {
|
||||
TilePage t = mPages.get(i);
|
||||
t.mRows = nRows;
|
||||
}
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
// The ViewPager likes to eat all of the space, instead force it to wrap to the max height
|
||||
// of the pages.
|
||||
int maxHeight = 0;
|
||||
@@ -249,13 +273,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
setMeasuredDimension(getMeasuredWidth(), maxHeight + getPaddingBottom());
|
||||
}
|
||||
|
||||
private final Runnable mDistribute = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
distributeTiles();
|
||||
}
|
||||
};
|
||||
|
||||
public int getColumnCount() {
|
||||
if (mPages.size() == 0) return 0;
|
||||
return mPages.get(0).mColumns;
|
||||
@@ -346,33 +363,17 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
};
|
||||
|
||||
public static class TilePage extends TileLayout {
|
||||
private int mMaxRows = 3;
|
||||
|
||||
public TilePage(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
updateResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateResources() {
|
||||
final int rows = getRows();
|
||||
boolean changed = rows != mMaxRows;
|
||||
if (changed) {
|
||||
mMaxRows = rows;
|
||||
requestLayout();
|
||||
}
|
||||
return super.updateResources() || changed;
|
||||
}
|
||||
|
||||
private int getRows() {
|
||||
return Math.max(1, getResources().getInteger(R.integer.quick_settings_num_rows));
|
||||
}
|
||||
|
||||
public void setMaxRows(int maxRows) {
|
||||
mMaxRows = maxRows;
|
||||
}
|
||||
|
||||
public boolean isFull() {
|
||||
return mRecords.size() >= mColumns * mMaxRows;
|
||||
return mRecords.size() >= mColumns * mRows;
|
||||
}
|
||||
|
||||
public int maxTiles() {
|
||||
return mColumns * mRows;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +402,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mNumPages;
|
||||
return mPages.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
@@ -91,15 +92,24 @@ public class QSContainerImpl extends FrameLayout {
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// Since we control our own bottom, be whatever size we want.
|
||||
// Otherwise the QSPanel ends up with 0 height when the window is only the
|
||||
// size of the status bar.
|
||||
mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
|
||||
MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
|
||||
// QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
|
||||
// bottom and footer are inside the screen.
|
||||
Configuration config = getResources().getConfiguration();
|
||||
boolean navBelow = config.smallestScreenWidthDp >= 600
|
||||
|| config.orientation != Configuration.ORIENTATION_LANDSCAPE;
|
||||
MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanel.getLayoutParams();
|
||||
|
||||
// The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to
|
||||
// subtract its height. We do not care if the collapsed notifications fit in the screen.
|
||||
int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
|
||||
- getPaddingBottom();
|
||||
if (navBelow) {
|
||||
maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
|
||||
}
|
||||
mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
|
||||
int width = mQSPanel.getMeasuredWidth();
|
||||
LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
|
||||
int height = layoutParams.topMargin + layoutParams.bottomMargin
|
||||
+ mQSPanel.getMeasuredHeight();
|
||||
+ mQSPanel.getMeasuredHeight() + getPaddingBottom();
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
||||
|
||||
|
||||
@@ -24,10 +24,12 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
|
||||
protected int mCellMarginHorizontal;
|
||||
protected int mCellMarginVertical;
|
||||
protected int mSidePadding;
|
||||
protected int mRows = 1;
|
||||
|
||||
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
|
||||
private int mCellMarginTop;
|
||||
private boolean mListening;
|
||||
protected int mMaxAllowedRows = 3;
|
||||
|
||||
public TileLayout(Context context) {
|
||||
this(context, null);
|
||||
@@ -86,6 +88,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
|
||||
mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
|
||||
mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
|
||||
mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
|
||||
mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
|
||||
if (mColumns != columns) {
|
||||
mColumns = columns;
|
||||
requestLayout();
|
||||
@@ -96,10 +99,16 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED
|
||||
// it will show all its tiles. In this case, the tiles have to be entered before the
|
||||
// container is measured. Any change in the tiles, should trigger a remeasure.
|
||||
final int numTiles = mRecords.size();
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
- getPaddingStart() - getPaddingEnd();
|
||||
final int numRows = (numTiles + mColumns - 1) / mColumns;
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
if (heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
mRows = (numTiles + mColumns - 1) / mColumns;
|
||||
}
|
||||
mCellWidth = (width - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns;
|
||||
|
||||
// Measure each QS tile.
|
||||
@@ -112,13 +121,35 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
|
||||
|
||||
// Only include the top margin in our measurement if we have more than 1 row to show.
|
||||
// Otherwise, don't add the extra margin buffer at top.
|
||||
int height = (mCellHeight + mCellMarginVertical) * numRows +
|
||||
(numRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0);
|
||||
int height = (mCellHeight + mCellMarginVertical) * mRows +
|
||||
(mRows != 0 ? (mCellMarginTop - mCellMarginVertical) : 0);
|
||||
if (height < 0) height = 0;
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the maximum number of rows that can be shown based on height. Clips at a minimum
|
||||
* of 1 and a maximum of mMaxAllowedRows.
|
||||
*
|
||||
* @param heightMeasureSpec Available height.
|
||||
* @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows.
|
||||
*/
|
||||
public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) {
|
||||
final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop;
|
||||
final int previousRows = mRows;
|
||||
mRows = availableHeight / (mCellHeight + mCellMarginVertical);
|
||||
if (mRows >= mMaxAllowedRows) {
|
||||
mRows = mMaxAllowedRows;
|
||||
} else if (mRows <= 1) {
|
||||
mRows = 1;
|
||||
}
|
||||
if (mRows > (tilesCount + mColumns - 1) / mColumns) {
|
||||
mRows = (tilesCount + mColumns - 1) / mColumns;
|
||||
}
|
||||
return previousRows != mRows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOverlappingRendering() {
|
||||
return false;
|
||||
@@ -135,7 +166,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
|
||||
int column = 0;
|
||||
|
||||
// Layout each QS tile.
|
||||
for (int i = 0; i < numRecords; i++, column++) {
|
||||
final int tilesToLayout = Math.min(numRecords, mRows * mColumns);
|
||||
for (int i = 0; i < tilesToLayout; i++, column++) {
|
||||
// If we reached the last column available to layout a tile, wrap back to the next row.
|
||||
if (column == mColumns) {
|
||||
column = 0;
|
||||
|
||||
Reference in New Issue
Block a user