/* * 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; /** *

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. * *

For example, you can create a fling animation that animates the translationX of a view: *

 * 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();
 * 
*/ public final class FlingAnimation extends DynamicAnimation { private final DragForce mFlingForce = new DragForce(); /** *

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()}. * *

Note: 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 the class on which the property is declared */ public FlingAnimation(K object, FloatPropertyCompat 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 * *

A non-zero 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. * *

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. * *

To convert from dp/second to pixel/second: *

     * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
     *         getResources().getDisplayMetrics());
     * 
* * @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; } } }