mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-03 09:26:51 +00:00
Fix some state issues with user-controlled animations
Previously, user-controlled animations weren't properly being canceled when a non-user-controlled animation started, e.g. when hitting home. Thus, we could end in the wrong or inconsistent state because the user-controlled animation's end runnable was still used. Now we add a cleanup callback for when we reset the user-controlled animation for one that isn't user-controlled. Also fixed a couple typos. Tests (easier with animation durations extended): - Swipe up and hit home before reaching overview -> land on home - Go to overview, swipe down slightly (before threshold to go to workspace) and let go -> return to overview without flash (recents was resetting) - Swipe up, press home while swiping -> goes home, stops responding to drag - Start dismissing task and hit home before it finishes (or while dragging) -> goes home, stops responding to drag Bug: 78249220 Change-Id: If11d8999e3fadba38c987b25af67cd2304cd859b
This commit is contained in:
@@ -187,10 +187,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
||||
builder = new AnimatorSetBuilder();
|
||||
}
|
||||
|
||||
if (mPendingAnimation != null) {
|
||||
mPendingAnimation.finish(false, Touch.SWIPE);
|
||||
mPendingAnimation = null;
|
||||
}
|
||||
cancelPendingAnim();
|
||||
|
||||
RecentsView recentsView = mLauncher.getOverviewPanel();
|
||||
TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage());
|
||||
@@ -199,10 +196,16 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
||||
mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
|
||||
mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
|
||||
|
||||
mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy);
|
||||
Runnable onCancelRunnable = () -> {
|
||||
cancelPendingAnim();
|
||||
clearState();
|
||||
};
|
||||
mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
|
||||
onCancelRunnable);
|
||||
mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
|
||||
} else {
|
||||
mCurrentAnimation = mLauncher.getStateManager()
|
||||
.createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
|
||||
.createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState);
|
||||
}
|
||||
|
||||
if (totalShift == 0) {
|
||||
@@ -212,6 +215,13 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
||||
return 1 / totalShift;
|
||||
}
|
||||
|
||||
private void cancelPendingAnim() {
|
||||
if (mPendingAnimation != null) {
|
||||
mPendingAnimation.finish(false, Touch.SWIPE);
|
||||
mPendingAnimation = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
|
||||
LauncherState targetState, float velocity, boolean isFling) {
|
||||
|
||||
@@ -63,7 +63,7 @@ public class RecentsViewStateController implements StateHandler {
|
||||
@Override
|
||||
public void setStateWithAnimation(final LauncherState toState,
|
||||
AnimatorSetBuilder builder, AnimationConfig config) {
|
||||
PropertySetter setter = config.getProperSetter(builder);
|
||||
PropertySetter setter = config.getPropertySetter(builder);
|
||||
float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
|
||||
setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
|
||||
builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
|
||||
|
||||
@@ -87,12 +87,14 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
|
||||
protected abstract boolean isRecentsInteractive();
|
||||
|
||||
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
|
||||
Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
|
||||
mDetector.finishedScrolling();
|
||||
mCurrentAnimation = null;
|
||||
clearState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,8 +196,12 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
mEndDisplacement = dl.getHeight() - mTempCords[1];
|
||||
}
|
||||
|
||||
if (mCurrentAnimation != null) {
|
||||
mCurrentAnimation.setOnCancelRunnable(null);
|
||||
}
|
||||
mCurrentAnimation = AnimatorPlaybackController
|
||||
.wrap(mPendingAnimation.anim, maxDuration);
|
||||
.wrap(mPendingAnimation.anim, maxDuration, this::clearState);
|
||||
onUserControlledAnimationCreated(mCurrentAnimation);
|
||||
mCurrentAnimation.getTarget().addListener(this);
|
||||
mCurrentAnimation.dispatchOnStart();
|
||||
mProgressMultiplier = 1 / mEndDisplacement;
|
||||
@@ -271,8 +277,17 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
mPendingAnimation.finish(wasSuccess, logAction);
|
||||
mPendingAnimation = null;
|
||||
}
|
||||
clearState();
|
||||
}
|
||||
|
||||
private void clearState() {
|
||||
mDetector.finishedScrolling();
|
||||
mDetector.setDetectableScrollConditions(0, false);
|
||||
mTaskBeingDragged = null;
|
||||
mCurrentAnimation = null;
|
||||
if (mPendingAnimation != null) {
|
||||
mPendingAnimation.finish(false, Touch.SWIPE);
|
||||
mPendingAnimation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.LauncherStateManager.StateHandler;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.quickstep.OverviewInteractionState;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
@@ -44,19 +45,19 @@ public class UiFactory {
|
||||
return new TouchController[] {
|
||||
launcher.getDragController(),
|
||||
new OverviewToAllAppsTouchController(launcher),
|
||||
new LauncherTaskViewcontroller(launcher)};
|
||||
new LauncherTaskViewController(launcher)};
|
||||
}
|
||||
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
|
||||
return new TouchController[] {
|
||||
launcher.getDragController(),
|
||||
new OverviewToAllAppsTouchController(launcher),
|
||||
new LandscapeEdgeSwipeController(launcher),
|
||||
new LauncherTaskViewcontroller(launcher)};
|
||||
new LauncherTaskViewController(launcher)};
|
||||
} else {
|
||||
return new TouchController[] {
|
||||
launcher.getDragController(),
|
||||
new PortraitStatesTouchController(launcher),
|
||||
new LauncherTaskViewcontroller(launcher)};
|
||||
new LauncherTaskViewController(launcher)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +115,9 @@ public class UiFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private static class LauncherTaskViewcontroller extends TaskViewTouchController<Launcher> {
|
||||
private static class LauncherTaskViewController extends TaskViewTouchController<Launcher> {
|
||||
|
||||
public LauncherTaskViewcontroller(Launcher activity) {
|
||||
public LauncherTaskViewController(Launcher activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@@ -124,5 +125,10 @@ public class UiFactory {
|
||||
protected boolean isRecentsInteractive() {
|
||||
return mActivity.isInState(OVERVIEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
|
||||
mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,16 +238,18 @@ public class LauncherStateManager {
|
||||
*/
|
||||
public AnimatorPlaybackController createAnimationToNewWorkspace(
|
||||
LauncherState state, long duration) {
|
||||
return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration);
|
||||
return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null);
|
||||
}
|
||||
|
||||
public AnimatorPlaybackController createAnimationToNewWorkspace(
|
||||
LauncherState state, AnimatorSetBuilder builder, long duration) {
|
||||
public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
|
||||
AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable) {
|
||||
mConfig.reset();
|
||||
mConfig.userControlled = true;
|
||||
mConfig.duration = duration;
|
||||
return AnimatorPlaybackController.wrap(
|
||||
createAnimationToNewWorkspaceInternal(state, builder, null), duration);
|
||||
mConfig.playbackController = AnimatorPlaybackController.wrap(
|
||||
createAnimationToNewWorkspaceInternal(state, builder, null), duration,
|
||||
onCancelRunnable);
|
||||
return mConfig.playbackController;
|
||||
}
|
||||
|
||||
protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
|
||||
@@ -358,6 +360,12 @@ public class LauncherStateManager {
|
||||
mConfig.reset();
|
||||
}
|
||||
|
||||
public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
|
||||
setCurrentAnimation(controller.getTarget());
|
||||
mConfig.userControlled = true;
|
||||
mConfig.playbackController = controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the animation as the current state animation, i.e., canceled when
|
||||
* starting another animation and may block some launcher interactions while running.
|
||||
@@ -405,30 +413,39 @@ public class LauncherStateManager {
|
||||
public static class AnimationConfig extends AnimatorListenerAdapter {
|
||||
public long duration;
|
||||
public boolean userControlled;
|
||||
private PropertySetter mProperSetter;
|
||||
public AnimatorPlaybackController playbackController;
|
||||
private PropertySetter mPropertySetter;
|
||||
|
||||
private AnimatorSet mCurrentAnimation;
|
||||
private LauncherState mTargetState;
|
||||
|
||||
/**
|
||||
* Cancels the current animation and resets config variables.
|
||||
*/
|
||||
public void reset() {
|
||||
duration = 0;
|
||||
userControlled = false;
|
||||
mProperSetter = null;
|
||||
mPropertySetter = null;
|
||||
mTargetState = null;
|
||||
|
||||
if (mCurrentAnimation != null) {
|
||||
if (playbackController != null) {
|
||||
playbackController.getAnimationPlayer().cancel();
|
||||
playbackController.dispatchOnCancel();
|
||||
} else if (mCurrentAnimation != null) {
|
||||
mCurrentAnimation.setDuration(0);
|
||||
mCurrentAnimation.cancel();
|
||||
mCurrentAnimation = null;
|
||||
}
|
||||
|
||||
mCurrentAnimation = null;
|
||||
playbackController = null;
|
||||
}
|
||||
|
||||
public PropertySetter getProperSetter(AnimatorSetBuilder builder) {
|
||||
if (mProperSetter == null) {
|
||||
mProperSetter = duration == 0 ? NO_ANIM_PROPERTY_SETTER
|
||||
public PropertySetter getPropertySetter(AnimatorSetBuilder builder) {
|
||||
if (mPropertySetter == null) {
|
||||
mPropertySetter = duration == 0 ? NO_ANIM_PROPERTY_SETTER
|
||||
: new AnimatedPropertySetter(duration, builder);
|
||||
}
|
||||
return mProperSetter;
|
||||
return mPropertySetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -53,7 +53,7 @@ public class WorkspaceStateTransitionAnimation {
|
||||
|
||||
public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
|
||||
AnimationConfig config) {
|
||||
setWorkspaceProperty(toState, config.getProperSetter(builder));
|
||||
setWorkspaceProperty(toState, config.getPropertySetter(builder));
|
||||
}
|
||||
|
||||
public float getFinalScale() {
|
||||
|
||||
@@ -159,7 +159,7 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
|
||||
AnimatorSetBuilder builder, AnimationConfig config) {
|
||||
float targetProgress = toState.getVerticalProgress(mLauncher);
|
||||
if (Float.compare(mProgress, targetProgress) == 0) {
|
||||
setAlphas(toState, config.getProperSetter(builder));
|
||||
setAlphas(toState, config.getPropertySetter(builder));
|
||||
// Fail fast
|
||||
onProgressAnimationEnd();
|
||||
return;
|
||||
@@ -174,7 +174,7 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
|
||||
|
||||
builder.play(anim);
|
||||
|
||||
setAlphas(toState, config.getProperSetter(builder));
|
||||
setAlphas(toState, config.getPropertySetter(builder));
|
||||
}
|
||||
|
||||
private void setAlphas(LauncherState toState, PropertySetter setter) {
|
||||
|
||||
@@ -35,18 +35,23 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
|
||||
|
||||
public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
|
||||
return wrap(anim, duration, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation controller for the provided animation.
|
||||
* The actual duration does not matter as the animation is manually controlled. It just
|
||||
* 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) {
|
||||
public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
|
||||
Runnable onCancelRunnable) {
|
||||
|
||||
/**
|
||||
* TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
|
||||
*/
|
||||
return new AnimatorPlaybackControllerVL(anim, duration);
|
||||
return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
|
||||
}
|
||||
|
||||
private final ValueAnimator mAnimationPlayer;
|
||||
@@ -58,10 +63,13 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
|
||||
private Runnable mEndAction;
|
||||
|
||||
protected boolean mTargetCancelled = false;
|
||||
protected Runnable mOnCancelRunnable;
|
||||
|
||||
protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
|
||||
protected AnimatorPlaybackController(AnimatorSet anim, long duration,
|
||||
Runnable onCancelRunnable) {
|
||||
mAnim = anim;
|
||||
mDuration = duration;
|
||||
mOnCancelRunnable = onCancelRunnable;
|
||||
|
||||
mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
|
||||
mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
|
||||
@@ -72,6 +80,21 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mTargetCancelled = true;
|
||||
if (mOnCancelRunnable != null) {
|
||||
mOnCancelRunnable.run();
|
||||
mOnCancelRunnable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mTargetCancelled = false;
|
||||
mOnCancelRunnable = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mTargetCancelled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -163,12 +186,33 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
|
||||
}
|
||||
}
|
||||
|
||||
public void dispatchOnCancel() {
|
||||
dispatchOnCancelRecursively(mAnim);
|
||||
}
|
||||
|
||||
private void dispatchOnCancelRecursively(Animator animator) {
|
||||
for (AnimatorListener l : nonNullList(animator.getListeners())) {
|
||||
l.onAnimationCancel(animator);
|
||||
}
|
||||
|
||||
if (animator instanceof AnimatorSet) {
|
||||
for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
|
||||
dispatchOnCancelRecursively(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnCancelRunnable(Runnable runnable) {
|
||||
mOnCancelRunnable = runnable;
|
||||
}
|
||||
|
||||
public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
|
||||
|
||||
private final ValueAnimator[] mChildAnimations;
|
||||
|
||||
private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration) {
|
||||
super(anim, duration);
|
||||
private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
|
||||
Runnable onCancelRunnable) {
|
||||
super(anim, duration, onCancelRunnable);
|
||||
|
||||
// Build animation list
|
||||
ArrayList<ValueAnimator> childAnims = new ArrayList<>();
|
||||
|
||||
@@ -18,10 +18,7 @@ package com.android.launcher3.touch;
|
||||
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
|
||||
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
@@ -30,13 +27,13 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.PendingAnimation;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
/**
|
||||
* TouchController for handling state changes
|
||||
*/
|
||||
public abstract class AbstractStateChangeTouchController extends AnimatorListenerAdapter
|
||||
public abstract class AbstractStateChangeTouchController
|
||||
implements TouchController, SwipeDetector.Listener {
|
||||
|
||||
private static final String TAG = "ASCTouchController";
|
||||
@@ -146,8 +143,10 @@ public abstract class AbstractStateChangeTouchController extends AnimatorListene
|
||||
mToState = newToState;
|
||||
|
||||
mStartProgress = 0;
|
||||
if (mCurrentAnimation != null) {
|
||||
mCurrentAnimation.setOnCancelRunnable(null);
|
||||
}
|
||||
mProgressMultiplier = initCurrentAnimation();
|
||||
mCurrentAnimation.getTarget().addListener(this);
|
||||
mCurrentAnimation.dispatchOnStart();
|
||||
return true;
|
||||
}
|
||||
@@ -203,7 +202,6 @@ public abstract class AbstractStateChangeTouchController extends AnimatorListene
|
||||
targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
|
||||
}
|
||||
|
||||
|
||||
final float endProgress;
|
||||
final float startProgress;
|
||||
final long duration;
|
||||
@@ -220,6 +218,8 @@ public abstract class AbstractStateChangeTouchController extends AnimatorListene
|
||||
endProgress - Math.max(progress, 0));
|
||||
}
|
||||
} else {
|
||||
mCurrentAnimation.setOnCancelRunnable(null);
|
||||
mCurrentAnimation.dispatchOnCancel();
|
||||
endProgress = 0;
|
||||
if (progress <= 0) {
|
||||
duration = 0;
|
||||
@@ -236,6 +236,7 @@ public abstract class AbstractStateChangeTouchController extends AnimatorListene
|
||||
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
|
||||
anim.setFloatValues(startProgress, endProgress);
|
||||
updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
|
||||
mCurrentAnimation.dispatchOnStart();
|
||||
anim.start();
|
||||
}
|
||||
|
||||
@@ -275,13 +276,6 @@ public abstract class AbstractStateChangeTouchController extends AnimatorListene
|
||||
protected void clearState() {
|
||||
mCurrentAnimation = null;
|
||||
mDetector.finishedScrolling();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
|
||||
Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
|
||||
clearState();
|
||||
}
|
||||
mDetector.setDetectableScrollConditions(0, false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user