public final class

SpringAnimation

extends DynamicAnimation<SpringAnimation>

 java.lang.Object

androidx.dynamicanimation.animation.DynamicAnimation<SpringAnimation>

↳androidx.dynamicanimation.animation.SpringAnimation

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.SpringAnimation android.support.animation.SpringAnimation

Overview

SpringAnimation is an animation that is driven by a SpringForce. The spring force defines the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is started, on each frame the spring force will update the animation's value and velocity. The animation will continue to run until the spring force reaches equilibrium. If the spring used in the animation is undamped, the animation will never reach equilibrium. Instead, it will oscillate forever.

Developer Guides

To create a simple SpringAnimation that uses the default SpringForce:

 // Create an animation to animate view's X property, set the rest position of the
 // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
 final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
         .setStartVelocity(5000);
 anim.start();
 

Alternatively, a SpringAnimation can take a pre-configured SpringForce, and use that to drive the animation.

 // Create a low stiffness, low bounce spring at position 0.
 SpringForce spring = new SpringForce(0)
         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
         .setStiffness(SpringForce.STIFFNESS_LOW);
 // Create an animation to animate view's scaleY property, and start the animation using
 // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
 // the animation to be non-negative, effectively preventing any spring overshoot.
 final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
         .setMinValue(0).setSpring(spring).setStartValue(1);
 anim.start();
 

Summary

Fields
from DynamicAnimation<T>ALPHA, MIN_VISIBLE_CHANGE_ALPHA, MIN_VISIBLE_CHANGE_PIXELS, MIN_VISIBLE_CHANGE_ROTATION_DEGREES, MIN_VISIBLE_CHANGE_SCALE, ROTATION, ROTATION_X, ROTATION_Y, SCALE_X, SCALE_Y, SCROLL_X, SCROLL_Y, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, X, Y, Z
Constructors
publicSpringAnimation(FloatValueHolder floatValueHolder)

This creates a SpringAnimation that animates a FloatValueHolder instance.

publicSpringAnimation(FloatValueHolder floatValueHolder, float finalPosition)

This creates a SpringAnimation that animates a FloatValueHolder instance.

publicSpringAnimation(java.lang.Object object, FloatPropertyCompat<java.lang.Object> property)

This creates a SpringAnimation that animates the property of the given object.

publicSpringAnimation(java.lang.Object object, FloatPropertyCompat<java.lang.Object> property, float finalPosition)

This creates a SpringAnimation that animates the property of the given object.

Methods
public voidanimateToFinalPosition(float finalPosition)

Updates the final position of the spring.

public voidcancel()

Cancels the on-going animation.

public booleancanSkipToEnd()

Queries whether the spring can eventually come to the rest position.

public SpringForcegetSpring()

Returns the spring that the animation uses for animations.

public SpringAnimationsetSpring(SpringForce force)

Uses the given spring as the force that drives this animation.

public voidskipToEnd()

Skips to the end of the animation.

public voidstart()

Starts an animation.

from DynamicAnimation<T>addEndListener, addUpdateListener, doAnimationFrame, getMinimumVisibleChange, isRunning, removeEndListener, removeUpdateListener, setMaxValue, setMinimumVisibleChange, setMinValue, setStartValue, setStartVelocity
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public SpringAnimation(FloatValueHolder floatValueHolder)

This creates a SpringAnimation that animates a FloatValueHolder instance. During the animation, the FloatValueHolder instance will be updated via FloatValueHolder.setValue(float) each frame. The caller can obtain the up-to-date animation value via FloatValueHolder.getValue().

Note: changing the value in the FloatValueHolder via FloatValueHolder.setValue(float) outside of the animation during an animation run will not have any effect on the on-going animation.

Parameters:

floatValueHolder: the property to be animated

public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition)

This creates a SpringAnimation that animates a FloatValueHolder instance. During the animation, the FloatValueHolder instance will be updated via FloatValueHolder.setValue(float) each frame. The caller can obtain the up-to-date animation value via FloatValueHolder.getValue(). A Spring will be created with the given final position and default stiffness and damping ratio. This spring can be accessed and reconfigured through SpringAnimation.setSpring(SpringForce).

Note: changing the value in the FloatValueHolder via FloatValueHolder.setValue(float) outside of the animation during an animation run will not have any effect on the on-going animation.

Parameters:

floatValueHolder: the property to be animated
finalPosition: the final position of the spring to be created.

public SpringAnimation(java.lang.Object object, FloatPropertyCompat<java.lang.Object> property)

This creates a SpringAnimation that animates the property of the given object. Note, a spring will need to setup through SpringAnimation.setSpring(SpringForce) before the animation starts.

Parameters:

object: the Object whose property will be animated
property: the property to be animated

public SpringAnimation(java.lang.Object object, FloatPropertyCompat<java.lang.Object> property, float finalPosition)

