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