Merge "Enabling springs for start dismiss animation" into ub-launcher3-master

This commit is contained in:
TreeHugger Robot
2020-03-07 08:38:49 +00:00
committed by Android (Google) Code Review
16 changed files with 417 additions and 699 deletions
@@ -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() {
@@ -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));
@@ -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;
@@ -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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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) {
@@ -1000,7 +1000,7 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
if (UNSTABLE_SPRINGS.get()) {
mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
mLauncherTransitionController.dispatchOnStart();
}
mLauncherTransitionController.getAnimationPlayer().start();
mHasLauncherTransitionControllerStarted = true;
@@ -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<T extends BaseActivity> extends PagedView impl
protected void applyLoadPlan(ArrayList<Task> tasks) {
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks));
return;
}
@@ -1210,37 +1206,29 @@ public abstract class RecentsView<T extends BaseActivity> 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<View> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> extends PagedView impl
taskView.setTranslationZ(0.1f);
}
mPendingAnimation = pendingAnimation;
mPendingAnimation.addEndListener(new Consumer<PendingAnimation.OnEndListener>() {
mPendingAnimation = anim;
mPendingAnimation.addEndListener(new Consumer<EndState>() {
@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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> extends PagedView impl
anim.setDuration(duration)
.setInterpolator(interpolator);
Consumer<Boolean> 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<Boolean> onLaunchResult = (result) -> {
onTaskLaunchFinish.accept(result);
onTaskLaunched(result);
if (!result) {
tv.notifyTaskLaunchFailed(TAG);
}
@@ -1795,11 +1769,11 @@ public abstract class RecentsView<T extends BaseActivity> 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;
});
@@ -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);
@@ -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());
@@ -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;
}
@@ -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);
}
@@ -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<Holder> 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<ValueAnimator> CURRENT_PLAY_TIME =
new FloatProperty<ValueAnimator>("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<SpringAnimation> 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<Holder> 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<AnimatorListener> 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<AnimatorListener, Animator> command) {
callAnimatorCommandRecursively(anim, a-> {
for (AnimatorListener l : nonNullList(a.getListeners())) {
command.accept(l, a);
}
});
}
private static void callAnimatorCommandRecursively(Animator anim, Consumer<Animator> 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<ValueAnimator> childAnims = new ArrayList<>();
getAnimationsRecur(mAnim, childAnims);
mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
}
private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> 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 <T> List<T> nonNullList(ArrayList<T> 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<Holder> 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);
}
}
}
@@ -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<Consumer<EndState>> mEndListeners = new ArrayList<>();
/** package private **/
final AnimatorSet anim = new AnimatorSet();
final ArrayList<Holder> 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<EndState> listeners : mEndListeners) {
listeners.accept(new EndState(isSuccess, logAction));
}
mEndListeners.clear();
}
public void addEndListener(Consumer<EndState> 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;
}
}
}
@@ -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<T> extends ValueAnimator {
private static final String TAG = "SpringObjectAnimator";
private static boolean DEBUG = false;
private ObjectAnimator mObjectAnimator;
private float[] mValues;
private SpringAnimation mSpring;
private SpringProperty<T> mProperty;
private ArrayList<AnimatorListener> mListeners;
private boolean mSpringEnded = true;
private boolean mAnimatorEnded = true;
private boolean mEnded = true;
public SpringObjectAnimator(T object, FloatProperty<T> 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<AnimatorListener> getObjectAnimatorListeners() {
return mObjectAnimator.getListeners();
}
@Override
public ArrayList<AnimatorListener> 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<T> extends FloatProperty<T> {
boolean useSpring = false;
final FloatProperty<T> mProperty;
final SpringAnimation mSpring;
public SpringProperty(FloatProperty<T> 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);
}
}
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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<Consumer<OnEndListener>> mEndListeners = new ArrayList<>();
public final AnimatorSet anim;
public PendingAnimation(AnimatorSet anim) {
this.anim = anim;
}
public void finish(boolean isSuccess, int logAction) {
for (Consumer<OnEndListener> listeners : mEndListeners) {
listeners.accept(new OnEndListener(isSuccess, logAction));
}
mEndListeners.clear();
}
public void addEndListener(Consumer<OnEndListener> 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;
}
}
}