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
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index b8d00bd..2537134 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1307,7 +1307,8 @@
}
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 dc22a61..0181cd7 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -137,6 +137,12 @@
// 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 d188018..2fee945 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -295,7 +295,7 @@
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 02ec68a..158fba9 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 c331a13..cb35809 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java
@@ -132,7 +132,7 @@
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 d4191fe..a30216c 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -116,7 +116,7 @@
@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 7c681a8..6fdb4de 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -184,8 +184,8 @@
<!-- Swipe up to home related -->
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
- <dimen name="swipe_up_y_overshoot">10dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
+ <dimen name="swipe_up_max_velocity">7.619dp</dimen>
<array name="dynamic_resources">
<item>@dimen/swipe_up_duration</item>
@@ -201,6 +201,7 @@
<item>@dimen/swipe_up_launcher_alpha_max_progress</item>
<item>@dimen/swipe_up_rect_2_y_stiffness_low_swipe_multiplier</item>
<item>@dimen/swipe_up_low_swipe_duration_multiplier</item>
+ <item>@dimen/swipe_up_max_velocity</item>
<item>@dimen/c1_a</item>
<item>@dimen/c1_b</item>
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
index 6ea38ec..51eab4c 100644
--- a/src/com/android/launcher3/anim/FlingSpringAnim.java
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -40,8 +40,8 @@
private float mTargetPosition;
public <K> FlingSpringAnim(K object, Context context, FloatPropertyCompat<K> 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 @@
// 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));