1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.anim;
17 
18 import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
19 
20 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ObjectAnimator;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.util.FloatProperty;
30 import android.util.Log;
31 
32 import java.util.ArrayList;
33 
34 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
35 import androidx.dynamicanimation.animation.SpringAnimation;
36 import androidx.dynamicanimation.animation.SpringForce;
37 
38 /**
39  * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
40  * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
41  */
42 public class SpringObjectAnimator<T> extends ValueAnimator {
43 
44     private static final String TAG = "SpringObjectAnimator";
45     private static boolean DEBUG = false;
46 
47     private ObjectAnimator mObjectAnimator;
48     private float[] mValues;
49 
50     private SpringAnimation mSpring;
51     private SpringProperty<T> mProperty;
52 
53     private ArrayList<AnimatorListener> mListeners;
54     private boolean mSpringEnded = true;
55     private boolean mAnimatorEnded = true;
56     private boolean mEnded = true;
57 
SpringObjectAnimator(T object, FloatProperty<T> property, float minimumVisibleChange, float damping, float stiffness, float... values)58     public SpringObjectAnimator(T object, FloatProperty<T> property, float minimumVisibleChange,
59             float damping, float stiffness, float... values) {
60         mSpring = new SpringAnimation(object, createFloatPropertyCompat(property));
61         mSpring.setMinimumVisibleChange(minimumVisibleChange);
62         mSpring.setSpring(new SpringForce(0)
63                 .setDampingRatio(damping)
64                 .setStiffness(stiffness));
65         mSpring.setStartVelocity(0.01f);
66         mProperty = new SpringProperty<>(property, mSpring);
67         mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
68         mValues = values;
69         mListeners = new ArrayList<>();
70         setFloatValues(values);
71 
72         // We use this listener and track mListeners so that we can sync the animator and spring
73         // listeners.
74         mObjectAnimator.addListener(new AnimatorListenerAdapter() {
75             @Override
76             public void onAnimationStart(Animator animation) {
77                 mAnimatorEnded = false;
78                 mEnded = false;
79                 for (AnimatorListener l : mListeners) {
80                     l.onAnimationStart(animation);
81                 }
82             }
83 
84             @Override
85             public void onAnimationEnd(Animator animation) {
86                 mAnimatorEnded = true;
87                 tryEnding();
88             }
89 
90             @Override
91             public void onAnimationCancel(Animator animation) {
92                 for (AnimatorListener l : mListeners) {
93                     l.onAnimationCancel(animation);
94                 }
95                 mSpring.cancel();
96             }
97         });
98 
99         mSpring.addUpdateListener((animation, value, velocity) -> {
100             mSpringEnded = false;
101             mEnded = false;
102         });
103         mSpring.addEndListener((animation, canceled, value, velocity) -> {
104             mSpringEnded = true;
105             tryEnding();
106         });
107     }
108 
tryEnding()109     private void tryEnding() {
110         if (DEBUG) {
111             Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded="
112                     + mSpringEnded + ", mEnded=" + mEnded);
113         }
114 
115         // If springs are disabled, ignore value of mSpringEnded
116         if (mAnimatorEnded && (mSpringEnded || !QUICKSTEP_SPRINGS.get()) && !mEnded) {
117             for (AnimatorListener l : mListeners) {
118                 l.onAnimationEnd(this);
119             }
120             mEnded = true;
121         }
122     }
123 
getSpring()124     public SpringAnimation getSpring() {
125         return mSpring;
126     }
127 
128     /**
129      * Initializes and sets up the spring to take over controlling the object.
130      */
startSpring(float end, float velocity, OnAnimationEndListener endListener)131     public void startSpring(float end, float velocity, OnAnimationEndListener endListener) {
132         // Cancel the spring so we can set new start velocity and final position. We need to remove
133         // the listener since the spring is not actually ending.
134         mSpring.removeEndListener(endListener);
135         mSpring.cancel();
136         mSpring.addEndListener(endListener);
137 
138         mProperty.switchToSpring();
139 
140         mSpring.setStartVelocity(velocity);
141 
142         float startValue = end == 0 ? mValues[1] : mValues[0];
143         float endValue = end == 0 ? mValues[0] : mValues[1];
144         mSpring.setStartValue(startValue);
145         new Handler(Looper.getMainLooper()).postDelayed(() -> {
146             mSpring.animateToFinalPosition(endValue);
147         }, getStartDelay());
148     }
149 
150     @Override
addListener(AnimatorListener listener)151     public void addListener(AnimatorListener listener) {
152         mListeners.add(listener);
153     }
154 
getObjectAnimatorListeners()155     public ArrayList<AnimatorListener> getObjectAnimatorListeners() {
156         return mObjectAnimator.getListeners();
157     }
158 
159     @Override
getListeners()160     public ArrayList<AnimatorListener> getListeners() {
161         return mListeners;
162     }
163 
164     @Override
removeAllListeners()165     public void removeAllListeners() {
166         mListeners.clear();
167     }
168 
169     @Override
removeListener(AnimatorListener listener)170     public void removeListener(AnimatorListener listener) {
171         mListeners.remove(listener);
172     }
173 
174     @Override
addPauseListener(AnimatorPauseListener listener)175     public void addPauseListener(AnimatorPauseListener listener) {
176         mObjectAnimator.addPauseListener(listener);
177     }
178 
179     @Override
cancel()180     public void cancel() {
181         mObjectAnimator.cancel();
182         mSpring.cancel();
183     }
184 
185     @Override
end()186     public void end() {
187         mObjectAnimator.end();
188     }
189 
190     @Override
getDuration()191     public long getDuration() {
192         return mObjectAnimator.getDuration();
193     }
194 
195     @Override
getInterpolator()196     public TimeInterpolator getInterpolator() {
197         return mObjectAnimator.getInterpolator();
198     }
199 
200     @Override
getStartDelay()201     public long getStartDelay() {
202         return mObjectAnimator.getStartDelay();
203     }
204 
205     @Override
getTotalDuration()206     public long getTotalDuration() {
207         return mObjectAnimator.getTotalDuration();
208     }
209 
210     @Override
isPaused()211     public boolean isPaused() {
212         return mObjectAnimator.isPaused();
213     }
214 
215     @Override
isRunning()216     public boolean isRunning() {
217         return mObjectAnimator.isRunning();
218     }
219 
220     @Override
isStarted()221     public boolean isStarted() {
222         return mObjectAnimator.isStarted();
223     }
224 
225     @Override
pause()226     public void pause() {
227         mObjectAnimator.pause();
228     }
229 
230     @Override
removePauseListener(AnimatorPauseListener listener)231     public void removePauseListener(AnimatorPauseListener listener) {
232         mObjectAnimator.removePauseListener(listener);
233     }
234 
235     @Override
resume()236     public void resume() {
237         mObjectAnimator.resume();
238     }
239 
240     @Override
setDuration(long duration)241     public ValueAnimator setDuration(long duration) {
242         return mObjectAnimator.setDuration(duration);
243     }
244 
245     @Override
setInterpolator(TimeInterpolator value)246     public void setInterpolator(TimeInterpolator value) {
247         mObjectAnimator.setInterpolator(value);
248     }
249 
250     @Override
setStartDelay(long startDelay)251     public void setStartDelay(long startDelay) {
252         mObjectAnimator.setStartDelay(startDelay);
253     }
254 
255     @Override
setTarget(Object target)256     public void setTarget(Object target) {
257         mObjectAnimator.setTarget(target);
258     }
259 
260     @Override
start()261     public void start() {
262         mObjectAnimator.start();
263     }
264 
265     @Override
setCurrentFraction(float fraction)266     public void setCurrentFraction(float fraction) {
267         mObjectAnimator.setCurrentFraction(fraction);
268     }
269 
270     @Override
setCurrentPlayTime(long playTime)271     public void setCurrentPlayTime(long playTime) {
272         mObjectAnimator.setCurrentPlayTime(playTime);
273     }
274 
275     public static class SpringProperty<T> extends FloatProperty<T> {
276 
277         boolean useSpring = false;
278         final FloatProperty<T> mProperty;
279         final SpringAnimation mSpring;
280 
SpringProperty(FloatProperty<T> property, SpringAnimation spring)281         public SpringProperty(FloatProperty<T> property, SpringAnimation spring) {
282             super(property.getName());
283             mProperty = property;
284             mSpring = spring;
285         }
286 
switchToSpring()287         public void switchToSpring() {
288             useSpring = true;
289         }
290 
291         @Override
get(T object)292         public Float get(T object) {
293             return mProperty.get(object);
294         }
295 
296         @Override
setValue(T object, float progress)297         public void setValue(T object, float progress) {
298             if (useSpring) {
299                 mSpring.animateToFinalPosition(progress);
300             } else {
301                 mProperty.setValue(object, progress);
302             }
303         }
304     }
305 }
306