diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 19a2bae467..e500f0fe3f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -171,7 +171,8 @@ public class NavBarToHomeTouchController implements TouchController, } } anim.setDuration(accuracy); - mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState); + mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy) + .setOnCancelRunnable(this::clearState); } private void clearState() { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 715529e355..52625dc74f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -393,7 +393,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, xOverviewAnim.setFloatValues(startXProgress, endXProgress); xOverviewAnim.setDuration(xDuration) .setInterpolator(scrollInterpolatorForVelocity(velocity.x)); - mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x); + mXOverviewAnim.dispatchOnStart(); boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; @@ -414,7 +414,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer(); yOverviewAnim.setFloatValues(startYProgress, endYProgress); yOverviewAnim.setDuration(yDuration); - mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y); + mYOverviewAnim.dispatchOnStart(); ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); if (flingUpToNormal && !mIsHomeScreenVisible) { @@ -436,8 +436,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, float startProgress = mNonOverviewAnim.getProgressFraction(); float endProgress = canceled ? 0 : 1; nonOverviewAnim.setFloatValues(startProgress, endProgress); - mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress, - horizontalFling ? velocity.x : velocity.y); + mNonOverviewAnim.dispatchOnStart(); } nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java index 1f5228a239..845699a761 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java @@ -22,7 +22,7 @@ import android.view.MotionEvent; import android.view.animation.Interpolator; import com.android.launcher3.Launcher; -import com.android.launcher3.util.PendingAnimation; +import com.android.launcher3.anim.PendingAnimation; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index e0532ac4cf..cc58fcfd57 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -16,17 +16,13 @@ package com.android.launcher3.uioverrides.touchcontrollers; import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; -import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE; -import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.view.MotionEvent; import com.android.launcher3.AbstractFloatingView; @@ -35,12 +31,12 @@ import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.BaseSwipeDetector; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.FlingBlockCheck; -import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.SysUINavigationMode; @@ -218,8 +214,8 @@ public abstract class TaskViewTouchController if (mCurrentAnimation != null) { mCurrentAnimation.setOnCancelRunnable(null); } - mCurrentAnimation = AnimatorPlaybackController.wrap( - mPendingAnimation.anim, maxDuration, this::clearState); + mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation, maxDuration) + .setOnCancelRunnable(this::clearState); onUserControlledAnimationCreated(mCurrentAnimation); mCurrentAnimation.getTarget().addListener(this); mCurrentAnimation.dispatchOnStart(); @@ -288,26 +284,16 @@ public abstract class TaskViewTouchController animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity); } - float nextFrameProgress = Utilities.boundToRange(progress - + velocity * getSingleFrameMs(mActivity) / Math.abs(mEndDisplacement), 0f, 1f); - mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction)); - - ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); - anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f); - anim.setDuration(animationDuration); - anim.setInterpolator(scrollInterpolatorForVelocity(velocity)); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - anim.addUpdateListener(valueAnimator -> { + mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> { if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) { mRecentsView.redrawLiveTile(true); } }); } - if (UNSTABLE_SPRINGS.get()) { - mCurrentAnimation.dispatchOnStartWithVelocity(goingToEnd ? 1f : 0f, velocity); - } - anim.start(); + mCurrentAnimation.startWithVelocity(mActivity, goingToEnd, + velocity, mEndDisplacement, animationDuration); } private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 4b6241c4ab..3328abcaf5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -1000,7 +1000,7 @@ public class LauncherSwipeHandler mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration)); if (UNSTABLE_SPRINGS.get()) { - mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y); + mLauncherTransitionController.dispatchOnStart(); } mLauncherTransitionController.getAnimationPlayer().start(); mHasLauncherTransitionControllerStarted = true; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 62d6604322..7eb327406c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -16,12 +16,9 @@ package com.android.quickstep.views; -import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS; - import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; @@ -32,7 +29,6 @@ import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR; import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; @@ -46,7 +42,6 @@ import android.animation.AnimatorSet; import android.animation.LayoutTransition; import android.animation.LayoutTransition.TransitionListener; import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager; @@ -96,8 +91,10 @@ 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.PendingAnimation; +import com.android.launcher3.anim.PendingAnimation.EndState; import com.android.launcher3.anim.PropertyListBuilder; -import com.android.launcher3.anim.SpringObjectAnimator; +import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; @@ -111,7 +108,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.OverScroller; -import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.Themes; import com.android.launcher3.util.ViewPool; import com.android.quickstep.RecentsAnimationController; @@ -623,7 +619,7 @@ public abstract class RecentsView extends PagedView impl protected void applyLoadPlan(ArrayList tasks) { if (mPendingAnimation != null) { - mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks)); + mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks)); return; } @@ -1210,37 +1206,29 @@ public abstract class RecentsView extends PagedView impl } } - private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) { - addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim); + private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) { + anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2); FloatProperty secondaryViewTranslate = mOrientationHandler.getSecondaryViewTranslate(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor(); - if (UNSTABLE_SPRINGS.get() && taskView instanceof TaskView) { - ResourceProvider rp = DynamicResource.provider(mActivity); - float dampingRatio = rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio); - float stiffness = rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness); - addAnim(new SpringObjectAnimator<>(taskView, secondaryViewTranslate, - MIN_VISIBLE_CHANGE_PIXELS, dampingRatio, - stiffness, 0, verticalFactor * secondaryTaskDimension), - duration, LINEAR, anim); - } else { - addAnim(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, - verticalFactor * secondaryTaskDimension), duration, LINEAR, anim); - } + ResourceProvider rp = DynamicResource.provider(mActivity); + SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START) + .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio)) + .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness)); + + anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, + verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp); } - private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener, - boolean shouldLog) { + private void removeTask(Task task, int index, EndState endState) { if (task != null) { ActivityManagerWrapper.getInstance().removeTask(task.key.id); - if (shouldLog) { - ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key); - mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( - onEndListener.logAction, Direction.UP, index, componentKey); - mActivity.getStatsLogManager().logTaskDismiss(this, componentKey); - } + ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key); + mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( + endState.logAction, Direction.UP, index, componentKey); + mActivity.getStatsLogManager().logTaskDismiss(this, componentKey); } } @@ -1249,12 +1237,11 @@ public abstract class RecentsView extends PagedView impl if (mPendingAnimation != null) { mPendingAnimation.finish(false, Touch.SWIPE); } - AnimatorSet anim = new AnimatorSet(); - PendingAnimation pendingAnimation = new PendingAnimation(anim); + PendingAnimation anim = new PendingAnimation(); int count = getPageCount(); if (count == 0) { - return pendingAnimation; + return anim; } int[] oldScroll = new int[count]; @@ -1273,7 +1260,7 @@ public abstract class RecentsView extends PagedView impl View child = getChildAt(i); if (child == taskView) { if (animateTaskView) { - addDismissedTaskAnimations(taskView, anim, duration); + addDismissedTaskAnimations(taskView, duration, anim); } } else { // If we just take newScroll - oldScroll, everything to the right of dragged task @@ -1297,20 +1284,15 @@ public abstract class RecentsView extends PagedView impl } int scrollDiff = newScroll[i] - oldScroll[i] + offset; if (scrollDiff != 0) { - if (UNSTABLE_SPRINGS.get() && child instanceof TaskView) { - ResourceProvider rp = DynamicResource.provider(mActivity); - float damping = rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio); - float stiffness = rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness); - - addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X, - MIN_VISIBLE_CHANGE_PIXELS, damping, - stiffness, 0, scrollDiff), duration, ACCEL, anim); - } else { - Property translationProperty = mOrientationHandler.getPrimaryViewTranslate(); - addAnim(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff), - duration, ACCEL, anim); - } + Property translationProperty = mOrientationHandler.getPrimaryViewTranslate(); + ResourceProvider rp = DynamicResource.provider(mActivity); + SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END) + .setDampingRatio( + rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio)) + .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness)); + anim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff) + .setDuration(duration), ACCEL, sp); needsCurveUpdates = true; } } @@ -1319,7 +1301,7 @@ public abstract class RecentsView extends PagedView impl if (needsCurveUpdates) { ValueAnimator va = ValueAnimator.ofFloat(0, 1); va.addUpdateListener((a) -> updateCurveProperties()); - anim.play(va); + anim.add(va); } // Add a tiny bit of translation Z, so that it draws on top of other views @@ -1327,22 +1309,22 @@ public abstract class RecentsView extends PagedView impl taskView.setTranslationZ(0.1f); } - mPendingAnimation = pendingAnimation; - mPendingAnimation.addEndListener(new Consumer() { + mPendingAnimation = anim; + mPendingAnimation.addEndListener(new Consumer() { @Override - public void accept(PendingAnimation.OnEndListener onEndListener) { + public void accept(EndState endState) { if (ENABLE_QUICKSTEP_LIVE_TILE.get() && - taskView.isRunningTask() && onEndListener.isSuccess) { - finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener)); + taskView.isRunningTask() && endState.isSuccess) { + finishRecentsAnimation(true /* toHome */, () -> onEnd(endState)); } else { - onEnd(onEndListener); + onEnd(endState); } } - private void onEnd(PendingAnimation.OnEndListener onEndListener) { - if (onEndListener.isSuccess) { + private void onEnd(EndState endState) { + if (endState.isSuccess) { if (shouldRemoveTask) { - removeTask(taskView.getTask(), draggedIndex, onEndListener, true); + removeTask(taskView.getTask(), draggedIndex, endState); } int pageToSnapTo = mCurrentPage; @@ -1364,24 +1346,23 @@ public abstract class RecentsView extends PagedView impl mPendingAnimation = null; } }); - return pendingAnimation; + return anim; } public PendingAnimation createAllTasksDismissAnimation(long duration) { if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { throw new IllegalStateException("Another pending animation is still running"); } - AnimatorSet anim = new AnimatorSet(); - PendingAnimation pendingAnimation = new PendingAnimation(anim); + PendingAnimation anim = new PendingAnimation(); int count = getTaskViewCount(); for (int i = 0; i < count; i++) { - addDismissedTaskAnimations(getTaskViewAt(i), anim, duration); + addDismissedTaskAnimations(getTaskViewAt(i), duration, anim); } - mPendingAnimation = pendingAnimation; - mPendingAnimation.addEndListener((onEndListener) -> { - if (onEndListener.isSuccess) { + mPendingAnimation = anim; + mPendingAnimation.addEndListener((endState) -> { + if (endState.isSuccess) { // Remove all the task views now ActivityManagerWrapper.getInstance().removeAllRecentTasks(); removeTasksViewsAndClearAllButton(); @@ -1389,13 +1370,7 @@ public abstract class RecentsView extends PagedView impl } mPendingAnimation = null; }); - return pendingAnimation; - } - - private static void addAnim(Animator anim, long duration, - TimeInterpolator interpolator, AnimatorSet set) { - anim.setDuration(duration).setInterpolator(interpolator); - set.play(anim); + return anim; } private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) { @@ -1412,8 +1387,8 @@ public abstract class RecentsView extends PagedView impl } private void runDismissAnimation(PendingAnimation pendingAnim) { - AnimatorPlaybackController controller = AnimatorPlaybackController.wrap( - pendingAnim.anim, DISMISS_TASK_DURATION); + AnimatorPlaybackController controller = + AnimatorPlaybackController.wrap(pendingAnim, DISMISS_TASK_DURATION); controller.dispatchOnStart(); controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE)); controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN); @@ -1738,7 +1713,7 @@ public abstract class RecentsView extends PagedView impl int count = getTaskViewCount(); if (count == 0) { - return new PendingAnimation(new AnimatorSet()); + return new PendingAnimation(); } int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags(); @@ -1780,13 +1755,12 @@ public abstract class RecentsView extends PagedView impl anim.setDuration(duration) .setInterpolator(interpolator); - Consumer onTaskLaunchFinish = this::onTaskLaunched; - - mPendingAnimation = new PendingAnimation(anim); - mPendingAnimation.addEndListener((onEndListener) -> { - if (onEndListener.isSuccess) { + mPendingAnimation = new PendingAnimation(); + mPendingAnimation.add(anim); + mPendingAnimation.addEndListener((endState) -> { + if (endState.isSuccess) { Consumer onLaunchResult = (result) -> { - onTaskLaunchFinish.accept(result); + onTaskLaunched(result); if (!result) { tv.notifyTaskLaunchFailed(TAG); } @@ -1795,11 +1769,11 @@ public abstract class RecentsView extends PagedView impl Task task = tv.getTask(); if (task != null) { mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( - onEndListener.logAction, Direction.DOWN, indexOfChild(tv), + endState.logAction, Direction.DOWN, indexOfChild(tv), TaskUtils.getLaunchComponentKeyForTask(task.key)); } } else { - onTaskLaunchFinish.accept(false); + onTaskLaunched(false); } mPendingAnimation = null; }); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index e09e01ff9f..abc037eb6f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -58,6 +58,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.states.RotationHelper; @@ -67,7 +68,6 @@ import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.ViewPool.Reusable; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskIconCache; @@ -278,8 +278,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { public AnimatorPlaybackController createLaunchAnimationForRunningTask() { final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation( this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR); - AnimatorPlaybackController currentAnimation = AnimatorPlaybackController.wrap( - pendingAnimation.anim, RECENTS_LAUNCH_DURATION); + AnimatorPlaybackController currentAnimation = + AnimatorPlaybackController.wrap(pendingAnimation, RECENTS_LAUNCH_DURATION); currentAnimation.setEndAction(() -> { pendingAnimation.finish(true, Touch.SWIPE); launchTask(false); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index fe830d221e..95e38e38fd 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -87,8 +87,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr protected boolean canInterceptTouch(MotionEvent ev) { if (mCurrentAnimation != null) { if (mFinishFastOnSecondTouch) { - // TODO: Animate to finish instead. - mCurrentAnimation.skipToEnd(); + mCurrentAnimation.getAnimationPlayer().end(); } AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); @@ -233,8 +232,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr cancelPendingAnim(); clearState(); }; - mCurrentAnimation = AnimatorPlaybackController.wrap( - mPendingAnimation.anim, maxAccuracy, onCancelRunnable); + mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation, maxAccuracy) + .setOnCancelRunnable(onCancelRunnable); mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation); totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile()); diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index 9f25729602..a8f2025d26 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -376,8 +376,8 @@ public class LauncherStateManager { mConfig.animComponents = animComponents; mConfig.duration = duration; mConfig.playbackController = AnimatorPlaybackController.wrap( - createAnimationToNewWorkspaceInternal(state, builder, null), duration, - onCancelRunnable); + createAnimationToNewWorkspaceInternal(state, builder, null), duration) + .setOnCancelRunnable(onCancelRunnable); return mConfig.playbackController; } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 217916205d..32c65a3a22 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -12,7 +12,6 @@ import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRE import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; -import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; import android.animation.Animator; @@ -31,11 +30,8 @@ import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.anim.SpringObjectAnimator; -import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; -import com.android.systemui.plugins.ResourceProvider; /** * Handles AllApps view transition. @@ -185,14 +181,6 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil } public Animator createSpringAnimation(float... progressValues) { - if (UNSTABLE_SPRINGS.get()) { - ResourceProvider rp = DynamicResource.provider(mLauncher); - float damping = rp.getFloat(R.dimen.all_apps_spring_damping_ratio); - float stiffness = rp.getFloat(R.dimen.all_apps_spring_stiffness); - - return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange, - damping, stiffness, progressValues); - } return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues); } diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 1c277ab8c0..1fc21fd63e 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -16,7 +16,9 @@ package com.android.launcher3.anim; import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; +import static com.android.launcher3.anim.Interpolators.clampToProgress; +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; import android.animation.Animator; import android.animation.Animator.AnimatorListener; @@ -24,17 +26,18 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; -import android.util.Log; +import android.content.Context; +import android.util.FloatProperty; import androidx.annotation.Nullable; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; + +import com.android.launcher3.Utilities; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators @@ -43,14 +46,7 @@ import java.util.Set; * Note: The implementation does not support start delays on child animations or * sequential playbacks. */ -public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener { - - private static final String TAG = "AnimatorPlaybackCtrler"; - private static boolean DEBUG = false; - - public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) { - return wrap(anim, duration, null); - } +public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener { /** * Creates an animation controller for the provided animation. @@ -58,20 +54,41 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat * needs to be larger than the total number of pixels so that we don't have jittering due * to float (animation-fraction * total duration) to int conversion. */ - public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration, - Runnable onCancelRunnable) { - + public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) { /** * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed. */ - return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable); + ArrayList childAnims = new ArrayList<>(); + addAnimationHoldersRecur(anim, SpringProperty.DEFAULT, childAnims); + + return new AnimatorPlaybackController(anim, duration, childAnims); } + public static AnimatorPlaybackController wrap(PendingAnimation anim, long duration) { + /** + * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed. + */ + return new AnimatorPlaybackController(anim.anim, duration, anim.animHolders); + } + + private static final FloatProperty CURRENT_PLAY_TIME = + new FloatProperty("current-play-time") { + @Override + public void setValue(ValueAnimator animator, float v) { + animator.setCurrentPlayTime((long) v); + } + + @Override + public Float get(ValueAnimator animator) { + return (float) animator.getCurrentPlayTime(); + } + }; + private final ValueAnimator mAnimationPlayer; private final long mDuration; - protected final AnimatorSet mAnim; - private Set mSprings; + private final AnimatorSet mAnim; + private final Holder[] mChildAnimations; protected float mCurrentFraction; private Runnable mEndAction; @@ -79,22 +96,14 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat protected boolean mTargetCancelled = false; protected Runnable mOnCancelRunnable; - private OnAnimationEndDispatcher mEndListener; - private DynamicAnimation.OnAnimationEndListener mSpringEndListener; - // We need this variable to ensure the end listener is called immediately, otherwise we run into - // issues where the callback interferes with the states of the swipe detector. - private boolean mSkipToEnd = false; - - protected AnimatorPlaybackController(AnimatorSet anim, long duration, - Runnable onCancelRunnable) { + private AnimatorPlaybackController( + AnimatorSet anim, long duration, ArrayList childAnims) { mAnim = anim; mDuration = duration; - mOnCancelRunnable = onCancelRunnable; mAnimationPlayer = ValueAnimator.ofFloat(0, 1); mAnimationPlayer.setInterpolator(LINEAR); - mEndListener = new OnAnimationEndDispatcher(); - mAnimationPlayer.addListener(mEndListener); + mAnimationPlayer.addListener(new OnAnimationEndDispatcher()); mAnimationPlayer.addUpdateListener(this); mAnim.addListener(new AnimatorListenerAdapter() { @@ -119,14 +128,7 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } }); - mSprings = new HashSet<>(); - mSpringEndListener = (animation, canceled, value, velocity1) -> { - if (canceled) { - mEndListener.onAnimationCancel(mAnimationPlayer); - } else { - mEndListener.onAnimationEnd(mAnimationPlayer); - } - }; + mChildAnimations = childAnims.toArray(new Holder[childAnims.size()]); } public AnimatorSet getTarget() { @@ -159,10 +161,69 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mAnimationPlayer.start(); } + /** + * Starts playing the animation with the provided velocity optionally playing any + * physics based animations + */ + public void startWithVelocity(Context context, boolean goingToEnd, + float velocity, float scale, long animationDuration) { + float scaleInverse = 1 / Math.abs(scale); + float scaledVelocity = velocity * scaleInverse; + + float nextFrameProgress = Utilities.boundToRange(getProgressFraction() + + scaledVelocity * getSingleFrameMs(context), 0f, 1f); + + // Update setters for spring + int springFlag = goingToEnd + ? SpringProperty.FLAG_CAN_SPRING_ON_END + : SpringProperty.FLAG_CAN_SPRING_ON_START; + + long springDuration = animationDuration; + for (Holder h : mChildAnimations) { + if ((h.springProperty.flags & springFlag) != 0) { + SpringAnimationBuilder s = new SpringAnimationBuilder(h.anim, CURRENT_PLAY_TIME) + .setStartValue(clampDuration(mCurrentFraction)) + .setEndValue(goingToEnd ? h.anim.getDuration() : 0) + .setStartVelocity(scaledVelocity * h.anim.getDuration()) + .setMinimumVisibleChange(scaleInverse) + .setDampingRatio(h.springProperty.mDampingRatio) + .setStiffness(h.springProperty.mStiffness); + + long expectedDurationL = s.build(context).getDuration(); + springDuration = Math.max(expectedDurationL, springDuration); + + float expectedDuration = expectedDurationL; + h.setter = (a, l) -> + s.setValue(a, mAnimationPlayer.getCurrentPlayTime() / expectedDuration); + h.anim.setInterpolator(LINEAR); + } + } + + mAnimationPlayer.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f); + + if (springDuration <= animationDuration) { + mAnimationPlayer.setDuration(animationDuration); + mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocity)); + } else { + // Since spring requires more time to run, we let the other animations play with + // current time and interpolation and by clamping the duration. + mAnimationPlayer.setDuration(springDuration); + + float cutOff = animationDuration / (float) springDuration; + mAnimationPlayer.setInterpolator( + clampToProgress(scrollInterpolatorForVelocity(velocity), 0, cutOff)); + } + mAnimationPlayer.start(); + } + /** * Pauses the currently playing animation. */ public void pause() { + // Reset property setters + for (Holder h : mChildAnimations) { + h.reset(); + } mAnimationPlayer.cancel(); } @@ -176,7 +237,18 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat /** * Sets the current animation position and updates all the child animators accordingly. */ - public abstract void setPlayFraction(float fraction); + public void setPlayFraction(float fraction) { + mCurrentFraction = fraction; + // Let the animator report the progress but don't apply the progress to child + // animations if it has been cancelled. + if (mTargetCancelled) { + return; + } + long playPos = clampDuration(fraction); + for (Holder holder : mChildAnimations) { + holder.setter.set(holder.anim, playPos); + } + } public float getProgressFraction() { return mCurrentFraction; @@ -208,49 +280,6 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } - /** - * Starts playback and sets the spring. - */ - public void dispatchOnStartWithVelocity(float end, float velocity) { - if (!QUICKSTEP_SPRINGS.get()) { - dispatchOnStart(); - return; - } - - if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity); - - for (Animator a : mAnim.getChildAnimations()) { - if (a instanceof SpringObjectAnimator) { - if (DEBUG) Log.d(TAG, "Found springAnimator=" + a); - SpringObjectAnimator springAnimator = (SpringObjectAnimator) a; - mSprings.add(springAnimator.getSpring()); - springAnimator.startSpring(end, velocity, mSpringEndListener); - } - } - - dispatchOnStart(); - } - - public void dispatchOnStart() { - dispatchOnStartRecursively(mAnim); - } - - private void dispatchOnStartRecursively(Animator animator) { - List listeners = animator instanceof SpringObjectAnimator - ? nonNullList(((SpringObjectAnimator) animator).getObjectAnimatorListeners()) - : nonNullList(animator.getListeners()); - - for (AnimatorListener l : listeners) { - l.onAnimationStart(animator); - } - - if (animator instanceof AnimatorSet) { - for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { - dispatchOnStartRecursively(anim); - } - } - } - /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */ public void dispatchOnCancelWithoutCancelRunnable() { dispatchOnCancelWithoutCancelRunnable(null); @@ -272,115 +301,47 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat setOnCancelRunnable(onCancel); } - public void dispatchOnCancel() { - dispatchOnCancelRecursively(mAnim); + + public AnimatorPlaybackController setOnCancelRunnable(Runnable runnable) { + mOnCancelRunnable = runnable; + return this; } - private void dispatchOnCancelRecursively(Animator animator) { - for (AnimatorListener l : nonNullList(animator.getListeners())) { - l.onAnimationCancel(animator); - } + public void dispatchOnStart() { + callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart); + } - if (animator instanceof AnimatorSet) { - for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { - dispatchOnCancelRecursively(anim); - } - } + public void dispatchOnCancel() { + callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationCancel); } public void dispatchSetInterpolator(TimeInterpolator interpolator) { - dispatchSetInterpolatorRecursively(mAnim, interpolator); + callAnimatorCommandRecursively(mAnim, a -> a.setInterpolator(interpolator)); } - private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) { - anim.setInterpolator(interpolator); + private static void callListenerCommandRecursively( + Animator anim, BiConsumer command) { + callAnimatorCommandRecursively(anim, a-> { + for (AnimatorListener l : nonNullList(a.getListeners())) { + command.accept(l, a); + } + }); + } + + private static void callAnimatorCommandRecursively(Animator anim, Consumer command) { + command.accept(anim); if (anim instanceof AnimatorSet) { for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) { - dispatchSetInterpolatorRecursively(child, interpolator); + callAnimatorCommandRecursively(child, command); } } } - public void setOnCancelRunnable(Runnable runnable) { - mOnCancelRunnable = runnable; - } - - public void skipToEnd() { - mSkipToEnd = true; - for (SpringAnimation spring : mSprings) { - if (spring.canSkipToEnd()) { - spring.skipToEnd(); - } - } - mAnimationPlayer.end(); - mSkipToEnd = false; - } - - public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController { - - private final ValueAnimator[] mChildAnimations; - - private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, - Runnable onCancelRunnable) { - super(anim, duration, onCancelRunnable); - - // Build animation list - ArrayList childAnims = new ArrayList<>(); - getAnimationsRecur(mAnim, childAnims); - mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]); - } - - private void getAnimationsRecur(AnimatorSet anim, ArrayList out) { - long forceDuration = anim.getDuration(); - TimeInterpolator forceInterpolator = anim.getInterpolator(); - for (Animator child : anim.getChildAnimations()) { - if (forceDuration > 0) { - child.setDuration(forceDuration); - } - if (forceInterpolator != null) { - child.setInterpolator(forceInterpolator); - } - if (child instanceof ValueAnimator) { - out.add((ValueAnimator) child); - } else if (child instanceof AnimatorSet) { - getAnimationsRecur((AnimatorSet) child, out); - } else { - throw new RuntimeException("Unknown animation type " + child); - } - } - } - - @Override - public void setPlayFraction(float fraction) { - mCurrentFraction = fraction; - // Let the animator report the progress but don't apply the progress to child - // animations if it has been cancelled. - if (mTargetCancelled) { - return; - } - long playPos = clampDuration(fraction); - for (ValueAnimator anim : mChildAnimations) { - anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration())); - } - } - } - - private boolean isAnySpringRunning() { - for (SpringAnimation spring : mSprings) { - if (spring.isRunning()) { - return true; - } - } - return false; - } - /** * Only dispatches the on end actions once the animator and all springs have completed running. */ private class OnAnimationEndDispatcher extends AnimationSuccessListener { - boolean mAnimatorDone = false; - boolean mSpringsDone = false; boolean mDispatched = false; @Override @@ -391,39 +352,76 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat @Override public void onAnimationSuccess(Animator animator) { - if (mSprings.isEmpty()) { - mSpringsDone = mAnimatorDone = true; - } - if (isAnySpringRunning()) { - mAnimatorDone = true; - } else { - mSpringsDone = true; - } - // We wait for the spring (if any) to finish running before completing the end callback. - if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) { - dispatchOnEndRecursively(mAnim); + if (!mDispatched) { + callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd); if (mEndAction != null) { mEndAction.run(); } mDispatched = true; } } - - private void dispatchOnEndRecursively(Animator animator) { - for (AnimatorListener l : nonNullList(animator.getListeners())) { - l.onAnimationEnd(animator); - } - - if (animator instanceof AnimatorSet) { - for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { - dispatchOnEndRecursively(anim); - } - } - } } private static List nonNullList(ArrayList list) { return list == null ? Collections.emptyList() : list; } + + /** + * Interface for setting position of value animator + */ + private interface PositionSetter { + + PositionSetter DEFAULT = (anim, playPos) -> + anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration())); + + void set(ValueAnimator anim, long position); + } + + /** + * Holder class for various child animations + */ + static class Holder { + + public final ValueAnimator anim; + + public final SpringProperty springProperty; + + public final TimeInterpolator interpolator; + + public PositionSetter setter; + + Holder(Animator anim, SpringProperty springProperty) { + this.anim = (ValueAnimator) anim; + this.springProperty = springProperty; + this.interpolator = this.anim.getInterpolator(); + this.setter = PositionSetter.DEFAULT; + } + + public void reset() { + anim.setInterpolator(interpolator); + setter = PositionSetter.DEFAULT; + } + } + + static void addAnimationHoldersRecur( + Animator anim, SpringProperty springProperty, ArrayList out) { + long forceDuration = anim.getDuration(); + TimeInterpolator forceInterpolator = anim.getInterpolator(); + if (anim instanceof ValueAnimator) { + out.add(new Holder(anim, springProperty)); + } else if (anim instanceof AnimatorSet) { + for (Animator child : ((AnimatorSet) anim).getChildAnimations()) { + if (forceDuration > 0) { + child.setDuration(forceDuration); + } + if (forceInterpolator != null) { + child.setInterpolator(forceInterpolator); + } + addAnimationHoldersRecur(child, springProperty, out); + } + } else { + throw new RuntimeException("Unknown animation type " + anim); + } + } } diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java new file mode 100644 index 0000000000..562d160b50 --- /dev/null +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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 static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.TimeInterpolator; +import android.annotation.TargetApi; +import android.os.Build; + +import com.android.launcher3.anim.AnimatorPlaybackController.Holder; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Utility class to keep track of a running animation. + * + * This class allows attaching end callbacks to an animation is intended to be used with + * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case + * AnimationListeners are not properly dispatched. + * + * TODO: Find a better name + */ +@TargetApi(Build.VERSION_CODES.O) +public class PendingAnimation { + + private final ArrayList> mEndListeners = new ArrayList<>(); + + /** package private **/ + final AnimatorSet anim = new AnimatorSet(); + final ArrayList animHolders = new ArrayList<>(); + + /** + * Utility method to sent an interpolator on an animation and add it to the list + */ + public void add(Animator anim, TimeInterpolator interpolator) { + add(anim, interpolator, SpringProperty.DEFAULT); + } + + public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) { + anim.setInterpolator(interpolator); + add(anim, springProperty); + } + + public void add(Animator anim) { + add(anim, SpringProperty.DEFAULT); + } + + public void add(Animator a, SpringProperty springProperty) { + anim.play(a); + addAnimationHoldersRecur(a, springProperty, animHolders); + } + + public void finish(boolean isSuccess, int logAction) { + for (Consumer listeners : mEndListeners) { + listeners.accept(new EndState(isSuccess, logAction)); + } + mEndListeners.clear(); + } + + public void addEndListener(Consumer listener) { + mEndListeners.add(listener); + } + + public static class EndState { + public boolean isSuccess; + public int logAction; + + public EndState(boolean isSuccess, int logAction) { + this.isSuccess = isSuccess; + this.logAction = logAction; + } + } +} diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java deleted file mode 100644 index 27b9c18cd4..0000000000 --- a/src/com/android/launcher3/anim/SpringObjectAnimator.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * 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 static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; - -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.os.Handler; -import android.os.Looper; -import android.util.FloatProperty; -import android.util.Log; - -import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - -import java.util.ArrayList; - - -/** - * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or - * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet. - */ -public class SpringObjectAnimator extends ValueAnimator { - - private static final String TAG = "SpringObjectAnimator"; - private static boolean DEBUG = false; - - private ObjectAnimator mObjectAnimator; - private float[] mValues; - - private SpringAnimation mSpring; - private SpringProperty mProperty; - - private ArrayList mListeners; - private boolean mSpringEnded = true; - private boolean mAnimatorEnded = true; - private boolean mEnded = true; - - public SpringObjectAnimator(T object, FloatProperty property, float minimumVisibleChange, - float damping, float stiffness, float... values) { - mSpring = new SpringAnimation(object, createFloatPropertyCompat(property)); - mSpring.setMinimumVisibleChange(minimumVisibleChange); - mSpring.setSpring(new SpringForce(0) - .setDampingRatio(damping) - .setStiffness(stiffness)); - mSpring.setStartVelocity(0.01f); - mProperty = new SpringProperty<>(property, mSpring); - mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values); - mValues = values; - mListeners = new ArrayList<>(); - setFloatValues(values); - - // We use this listener and track mListeners so that we can sync the animator and spring - // listeners. - mObjectAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mAnimatorEnded = false; - mEnded = false; - for (AnimatorListener l : mListeners) { - l.onAnimationStart(animation); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - mAnimatorEnded = true; - tryEnding(); - } - - @Override - public void onAnimationCancel(Animator animation) { - for (AnimatorListener l : mListeners) { - l.onAnimationCancel(animation); - } - mSpring.cancel(); - } - }); - - mSpring.addUpdateListener((animation, value, velocity) -> { - mSpringEnded = false; - mEnded = false; - }); - mSpring.addEndListener((animation, canceled, value, velocity) -> { - mSpringEnded = true; - tryEnding(); - }); - } - - private void tryEnding() { - if (DEBUG) { - Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded=" - + mSpringEnded + ", mEnded=" + mEnded); - } - - // If springs are disabled, ignore value of mSpringEnded - if (mAnimatorEnded && (mSpringEnded || !QUICKSTEP_SPRINGS.get()) && !mEnded) { - for (AnimatorListener l : mListeners) { - l.onAnimationEnd(this); - } - mEnded = true; - } - } - - public SpringAnimation getSpring() { - return mSpring; - } - - /** - * Initializes and sets up the spring to take over controlling the object. - */ - public void startSpring(float end, float velocity, OnAnimationEndListener endListener) { - // Cancel the spring so we can set new start velocity and final position. We need to remove - // the listener since the spring is not actually ending. - mSpring.removeEndListener(endListener); - mSpring.cancel(); - mSpring.addEndListener(endListener); - - mProperty.switchToSpring(); - - float startValue = end == 0 ? mValues[1] : mValues[0]; - float endValue = end == 0 ? mValues[0] : mValues[1]; - - // Ensures that the velocity matches the direction of the values. - velocity = Math.signum(endValue - startValue) * Math.abs(velocity); - mSpring.setStartVelocity(velocity); - - new Handler(Looper.getMainLooper()).postDelayed(() -> { - mSpring.animateToFinalPosition(endValue); - }, getStartDelay()); - } - - @Override - public void addListener(AnimatorListener listener) { - mListeners.add(listener); - } - - public ArrayList getObjectAnimatorListeners() { - return mObjectAnimator.getListeners(); - } - - @Override - public ArrayList getListeners() { - return mListeners; - } - - @Override - public void removeAllListeners() { - mListeners.clear(); - } - - @Override - public void removeListener(AnimatorListener listener) { - mListeners.remove(listener); - } - - @Override - public void addPauseListener(AnimatorPauseListener listener) { - mObjectAnimator.addPauseListener(listener); - } - - @Override - public void cancel() { - mObjectAnimator.cancel(); - mSpring.cancel(); - } - - @Override - public void end() { - mObjectAnimator.end(); - } - - @Override - public long getDuration() { - return mObjectAnimator.getDuration(); - } - - @Override - public TimeInterpolator getInterpolator() { - return mObjectAnimator.getInterpolator(); - } - - @Override - public long getStartDelay() { - return mObjectAnimator.getStartDelay(); - } - - @Override - public long getTotalDuration() { - return mObjectAnimator.getTotalDuration(); - } - - @Override - public boolean isPaused() { - return mObjectAnimator.isPaused(); - } - - @Override - public boolean isRunning() { - return mObjectAnimator.isRunning(); - } - - @Override - public boolean isStarted() { - return mObjectAnimator.isStarted(); - } - - @Override - public void pause() { - mObjectAnimator.pause(); - } - - @Override - public void removePauseListener(AnimatorPauseListener listener) { - mObjectAnimator.removePauseListener(listener); - } - - @Override - public void resume() { - mObjectAnimator.resume(); - } - - @Override - public ValueAnimator setDuration(long duration) { - return mObjectAnimator.setDuration(duration); - } - - @Override - public void setInterpolator(TimeInterpolator value) { - mObjectAnimator.setInterpolator(value); - } - - @Override - public void setStartDelay(long startDelay) { - mObjectAnimator.setStartDelay(startDelay); - } - - @Override - public void setTarget(Object target) { - mObjectAnimator.setTarget(target); - } - - @Override - public void start() { - mObjectAnimator.start(); - } - - @Override - public void setCurrentFraction(float fraction) { - mObjectAnimator.setCurrentFraction(fraction); - } - - @Override - public void setCurrentPlayTime(long playTime) { - mObjectAnimator.setCurrentPlayTime(playTime); - } - - public static class SpringProperty extends FloatProperty { - - boolean useSpring = false; - final FloatProperty mProperty; - final SpringAnimation mSpring; - - public SpringProperty(FloatProperty property, SpringAnimation spring) { - super(property.getName()); - mProperty = property; - mSpring = spring; - } - - public void switchToSpring() { - useSpring = true; - } - - @Override - public Float get(T object) { - return mProperty.get(object); - } - - @Override - public void setValue(T object, float progress) { - if (useSpring) { - mSpring.animateToFinalPosition(progress); - } else { - mProperty.setValue(object, progress); - } - } - } -} diff --git a/src/com/android/launcher3/anim/SpringProperty.java b/src/com/android/launcher3/anim/SpringProperty.java new file mode 100644 index 0000000000..caedd6c658 --- /dev/null +++ b/src/com/android/launcher3/anim/SpringProperty.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 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.SpringForce; + +/** + * Utility class to store configurations for spring animation + */ +public class SpringProperty { + + public static final SpringProperty DEFAULT = new SpringProperty(); + + // Play spring when the animation is going towards the end + public static final int FLAG_CAN_SPRING_ON_END = 1 << 0; + // Play spring when animation is going towards the start (in reverse direction) + public static final int FLAG_CAN_SPRING_ON_START = 1 << 1; + + public final int flags; + + float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; + float mStiffness = SpringForce.STIFFNESS_MEDIUM; + + public SpringProperty() { + this(0); + } + + public SpringProperty(int flags) { + this.flags = flags; + } + + public SpringProperty setDampingRatio(float dampingRatio) { + mDampingRatio = dampingRatio; + return this; + } + + public SpringProperty setStiffness(float stiffness) { + mStiffness = stiffness; + return this; + } +} diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 34d69e9eec..4e9878105d 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -42,11 +42,11 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.FlingBlockCheck; -import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.TouchController; /** @@ -434,7 +434,7 @@ public abstract class AbstractStateChangeTouchController maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f); updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()), targetState, velocity, fling); - mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, progressVelocity); + mCurrentAnimation.dispatchOnStart(); if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) { mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity); } diff --git a/src/com/android/launcher3/util/PendingAnimation.java b/src/com/android/launcher3/util/PendingAnimation.java deleted file mode 100644 index 617a38bbed..0000000000 --- a/src/com/android/launcher3/util/PendingAnimation.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2018 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.util; - -import android.animation.AnimatorSet; -import android.annotation.TargetApi; -import android.os.Build; - -import java.util.ArrayList; -import java.util.function.Consumer; - -/** - * Utility class to keep track of a running animation. - * - * This class allows attaching end callbacks to an animation is intended to be used with - * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case - * AnimationListeners are not properly dispatched. - */ -@TargetApi(Build.VERSION_CODES.O) -public class PendingAnimation { - - private final ArrayList> mEndListeners = new ArrayList<>(); - - public final AnimatorSet anim; - - public PendingAnimation(AnimatorSet anim) { - this.anim = anim; - } - - public void finish(boolean isSuccess, int logAction) { - for (Consumer listeners : mEndListeners) { - listeners.accept(new OnEndListener(isSuccess, logAction)); - } - mEndListeners.clear(); - } - - public void addEndListener(Consumer listener) { - mEndListeners.add(listener); - } - - public static class OnEndListener { - public boolean isSuccess; - public int logAction; - - public OnEndListener(boolean isSuccess, int logAction) { - this.isSuccess = isSuccess; - this.logAction = logAction; - } - } -}