Home | History | Annotate | Download | only in animation
      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> {
     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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    535     public float getMinimumVisibleChange() {
    536         return mMinVisibleChange;
    537     }
    538 
    539     /**
    540      * Remove {@code null} entries from the list.
    541      */
    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      */
    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      */
    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      */
    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      */
    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.
    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
    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      */
    664     abstract boolean updateValueAndVelocity(long deltaT);
    665 
    666     /**
    667      * Internal method to reset the animation states when animation is finished/canceled.
    668      */
    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      */
    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      */
    698     float getValueThreshold() {
    699         return mMinVisibleChange * THRESHOLD_MULTIPLIER;
    700     }
    701 
    702     /**
    703      * Obtain the property value through the corresponding getter.
    704      */
    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      **/
    713     abstract float getAcceleration(float value, float velocity);
    714 
    715     /**
    716      * Returns whether the animation has reached equilibrium.
    717      */
    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      */
    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          */
    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          */
    757         void onAnimationUpdate(DynamicAnimation animation, float value, float velocity);
    758     }
    759 }
    760