diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java index d61ed724dd..ef46b3b0d9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java @@ -21,7 +21,6 @@ import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.content.ComponentName; import android.content.Context; import android.graphics.Rect; import android.graphics.RectF; @@ -90,7 +89,7 @@ public final class FallbackActivityControllerHelper implements @NonNull @Override - public Animator createActivityAnimationToHome() { + public AnimatorPlaybackController createActivityAnimationToHome() { Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0); anim.addListener(new AnimationSuccessListener() { @Override @@ -98,7 +97,10 @@ public final class FallbackActivityControllerHelper implements recentsView.startHome(); } }); - return anim; + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.play(anim); + long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight()); + return AnimatorPlaybackController.wrap(animatorSet, accuracy); } }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index e95e2a0de5..b0bd71bf03 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -144,10 +144,9 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @NonNull @Override - public Animator createActivityAnimationToHome() { + public AnimatorPlaybackController createActivityAnimationToHome() { long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); - return activity.getStateManager().createAnimationToNewWorkspace( - NORMAL, accuracy).getTarget(); + return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy); } }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java index 4e010d25e7..4792cc7916 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java @@ -21,7 +21,6 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.INVALID_POINTER_ID; - import static com.android.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR; @@ -45,8 +44,6 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowManager; -import androidx.annotation.UiThread; - import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RaceConditionTracker; @@ -63,6 +60,8 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import java.util.function.Consumer; +import androidx.annotation.UiThread; + /** * Input consumer for handling events originating from an activity other than Launcher */ @@ -331,12 +330,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.get(this).getScaledMaximumFlingVelocity()); float velocityX = mVelocityTracker.getXVelocity(mActivePointerId); + float velocityY = mVelocityTracker.getYVelocity(mActivePointerId); float velocity = isNavBarOnRight() ? velocityX : isNavBarOnLeft() ? -velocityX - : mVelocityTracker.getYVelocity(mActivePointerId); + : velocityY; mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement); - mInteractionHandler.onGestureEnded(velocity, velocityX); + mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY)); } else { // Since we start touch tracking on DOWN, we may reach this state without actually // starting the gesture. In that case, just cleanup immediately. diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index fd53f9c87c..efc228b596 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -43,12 +43,12 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; @@ -85,6 +85,7 @@ import com.android.quickstep.ActivityControlHelper.AnimationFactory; import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState; import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; import com.android.quickstep.util.ClipAnimationHelper; +import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener; @@ -92,7 +93,6 @@ import com.android.quickstep.views.LiveTileOverlay; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; -import com.android.systemui.shared.recents.utilities.RectFEvaluator; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.LatencyTrackerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -677,15 +677,19 @@ public class WindowTransformSwipeHandler } } + /** + * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen. + * @param velocity The x and y components of the velocity when the gesture ends. + */ @UiThread - public void onGestureEnded(float endVelocity, float velocityX) { + public void onGestureEnded(float endVelocity, PointF velocity) { float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold; setStateOnUiThread(STATE_GESTURE_COMPLETED); mLogAction = isFling ? Touch.FLING : Touch.SWIPE; - handleNormalGestureEnd(endVelocity, isFling, velocityX); + handleNormalGestureEnd(endVelocity, isFling, velocity); } @UiThread @@ -703,9 +707,8 @@ public class WindowTransformSwipeHandler } @UiThread - private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) { - float velocityPxPerMs = endVelocity / 1000; - float velocityXPxPerMs = velocityX / 1000; + private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) { + PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000); long duration = MAX_SWIPE_DURATION; float currentShift = mCurrentShift.value; final GestureEndTarget endTarget; @@ -750,7 +753,7 @@ public class WindowTransformSwipeHandler } else { if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) { // If swiping at a diagonal, base end target on the faster velocity. - endTarget = goingToNewTask && Math.abs(velocityX) > Math.abs(endVelocity) + endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity) ? NEW_TASK : HOME; } else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) { // If user scrolled to a new task, only go to recents if they already passed @@ -760,14 +763,15 @@ public class WindowTransformSwipeHandler endTarget = goingToNewTask ? NEW_TASK : LAST_TASK; } endShift = endTarget.endShift; - startShift = Utilities.boundToRange(currentShift - velocityPxPerMs + startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1); float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { if (endTarget == RECENTS) { Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams( - startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength); + startShift, endShift, endShift, velocityPxPerMs.y, + mTransitionDragLength); endShift = overshoot.end; interpolator = overshoot.interpolator; duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION, @@ -778,7 +782,7 @@ 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(Math.abs(distanceToTravel / velocityPxPerMs)); + long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); } } @@ -835,14 +839,14 @@ public class WindowTransformSwipeHandler /** Animates to the given progress, where 0 is the current app and 1 is overview. */ @UiThread private void animateToProgress(float start, float end, long duration, Interpolator interpolator, - GestureEndTarget target, float velocityPxPerMs) { + GestureEndTarget target, PointF velocityPxPerMs) { mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration, interpolator, target, velocityPxPerMs)); } @UiThread private void animateToProgressInternal(float start, float end, long duration, - Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) { + Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) { mGestureEndTarget = target; if (mGestureEndTarget.canBeContinued) { @@ -856,7 +860,6 @@ public class WindowTransformSwipeHandler } HomeAnimationFactory homeAnimFactory; - Animator windowAnim; if (mGestureEndTarget == HOME) { if (mActivity != null) { homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity); @@ -872,27 +875,34 @@ public class WindowTransformSwipeHandler @NonNull @Override - public Animator createActivityAnimationToHome() { - return new AnimatorSet(); + public AnimatorPlaybackController createActivityAnimationToHome() { + return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); } }; mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, isPresent -> mRecentsView.startHome()); } - windowAnim = createWindowAnimationToHome(start, homeAnimFactory); + RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory); + windowAnim.addAnimatorListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + setStateOnUiThread(target.endState); + } + }); + windowAnim.start(velocityPxPerMs); mLauncherTransitionController = null; } else { - windowAnim = mCurrentShift.animateToValue(start, end); + Animator windowAnim = mCurrentShift.animateToValue(start, end); + windowAnim.setDuration(duration).setInterpolator(interpolator); + windowAnim.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + setStateOnUiThread(target.endState); + } + }); + windowAnim.start(); homeAnimFactory = null; } - windowAnim.setDuration(duration).setInterpolator(interpolator); - windowAnim.addListener(new AnimationSuccessListener() { - @Override - public void onAnimationSuccess(Animator animator) { - setStateOnUiThread(target.endState); - } - }); - windowAnim.start(); // Always play the entire launcher animation when going home, since it is separate from // the animation that has been controlled thus far. if (mGestureEndTarget == HOME) { @@ -903,12 +913,6 @@ public class WindowTransformSwipeHandler // interpolate over the remaining progress (end - start). TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress( interpolator, start, end); - if (homeAnimFactory != null) { - Animator homeAnim = homeAnimFactory.createActivityAnimationToHome(); - homeAnim.setDuration(duration).setInterpolator(adjustedInterpolator); - homeAnim.start(); - mLauncherTransitionController = null; - } if (mLauncherTransitionController == null) { return; } @@ -920,50 +924,40 @@ public class WindowTransformSwipeHandler mLauncherTransitionController.getAnimationPlayer().setDuration(duration); if (QUICKSTEP_SPRINGS.get()) { - mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs); + mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y); } mLauncherTransitionController.getAnimationPlayer().start(); } } /** - * Creates an Animator that transforms the current app window into the home app. + * Creates an animation that transforms the current app window into the home app. * @param startProgress The progress of {@link #mCurrentShift} to start the window from. * @param homeAnimationFactory The home animation factory. */ - private Animator createWindowAnimationToHome(float startProgress, + private RectFSpringAnim createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory) { final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet; - RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet, + final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet, mTransformParams.setProgress(startProgress))); - RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect()); - final RectF finalTarget = homeAnimationFactory.getWindowTargetRect(); - - final RectFEvaluator rectFEvaluator = new RectFEvaluator(); - final RectF targetRect = new RectF(); - final RectF currentRect = new RectF(); + final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); final View floatingView = homeAnimationFactory.getFloatingView(); final boolean isFloatingIconView = floatingView instanceof FloatingIconView; - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect); if (isFloatingIconView) { - anim.addListener((FloatingIconView) floatingView); + anim.addAnimatorListener((FloatingIconView) floatingView); } + AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome(); // We want the window alpha to be 0 once this threshold is met, so that the // FolderIconView can be seen morphing into the icon shape. final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f; - anim.addUpdateListener(animation -> { - float progress = animation.getAnimatedFraction(); + anim.addOnUpdateListener((currentRect, progress) -> { float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress); - // Initially go towards original target (task view in recents), - // but accelerate towards the final target. - // TODO: This is technically not correct. Instead, motion should continue at - // the released velocity but accelerate towards the target. - targetRect.set(rectFEvaluator.evaluate(interpolatedProgress, - originalTarget, finalTarget)); - currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect)); + + homeAnim.setPlayFraction(progress); float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0, windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR); @@ -975,10 +969,17 @@ public class WindowTransformSwipeHandler ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress, windowAlphaThreshold); } + }); - anim.addListener(new AnimationSuccessListener() { + anim.addAnimatorListener(new AnimationSuccessListener() { + @Override + public void onAnimationStart(Animator animation) { + homeAnim.dispatchOnStart(); + } + @Override public void onAnimationSuccess(Animator animator) { + homeAnim.getAnimationPlayer().end(); if (mRecentsView != null) { mRecentsView.post(mRecentsView::resetTaskVisuals); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java new file mode 100644 index 0000000000..2edeb3aec2 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2019 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. + */ +package com.android.quickstep.util; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.FloatProperty; + +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.FlingSpringAnim; + +import java.util.ArrayList; +import java.util.List; + +import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; +import androidx.dynamicanimation.animation.FloatPropertyCompat; + +/** + * Applies spring forces to animate from a starting rect to a target rect, + * while providing update callbacks to the caller. + */ +public class RectFSpringAnim { + + /** + * Although the rect position animation takes an indefinite amount of time since it depends on + * the initial velocity and applied forces, scaling from the starting rect to the target rect + * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress + * of this animation, while the end callback is sent after all animations finish. + */ + private static final long RECT_SCALE_DURATION = 180; + + private static final FloatPropertyCompat RECT_CENTER_X = + new FloatPropertyCompat("rectCenterXSpring") { + @Override + public float getValue(RectFSpringAnim anim) { + return anim.mCurrentCenterX; + } + + @Override + public void setValue(RectFSpringAnim anim, float currentCenterX) { + anim.mCurrentCenterX = currentCenterX; + anim.onUpdate(); + } + }; + + private static final FloatPropertyCompat RECT_CENTER_Y = + new FloatPropertyCompat("rectCenterYSpring") { + @Override + public float getValue(RectFSpringAnim anim) { + return anim.mCurrentCenterY; + } + + @Override + public void setValue(RectFSpringAnim anim, float currentCenterY) { + anim.mCurrentCenterY = currentCenterY; + anim.onUpdate(); + } + }; + + private static final FloatProperty RECT_SCALE_PROGRESS = + new FloatProperty("rectScaleProgress") { + @Override + public Float get(RectFSpringAnim anim) { + return anim.mCurrentScaleProgress; + } + + @Override + public void setValue(RectFSpringAnim anim, float currentScaleProgress) { + anim.mCurrentScaleProgress = currentScaleProgress; + anim.onUpdate(); + } + }; + + private final RectF mStartRect; + private final RectF mTargetRect; + private final RectF mCurrentRect = new RectF(); + private final List mOnUpdateListeners = new ArrayList<>(); + private final List mAnimatorListeners = new ArrayList<>(); + + private float mCurrentCenterX; + private float mCurrentCenterY; + private float mCurrentScaleProgress; + private boolean mRectXAnimEnded; + private boolean mRectYAnimEnded; + private boolean mRectScaleAnimEnded; + + public RectFSpringAnim(RectF startRect, RectF targetRect) { + mStartRect = startRect; + mTargetRect = targetRect; + mCurrentCenterX = mStartRect.centerX(); + mCurrentCenterY = mStartRect.centerY(); + } + + public void addOnUpdateListener(OnUpdateListener onUpdateListener) { + mOnUpdateListeners.add(onUpdateListener); + } + + public void addAnimatorListener(Animator.AnimatorListener animatorListener) { + mAnimatorListeners.add(animatorListener); + } + + public void start(PointF velocityPxPerMs) { + // Only tell caller that we ended if both x and y animations have ended. + OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> { + mRectXAnimEnded = true; + maybeOnEnd(); + }); + OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> { + mRectYAnimEnded = true; + maybeOnEnd(); + }); + FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX, + mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener); + FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY, + mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener); + + ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this, + PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1)) + .setDuration(RECT_SCALE_DURATION); + rectScaleAnim.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mRectScaleAnimEnded = true; + maybeOnEnd(); + } + }); + + rectXAnim.start(); + rectYAnim.start(); + rectScaleAnim.start(); + for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { + animatorListener.onAnimationStart(null); + } + } + + private void onUpdate() { + if (!mOnUpdateListeners.isEmpty()) { + float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(), + mTargetRect.width()); + float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(), + mTargetRect.height()); + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2, + mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2); + for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { + onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress); + } + } + } + + private void maybeOnEnd() { + if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) { + for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { + animatorListener.onAnimationEnd(null); + } + } + } + + public interface OnUpdateListener { + void onUpdate(RectF currentRect, float progress); + } +} diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 75be2e48e1..418f7f4424 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -15,7 +15,6 @@ */ package com.android.quickstep; -import android.animation.Animator; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -132,6 +131,6 @@ public interface ActivityControlHelper { @NonNull RectF getWindowTargetRect(); - @NonNull Animator createActivityAnimationToHome(); + @NonNull AnimatorPlaybackController createActivityAnimationToHome(); } } diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java new file mode 100644 index 0000000000..3d21d82a25 --- /dev/null +++ b/src/com/android/launcher3/anim/FlingSpringAnim.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 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. + */ +package com.android.launcher3.anim; + +import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; +import androidx.dynamicanimation.animation.FlingAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +/** + * Given a property to animate and a target value and starting velocity, first apply friction to + * the fling until we pass the target, then apply a spring force to pull towards the target. + */ +public class FlingSpringAnim { + + private static final float FLING_FRICTION = 1.5f; + // Have the spring pull towards the target if we've slowed down too much before reaching it. + private static final float FLING_END_THRESHOLD_PX = 50f; + private static final float SPRING_STIFFNESS = 350f; + private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY; + + private final FlingAnimation mFlingAnim; + + public FlingSpringAnim(K object, FloatPropertyCompat property, float startPosition, + float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) { + mFlingAnim = new FlingAnimation(object, property) + .setFriction(FLING_FRICTION) + .setMinimumVisibleChange(FLING_END_THRESHOLD_PX) + .setStartVelocity(startVelocity) + .setMinValue(Math.min(startPosition, targetPosition)) + .setMaxValue(Math.max(startPosition, targetPosition)); + mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> { + SpringAnimation springAnim = new SpringAnimation(object, property) + .setStartVelocity(velocity) + .setSpring(new SpringForce(targetPosition) + .setStiffness(SPRING_STIFFNESS) + .setDampingRatio(SPRING_DAMPING)); + springAnim.addEndListener(onEndListener); + springAnim.start(); + })); + } + + public void start() { + mFlingAnim.start(); + } +} diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index e5c70da5f6..2a5418d081 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.views; +import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -55,8 +57,6 @@ import com.android.launcher3.shortcuts.DeepShortcutView; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; - /** * A view that is created to look like another view with the purpose of creating fluid animations. */ @@ -143,9 +143,6 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, setBackgroundDrawableBounds(bgScale); mRevealAnimator.setCurrentFraction(shapeRevealProgress); - if (Float.compare(shapeRevealProgress, 1f) >= 0f) { - mRevealAnimator.end(); - } } invalidate(); invalidateOutline(); @@ -160,6 +157,9 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, @Override public void onAnimationEnd(Animator animator) { + if (mRevealAnimator != null) { + mRevealAnimator.end(); + } if (mEndRunnable != null) { mEndRunnable.run(); }