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 78b3d4f479..8c376440f7 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); @@ -2678,6 +2717,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); @@ -2757,22 +2884,25 @@ public abstract class RecentsView() { @Override public void accept(Boolean success) { @@ -2831,49 +2963,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); @@ -2938,7 +3089,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() {