| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.dynamicanimation.animation; |
| |
| import android.os.Looper; |
| import android.util.AndroidRuntimeException; |
| import android.view.View; |
| |
| import androidx.annotation.FloatRange; |
| import androidx.annotation.MainThread; |
| import androidx.annotation.RestrictTo; |
| import androidx.core.view.ViewCompat; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * This class is the base class of physics-based animations. It manages the animation's |
| * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common |
| * setup for all the subclass animations. For example, DynamicAnimation supports adding |
| * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important |
| * animation events can be observed through the callbacks. The start conditions for any subclass of |
| * DynamicAnimation can be set using {@link #setStartValue(float)} and |
| * {@link #setStartVelocity(float)}. |
| * |
| * @param <T> subclass of DynamicAnimation |
| */ |
| public abstract class DynamicAnimation<T extends DynamicAnimation<T>> |
| implements AnimationHandler.AnimationFrameCallback { |
| |
| /** |
| * ViewProperty holds the access of a property of a {@link View}. When an animation is |
| * created with a {@link ViewProperty} instance, the corresponding property value of the view |
| * will be updated through this ViewProperty instance. |
| */ |
| public abstract static class ViewProperty extends FloatPropertyCompat<View> { |
| private ViewProperty(String name) { |
| super(name); |
| } |
| } |
| |
| /** |
| * View's translationX property. |
| */ |
| public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setTranslationX(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getTranslationX(); |
| } |
| }; |
| |
| /** |
| * View's translationY property. |
| */ |
| public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setTranslationY(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getTranslationY(); |
| } |
| }; |
| |
| /** |
| * View's translationZ property. |
| */ |
| public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") { |
| @Override |
| public void setValue(View view, float value) { |
| ViewCompat.setTranslationZ(view, value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return ViewCompat.getTranslationZ(view); |
| } |
| }; |
| |
| /** |
| * View's scaleX property. |
| */ |
| public static final ViewProperty SCALE_X = new ViewProperty("scaleX") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setScaleX(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getScaleX(); |
| } |
| }; |
| |
| /** |
| * View's scaleY property. |
| */ |
| public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setScaleY(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getScaleY(); |
| } |
| }; |
| |
| /** |
| * View's rotation property. |
| */ |
| public static final ViewProperty ROTATION = new ViewProperty("rotation") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setRotation(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getRotation(); |
| } |
| }; |
| |
| /** |
| * View's rotationX property. |
| */ |
| public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setRotationX(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getRotationX(); |
| } |
| }; |
| |
| /** |
| * View's rotationY property. |
| */ |
| public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setRotationY(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getRotationY(); |
| } |
| }; |
| |
| /** |
| * View's x property. |
| */ |
| public static final ViewProperty X = new ViewProperty("x") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setX(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getX(); |
| } |
| }; |
| |
| /** |
| * View's y property. |
| */ |
| public static final ViewProperty Y = new ViewProperty("y") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setY(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getY(); |
| } |
| }; |
| |
| /** |
| * View's z property. |
| */ |
| public static final ViewProperty Z = new ViewProperty("z") { |
| @Override |
| public void setValue(View view, float value) { |
| ViewCompat.setZ(view, value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return ViewCompat.getZ(view); |
| } |
| }; |
| |
| /** |
| * View's alpha property. |
| */ |
| public static final ViewProperty ALPHA = new ViewProperty("alpha") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setAlpha(value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getAlpha(); |
| } |
| }; |
| |
| // Properties below are not RenderThread compatible |
| /** |
| * View's scrollX property. |
| */ |
| public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setScrollX((int) value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getScrollX(); |
| } |
| }; |
| |
| /** |
| * View's scrollY property. |
| */ |
| public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") { |
| @Override |
| public void setValue(View view, float value) { |
| view.setScrollY((int) value); |
| } |
| |
| @Override |
| public float getValue(View view) { |
| return view.getScrollY(); |
| } |
| }; |
| |
| /** |
| * The minimum visible change in pixels that can be visible to users. |
| */ |
| public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f; |
| /** |
| * The minimum visible change in degrees that can be visible to users. |
| */ |
| public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f; |
| /** |
| * The minimum visible change in alpha that can be visible to users. |
| */ |
| public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f; |
| /** |
| * The minimum visible change in scale that can be visible to users. |
| */ |
| public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f; |
| |
| // Use the max value of float to indicate an unset state. |
| private static final float UNSET = Float.MAX_VALUE; |
| |
| // Multiplier to the min visible change value for value threshold |
| private static final float THRESHOLD_MULTIPLIER = 0.75f; |
| |
| // Internal tracking for velocity. |
| float mVelocity = 0; |
| |
| // Internal tracking for value. |
| float mValue = UNSET; |
| |
| // Tracks whether start value is set. If not, the animation will obtain the value at the time |
| // of starting through the getter and use that as the starting value of the animation. |
| boolean mStartValueIsSet = false; |
| |
| // Target to be animated. |
| final Object mTarget; |
| |
| // View property id. |
| final FloatPropertyCompat mProperty; |
| |
| // Package private tracking of animation lifecycle state. Visible to subclass animations. |
| boolean mRunning = false; |
| |
| // Min and max values that defines the range of the animation values. |
| float mMaxValue = Float.MAX_VALUE; |
| float mMinValue = -mMaxValue; |
| |
| // Last frame time. Always gets reset to -1 at the end of the animation. |
| private long mLastFrameTime = 0; |
| |
| private float mMinVisibleChange; |
| |
| // List of end listeners |
| private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>(); |
| |
| // List of update listeners |
| private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>(); |
| |
| // Internal state for value/velocity pair. |
| static class MassState { |
| float mValue; |
| float mVelocity; |
| } |
| |
| /** |
| * Creates a dynamic animation with the given FloatValueHolder instance. |
| * |
| * @param floatValueHolder the FloatValueHolder instance to be animated. |
| */ |
| DynamicAnimation(final FloatValueHolder floatValueHolder) { |
| mTarget = null; |
| mProperty = new FloatPropertyCompat("FloatValueHolder") { |
| @Override |
| public float getValue(Object object) { |
| return floatValueHolder.getValue(); |
| } |
| |
| @Override |
| public void setValue(Object object, float value) { |
| floatValueHolder.setValue(value); |
| } |
| }; |
| mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; |
| } |
| |
| /** |
| * Creates a dynamic animation to animate the given property for the given {@link View} |
| * |
| * @param object the Object whose property is to be animated |
| * @param property the property to be animated |
| */ |
| |
| <K> DynamicAnimation(K object, FloatPropertyCompat<K> property) { |
| mTarget = object; |
| mProperty = property; |
| if (mProperty == ROTATION || mProperty == ROTATION_X |
| || mProperty == ROTATION_Y) { |
| mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES; |
| } else if (mProperty == ALPHA) { |
| mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA; |
| } else if (mProperty == SCALE_X || mProperty == SCALE_Y) { |
| mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA; |
| } else { |
| mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS; |
| } |
| } |
| |
| /** |
| * Sets the start value of the animation. If start value is not set, the animation will get |
| * the current value for the view's property, and use that as the start value. |
| * |
| * @param startValue start value for the animation |
| * @return the Animation whose start value is being set |
| */ |
| @SuppressWarnings("unchecked") |
| public T setStartValue(float startValue) { |
| mValue = startValue; |
| mStartValueIsSet = true; |
| return (T) this; |
| } |
| |
| /** |
| * Start velocity of the animation. Default velocity is 0. Unit: change in property per |
| * second (e.g. pixels per second, scale/alpha value change per second). |
| * |
| * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity |
| * through touch events), it is recommended to define such a value in dp/second and convert it |
| * to pixel/second based on the density of the screen to achieve a consistent look across |
| * different screens. |
| * |
| * <p>To convert from dp/second to pixel/second: |
| * <pre class="prettyprint"> |
| * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, |
| * getResources().getDisplayMetrics()); |
| * </pre> |
| * |
| * @param startVelocity start velocity of the animation |
| * @return the Animation whose start velocity is being set |
| */ |
| @SuppressWarnings("unchecked") |
| public T setStartVelocity(float startVelocity) { |
| mVelocity = startVelocity; |
| return (T) this; |
| } |
| |
| /** |
| * Sets the max value of the animation. Animations will not animate beyond their max value. |
| * Whether or not animation will come to an end when max value is reached is dependent on the |
| * child animation's implementation. |
| * |
| * @param max maximum value of the property to be animated |
| * @return the Animation whose max value is being set |
| */ |
| @SuppressWarnings("unchecked") |
| public T setMaxValue(float max) { |
| // This max value should be checked and handled in the subclass animations, instead of |
| // assuming the end of the animations when the max/min value is hit in the base class. |
| // The reason is that hitting max/min value may just be a transient state, such as during |
| // the spring oscillation. |
| mMaxValue = max; |
| return (T) this; |
| } |
| |
| /** |
| * Sets the min value of the animation. Animations will not animate beyond their min value. |
| * Whether or not animation will come to an end when min value is reached is dependent on the |
| * child animation's implementation. |
| * |
| * @param min minimum value of the property to be animated |
| * @return the Animation whose min value is being set |
| */ |
| @SuppressWarnings("unchecked") |
| public T setMinValue(float min) { |
| mMinValue = min; |
| return (T) this; |
| } |
| |
| /** |
| * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener |
| * is {@code null} or has already been added to the list of listeners for the animation, no op. |
| * |
| * @param listener the listener to be added |
| * @return the animation to which the listener is added |
| */ |
| @SuppressWarnings("unchecked") |
| public T addEndListener(OnAnimationEndListener listener) { |
| if (!mEndListeners.contains(listener)) { |
| mEndListeners.add(listener); |
| } |
| return (T) this; |
| } |
| |
| /** |
| * Removes the end listener from the animation, so as to stop receiving animation end callbacks. |
| * |
| * @param listener the listener to be removed |
| */ |
| public void removeEndListener(OnAnimationEndListener listener) { |
| removeEntry(mEndListeners, listener); |
| } |
| |
| /** |
| * Adds an update listener to the animation for receiving per-frame animation update callbacks. |
| * If the listener is {@code null} or has already been added to the list of listeners for the |
| * animation, no op. |
| * |
| * <p>Note that update listener should only be added before the start of the animation. |
| * |
| * @param listener the listener to be added |
| * @return the animation to which the listener is added |
| * @throws UnsupportedOperationException if the update listener is added after the animation has |
| * started |
| */ |
| @SuppressWarnings("unchecked") |
| public T addUpdateListener(OnAnimationUpdateListener listener) { |
| if (isRunning()) { |
| // Require update listener to be added before the animation, such as when we start |
| // the animation, we know whether the animation is RenderThread compatible. |
| throw new UnsupportedOperationException("Error: Update listeners must be added before" |
| + "the animation."); |
| } |
| if (!mUpdateListeners.contains(listener)) { |
| mUpdateListeners.add(listener); |
| } |
| return (T) this; |
| } |
| |
| /** |
| * Removes the update listener from the animation, so as to stop receiving animation update |
| * callbacks. |
| * |
| * @param listener the listener to be removed |
| */ |
| public void removeUpdateListener(OnAnimationUpdateListener listener) { |
| removeEntry(mUpdateListeners, listener); |
| } |
| |
| |
| /** |
| * This method sets the minimal change of animation value that is visible to users, which helps |
| * determine a reasonable threshold for the animation's termination condition. It is critical |
| * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s) |
| * unless the custom property is in pixels. |
| * |
| * <p>For custom properties, this minimum visible change defaults to change in pixel |
| * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is |
| * reasonable for the property to be animated. A general rule of thumb to calculate such a value |
| * is: minimum visible change = range of custom property value / corresponding pixel range. For |
| * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a |
| * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5). |
| * |
| * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the |
| * minimum visible change will be derived from the property. For example, if the property to be |
| * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y}, |
| * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible |
| * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the |
| * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change, |
| * which is 1/10. Similarly, the minimum visible change for alpha ( |
| * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256. |
| * |
| * @param minimumVisibleChange minimum change in property value that is visible to users |
| * @return the animation whose min visible change is being set |
| * @throws IllegalArgumentException if the given threshold is not positive |
| */ |
| @SuppressWarnings("unchecked") |
| public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false) |
| float minimumVisibleChange) { |
| if (minimumVisibleChange <= 0) { |
| throw new IllegalArgumentException("Minimum visible change must be positive."); |
| } |
| mMinVisibleChange = minimumVisibleChange; |
| setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER); |
| return (T) this; |
| } |
| |
| /** |
| * Returns the minimum change in the animation property that could be visibly different to |
| * users. |
| * |
| * @return minimum change in property value that is visible to users |
| */ |
| public float getMinimumVisibleChange() { |
| return mMinVisibleChange; |
| } |
| |
| /** |
| * Remove {@code null} entries from the list. |
| */ |
| private static <T> void removeNullEntries(ArrayList<T> list) { |
| // Clean up null entries |
| for (int i = list.size() - 1; i >= 0; i--) { |
| if (list.get(i) == null) { |
| list.remove(i); |
| } |
| } |
| } |
| |
| /** |
| * Remove an entry from the list by marking it {@code null} and clean up later. |
| */ |
| private static <T> void removeEntry(ArrayList<T> list, T entry) { |
| int id = list.indexOf(entry); |
| if (id >= 0) { |
| list.set(id, null); |
| } |
| } |
| |
| /****************Animation Lifecycle Management***************/ |
| |
| /** |
| * Starts an animation. If the animation has already been started, no op. Note that calling |
| * {@link #start()} will not immediately set the property value to start value of the animation. |
| * The property values will be changed at each animation pulse, which happens before the draw |
| * pass. As a result, the changes will be reflected in the next frame, the same as if the values |
| * were set immediately. This method should only be called on main thread. |
| * |
| * @throws AndroidRuntimeException if this method is not called on the main thread |
| */ |
| @MainThread |
| public void start() { |
| if (Looper.myLooper() != Looper.getMainLooper()) { |
| throw new AndroidRuntimeException("Animations may only be started on the main thread"); |
| } |
| if (!mRunning) { |
| startAnimationInternal(); |
| } |
| } |
| |
| /** |
| * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method |
| * should only be called on main thread. |
| * |
| * @throws AndroidRuntimeException if this method is not called on the main thread |
| */ |
| @MainThread |
| public void cancel() { |
| if (Looper.myLooper() != Looper.getMainLooper()) { |
| throw new AndroidRuntimeException("Animations may only be canceled on the main thread"); |
| } |
| if (mRunning) { |
| endAnimationInternal(true); |
| } |
| } |
| |
| /** |
| * Returns whether the animation is currently running. |
| * |
| * @return {@code true} if the animation is currently running, {@code false} otherwise |
| */ |
| public boolean isRunning() { |
| return mRunning; |
| } |
| |
| /************************** Private APIs below ********************************/ |
| |
| // This gets called when the animation is started, to finish the setup of the animation |
| // before the animation pulsing starts. |
| private void startAnimationInternal() { |
| if (!mRunning) { |
| mRunning = true; |
| if (!mStartValueIsSet) { |
| mValue = getPropertyValue(); |
| } |
| // Sanity check: |
| if (mValue > mMaxValue || mValue < mMinValue) { |
| throw new IllegalArgumentException("Starting value need to be in between min" |
| + " value and max value"); |
| } |
| AnimationHandler.getInstance().addAnimationFrameCallback(this, 0); |
| } |
| } |
| |
| /** |
| * This gets call on each frame of the animation. Animation value and velocity are updated |
| * in this method based on the new frame time. The property value of the view being animated |
| * is then updated. The animation's ending conditions are also checked in this method. Once |
| * the animation reaches equilibrium, the animation will come to its end, and end listeners |
| * will be notified, if any. |
| * |
| * @hide |
| */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| @Override |
| public boolean doAnimationFrame(long frameTime) { |
| if (mLastFrameTime == 0) { |
| // First frame. |
| mLastFrameTime = frameTime; |
| setPropertyValue(mValue); |
| return false; |
| } |
| long deltaT = frameTime - mLastFrameTime; |
| mLastFrameTime = frameTime; |
| boolean finished = updateValueAndVelocity(deltaT); |
| // Clamp value & velocity. |
| mValue = Math.min(mValue, mMaxValue); |
| mValue = Math.max(mValue, mMinValue); |
| |
| setPropertyValue(mValue); |
| |
| if (finished) { |
| endAnimationInternal(false); |
| } |
| return finished; |
| } |
| |
| /** |
| * Updates the animation state (i.e. value and velocity). This method is package private, so |
| * subclasses can override this method to calculate the new value and velocity in their custom |
| * way. |
| * |
| * @param deltaT time elapsed in millisecond since last frame |
| * @return whether the animation has finished |
| */ |
| abstract boolean updateValueAndVelocity(long deltaT); |
| |
| /** |
| * Internal method to reset the animation states when animation is finished/canceled. |
| */ |
| private void endAnimationInternal(boolean canceled) { |
| mRunning = false; |
| AnimationHandler.getInstance().removeCallback(this); |
| mLastFrameTime = 0; |
| mStartValueIsSet = false; |
| for (int i = 0; i < mEndListeners.size(); i++) { |
| if (mEndListeners.get(i) != null) { |
| mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity); |
| } |
| } |
| removeNullEntries(mEndListeners); |
| } |
| |
| /** |
| * Updates the property value through the corresponding setter. |
| */ |
| @SuppressWarnings("unchecked") |
| void setPropertyValue(float value) { |
| mProperty.setValue(mTarget, value); |
| for (int i = 0; i < mUpdateListeners.size(); i++) { |
| if (mUpdateListeners.get(i) != null) { |
| mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity); |
| } |
| } |
| removeNullEntries(mUpdateListeners); |
| } |
| |
| /** |
| * Returns the default threshold. |
| */ |
| float getValueThreshold() { |
| return mMinVisibleChange * THRESHOLD_MULTIPLIER; |
| } |
| |
| /** |
| * Obtain the property value through the corresponding getter. |
| */ |
| @SuppressWarnings("unchecked") |
| private float getPropertyValue() { |
| return mProperty.getValue(mTarget); |
| } |
| |
| /****************Sub class animations**************/ |
| /** |
| * Returns the acceleration at the given value with the given velocity. |
| **/ |
| abstract float getAcceleration(float value, float velocity); |
| |
| /** |
| * Returns whether the animation has reached equilibrium. |
| */ |
| abstract boolean isAtEquilibrium(float value, float velocity); |
| |
| /** |
| * Updates the default value threshold for the animation based on the property to be animated. |
| */ |
| abstract void setValueThreshold(float threshold); |
| |
| /** |
| * An animation listener that receives end notifications from an animation. |
| */ |
| public interface OnAnimationEndListener { |
| /** |
| * Notifies the end of an animation. Note that this callback will be invoked not only when |
| * an animation reach equilibrium, but also when the animation is canceled. |
| * |
| * @param animation animation that has ended or was canceled |
| * @param canceled whether the animation has been canceled |
| * @param value the final value when the animation stopped |
| * @param velocity the final velocity when the animation stopped |
| */ |
| void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, |
| float velocity); |
| } |
| |
| /** |
| * Implementors of this interface can add themselves as update listeners |
| * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation |
| * frame, after the current frame's values have been calculated for that |
| * <code>DynamicAnimation</code>. |
| */ |
| public interface OnAnimationUpdateListener { |
| |
| /** |
| * Notifies the occurrence of another frame of the animation. |
| * |
| * @param animation animation that the update listener is added to |
| * @param value the current value of the animation |
| * @param velocity the current velocity of the animation |
| */ |
| void onAnimationUpdate(DynamicAnimation animation, float value, float velocity); |
| } |
| } |