• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.dynamicanimation.animation;
18 
19 import androidx.annotation.FloatRange;
20 
21 /**
22  * <p>Fling animation is an animation that continues an initial momentum (most often from gesture
23  * velocity) and gradually slows down. The fling animation will come to a stop when the velocity of
24  * the animation is below the threshold derived from {@link #setMinimumVisibleChange(float)},
25  * or when the value of the animation has gone beyond the min or max value defined via
26  * {@link DynamicAnimation#setMinValue(float)} or {@link DynamicAnimation#setMaxValue(float)}.
27  * It is recommended to restrict the fling animation with min and/or max value, such that the
28  * animation can end when it goes beyond screen bounds, thus preserving CPU cycles and resources.
29  *
30  * <p>For example, you can create a fling animation that animates the translationX of a view:
31  * <pre class="prettyprint">
32  * FlingAnimation flingAnim = new FlingAnimation(view, DynamicAnimation.TRANSLATION_X)
33  *         // Sets the start velocity to -2000 (pixel/s)
34  *         .setStartVelocity(-2000)
35  *         // Optional but recommended to set a reasonable min and max range for the animation.
36  *         // In this particular case, we set the min and max to -200 and 2000 respectively.
37  *         .setMinValue(-200).setMaxValue(2000);
38  * flingAnim.start();
39  * </pre>
40  */
41 public final class FlingAnimation extends DynamicAnimation<FlingAnimation> {
42 
43     private final DragForce mFlingForce = new DragForce();
44 
45     /**
46      * <p>This creates a FlingAnimation that animates a {@link FloatValueHolder} instance. During
47      * the animation, the {@link FloatValueHolder} instance will be updated via
48      * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
49      * animation value via {@link FloatValueHolder#getValue()}.
50      *
51      * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
52      * {@link FloatValueHolder#setValue(float)} outside of the animation during an
53      * animation run will not have any effect on the on-going animation.
54      *
55      * @param floatValueHolder the property to be animated
56      */
FlingAnimation(FloatValueHolder floatValueHolder)57     public FlingAnimation(FloatValueHolder floatValueHolder) {
58         super(floatValueHolder);
59         mFlingForce.setValueThreshold(getValueThreshold());
60     }
61 
62     /**
63      * This creates a FlingAnimation that animates the property of the given object.
64      *
65      * @param object the Object whose property will be animated
66      * @param property the property to be animated
67      * @param <K> the class on which the property is declared
68      */
FlingAnimation(K object, FloatPropertyCompat<K> property)69     public <K> FlingAnimation(K object, FloatPropertyCompat<K> property) {
70         super(object, property);
71         mFlingForce.setValueThreshold(getValueThreshold());
72     }
73 
74     /**
75      * Sets the friction for the fling animation. The greater the friction is, the sooner the
76      * animation will slow down. When not set, the friction defaults to 1.
77      *
78      * @param friction the friction used in the animation
79      * @return the animation whose friction will be scaled
80      * @throws IllegalArgumentException if the input friction is not positive
81      */
setFriction( @loatRangefrom = 0.0, fromInclusive = false) float friction)82     public FlingAnimation setFriction(
83             @FloatRange(from = 0.0, fromInclusive = false) float friction) {
84         if (friction <= 0) {
85             throw new IllegalArgumentException("Friction must be positive");
86         }
87         mFlingForce.setFrictionScalar(friction);
88         return this;
89     }
90 
91     /**
92      * Returns the friction being set on the animation via {@link #setFriction(float)}. If the
93      * friction has not been set, the default friction of 1 will be returned.
94      *
95      * @return friction being used in the animation
96      */
getFriction()97     public float getFriction() {
98         return mFlingForce.getFrictionScalar();
99     }
100 
101     /**
102      * Sets the min value of the animation. When a fling animation reaches the min value, the
103      * animation will end immediately. Animations will not animate beyond the min value.
104      *
105      * @param minValue minimum value of the property to be animated
106      * @return the Animation whose min value is being set
107      */
108     @Override
setMinValue(float minValue)109     public FlingAnimation setMinValue(float minValue) {
110         super.setMinValue(minValue);
111         return this;
112     }
113 
114     /**
115      * Sets the max value of the animation. When a fling animation reaches the max value, the
116      * animation will end immediately. Animations will not animate beyond the max value.
117      *
118      * @param maxValue maximum value of the property to be animated
119      * @return the Animation whose max value is being set
120      */
121     @Override
setMaxValue(float maxValue)122     public FlingAnimation setMaxValue(float maxValue) {
123         super.setMaxValue(maxValue);
124         return this;
125     }
126 
127     /**
128      * Start velocity of the animation. Default velocity is 0. Unit: pixel/second
129      *
130      * <p>A <b>non-zero</b> start velocity is required for a FlingAnimation. If no start velocity is
131      * set through {@link #setStartVelocity(float)}, the start velocity defaults to 0. In that
132      * case, the fling animation will consider itself done in the next frame.
133      *
134      * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity
135      * through touch events), it is recommended to define such a value in dp/second and convert it
136      * to pixel/second based on the density of the screen to achieve a consistent look across
137      * different screens.
138      *
139      * <p>To convert from dp/second to pixel/second:
140      * <pre class="prettyprint">
141      * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
142      *         getResources().getDisplayMetrics());
143      * </pre>
144      *
145      * @param startVelocity start velocity of the animation in pixel/second
146      * @return the Animation whose start velocity is being set
147      */
148     @Override
setStartVelocity(float startVelocity)149     public FlingAnimation setStartVelocity(float startVelocity) {
150         super.setStartVelocity(startVelocity);
151         return this;
152     }
153 
154     @Override
updateValueAndVelocity(long deltaT)155     boolean updateValueAndVelocity(long deltaT) {
156 
157         MassState state = mFlingForce.updateValueAndVelocity(mValue, mVelocity, deltaT);
158         mValue = state.mValue;
159         mVelocity = state.mVelocity;
160 
161         // When the animation hits the max/min value, consider animation done.
162         if (mValue < mMinValue) {
163             mValue = mMinValue;
164             return true;
165         }
166         if (mValue > mMaxValue) {
167             mValue = mMaxValue;
168             return true;
169         }
170 
171         if (isAtEquilibrium(mValue, mVelocity)) {
172             return true;
173         }
174         return false;
175     }
176 
177     @Override
getAcceleration(float value, float velocity)178     float getAcceleration(float value, float velocity) {
179         return mFlingForce.getAcceleration(value, velocity);
180     }
181 
182     @Override
isAtEquilibrium(float value, float velocity)183     boolean isAtEquilibrium(float value, float velocity) {
184         return value >= mMaxValue
185                 || value <= mMinValue
186                 || mFlingForce.isAtEquilibrium(value, velocity);
187     }
188 
189     @Override
setValueThreshold(float threshold)190     void setValueThreshold(float threshold) {
191         mFlingForce.setValueThreshold(threshold);
192     }
193 
194     private static final class DragForce implements Force {
195 
196         private static final float DEFAULT_FRICTION = -4.2f;
197 
198         // This multiplier is used to calculate the velocity threshold given a certain value
199         // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount,
200         // then the velocity is a reasonable threshold.
201         private static final float VELOCITY_THRESHOLD_MULTIPLIER = 1000f / 16f;
202         private float mFriction = DEFAULT_FRICTION;
203         private float mVelocityThreshold;
204 
205         // Internal state to hold a value/velocity pair.
206         private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState();
207 
setFrictionScalar(float frictionScalar)208         void setFrictionScalar(float frictionScalar) {
209             mFriction = frictionScalar * DEFAULT_FRICTION;
210         }
211 
getFrictionScalar()212         float getFrictionScalar() {
213             return mFriction / DEFAULT_FRICTION;
214         }
215 
updateValueAndVelocity(float value, float velocity, long deltaT)216         MassState updateValueAndVelocity(float value, float velocity, long deltaT) {
217             mMassState.mVelocity = (float) (velocity * Math.exp((deltaT / 1000f) * mFriction));
218             mMassState.mValue = (float) (value - velocity / mFriction
219                     + velocity / mFriction * Math.exp(mFriction * deltaT / 1000f));
220             if (isAtEquilibrium(mMassState.mValue, mMassState.mVelocity)) {
221                 mMassState.mVelocity = 0f;
222             }
223             return mMassState;
224         }
225 
226         @Override
getAcceleration(float position, float velocity)227         public float getAcceleration(float position, float velocity) {
228             return velocity * mFriction;
229         }
230 
231         @Override
isAtEquilibrium(float value, float velocity)232         public boolean isAtEquilibrium(float value, float velocity) {
233             return Math.abs(velocity) < mVelocityThreshold;
234         }
235 
setValueThreshold(float threshold)236         void setValueThreshold(float threshold) {
237             mVelocityThreshold = threshold * VELOCITY_THRESHOLD_MULTIPLIER;
238         }
239     }
240 
241 }
242