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));