This creates a SpringAnimation that animates the property of the given object. A Spring will be created with the given final position and default stiffness and damping ratio. This spring can be accessed and reconfigured through SpringAnimation.setSpring(SpringForce).

Parameters:

object: the Object whose property will be animated
property: the property to be animated
finalPosition: the final position of the spring to be created.

Methods

public SpringForce getSpring()

Returns the spring that the animation uses for animations.

Returns:

the spring that the animation uses for animations

public SpringAnimation setSpring(SpringForce force)

Uses the given spring as the force that drives this animation. If this spring force has its parameters re-configured during the animation, the new configuration will be reflected in the animation immediately.

Parameters:

force: a pre-defined spring force that drives the animation

Returns:

the animation that the spring force is set on

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 animateToFinalPosition(float finalPosition)

Updates the final position of the spring.

When the animation is running, calling this method would assume the position change of the spring as a continuous movement since last frame, which yields more accurate results than changing the spring position directly through SpringForce.setFinalPosition(float).

If the animation hasn't started, calling this method will change the spring position, and immediately start the animation.

Parameters:

finalPosition: rest position of the spring

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 void skipToEnd()

Skips to the end of the animation. If the spring is undamped, an java.lang.IllegalStateException will be thrown, as the animation would never reach to an end. It is recommended to check SpringAnimation.canSkipToEnd() before calling this method. This method should only be called on main thread. If animation is not running, no-op.

public boolean canSkipToEnd()

Queries whether the spring can eventually come to the rest position.

Returns:

true if the spring is damped, otherwise false

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.os.Looper;
import android.util.AndroidRuntimeException;

import androidx.annotation.MainThread;

/**
 * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines
 * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is
 * started, on each frame the spring force will update the animation's value and velocity.
 * The animation will continue to run until the spring force reaches equilibrium. If the spring used
 * in the animation is undamped, the animation will never reach equilibrium. Instead, it will
 * oscillate forever.
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 * </div>
 *
 * <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p>
 * <pre class="prettyprint">
 * // Create an animation to animate view's X property, set the rest position of the
 * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
 * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
 *         .setStartVelocity(5000);
 * anim.start();
 * </pre>
 *
 * <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and
 * use that to drive the animation. </p>
 * <pre class="prettyprint">
 * // Create a low stiffness, low bounce spring at position 0.
 * SpringForce spring = new SpringForce(0)
 *         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
 *         .setStiffness(SpringForce.STIFFNESS_LOW);
 * // Create an animation to animate view's scaleY property, and start the animation using
 * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
 * // the animation to be non-negative, effectively preventing any spring overshoot.
 * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
 *         .setMinValue(0).setSpring(spring).setStartValue(1);
 * anim.start();
 * </pre>
 */
