| /* |
| * 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 android.support.animation; |
| |
| import android.support.annotation.FloatRange; |
| |
| /** |
| * <p>Fling animation is an animation that continues an initial momentum (most often from gesture |
| * velocity) and gradually slows down. The fling animation will come to a stop when the velocity of |
| * the animation is below the threshold derived from {@link #setMinimumVisibleChange(float)}, |
| * or when the value of the animation has gone beyond the min or max value defined via |
| * {@link DynamicAnimation#setMinValue(float)} or {@link DynamicAnimation#setMaxValue(float)}. |
| * It is recommended to restrict the fling animation with min and/or max value, such that the |
| * animation can end when it goes beyond screen bounds, thus preserving CPU cycles and resources. |
| * |
| * <p>For example, you can create a fling animation that animates the translationX of a view: |
| * <pre class="prettyprint"> |
| * FlingAnimation flingAnim = new FlingAnimation(view, DynamicAnimation.TRANSLATION_X) |
| * // Sets the start velocity to -2000 (pixel/s) |
| * .setStartVelocity(-2000) |
| * // Optional but recommended to set a reasonable min and max range for the animation. |
| * // In this particular case, we set the min and max to -200 and 2000 respectively. |
| * .setMinValue(-200).setMaxValue(2000); |
| * flingAnim.start(); |
| * </pre> |
| */ |
| public final class FlingAnimation extends DynamicAnimation<FlingAnimation> { |
| |
| private final DragForce mFlingForce = new DragForce(); |
| |
| /** |
| * <p>This creates a FlingAnimation 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 FlingAnimation(FloatValueHolder floatValueHolder) { |
| super(floatValueHolder); |
| mFlingForce.setValueThreshold(getValueThreshold()); |
| } |
| |
| /** |
| * This creates a FlingAnimation that animates the property of the given object. |
| * |
| * @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> FlingAnimation(K object, FloatPropertyCompat<K> property) { |
| super(object, property); |
| mFlingForce.setValueThreshold(getValueThreshold()); |
| } |
| |
| /** |
| * Sets the friction for the fling animation. The greater the friction is, the sooner the |
| * animation will slow down. When not set, the friction defaults to 1. |
| * |
| * @param friction the friction used in the animation |
| * @return the animation whose friction will be scaled |
| * @throws IllegalArgumentException if the input friction is not positive |
| */ |
| public FlingAnimation setFriction( |
| @FloatRange(from = 0.0, fromInclusive = false) float friction) { |
| if (friction <= 0) { |
| throw new IllegalArgumentException("Friction must be positive"); |
| } |
| mFlingForce.setFrictionScalar(friction); |
| return this; |
| } |
| |
| /** |
| * Returns the friction being set on the animation via {@link #setFriction(float)}. If the |
| * friction has not been set, the default friction of 1 will be returned. |
| * |
| * @return friction being used in the animation |
| */ |
| public float getFriction() { |
| return mFlingForce.getFrictionScalar(); |
| } |
| |
| /** |
| * Sets the min value of the animation. When a fling animation reaches the min value, the |
| * animation will end immediately. Animations will not animate beyond the min value. |
| * |
| * @param minValue minimum value of the property to be animated |
| * @return the Animation whose min value is being set |
| */ |
| @Override |
| public FlingAnimation setMinValue(float minValue) { |
| super.setMinValue(minValue); |
| return this; |
| } |
| |
| /** |
| * Sets the max value of the animation. When a fling animation reaches the max value, the |
| * animation will end immediately. Animations will not animate beyond the max value. |
| * |
| * @param maxValue maximum value of the property to be animated |
| * @return the Animation whose max value is being set |
| */ |
| @Override |
| public FlingAnimation setMaxValue(float maxValue) { |
| super.setMaxValue(maxValue); |
| return this; |
| } |
| |
| /** |
| * Start velocity of the animation. Default velocity is 0. Unit: pixel/second |
| * |
| * <p>A <b>non-zero</b> start velocity is required for a FlingAnimation. If no start velocity is |
| * set through {@link #setStartVelocity(float)}, the start velocity defaults to 0. In that |
| * case, the fling animation will consider itself done in the next frame. |
| * |
| * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity |
| * through touch events), it is recommended to define such a value in dp/second and convert it |
| * to pixel/second based on the density of the screen to achieve a consistent look across |
| * different screens. |
| * |
| * <p>To convert from dp/second to pixel/second: |
| * <pre class="prettyprint"> |
| * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, |
| * getResources().getDisplayMetrics()); |
| * </pre> |
| * |
| * @param startVelocity start velocity of the animation in pixel/second |
| * @return the Animation whose start velocity is being set |
| */ |
| @Override |
| public FlingAnimation setStartVelocity(float startVelocity) { |
| super.setStartVelocity(startVelocity); |
| return this; |
| } |
| |
| @Override |
| boolean updateValueAndVelocity(long deltaT) { |
| |
| MassState state = mFlingForce.updateValueAndVelocity(mValue, mVelocity, deltaT); |
| mValue = state.mValue; |
| mVelocity = state.mVelocity; |
| |
| // When the animation hits the max/min value, consider animation done. |
| if (mValue < mMinValue) { |
| mValue = mMinValue; |
| return true; |
| } |
| if (mValue > mMaxValue) { |
| mValue = mMaxValue; |
| return true; |
| } |
| |
| if (isAtEquilibrium(mValue, mVelocity)) { |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| float getAcceleration(float value, float velocity) { |
| return mFlingForce.getAcceleration(value, velocity); |
| } |
| |
| @Override |
| boolean isAtEquilibrium(float value, float velocity) { |
| return value >= mMaxValue |
| || value <= mMinValue |
| || mFlingForce.isAtEquilibrium(value, velocity); |
| } |
| |
| @Override |
| void setValueThreshold(float threshold) { |
| mFlingForce.setValueThreshold(threshold); |
| } |
| |
| private static final class DragForce implements Force { |
| |
| private static final float DEFAULT_FRICTION = -4.2f; |
| |
| // 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 float VELOCITY_THRESHOLD_MULTIPLIER = 1000f / 16f; |
| private float mFriction = DEFAULT_FRICTION; |
| private float mVelocityThreshold; |
| |
| // Internal state to hold a value/velocity pair. |
| private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState(); |
| |
| void setFrictionScalar(float frictionScalar) { |
| mFriction = frictionScalar * DEFAULT_FRICTION; |
| } |
| |
| float getFrictionScalar() { |
| return mFriction / DEFAULT_FRICTION; |
| } |
| |
| MassState updateValueAndVelocity(float value, float velocity, long deltaT) { |
| mMassState.mVelocity = (float) (velocity * Math.exp((deltaT / 1000f) * mFriction)); |
| mMassState.mValue = (float) (value - velocity / mFriction |
| + velocity / mFriction * Math.exp(mFriction * deltaT / 1000f)); |
| if (isAtEquilibrium(mMassState.mValue, mMassState.mVelocity)) { |
| mMassState.mVelocity = 0f; |
| } |
| return mMassState; |
| } |
| |
| @Override |
| public float getAcceleration(float position, float velocity) { |
| return velocity * mFriction; |
| } |
| |
| @Override |
| public boolean isAtEquilibrium(float value, float velocity) { |
| return Math.abs(velocity) < mVelocityThreshold; |
| } |
| |
| void setValueThreshold(float threshold) { |
| mVelocityThreshold = threshold * VELOCITY_THRESHOLD_MULTIPLIER; |
| } |
| } |
| |
| } |