public abstract class

DynamicAnimation<T extends DynamicAnimation>

extends java.lang.Object

implements androidx.dynamicanimation.animation.AnimationHandler.AnimationFrameCallback

 java.lang.Object

↳androidx.dynamicanimation.animation.DynamicAnimation<T>

Subclasses:

SpringAnimation, FlingAnimation

Gradle dependencies

compile group: 'androidx.dynamicanimation', name: 'dynamicanimation', version: '1.1.0-alpha03'

  • groupId: androidx.dynamicanimation
  • artifactId: dynamicanimation
  • version: 1.1.0-alpha03

Artifact androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.dynamicanimation:dynamicanimation com.android.support:support-dynamic-animation

Androidx class mapping:

androidx.dynamicanimation.animation.DynamicAnimation android.support.animation.DynamicAnimation

Overview

This class is the base class of physics-based animations. It manages the animation's lifecycle such as DynamicAnimation.start() and DynamicAnimation.cancel(). This base class also handles the common setup for all the subclass animations. For example, DynamicAnimation supports adding DynamicAnimation.OnAnimationEndListener and DynamicAnimation.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 DynamicAnimation.setStartValue(float) and DynamicAnimation.setStartVelocity(float).

Summary

Fields
public static final DynamicAnimation.ViewPropertyALPHA

View's alpha property.

public static final floatMIN_VISIBLE_CHANGE_ALPHA

The minimum visible change in alpha that can be visible to users.

public static final floatMIN_VISIBLE_CHANGE_PIXELS

The minimum visible change in pixels that can be visible to users.

public static final floatMIN_VISIBLE_CHANGE_ROTATION_DEGREES

The minimum visible change in degrees that can be visible to users.

public static final floatMIN_VISIBLE_CHANGE_SCALE

The minimum visible change in scale that can be visible to users.

public static final DynamicAnimation.ViewPropertyROTATION

View's rotation property.

public static final DynamicAnimation.ViewPropertyROTATION_X

View's rotationX property.

public static final DynamicAnimation.ViewPropertyROTATION_Y

View's rotationY property.

public static final DynamicAnimation.ViewPropertySCALE_X

View's scaleX property.

public static final DynamicAnimation.ViewPropertySCALE_Y

View's scaleY property.

public static final DynamicAnimation.ViewPropertySCROLL_X

View's scrollX property.

public static final DynamicAnimation.ViewPropertySCROLL_Y

View's scrollY property.

public static final DynamicAnimation.ViewPropertyTRANSLATION_X

View's translationX property.

public static final DynamicAnimation.ViewPropertyTRANSLATION_Y

View's translationY property.

public static final DynamicAnimation.ViewPropertyTRANSLATION_Z

View's translationZ property.

public static final DynamicAnimation.ViewPropertyX

View's x property.

public static final DynamicAnimation.ViewPropertyY

View's y property.

public static final DynamicAnimation.ViewPropertyZ

View's z property.

Methods
public DynamicAnimation<T>addEndListener(DynamicAnimation.OnAnimationEndListener listener)

Adds an end listener to the animation for receiving onAnimationEnd callbacks.

public DynamicAnimation<T>addUpdateListener(DynamicAnimation.OnAnimationUpdateListener listener)

Adds an update listener to the animation for receiving per-frame animation update callbacks.

public voidcancel()

Cancels the on-going animation.

public booleandoAnimationFrame(long frameTime)

This gets call on each frame of the animation.

public floatgetMinimumVisibleChange()

Returns the minimum change in the animation property that could be visibly different to users.

public booleanisRunning()

Returns whether the animation is currently running.

public voidremoveEndListener(DynamicAnimation.OnAnimationEndListener listener)

Removes the end listener from the animation, so as to stop receiving animation end callbacks.

public voidremoveUpdateListener(DynamicAnimation.OnAnimationUpdateListener listener)

Removes the update listener from the animation, so as to stop receiving animation update callbacks.

public DynamicAnimation<T>setMaxValue(float max)

Sets the max value of the animation.

public DynamicAnimation<T>setMinimumVisibleChange(float minimumVisibleChange)

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.

public DynamicAnimation<T>setMinValue(float min)

Sets the min value of the animation.

public DynamicAnimation<T>setStartValue(float startValue)

Sets the start value of the animation.

public DynamicAnimation<T>setStartVelocity(float startVelocity)

Start velocity of the animation.

public voidstart()

Starts an animation.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final DynamicAnimation.ViewProperty TRANSLATION_X

View's translationX property.

public static final DynamicAnimation.ViewProperty TRANSLATION_Y

View's translationY property.

public static final DynamicAnimation.ViewProperty TRANSLATION_Z

View's translationZ property.

public static final DynamicAnimation.ViewProperty SCALE_X

View's scaleX property.

public static final DynamicAnimation.ViewProperty SCALE_Y

View's scaleY property.

