diff options
author | Jon Miranda <jonmiranda@google.com> | 2018-12-13 12:34:52 -0800 |
---|---|---|
committer | Jon Miranda <jonmiranda@google.com> | 2019-01-04 11:12:53 -0800 |
commit | e018711aac98522f2b24cb01cd95e17125aaf178 (patch) | |
tree | 280b2c15294254547f7ea4cc7866e5edc33a948b /src/com/android | |
parent | 1c8db791c87575e47b3ef20f8ad247caa5f2a2c2 (diff) | |
download | android_packages_apps_Trebuchet-e018711aac98522f2b24cb01cd95e17125aaf178.tar.gz android_packages_apps_Trebuchet-e018711aac98522f2b24cb01cd95e17125aaf178.tar.bz2 android_packages_apps_Trebuchet-e018711aac98522f2b24cb01cd95e17125aaf178.zip |
Add spring to shelf for home <-> overview <-> all apps state transitions.
Added new SpringObjectAnimator class that wraps an ObjectAnimator so the
Object can be controlled via the Animator or via a SpringAnimation. It extends
ValueAnimator so that it remains compatible with AnimatorPlaybackController.
Code is behind feature flag toggle QUICKSTEP_SPRINGS.
Bug: 111698021
Change-Id: I1b20179ede37e89a6a6bb2a45d407cc74c99ac4e
Diffstat (limited to 'src/com/android')
5 files changed, 397 insertions, 11 deletions
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index ffbf34c7b..962c25bf5 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -1,7 +1,6 @@ package com.android.launcher3.allapps; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; -import static com.android.launcher3.LauncherState.ALL_APPS_HEADER; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; @@ -15,9 +14,7 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.util.Property; -import android.view.View; import android.view.animation.Interpolator; import com.android.launcher3.DeviceProfile; @@ -29,10 +26,15 @@ import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.anim.PropertySetter; +import com.android.launcher3.anim.SpringObjectAnimator.SpringProperty; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; + /** * Handles AllApps view transition. * 1) Slides all apps view using direct manipulation @@ -59,6 +61,53 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil } }; + public static final FloatPropertyCompat<AllAppsTransitionController> ALL_APPS_PROGRESS_SPRING + = new FloatPropertyCompat<AllAppsTransitionController>("allAppsProgressSpring") { + @Override + public float getValue(AllAppsTransitionController controller) { + return controller.mProgress; + } + + @Override + public void setValue(AllAppsTransitionController controller, float progress) { + controller.setProgress(progress); + } + }; + + /** + * Property that either sets the progress directly or animates the progress via a spring. + */ + public static class AllAppsSpringProperty extends + SpringProperty<AllAppsTransitionController, Float> { + + SpringAnimation mSpring; + boolean useSpring = false; + + public AllAppsSpringProperty(SpringAnimation spring) { + super(Float.class, "allAppsSpringProperty"); + mSpring = spring; + } + + @Override + public Float get(AllAppsTransitionController controller) { + return controller.getProgress(); + } + + @Override + public void set(AllAppsTransitionController controller, Float progress) { + if (useSpring) { + mSpring.animateToFinalPosition(progress); + } else { + controller.setProgress(progress); + } + } + + @Override + public void switchToSpring() { + useSpring = true; + } + } + private AllAppsContainerView mAppsView; private ScrimView mScrimView; @@ -174,8 +223,8 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN) : FAST_OUT_SLOW_IN; - ObjectAnimator anim = - ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress); + Animator anim = new SpringObjectAnimator(this, 1f / mShiftRange, mProgress, + targetProgress); anim.setDuration(config.duration); anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator)); anim.addListener(getProgressAnimatorListener()); diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 819c8439b..62f59e402 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -16,6 +16,7 @@ package com.android.launcher3.anim; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import android.animation.Animator; import android.animation.Animator.AnimatorListener; @@ -23,10 +24,16 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; +import android.util.Log; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; /** * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators @@ -37,6 +44,9 @@ import java.util.List; */ public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener { + private static final String TAG = "AnimatorPlaybackCtrler"; + private static boolean DEBUG = false; + public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) { return wrap(anim, duration, null); } @@ -60,6 +70,7 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat private final long mDuration; protected final AnimatorSet mAnim; + private Set<SpringAnimation> mSprings; protected float mCurrentFraction; private Runnable mEndAction; @@ -67,6 +78,9 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat protected boolean mTargetCancelled = false; protected Runnable mOnCancelRunnable; + private OnAnimationEndDispatcher mEndListener; + private DynamicAnimation.OnAnimationEndListener mSpringEndListener; + protected AnimatorPlaybackController(AnimatorSet anim, long duration, Runnable onCancelRunnable) { mAnim = anim; @@ -75,7 +89,8 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mAnimationPlayer = ValueAnimator.ofFloat(0, 1); mAnimationPlayer.setInterpolator(LINEAR); - mAnimationPlayer.addListener(new OnAnimationEndDispatcher()); + mEndListener = new OnAnimationEndDispatcher(); + mAnimationPlayer.addListener(mEndListener); mAnimationPlayer.addUpdateListener(this); mAnim.addListener(new AnimatorListenerAdapter() { @@ -99,6 +114,15 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat mTargetCancelled = false; } }); + + mSprings = new HashSet<>(); + mSpringEndListener = (animation, canceled, value, velocity1) -> { + if (canceled) { + mEndListener.onAnimationCancel(mAnimationPlayer); + } else { + mEndListener.onAnimationEnd(mAnimationPlayer); + } + }; } public AnimatorSet getTarget() { @@ -180,6 +204,29 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + /** + * Starts playback and sets the spring. + */ + public void dispatchOnStartWithVelocity(float end, float velocity) { + if (!QUICKSTEP_SPRINGS.get()) { + dispatchOnStart(); + return; + } + + if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity); + + for (Animator a : mAnim.getChildAnimations()) { + if (a instanceof SpringObjectAnimator) { + if (DEBUG) Log.d(TAG, "Found springAnimator=" + a); + SpringObjectAnimator springAnimator = (SpringObjectAnimator) a; + mSprings.add(springAnimator.getSpring()); + springAnimator.startSpring(end, velocity, mSpringEndListener); + } + } + + dispatchOnStart(); + } + public void dispatchOnStart() { dispatchOnStartRecursively(mAnim); } @@ -282,6 +329,18 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat } } + private boolean isAnySpringRunning() { + for (SpringAnimation spring : mSprings) { + if (spring.isRunning()) { + return true; + } + } + return false; + } + + /** + * Only dispatches the on end actions once the animator and all springs have completed running. + */ private class OnAnimationEndDispatcher extends AnimationSuccessListener { @Override @@ -291,9 +350,12 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat @Override public void onAnimationSuccess(Animator animator) { - dispatchOnEndRecursively(mAnim); - if (mEndAction != null) { - mEndAction.run(); + // We wait for the spring (if any) to finish running before completing the end callback. + if (mSprings.isEmpty() || !isAnySpringRunning()) { + dispatchOnEndRecursively(mAnim); + if (mEndAction != null) { + mEndAction.run(); + } } } diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java new file mode 100644 index 000000000..1e365707c --- /dev/null +++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.anim; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.util.Log; +import android.util.Property; + +import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.allapps.AllAppsTransitionController.AllAppsSpringProperty; + +import java.util.ArrayList; + +import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +/** + * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or + * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet. + */ +public class SpringObjectAnimator extends ValueAnimator { + + private static final String TAG = "SpringObjectAnimator"; + private static boolean DEBUG = false; + + private AllAppsTransitionController mObject; + private ObjectAnimator mObjectAnimator; + private float[] mValues; + + private SpringAnimation mSpring; + private AllAppsSpringProperty mProperty; + + private ArrayList<AnimatorListener> mListeners; + private boolean mSpringEnded = false; + private boolean mAnimatorEnded = false; + private boolean mEnded = false; + + private static final float SPRING_DAMPING_RATIO = 0.9f; + private static final float SPRING_STIFFNESS = 600f; + + public SpringObjectAnimator(AllAppsTransitionController object, float minimumVisibleChange, + float... values) { + mObject = object; + mSpring = new SpringAnimation(object, AllAppsTransitionController.ALL_APPS_PROGRESS_SPRING); + mSpring.setMinimumVisibleChange(minimumVisibleChange); + mSpring.setSpring(new SpringForce(0) + .setDampingRatio(SPRING_DAMPING_RATIO) + .setStiffness(SPRING_STIFFNESS)); + mSpring.setStartVelocity(0.01f); + mProperty = new AllAppsSpringProperty(mSpring); + mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values); + mValues = values; + mListeners = new ArrayList<>(); + setFloatValues(values); + + mObjectAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mAnimatorEnded = false; + mEnded = false; + for (AnimatorListener l : mListeners) { + l.onAnimationStart(animation); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimatorEnded = true; + tryEnding(); + } + + @Override + public void onAnimationCancel(Animator animation) { + for (AnimatorListener l : mListeners) { + l.onAnimationCancel(animation); + } + mSpring.animateToFinalPosition(mObject.getProgress()); + } + }); + + mSpring.addUpdateListener((animation, value, velocity) -> mSpringEnded = false); + mSpring.addEndListener((animation, canceled, value, velocity) -> { + mSpringEnded = true; + tryEnding(); + }); + } + + private void tryEnding() { + if (DEBUG) { + Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded=" + + mSpringEnded + ", mEnded=" + mEnded); + } + + if (mAnimatorEnded && mSpringEnded && !mEnded) { + for (AnimatorListener l : mListeners) { + l.onAnimationEnd(mObjectAnimator); + } + mEnded = true; + } + } + + public SpringAnimation getSpring() { + return mSpring; + } + + /** + * Initializes and sets up the spring to take over controlling the object. + */ + void startSpring(float end, float velocity, OnAnimationEndListener endListener) { + // Cancel the spring so we can set new start velocity and final position. We need to remove + // the listener since the spring is not actually ending. + mSpring.removeEndListener(endListener); + mSpring.cancel(); + mSpring.addEndListener(endListener); + + mProperty.switchToSpring(); + + mSpring.setStartVelocity(velocity); + mSpring.animateToFinalPosition(end == 0 ? mValues[0] : mValues[1]); + } + + @Override + public void addListener(AnimatorListener listener) { + mListeners.add(listener); + } + + @Override + public void addPauseListener(AnimatorPauseListener listener) { + mObjectAnimator.addPauseListener(listener); + } + + @Override + public void cancel() { + mSpring.animateToFinalPosition(mObject.getProgress()); + mObjectAnimator.cancel(); + } + + @Override + public void end() { + mObjectAnimator.end(); + } + + @Override + public long getDuration() { + return mObjectAnimator.getDuration(); + } + + @Override + public TimeInterpolator getInterpolator() { + return mObjectAnimator.getInterpolator(); + } + + @Override + public ArrayList<AnimatorListener> getListeners() { + return mObjectAnimator.getListeners(); + } + + @Override + public long getStartDelay() { + return mObjectAnimator.getStartDelay(); + } + + @Override + public long getTotalDuration() { + return mObjectAnimator.getTotalDuration(); + } + + @Override + public boolean isPaused() { + return mObjectAnimator.isPaused(); + } + + @Override + public boolean isRunning() { + return mObjectAnimator.isRunning(); + } + + @Override + public boolean isStarted() { + return mObjectAnimator.isStarted(); + } + + @Override + public void pause() { + mObjectAnimator.pause(); + } + + @Override + public void removeAllListeners() { + mObjectAnimator.removeAllListeners(); + } + + @Override + public void removeListener(AnimatorListener listener) { + mObjectAnimator.removeListener(listener); + } + + @Override + public void removePauseListener(AnimatorPauseListener listener) { + mObjectAnimator.removePauseListener(listener); + } + + @Override + public void resume() { + mObjectAnimator.resume(); + } + + @Override + public ValueAnimator setDuration(long duration) { + return mObjectAnimator.setDuration(duration); + } + + @Override + public void setInterpolator(TimeInterpolator value) { + mObjectAnimator.setInterpolator(value); + } + + @Override + public void setStartDelay(long startDelay) { + mObjectAnimator.setStartDelay(startDelay); + } + + @Override + public void setTarget(Object target) { + mObjectAnimator.setTarget(target); + } + + @Override + public void start() { + mObjectAnimator.start(); + } + + @Override + public void setCurrentFraction(float fraction) { + mObjectAnimator.setCurrentFraction(fraction); + } + + @Override + public void setCurrentPlayTime(long playTime) { + mObjectAnimator.setCurrentPlayTime(playTime); + } + + public static abstract class SpringProperty<T, V> extends Property<T, V> { + + public SpringProperty(Class<V> type, String name) { + super(type, name); + } + + abstract public void switchToSpring(); + } + +} diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index b01e41ea9..3a7c949a6 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -98,6 +98,9 @@ abstract class BaseFlags { public static final TogglableFlag ENABLE_TASK_STABILIZER = new TogglableFlag( "ENABLE_TASK_STABILIZER", false, "Stable task list across fast task switches"); + public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS", + false, "Enable springs for quickstep animations"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index a7bd243a6..bb143288c 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -24,6 +24,7 @@ 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 static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -45,6 +46,7 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.compat.AccessibilityManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; @@ -428,8 +430,8 @@ public abstract class AbstractStateChangeTouchController maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f); updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()), targetState, velocity, fling); - mCurrentAnimation.dispatchOnStart(); - if (fling && targetState == LauncherState.ALL_APPS) { + mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity); + if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) { mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity); } anim.start(); |