From a74a7c079e905726d81db1975b24682115faf37a Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Tue, 14 Dec 2021 09:21:36 -0500 Subject: [PATCH] Fix QS expansion animations * Fix gap between tiles when expanding * Fix second page collapsing animation: tiles now will shrink and at the end be replaced by QQS tiles. We need to create the animators as we move through the pages, as PagedTileLayout only has the current page and the (up to) two adjacent ones. This means that if the page has not been added, we don't have correct positions and measures. See bug for video Fixes: 207895040 Test: manual Change-Id: Id2b14b11ace4199ed07f7675b6575cb4b71e93d5 --- .../com/android/systemui/plugins/qs/QS.java | 7 +- .../res/layout/qs_paged_tile_layout.xml | 4 +- .../android/systemui/qs/PagedTileLayout.java | 30 +- .../com/android/systemui/qs/QSAnimator.java | 280 +++++++++++++----- .../com/android/systemui/qs/QSFragment.java | 9 +- .../src/com/android/systemui/qs/QSPanel.java | 22 +- .../systemui/qs/QSPanelControllerBase.java | 10 + .../com/android/systemui/qs/QuickQSPanel.java | 8 +- 8 files changed, 284 insertions(+), 86 deletions(-) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index d5f858c4df941..8ad200951eb47 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -34,7 +34,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 12; + int VERSION = 13; String TAG = "QS"; @@ -70,6 +70,11 @@ public interface QS extends FragmentBase { void setContainerController(QSContainerController controller); void setExpandClickListener(OnClickListener onClickListener); + /** + * Returns the height difference between the QSPanel container and the QuickQSPanel container + */ + int getHeightDiff(); + View getHeader(); default void setHasNotifications(boolean hasNotifications) { diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml index 6b054a91d46b9..619591d4f7eca 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml +++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml @@ -20,4 +20,6 @@ android:id="@+id/qs_pager" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1"/> + android:layout_weight="1" + android:clipChildren="false" + android:clipToPadding="false" /> diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 3e2da7204c057..6d1f8f7d01b1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -30,6 +30,7 @@ import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import java.util.ArrayList; +import java.util.List; import java.util.Set; public class PagedTileLayout extends ViewPager implements QSTileLayout { @@ -332,6 +333,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageListener = listener; } + public List getSpecsForPage(int page) { + ArrayList out = new ArrayList<>(); + if (page < 0) return out; + int perPage = mPages.get(0).maxTiles(); + int startOfPage = page * perPage; + int endOfPage = (page + 1) * perPage; + for (int i = startOfPage; i < endOfPage && i < mTiles.size(); i++) { + out.add(mTiles.get(i).tile.getTileSpec()); + } + return out; + } + private void distributeTiles() { emptyAndInflateOrRemovePages(); @@ -491,6 +504,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return currentPage.mRecords.size(); } + public int getNumTilesFirstPage() { + if (mPages.size() == 0) return 0; + return mPages.get(0).mRecords.size(); + } + public void startTileReveal(Set tileSpecs, final Runnable postAnimation) { if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) { // Do not start the reveal animation unless there are tiles to animate, multiple @@ -556,8 +574,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { updateSelected(); if (mPageIndicator == null) return; if (mPageListener != null) { + int pageNumber = isLayoutRtl() ? mPages.size() - 1 - position : position; mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1 - : position == 0); + : position == 0, pageNumber); } } @@ -575,8 +594,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicatorPosition = position + positionOffset; mPageIndicator.setLocation(mPageIndicatorPosition); if (mPageListener != null) { + int pageNumber = isLayoutRtl() ? mPages.size() - 1 - position : position; mPageListener.onPageChanged(positionOffsetPixels == 0 && - (isLayoutRtl() ? position == mPages.size() - 1 : position == 0)); + (isLayoutRtl() ? position == mPages.size() - 1 : position == 0), + // Send only valid page number on integer pages + positionOffsetPixels == 0 ? pageNumber : PageListener.INVALID_PAGE + ); } } @@ -626,6 +649,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { }; public interface PageListener { - void onPageChanged(boolean isFirst); + int INVALID_PAGE = -1; + void onPageChanged(boolean isFirst, int pageNumber); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 44d5e21e6354b..6c7d6e01e9ec7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -20,6 +20,8 @@ import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; @@ -57,11 +59,14 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim"; private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows"; - public static final float EXPANDED_TILE_DELAY = .86f; + private static final float EXPANDED_TILE_DELAY = .86f; + //Non first page delays + private static final float QS_TILE_LABEL_FADE_OUT_START = 0.15f; + private static final float QS_TILE_LABEL_FADE_OUT_END = 0.7f; + private static final float QQS_FADE_IN_INTERVAL = 0.1f; + public static final float SHORT_PARALLAX_AMOUNT = 0.1f; - private static final long QQS_FADE_IN_DURATION = 200L; - // Fade out faster than fade in to finish before QQS hides. - private static final long QQS_FADE_OUT_DURATION = 50L; + /** * List of all views that will be reset when clearing animation state @@ -86,33 +91,54 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private PagedTileLayout mPagedLayout; private boolean mOnFirstPage = true; - private QSExpansionPathInterpolator mQSExpansionPathInterpolator; + private int mCurrentPage = 0; + private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; + // Animator for elements in the first page, including secondary labels and qqs brightness + // slider, as well as animating the alpha of the QS tile layout (as we are tracking QQS tiles) private TouchAnimator mFirstPageAnimator; - private TouchAnimator mFirstPageDelayedAnimator; + // TranslationX animator for QQS/QS tiles private TouchAnimator mTranslationXAnimator; + // TranslationY animator for QS tiles (and their components) in the first page private TouchAnimator mTranslationYAnimator; - private TouchAnimator mNonfirstPageAnimator; - private TouchAnimator mNonfirstPageDelayedAnimator; + // TranslationY animator for QQS tiles (and their components) + private TouchAnimator mQQSTranslationYAnimator; + // Animates alpha of permanent views (QS tile layout, QQS tiles) when not in first page + private TouchAnimator mNonfirstPageAlphaAnimator; + // TranslatesY the QS Tile layout using QS.getHeightDiff() + private TouchAnimator mQSTileLayoutTranslatorAnimator; // This animates fading of SecurityFooter and media divider private TouchAnimator mAllPagesDelayedAnimator; + // Animator for brightness slider(s) private TouchAnimator mBrightnessAnimator; + // Animator for Footer actions in QQS private TouchAnimator mQQSFooterActionsAnimator; + // Height animator for QQS tiles (height changing from QQS size to QS size) private HeightExpansionAnimator mQQSTileHeightAnimator; - private HeightExpansionAnimator mOtherTilesExpandAnimator; + // Height animator for QS tile in first page but not in QQS, to present the illusion that they + // are expanding alongside the QQS tiles + private HeightExpansionAnimator mOtherFirstPageTilesHeightAnimator; + // Pair of animators for each non first page. The creation is delayed until the user first + // scrolls to that page, in order to get the proper measures and layout. + private final SparseArray> + mNonFirstPageQSAnimators = new SparseArray<>(); private boolean mNeedsAnimatorUpdate = false; - private boolean mToShowing; private boolean mOnKeyguard; private boolean mAllowFancy; private boolean mFullRows; private int mNumQuickTiles; + private int mLastQQSTileHeight; private float mLastPosition; private final QSTileHost mHost; private final Executor mExecutor; private final TunerService mTunerService; private boolean mShowCollapsedOnKeyguard; private boolean mTranslateWhileExpanding; + private int mQQSTop; + + private int[] mTmpLoc1 = new int[2]; + private int[] mTmpLoc2 = new int[2]; @Inject public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, @@ -213,8 +239,19 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateAnimators(); } + private void addNonFirstPageAnimators(int page) { + Pair pair = createSecondaryPageAnimators(page); + mNonFirstPageQSAnimators.put(page, pair); + } + @Override - public void onPageChanged(boolean isFirst) { + public void onPageChanged(boolean isFirst, int currentPage) { + if (currentPage != INVALID_PAGE && mCurrentPage != currentPage) { + mCurrentPage = currentPage; + if (!isFirst && !mNonFirstPageQSAnimators.contains(currentPage)) { + addNonFirstPageAnimators(currentPage); + } + } if (mOnFirstPage == isFirst) return; if (!isFirst) { clearAnimationState(); @@ -230,7 +267,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha int yOffset, int[] temp, TouchAnimator.Builder animatorBuilderX, - TouchAnimator.Builder animatorBuilderY + TouchAnimator.Builder animatorBuilderY, + TouchAnimator.Builder qqsAnimatorBuilderY ) { getRelativePosition(temp, qqsView, commonParent); int qqsPosX = temp[0]; @@ -243,7 +281,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha animatorBuilderX.addFloat(qqsView, "translationX", 0, xDiff); animatorBuilderX.addFloat(qsView, "translationX", -xDiff, 0); int yDiff = qsPosY - qqsPosY - yOffset; - animatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff); + qqsAnimatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff); animatorBuilderY.addFloat(qsView, "translationY", -yDiff, 0); mAllViews.add(qqsView); mAllViews.add(qsView); @@ -253,40 +291,47 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mNeedsAnimatorUpdate = false; TouchAnimator.Builder firstPageBuilder = new Builder(); TouchAnimator.Builder translationYBuilder = new Builder(); + TouchAnimator.Builder qqsTranslationYBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); + TouchAnimator.Builder nonFirstPageAlphaBuilder = new Builder(); Collection tiles = mHost.getTiles(); int count = 0; - int[] loc1 = new int[2]; - int[] loc2 = new int[2]; clearAnimationState(); + mNonFirstPageQSAnimators.clear(); mAllViews.clear(); mAnimatedQsViews.clear(); mQQSTileHeightAnimator = null; - mOtherTilesExpandAnimator = null; + mOtherFirstPageTilesHeightAnimator = null; mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(); QSTileLayout tileLayout = mQsPanelController.getTileLayout(); mAllViews.add((View) tileLayout); - int height = mQs.getView() != null ? mQs.getView().getMeasuredHeight() : 0; - int heightDiff = height - mQs.getHeader().getBottom() - + mQs.getHeader().getPaddingBottom(); + int heightDiff = mQs.getHeightDiff(); if (!mTranslateWhileExpanding) { heightDiff *= SHORT_PARALLAX_AMOUNT; } - firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); + mQSTileLayoutTranslatorAnimator = new Builder() + .addFloat(tileLayout, "translationY", heightDiff, 0) + .build(); - int qqsTileHeight = 0; + mLastQQSTileHeight = 0; if (mQsPanelController.areThereTiles()) { for (QSTile tile : tiles) { QSTileView tileView = mQsPanelController.getTileView(tile); + if (tileView == null) { Log.e(TAG, "tileView is null " + tile.getTileSpec()); continue; } + // Only animate tiles in the first page + if (mPagedLayout != null && count >= mPagedLayout.getNumTilesFirstPage()) { + break; + } + final View tileIcon = tileView.getIcon().getIconView(); View view = mQs.getView(); @@ -297,16 +342,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); if (quickTileView == null) continue; - getRelativePosition(loc1, quickTileView, view); - getRelativePosition(loc2, tileView, view); - int yOffset = loc2[1] - loc1[1]; - int xOffset = loc2[0] - loc1[0]; + getRelativePosition(mTmpLoc1, quickTileView, view); + getRelativePosition(mTmpLoc2, tileView, view); + int yOffset = mTmpLoc2[1] - mTmpLoc1[1]; + int xOffset = mTmpLoc2[0] - mTmpLoc1[0]; // Offset the translation animation on the views // (that goes from 0 to getOffsetTranslation) int offsetWithQSBHTranslation = yOffset - mQuickStatusBarHeader.getOffsetTranslation(); - translationYBuilder.addFloat(quickTileView, "translationY", 0, + qqsTranslationYBuilder.addFloat(quickTileView, "translationY", 0, offsetWithQSBHTranslation); translationYBuilder.addFloat(tileView, "translationY", -offsetWithQSBHTranslation, 0); @@ -317,7 +362,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if (mQQSTileHeightAnimator == null) { mQQSTileHeightAnimator = new HeightExpansionAnimator(this, quickTileView.getMeasuredHeight(), tileView.getMeasuredHeight()); - qqsTileHeight = quickTileView.getMeasuredHeight(); + mLastQQSTileHeight = quickTileView.getMeasuredHeight(); } mQQSTileHeightAnimator.addView(quickTileView); @@ -329,9 +374,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha view, xOffset, yOffset, - loc1, + mTmpLoc1, translationXBuilder, - translationYBuilder + translationYBuilder, + qqsTranslationYBuilder ); // Label containers @@ -341,9 +387,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha view, xOffset, yOffset, - loc1, + mTmpLoc1, translationXBuilder, - translationYBuilder + translationYBuilder, + qqsTranslationYBuilder ); // Secondary icon @@ -353,12 +400,15 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha view, xOffset, yOffset, - loc1, + mTmpLoc1, translationXBuilder, - translationYBuilder + translationYBuilder, + qqsTranslationYBuilder ); firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); + nonFirstPageAlphaBuilder + .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 0); mAnimatedQsViews.add(tileView); mAllViews.add(quickTileView); @@ -373,16 +423,17 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // expanding from. SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); - getRelativePosition(loc1, qqsLayout, view); - getRelativePosition(loc2, tileView, view); - int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count)); + getRelativePosition(mTmpLoc1, qqsLayout, view); + mQQSTop = mTmpLoc1[1]; + getRelativePosition(mTmpLoc2, tileView, view); + int diff = mTmpLoc2[1] - (mTmpLoc1[1] + qqsLayout.getPhantomTopPosition(count)); translationYBuilder.addFloat(tileView, "translationY", -diff, 0); - if (mOtherTilesExpandAnimator == null) { - mOtherTilesExpandAnimator = + if (mOtherFirstPageTilesHeightAnimator == null) { + mOtherFirstPageTilesHeightAnimator = new HeightExpansionAnimator( - this, qqsTileHeight, tileView.getMeasuredHeight()); + this, mLastQQSTileHeight, tileView.getMeasuredHeight()); } - mOtherTilesExpandAnimator.addView(tileView); + mOtherFirstPageTilesHeightAnimator.addView(tileView); tileView.setClipChildren(true); tileView.setClipToPadding(true); firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1); @@ -392,18 +443,19 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mAllViews.add(tileView); count++; } + if (mCurrentPage != 0) { + addNonFirstPageAnimators(mCurrentPage); + } } if (mAllowFancy) { animateBrightnessSlider(firstPageBuilder); mFirstPageAnimator = firstPageBuilder + // Fade in the tiles/labels as we reach the final position. + .addFloat(tileLayout, "alpha", 0, 1) .setListener(this) .build(); - // Fade in the tiles/labels as we reach the final position. - Builder builder = new Builder() - .addFloat(tileLayout, "alpha", 0, 1); - mFirstPageDelayedAnimator = builder.build(); if (mQQSFooterActions.getVisibility() != View.GONE) { // only when qqs footer is present (which means split shade mode) it needs to @@ -411,9 +463,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateQQSFooterAnimation(); } - // Fade in the security footer and the divider as we reach the final position - builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); + Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1); if (mQsPanelController.shouldUseHorizontalLayout() && mQsPanelController.mMediaHost.hostView != null) { @@ -425,26 +476,100 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mAllPagesDelayedAnimator = builder.build(); mAllViews.add(mSecurityFooter.getView()); translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); + qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator()); + if (mOnFirstPage) { + // Only recreate this animator if we're in the first page. That way we know that + // the first page is attached and has the proper positions/measures. + mQQSTranslationYAnimator = qqsTranslationYBuilder.build(); + } mTranslationYAnimator = translationYBuilder.build(); mTranslationXAnimator = translationXBuilder.build(); if (mQQSTileHeightAnimator != null) { mQQSTileHeightAnimator.setInterpolator( mQSExpansionPathInterpolator.getYInterpolator()); } - if (mOtherTilesExpandAnimator != null) { - mOtherTilesExpandAnimator.setInterpolator( + if (mOtherFirstPageTilesHeightAnimator != null) { + mOtherFirstPageTilesHeightAnimator.setInterpolator( mQSExpansionPathInterpolator.getYInterpolator()); } } - mNonfirstPageAnimator = new TouchAnimator.Builder() + mNonfirstPageAlphaAnimator = nonFirstPageAlphaBuilder .addFloat(mQuickQsPanel, "alpha", 1, 0) + .addFloat(tileLayout, "alpha", 0, 1) .setListener(mNonFirstPageListener) - .setEndDelay(.5f) + .setEndDelay(1 - QQS_FADE_IN_INTERVAL) .build(); - mNonfirstPageDelayedAnimator = new TouchAnimator.Builder() - .setStartDelay(.14f) - .addFloat(tileLayout, "alpha", 0, 1).build(); + } + + private Pair createSecondaryPageAnimators(int page) { + if (mPagedLayout == null) return null; + HeightExpansionAnimator animator = null; + TouchAnimator.Builder builder = new Builder() + .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); + TouchAnimator.Builder alphaDelayedBuilder = new Builder() + .setStartDelay(QS_TILE_LABEL_FADE_OUT_START) + .setEndDelay(QS_TILE_LABEL_FADE_OUT_END); + SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); + View view = mQs.getView(); + List specs = mPagedLayout.getSpecsForPage(page); + + int row = -1; + int lastTileTop = -1; + + for (int i = 0; i < specs.size(); i++) { + QSTileView tileView = mQsPanelController.getTileView(specs.get(i)); + getRelativePosition(mTmpLoc2, tileView, view); + int diff = mTmpLoc2[1] - (mQQSTop + qqsLayout.getPhantomTopPosition(i)); + builder.addFloat(tileView, "translationY", -diff, 0); + // The different elements in the tile should be centered, so maintain them centered + int centerDiff = (tileView.getMeasuredHeight() - mLastQQSTileHeight) / 2; + builder.addFloat(tileView.getIcon(), "translationY", -centerDiff, 0); + builder.addFloat(tileView.getSecondaryIcon(), "translationY", -centerDiff, 0); + // The labels have different apparent size in QQS vs QS (no secondary label), so the + // translation needs to account for that. + int labelDiff = centerDiff - tileView.getSecondaryLabel().getMeasuredHeight() / 2; + builder.addFloat(tileView.getLabelContainer(), "translationY", -labelDiff, 0); + builder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 0.3f, 1); + + alphaDelayedBuilder.addFloat(tileView.getLabelContainer(), "alpha", 0, 1); + alphaDelayedBuilder.addFloat(tileView.getIcon(), "alpha", 0, 1); + alphaDelayedBuilder.addFloat(tileView.getSecondaryIcon(), "alpha", 0, 1); + + final int tileTop = tileView.getTop(); + if (tileTop != lastTileTop) { + row++; + lastTileTop = tileTop; + } + if (i >= mQuickQsPanel.getTileLayout().getNumVisibleTiles() && row >= 2) { + // Fade completely the tiles in rows below the ones that will merge into QQS. + // args is an array of 0s where the length is the current row index (at least third + // row) + final float[] args = new float[row]; + args[args.length - 1] = 1f; + builder.addFloat(tileView, "alpha", args); + } else { + // For all the other rows, fade them a bit + builder.addFloat(tileView, "alpha", 0.6f, 1); + } + + if (animator == null) { + animator = new HeightExpansionAnimator( + this, mLastQQSTileHeight, tileView.getMeasuredHeight()); + animator.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); + } + animator.addView(tileView); + + tileView.setClipChildren(true); + tileView.setClipToPadding(true); + mAllViews.add(tileView); + mAllViews.add(tileView.getSecondaryLabel()); + mAllViews.add(tileView.getIcon()); + mAllViews.add(tileView.getSecondaryIcon()); + mAllViews.add(tileView.getLabelContainer()); + } + builder.addFloat(alphaDelayedBuilder.build(), "position", 0, 1); + return new Pair<>(animator, builder.build()); } private void animateBrightnessSlider(Builder firstPageBuilder) { @@ -542,31 +667,37 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } mLastPosition = position; - if (mOnFirstPage && mAllowFancy) { + if (!mAllowFancy) return; + if (mOnFirstPage) { mQuickQsPanel.setAlpha(1); mFirstPageAnimator.setPosition(position); - mFirstPageDelayedAnimator.setPosition(position); mTranslationYAnimator.setPosition(position); mTranslationXAnimator.setPosition(position); - if (mQQSTileHeightAnimator != null) { - mQQSTileHeightAnimator.setPosition(position); - } - if (mOtherTilesExpandAnimator != null) { - mOtherTilesExpandAnimator.setPosition(position); + if (mOtherFirstPageTilesHeightAnimator != null) { + mOtherFirstPageTilesHeightAnimator.setPosition(position); } } else { - mNonfirstPageAnimator.setPosition(position); - mNonfirstPageDelayedAnimator.setPosition(position); + mNonfirstPageAlphaAnimator.setPosition(position); } - if (mAllowFancy) { - mAllPagesDelayedAnimator.setPosition(position); - if (mBrightnessAnimator != null) { - mBrightnessAnimator.setPosition(position); - } - if (mQQSFooterActionsAnimator != null) { - mQQSFooterActionsAnimator.setPosition(position); + for (int i = 0; i < mNonFirstPageQSAnimators.size(); i++) { + Pair pair = mNonFirstPageQSAnimators.valueAt(i); + if (pair != null) { + pair.first.setPosition(position); + pair.second.setPosition(position); } } + if (mQQSTileHeightAnimator != null) { + mQQSTileHeightAnimator.setPosition(position); + } + mQSTileLayoutTranslatorAnimator.setPosition(position); + mQQSTranslationYAnimator.setPosition(position); + mAllPagesDelayedAnimator.setPosition(position); + if (mBrightnessAnimator != null) { + mBrightnessAnimator.setPosition(position); + } + if (mQQSFooterActionsAnimator != null) { + mQQSFooterActionsAnimator.setPosition(position); + } } @Override @@ -611,8 +742,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if (mQQSTileHeightAnimator != null) { mQQSTileHeightAnimator.resetViewsHeights(); } - if (mOtherTilesExpandAnimator != null) { - mOtherTilesExpandAnimator.resetViewsHeights(); + if (mOtherFirstPageTilesHeightAnimator != null) { + mOtherFirstPageTilesHeightAnimator.resetViewsHeights(); + } + for (int i = 0; i < mNonFirstPageQSAnimators.size(); i++) { + mNonFirstPageQSAnimators.valueAt(i).first.resetViewsHeights(); } final int N2 = mAnimatedQsViews.size(); for (int i = 0; i < N2; i++) { @@ -623,7 +757,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - mExecutor.execute(mUpdateAnimators); + boolean actualChange = + left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom; + if (actualChange) mExecutor.execute(mUpdateAnimators); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index e82e9d284bdd7..bb8a1e8e22a4b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -503,6 +503,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca isTransitioningToFullShade ? progress : mSquishinessFraction); } + @Override + public int getHeightDiff() { + return mQSPanelScrollView.getBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); + } + @Override public void setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction) { @@ -537,8 +543,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca boolean fullyExpanded = expansion == 1; boolean fullyCollapsed = expansion == 0.0f; - int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom() - + mHeader.getPaddingBottom(); + int heightDiff = getHeightDiff(); float panelTranslationY = translationScaleY * heightDiff; // Let the views animate their contents correctly by giving them the necessary context. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 20c0fdd7de890..6ab1c1358bf14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -106,6 +107,7 @@ public class QSPanel extends LinearLayout implements Tunable { protected QSTileLayout mTileLayout; private float mSquishinessFraction = 1f; private final ArrayMap mChildrenLayoutTop = new ArrayMap<>(); + private final Rect mClippingRect = new Rect(); public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -133,8 +135,7 @@ public class QSPanel extends LinearLayout implements Tunable { mHorizontalContentContainer = new RemeasuringLinearLayout(mContext); mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL); - mHorizontalContentContainer.setClipChildren(true); - mHorizontalContentContainer.setClipToPadding(false); + setHorizontalContentContainerClipping(); LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); int marginSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_padding); @@ -148,6 +149,23 @@ public class QSPanel extends LinearLayout implements Tunable { } } + protected void setHorizontalContentContainerClipping() { + mHorizontalContentContainer.setClipChildren(true); + mHorizontalContentContainer.setClipToPadding(false); + // Don't clip on the top, that way, secondary pages tiles can animate up + mHorizontalContentContainer.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (left != oldLeft || right != oldRight || bottom != oldBottom) { + mClippingRect.left = left; + mClippingRect.right = right; + mClippingRect.bottom = bottom; + mHorizontalContentContainer.setClipBounds(mClippingRect); + } + }); + mClippingRect.top = -1000; + mHorizontalContentContainer.setClipBounds(mClippingRect); + } + /** * Add brightness view above the tile layout. * diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index d470fa242f713..ff9790c3595f1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -45,6 +45,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -258,6 +259,15 @@ public abstract class QSPanelControllerBase extends ViewContr return null; } + QSTileView getTileView(String spec) { + for (QSPanelControllerBase.TileRecord r : mRecords) { + if (Objects.equals(r.tile.getTileSpec(), spec)) { + return r.tileView; + } + } + return null; + } + private String getTilesSpecs() { return mRecords.stream() .map(tileRecord -> tileRecord.tile.getTileSpec()) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 613e7f87371c5..f5ae019c20aba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -46,11 +46,9 @@ public class QuickQSPanel extends QSPanel { } @Override - void initialize() { - super.initialize(); - if (mHorizontalContentContainer != null) { - mHorizontalContentContainer.setClipChildren(false); - } + protected void setHorizontalContentContainerClipping() { + mHorizontalContentContainer.setClipToPadding(false); + mHorizontalContentContainer.setClipChildren(false); } @Override