diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java index c58bb94bb6..717179dfe0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java @@ -25,7 +25,6 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.view.MotionEvent; import android.view.animation.Interpolator; -import android.view.animation.OvershootInterpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; @@ -192,7 +191,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr // Update all apps interpolator to add a bit of overshoot starting from currFraction final float currFraction = mCurrentAnimation.getProgressFraction(); mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress( - new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)), currFraction, 1); + Interpolators.overshootInterpolatorForVelocity(velocity), currFraction, 1); animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION)) .setInterpolator(LINEAR); } diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 202d8fc371..0205c1f032 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -15,7 +15,9 @@ */ package com.android.quickstep; +import static android.view.View.TRANSLATION_Y; import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.FAST_OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; @@ -25,6 +27,7 @@ import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION; +import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; @@ -55,14 +58,15 @@ import com.android.launcher3.uioverrides.FastOverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.TouchConsumer.InteractionType; +import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.LayoutUtils; -import com.android.quickstep.util.TransformedRect; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationTargetSet; +import com.android.quickstep.util.TransformedRect; import com.android.quickstep.views.LauncherLayoutListener; -import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsViewContainer; +import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.Objects; @@ -248,28 +252,52 @@ public interface ActivityControlHelper { return; } - if (activity.getDeviceProfile().isVerticalBarLayout()) { - return; - } - - AllAppsTransitionController controller = activity.getAllAppsController(); AnimatorSet anim = new AnimatorSet(); - float scrollRange = Math.max(controller.getShiftRange(), 1); - float progressDelta = (transitionLength / scrollRange); + if (!activity.getDeviceProfile().isVerticalBarLayout()) { + AllAppsTransitionController controller = activity.getAllAppsController(); + float scrollRange = Math.max(controller.getShiftRange(), 1); + float progressDelta = (transitionLength / scrollRange); - float endProgress = endState.getVerticalProgress(activity); - float startProgress = endProgress + progressDelta; - ObjectAnimator shiftAnim = ObjectAnimator.ofFloat( - controller, ALL_APPS_PROGRESS, startProgress, endProgress); - shiftAnim.setInterpolator(LINEAR); - anim.play(shiftAnim); + float endProgress = endState.getVerticalProgress(activity); + float startProgress = endProgress + progressDelta; + ObjectAnimator shiftAnim = ObjectAnimator.ofFloat( + controller, ALL_APPS_PROGRESS, startProgress, endProgress); + shiftAnim.setInterpolator(LINEAR); + anim.play(shiftAnim); + } + + if (interactionType == INTERACTION_NORMAL) { + playScaleDownAnim(anim, activity); + } anim.setDuration(transitionLength * 2); activity.getStateManager().setCurrentAnimation(anim); callback.accept(AnimatorPlaybackController.wrap(anim, transitionLength * 2)); } + /** + * Scale down recents from the center task being full screen to being in overview. + */ + private void playScaleDownAnim(AnimatorSet anim, Launcher launcher) { + RecentsView recentsView = launcher.getOverviewPanel(); + TaskView v = recentsView.getPageAt(recentsView.getCurrentPage()); + ClipAnimationHelper clipHelper = new ClipAnimationHelper(); + clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null); + if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) { + float fromScale = clipHelper.getSourceRect().width() + / clipHelper.getTargetRect().width(); + float fromTranslationY = clipHelper.getSourceRect().centerY() + - clipHelper.getTargetRect().centerY(); + Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1); + Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, + fromTranslationY, 0); + scale.setInterpolator(LINEAR); + translateY.setInterpolator(LINEAR); + anim.playTogether(scale, translateY); + } + } + @Override public ActivityInitListener createActivityInitListener( BiPredicate onInitListener) { diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 765b5ffb31..66bc501bb2 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -58,6 +58,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; @@ -569,7 +570,8 @@ public class WindowTransformSwipeHandler { } private void updateFinalShiftUi() { - if (mLauncherTransitionController == null) { + if (mLauncherTransitionController == null || mLauncherTransitionController + .getAnimationPlayer().isStarted()) { return; } mLauncherTransitionController.setPlayFraction(mCurrentShift.value); @@ -663,17 +665,23 @@ public class WindowTransformSwipeHandler { } private void handleNormalGestureEnd(float endVelocity, boolean isFling) { + float velocityPxPerMs = endVelocity / 1000; long duration = MAX_SWIPE_DURATION; final float endShift; final float startShift; + final Interpolator interpolator; if (!isFling) { endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted ? 1 : 0; long expectedDuration = Math.abs(Math.round((endShift - mCurrentShift.value) * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); startShift = mCurrentShift.value; + interpolator = DEACCEL; } else { endShift = endVelocity < 0 ? 1 : 0; + interpolator = endVelocity < 0 + ? Interpolators.overshootInterpolatorForVelocity(velocityPxPerMs, 2f) + : DEACCEL; float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { @@ -682,14 +690,13 @@ public class WindowTransformSwipeHandler { // we want the page's snap velocity to approximately match the velocity at // which the user flings, so we scale the duration by a value near to the // derivative of the scroll interpolator at zero, ie. 2. - long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / endVelocity)); + long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs)); duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); } - startShift = Utilities.boundToRange(mCurrentShift.value - endVelocity * SINGLE_FRAME_MS - / (mTransitionDragLength * 1000), 0, 1); + startShift = Utilities.boundToRange(mCurrentShift.value - velocityPxPerMs + * SINGLE_FRAME_MS / (mTransitionDragLength), 0, 1); } - - animateToProgress(startShift, endShift, duration, DEACCEL); + animateToProgress(startShift, endShift, duration, interpolator); } private void doLogGesture(boolean toLauncher) { @@ -716,6 +723,12 @@ public class WindowTransformSwipeHandler { /** Animates to the given progress, where 0 is the current app and 1 is overview. */ private void animateToProgress(float start, float end, long duration, Interpolator interpolator) { + mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration, + interpolator)); + } + + private void animateToProgressInternal(float start, float end, long duration, + Interpolator interpolator) { mIsGoingToHome = Float.compare(end, 1) == 0; ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration); anim.setInterpolator(interpolator); @@ -727,7 +740,26 @@ public class WindowTransformSwipeHandler { : STATE_SCALED_CONTROLLER_APP); } }); - mRecentsAnimationWrapper.runOnInit(anim::start); + anim.start(); + long startMillis = SystemClock.uptimeMillis(); + executeOnUiThread(() -> { + // Animate the launcher components at the same time as the window, always on UI thread. + if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible + && start != end && duration > 0) { + // Adjust start progress and duration in case we are on a different thread. + long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration); + float elapsedProgress = (float) elapsedMillis / duration; + float adjustedStart = Utilities.mapRange(elapsedProgress, start, end); + long adjustedDuration = duration - elapsedMillis; + // We want to use the same interpolator as the window, but need to adjust it to + // interpolate over the remaining progress (end - start). + mLauncherTransitionController.dispatchSetInterpolator(Interpolators.mapToProgress( + interpolator, adjustedStart, end)); + mLauncherTransitionController.getAnimationPlayer().setDuration(adjustedDuration); + mLauncherTransitionController.getAnimationPlayer().start(); + } + }); } @UiThread diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 4bd9a9bf25..5355c5e551 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -272,6 +272,24 @@ public final class Utilities { return scale; } + /** + * Maps t from one range to another range. + * @param t The value to map. + * @param fromMin The lower bound of the range that t is being mapped from. + * @param fromMax The upper bound of the range that t is being mapped from. + * @param toMin The lower bound of the range that t is being mapped to. + * @param toMax The upper bound of the range that t is being mapped to. + * @return The mapped value of t. + */ + public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax) { + if (fromMin == fromMax || toMin == toMax) { + Log.e(TAG, "mapToRange: range has 0 length"); + return toMin; + } + float progress = Math.abs(t - fromMin) / Math.abs(fromMax - fromMin); + return mapRange(progress, toMin, toMax); + } + public static float mapRange(float value, float min, float max) { return min + (value * (max - min)); } @@ -462,6 +480,13 @@ public final class Utilities { return Math.max(lowerBound, Math.min(value, upperBound)); } + /** + * @see #boundToRange(int, int, int). + */ + public static long boundToRange(long value, long lowerBound, long upperBound) { + return Math.max(lowerBound, Math.min(value, upperBound)); + } + /** * Wraps a message with a TTS span, so that a different message is spoken than * what is getting displayed. diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 84085cb6c0..50fb0a51a8 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -202,6 +202,19 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + public void dispatchSetInterpolator(TimeInterpolator interpolator) { + dispatchSetInterpolatorRecursively(mAnim, interpolator); + } + + private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) { + anim.setInterpolator(interpolator); + if (anim instanceof AnimatorSet) { + for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) { + dispatchSetInterpolatorRecursively(child, interpolator); + } + } + } + public void setOnCancelRunnable(Runnable runnable) { mOnCancelRunnable = runnable; } diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index bace7df575..d17572e6cc 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -24,6 +24,8 @@ import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; import android.view.animation.PathInterpolator; +import com.android.launcher3.Utilities; + /** * Common interpolators used in Launcher @@ -116,6 +118,19 @@ public class Interpolators { return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC; } + public static Interpolator overshootInterpolatorForVelocity(float velocity) { + return overshootInterpolatorForVelocity(velocity, 1f); + } + + /** + * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms). + * @param velocity The start velocity of the animation we want to overshoot. + * @param dampFactor An optional factor to reduce the amount of tension (how far we overshoot). + */ + public static Interpolator overshootInterpolatorForVelocity(float velocity, float dampFactor) { + return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f) / dampFactor); + } + /** * Runs the given interpolator such that the entire progress is set between the given bounds. * That is, we set the interpolation to 0 until lowerBound and reach 1 by upperBound. @@ -135,4 +150,15 @@ public class Interpolators { return interpolator.getInterpolation((t - lowerBound) / (upperBound - lowerBound)); }; } + + /** + * Runs the given interpolator such that the interpolated value is mapped to the given range. + * This is useful, for example, if we only use this interpolator for part of the animation, + * such as to take over a user-controlled animation when they let go. + */ + public static Interpolator mapToProgress(Interpolator interpolator, float lowerBound, + float upperBound) { + return t -> Utilities.mapToRange(interpolator.getInterpolation(t), 0, 1, + lowerBound, upperBound); + } } \ No newline at end of file