public final class

SpringForce

extends java.lang.Object

implements androidx.dynamicanimation.animation.Force

 java.lang.Object

↳androidx.dynamicanimation.animation.SpringForce

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

Overview

Spring Force defines the characteristics of the spring being used in the animation.

By configuring the stiffness and damping ratio, callers can create a spring with the look and feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring is, the harder it is to stretch it, the faster it undergoes dampening.

Spring damping ratio describes how oscillations in a system decay after a disturbance. When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will return to equilibrium within the shortest amount of time. When damping ratio is less than 1 (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any damping (i.e. damping ratio = 0), the mass will oscillate forever.

Summary

Fields
public static final floatDAMPING_RATIO_HIGH_BOUNCY

Damping ratio for a very bouncy spring.

public static final floatDAMPING_RATIO_LOW_BOUNCY

Damping ratio for a spring with low bounciness.

public static final floatDAMPING_RATIO_MEDIUM_BOUNCY

Damping ratio for a medium bouncy spring.

public static final floatDAMPING_RATIO_NO_BOUNCY

Damping ratio for a spring with no bounciness.

public static final floatSTIFFNESS_HIGH

Stiffness constant for extremely stiff spring.

public static final floatSTIFFNESS_LOW

Stiffness constant for a spring with low stiffness.

public static final floatSTIFFNESS_MEDIUM

Stiffness constant for medium stiff spring.

public static final floatSTIFFNESS_VERY_LOW

Stiffness constant for a spring with very low stiffness.

Constructors
publicSpringForce()

Creates a spring force.

publicSpringForce(float finalPosition)

Creates a spring with a given final rest position.

Methods
public floatgetAcceleration(float lastDisplacement, float lastVelocity)

public floatgetDampingRatio()

Returns the damping ratio of the spring.

public floatgetFinalPosition()

Returns the rest position of the spring.

public floatgetStiffness()

Gets the stiffness of the spring.

public booleanisAtEquilibrium(float value, float velocity)

public SpringForcesetDampingRatio(float dampingRatio)

Spring damping ratio describes how oscillations in a system decay after a disturbance.

public SpringForcesetFinalPosition(float finalPosition)

Sets the rest position of the spring.

public SpringForcesetStiffness(float stiffness)

Sets the stiffness of a spring.

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

Fields

public static final float STIFFNESS_HIGH

Stiffness constant for extremely stiff spring.

public static final float STIFFNESS_MEDIUM

Stiffness constant for medium stiff spring. This is the default stiffness for spring force.

public static final float STIFFNESS_LOW

Stiffness constant for a spring with low stiffness.

public static final float STIFFNESS_VERY_LOW

Stiffness constant for a spring with very low stiffness.

public static final float DAMPING_RATIO_HIGH_BOUNCY

Damping ratio for a very bouncy spring. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.

public static final float DAMPING_RATIO_MEDIUM_BOUNCY

Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.

public static final float DAMPING_RATIO_LOW_BOUNCY

Damping ratio for a spring with low bounciness. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.

public static final float DAMPING_RATIO_NO_BOUNCY

Damping ratio for a spring with no bounciness. This damping ratio will create a critically damped spring that returns to equilibrium within the shortest amount of time without oscillating.

Constructors

public SpringForce()

Creates a spring force. Note that final position of the spring must be set through SpringForce.setFinalPosition(float) before the spring animation starts.

public SpringForce(float finalPosition)

Creates a spring with a given final rest position.

Parameters:

finalPosition: final position of the spring when it reaches equilibrium

Methods

public SpringForce setStiffness(float stiffness)

Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to the object attached when the spring is not at the final position. Default stiffness is SpringForce.STIFFNESS_MEDIUM.

Parameters:

stiffness: non-negative stiffness constant of a spring

Returns:

the spring force that the given stiffness is set on

public float getStiffness()

Gets the stiffness of the spring.

Returns:

the stiffness of the spring

public SpringForce setDampingRatio(float dampingRatio)

Spring damping ratio describes how oscillations in a system decay after a disturbance.

When damping ratio > 1 (over-damped), the object will quickly return to the rest position without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will return to equilibrium within the shortest amount of time. When damping ratio is less than 1 (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any damping (i.e. damping ratio = 0), the mass will oscillate forever.

Default damping ratio is SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY.

Parameters:

dampingRatio: damping ratio of the spring, it should be non-negative

Returns:

the spring force that the given damping ratio is set on

public float getDampingRatio()

Returns the damping ratio of the spring.

Returns:

damping ratio of the spring

public SpringForce setFinalPosition(float finalPosition)

Sets the rest position of the spring.

Parameters:

finalPosition: rest position of the spring

Returns:

the spring force that the given final position is set on

public float getFinalPosition()

Returns the rest position of the spring.

Returns:

rest position of the spring

public float getAcceleration(float lastDisplacement, float lastVelocity)

public boolean isAtEquilibrium(float value, float velocity)

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 androidx.annotation.FloatRange;
import androidx.annotation.RestrictTo;

/**
 * Spring Force defines the characteristics of the spring being used in the animation.
 * <p>
 * By configuring the stiffness and damping ratio, callers can create a spring with the look and
 * feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring
 * is, the harder it is to stretch it, the faster it undergoes dampening.
 * <p>
 * Spring damping ratio describes how oscillations in a system decay after a disturbance.
 * When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position
 * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
 * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
 * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any
 * damping (i.e. damping ratio = 0), the mass will oscillate forever.
 */
public final class SpringForce implements Force {
    /**
     * Stiffness constant for extremely stiff spring.
     */
    public static final float STIFFNESS_HIGH = 10_000f;
    /**
     * Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
     */
    public static final float STIFFNESS_MEDIUM = 1500f;
    /**
     * Stiffness constant for a spring with low stiffness.
     */
    public static final float STIFFNESS_LOW = 200f;
    /**
     * Stiffness constant for a spring with very low stiffness.
     */
    public static final float STIFFNESS_VERY_LOW = 50f;

    /**
     * Damping ratio for a very bouncy spring. Note for under-damped springs
     * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
     */
    public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
    /**
     * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
     * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
     * the more bouncy the spring.
     */
    public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
    /**
     * Damping ratio for a spring with low bounciness. Note for under-damped springs
     * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
     */
    public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
    /**
     * Damping ratio for a spring with no bounciness. This damping ratio will create a critically
     * damped spring that returns to equilibrium within the shortest amount of time without
     * oscillating.
     */
    public static final float DAMPING_RATIO_NO_BOUNCY = 1f;

    // This multiplier is used to calculate the velocity threshold given a certain value threshold.
    // The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity
    // is a reasonable threshold.
    private static final double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0;

    // Natural frequency
    double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM);
    // Damping ratio.
    double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY;

    // Value to indicate an unset state.
    private static final double UNSET = Double.MAX_VALUE;

    // Indicates whether the spring has been initialized
    private boolean mInitialized = false;

    // Threshold for velocity and value to determine when it's reasonable to assume that the spring
    // is approximately at rest.
    private double mValueThreshold;
    private double mVelocityThreshold;

    // Intermediate values to simplify the spring function calculation per frame.
    private double mGammaPlus;
    private double mGammaMinus;
    private double mDampedFreq;

    // Final position of the spring. This must be set before the start of the animation.
    private double mFinalPosition = UNSET;

    // Internal state to hold a value/velocity pair.
    private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState();

    /**
     * Creates a spring force. Note that final position of the spring must be set through
     * {@link #setFinalPosition(float)} before the spring animation starts.
     */
    public SpringForce() {
        // No op.
    }

    /**
     * Creates a spring with a given final rest position.
     *
     * @param finalPosition final position of the spring when it reaches equilibrium
     */
    public SpringForce(float finalPosition) {
        mFinalPosition = finalPosition;
    }

    /**
     * Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to
     * the object attached when the spring is not at the final position. Default stiffness is
     * {@link #STIFFNESS_MEDIUM}.
     *
     * @param stiffness non-negative stiffness constant of a spring
     * @return the spring force that the given stiffness is set on
     * @throws IllegalArgumentException if the given spring stiffness is not positive
     */
    public SpringForce setStiffness(
            @FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
        if (stiffness <= 0) {
            throw new IllegalArgumentException("Spring stiffness constant must be positive.");
        }
        mNaturalFreq = Math.sqrt(stiffness);
        // All the intermediate values need to be recalculated.
        mInitialized = false;
        return this;
    }

    /**
     * Gets the stiffness of the spring.
     *
     * @return the stiffness of the spring
     */
    public float getStiffness() {
        return (float) (mNaturalFreq * mNaturalFreq);
    }

    /**
     * Spring damping ratio describes how oscillations in a system decay after a disturbance.
     * <p>
     * When damping ratio > 1 (over-damped), the object will quickly return to the rest position
     * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
     * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
     * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without
     * any damping (i.e. damping ratio = 0), the mass will oscillate forever.
     * <p>
     * Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}.
     *
     * @param dampingRatio damping ratio of the spring, it should be non-negative
     * @return the spring force that the given damping ratio is set on
     * @throws IllegalArgumentException if the {@param dampingRatio} is negative.
     */
    public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) {
        if (dampingRatio < 0) {
            throw new IllegalArgumentException("Damping ratio must be non-negative");
        }
        mDampingRatio = dampingRatio;
        // All the intermediate values need to be recalculated.
        mInitialized = false;
        return this;
    }

    /**
     * Returns the damping ratio of the spring.
     *
     * @return damping ratio of the spring
     */
    public float getDampingRatio() {
        return (float) mDampingRatio;
    }

    /**
     * Sets the rest position of the spring.
     *
     * @param finalPosition rest position of the spring
     * @return the spring force that the given final position is set on
     */
    public SpringForce setFinalPosition(float finalPosition) {
        mFinalPosition = finalPosition;
        return this;
    }

    /**
     * Returns the rest position of the spring.
     *
     * @return rest position of the spring
     */
    public float getFinalPosition() {
        return (float) mFinalPosition;
    }

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

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Override
    public float getAcceleration(float lastDisplacement, float lastVelocity) {

        lastDisplacement -= getFinalPosition();

        double k = mNaturalFreq * mNaturalFreq;
        double c = 2 * mNaturalFreq * mDampingRatio;

        return (float) (-k * lastDisplacement - c * lastVelocity);
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Override
    public boolean isAtEquilibrium(float value, float velocity) {
        if (Math.abs(velocity) < mVelocityThreshold
                && Math.abs(value - getFinalPosition()) < mValueThreshold) {
            return true;
        }
        return false;
    }

    /**
     * Initialize the string by doing the necessary pre-calculation as well as some sanity check
     * on the setup.
     *
     * @throws IllegalStateException if the final position is not yet set by the time the spring
     *                               animation has started
     */
    private void init() {
        if (mInitialized) {
            return;
        }

        if (mFinalPosition == UNSET) {
            throw new IllegalStateException("Error: Final position of the spring must be"
                    + " set before the animation starts");
        }

        if (mDampingRatio > 1) {
            // Over damping
            mGammaPlus = -mDampingRatio * mNaturalFreq
                    + mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
            mGammaMinus = -mDampingRatio * mNaturalFreq
                    - mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
        } else if (mDampingRatio >= 0 && mDampingRatio < 1) {
            // Under damping
            mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
        }

        mInitialized = true;
    }

    /**
     * Internal only call for Spring to calculate the spring position/velocity using
     * an analytical approach.
     */
    DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
            long timeElapsed) {
        init();

        double deltaT = timeElapsed / 1000d; // unit: seconds
        lastDisplacement -= mFinalPosition;
        double displacement;
        double currentVelocity;
        if (mDampingRatio > 1) {
            // Overdamped
            double coeffA =  lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
                    / (mGammaMinus - mGammaPlus);
            double coeffB =  (mGammaMinus * lastDisplacement - lastVelocity)
                    / (mGammaMinus - mGammaPlus);
            displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
                    + coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
            currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
                    + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
        } else if (mDampingRatio == 1) {
            // Critically damped
            double coeffA = lastDisplacement;
            double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
            displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
            currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
                    * -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
        } else {
            // Underdamped
            double cosCoeff = lastDisplacement;
            double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
                    * lastDisplacement + lastVelocity);
            displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
                    * (cosCoeff * Math.cos(mDampedFreq * deltaT)
                    + sinCoeff * Math.sin(mDampedFreq * deltaT));
            currentVelocity = displacement * -mNaturalFreq * mDampingRatio
                    + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
                    * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
                    + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
        }

        mMassState.mValue = (float) (displacement + mFinalPosition);
        mMassState.mVelocity = (float) currentVelocity;
        return mMassState;
    }

    /**
     * This threshold defines how close the animation value needs to be before the animation can
     * finish. This default value is based on the property being animated, e.g. animations on alpha,
     * scale, translation or rotation would have different thresholds. This value should be small
     * enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that
     * animations take seconds to finish.
     *
     * @param threshold the difference between the animation value and final spring position that
     *                  is allowed to end the animation when velocity is very low
     */
    void setValueThreshold(double threshold) {
        mValueThreshold = Math.abs(threshold);
        mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER;
    }
}