mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-01 16:26:47 +00:00
Add atomic recents animation while swiping up
State handlers can now specify atomic and non-atomic components of
their animations to states, which can be specified when creating a
new animation. There is now one atomic animation, when going from
NORMAL to OVERVIEW (and in reverse):
- RecentsViewStateController's animation (scale/alpha) is all atomic
- WorkspaceStateTransitionAnimation has atomic and non-atomic:
- Hotseat and workspace alpha is atomic, as is workspace scale
- Everything else (scrim, translation, qsb and drag handle alpha) is
non-atomic
- All apps progress is non-atomic
Also simplified dragging through overview; no longer pulls against you,
so we use an OvershootInterpolator when flinging instead of our custom
interpolator for the spring effect.
Bug: 76449024
Bug: 78089840
Change-Id: Iafac84d0c2b99ee9cf9dd5b30e2218286713b449
This commit is contained in:
@@ -18,16 +18,26 @@ package com.android.launcher3.touch;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
|
||||
import static com.android.launcher3.LauncherStateManager.ATOMIC_COMPONENT;
|
||||
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
|
||||
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.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
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.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.AnimatorSetBuilder;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
@@ -41,11 +51,17 @@ public abstract class AbstractStateChangeTouchController
|
||||
implements TouchController, SwipeDetector.Listener {
|
||||
|
||||
private static final String TAG = "ASCTouchController";
|
||||
public static final float RECATCH_REJECTION_FRACTION = .0875f;
|
||||
|
||||
// Progress after which the transition is assumed to be a success in case user does not fling
|
||||
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
|
||||
|
||||
/**
|
||||
* Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
|
||||
*/
|
||||
public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f;
|
||||
private static final long ATOMIC_NORMAL_TO_OVERVIEW_DURATION = 120;
|
||||
private static final long ATOMIC_OVERVIEW_TO_NORMAL_DURATION = 200;
|
||||
|
||||
protected final Launcher mLauncher;
|
||||
protected final SwipeDetector mDetector;
|
||||
|
||||
@@ -62,6 +78,11 @@ public abstract class AbstractStateChangeTouchController
|
||||
private float mProgressMultiplier;
|
||||
private float mDisplacementShift;
|
||||
|
||||
private AnimatorSet mAtomicAnim;
|
||||
private boolean mPassedOverviewAtomicThreshold;
|
||||
private boolean mCanBlockFling;
|
||||
private boolean mBlockFling;
|
||||
|
||||
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
|
||||
mLauncher = l;
|
||||
mDetector = new SwipeDetector(l, this, dir);
|
||||
@@ -83,14 +104,8 @@ public abstract class AbstractStateChangeTouchController
|
||||
boolean ignoreSlopWhenSettling = false;
|
||||
|
||||
if (mCurrentAnimation != null) {
|
||||
if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
|
||||
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
|
||||
} else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
|
||||
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
|
||||
} else {
|
||||
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
|
||||
ignoreSlopWhenSettling = true;
|
||||
}
|
||||
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
|
||||
ignoreSlopWhenSettling = true;
|
||||
} else {
|
||||
directionsToDetectScroll = getSwipeDirection();
|
||||
if (directionsToDetectScroll == 0) {
|
||||
@@ -138,7 +153,7 @@ public abstract class AbstractStateChangeTouchController
|
||||
protected abstract LauncherState getTargetState(LauncherState fromState,
|
||||
boolean isDragTowardPositive);
|
||||
|
||||
protected abstract float initCurrentAnimation();
|
||||
protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
|
||||
|
||||
/**
|
||||
* Returns the container that the touch started from when leaving NORMAL state.
|
||||
@@ -169,14 +184,28 @@ public abstract class AbstractStateChangeTouchController
|
||||
mToState = newToState;
|
||||
|
||||
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);
|
||||
}
|
||||
mProgressMultiplier = initCurrentAnimation();
|
||||
int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
|
||||
? NON_ATOMIC_COMPONENT : ANIM_ALL;
|
||||
mProgressMultiplier = initCurrentAnimation(animComponents);
|
||||
mCurrentAnimation.dispatchOnStart();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean goingBetweenNormalAndOverview(LauncherState fromState, LauncherState toState) {
|
||||
return (fromState == NORMAL || fromState == OVERVIEW)
|
||||
&& (toState == NORMAL || toState == OVERVIEW)
|
||||
&& mPendingAnimation == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStart(boolean start) {
|
||||
if (mCurrentAnimation == null) {
|
||||
@@ -187,6 +216,8 @@ public abstract class AbstractStateChangeTouchController
|
||||
mCurrentAnimation.pause();
|
||||
mStartProgress = mCurrentAnimation.getProgressFraction();
|
||||
}
|
||||
mCanBlockFling = mFromState == NORMAL;
|
||||
mBlockFling = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -198,17 +229,61 @@ public abstract class AbstractStateChangeTouchController
|
||||
if (progress <= 0) {
|
||||
if (reinitCurrentAnimation(false, isDragTowardPositive)) {
|
||||
mDisplacementShift = displacement;
|
||||
mBlockFling = mCanBlockFling;
|
||||
}
|
||||
} else if (progress >= 1) {
|
||||
if (reinitCurrentAnimation(true, isDragTowardPositive)) {
|
||||
mDisplacementShift = displacement;
|
||||
mBlockFling = mCanBlockFling;
|
||||
}
|
||||
} else if (Math.abs(velocity) < SwipeDetector.RELEASE_VELOCITY_PX_MS) {
|
||||
// We prevent flinging after passing a state, but allow it if the user pauses briefly.
|
||||
mBlockFling = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void updateProgress(float fraction) {
|
||||
mCurrentAnimation.setPlayFraction(fraction);
|
||||
maybeUpdateAtomicAnim(mFromState, mToState, fraction);
|
||||
}
|
||||
|
||||
/**
|
||||
* When going between normal and overview states, see if we passed the overview threshold and
|
||||
* play the appropriate atomic animation if so.
|
||||
*/
|
||||
private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
|
||||
float progress) {
|
||||
if (!goingBetweenNormalAndOverview(fromState, toState)) {
|
||||
return;
|
||||
}
|
||||
float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
|
||||
: 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
|
||||
boolean passedThreshold = progress >= threshold;
|
||||
if (passedThreshold != mPassedOverviewAtomicThreshold) {
|
||||
LauncherState targetState = passedThreshold ? toState : fromState;
|
||||
mPassedOverviewAtomicThreshold = passedThreshold;
|
||||
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
|
||||
: ATOMIC_OVERVIEW_TO_NORMAL_DURATION;
|
||||
for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
|
||||
handler.setStateWithAnimation(targetState, builder, config);
|
||||
}
|
||||
mAtomicAnim = builder.build();
|
||||
mAtomicAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAtomicAnim = null;
|
||||
}
|
||||
});
|
||||
mAtomicAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,6 +292,11 @@ public abstract class AbstractStateChangeTouchController
|
||||
final LauncherState targetState;
|
||||
final float progress = mCurrentAnimation.getProgressFraction();
|
||||
|
||||
boolean blockedFling = fling && mBlockFling;
|
||||
if (blockedFling) {
|
||||
fling = false;
|
||||
}
|
||||
|
||||
if (fling) {
|
||||
logAction = Touch.FLING;
|
||||
targetState =
|
||||
@@ -231,6 +311,8 @@ public abstract class AbstractStateChangeTouchController
|
||||
final float endProgress;
|
||||
final float startProgress;
|
||||
final long duration;
|
||||
// Increase the duration if we prevented the fling, as we are going against a high velocity.
|
||||
final long durationMultiplier = blockedFling && targetState == mFromState ? 6 : 1;
|
||||
|
||||
if (targetState == mToState) {
|
||||
endProgress = 1;
|
||||
@@ -241,7 +323,7 @@ public abstract class AbstractStateChangeTouchController
|
||||
startProgress = Utilities.boundToRange(
|
||||
progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
|
||||
duration = SwipeDetector.calculateDuration(velocity,
|
||||
endProgress - Math.max(progress, 0));
|
||||
endProgress - Math.max(progress, 0)) * durationMultiplier;
|
||||
}
|
||||
} else {
|
||||
// Let the state manager know that the animation didn't go to the target state,
|
||||
@@ -259,18 +341,35 @@ public abstract class AbstractStateChangeTouchController
|
||||
startProgress = Utilities.boundToRange(
|
||||
progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
|
||||
duration = SwipeDetector.calculateDuration(velocity,
|
||||
Math.min(progress, 1) - endProgress);
|
||||
Math.min(progress, 1) - endProgress) * durationMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
|
||||
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
|
||||
anim.setFloatValues(startProgress, endProgress);
|
||||
updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
|
||||
maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
|
||||
updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
|
||||
targetState, velocity, fling);
|
||||
mCurrentAnimation.dispatchOnStart();
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private long getRemainingAtomicDuration() {
|
||||
if (mAtomicAnim == null) {
|
||||
return 0;
|
||||
}
|
||||
if (Utilities.ATLEAST_OREO) {
|
||||
return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
|
||||
} else {
|
||||
long remainingDuration = 0;
|
||||
for (Animator anim : mAtomicAnim.getChildAnimations()) {
|
||||
remainingDuration = Math.max(remainingDuration, anim.getDuration());
|
||||
}
|
||||
return remainingDuration;
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
|
||||
LauncherState targetState, float velocity, boolean isFling) {
|
||||
animator.setDuration(expectedDuration)
|
||||
|
||||
Reference in New Issue
Block a user