SpringAnimation.java revision d5206a7781d95cba12ca70a20f7ee742a2e9d807
1d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu/*
2d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * Copyright (C) 2017 The Android Open Source Project
3d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu *
4d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * Licensed under the Apache License, Version 2.0 (the "License");
5d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * you may not use this file except in compliance with the License.
6d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * You may obtain a copy of the License at
7d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu *
8d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu *      http://www.apache.org/licenses/LICENSE-2.0
9d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu *
10d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * Unless required by applicable law or agreed to in writing, software
11d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * distributed under the License is distributed on an "AS IS" BASIS,
12d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * See the License for the specific language governing permissions and
14d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * limitations under the License.
15d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu */
16d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
17d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liupackage android.support.animation;
18d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
19d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liuimport android.os.Looper;
20d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liuimport android.util.AndroidRuntimeException;
21d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liuimport android.view.View;
22d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
23d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu/**
24d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines
25d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is
26d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * started, on each frame the spring force will update the animation's value and velocity.
27d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * The animation will continue to run until the spring force reaches equilibrium. If the spring used
28d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * in the animation is undamped, the animation will never reach equilibrium. Instead, it will
29d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu * oscillate forever.
30d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu */
31d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liupublic final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
32d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
33d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    private SpringForce mSpring = null;
34d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    private float mPendingPosition = UNSET;
35d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    private static final float UNSET = Float.MAX_VALUE;
36d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
37d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
38d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * This creates a SpringAnimation that animates the property of the given view.
39d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
40d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * the animation starts.
41d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
42d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param v The View whose property will be animated
43d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param property the property index of the view
44d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
45d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public SpringAnimation(View v, ViewProperty property) {
46d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        super(v, property);
47d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
48d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
49d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
50d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * This creates a SpringAnimation that animates the property of the given view. A Spring will be
51d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * created with the given final position and default stiffness and damping ratio.
52d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
53d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
54d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param v The View whose property will be animated
55d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param property the property index of the view
56d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param finalPosition the final position of the spring to be created.
57d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
58d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public SpringAnimation(View v, ViewProperty property, float finalPosition) {
59d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        super(v, property);
60d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        mSpring = new SpringForce(finalPosition);
61d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        setSpringThreshold();
62d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
63d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
64d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
65d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * Returns the spring that the animation uses for animations.
66d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
67d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @return the spring that the animation uses for animations
68d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
69d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public SpringForce getSpring() {
70d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        return mSpring;
71d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
72d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
73d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
74d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * Uses the given spring as the force that drives this animation. If this spring force has its
75d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * parameters re-configured during the animation, the new configuration will be reflected in the
76d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * animation immediately.
77d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
78d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param force a pre-defined spring force that drives the animation
79d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @return the animation that the spring force is set on
80d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
81d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public SpringAnimation setSpring(SpringForce force) {
82d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        mSpring = force;
83d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        setSpringThreshold();
84d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        return this;
85d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
86d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
87d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    @Override
88d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public void start() {
89d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        sanityCheck();
90d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        super.start();
91d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
92d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
93d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
94d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * Updates the final position of the spring.
95d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * <p/>
96d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * When the animation is running, calling this method would assume the position change of the
97d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * spring as a continuous movement since last frame, which yields more accurate results than
98d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}.
99d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * <p/>
100d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * If the animation hasn't started, calling this method will change the spring position, and
101d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * immediately start the animation.
102d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
103d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @param finalPosition rest position of the spring
104d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
105d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public void animateToFinalPosition(float finalPosition) {
106d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (isRunning()) {
107d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mPendingPosition = finalPosition;
108d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        } else {
109d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            if (mSpring == null) {
110d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                mSpring = new SpringForce(finalPosition);
111d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            }
112d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mSpring.setFinalPosition(finalPosition);
113d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            start();
114d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
115d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
116d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
117d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
118d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * Skips to the end of the animation. If the spring is undamped, an
119d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * {@link IllegalStateException} will be thrown, as the animation would never reach to an end.
120d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * It is recommended to check {@link #canSkipToEnd()} before calling this method. This method
121d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * should only be called on main thread. If animation is not running, no-op.
122d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
123d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0)
124d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @throws AndroidRuntimeException if this method is not called on the main thread
125d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
126d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public void skipToEnd() {
127d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (!canSkipToEnd()) {
128d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            throw new UnsupportedOperationException("Spring animations can only come to an end"
129d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                    + " when there is damping");
130d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
131d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (Looper.myLooper() != Looper.getMainLooper()) {
132d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            throw new AndroidRuntimeException("Animations may only be started on the main thread");
133d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
134d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (mRunning) {
135d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            if (mPendingPosition != UNSET) {
136d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                mSpring.setFinalPosition(mPendingPosition);
137d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                mPendingPosition = UNSET;
138d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            }
139d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mValue = mSpring.getFinalPosition();
140d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mVelocity = 0;
141d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            cancel();
142d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
143d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
144d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
145d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /**
146d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * Queries whether the spring can eventually come to the rest position.
147d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     *
148d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     * @return {@code true} if the spring is damped, otherwise {@code false}
149d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu     */
150d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    public boolean canSkipToEnd() {
151d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        return mSpring.mDampingRatio > 0;
152d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
153d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
154d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    /************************ Below are private APIs *************************/
155d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
156d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    private void setSpringThreshold() {
157d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (mViewProperty == ROTATION || mViewProperty == ROTATION_X
158d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                || mViewProperty == ROTATION_Y) {
159d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mSpring.setDefaultThreshold(SpringForce.VALUE_THRESHOLD_ROTATION);
160d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        } else if (mViewProperty == ALPHA) {
161d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mSpring.setDefaultThreshold(SpringForce.VALUE_THRESHOLD_ALPHA);
162d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        } else if (mViewProperty == SCALE_X || mViewProperty == SCALE_Y) {
163d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mSpring.setDefaultThreshold(SpringForce.VALUE_THRESHOLD_SCALE);
164d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        } else {
165d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mSpring.setDefaultThreshold(SpringForce.VALUE_THRESHOLD_IN_PIXEL);
166d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
167d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
168d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
169d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    private void sanityCheck() {
170d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (mSpring == null) {
171d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
172d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                    + " position or a spring force needs to be set.");
173d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
174d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        double finalPosition = mSpring.getFinalPosition();
175d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (finalPosition > mMaxValue) {
176d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            throw new UnsupportedOperationException("Final position of the spring cannot be greater"
177d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                    + " than the max value.");
178d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        } else if (finalPosition < mMinValue) {
179d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            throw new UnsupportedOperationException("Final position of the spring cannot be less"
180d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu                    + " than the min value.");
181d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
182d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
183d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
184d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    @Override
185d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    boolean updateValueAndVelocity(long deltaT) {
186d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (mPendingPosition != UNSET) {
187d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            double lastPosition = mSpring.getFinalPosition();
188d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            // Approximate by considering half of the time spring position stayed at the old
189d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            // position, half of the time it's at the new position.
190d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            SpringForce.MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
191d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mSpring.setFinalPosition(mPendingPosition);
192d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mPendingPosition = UNSET;
193d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
194d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
195d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mValue = massState.mValue;
196d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mVelocity = massState.mVelocity;
197d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
198d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        } else {
199d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            SpringForce.MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
200d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mValue = massState.mValue;
201d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mVelocity = massState.mVelocity;
202d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
203d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
204d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        mValue = Math.max(mValue, mMinValue);
205d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        mValue = Math.min(mValue, mMaxValue);
206d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
207d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        if (isAtEquilibrium(mValue, mVelocity)) {
208d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mValue = mSpring.getFinalPosition();
209d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            mVelocity = 0f;
210d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu            return true;
211d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        }
212d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        return false;
213d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
214d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
215d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    @Override
216d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    float getAcceleration(float value, float velocity) {
217d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        return mSpring.getAcceleration(value, velocity);
218d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
219d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu
220d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    @Override
221d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    boolean isAtEquilibrium(float value, float velocity) {
222d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu        return mSpring.isAtEquilibrium(value, velocity);
223d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu    }
224d5206a7781d95cba12ca70a20f7ee742a2e9d807Doris Liu}
225