summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/anim
diff options
context:
space:
mode:
authorJon Miranda <jonmiranda@google.com>2018-12-13 12:34:52 -0800
committerJon Miranda <jonmiranda@google.com>2019-01-04 11:12:53 -0800
commite018711aac98522f2b24cb01cd95e17125aaf178 (patch)
tree280b2c15294254547f7ea4cc7866e5edc33a948b /src/com/android/launcher3/anim
parent1c8db791c87575e47b3ef20f8ad247caa5f2a2c2 (diff)
downloadandroid_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/launcher3/anim')
-rw-r--r--src/com/android/launcher3/anim/AnimatorPlaybackController.java70
-rw-r--r--src/com/android/launcher3/anim/SpringObjectAnimator.java270
2 files changed, 336 insertions, 4 deletions
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();
+ }
+
+}