public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {

    private SpringForce mSpring = null;
    private float mPendingPosition = UNSET;
    private static final float UNSET = Float.MAX_VALUE;
    private boolean mEndRequested = false;

    /**
     * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
     * the animation, the {@link FloatValueHolder} instance will be updated via
     * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
     * animation value via {@link FloatValueHolder#getValue()}.
     *
     * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
     * {@link FloatValueHolder#setValue(float)} outside of the animation during an
     * animation run will not have any effect on the on-going animation.
     *
     * @param floatValueHolder the property to be animated
     */
    public SpringAnimation(FloatValueHolder floatValueHolder) {
        super(floatValueHolder);
    }

    /**
     * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
     * the animation, the {@link FloatValueHolder} instance will be updated via
     * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
     * animation value via {@link FloatValueHolder#getValue()}.
     *
     * A Spring will be created with the given final position and default stiffness and damping
     * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
     *
     * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
     * {@link FloatValueHolder#setValue(float)} outside of the animation during an
     * animation run will not have any effect on the on-going animation.
     *
     * @param floatValueHolder the property to be animated
     * @param finalPosition the final position of the spring to be created.
     */
    public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) {
        super(floatValueHolder);
        mSpring = new SpringForce(finalPosition);
    }

    /**
     * This creates a SpringAnimation that animates the property of the given object.
     * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
     * the animation starts.
     *
     * @param object the Object whose property will be animated
     * @param property the property to be animated
     * @param <K> the class on which the Property is declared
     */
    public <K> SpringAnimation(K object, FloatPropertyCompat<K> property) {
        super(object, property);
    }

    /**
     * This creates a SpringAnimation that animates the property of the given object. A Spring will
     * be created with the given final position and default stiffness and damping ratio.
     * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
     *
     * @param object the Object whose property will be animated
     * @param property the property to be animated
     * @param finalPosition the final position of the spring to be created.
     * @param <K> the class on which the Property is declared
     */
    public <K> SpringAnimation(K object, FloatPropertyCompat<K> property,
            float finalPosition) {
        super(object, property);
        mSpring = new SpringForce(finalPosition);
    }

    /**
     * Returns the spring that the animation uses for animations.
     *
     * @return the spring that the animation uses for animations
     */
    public SpringForce getSpring() {
        return mSpring;
    }

    /**
     * Uses the given spring as the force that drives this animation. If this spring force has its
     * parameters re-configured during the animation, the new configuration will be reflected in the
     * animation immediately.
     *
     * @param force a pre-defined spring force that drives the animation
     * @return the animation that the spring force is set on
     */
    public SpringAnimation setSpring(SpringForce force) {
        mSpring = force;
        return this;
    }

    @MainThread
    @Override
    public void start() {
        sanityCheck();
        mSpring.setValueThreshold(getValueThreshold());
        super.start();
    }

    /**
     * Updates the final position of the spring.
     * <p/>
     * When the animation is running, calling this method would assume the position change of the
     * spring as a continuous movement since last frame, which yields more accurate results than
     * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}.
     * <p/>
     * If the animation hasn't started, calling this method will change the spring position, and
     * immediately start the animation.
     *
     * @param finalPosition rest position of the spring
     */
    public void animateToFinalPosition(float finalPosition) {
        if (isRunning()) {
            mPendingPosition = finalPosition;
        } else {
            if (mSpring == null) {
                mSpring = new SpringForce(finalPosition);
            }
            mSpring.setFinalPosition(finalPosition);
            start();
        }
    }

    /**
     * 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
    @Override
    public void cancel() {
        super.cancel();
        if (mPendingPosition != UNSET) {
            if (mSpring == null) {
                mSpring = new SpringForce(mPendingPosition);
            } else {
                mSpring.setFinalPosition(mPendingPosition);
            }
            mPendingPosition = UNSET;
        }
    }

    /**
     * Skips to the end of the animation. If the spring is undamped, an
     * {@link IllegalStateException} will be thrown, as the animation would never reach to an end.
     * It is recommended to check {@link #canSkipToEnd()} before calling this method. This method
     * should only be called on main thread. If animation is not running, no-op.
     *
     * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0)
     * @throws AndroidRuntimeException if this method is not called on the main thread
     */
    public void skipToEnd() {
        if (!canSkipToEnd()) {
            throw new UnsupportedOperationException("Spring animations can only come to an end"
                    + " when there is damping");
        }
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new AndroidRuntimeException("Animations may only be started on the main thread");
        }
        if (mRunning) {
            mEndRequested = true;
        }
    }

    /**
     * Queries whether the spring can eventually come to the rest position.
     *
     * @return {@code true} if the spring is damped, otherwise {@code false}
     */
    public boolean canSkipToEnd() {
        return mSpring.mDampingRatio > 0;
    }

    /************************ Below are private APIs *************************/

    private void sanityCheck() {
        if (mSpring == null) {
            throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
                    + " position or a spring force needs to be set.");
        }
        double finalPosition = mSpring.getFinalPosition();
        if (finalPosition > mMaxValue) {
            throw new UnsupportedOperationException("Final position of the spring cannot be greater"
                    + " than the max value.");
        } else if (finalPosition < mMinValue) {
            throw new UnsupportedOperationException("Final position of the spring cannot be less"
                    + " than the min value.");
        }
    }

    @Override
    boolean updateValueAndVelocity(long deltaT) {
        // If user had requested end, then update the value and velocity to end state and consider
        // animation done.
        if (mEndRequested) {
            if (mPendingPosition != UNSET) {
                mSpring.setFinalPosition(mPendingPosition);
                mPendingPosition = UNSET;
            }
            mValue = mSpring.getFinalPosition();
            mVelocity = 0;
            mEndRequested = false;
            return true;
        }

        if (mPendingPosition != UNSET) {
            // Approximate by considering half of the time spring position stayed at the old
            // position, half of the time it's at the new position.
            MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
            mSpring.setFinalPosition(mPendingPosition);
            mPendingPosition = UNSET;

            massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
            mValue = massState.mValue;
            mVelocity = massState.mVelocity;

        } else {
            MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
            mValue = massState.mValue;
            mVelocity = massState.mVelocity;
        }

        mValue = Math.max(mValue, mMinValue);
        mValue = Math.min(mValue, mMaxValue);

        if (isAtEquilibrium(mValue, mVelocity)) {
            mValue = mSpring.getFinalPosition();
            mVelocity = 0f;
            return true;
        }
        return false;
    }

    @Override
    float getAcceleration(float value, float velocity) {
        return mSpring.getAcceleration(value, velocity);
    }

    @Override
    boolean isAtEquilibrium(float value, float velocity) {
        return mSpring.isAtEquilibrium(value, velocity);
    }

    @Override
    void setValueThreshold(float threshold) {
    }
}