From 13519e44029022db37743e9340750a6117f32729 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 14 Sep 2021 13:59:07 -0700 Subject: [PATCH] Significantly reduce gesture feedback when swiping up to home screen. - Uses overscroll damping logic to reduce the velocity - The start to target rect interpolation can be from the start, center, or bottom of the rect depending on where the item is on the workspace. This reduces the amount of distance needed to travel between, which helps further reduce gesture feedback. Bug: 173107751 Test: test closing app that is on: - top row of home screen - middle of home screen - in hotseat Change-Id: I055dd61ca3491807109ff2f6c501bf710c8d340f --- .../launcher3/QuickstepTransitionManager.java | 3 +- .../quickstep/LauncherSwipeHandlerV2.java | 6 + .../quickstep/SwipeUpAnimationLogic.java | 2 +- .../quickstep/util/RectFSpringAnim.java | 138 ++++++++++++++---- .../quickstep/util/RectFSpringAnim2.java | 2 +- .../util/SwipePipToHomeAnimator.java | 2 +- res/values/config.xml | 3 +- .../launcher3/anim/FlingSpringAnim.java | 12 +- 8 files changed, 131 insertions(+), 37 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index b8d00bd10c..25371348af 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -1307,7 +1307,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } final RectF startRect = new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); - RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mLauncher); + RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mLauncher, + mDeviceProfile); // Hook up floating views to the closing window animators. if (floatingIconView != null) { diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index dc22a61dac..0181cd7018 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -137,6 +137,12 @@ public class LauncherSwipeHandlerV2 extends // opaque until it is ready. private boolean mIsFloatingIconReady = false; + @Nullable + @Override + protected View getViewIgnoredInWorkspaceRevealAnimation() { + return workspaceView; + } + @Override public RectF getWindowTargetRect() { super.getWindowTargetRect(); diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index d1880189cc..2fee945381 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -295,7 +295,7 @@ public abstract class SwipeUpAnimationLogic implements taskViewSimulator.getCurrentCornerRadius(), homeAnimationFactory.getEndRadius(cropRectF)); } else { - anim = new RectFSpringAnim(startRect, targetRect, mContext); + anim = new RectFSpringAnim(startRect, targetRect, mContext, mDp); } homeAnimationFactory.setAnimation(anim); diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java index 02ec68a8d9..158fba9b31 100644 --- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java @@ -15,24 +15,31 @@ */ package com.android.quickstep.util; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.animation.Animator; import android.content.Context; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.FlingSpringAnim; +import com.android.launcher3.touch.OverScroll; import com.android.launcher3.util.DynamicResource; import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck; import com.android.systemui.plugins.ResourceProvider; +import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.List; @@ -94,7 +101,6 @@ public class RectFSpringAnim extends ReleaseCheck { private float mCurrentCenterX; private float mCurrentY; // If true, tracking the bottom of the rects, else tracking the top. - private boolean mTrackingBottomY; private float mCurrentScaleProgress; private FlingSpringAnim mRectXAnim; private FlingSpringAnim mRectYAnim; @@ -105,20 +111,68 @@ public class RectFSpringAnim extends ReleaseCheck { private boolean mRectScaleAnimEnded; private float mMinVisChange; - private float mYOvershoot; + private int mMaxVelocityPxPerS; - public RectFSpringAnim(RectF startRect, RectF targetRect, Context context) { + /** + * Indicates which part of the start & target rects we are interpolating between. + */ + public static final int TRACKING_TOP = 0; + public static final int TRACKING_CENTER = 1; + public static final int TRACKING_BOTTOM = 2; + + @Retention(SOURCE) + @IntDef(value = {TRACKING_TOP, + TRACKING_CENTER, + TRACKING_BOTTOM}) + public @interface Tracking{} + + @Tracking + public final int mTracking; + + public RectFSpringAnim(RectF startRect, RectF targetRect, Context context, + @Nullable DeviceProfile deviceProfile) { mStartRect = startRect; mTargetRect = targetRect; mCurrentCenterX = mStartRect.centerX(); - mTrackingBottomY = startRect.bottom < targetRect.bottom; - mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top; - ResourceProvider rp = DynamicResource.provider(context); mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change); - mYOvershoot = rp.getDimension(R.dimen.swipe_up_y_overshoot); + mMaxVelocityPxPerS = (int) rp.getDimension(R.dimen.swipe_up_max_velocity); setCanRelease(true); + + if (deviceProfile == null) { + mTracking = startRect.bottom < targetRect.bottom + ? TRACKING_BOTTOM + : TRACKING_TOP; + } else { + int heightPx = deviceProfile.heightPx; + Rect padding = deviceProfile.workspacePadding; + + final float topThreshold = heightPx / 3f; + final float bottomThreshold = deviceProfile.heightPx - padding.bottom; + + if (targetRect.bottom > bottomThreshold) { + mTracking = TRACKING_BOTTOM; + } else if (targetRect.top < topThreshold) { + mTracking = TRACKING_TOP; + } else { + mTracking = TRACKING_CENTER; + } + } + + mCurrentY = getTrackedYFromRect(mStartRect); + } + + private float getTrackedYFromRect(RectF rect) { + switch (mTracking) { + case TRACKING_TOP: + return rect.top; + case TRACKING_BOTTOM: + return rect.bottom; + case TRACKING_CENTER: + default: + return rect.centerY(); + } } public void onTargetPositionChanged() { @@ -127,10 +181,22 @@ public class RectFSpringAnim extends ReleaseCheck { } if (mRectYAnim != null) { - if (mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.bottom) { - mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom); - } else if (!mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.top) { - mRectYAnim.updatePosition(mCurrentY, mTargetRect.top); + switch (mTracking) { + case TRACKING_TOP: + if (mRectYAnim.getTargetPosition() != mTargetRect.top) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.top); + } + break; + case TRACKING_BOTTOM: + if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom); + } + break; + case TRACKING_CENTER: + if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY()); + } + break; } } } @@ -159,22 +225,29 @@ public class RectFSpringAnim extends ReleaseCheck { maybeOnEnd(); }); + // We dampen the user velocity here to keep the natural feeling and to prevent the + // rect from straying too from a linear path. + final float xVelocityPxPerS = velocityPxPerMs.x * 1000; + final float yVelocityPxPerS = velocityPxPerMs.y * 1000; + final float dampedXVelocityPxPerS = OverScroll.dampedScroll( + Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS); + final float dampedYVelocityPxPerS = OverScroll.dampedScroll( + Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS); + float startX = mCurrentCenterX; float endX = mTargetRect.centerX(); float minXValue = Math.min(startX, endX); float maxXValue = Math.max(startX, endX); - mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX, - velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener); - float startVelocityY = velocityPxPerMs.y * 1000; - // Scale the Y velocity based on the initial velocity to tune the curves. - float springVelocityFactor = 0.1f + 0.9f * Math.abs(startVelocityY) / 20000.0f; + mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX, + dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, onXEndListener); + float startY = mCurrentY; - float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top; - float minYValue = Math.min(startY, endY - mYOvershoot); + float endY = getTrackedYFromRect(mTargetRect); + float minYValue = Math.min(startY, endY); float maxYValue = Math.max(startY, endY); - mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, startVelocityY, - mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener); + mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, dampedYVelocityPxPerS, + mMinVisChange, minYValue, maxYValue, onYEndListener); float minVisibleChange = Math.abs(1f / mStartRect.height()); ResourceProvider rp = DynamicResource.provider(context); @@ -234,12 +307,25 @@ public class RectFSpringAnim extends ReleaseCheck { mTargetRect.width()); float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(), mTargetRect.height()); - if (mTrackingBottomY) { - mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight, - mCurrentCenterX + currentWidth / 2, mCurrentY); - } else { - mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY, - mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight); + switch (mTracking) { + case TRACKING_TOP: + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, + mCurrentY, + mCurrentCenterX + currentWidth / 2, + mCurrentY + currentHeight); + break; + case TRACKING_BOTTOM: + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, + mCurrentY - currentHeight, + mCurrentCenterX + currentWidth / 2, + mCurrentY); + break; + case TRACKING_CENTER: + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, + mCurrentY - currentHeight / 2, + mCurrentCenterX + currentWidth / 2, + mCurrentY + currentHeight / 2); + break; } for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { onUpdateListener.onUpdate(null, mCurrentRect, mCurrentScaleProgress); diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java index c331a136d9..cb35809de9 100644 --- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java @@ -132,7 +132,7 @@ public class RectFSpringAnim2 extends RectFSpringAnim { public RectFSpringAnim2(RectF startRect, RectF targetRect, Context context, float startRadius, float endRadius) { - super(startRect, targetRect, context); + super(startRect, targetRect, context, null); mStartRect = startRect; mTargetRect = targetRect; diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java index d4191fe200..a30216cdff 100644 --- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java +++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java @@ -116,7 +116,7 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim { @NonNull Rect destinationBoundsTransformed, int cornerRadius, @NonNull View view) { - super(startBounds, new RectF(destinationBoundsTransformed), context); + super(startBounds, new RectF(destinationBoundsTransformed), context, null); mTaskId = taskId; mComponentName = componentName; mLeash = leash; diff --git a/res/values/config.xml b/res/values/config.xml index 7c681a88ac..6fdb4de1fe 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -184,8 +184,8 @@ 18dp - 10dp -60dp + 7.619dp @dimen/swipe_up_duration @@ -201,6 +201,7 @@ @dimen/swipe_up_launcher_alpha_max_progress @dimen/swipe_up_rect_2_y_stiffness_low_swipe_multiplier @dimen/swipe_up_low_swipe_duration_multiplier + @dimen/swipe_up_max_velocity @dimen/c1_a @dimen/c1_b diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java index 6ea38ec8ab..51eab4c386 100644 --- a/src/com/android/launcher3/anim/FlingSpringAnim.java +++ b/src/com/android/launcher3/anim/FlingSpringAnim.java @@ -40,8 +40,8 @@ public class FlingSpringAnim { private float mTargetPosition; public FlingSpringAnim(K object, Context context, FloatPropertyCompat property, - float startPosition, float targetPosition, float startVelocity, float minVisChange, - float minValue, float maxValue, float springVelocityFactor, + float startPosition, float targetPosition, float startVelocityPxPerS, + float minVisChange, float minValue, float maxValue, OnAnimationEndListener onEndListener) { ResourceProvider rp = DynamicResource.provider(context); float damping = rp.getFloat(R.dimen.swipe_up_rect_xy_damping_ratio); @@ -53,19 +53,19 @@ public class FlingSpringAnim { // Have the spring pull towards the target if we've slowed down too much before // reaching it. .setMinimumVisibleChange(minVisChange) - .setStartVelocity(startVelocity) + .setStartVelocity(startVelocityPxPerS) .setMinValue(minValue) .setMaxValue(maxValue); mTargetPosition = targetPosition; // We are already past the fling target, so skip it to avoid losing a frame of the spring. - mSkipFlingAnim = startPosition <= minValue && startVelocity < 0 - || startPosition >= maxValue && startVelocity > 0; + mSkipFlingAnim = startPosition <= minValue && startVelocityPxPerS < 0 + || startPosition >= maxValue && startVelocityPxPerS > 0; mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> { mSpringAnim = new SpringAnimation(object, property) .setStartValue(value) - .setStartVelocity(velocity * springVelocityFactor) + .setStartVelocity(velocity) .setSpring(new SpringForce(mTargetPosition) .setStiffness(stiffness) .setDampingRatio(damping));