summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorTony Wickham <twickham@google.com>2018-04-19 11:39:34 -0700
committerTony Wickham <twickham@google.com>2018-05-09 12:33:46 -0700
commit6becf7c07d4714369d40dcd575a99855894c0f8a (patch)
tree3c00366778c8ff372719ec58769648e4bb0a3a36 /src/com
parent639b07178f6412cd931818c7787c5f6effd42317 (diff)
downloadandroid_packages_apps_Trebuchet-6becf7c07d4714369d40dcd575a99855894c0f8a.tar.gz
android_packages_apps_Trebuchet-6becf7c07d4714369d40dcd575a99855894c0f8a.tar.bz2
android_packages_apps_Trebuchet-6becf7c07d4714369d40dcd575a99855894c0f8a.zip
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
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/launcher3/LauncherState.java3
-rw-r--r--src/com/android/launcher3/LauncherStateManager.java40
-rw-r--r--src/com/android/launcher3/Workspace.java1
-rw-r--r--src/com/android/launcher3/WorkspaceStateTransitionAnimation.java58
-rw-r--r--src/com/android/launcher3/allapps/AllAppsTransitionController.java5
-rw-r--r--src/com/android/launcher3/anim/AnimatorSetBuilder.java5
-rw-r--r--src/com/android/launcher3/touch/AbstractStateChangeTouchController.java127
7 files changed, 199 insertions, 40 deletions
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index f548095ef..6790b7f0c 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,6 +21,7 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CH
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;
@@ -86,6 +87,8 @@ public class LauncherState {
public static final LauncherState FAST_OVERVIEW = new FastOverviewState(3);
public static final LauncherState ALL_APPS = new AllAppsState(4);
+ protected static final Rect sTempRect = new Rect();
+
public final int ordinal;
/**
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 1b9ac2151..d04401e5c 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
+import android.support.annotation.IntDef;
import android.view.View;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -33,6 +34,8 @@ import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
import com.android.launcher3.uioverrides.UiFactory;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -80,6 +83,21 @@ public class LauncherStateManager {
public static final String TAG = "StateManager";
+ // We separate the state animations into "atomic" and "non-atomic" components. The atomic
+ // components may be run atomically - that is, all at once, instead of user-controlled. However,
+ // atomic components are not restricted to this purpose; they can be user-controlled alongside
+ // non atomic components as well.
+ @IntDef(flag = true, value = {
+ NON_ATOMIC_COMPONENT,
+ ATOMIC_COMPONENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnimationComponents {}
+ public static final int NON_ATOMIC_COMPONENT = 1 << 0;
+ public static final int ATOMIC_COMPONENT = 1 << 1;
+
+ public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_COMPONENT;
+
private final AnimationConfig mConfig = new AnimationConfig();
private final Handler mUiHandler;
private final Launcher mLauncher;
@@ -238,13 +256,21 @@ public class LauncherStateManager {
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
LauncherState state, long duration) {
- return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null);
+ return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
+ }
+
+ public AnimatorPlaybackController createAnimationToNewWorkspace(
+ LauncherState state, long duration, @AnimationComponents int animComponents) {
+ return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
+ animComponents);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
- AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable) {
+ AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
+ @AnimationComponents int animComponents) {
mConfig.reset();
mConfig.userControlled = true;
+ mConfig.animComponents = animComponents;
mConfig.duration = duration;
mConfig.playbackController = AnimatorPlaybackController.wrap(
createAnimationToNewWorkspaceInternal(state, builder, null), duration,
@@ -425,6 +451,7 @@ public class LauncherStateManager {
public long duration;
public boolean userControlled;
public AnimatorPlaybackController playbackController;
+ public @AnimationComponents int animComponents = ANIM_ALL;
private PropertySetter mPropertySetter;
private AnimatorSet mCurrentAnimation;
@@ -436,6 +463,7 @@ public class LauncherStateManager {
public void reset() {
duration = 0;
userControlled = false;
+ animComponents = ANIM_ALL;
mPropertySetter = null;
mTargetState = null;
@@ -471,6 +499,14 @@ public class LauncherStateManager {
mTargetState = targetState;
mCurrentAnimation.addListener(this);
}
+
+ public boolean playAtomicComponent() {
+ return (animComponents & ATOMIC_COMPONENT) != 0;
+ }
+
+ public boolean playNonAtomicComponent() {
+ return (animComponents & NON_ATOMIC_COMPONENT) != 0;
+ }
}
public interface StateHandler {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index bd7e6eb5c..720c57466 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -82,7 +82,6 @@ import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.touch.WorkspaceTouchListener;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8d3d459cc..55968db02 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -24,6 +24,7 @@ import static com.android.launcher3.LauncherState.HOTSEAT_SEARCH_BOX;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -48,12 +49,12 @@ public class WorkspaceStateTransitionAnimation {
}
public void setState(LauncherState toState) {
- setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
+ setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER, new AnimationConfig());
}
public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
AnimationConfig config) {
- setWorkspaceProperty(toState, config.getPropertySetter(builder));
+ setWorkspaceProperty(toState, config.getPropertySetter(builder), config);
}
public float getFinalScale() {
@@ -63,28 +64,40 @@ public class WorkspaceStateTransitionAnimation {
/**
* Starts a transition animation for the workspace.
*/
- private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter) {
+ private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
+ AnimationConfig config) {
float[] scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
mNewScale = scaleAndTranslation[0];
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
final int childCount = mWorkspace.getChildCount();
for (int i = 0; i < childCount; i++) {
applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, pageAlphaProvider,
- propertySetter);
+ propertySetter, config);
}
- propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
- propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
- scaleAndTranslation[1], Interpolators.ZOOM_OUT);
- propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
- scaleAndTranslation[2], Interpolators.ZOOM_OUT);
int elements = state.getVisibleElements(mLauncher);
- float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
- propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
- pageAlphaProvider.interpolator);
- propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
- hotseatIconsAlpha, pageAlphaProvider.interpolator);
+ boolean playAtomicComponent = config.playAtomicComponent();
+ if (playAtomicComponent) {
+ propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
+ float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
+ propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
+ pageAlphaProvider.interpolator);
+ propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
+ hotseatIconsAlpha, pageAlphaProvider.interpolator);
+ }
+
+ if (!config.playNonAtomicComponent()) {
+ // Only the alpha and scale, handled above, are included in the atomic animation.
+ return;
+ }
+
+ Interpolator translationInterpolator = !playAtomicComponent ? Interpolators.LINEAR
+ : Interpolators.ZOOM_OUT;
+ propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
+ scaleAndTranslation[1], translationInterpolator);
+ propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
+ scaleAndTranslation[2], translationInterpolator);
propertySetter.setViewAlpha(mLauncher.getHotseatSearchBox(),
(elements & HOTSEAT_SEARCH_BOX) != 0 ? 1 : 0,
@@ -101,17 +114,22 @@ public class WorkspaceStateTransitionAnimation {
public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
applyChildState(state, cl, childIndex, state.getWorkspacePageAlphaProvider(mLauncher),
- NO_ANIM_PROPERTY_SETTER);
+ NO_ANIM_PROPERTY_SETTER, new AnimationConfig());
}
private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
- PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter) {
+ PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
+ AnimationConfig config) {
float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
- propertySetter.setInt(cl.getScrimBackground(),
- DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
- propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
- pageAlpha, pageAlphaProvider.interpolator);
+ if (config.playNonAtomicComponent()) {
+ propertySetter.setInt(cl.getScrimBackground(),
+ DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
+ }
+ if (config.playAtomicComponent()) {
+ propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+ pageAlpha, pageAlphaProvider.interpolator);
+ }
}
} \ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 6d70a08bd..4a0b62209 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -168,6 +168,11 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
return;
}
+ if (!config.playNonAtomicComponent()) {
+ // There is no atomic component for the all apps transition, so just return early.
+ return;
+ }
+
Interpolator interpolator = config.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
ObjectAnimator anim =
ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index b209a2dee..dae2dbc0a 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -32,7 +32,6 @@ import java.util.List;
public class AnimatorSetBuilder {
public static final int ANIM_VERTICAL_PROGRESS = 0;
- public static final int ANIM_OVERVIEW_TRANSLATION = 1;
protected final ArrayList<Animator> mAnims = new ArrayList<>();
@@ -56,9 +55,9 @@ public class AnimatorSetBuilder {
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
anim.playTogether(mAnims);
if (!mOnFinishRunnables.isEmpty()) {
- anim.addListener(new AnimatorListenerAdapter() {
+ anim.addListener(new AnimationSuccessListener() {
@Override
- public void onAnimationEnd(Animator animation) {
+ public void onAnimationSuccess(Animator animation) {
for (Runnable onFinishRunnable : mOnFinishRunnables) {
onFinishRunnable.run();
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index bad4976e4..977572ea4 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -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)