diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 68bad5c85f..853ac7415c 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -32,8 +32,11 @@ 50dp - + 0.7 + + 0.46 48dp diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 6698600f90..4752225ec6 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -30,6 +30,7 @@ import static com.android.launcher3.BaseActivity.EVENT_DESTROYED; import static com.android.launcher3.BaseActivity.EVENT_STARTED; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; +import static com.android.launcher3.Flags.enableGridOnlyOverview; import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD; import static com.android.launcher3.PagedView.INVALID_PAGE; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; @@ -2561,9 +2562,11 @@ public abstract class AbsSwipeUpHandler, } float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage())); + Rect carouselTaskSize = enableGridOnlyOverview() + ? mRecentsView.getLastComputedCarouselTaskSize() + : mRecentsView.getLastComputedTaskSize(); int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue( - mRecentsView.getLastComputedTaskSize().width(), - mRecentsView.getLastComputedTaskSize().height()); + carouselTaskSize.width(), carouselTaskSize.height()); maxScrollOffset += mRecentsView.getPageSpacing(); float maxScaleProgress = diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index b89d20ca97..879312d032 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -261,6 +261,23 @@ public abstract class BaseActivityInterface { - if (t < startResist) { + if (t <= startResist) { return t; } - if (t > stopResist) { + if (t >= stopResist) { return maxResist; } float resistProgress = Utilities.getProgress(t, startResist, stopResist); @@ -304,7 +306,9 @@ public class AnimatorControllerWithResistance { resistanceParams = recentsOrientedState.getActivityInterface().allowAllAppsFromOverview() ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET - : RecentsResistanceParams.FROM_APP_TABLET; + : enableGridOnlyOverview() + ? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY + : RecentsResistanceParams.FROM_APP_TABLET; } else { resistanceParams = recentsOrientedState.getActivityInterface().allowAllAppsFromOverview() diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 0bb6b23dca..1152de2750 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -38,10 +38,12 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.RemoteAnimationTarget; +import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.app.animation.Interpolators; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; @@ -76,6 +78,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final Rect mTaskRect = new Rect(); private final Rect mFullTaskSize = new Rect(); + private final Rect mCarouselTaskSize = new Rect(); + private PointF mPivotOverride = null; private final PointF mPivot = new PointF(); private DeviceProfile mDp; @StagePosition @@ -95,6 +99,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat(); public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat(); + // Carousel properties + public final AnimatedFloat carouselScale = new AnimatedFloat(); + public final AnimatedFloat carouselPrimaryTranslation = new AnimatedFloat(); + public final AnimatedFloat carouselSecondaryTranslation = new AnimatedFloat(); + // RecentsView properties public final AnimatedFloat recentsViewScale = new AnimatedFloat(); public final AnimatedFloat fullScreenProgress = new AnimatedFloat(); @@ -109,9 +118,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private Boolean mDrawsBelowRecents = null; private boolean mIsGridTask; private boolean mIsDesktopTask; + private boolean mScaleToCarouselTaskSize = false; private int mTaskRectTranslationX; private int mTaskRectTranslationY; - private int mPivotOffsetX; public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) { mContext = context; @@ -124,6 +133,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mOrientationStateId = mOrientationState.getStateId(); Resources resources = context.getResources(); mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources); + carouselScale.value = 1f; } /** @@ -149,6 +159,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mOrientationState.getOrientationHandler()); } + if (enableGridOnlyOverview()) { + mSizeStrategy.calculateCarouselTaskSize(mContext, mDp, mCarouselTaskSize, + mOrientationState.getOrientationHandler()); + } + if (mSplitBounds != null) { // The task rect changes according to the staged split task sizes, but recents // fullscreen scale and pivot remains the same since the task fits into the existing @@ -193,9 +208,18 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } // Copy mFullTaskSize instead of updating it directly so it could be reused next time // without recalculating - Rect scaleRect = new Rect(mFullTaskSize); - scaleRect.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY); - return mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot); + Rect scaleRect = new Rect(); + if (mScaleToCarouselTaskSize) { + scaleRect.set(mCarouselTaskSize); + } else { + scaleRect.set(mFullTaskSize); + } + scaleRect.offset(mTaskRectTranslationX, mTaskRectTranslationY); + float scale = mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot); + if (mPivotOverride != null) { + mPivot.set(mPivotOverride); + } + return scale; } /** @@ -278,14 +302,64 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { /** * Adds animation for all the components corresponding to transition from an app to overview. */ - public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) { + public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) { pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator); - if (enableGridOnlyOverview() && mDp.isTablet) { - int translationXToMiddle = mDp.widthPx / 2 - mFullTaskSize.centerX(); - taskPrimaryTranslation.value = translationXToMiddle; - mPivotOffsetX = translationXToMiddle; + float fullScreenScale; + if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) { + // Move pivot to top right edge of the screen, to avoid task scaling down in opposite + // direction of app window movement, otherwise the animation will wiggle left and right. + // Also translate the app window to top right edge of the screen to simplify + // calculations. + taskPrimaryTranslation.value = mIsRecentsRtl + ? mDp.widthPx - mFullTaskSize.right + : -mFullTaskSize.left; + taskSecondaryTranslation.value = -mFullTaskSize.top; + mPivotOverride = new PointF(mIsRecentsRtl ? mDp.widthPx : 0, 0); + + // Scale down to the carousel and use the carousel Rect to calculate fullScreenScale. + mScaleToCarouselTaskSize = true; + carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width(); + fullScreenScale = getFullScreenScale(); + + float carouselPrimaryTranslationTarget = mIsRecentsRtl + ? mCarouselTaskSize.right - mDp.widthPx + : mCarouselTaskSize.left; + float carouselSecondaryTranslationTarget = mCarouselTaskSize.top; + + // Expected carousel position's center is in the middle, and invariant of + // recentsViewScale. + float exceptedCarouselCenterX = mCarouselTaskSize.centerX(); + // Animating carousel translations linearly will result in a curved path, therefore + // we'll need to calculate the expected translation at each recentsView scale. Luckily + // primary and secondary follow the same translation, and primary is used here due to + // it being simpler. + Interpolator carouselTranslationInterpolator = t -> { + // recentsViewScale is calculated rather than using recentsViewScale.value, so that + // this interpolator works independently even if recentsViewScale don't animate. + float recentsViewScale = + Utilities.mapToRange(t, 0, 1, fullScreenScale, 1, Interpolators.LINEAR); + // Without the translation, the app window will animate from fullscreen into top + // right corner. + float expectedTaskCenterX = mIsRecentsRtl + ? mDp.widthPx - mCarouselTaskSize.width() * recentsViewScale / 2f + : mCarouselTaskSize.width() * recentsViewScale / 2f; + // Calculate the expected translation, then work back the animatedFraction that + // results in this value. + float carouselPrimaryTranslation = + (exceptedCarouselCenterX - expectedTaskCenterX) / recentsViewScale; + return carouselPrimaryTranslation / carouselPrimaryTranslationTarget; + }; + + // Use addAnimatedFloat so this animation can later be canceled and animate to a + // different value in RecentsView.onPrepareGestureEndAnimation. + pa.addAnimatedFloat(carouselPrimaryTranslation, 0, carouselPrimaryTranslationTarget, + carouselTranslationInterpolator); + pa.addAnimatedFloat(carouselSecondaryTranslation, 0, carouselSecondaryTranslationTarget, + carouselTranslationInterpolator); + } else { + fullScreenScale = getFullScreenScale(); } - pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator); + pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, fullScreenScale, 1, interpolator); } /** @@ -382,7 +456,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1); mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value, - /* taskViewScale= */1f); + carouselScale.value); // Apply thumbnail matrix float taskWidth = mTaskRect.width(); @@ -396,6 +470,13 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { taskPrimaryTranslation.value); mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, taskSecondaryTranslation.value); + + mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y); + mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE, + carouselPrimaryTranslation.value); + mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, + carouselSecondaryTranslation.value); + mOrientationState.getOrientationHandler().setPrimary( mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value); @@ -420,15 +501,18 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { return; } Log.d(TAG, "progress: " + fullScreenProgress + + " carouselScale: " + carouselScale.value + " recentsViewScale: " + recentsViewScale.value + " crop: " + mTmpCropRect + " radius: " + getCurrentCornerRadius() + " taskW: " + taskWidth + " H: " + taskHeight + " taskRect: " + mTaskRect + " taskPrimaryT: " + taskPrimaryTranslation.value + + " taskSecondaryT: " + taskSecondaryTranslation.value + + " carouselPrimaryT: " + carouselPrimaryTranslation.value + + " carouselSecondaryT: " + carouselSecondaryTranslation.value + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value - + " taskSecondaryT: " + taskSecondaryTranslation.value + " recentsScroll: " + recentsViewScroll.value + " pivot: " + mPivot ); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 9884d8dd1d..f6afaf0043 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -459,6 +459,7 @@ public abstract class RecentsView lastTaskScroll)) { pageScroll = lastTaskScroll; diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 5057c383e5..55da1601b6 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -23,6 +23,7 @@ import static android.widget.Toast.LENGTH_SHORT; import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.launcher3.Flags.enableCursorHoverStates; +import static com.android.launcher3.Flags.enableGridOnlyOverview; import static com.android.launcher3.Flags.enableOverviewIconMenu; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; @@ -1750,7 +1751,13 @@ public class TaskView extends FrameLayout implements Reusable { expectedHeight = boxHeight + thumbnailPadding; // Scale to to fit task Rect. - nonGridScale = taskWidth / (float) boxWidth; + if (enableGridOnlyOverview()) { + final Rect lastComputedCarouselTaskSize = + getRecentsView().getLastComputedCarouselTaskSize(); + nonGridScale = lastComputedCarouselTaskSize.width() / (float) taskWidth; + } else { + nonGridScale = taskWidth / (float) boxWidth; + } // Align to top of task Rect. boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f; diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java index 2f3fa63321..b414ab6e35 100644 --- a/src/com/android/launcher3/anim/AnimatedFloat.java +++ b/src/com/android/launcher3/anim/AnimatedFloat.java @@ -109,6 +109,13 @@ public class AnimatedFloat { public void cancelAnimation() { if (mValueAnimator != null) { mValueAnimator.cancel(); + // Clears the property values, so further ObjectAnimator#setCurrentFraction from e.g. + // AnimatorPlaybackController calls would do nothing. The null check is necessary to + // avoid mValueAnimator being set to null in onAnimationEnd. + if (mValueAnimator != null) { + mValueAnimator.setValues(); + mValueAnimator = null; + } } } diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 586beb2d5e..e58890f7ca 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -83,6 +83,20 @@ public class PendingAnimation extends AnimatedPropertySetter { add(anim); } + /** + * Add an {@link AnimatedFloat} to the animation. + *

+ * Different from {@link #addFloat}, this method use animator provided by + * {@link AnimatedFloat#animateToValue}, which tracks the animator inside the AnimatedFloat, + * allowing the animation to be canceled and animate again from AnimatedFloat side. + */ + public void addAnimatedFloat(AnimatedFloat target, float from, float to, + TimeInterpolator interpolator) { + Animator anim = target.animateToValue(from, to); + anim.setInterpolator(interpolator); + add(anim); + } + /** If trace is enabled, add counter to trace animation progress. */ public void logAnimationProgressToTrace(String counterName) { if (Trace.isEnabled()) {