diff options
author | Tony Wickham <twickham@google.com> | 2018-04-19 11:39:34 -0700 |
---|---|---|
committer | Tony Wickham <twickham@google.com> | 2018-05-09 12:33:46 -0700 |
commit | 6becf7c07d4714369d40dcd575a99855894c0f8a (patch) | |
tree | 3c00366778c8ff372719ec58769648e4bb0a3a36 | |
parent | 639b07178f6412cd931818c7787c5f6effd42317 (diff) | |
download | android_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
13 files changed, 241 insertions, 147 deletions
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java index e31805c7e..b0f8d99b1 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java @@ -84,7 +84,7 @@ public class AllAppsState extends LauncherState { @Override public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) { - return new float[] {1f, -0.2f}; + return new float[] {0.9f, -0.2f}; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java index 42f6c74dc..ce8192fc8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java @@ -9,6 +9,7 @@ import android.view.MotionEvent; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager.AnimationComponents; import com.android.launcher3.touch.AbstractStateChangeTouchController; import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -56,11 +57,11 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro } @Override - protected float initCurrentAnimation() { + protected float initCurrentAnimation(@AnimationComponents int animComponent) { float range = getShiftRange(); long maxAccuracy = (long) (2 * range); - mCurrentAnimation = mLauncher.getStateManager() - .createAnimationToNewWorkspace(mToState, maxAccuracy); + mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState, + maxAccuracy, animComponent); return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java index 91e1e7faa..eaf8aa043 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java @@ -25,6 +25,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.quickstep.views.RecentsView; @@ -47,8 +48,12 @@ public class OverviewState extends LauncherState { @Override public float[] getWorkspaceScaleAndTranslation(Launcher launcher) { - // TODO: provide a valid value - return new float[]{1, 0, -launcher.getDeviceProfile().hotseatBarSizePx / 2}; + RecentsView recentsView = launcher.getOverviewPanel(); + Workspace workspace = launcher.getWorkspace(); + recentsView.getTaskSize(sTempRect); + float scale = (float) sTempRect.width() / workspace.getWidth(); + float parallaxFactor = 0.4f; + return new float[]{scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor}; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java index 2f0bdc6b3..514c0e8bd 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java @@ -18,20 +18,20 @@ package com.android.launcher3.uioverrides; 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.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; -import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.view.MotionEvent; import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager.AnimationComponents; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; @@ -52,43 +52,11 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr private static final String TAG = "PortraitStatesTouchCtrl"; - private static final float TOTAL_DISTANCE_MULTIPLIER = 3f; - private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER; - - // Must be greater than LINEAR_SCALE_LIMIT; - private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f; - - // Maximum amount to overshoot. - private static final float MAX_OVERSHOOT = 0.3f; - - private static final double PI_BY_2 = Math.PI / 2; - private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper(); // If true, we will finish the current animation instantly on second touch. private boolean mFinishFastOnSecondTouch; - private final Interpolator mAllAppsDampedInterpolator = new Interpolator() { - - private final double mAngleMultiplier = Math.PI / - (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT)); - - @Override - public float getInterpolation(float v) { - if (v <= LINEAR_SCALE_LIMIT) { - return v * TOTAL_DISTANCE_MULTIPLIER; - } - float overshoot = (v - LINEAR_SCALE_LIMIT); - return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier)); - } - }; - - private final Interpolator mOverviewBoundInterpolator = (v) -> { - if (v >= MAXIMUM_DISTANCE_FACTOR) { - return 1; - } - return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR); - }; public PortraitStatesTouchController(Launcher l) { super(l, SwipeDetector.VERTICAL); @@ -144,17 +112,16 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr } private AnimatorSetBuilder getNormalToOverviewAnimation() { - mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator; + mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR; AnimatorSetBuilder builder = new AnimatorSetBuilder(); builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper); - builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator); return builder; } @Override - protected float initCurrentAnimation() { + protected float initCurrentAnimation(@AnimationComponents int animComponents) { float range = getShiftRange(); long maxAccuracy = (long) (2 * range); @@ -167,7 +134,6 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) { builder = getNormalToOverviewAnimation(); - totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER; } else { builder = new AnimatorSetBuilder(); } @@ -190,7 +156,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation); } else { mCurrentAnimation = mLauncher.getStateManager() - .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState); + .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState, + animComponents); } if (totalShift == 0) { @@ -210,9 +177,9 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr @Override protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling) { - handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling); super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity, isFling); + handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling); } private void handleFirstSwipeToOverview(final ValueAnimator animator, @@ -220,62 +187,22 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr final boolean isFling) { if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) { mFinishFastOnSecondTouch = true; - - // Update all apps interpolator - float currentFraction = mCurrentAnimation.getProgressFraction(); - float absVelocity = Math.abs(velocity); - float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction); - - if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) { - - // TODO: Clean up these magic calculations - // Linearly interpolate the max value based on the velocity. - float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT : - 1 + (absVelocity - 1) * MAX_OVERSHOOT / 3, - currentValue); - double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue); - - if (expectedDuration != 0 && angleToPeak != 0) { - - float distanceLeft = 1 - currentFraction; - mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> { - float scaledF = (f - currentFraction) / distanceLeft; - - if (scaledF < 0.5f) { - double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f; - return (float) (maxValue * Math.sin(angle)); - } - - scaledF = ((scaledF - .5f) / .5f); - double angle = PI_BY_2 + 3 * scaledF * PI_BY_2; - float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1); - return 1 + (float) (amplitude * Math.sin(angle)); - }; - - animator.setDuration(expectedDuration).setInterpolator(LINEAR); - return; - } - } - - if (currentFraction < LINEAR_SCALE_LIMIT) { - mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR; - return; + if (isFling && expectedDuration != 0) { + // Update all apps interpolator to add a bit of overshoot starting from currFraction + final float currFraction = mCurrentAnimation.getProgressFraction(); + mAllAppsInterpolatorWrapper.baseInterpolator + = new OvershootInterpolator(Math.min(Math.abs(velocity) / 3, 3f)) { + @Override + public float getInterpolation(float t) { + return super.getInterpolation(t) + ((1 - t) * currFraction); + } + }; + animator.setFloatValues(0, 1); + animator.setDuration(Math.max(expectedDuration, 300)).setInterpolator(LINEAR); } - float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1; - float distanceLeft = 1 - currentFraction; - - animator.setFloatValues(currentFraction, 1); - mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> { - float scaledF = (f - currentFraction) / distanceLeft; - - double angle = scaledF * 1.5 * Math.PI; - float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue; - return 1 + (float) (amplitude * Math.sin(angle)); - }; - animator.setDuration(200).setInterpolator(LINEAR); - return; + } else { + mFinishFastOnSecondTouch = false; } - mFinishFastOnSecondTouch = false; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 2579bc293..febe36034 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.uioverrides; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR; @@ -63,12 +62,14 @@ public class RecentsViewStateController implements StateHandler { @Override public void setStateWithAnimation(final LauncherState toState, AnimatorSetBuilder builder, AnimationConfig config) { + if (!config.playAtomicComponent()) { + // The entire recents animation is played atomically. + return; + } PropertySetter setter = config.getPropertySetter(builder); float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher); - setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0], - builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR)); - setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1], - builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR)); + setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0], LINEAR); + setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1], LINEAR); setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0, AGGRESSIVE_EASE_IN_OUT); 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) diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java index 860be5ff7..80c2485fe 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java @@ -8,6 +8,7 @@ import android.view.MotionEvent; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager.AnimationComponents; import com.android.launcher3.touch.AbstractStateChangeTouchController; import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -62,11 +63,11 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } @Override - protected float initCurrentAnimation() { + protected float initCurrentAnimation(@AnimationComponents int animComponents) { float range = getShiftRange(); long maxAccuracy = (long) (2 * range); mCurrentAnimation = mLauncher.getStateManager() - .createAnimationToNewWorkspace(mToState, maxAccuracy); + .createAnimationToNewWorkspace(mToState, maxAccuracy, animComponents); float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range; float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range; float totalShift = endVerticalShift - startVerticalShift; |