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 17package android.support.animation; 18 19import android.support.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 */ 41public 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 */ 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 */ 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 */ 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 */ 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 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 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 149 public FlingAnimation setStartVelocity(float startVelocity) { 150 super.setStartVelocity(startVelocity); 151 return this; 152 } 153 154 @Override 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 178 float getAcceleration(float value, float velocity) { 179 return mFlingForce.getAcceleration(value, velocity); 180 } 181 182 @Override 183 boolean isAtEquilibrium(float value, float velocity) { 184 return value >= mMaxValue 185 || value <= mMinValue 186 || mFlingForce.isAtEquilibrium(value, velocity); 187 } 188 189 @Override 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 208 void setFrictionScalar(float frictionScalar) { 209 mFriction = frictionScalar * DEFAULT_FRICTION; 210 } 211 212 float getFrictionScalar() { 213 return mFriction / DEFAULT_FRICTION; 214 } 215 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 227 public float getAcceleration(float position, float velocity) { 228 return velocity * mFriction; 229 } 230 231 @Override 232 public boolean isAtEquilibrium(float value, float velocity) { 233 return Math.abs(velocity) < mVelocityThreshold; 234 } 235 236 void setValueThreshold(float threshold) { 237 mVelocityThreshold = threshold * VELOCITY_THRESHOLD_MULTIPLIER; 238 } 239 } 240 241} 242