public static final DynamicAnimation.ViewProperty ROTATION

View's rotation property.

public static final DynamicAnimation.ViewProperty ROTATION_X

View's rotationX property.

public static final DynamicAnimation.ViewProperty ROTATION_Y

View's rotationY property.

public static final DynamicAnimation.ViewProperty X

View's x property.

public static final DynamicAnimation.ViewProperty Y

View's y property.

public static final DynamicAnimation.ViewProperty Z

View's z property.

public static final DynamicAnimation.ViewProperty ALPHA

View's alpha property.

public static final DynamicAnimation.ViewProperty SCROLL_X

View's scrollX property.

public static final DynamicAnimation.ViewProperty SCROLL_Y

View's scrollY property.

public static final float MIN_VISIBLE_CHANGE_PIXELS

The minimum visible change in pixels that can be visible to users.

public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES

The minimum visible change in degrees that can be visible to users.

public static final float MIN_VISIBLE_CHANGE_ALPHA

The minimum visible change in alpha that can be visible to users.

public static final float MIN_VISIBLE_CHANGE_SCALE

The minimum visible change in scale that can be visible to users.

Methods

public DynamicAnimation<T> setStartValue(float startValue)

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.

Parameters:

startValue: start value for the animation

Returns:

the Animation whose start value is being set

public DynamicAnimation<T> setStartVelocity(float startVelocity)

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).

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.

To convert from dp/second to pixel/second:

 float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
         getResources().getDisplayMetrics());
 

Parameters:

startVelocity: start velocity of the animation

Returns:

the Animation whose start velocity is being set

public DynamicAnimation<T> setMaxValue(float max)

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.

Parameters:

max: maximum value of the property to be animated

Returns:

the Animation whose max value is being set

public DynamicAnimation<T> setMinValue(float min)

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.

Parameters:

min: minimum value of the property to be animated

Returns:

the Animation whose min value is being set

Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener is null or has already been added to the list of listeners for the animation, no op.

Parameters:

listener: the listener to be added

Returns:

the animation to which the listener is added

public void removeEndListener(DynamicAnimation.OnAnimationEndListener listener)

Removes the end listener from the animation, so as to stop receiving animation end callbacks.

Parameters:

listener: the listener to be removed

public DynamicAnimation<T> addUpdateListener(DynamicAnimation.OnAnimationUpdateListener listener)

Adds an update listener to the animation for receiving per-frame animation update callbacks. If the listener is null or has already been added to the list of listeners for the animation, no op.

Note that update listener should only be added before the start of the animation.

Parameters:

listener: the listener to be added

Returns:

the animation to which the listener is added

public void removeUpdateListener(DynamicAnimation.OnAnimationUpdateListener listener)

Removes the update listener from the animation, so as to stop receiving animation update callbacks.

Parameters:

listener: the listener to be removed

public DynamicAnimation<T> setMinimumVisibleChange(float minimumVisibleChange)

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-ViewPropertys) unless the custom property is in pixels.

For custom properties, this minimum visible change defaults to change in pixel (i.e. DynamicAnimation.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).

It's not necessary to call this method when animating DynamicAnimation.ViewPropertys, as the minimum visible change will be derived from the property. For example, if the property to be animated is in pixels (i.e. DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y, DynamicAnimation.TRANSLATION_Z, @DynamicAnimation.SCROLL_X or DynamicAnimation.SCROLL_Y), the default minimum visible change is 1 (pixel). For DynamicAnimation.ROTATION, DynamicAnimation.ROTATION_X or DynamicAnimation.ROTATION_Y, the animation will use DynamicAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES as the min visible change, which is 1/10. Similarly, the minimum visible change for alpha ( i.e. DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA is defined as 1 / 256.

Parameters:

minimumVisibleChange: minimum change in property value that is visible to users

Returns:

the animation whose min visible change is being set

public float getMinimumVisibleChange()

Returns the minimum change in the animation property that could be visibly different to users.

Returns:

minimum change in property value that is visible to users

public void start()

Starts an animation. If the animation has already been started, no op. Note that calling DynamicAnimation.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.

public void cancel()

Cancels the on-going animation. If the animation hasn't started, no op. Note that this method should only be called on main thread.

public boolean isRunning()

Returns whether the animation is currently running.

Returns:

true if the animation is currently running, false otherwise

public boolean doAnimationFrame(long frameTime)

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.

Source

/*
 * 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.annotation.SuppressLint;
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.
     */
    @SuppressLint("MinMaxConstant")
    public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f;
    /**
     * The minimum visible change in degrees that can be visible to users.
     */
    @SuppressLint("MinMaxConstant")
    public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f;
    /**
     * The minimum visible change in alpha that can be visible to users.
     */
    @SuppressLint("MinMaxConstant")
    public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f;
    /**
     * The minimum visible change in scale that can be visible to users.
     */
    @SuppressLint("MinMaxConstant")
    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);
    }
}