Merge "Polish the back to home gesture animation to match the spec." into tm-dev am: cf80645111
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/17646341
Change-Id: I15123f319e79ae994a3b5b31f64596f29194d0f9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 36c9d13..5b912ad 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -244,8 +244,7 @@
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
- mBackAnimationController = new LauncherBackAnimationController(
- mDeviceProfile, mLauncher, this);
+ mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
Resources res = mLauncher.getResources();
mContentScale = res.getFloat(R.dimen.content_scale);
@@ -1441,6 +1440,10 @@
}
};
anim.addOnUpdateListener(runner);
+ } else {
+ // If no floating icon or widget is present, animate the to the default window
+ // target rect.
+ anim.addOnUpdateListener(new SpringAnimRunner(targets, targetRect, windowTargetBounds));
}
// Use a fixed velocity to start the animation.
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7abcbdb..cc79f4a 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -25,6 +25,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
@@ -33,11 +34,12 @@
import android.util.Pair;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.IOnBackInvokedCallback;
import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -62,34 +64,36 @@
*
*/
public class LauncherBackAnimationController {
- private static final int CANCEL_TRANSITION_DURATION = 150;
+ private static final int CANCEL_TRANSITION_DURATION = 233;
+ private static final float MIN_WINDOW_SCALE = 0.7f;
private static final String TAG = "LauncherBackAnimationController";
- private final DeviceProfile mDeviceProfile;
private final QuickstepTransitionManager mQuickstepTransitionManager;
private final Matrix mTransformMatrix = new Matrix();
- private final RectF mTargetRectF = new RectF();
- private final RectF mStartRectF = new RectF();
+ /** The window position at the beginning of the back animation. */
+ private final Rect mStartRect = new Rect();
+ /** The window position when the back gesture is cancelled. */
+ private final RectF mCancelRect = new RectF();
+ /** The current window position. */
private final RectF mCurrentRect = new RectF();
private final BaseQuickstepLauncher mLauncher;
private final int mWindowScaleMarginX;
- private final int mWindowScaleMarginY;
+ /** Max window translation in the Y axis. */
+ private final int mWindowMaxDeltaY;
private final float mWindowScaleEndCornerRadius;
private final float mWindowScaleStartCornerRadius;
+ private final Interpolator mCancelInterpolator;
+ private final PointF mInitialTouchPos = new PointF();
private RemoteAnimationTargetCompat mBackTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mSpringAnimationInProgress = false;
private boolean mAnimatorSetInProgress = false;
- @BackEvent.SwipeEdge
- private int mSwipeEdge;
private float mBackProgress = 0;
private boolean mBackInProgress = false;
public LauncherBackAnimationController(
- DeviceProfile deviceProfile,
BaseQuickstepLauncher launcher,
QuickstepTransitionManager quickstepTransitionManager) {
- mDeviceProfile = deviceProfile;
mLauncher = launcher;
mQuickstepTransitionManager = quickstepTransitionManager;
mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
@@ -100,8 +104,10 @@
mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_scale_x_margin);
- mWindowScaleMarginY = mLauncher.getResources().getDimensionPixelSize(
- R.dimen.swipe_back_window_scale_y_margin);
+ mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.swipe_back_window_max_delta_y);
+ mCancelInterpolator =
+ AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel);
}
/**
@@ -136,7 +142,7 @@
if (!mBackInProgress) {
startBack(backEvent);
} else {
- updateBackProgress(mBackProgress);
+ updateBackProgress(mBackProgress, backEvent);
}
}
@@ -145,11 +151,13 @@
}
private void resetPositionAnimated() {
- ValueAnimator cancelAnimator = ValueAnimator.ofFloat(mBackProgress, 0);
+ ValueAnimator cancelAnimator = ValueAnimator.ofFloat(0, 1);
+ mCancelRect.set(mCurrentRect);
cancelAnimator.setDuration(CANCEL_TRANSITION_DURATION);
+ cancelAnimator.setInterpolator(mCancelInterpolator);
cancelAnimator.addUpdateListener(
animation -> {
- updateBackProgress((float) animation.getAnimatedValue());
+ updateCancelProgress((float) animation.getAnimatedValue());
});
cancelAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -179,50 +187,70 @@
mTransaction.show(appTarget.leash).apply();
mTransaction.setAnimationTransaction();
mBackTarget = new RemoteAnimationTargetCompat(appTarget);
- mSwipeEdge = backEvent.getSwipeEdge();
- float screenWidth = mDeviceProfile.widthPx;
- float screenHeight = mDeviceProfile.heightPx;
- float targetHeight = screenHeight - 2 * mWindowScaleMarginY;
- float targetWidth = targetHeight * screenWidth / screenHeight;
- float left;
- if (mSwipeEdge == BackEvent.EDGE_LEFT) {
- left = screenWidth - targetWidth - mWindowScaleMarginX;
- } else {
- left = mWindowScaleMarginX;
- }
- float top = mWindowScaleMarginY;
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
// TODO(b/218916755): Offset start rectangle in multiwindow mode.
- mStartRectF.set(0, 0, screenWidth, screenHeight);
- mTargetRectF.set(left, top, targetWidth + left, targetHeight + top);
+ mStartRect.set(mBackTarget.windowConfiguration.getMaxBounds());
}
- private void updateBackProgress(float progress) {
+ private void updateBackProgress(float progress, BackEvent event) {
if (mBackTarget == null) {
return;
}
+ float screenWidth = mStartRect.width();
+ float screenHeight = mStartRect.height();
+ float dX = Math.abs(event.getTouchX() - mInitialTouchPos.x);
+ // The 'follow width' is the width of the window if it completely matches
+ // the gesture displacement.
+ float followWidth = screenWidth - dX;
+ // The 'progress width' is the width of the window if it strictly linearly interpolates
+ // to minimum scale base on progress.
+ float progressWidth = MathUtils.lerp(1, MIN_WINDOW_SCALE, progress) * screenWidth;
+ // The final width is derived from interpolating between the follow with and progress width
+ // using gesture progress.
+ float width = MathUtils.lerp(followWidth, progressWidth, progress);
+ float height = screenHeight / screenWidth * width;
+ float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight;
+ // Base the window movement in the Y axis on the touch movement in the Y axis.
+ float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * mWindowMaxDeltaY;
+ // Move the window along the Y axis.
+ float top = (screenHeight - height) * 0.5f + deltaY;
+ // Move the window along the X axis.
+ float left = event.getSwipeEdge() == BackEvent.EDGE_RIGHT
+ ? progress * mWindowScaleMarginX
+ : screenWidth - progress * mWindowScaleMarginX - width;
- mCurrentRect.set(
- MathUtils.lerp(mStartRectF.left, mTargetRectF.left, progress),
- MathUtils.lerp(mStartRectF.top, mTargetRectF.top, progress),
- MathUtils.lerp(mStartRectF.right, mTargetRectF.right, progress),
- MathUtils.lerp(mStartRectF.bottom, mTargetRectF.bottom, progress));
- SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder =
- new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash);
-
- Rect currentRect = new Rect();
- mCurrentRect.round(currentRect);
-
- // Scale the target window to match the currentRectF.
- final float scale = mCurrentRect.width() / mStartRectF.width();
- mTransformMatrix.reset();
- mTransformMatrix.setScale(scale, scale);
- mTransformMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
- Rect startRect = new Rect();
- mStartRectF.round(startRect);
+ mCurrentRect.set(left, top, left + width, top + height);
float cornerRadius = Utilities.mapRange(
progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
+ applyTransform(mCurrentRect, cornerRadius);
+ }
+
+ private void updateCancelProgress(float progress) {
+ if (mBackTarget == null) {
+ return;
+ }
+ mCurrentRect.set(
+ MathUtils.lerp(mCancelRect.left, mStartRect.left, progress),
+ MathUtils.lerp(mCancelRect.top, mStartRect.top, progress),
+ MathUtils.lerp(mCancelRect.right, mStartRect.right, progress),
+ MathUtils.lerp(mCancelRect.bottom, mStartRect.bottom, progress));
+
+ float cornerRadius = Utilities.mapRange(
+ progress, mWindowScaleEndCornerRadius, mWindowScaleStartCornerRadius);
+ applyTransform(mCurrentRect, cornerRadius);
+ }
+
+ /** Transform the target window to match the target rect. */
+ private void applyTransform(RectF targetRect, float cornerRadius) {
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder =
+ new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash);
+ final float scale = targetRect.width() / mStartRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
builder.withMatrix(mTransformMatrix)
- .withWindowCrop(startRect)
+ .withWindowCrop(mStartRect)
.withCornerRadius(cornerRadius);
SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = builder.build();
@@ -263,11 +291,11 @@
mBackTarget = null;
mBackInProgress = false;
mBackProgress = 0;
- mSwipeEdge = BackEvent.EDGE_LEFT;
mTransformMatrix.reset();
- mTargetRectF.setEmpty();
+ mCancelRect.setEmpty();
mCurrentRect.setEmpty();
- mStartRectF.setEmpty();
+ mStartRect.setEmpty();
+ mInitialTouchPos.set(0, 0);
mAnimatorSetInProgress = false;
mSpringAnimationInProgress = false;
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
diff --git a/res/interpolator/back_cancel.xml b/res/interpolator/back_cancel.xml
new file mode 100644
index 0000000..2165457
--- /dev/null
+++ b/res/interpolator/back_cancel.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.2"
+ android:controlY1="0"
+ android:controlX2="0"
+ android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 0ed2d85..5ecd929 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -166,6 +166,6 @@
<!-- Swipe back to home related -->
<dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
- <dimen name="swipe_back_window_scale_y_margin">80dp</dimen>
+ <dimen name="swipe_back_window_max_delta_y">160dp</dimen>
<dimen name="swipe_back_window_corner_radius">40dp</dimen>
</resources>