From 96dad8b03931853eff3daccf74f82846a7142052 Mon Sep 17 00:00:00 2001 From: Pat Manning Date: Fri, 6 Aug 2021 14:24:56 +0000 Subject: [PATCH] Animate scrolling grid into place when there is a gap between last tasks and clear all. - Stagger animation starts - Adds a grid translation property to TaskView for animating these translations. Also fixes an issue where the gap between clear all and focused task is greater than spacing between grid tasks and clear all. Test: manual Fix: 188793333 Change-Id: Ib2ba8b1b84dc63c4ba186bd0b9b4962d3c66ce5a --- .../quickstep/views/ClearAllButton.java | 23 +- .../android/quickstep/views/RecentsView.java | 241 +++++++++++++++--- .../com/android/quickstep/views/TaskView.java | 26 +- 3 files changed, 244 insertions(+), 46 deletions(-) diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java index b9a90062b7..22c87b0919 100644 --- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java +++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java @@ -39,10 +39,24 @@ public class ClearAllButton extends Button { } }; + public static final FloatProperty DISMISS_ALPHA = + new FloatProperty("dismissAlpha") { + @Override + public Float get(ClearAllButton view) { + return view.mDismissAlpha; + } + + @Override + public void setValue(ClearAllButton view, float v) { + view.setDismissAlpha(v); + } + }; + private final StatefulActivity mActivity; private float mScrollAlpha = 1; private float mContentAlpha = 1; private float mVisibilityAlpha = 1; + private float mDismissAlpha = 1; private float mFullscreenProgress = 1; private float mGridProgress = 1; @@ -97,6 +111,13 @@ public class ClearAllButton extends Button { } } + public void setDismissAlpha(float alpha) { + if (mDismissAlpha != alpha) { + mDismissAlpha = alpha; + updateAlpha(); + } + } + public void onRecentsViewScroll(int scroll, boolean gridEnabled) { RecentsView recentsView = getRecentsView(); if (recentsView == null) { @@ -123,7 +144,7 @@ public class ClearAllButton extends Button { } private void updateAlpha() { - final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha; + final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha; setAlpha(alpha); setClickable(Math.min(alpha, 1) == 1); } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index aeb10aaff6..dff82cc320 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -29,6 +29,7 @@ import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; +import static com.android.launcher3.Utilities.boundToRange; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; @@ -49,6 +50,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; +import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; @@ -359,6 +361,7 @@ public abstract class RecentsView= bottomRowIdArray.size() ? topRowIdArray.get( + topRowIdArray.size() - 1) : bottomRowIdArray.get(bottomRowIdArray.size() - 1); + return getTaskViewFromTaskViewId(lastTaskViewId); + } + + private int getSnapToLastTaskScrollDiff() { + // Snap to a position where ClearAll is just invisible. + int screenStart = mOrientationHandler.getPrimaryScroll(this); + int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton); + int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton)); + int targetScroll = clearAllScroll + (mIsRtl ? clearAllWidth : -clearAllWidth); + return screenStart - targetScroll; + } + + private int getSnapToFocusedTaskScrollDiff(boolean isClearAllHidden) { + int screenStart = mOrientationHandler.getPrimaryScroll(this); + int targetScroll = getScrollForPage(indexOfChild(getFocusedTaskView())); + if (!isClearAllHidden) { + int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton); + int taskGridHorizontalDiff = mLastComputedTaskSize.right - mLastComputedGridSize.right; + int clearAllFocusScrollDiff = taskGridHorizontalDiff - clearAllWidth; + targetScroll += mIsRtl ? clearAllFocusScrollDiff : -clearAllFocusScrollDiff; + } + return screenStart - targetScroll; + } + private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) { int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment( showAsFullscreen(), showAsGrid()); @@ -1083,7 +1123,6 @@ public abstract class RecentsView 0) { setSwipeDownShouldLaunchApp(true); @@ -2676,6 +2715,94 @@ public abstract class RecentsView bottomGridRowSize; + boolean bottomRowLonger = bottomGridRowSize > topGridRowSize; + boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId); + boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed; + float gapWidth = 0; + if ((topRowLonger && dismissedTaskFromTop) + || (bottomRowLonger && dismissedTaskFromBottom)) { + gapWidth = dismissedTaskWidth; + } else if ((topRowLonger && nextFocusedTaskFromTop) + || (bottomRowLonger && !nextFocusedTaskFromTop)) { + gapWidth = nextFocusedTaskWidth; + } + if (gapWidth > 0) { + if (taskCount > 2) { + // Compensate the removed gap. + longGridRowWidthDiff += mIsRtl ? -gapWidth : gapWidth; + if (isClearAllHidden) { + // If ClearAllButton isn't fully shown, snap to the last task. + longGridRowWidthDiff += getSnapToLastTaskScrollDiff(); + } + } else { + // If only focused task will be left, snap to focused task instead. + longGridRowWidthDiff += getSnapToFocusedTaskScrollDiff(isClearAllHidden); + } + } + + // If we need to animate the grid to compensate the clear all gap, we split the second + // half of the dismiss pending animation (in which the non-dismissed tasks slide into + // place) in half again, making the first quarter the existing non-dismissal sliding + // and the second quarter this new animation of gap filling. This is due to the fact + // that PendingAnimation is a single animation, not a sequence of animations, so we + // fake it using interpolation. + if (longGridRowWidthDiff != 0) { + closeGapBetweenClearAll = true; + // Stagger the offsets of each additional task for a delayed animation. We use + // half here as this animation is half of half of an animation (1/4th). + float halfAdditionalDismissTranslationOffset = + (0.5f * ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET); + dismissTranslationInterpolationEnd = Utilities.boundToRange( + END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET + + (taskCount - 1) * halfAdditionalDismissTranslationOffset, + END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1); + for (int i = 0; i < taskCount; i++) { + TaskView taskView = getTaskViewAt(i); + anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff, + clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1)); + dismissTranslationInterpolationEnd = Utilities.boundToRange( + dismissTranslationInterpolationEnd + - halfAdditionalDismissTranslationOffset, + END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1); + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile + && taskView.isRunningTask()) { + anim.addOnFrameCallback(() -> { + runActionOnRemoteHandles( + remoteTargetHandle -> + remoteTargetHandle.mTaskViewSimulator + .taskPrimaryTranslation.value = + TaskView.GRID_END_TRANSLATION_X.get(taskView)); + redrawLiveTile(); + }); + } + } + + // Change alpha of clear all if translating grid to hide it + if (isClearAllHidden) { + anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mClearAllButton.setDismissAlpha(1); + } + }); + } + } + } + int distanceFromDismissedTask = 0; for (int i = 0; i < count; i++) { View child = getChildAt(i); @@ -2755,22 +2882,25 @@ public abstract class RecentsView() { @Override public void accept(Boolean success) { @@ -2829,49 +2961,68 @@ public abstract class RecentsView 2) { + pageToSnapTo = indexOfChild(mClearAllButton); + if (isClearAllHidden) { + int clearAllWidth = mOrientationHandler.getPrimarySize( + mClearAllButton); + mCurrentPageScrollDiff = + isRtl() ? clearAllWidth : -clearAllWidth; } - } else { - int snappedTaskViewId = snappedTaskView.getTaskViewId(); - boolean isSnappedTaskInTopRow = mTopRowIdSet.contains( - snappedTaskViewId); - IntArray taskViewIdArray = - isSnappedTaskInTopRow ? getTopRowIdArray() - : getBottomRowIdArray(); - int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId); - taskViewIdArray.removeValue(dismissedTaskViewId); - if (snappedIndex < taskViewIdArray.size()) { - taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex); - } else if (snappedIndex == taskViewIdArray.size()) { - // If the snapped task is the last item from the dismissed row, - // snap to the same column in the other grid row - IntArray inverseRowTaskViewIdArray = - isSnappedTaskInTopRow ? getBottomRowIdArray() - : getTopRowIdArray(); - if (snappedIndex < inverseRowTaskViewIdArray.size()) { - taskViewIdToSnapTo = inverseRowTaskViewIdArray.get( - snappedIndex); + } else if (isClearAllHidden) { + // Snap to focused task if clear all is hidden. + pageToSnapTo = 0; + } + } else { + // Get the id of the task view we will snap to based on the current + // page's relative position as the order of indices change over time due + // to dismissals. + TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage); + if (snappedTaskView != null) { + if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) { + if (finalNextFocusedTaskView != null) { + taskViewIdToSnapTo = + finalNextFocusedTaskView.getTaskViewId(); + } else { + taskViewIdToSnapTo = mFocusedTaskViewId; + } + } else { + int snappedTaskViewId = snappedTaskView.getTaskViewId(); + boolean isSnappedTaskInTopRow = mTopRowIdSet.contains( + snappedTaskViewId); + IntArray taskViewIdArray = + isSnappedTaskInTopRow ? getTopRowIdArray() + : getBottomRowIdArray(); + int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId); + taskViewIdArray.removeValue(dismissedTaskViewId); + if (snappedIndex < taskViewIdArray.size()) { + taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex); + } else if (snappedIndex == taskViewIdArray.size()) { + // If the snapped task is the last item from the + // dismissed row, + // snap to the same column in the other grid row + IntArray inverseRowTaskViewIdArray = + isSnappedTaskInTopRow ? getBottomRowIdArray() + : getTopRowIdArray(); + if (snappedIndex < inverseRowTaskViewIdArray.size()) { + taskViewIdToSnapTo = inverseRowTaskViewIdArray.get( + snappedIndex); + } } } } - } - int primaryScroll = mOrientationHandler.getPrimaryScroll(RecentsView.this); - int currentPageScroll = getScrollForPage(pageToSnapTo); - mCurrentPageScrollDiff = primaryScroll - currentPageScroll; + int primaryScroll = mOrientationHandler.getPrimaryScroll( + RecentsView.this); + int currentPageScroll = getScrollForPage(pageToSnapTo); + mCurrentPageScrollDiff = primaryScroll - currentPageScroll; + } } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) { - pageToSnapTo -= 1; + pageToSnapTo--; } removeViewInLayout(dismissedTaskView); mTopRowIdSet.remove(dismissedTaskViewId); @@ -2936,7 +3087,13 @@ public abstract class RecentsView GRID_END_TRANSLATION_X = + new FloatProperty("gridEndTranslationX") { + @Override + public void setValue(TaskView taskView, float v) { + taskView.setGridEndTranslationX(v); + } + + @Override + public Float get(TaskView taskView) { + return taskView.mGridEndTranslationX; + } + }; + public static final FloatProperty SNAPSHOT_SCALE = new FloatProperty("snapshotScale") { @Override @@ -381,6 +394,8 @@ public class TaskView extends FrameLayout implements Reusable { // The following grid translations scales with mGridProgress. private float mGridTranslationX; private float mGridTranslationY; + // The following grid translation is used to animate closing the gap between grid and clear all. + private float mGridEndTranslationX; // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick // switch. private float mNonGridTranslationX; @@ -950,8 +965,8 @@ public class TaskView extends FrameLayout implements Reusable { protected void resetViewTransforms() { // fullscreenTranslation and accumulatedTranslation should not be reset, as // resetViewTransforms is called during Quickswitch scrolling. - mDismissTranslationX = mTaskOffsetTranslationX = mTaskResistanceTranslationX = - mSplitSelectTranslationX = 0f; + mDismissTranslationX = mTaskOffsetTranslationX = + mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f; mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = mSplitSelectTranslationY = 0f; setSnapshotScale(1f); @@ -1162,6 +1177,11 @@ public class TaskView extends FrameLayout implements Reusable { return mGridTranslationY; } + private void setGridEndTranslationX(float gridEndTranslationX) { + mGridEndTranslationX = gridEndTranslationX; + applyTranslationX(); + } + public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { float scrollAdjustment = 0; if (gridEnabled) { @@ -1191,7 +1211,7 @@ public class TaskView extends FrameLayout implements Reusable { private void applyTranslationX() { setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX - + mSplitSelectTranslationX + getPersistentTranslationX()); + + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX()); } private void applyTranslationY() {