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