• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 com.android.launcher3.anim.Interpolators.LINEAR;
19 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
20 
21 import android.animation.Animator;
22 import android.animation.Animator.AnimatorListener;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.AnimatorSet;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Set;
34 
35 import androidx.dynamicanimation.animation.DynamicAnimation;
36 import androidx.dynamicanimation.animation.SpringAnimation;
37 
38 /**
39  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
40  * and durations.
41  *
42  * Note: The implementation does not support start delays on child animations or
43  * sequential playbacks.
44  */
45 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
46 
47     private static final String TAG = "AnimatorPlaybackCtrler";
48     private static boolean DEBUG = false;
49 
wrap(AnimatorSet anim, long duration)50     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
51         return wrap(anim, duration, null);
52     }
53 
54     /**
55      * Creates an animation controller for the provided animation.
56      * The actual duration does not matter as the animation is manually controlled. It just
57      * needs to be larger than the total number of pixels so that we don't have jittering due
58      * to float (animation-fraction * total duration) to int conversion.
59      */
wrap(AnimatorSet anim, long duration, Runnable onCancelRunnable)60     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
61             Runnable onCancelRunnable) {
62 
63         /**
64          * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
65          */
66         return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
67     }
68 
69     private final ValueAnimator mAnimationPlayer;
70     private final long mDuration;
71 
72     protected final AnimatorSet mAnim;
73     private Set<SpringAnimation> mSprings;
74 
75     protected float mCurrentFraction;
76     private Runnable mEndAction;
77 
78     protected boolean mTargetCancelled = false;
79     protected Runnable mOnCancelRunnable;
80 
81     private OnAnimationEndDispatcher mEndListener;
82     private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
83     // We need this variable to ensure the end listener is called immediately, otherwise we run into
84     // issues where the callback interferes with the states of the swipe detector.
85     private boolean mSkipToEnd = false;
86 
AnimatorPlaybackController(AnimatorSet anim, long duration, Runnable onCancelRunnable)87     protected AnimatorPlaybackController(AnimatorSet anim, long duration,
88             Runnable onCancelRunnable) {
89         mAnim = anim;
90         mDuration = duration;
91         mOnCancelRunnable = onCancelRunnable;
92 
93         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
94         mAnimationPlayer.setInterpolator(LINEAR);
95         mEndListener = new OnAnimationEndDispatcher();
96         mAnimationPlayer.addListener(mEndListener);
97         mAnimationPlayer.addUpdateListener(this);
98 
99         mAnim.addListener(new AnimatorListenerAdapter() {
100             @Override
101             public void onAnimationCancel(Animator animation) {
102                 mTargetCancelled = true;
103                 if (mOnCancelRunnable != null) {
104                     mOnCancelRunnable.run();
105                     mOnCancelRunnable = null;
106                 }
107             }
108 
109             @Override
110             public void onAnimationEnd(Animator animation) {
111                 mTargetCancelled = false;
112                 mOnCancelRunnable = null;
113             }
114 
115             @Override
116             public void onAnimationStart(Animator animation) {
117                 mTargetCancelled = false;
118             }
119         });
120 
121         mSprings = new HashSet<>();
122         mSpringEndListener = (animation, canceled, value, velocity1) -> {
123             if (canceled) {
124                 mEndListener.onAnimationCancel(mAnimationPlayer);
125             } else {
126                 mEndListener.onAnimationEnd(mAnimationPlayer);
127             }
128         };
129     }
130 
getTarget()131     public AnimatorSet getTarget() {
132         return mAnim;
133     }
134 
getDuration()135     public long getDuration() {
136         return mDuration;
137     }
138 
getInterpolator()139     public TimeInterpolator getInterpolator() {
140         return mAnim.getInterpolator() != null ? mAnim.getInterpolator() : LINEAR;
141     }
142 
143     /**
144      * Starts playing the animation forward from current position.
145      */
start()146     public void start() {
147         mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
148         mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
149         mAnimationPlayer.start();
150     }
151 
152     /**
153      * Starts playing the animation backwards from current position
154      */
reverse()155     public void reverse() {
156         mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
157         mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
158         mAnimationPlayer.start();
159     }
160 
161     /**
162      * Pauses the currently playing animation.
163      */
pause()164     public void pause() {
165         mAnimationPlayer.cancel();
166     }
167 
168     /**
169      * Returns the underlying animation used for controlling the set.
170      */
getAnimationPlayer()171     public ValueAnimator getAnimationPlayer() {
172         return mAnimationPlayer;
173     }
174 
175     /**
176      * Sets the current animation position and updates all the child animators accordingly.
177      */
setPlayFraction(float fraction)178     public abstract void setPlayFraction(float fraction);
179 
getProgressFraction()180     public float getProgressFraction() {
181         return mCurrentFraction;
182     }
183 
getInterpolatedProgress()184     public float getInterpolatedProgress() {
185         return getInterpolator().getInterpolation(mCurrentFraction);
186     }
187 
188     /**
189      * Sets the action to be called when the animation is completed. Also clears any
190      * previously set action.
191      */
setEndAction(Runnable runnable)192     public void setEndAction(Runnable runnable) {
193         mEndAction = runnable;
194     }
195 
196     @Override
onAnimationUpdate(ValueAnimator valueAnimator)197     public void onAnimationUpdate(ValueAnimator valueAnimator) {
198         setPlayFraction((float) valueAnimator.getAnimatedValue());
199     }
200 
clampDuration(float fraction)201     protected long clampDuration(float fraction) {
202         float playPos = mDuration * fraction;
203         if (playPos <= 0) {
204             return 0;
205         } else {
206             return Math.min((long) playPos, mDuration);
207         }
208     }
209 
210     /**
211      * Starts playback and sets the spring.
212      */
dispatchOnStartWithVelocity(float end, float velocity)213     public void dispatchOnStartWithVelocity(float end, float velocity) {
214         if (!QUICKSTEP_SPRINGS.get()) {
215             dispatchOnStart();
216             return;
217         }
218 
219         if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity);
220 
221         for (Animator a : mAnim.getChildAnimations()) {
222             if (a instanceof SpringObjectAnimator) {
223                 if (DEBUG) Log.d(TAG, "Found springAnimator=" + a);
224                 SpringObjectAnimator springAnimator = (SpringObjectAnimator) a;
225                 mSprings.add(springAnimator.getSpring());
226                 springAnimator.startSpring(end, velocity, mSpringEndListener);
227             }
228         }
229 
230         dispatchOnStart();
231     }
232 
dispatchOnStart()233     public void dispatchOnStart() {
234         dispatchOnStartRecursively(mAnim);
235     }
236 
dispatchOnStartRecursively(Animator animator)237     private void dispatchOnStartRecursively(Animator animator) {
238         List<AnimatorListener> listeners = animator instanceof SpringObjectAnimator
239                 ? nonNullList(((SpringObjectAnimator) animator).getObjectAnimatorListeners())
240                 : nonNullList(animator.getListeners());
241 
242         for (AnimatorListener l : listeners) {
243             l.onAnimationStart(animator);
244         }
245 
246         if (animator instanceof AnimatorSet) {
247             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
248                 dispatchOnStartRecursively(anim);
249             }
250         }
251     }
252 
dispatchOnCancel()253     public void dispatchOnCancel() {
254         dispatchOnCancelRecursively(mAnim);
255     }
256 
dispatchOnCancelRecursively(Animator animator)257     private void dispatchOnCancelRecursively(Animator animator) {
258         for (AnimatorListener l : nonNullList(animator.getListeners())) {
259             l.onAnimationCancel(animator);
260         }
261 
262         if (animator instanceof AnimatorSet) {
263             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
264                 dispatchOnCancelRecursively(anim);
265             }
266         }
267     }
268 
dispatchSetInterpolator(TimeInterpolator interpolator)269     public void dispatchSetInterpolator(TimeInterpolator interpolator) {
270         dispatchSetInterpolatorRecursively(mAnim, interpolator);
271     }
272 
dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator)273     private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) {
274         anim.setInterpolator(interpolator);
275         if (anim instanceof AnimatorSet) {
276             for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) {
277                 dispatchSetInterpolatorRecursively(child, interpolator);
278             }
279         }
280     }
281 
setOnCancelRunnable(Runnable runnable)282     public void setOnCancelRunnable(Runnable runnable) {
283         mOnCancelRunnable = runnable;
284     }
285 
getOnCancelRunnable()286     public Runnable getOnCancelRunnable() {
287         return mOnCancelRunnable;
288     }
289 
skipToEnd()290     public void skipToEnd() {
291         mSkipToEnd = true;
292         for (SpringAnimation spring : mSprings) {
293             if (spring.canSkipToEnd()) {
294                 spring.skipToEnd();
295             }
296         }
297         mAnimationPlayer.end();
298         mSkipToEnd = false;
299     }
300 
301     public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
302 
303         private final ValueAnimator[] mChildAnimations;
304 
AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, Runnable onCancelRunnable)305         private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
306                 Runnable onCancelRunnable) {
307             super(anim, duration, onCancelRunnable);
308 
309             // Build animation list
310             ArrayList<ValueAnimator> childAnims = new ArrayList<>();
311             getAnimationsRecur(mAnim, childAnims);
312             mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
313         }
314 
getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out)315         private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
316             long forceDuration = anim.getDuration();
317             TimeInterpolator forceInterpolator = anim.getInterpolator();
318             for (Animator child : anim.getChildAnimations()) {
319                 if (forceDuration > 0) {
320                     child.setDuration(forceDuration);
321                 }
322                 if (forceInterpolator != null) {
323                     child.setInterpolator(forceInterpolator);
324                 }
325                 if (child instanceof ValueAnimator) {
326                     out.add((ValueAnimator) child);
327                 } else if (child instanceof AnimatorSet) {
328                     getAnimationsRecur((AnimatorSet) child, out);
329                 } else {
330                     throw new RuntimeException("Unknown animation type " + child);
331                 }
332             }
333         }
334 
335         @Override
setPlayFraction(float fraction)336         public void setPlayFraction(float fraction) {
337             mCurrentFraction = fraction;
338             // Let the animator report the progress but don't apply the progress to child
339             // animations if it has been cancelled.
340             if (mTargetCancelled) {
341                 return;
342             }
343             long playPos = clampDuration(fraction);
344             for (ValueAnimator anim : mChildAnimations) {
345                 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
346             }
347         }
348     }
349 
isAnySpringRunning()350     private boolean isAnySpringRunning() {
351         for (SpringAnimation spring : mSprings) {
352             if (spring.isRunning()) {
353                 return true;
354             }
355         }
356         return false;
357     }
358 
359     /**
360      * Only dispatches the on end actions once the animator and all springs have completed running.
361      */
362     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
363 
364         boolean mAnimatorDone = false;
365         boolean mSpringsDone = false;
366         boolean mDispatched = false;
367 
368         @Override
onAnimationStart(Animator animation)369         public void onAnimationStart(Animator animation) {
370             mCancelled = false;
371             mDispatched = false;
372         }
373 
374         @Override
onAnimationSuccess(Animator animator)375         public void onAnimationSuccess(Animator animator) {
376             if (mSprings.isEmpty()) {
377                 mSpringsDone = mAnimatorDone = true;
378             }
379             if (isAnySpringRunning()) {
380                 mAnimatorDone = true;
381             } else {
382                 mSpringsDone = true;
383             }
384 
385             // We wait for the spring (if any) to finish running before completing the end callback.
386             if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) {
387                 dispatchOnEndRecursively(mAnim);
388                 if (mEndAction != null) {
389                     mEndAction.run();
390                 }
391                 mDispatched = true;
392             }
393         }
394 
dispatchOnEndRecursively(Animator animator)395         private void dispatchOnEndRecursively(Animator animator) {
396             for (AnimatorListener l : nonNullList(animator.getListeners())) {
397                 l.onAnimationEnd(animator);
398             }
399 
400             if (animator instanceof AnimatorSet) {
401                 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
402                     dispatchOnEndRecursively(anim);
403                 }
404             }
405         }
406     }
407 
nonNullList(ArrayList<T> list)408     private static <T> List<T> nonNullList(ArrayList<T> list) {
409         return list == null ? Collections.emptyList() : list;
410     }
411 }
412