Don't end atomic animation when passing through state

Previously we were ending the atomic animation with the assumption
that it should be complete/almost complete by the time you drag to
the next state. However, it is very easy to drag quickly enough where
that assumption doesn't hold, and thus you just see the atomic
animation pop to the end (i.e. recents showing without animation).

Now instead of ending the atomic animation, we let it continue. But
because the new state animation will have an atomic component that
interferes with the still playing atomic animation, we have to
control the atomic component separately; we control the non-atomic
components until the atomic animation ends, at which point we create
a separate controller to control the atomic components.

Bug: 76449024
Change-Id: Ia4bf19e26d0838f952d9e500fbdd8aba19856a41
This commit is contained in:
Tony Wickham 2018-05-09 10:13:01 -07:00
parent 6becf7c07d
commit fc564f1477
1 changed files with 88 additions and 15 deletions

View File

@ -28,6 +28,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
@ -36,6 +37,7 @@ import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
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.userevent.nano.LauncherLogProto.Action.Direction;
@ -77,11 +79,18 @@ public abstract class AbstractStateChangeTouchController
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
private float mDisplacementShift;
private boolean mCanBlockFling;
private boolean mBlockFling;
private AnimatorSet mAtomicAnim;
private boolean mPassedOverviewAtomicThreshold;
private boolean mCanBlockFling;
private boolean mBlockFling;
// mAtomicAnim plays the atomic components of the state animations when we pass the threshold.
// However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the
// atomic animation finishes, we only control the non-atomic components so that we don't
// interfere with the atomic animation. When the atomic animation ends, we start controlling
// the atomic components as well, using this controller.
private AnimatorPlaybackController mAtomicComponentsController;
private float mAtomicComponentsStartProgress;
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
mLauncher = l;
@ -185,16 +194,30 @@ public abstract class AbstractStateChangeTouchController
mStartProgress = 0;
mPassedOverviewAtomicThreshold = false;
if (mAtomicAnim != null) {
// Most likely the animation is finished by now, but just in case it's not,
// make sure the next state animation starts from the expected state.
mAtomicAnim.end();
}
if (mCurrentAnimation != null) {
mCurrentAnimation.setOnCancelRunnable(null);
}
int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
? NON_ATOMIC_COMPONENT : ANIM_ALL;
if (mAtomicAnim != null) {
// Control the non-atomic components until the atomic animation finishes, then control
// the atomic components as well.
animComponents = NON_ATOMIC_COMPONENT;
mAtomicAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animation) {
cancelAtomicComponentsController();
mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
long duration = (long) (getShiftRange() * 2);
mAtomicComponentsController = AnimatorPlaybackController.wrap(
createAtomicAnimForState(mToState, duration), duration);
mAtomicComponentsController.dispatchOnStart();
}
});
}
if (goingBetweenNormalAndOverview(mFromState, mToState)) {
cancelAtomicComponentsController();
}
mProgressMultiplier = initCurrentAnimation(animComponents);
mCurrentAnimation.dispatchOnStart();
return true;
@ -210,6 +233,7 @@ public abstract class AbstractStateChangeTouchController
public void onDragStart(boolean start) {
if (mCurrentAnimation == null) {
mFromState = mToState = null;
mAtomicComponentsController = null;
reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
mDisplacementShift = 0;
} else {
@ -246,6 +270,9 @@ public abstract class AbstractStateChangeTouchController
protected void updateProgress(float fraction) {
mCurrentAnimation.setPlayFraction(fraction);
if (mAtomicComponentsController != null) {
mAtomicComponentsController.setPlayFraction(fraction - mAtomicComponentsStartProgress);
}
maybeUpdateAtomicAnim(mFromState, mToState, fraction);
}
@ -267,15 +294,9 @@ public abstract class AbstractStateChangeTouchController
if (mAtomicAnim != null) {
mAtomicAnim.cancel();
}
AnimatorSetBuilder builder = new AnimatorSetBuilder();
AnimationConfig config = new AnimationConfig();
config.animComponents = ATOMIC_COMPONENT;
config.duration = targetState == OVERVIEW ? ATOMIC_NORMAL_TO_OVERVIEW_DURATION
long duration = targetState == OVERVIEW ? ATOMIC_NORMAL_TO_OVERVIEW_DURATION
: ATOMIC_OVERVIEW_TO_NORMAL_DURATION;
for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
handler.setStateWithAnimation(targetState, builder, config);
}
mAtomicAnim = builder.build();
mAtomicAnim = createAtomicAnimForState(targetState, duration);
mAtomicAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@ -283,9 +304,21 @@ public abstract class AbstractStateChangeTouchController
}
});
mAtomicAnim.start();
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
}
}
private AnimatorSet createAtomicAnimForState(LauncherState targetState, long duration) {
AnimatorSetBuilder builder = new AnimatorSetBuilder();
AnimationConfig config = new AnimationConfig();
config.animComponents = ATOMIC_COMPONENT;
config.duration = duration;
for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
handler.setStateWithAnimation(targetState, builder, config);
}
return builder.build();
}
@Override
public void onDragEnd(float velocity, boolean fling) {
final int logAction;
@ -353,6 +386,38 @@ public abstract class AbstractStateChangeTouchController
targetState, velocity, fling);
mCurrentAnimation.dispatchOnStart();
anim.start();
if (mAtomicAnim == null) {
startAtomicComponentsAnim(endProgress, anim.getDuration());
} else {
mAtomicAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
startAtomicComponentsAnim(endProgress, anim.getDuration());
}
});
}
}
/**
* Animates the atomic components from the current progress to the final progress.
*
* Note that this only applies when we are controlling the atomic components separately from
* the non-atomic components, which only happens if we reinit before the atomic animation
* finishes.
*/
private void startAtomicComponentsAnim(float toProgress, long duration) {
if (mAtomicComponentsController != null) {
ValueAnimator atomicAnim = mAtomicComponentsController.getAnimationPlayer();
atomicAnim.setFloatValues(mAtomicComponentsController.getProgressFraction(), toProgress);
atomicAnim.setDuration(duration);
atomicAnim.start();
atomicAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAtomicComponentsController = null;
}
});
}
}
private long getRemainingAtomicDuration() {
@ -409,7 +474,15 @@ public abstract class AbstractStateChangeTouchController
protected void clearState() {
mCurrentAnimation = null;
cancelAtomicComponentsController();
mDetector.finishedScrolling();
mDetector.setDetectableScrollConditions(0, false);
}
private void cancelAtomicComponentsController() {
if (mAtomicComponentsController != null) {
mAtomicComponentsController.getAnimationPlayer().cancel();
mAtomicComponentsController = null;
}
}
}