• 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 android.animation.Animator;
19 import android.animation.Animator.AnimatorListener;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.TimeInterpolator;
23 import android.animation.ValueAnimator;
24 
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 
29 /**
30  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
31  * and durations.
32  *
33  * Note: The implementation does not support start delays on child animations or
34  * sequential playbacks.
35  */
36 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
37 
wrap(AnimatorSet anim, long duration)38     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
39         return wrap(anim, duration, null);
40     }
41 
42     /**
43      * Creates an animation controller for the provided animation.
44      * The actual duration does not matter as the animation is manually controlled. It just
45      * needs to be larger than the total number of pixels so that we don't have jittering due
46      * to float (animation-fraction * total duration) to int conversion.
47      */
wrap(AnimatorSet anim, long duration, Runnable onCancelRunnable)48     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration,
49             Runnable onCancelRunnable) {
50 
51         /**
52          * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
53          */
54         return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable);
55     }
56 
57     private final ValueAnimator mAnimationPlayer;
58     private final long mDuration;
59 
60     protected final AnimatorSet mAnim;
61 
62     protected float mCurrentFraction;
63     private Runnable mEndAction;
64 
65     protected boolean mTargetCancelled = false;
66     protected Runnable mOnCancelRunnable;
67 
AnimatorPlaybackController(AnimatorSet anim, long duration, Runnable onCancelRunnable)68     protected AnimatorPlaybackController(AnimatorSet anim, long duration,
69             Runnable onCancelRunnable) {
70         mAnim = anim;
71         mDuration = duration;
72         mOnCancelRunnable = onCancelRunnable;
73 
74         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
75         mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
76         mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
77         mAnimationPlayer.addUpdateListener(this);
78 
79         mAnim.addListener(new AnimatorListenerAdapter() {
80             @Override
81             public void onAnimationCancel(Animator animation) {
82                 mTargetCancelled = true;
83                 if (mOnCancelRunnable != null) {
84                     mOnCancelRunnable.run();
85                     mOnCancelRunnable = null;
86                 }
87             }
88 
89             @Override
90             public void onAnimationEnd(Animator animation) {
91                 mTargetCancelled = false;
92                 mOnCancelRunnable = null;
93             }
94 
95             @Override
96             public void onAnimationStart(Animator animation) {
97                 mTargetCancelled = false;
98             }
99         });
100     }
101 
getTarget()102     public AnimatorSet getTarget() {
103         return mAnim;
104     }
105 
getDuration()106     public long getDuration() {
107         return mDuration;
108     }
109 
110     /**
111      * Starts playing the animation forward from current position.
112      */
start()113     public void start() {
114         mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
115         mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
116         mAnimationPlayer.start();
117     }
118 
119     /**
120      * Starts playing the animation backwards from current position
121      */
reverse()122     public void reverse() {
123         mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
124         mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
125         mAnimationPlayer.start();
126     }
127 
128     /**
129      * Pauses the currently playing animation.
130      */
pause()131     public void pause() {
132         mAnimationPlayer.cancel();
133     }
134 
135     /**
136      * Returns the underlying animation used for controlling the set.
137      */
getAnimationPlayer()138     public ValueAnimator getAnimationPlayer() {
139         return mAnimationPlayer;
140     }
141 
142     /**
143      * Sets the current animation position and updates all the child animators accordingly.
144      */
setPlayFraction(float fraction)145     public abstract void setPlayFraction(float fraction);
146 
getProgressFraction()147     public float getProgressFraction() {
148         return mCurrentFraction;
149     }
150 
151     /**
152      * Sets the action to be called when the animation is completed. Also clears any
153      * previously set action.
154      */
setEndAction(Runnable runnable)155     public void setEndAction(Runnable runnable) {
156         mEndAction = runnable;
157     }
158 
159     @Override
onAnimationUpdate(ValueAnimator valueAnimator)160     public void onAnimationUpdate(ValueAnimator valueAnimator) {
161         setPlayFraction((float) valueAnimator.getAnimatedValue());
162     }
163 
clampDuration(float fraction)164     protected long clampDuration(float fraction) {
165         float playPos = mDuration * fraction;
166         if (playPos <= 0) {
167             return 0;
168         } else {
169             return Math.min((long) playPos, mDuration);
170         }
171     }
172 
dispatchOnStart()173     public void dispatchOnStart() {
174         dispatchOnStartRecursively(mAnim);
175     }
176 
dispatchOnStartRecursively(Animator animator)177     private void dispatchOnStartRecursively(Animator animator) {
178         for (AnimatorListener l : nonNullList(animator.getListeners())) {
179             l.onAnimationStart(animator);
180         }
181 
182         if (animator instanceof AnimatorSet) {
183             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
184                 dispatchOnStartRecursively(anim);
185             }
186         }
187     }
188 
dispatchOnCancel()189     public void dispatchOnCancel() {
190         dispatchOnCancelRecursively(mAnim);
191     }
192 
dispatchOnCancelRecursively(Animator animator)193     private void dispatchOnCancelRecursively(Animator animator) {
194         for (AnimatorListener l : nonNullList(animator.getListeners())) {
195             l.onAnimationCancel(animator);
196         }
197 
198         if (animator instanceof AnimatorSet) {
199             for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
200                 dispatchOnCancelRecursively(anim);
201             }
202         }
203     }
204 
setOnCancelRunnable(Runnable runnable)205     public void setOnCancelRunnable(Runnable runnable) {
206         mOnCancelRunnable = runnable;
207     }
208 
getOnCancelRunnable()209     public Runnable getOnCancelRunnable() {
210         return mOnCancelRunnable;
211     }
212 
213     public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
214 
215         private final ValueAnimator[] mChildAnimations;
216 
AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, Runnable onCancelRunnable)217         private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration,
218                 Runnable onCancelRunnable) {
219             super(anim, duration, onCancelRunnable);
220 
221             // Build animation list
222             ArrayList<ValueAnimator> childAnims = new ArrayList<>();
223             getAnimationsRecur(mAnim, childAnims);
224             mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
225         }
226 
getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out)227         private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
228             long forceDuration = anim.getDuration();
229             TimeInterpolator forceInterpolator = anim.getInterpolator();
230             for (Animator child : anim.getChildAnimations()) {
231                 if (forceDuration > 0) {
232                     child.setDuration(forceDuration);
233                 }
234                 if (forceInterpolator != null) {
235                     child.setInterpolator(forceInterpolator);
236                 }
237                 if (child instanceof ValueAnimator) {
238                     out.add((ValueAnimator) child);
239                 } else if (child instanceof AnimatorSet) {
240                     getAnimationsRecur((AnimatorSet) child, out);
241                 } else {
242                     throw new RuntimeException("Unknown animation type " + child);
243                 }
244             }
245         }
246 
247         @Override
setPlayFraction(float fraction)248         public void setPlayFraction(float fraction) {
249             mCurrentFraction = fraction;
250             // Let the animator report the progress but don't apply the progress to child
251             // animations if it has been cancelled.
252             if (mTargetCancelled) {
253                 return;
254             }
255             long playPos = clampDuration(fraction);
256             for (ValueAnimator anim : mChildAnimations) {
257                 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
258             }
259         }
260     }
261 
262     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
263 
264         @Override
onAnimationStart(Animator animation)265         public void onAnimationStart(Animator animation) {
266             mCancelled = false;
267         }
268 
269         @Override
onAnimationSuccess(Animator animator)270         public void onAnimationSuccess(Animator animator) {
271             dispatchOnEndRecursively(mAnim);
272             if (mEndAction != null) {
273                 mEndAction.run();
274             }
275         }
276 
dispatchOnEndRecursively(Animator animator)277         private void dispatchOnEndRecursively(Animator animator) {
278             for (AnimatorListener l : nonNullList(animator.getListeners())) {
279                 l.onAnimationEnd(animator);
280             }
281 
282             if (animator instanceof AnimatorSet) {
283                 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
284                     dispatchOnEndRecursively(anim);
285                 }
286             }
287         }
288     }
289 
nonNullList(ArrayList<T> list)290     private static <T> List<T> nonNullList(ArrayList<T> list) {
291         return list == null ? Collections.emptyList() : list;
292     }
293 }
294