• 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 
17 package android.support.animation;
18 
19 import android.os.Looper;
20 import android.support.annotation.FloatRange;
21 import android.support.v4.view.ViewCompat;
22 import android.util.AndroidRuntimeException;
23 import android.view.View;
24 
25 import java.util.ArrayList;
26 
27 /**
28  * This class is the base class of physics-based animations. It manages the animation's
29  * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common
30  * setup for all the subclass animations. For example, DynamicAnimation supports adding
31  * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important
32  * animation events can be observed through the callbacks. The start conditions for any subclass of
33  * DynamicAnimation can be set using {@link #setStartValue(float)} and
34  * {@link #setStartVelocity(float)}.
35  *
36  * @param <T> subclass of DynamicAnimation
37  */
38 public abstract class DynamicAnimation<T extends DynamicAnimation<T>>
39         implements AnimationHandler.AnimationFrameCallback {
40 
41     /**
42      * ViewProperty holds the access of a property of a {@link View}. When an animation is
43      * created with a {@link ViewProperty} instance, the corresponding property value of the view
44      * will be updated through this ViewProperty instance.
45      */
46     public abstract static class ViewProperty extends FloatPropertyCompat<View> {
ViewProperty(String name)47         private ViewProperty(String name) {
48             super(name);
49         }
50     }
51 
52     /**
53      * View's translationX property.
54      */
55     public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") {
56         @Override
57         public void setValue(View view, float value) {
58             view.setTranslationX(value);
59         }
60 
61         @Override
62         public float getValue(View view) {
63             return view.getTranslationX();
64         }
65     };
66 
67     /**
68      * View's translationY property.
69      */
70     public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") {
71         @Override
72         public void setValue(View view, float value) {
73             view.setTranslationY(value);
74         }
75 
76         @Override
77         public float getValue(View view) {
78             return view.getTranslationY();
79         }
80     };
81 
82     /**
83      * View's translationZ property.
84      */
85     public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") {
86         @Override
87         public void setValue(View view, float value) {
88             ViewCompat.setTranslationZ(view, value);
89         }
90 
91         @Override
92         public float getValue(View view) {
93             return ViewCompat.getTranslationZ(view);
94         }
95     };
96 
97     /**
98      * View's scaleX property.
99      */
100     public static final ViewProperty SCALE_X = new ViewProperty("scaleX") {
101         @Override
102         public void setValue(View view, float value) {
103             view.setScaleX(value);
104         }
105 
106         @Override
107         public float getValue(View view) {
108             return view.getScaleX();
109         }
110     };
111 
112     /**
113      * View's scaleY property.
114      */
115     public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") {
116         @Override
117         public void setValue(View view, float value) {
118             view.setScaleY(value);
119         }
120 
121         @Override
122         public float getValue(View view) {
123             return view.getScaleY();
124         }
125     };
126 
127     /**
128      * View's rotation property.
129      */
130     public static final ViewProperty ROTATION = new ViewProperty("rotation") {
131         @Override
132         public void setValue(View view, float value) {
133             view.setRotation(value);
134         }
135 
136         @Override
137         public float getValue(View view) {
138             return view.getRotation();
139         }
140     };
141 
142     /**
143      * View's rotationX property.
144      */
145     public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") {
146         @Override
147         public void setValue(View view, float value) {
148             view.setRotationX(value);
149         }
150 
151         @Override
152         public float getValue(View view) {
153             return view.getRotationX();
154         }
155     };
156 
157     /**
158      * View's rotationY property.
159      */
160     public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") {
161         @Override
162         public void setValue(View view, float value) {
163             view.setRotationY(value);
164         }
165 
166         @Override
167         public float getValue(View view) {
168             return view.getRotationY();
169         }
170     };
171 
172     /**
173      * View's x property.
174      */
175     public static final ViewProperty X = new ViewProperty("x") {
176         @Override
177         public void setValue(View view, float value) {
178             view.setX(value);
179         }
180 
181         @Override
182         public float getValue(View view) {
183             return view.getX();
184         }
185     };
186 
187     /**
188      * View's y property.
189      */
190     public static final ViewProperty Y = new ViewProperty("y") {
191         @Override
192         public void setValue(View view, float value) {
193             view.setY(value);
194         }
195 
196         @Override
197         public float getValue(View view) {
198             return view.getY();
199         }
200     };
201 
202     /**
203      * View's z property.
204      */
205     public static final ViewProperty Z = new ViewProperty("z") {
206         @Override
207         public void setValue(View view, float value) {
208             ViewCompat.setZ(view, value);
209         }
210 
211         @Override
212         public float getValue(View view) {
213             return ViewCompat.getZ(view);
214         }
215     };
216 
217     /**
218      * View's alpha property.
219      */
220     public static final ViewProperty ALPHA = new ViewProperty("alpha") {
221         @Override
222         public void setValue(View view, float value) {
223             view.setAlpha(value);
224         }
225 
226         @Override
227         public float getValue(View view) {
228             return view.getAlpha();
229         }
230     };
231 
232     // Properties below are not RenderThread compatible
233     /**
234      * View's scrollX property.
235      */
236     public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") {
237         @Override
238         public void setValue(View view, float value) {
239             view.setScrollX((int) value);
240         }
241 
242         @Override
243         public float getValue(View view) {
244             return view.getScrollX();
245         }
246     };
247 
248     /**
249      * View's scrollY property.
250      */
251     public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") {
252         @Override
253         public void setValue(View view, float value) {
254             view.setScrollY((int) value);
255         }
256 
257         @Override
258         public float getValue(View view) {
259             return view.getScrollY();
260         }
261     };
262 
263     /**
264      * The minimum visible change in pixels that can be visible to users.
265      */
266     public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f;
267     /**
268      * The minimum visible change in degrees that can be visible to users.
269      */
270     public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f;
271     /**
272      * The minimum visible change in alpha that can be visible to users.
273      */
274     public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f;
275     /**
276      * The minimum visible change in scale that can be visible to users.
277      */
278     public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f;
279 
280     // Use the max value of float to indicate an unset state.
281     private static final float UNSET = Float.MAX_VALUE;
282 
283     // Multiplier to the min visible change value for value threshold
284     private static final float THRESHOLD_MULTIPLIER = 0.75f;
285 
286     // Internal tracking for velocity.
287     float mVelocity = 0;
288 
289     // Internal tracking for value.
290     float mValue = UNSET;
291 
292     // Tracks whether start value is set. If not, the animation will obtain the value at the time
293     // of starting through the getter and use that as the starting value of the animation.
294     boolean mStartValueIsSet = false;
295 
296     // Target to be animated.
297     final Object mTarget;
298 
299     // View property id.
300     final FloatPropertyCompat mProperty;
301 
302     // Package private tracking of animation lifecycle state. Visible to subclass animations.
303     boolean mRunning = false;
304 
305     // Min and max values that defines the range of the animation values.
306     float mMaxValue = Float.MAX_VALUE;
307     float mMinValue = -mMaxValue;
308 
309     // Last frame time. Always gets reset to -1  at the end of the animation.
310     private long mLastFrameTime = 0;
311 
312     private float mMinVisibleChange;
313 
314     // List of end listeners
315     private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>();
316 
317     // List of update listeners
318     private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>();
319 
320     // Internal state for value/velocity pair.
321     static class MassState {
322         float mValue;
323         float mVelocity;
324     }
325 
326     /**
327      * Creates a dynamic animation with the given FloatValueHolder instance.
328      *
329      * @param floatValueHolder the FloatValueHolder instance to be animated.
330      */
DynamicAnimation(final FloatValueHolder floatValueHolder)331     DynamicAnimation(final FloatValueHolder floatValueHolder) {
332         mTarget = null;
333         mProperty = new FloatPropertyCompat("FloatValueHolder") {
334             @Override
335             public float getValue(Object object) {
336                 return floatValueHolder.getValue();
337             }
338 
339             @Override
340             public void setValue(Object object, float value) {
341                 floatValueHolder.setValue(value);
342             }
343         };
344         mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
345     }
346 
347     /**
348      * Creates a dynamic animation to animate the given property for the given {@link View}
349      *
350      * @param object the Object whose property is to be animated
351      * @param property the property to be animated
352      */
353 
DynamicAnimation(K object, FloatPropertyCompat<K> property)354     <K> DynamicAnimation(K object, FloatPropertyCompat<K> property) {
355         mTarget = object;
356         mProperty = property;
357         if (mProperty == ROTATION || mProperty == ROTATION_X
358                 || mProperty == ROTATION_Y) {
359             mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
360         } else if (mProperty == ALPHA) {
361             mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
362         } else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
363             mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
364         } else {
365             mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
366         }
367     }
368 
369     /**
370      * Sets the start value of the animation. If start value is not set, the animation will get
371      * the current value for the view's property, and use that as the start value.
372      *
373      * @param startValue start value for the animation
374      * @return the Animation whose start value is being set
375      */
setStartValue(float startValue)376     public T setStartValue(float startValue) {
377         mValue = startValue;
378         mStartValueIsSet = true;
379         return (T) this;
380     }
381 
382     /**
383      * Start velocity of the animation. Default velocity is 0. Unit: pixel/second
384      *
385      * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity
386      * through touch events), it is recommended to define such a value in dp/second and convert it
387      * to pixel/second based on the density of the screen to achieve a consistent look across
388      * different screens.
389      *
390      * <p>To convert from dp/second to pixel/second:
391      * <pre class="prettyprint">
392      * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
393      *         getResources().getDisplayMetrics());
394      * </pre>
395      *
396      * @param startVelocity start velocity of the animation in pixel/second
397      * @return the Animation whose start velocity is being set
398      */
setStartVelocity(float startVelocity)399     public T setStartVelocity(float startVelocity) {
400         mVelocity = startVelocity;
401         return (T) this;
402     }
403 
404     /**
405      * Sets the max value of the animation. Animations will not animate beyond their max value.
406      * Whether or not animation will come to an end when max value is reached is dependent on the
407      * child animation's implementation.
408      *
409      * @param max maximum value of the property to be animated
410      * @return the Animation whose max value is being set
411      */
setMaxValue(float max)412     public T setMaxValue(float max) {
413         // This max value should be checked and handled in the subclass animations, instead of
414         // assuming the end of the animations when the max/min value is hit in the base class.
415         // The reason is that hitting max/min value may just be a transient state, such as during
416         // the spring oscillation.
417         mMaxValue = max;
418         return (T) this;
419     }
420 
421     /**
422      * Sets the min value of the animation. Animations will not animate beyond their min value.
423      * Whether or not animation will come to an end when min value is reached is dependent on the
424      * child animation's implementation.
425      *
426      * @param min minimum value of the property to be animated
427      * @return the Animation whose min value is being set
428      */
setMinValue(float min)429     public T setMinValue(float min) {
430         mMinValue = min;
431         return (T) this;
432     }
433 
434     /**
435      * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener
436      * is {@code null} or has already been added to the list of listeners for the animation, no op.
437      *
438      * @param listener the listener to be added
439      * @return the animation to which the listener is added
440      */
addEndListener(OnAnimationEndListener listener)441     public T addEndListener(OnAnimationEndListener listener) {
442         if (!mEndListeners.contains(listener)) {
443             mEndListeners.add(listener);
444         }
445         return (T) this;
446     }
447 
448     /**
449      * Removes the end listener from the animation, so as to stop receiving animation end callbacks.
450      *
451      * @param listener the listener to be removed
452      */
removeEndListener(OnAnimationEndListener listener)453     public void removeEndListener(OnAnimationEndListener listener) {
454         removeEntry(mEndListeners, listener);
455     }
456 
457     /**
458      * Adds an update listener to the animation for receiving per-frame animation update callbacks.
459      * If the listener is {@code null} or has already been added to the list of listeners for the
460      * animation, no op.
461      *
462      * <p>Note that update listener should only be added before the start of the animation.
463      *
464      * @param listener the listener to be added
465      * @return the animation to which the listener is added
466      * @throws UnsupportedOperationException if the update listener is added after the animation has
467      *                                       started
468      */
addUpdateListener(OnAnimationUpdateListener listener)469     public T addUpdateListener(OnAnimationUpdateListener listener) {
470         if (isRunning()) {
471             // Require update listener to be added before the animation, such as when we start
472             // the animation, we know whether the animation is RenderThread compatible.
473             throw new UnsupportedOperationException("Error: Update listeners must be added before"
474                     + "the animation.");
475         }
476         if (!mUpdateListeners.contains(listener)) {
477             mUpdateListeners.add(listener);
478         }
479         return (T) this;
480     }
481 
482     /**
483      * Removes the update listener from the animation, so as to stop receiving animation update
484      * callbacks.
485      *
486      * @param listener the listener to be removed
487      */
removeUpdateListener(OnAnimationUpdateListener listener)488     public void removeUpdateListener(OnAnimationUpdateListener listener) {
489         removeEntry(mUpdateListeners, listener);
490     }
491 
492 
493     /**
494      * This method sets the minimal change of animation value that is visible to users, which helps
495      * determine a reasonable threshold for the animation's termination condition. It is critical
496      * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s)
497      * unless the custom property is in pixels.
498      *
499      * <p>For custom properties, this minimum visible change defaults to change in pixel
500      * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is
501      * reasonable for the property to be animated. A general rule of thumb to calculate such a value
502      * is: minimum visible change = range of custom property value / corresponding pixel range. For
503      * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a
504      * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5).
505      *
506      * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the
507      * minimum visible change will be derived from the property. For example, if the property to be
508      * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y},
509      * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible
510      * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the
511      * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change,
512      * which is 1/10. Similarly, the minimum visible change for alpha (
513      * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256.
514      *
515      * @param minimumVisibleChange minimum change in property value that is visible to users
516      * @return the animation whose min visible change is being set
517      * @throws IllegalArgumentException if the given threshold is not positive
518      */
setMinimumVisibleChange(@loatRangefrom = 0.0, fromInclusive = false) float minimumVisibleChange)519     public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false)
520             float minimumVisibleChange) {
521         if (minimumVisibleChange <= 0) {
522             throw new IllegalArgumentException("Minimum visible change must be positive.");
523         }
524         mMinVisibleChange = minimumVisibleChange;
525         setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER);
526         return (T) this;
527     }
528 
529     /**
530      * Returns the minimum change in the animation property that could be visibly different to
531      * users.
532      *
533      * @return minimum change in property value that is visible to users
534      */
getMinimumVisibleChange()535     public float getMinimumVisibleChange() {
536         return mMinVisibleChange;
537     }
538 
539     /**
540      * Remove {@code null} entries from the list.
541      */
removeNullEntries(ArrayList<T> list)542     private static <T> void removeNullEntries(ArrayList<T> list) {
543         // Clean up null entries
544         for (int i = list.size() - 1; i >= 0; i--) {
545             if (list.get(i) == null) {
546                 list.remove(i);
547             }
548         }
549     }
550 
551     /**
552      * Remove an entry from the list by marking it {@code null} and clean up later.
553      */
removeEntry(ArrayList<T> list, T entry)554     private static <T> void removeEntry(ArrayList<T> list, T entry) {
555         int id = list.indexOf(entry);
556         if (id >= 0) {
557             list.set(id, null);
558         }
559     }
560 
561     /****************Animation Lifecycle Management***************/
562 
563     /**
564      * Starts an animation. If the animation has already been started, no op. Note that calling
565      * {@link #start()} will not immediately set the property value to start value of the animation.
566      * The property values will be changed at each animation pulse, which happens before the draw
567      * pass. As a result, the changes will be reflected in the next frame, the same as if the values
568      * were set immediately. This method should only be called on main thread.
569      *
570      * @throws AndroidRuntimeException if this method is not called on the main thread
571      */
start()572     public void start() {
573         if (Looper.myLooper() != Looper.getMainLooper()) {
574             throw new AndroidRuntimeException("Animations may only be started on the main thread");
575         }
576         if (!mRunning) {
577             startAnimationInternal();
578         }
579     }
580 
581     /**
582      * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method
583      * should only be called on main thread.
584      *
585      * @throws AndroidRuntimeException if this method is not called on the main thread
586      */
cancel()587     public void cancel() {
588         if (Looper.myLooper() != Looper.getMainLooper()) {
589             throw new AndroidRuntimeException("Animations may only be canceled on the main thread");
590         }
591         if (mRunning) {
592             endAnimationInternal(true);
593         }
594     }
595 
596     /**
597      * Returns whether the animation is currently running.
598      *
599      * @return {@code true} if the animation is currently running, {@code false} otherwise
600      */
isRunning()601     public boolean isRunning() {
602         return mRunning;
603     }
604 
605     /************************** Private APIs below ********************************/
606 
607     // This gets called when the animation is started, to finish the setup of the animation
608     // before the animation pulsing starts.
startAnimationInternal()609     private void startAnimationInternal() {
610         if (!mRunning) {
611             mRunning = true;
612             if (!mStartValueIsSet) {
613                 mValue = getPropertyValue();
614             }
615             // Sanity check:
616             if (mValue > mMaxValue || mValue < mMinValue) {
617                 throw new IllegalArgumentException("Starting value need to be in between min"
618                         + " value and max value");
619             }
620             AnimationHandler.getInstance().addAnimationFrameCallback(this, 0);
621         }
622     }
623 
624     /**
625      * This gets call on each frame of the animation. Animation value and velocity are updated
626      * in this method based on the new frame time. The property value of the view being animated
627      * is then updated. The animation's ending conditions are also checked in this method. Once
628      * the animation reaches equilibrium, the animation will come to its end, and end listeners
629      * will be notified, if any.
630      *
631      * @hide
632      */
633     @Override
doAnimationFrame(long frameTime)634     public boolean doAnimationFrame(long frameTime) {
635         if (mLastFrameTime == 0) {
636             // First frame.
637             mLastFrameTime = frameTime;
638             setPropertyValue(mValue);
639             return false;
640         }
641         long deltaT = frameTime - mLastFrameTime;
642         mLastFrameTime = frameTime;
643         boolean finished = updateValueAndVelocity(deltaT);
644         // Clamp value & velocity.
645         mValue = Math.min(mValue, mMaxValue);
646         mValue = Math.max(mValue, mMinValue);
647 
648         setPropertyValue(mValue);
649 
650         if (finished) {
651             endAnimationInternal(false);
652         }
653         return finished;
654     }
655 
656     /**
657      * Updates the animation state (i.e. value and velocity). This method is package private, so
658      * subclasses can override this method to calculate the new value and velocity in their custom
659      * way.
660      *
661      * @param deltaT time elapsed in millisecond since last frame
662      * @return whether the animation has finished
663      */
updateValueAndVelocity(long deltaT)664     abstract boolean updateValueAndVelocity(long deltaT);
665 
666     /**
667      * Internal method to reset the animation states when animation is finished/canceled.
668      */
endAnimationInternal(boolean canceled)669     private void endAnimationInternal(boolean canceled) {
670         mRunning = false;
671         AnimationHandler.getInstance().removeCallback(this);
672         mLastFrameTime = 0;
673         mStartValueIsSet = false;
674         for (int i = 0; i < mEndListeners.size(); i++) {
675             if (mEndListeners.get(i) != null) {
676                 mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
677             }
678         }
679         removeNullEntries(mEndListeners);
680     }
681 
682     /**
683      * Updates the property value through the corresponding setter.
684      */
setPropertyValue(float value)685     void setPropertyValue(float value) {
686         mProperty.setValue(mTarget, value);
687         for (int i = 0; i < mUpdateListeners.size(); i++) {
688             if (mUpdateListeners.get(i) != null) {
689                 mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity);
690             }
691         }
692         removeNullEntries(mUpdateListeners);
693     }
694 
695     /**
696      * Returns the default threshold.
697      */
getValueThreshold()698     float getValueThreshold() {
699         return mMinVisibleChange * THRESHOLD_MULTIPLIER;
700     }
701 
702     /**
703      * Obtain the property value through the corresponding getter.
704      */
getPropertyValue()705     private float getPropertyValue() {
706         return mProperty.getValue(mTarget);
707     }
708 
709     /****************Sub class animations**************/
710     /**
711      * Returns the acceleration at the given value with the given velocity.
712      **/
getAcceleration(float value, float velocity)713     abstract float getAcceleration(float value, float velocity);
714 
715     /**
716      * Returns whether the animation has reached equilibrium.
717      */
isAtEquilibrium(float value, float velocity)718     abstract boolean isAtEquilibrium(float value, float velocity);
719 
720     /**
721      * Updates the default value threshold for the animation based on the property to be animated.
722      */
setValueThreshold(float threshold)723     abstract void setValueThreshold(float threshold);
724 
725     /**
726      * An animation listener that receives end notifications from an animation.
727      */
728     public interface OnAnimationEndListener {
729         /**
730          * Notifies the end of an animation. Note that this callback will be invoked not only when
731          * an animation reach equilibrium, but also when the animation is canceled.
732          *
733          * @param animation animation that has ended or was canceled
734          * @param canceled whether the animation has been canceled
735          * @param value the final value when the animation stopped
736          * @param velocity the final velocity when the animation stopped
737          */
onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity)738         void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
739                               float velocity);
740     }
741 
742     /**
743      * Implementors of this interface can add themselves as update listeners
744      * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation
745      * frame, after the current frame's values have been calculated for that
746      * <code>DynamicAnimation</code>.
747      */
748     public interface OnAnimationUpdateListener {
749 
750         /**
751          * Notifies the occurrence of another frame of the animation.
752          *
753          * @param animation animation that the update listener is added to
754          * @param value the current value of the animation
755          * @param velocity the current velocity of the animation
756          */
onAnimationUpdate(DynamicAnimation animation, float value, float velocity)757         void onAnimationUpdate(DynamicAnimation animation, float value, float velocity);
758     }
759 }
760