Merge "Add AndroidX SpringAnimation library to core." into tm-qpr-dev am: a24bed9f38
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20171590 Change-Id: I40d68291f995c5db5adda9e3ed6d7b8fa3ea6840 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -432,8 +432,9 @@ public class AnimationHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Callbacks that receives notifications for animation timing and frame commit timing.
|
* Callbacks that receives notifications for animation timing and frame commit timing.
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
interface AnimationFrameCallback {
|
public interface AnimationFrameCallback {
|
||||||
/**
|
/**
|
||||||
* Run animation based on the frame time.
|
* Run animation based on the frame time.
|
||||||
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
|
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
|
||||||
|
|||||||
@@ -0,0 +1,815 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.internal.dynamicanimation.animation;
|
||||||
|
|
||||||
|
import android.animation.AnimationHandler;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.annotation.FloatRange;
|
||||||
|
import android.annotation.MainThread;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.AndroidRuntimeException;
|
||||||
|
import android.util.FloatProperty;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the base class of physics-based animations. It manages the animation's
|
||||||
|
* lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common
|
||||||
|
* setup for all the subclass animations. For example, DynamicAnimation supports adding
|
||||||
|
* {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important
|
||||||
|
* animation events can be observed through the callbacks. The start conditions for any subclass of
|
||||||
|
* DynamicAnimation can be set using {@link #setStartValue(float)} and
|
||||||
|
* {@link #setStartVelocity(float)}.
|
||||||
|
*
|
||||||
|
* @param <T> subclass of DynamicAnimation
|
||||||
|
*/
|
||||||
|
public abstract class DynamicAnimation<T extends DynamicAnimation<T>>
|
||||||
|
implements AnimationHandler.AnimationFrameCallback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewProperty holds the access of a property of a {@link View}. When an animation is
|
||||||
|
* created with a {@link ViewProperty} instance, the corresponding property value of the view
|
||||||
|
* will be updated through this ViewProperty instance.
|
||||||
|
*/
|
||||||
|
public abstract static class ViewProperty extends FloatProperty<View> {
|
||||||
|
private ViewProperty(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's translationX property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setTranslationX(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getTranslationX();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's translationY property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setTranslationY(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getTranslationY();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's translationZ property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setTranslationZ(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getTranslationZ();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's scaleX property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty SCALE_X = new ViewProperty("scaleX") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setScaleX(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getScaleX();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's scaleY property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setScaleY(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getScaleY();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's rotation property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty ROTATION = new ViewProperty("rotation") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setRotation(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getRotation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's rotationX property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setRotationX(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getRotationX();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's rotationY property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setRotationY(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getRotationY();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's x property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty X = new ViewProperty("x") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setX(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getX();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's y property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty Y = new ViewProperty("y") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setY(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getY();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's z property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty Z = new ViewProperty("z") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setZ(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getZ();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's alpha property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty ALPHA = new ViewProperty("alpha") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setAlpha(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return view.getAlpha();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Properties below are not RenderThread compatible
|
||||||
|
/**
|
||||||
|
* View's scrollX property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setScrollX((int) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return (float) view.getScrollX();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View's scrollY property.
|
||||||
|
*/
|
||||||
|
public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") {
|
||||||
|
@Override
|
||||||
|
public void setValue(View view, float value) {
|
||||||
|
view.setScrollY((int) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(View view) {
|
||||||
|
return (float) view.getScrollY();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum visible change in pixels that can be visible to users.
|
||||||
|
*/
|
||||||
|
@SuppressLint("MinMaxConstant")
|
||||||
|
public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f;
|
||||||
|
/**
|
||||||
|
* The minimum visible change in degrees that can be visible to users.
|
||||||
|
*/
|
||||||
|
@SuppressLint("MinMaxConstant")
|
||||||
|
public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f;
|
||||||
|
/**
|
||||||
|
* The minimum visible change in alpha that can be visible to users.
|
||||||
|
*/
|
||||||
|
@SuppressLint("MinMaxConstant")
|
||||||
|
public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f;
|
||||||
|
/**
|
||||||
|
* The minimum visible change in scale that can be visible to users.
|
||||||
|
*/
|
||||||
|
@SuppressLint("MinMaxConstant")
|
||||||
|
public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f;
|
||||||
|
|
||||||
|
// Use the max value of float to indicate an unset state.
|
||||||
|
private static final float UNSET = Float.MAX_VALUE;
|
||||||
|
|
||||||
|
// Multiplier to the min visible change value for value threshold
|
||||||
|
private static final float THRESHOLD_MULTIPLIER = 0.75f;
|
||||||
|
|
||||||
|
// Internal tracking for velocity.
|
||||||
|
float mVelocity = 0;
|
||||||
|
|
||||||
|
// Internal tracking for value.
|
||||||
|
float mValue = UNSET;
|
||||||
|
|
||||||
|
// Tracks whether start value is set. If not, the animation will obtain the value at the time
|
||||||
|
// of starting through the getter and use that as the starting value of the animation.
|
||||||
|
boolean mStartValueIsSet = false;
|
||||||
|
|
||||||
|
// Target to be animated.
|
||||||
|
final Object mTarget;
|
||||||
|
|
||||||
|
// View property id.
|
||||||
|
final FloatProperty mProperty;
|
||||||
|
|
||||||
|
// Package private tracking of animation lifecycle state. Visible to subclass animations.
|
||||||
|
boolean mRunning = false;
|
||||||
|
|
||||||
|
// Min and max values that defines the range of the animation values.
|
||||||
|
float mMaxValue = Float.MAX_VALUE;
|
||||||
|
float mMinValue = -mMaxValue;
|
||||||
|
|
||||||
|
// Last frame time. Always gets reset to -1 at the end of the animation.
|
||||||
|
private long mLastFrameTime = 0;
|
||||||
|
|
||||||
|
private float mMinVisibleChange;
|
||||||
|
|
||||||
|
// List of end listeners
|
||||||
|
private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
// List of update listeners
|
||||||
|
private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
// Animation handler used to schedule updates for this animation.
|
||||||
|
private AnimationHandler mAnimationHandler;
|
||||||
|
|
||||||
|
// Internal state for value/velocity pair.
|
||||||
|
static class MassState {
|
||||||
|
float mValue;
|
||||||
|
float mVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dynamic animation with the given FloatValueHolder instance.
|
||||||
|
*
|
||||||
|
* @param floatValueHolder the FloatValueHolder instance to be animated.
|
||||||
|
*/
|
||||||
|
DynamicAnimation(final FloatValueHolder floatValueHolder) {
|
||||||
|
mTarget = null;
|
||||||
|
mProperty = new FloatProperty("FloatValueHolder") {
|
||||||
|
@Override
|
||||||
|
public Float get(Object object) {
|
||||||
|
return floatValueHolder.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(Object object, float value) {
|
||||||
|
floatValueHolder.setValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dynamic animation to animate the given property for the given {@link View}
|
||||||
|
*
|
||||||
|
* @param object the Object whose property is to be animated
|
||||||
|
* @param property the property to be animated
|
||||||
|
*/
|
||||||
|
|
||||||
|
<K> DynamicAnimation(K object, FloatProperty<K> property) {
|
||||||
|
mTarget = object;
|
||||||
|
mProperty = property;
|
||||||
|
if (mProperty == ROTATION || mProperty == ROTATION_X
|
||||||
|
|| mProperty == ROTATION_Y) {
|
||||||
|
mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
|
||||||
|
} else if (mProperty == ALPHA) {
|
||||||
|
mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
|
||||||
|
} else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
|
||||||
|
mMinVisibleChange = MIN_VISIBLE_CHANGE_SCALE;
|
||||||
|
} else {
|
||||||
|
mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the start value of the animation. If start value is not set, the animation will get
|
||||||
|
* the current value for the view's property, and use that as the start value.
|
||||||
|
*
|
||||||
|
* @param startValue start value for the animation
|
||||||
|
* @return the Animation whose start value is being set
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T setStartValue(float startValue) {
|
||||||
|
mValue = startValue;
|
||||||
|
mStartValueIsSet = true;
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start velocity of the animation. Default velocity is 0. Unit: change in property per
|
||||||
|
* second (e.g. pixels per second, scale/alpha value change per second).
|
||||||
|
*
|
||||||
|
* <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
|
||||||
|
* @return the Animation whose start velocity is being set
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T setStartVelocity(float startVelocity) {
|
||||||
|
mVelocity = startVelocity;
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the max value of the animation. Animations will not animate beyond their max value.
|
||||||
|
* Whether or not animation will come to an end when max value is reached is dependent on the
|
||||||
|
* child animation's implementation.
|
||||||
|
*
|
||||||
|
* @param max maximum value of the property to be animated
|
||||||
|
* @return the Animation whose max value is being set
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T setMaxValue(float max) {
|
||||||
|
// This max value should be checked and handled in the subclass animations, instead of
|
||||||
|
// assuming the end of the animations when the max/min value is hit in the base class.
|
||||||
|
// The reason is that hitting max/min value may just be a transient state, such as during
|
||||||
|
// the spring oscillation.
|
||||||
|
mMaxValue = max;
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the min value of the animation. Animations will not animate beyond their min value.
|
||||||
|
* Whether or not animation will come to an end when min value is reached is dependent on the
|
||||||
|
* child animation's implementation.
|
||||||
|
*
|
||||||
|
* @param min minimum value of the property to be animated
|
||||||
|
* @return the Animation whose min value is being set
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T setMinValue(float min) {
|
||||||
|
mMinValue = min;
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener
|
||||||
|
* is {@code null} or has already been added to the list of listeners for the animation, no op.
|
||||||
|
*
|
||||||
|
* @param listener the listener to be added
|
||||||
|
* @return the animation to which the listener is added
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T addEndListener(OnAnimationEndListener listener) {
|
||||||
|
if (!mEndListeners.contains(listener)) {
|
||||||
|
mEndListeners.add(listener);
|
||||||
|
}
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the end listener from the animation, so as to stop receiving animation end callbacks.
|
||||||
|
*
|
||||||
|
* @param listener the listener to be removed
|
||||||
|
*/
|
||||||
|
public void removeEndListener(OnAnimationEndListener listener) {
|
||||||
|
removeEntry(mEndListeners, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an update listener to the animation for receiving per-frame animation update callbacks.
|
||||||
|
* If the listener is {@code null} or has already been added to the list of listeners for the
|
||||||
|
* animation, no op.
|
||||||
|
*
|
||||||
|
* <p>Note that update listener should only be added before the start of the animation.
|
||||||
|
*
|
||||||
|
* @param listener the listener to be added
|
||||||
|
* @return the animation to which the listener is added
|
||||||
|
* @throws UnsupportedOperationException if the update listener is added after the animation has
|
||||||
|
* started
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T addUpdateListener(OnAnimationUpdateListener listener) {
|
||||||
|
if (isRunning()) {
|
||||||
|
// Require update listener to be added before the animation, such as when we start
|
||||||
|
// the animation, we know whether the animation is RenderThread compatible.
|
||||||
|
throw new UnsupportedOperationException("Error: Update listeners must be added before"
|
||||||
|
+ "the animation.");
|
||||||
|
}
|
||||||
|
if (!mUpdateListeners.contains(listener)) {
|
||||||
|
mUpdateListeners.add(listener);
|
||||||
|
}
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the update listener from the animation, so as to stop receiving animation update
|
||||||
|
* callbacks.
|
||||||
|
*
|
||||||
|
* @param listener the listener to be removed
|
||||||
|
*/
|
||||||
|
public void removeUpdateListener(OnAnimationUpdateListener listener) {
|
||||||
|
removeEntry(mUpdateListeners, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets the minimal change of animation value that is visible to users, which helps
|
||||||
|
* determine a reasonable threshold for the animation's termination condition. It is critical
|
||||||
|
* to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s)
|
||||||
|
* unless the custom property is in pixels.
|
||||||
|
*
|
||||||
|
* <p>For custom properties, this minimum visible change defaults to change in pixel
|
||||||
|
* (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is
|
||||||
|
* reasonable for the property to be animated. A general rule of thumb to calculate such a value
|
||||||
|
* is: minimum visible change = range of custom property value / corresponding pixel range. For
|
||||||
|
* example, if the property to be animated is a progress (from 0 to 100) that corresponds to a
|
||||||
|
* 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5).
|
||||||
|
*
|
||||||
|
* <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the
|
||||||
|
* minimum visible change will be derived from the property. For example, if the property to be
|
||||||
|
* animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y},
|
||||||
|
* {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible
|
||||||
|
* change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the
|
||||||
|
* animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change,
|
||||||
|
* which is 1/10. Similarly, the minimum visible change for alpha (
|
||||||
|
* i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256.
|
||||||
|
*
|
||||||
|
* @param minimumVisibleChange minimum change in property value that is visible to users
|
||||||
|
* @return the animation whose min visible change is being set
|
||||||
|
* @throws IllegalArgumentException if the given threshold is not positive
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false)
|
||||||
|
float minimumVisibleChange) {
|
||||||
|
if (minimumVisibleChange <= 0) {
|
||||||
|
throw new IllegalArgumentException("Minimum visible change must be positive.");
|
||||||
|
}
|
||||||
|
mMinVisibleChange = minimumVisibleChange;
|
||||||
|
setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum change in the animation property that could be visibly different to
|
||||||
|
* users.
|
||||||
|
*
|
||||||
|
* @return minimum change in property value that is visible to users
|
||||||
|
*/
|
||||||
|
public float getMinimumVisibleChange() {
|
||||||
|
return mMinVisibleChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove {@code null} entries from the list.
|
||||||
|
*/
|
||||||
|
private static <T> void removeNullEntries(ArrayList<T> list) {
|
||||||
|
// Clean up null entries
|
||||||
|
for (int i = list.size() - 1; i >= 0; i--) {
|
||||||
|
if (list.get(i) == null) {
|
||||||
|
list.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an entry from the list by marking it {@code null} and clean up later.
|
||||||
|
*/
|
||||||
|
private static <T> void removeEntry(ArrayList<T> list, T entry) {
|
||||||
|
int id = list.indexOf(entry);
|
||||||
|
if (id >= 0) {
|
||||||
|
list.set(id, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************Animation Lifecycle Management***************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an animation. If the animation has already been started, no op. Note that calling
|
||||||
|
* {@link #start()} will not immediately set the property value to start value of the animation.
|
||||||
|
* The property values will be changed at each animation pulse, which happens before the draw
|
||||||
|
* pass. As a result, the changes will be reflected in the next frame, the same as if the values
|
||||||
|
* were set immediately. This method should only be called on main thread.
|
||||||
|
*
|
||||||
|
* Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
|
||||||
|
* is created on the same thread as the first call to start/cancel an animation. All the
|
||||||
|
* subsequent animation lifecycle manipulations need to be on that same thread, until the
|
||||||
|
* AnimationHandler is reset (using [setAnimationHandler]).
|
||||||
|
*
|
||||||
|
* @throws AndroidRuntimeException if this method is not called on the same thread as the
|
||||||
|
* animation handler
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public void start() {
|
||||||
|
if (!isCurrentThread()) {
|
||||||
|
throw new AndroidRuntimeException("Animations may only be started on the same thread "
|
||||||
|
+ "as the animation handler");
|
||||||
|
}
|
||||||
|
if (!mRunning) {
|
||||||
|
startAnimationInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCurrentThread() {
|
||||||
|
return Thread.currentThread() == Looper.myLooper().getThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the on-going animation. If the animation hasn't started, no op.
|
||||||
|
*
|
||||||
|
* Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
|
||||||
|
* is created on the same thread as the first call to start/cancel an animation. All the
|
||||||
|
* subsequent animation lifecycle manipulations need to be on that same thread, until the
|
||||||
|
* AnimationHandler is reset (using [setAnimationHandler]).
|
||||||
|
*
|
||||||
|
* @throws AndroidRuntimeException if this method is not called on the same thread as the
|
||||||
|
* animation handler
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public void cancel() {
|
||||||
|
if (!isCurrentThread()) {
|
||||||
|
throw new AndroidRuntimeException("Animations may only be canceled from the same "
|
||||||
|
+ "thread as the animation handler");
|
||||||
|
}
|
||||||
|
if (mRunning) {
|
||||||
|
endAnimationInternal(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the animation is currently running.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the animation is currently running, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRunning() {
|
||||||
|
return mRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** Private APIs below ********************************/
|
||||||
|
|
||||||
|
// This gets called when the animation is started, to finish the setup of the animation
|
||||||
|
// before the animation pulsing starts.
|
||||||
|
private void startAnimationInternal() {
|
||||||
|
if (!mRunning) {
|
||||||
|
mRunning = true;
|
||||||
|
if (!mStartValueIsSet) {
|
||||||
|
mValue = getPropertyValue();
|
||||||
|
}
|
||||||
|
// Sanity check:
|
||||||
|
if (mValue > mMaxValue || mValue < mMinValue) {
|
||||||
|
throw new IllegalArgumentException("Starting value need to be in between min"
|
||||||
|
+ " value and max value");
|
||||||
|
}
|
||||||
|
getAnimationHandler().addAnimationFrameCallback(this, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This gets call on each frame of the animation. Animation value and velocity are updated
|
||||||
|
* in this method based on the new frame time. The property value of the view being animated
|
||||||
|
* is then updated. The animation's ending conditions are also checked in this method. Once
|
||||||
|
* the animation reaches equilibrium, the animation will come to its end, and end listeners
|
||||||
|
* will be notified, if any.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean doAnimationFrame(long frameTime) {
|
||||||
|
if (mLastFrameTime == 0) {
|
||||||
|
// First frame.
|
||||||
|
mLastFrameTime = frameTime;
|
||||||
|
setPropertyValue(mValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long deltaT = frameTime - mLastFrameTime;
|
||||||
|
mLastFrameTime = frameTime;
|
||||||
|
float durationScale = ValueAnimator.getDurationScale();
|
||||||
|
deltaT = durationScale == 0.0f ? Integer.MAX_VALUE : (long) (deltaT / durationScale);
|
||||||
|
boolean finished = updateValueAndVelocity(deltaT);
|
||||||
|
// Clamp value & velocity.
|
||||||
|
mValue = Math.min(mValue, mMaxValue);
|
||||||
|
mValue = Math.max(mValue, mMinValue);
|
||||||
|
|
||||||
|
setPropertyValue(mValue);
|
||||||
|
|
||||||
|
if (finished) {
|
||||||
|
endAnimationInternal(false);
|
||||||
|
}
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commitAnimationFrame(long frameTime) {
|
||||||
|
doAnimationFrame(frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the animation state (i.e. value and velocity). This method is package private, so
|
||||||
|
* subclasses can override this method to calculate the new value and velocity in their custom
|
||||||
|
* way.
|
||||||
|
*
|
||||||
|
* @param deltaT time elapsed in millisecond since last frame
|
||||||
|
* @return whether the animation has finished
|
||||||
|
*/
|
||||||
|
abstract boolean updateValueAndVelocity(long deltaT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method to reset the animation states when animation is finished/canceled.
|
||||||
|
*/
|
||||||
|
private void endAnimationInternal(boolean canceled) {
|
||||||
|
mRunning = false;
|
||||||
|
getAnimationHandler().removeCallback(this);
|
||||||
|
mLastFrameTime = 0;
|
||||||
|
mStartValueIsSet = false;
|
||||||
|
for (int i = 0; i < mEndListeners.size(); i++) {
|
||||||
|
if (mEndListeners.get(i) != null) {
|
||||||
|
mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeNullEntries(mEndListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the property value through the corresponding setter.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void setPropertyValue(float value) {
|
||||||
|
mProperty.setValue(mTarget, value);
|
||||||
|
for (int i = 0; i < mUpdateListeners.size(); i++) {
|
||||||
|
if (mUpdateListeners.get(i) != null) {
|
||||||
|
mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeNullEntries(mUpdateListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default threshold.
|
||||||
|
*/
|
||||||
|
float getValueThreshold() {
|
||||||
|
return mMinVisibleChange * THRESHOLD_MULTIPLIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the property value through the corresponding getter.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private float getPropertyValue() {
|
||||||
|
return (Float) mProperty.get(mTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AnimationHandler} used to schedule updates for this animator.
|
||||||
|
*
|
||||||
|
* @return the {@link AnimationHandler} for this animator.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public AnimationHandler getAnimationHandler() {
|
||||||
|
return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************Sub class animations**************/
|
||||||
|
/**
|
||||||
|
* Returns the acceleration at the given value with the given velocity.
|
||||||
|
**/
|
||||||
|
abstract float getAcceleration(float value, float velocity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the animation has reached equilibrium.
|
||||||
|
*/
|
||||||
|
abstract boolean isAtEquilibrium(float value, float velocity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the default value threshold for the animation based on the property to be animated.
|
||||||
|
*/
|
||||||
|
abstract void setValueThreshold(float threshold);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An animation listener that receives end notifications from an animation.
|
||||||
|
*/
|
||||||
|
public interface OnAnimationEndListener {
|
||||||
|
/**
|
||||||
|
* Notifies the end of an animation. Note that this callback will be invoked not only when
|
||||||
|
* an animation reach equilibrium, but also when the animation is canceled.
|
||||||
|
*
|
||||||
|
* @param animation animation that has ended or was canceled
|
||||||
|
* @param canceled whether the animation has been canceled
|
||||||
|
* @param value the final value when the animation stopped
|
||||||
|
* @param velocity the final velocity when the animation stopped
|
||||||
|
*/
|
||||||
|
void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
|
||||||
|
float velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementors of this interface can add themselves as update listeners
|
||||||
|
* to an <code>DynamicAnimation</code> instance to receive callbacks on every animation
|
||||||
|
* frame, after the current frame's values have been calculated for that
|
||||||
|
* <code>DynamicAnimation</code>.
|
||||||
|
*/
|
||||||
|
public interface OnAnimationUpdateListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the occurrence of another frame of the animation.
|
||||||
|
*
|
||||||
|
* @param animation animation that the update listener is added to
|
||||||
|
* @param value the current value of the animation
|
||||||
|
* @param velocity the current velocity of the animation
|
||||||
|
*/
|
||||||
|
void onAnimationUpdate(DynamicAnimation animation, float value, float velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.internal.dynamicanimation.animation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>FloatValueHolder holds a float value. FloatValueHolder provides a setter and a getter (
|
||||||
|
* i.e. {@link #setValue(float)} and {@link #getValue()}) to access this float value. Animations can
|
||||||
|
* be performed on a FloatValueHolder instance. During each frame of the animation, the
|
||||||
|
* FloatValueHolder will have its value updated via {@link #setValue(float)}. The caller can
|
||||||
|
* obtain the up-to-date animation value via {@link FloatValueHolder#getValue()}.
|
||||||
|
*
|
||||||
|
* @see SpringAnimation#SpringAnimation(FloatValueHolder)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class FloatValueHolder {
|
||||||
|
private float mValue = 0.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a holder for a float value that is initialized to 0.
|
||||||
|
*/
|
||||||
|
public FloatValueHolder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a holder for a float value that is initialized to the input value.
|
||||||
|
*
|
||||||
|
* @param value the value to initialize the value held in the FloatValueHolder
|
||||||
|
*/
|
||||||
|
public FloatValueHolder(float value) {
|
||||||
|
setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value held in the FloatValueHolder instance.
|
||||||
|
*
|
||||||
|
* @param value float value held in the FloatValueHolder instance
|
||||||
|
*/
|
||||||
|
public void setValue(float value) {
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the float value held in the FloatValueHolder instance.
|
||||||
|
*
|
||||||
|
* @return float value held in the FloatValueHolder instance
|
||||||
|
*/
|
||||||
|
public float getValue() {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.internal.dynamicanimation.animation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide this for now, in case we want to change the API.
|
||||||
|
*/
|
||||||
|
interface Force {
|
||||||
|
// Acceleration based on position.
|
||||||
|
float getAcceleration(float position, float velocity);
|
||||||
|
|
||||||
|
boolean isAtEquilibrium(float value, float velocity);
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.internal.dynamicanimation.animation;
|
||||||
|
|
||||||
|
import android.util.AndroidRuntimeException;
|
||||||
|
import android.util.FloatProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines
|
||||||
|
* the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is
|
||||||
|
* started, on each frame the spring force will update the animation's value and velocity.
|
||||||
|
* The animation will continue to run until the spring force reaches equilibrium. If the spring used
|
||||||
|
* in the animation is undamped, the animation will never reach equilibrium. Instead, it will
|
||||||
|
* oscillate forever.
|
||||||
|
*
|
||||||
|
* <div class="special reference">
|
||||||
|
* <h3>Developer Guides</h3>
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p>
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* // Create an animation to animate view's X property, set the rest position of the
|
||||||
|
* // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
|
||||||
|
* final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
|
||||||
|
* .setStartVelocity(5000);
|
||||||
|
* anim.start();
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and
|
||||||
|
* use that to drive the animation. </p>
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* // Create a low stiffness, low bounce spring at position 0.
|
||||||
|
* SpringForce spring = new SpringForce(0)
|
||||||
|
* .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||||
|
* .setStiffness(SpringForce.STIFFNESS_LOW);
|
||||||
|
* // Create an animation to animate view's scaleY property, and start the animation using
|
||||||
|
* // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
|
||||||
|
* // the animation to be non-negative, effectively preventing any spring overshoot.
|
||||||
|
* final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
|
||||||
|
* .setMinValue(0).setSpring(spring).setStartValue(1);
|
||||||
|
* anim.start();
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
|
||||||
|
|
||||||
|
private SpringForce mSpring = null;
|
||||||
|
private float mPendingPosition = UNSET;
|
||||||
|
private static final float UNSET = Float.MAX_VALUE;
|
||||||
|
private boolean mEndRequested = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This creates a SpringAnimation 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 SpringAnimation(FloatValueHolder floatValueHolder) {
|
||||||
|
super(floatValueHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This creates a SpringAnimation 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()}.
|
||||||
|
*
|
||||||
|
* A Spring will be created with the given final position and default stiffness and damping
|
||||||
|
* ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
|
||||||
|
*
|
||||||
|
* <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
|
||||||
|
* @param finalPosition the final position of the spring to be created.
|
||||||
|
*/
|
||||||
|
public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) {
|
||||||
|
super(floatValueHolder);
|
||||||
|
mSpring = new SpringForce(finalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This creates a SpringAnimation that animates the property of the given object.
|
||||||
|
* Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
|
||||||
|
* the animation starts.
|
||||||
|
*
|
||||||
|
* @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> SpringAnimation(K object, FloatProperty<K> property) {
|
||||||
|
super(object, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This creates a SpringAnimation that animates the property of the given object. A Spring will
|
||||||
|
* be created with the given final position and default stiffness and damping ratio.
|
||||||
|
* This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
|
||||||
|
*
|
||||||
|
* @param object the Object whose property will be animated
|
||||||
|
* @param property the property to be animated
|
||||||
|
* @param finalPosition the final position of the spring to be created.
|
||||||
|
* @param <K> the class on which the Property is declared
|
||||||
|
*/
|
||||||
|
public <K> SpringAnimation(K object, FloatProperty<K> property,
|
||||||
|
float finalPosition) {
|
||||||
|
super(object, property);
|
||||||
|
mSpring = new SpringForce(finalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the spring that the animation uses for animations.
|
||||||
|
*
|
||||||
|
* @return the spring that the animation uses for animations
|
||||||
|
*/
|
||||||
|
public SpringForce getSpring() {
|
||||||
|
return mSpring;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the given spring as the force that drives this animation. If this spring force has its
|
||||||
|
* parameters re-configured during the animation, the new configuration will be reflected in the
|
||||||
|
* animation immediately.
|
||||||
|
*
|
||||||
|
* @param force a pre-defined spring force that drives the animation
|
||||||
|
* @return the animation that the spring force is set on
|
||||||
|
*/
|
||||||
|
public SpringAnimation setSpring(SpringForce force) {
|
||||||
|
mSpring = force;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
sanityCheck();
|
||||||
|
mSpring.setValueThreshold(getValueThreshold());
|
||||||
|
super.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the final position of the spring.
|
||||||
|
* <p/>
|
||||||
|
* When the animation is running, calling this method would assume the position change of the
|
||||||
|
* spring as a continuous movement since last frame, which yields more accurate results than
|
||||||
|
* changing the spring position directly through {@link SpringForce#setFinalPosition(float)}.
|
||||||
|
* <p/>
|
||||||
|
* If the animation hasn't started, calling this method will change the spring position, and
|
||||||
|
* immediately start the animation.
|
||||||
|
*
|
||||||
|
* @param finalPosition rest position of the spring
|
||||||
|
*/
|
||||||
|
public void animateToFinalPosition(float finalPosition) {
|
||||||
|
if (isRunning()) {
|
||||||
|
mPendingPosition = finalPosition;
|
||||||
|
} else {
|
||||||
|
if (mSpring == null) {
|
||||||
|
mSpring = new SpringForce(finalPosition);
|
||||||
|
}
|
||||||
|
mSpring.setFinalPosition(finalPosition);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the on-going animation. If the animation hasn't started, no op. Note that this method
|
||||||
|
* should only be called on main thread.
|
||||||
|
*
|
||||||
|
* @throws AndroidRuntimeException if this method is not called on the main thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
super.cancel();
|
||||||
|
if (mPendingPosition != UNSET) {
|
||||||
|
if (mSpring == null) {
|
||||||
|
mSpring = new SpringForce(mPendingPosition);
|
||||||
|
} else {
|
||||||
|
mSpring.setFinalPosition(mPendingPosition);
|
||||||
|
}
|
||||||
|
mPendingPosition = UNSET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips to the end of the animation. If the spring is undamped, an
|
||||||
|
* {@link IllegalStateException} will be thrown, as the animation would never reach to an end.
|
||||||
|
* It is recommended to check {@link #canSkipToEnd()} before calling this method. If animation
|
||||||
|
* is not running, no-op.
|
||||||
|
*
|
||||||
|
* Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
|
||||||
|
* is created on the same thread as the first call to start/cancel an animation. All the
|
||||||
|
* subsequent animation lifecycle manipulations need to be on that same thread, until the
|
||||||
|
* AnimationHandler is reset (using [setAnimationHandler]).
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0)
|
||||||
|
* @throws AndroidRuntimeException if this method is not called on the same thread as the
|
||||||
|
* animation handler
|
||||||
|
*/
|
||||||
|
public void skipToEnd() {
|
||||||
|
if (!canSkipToEnd()) {
|
||||||
|
throw new UnsupportedOperationException("Spring animations can only come to an end"
|
||||||
|
+ " when there is damping");
|
||||||
|
}
|
||||||
|
if (!isCurrentThread()) {
|
||||||
|
throw new AndroidRuntimeException("Animations may only be started on the same thread "
|
||||||
|
+ "as the animation handler");
|
||||||
|
}
|
||||||
|
if (mRunning) {
|
||||||
|
mEndRequested = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries whether the spring can eventually come to the rest position.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the spring is damped, otherwise {@code false}
|
||||||
|
*/
|
||||||
|
public boolean canSkipToEnd() {
|
||||||
|
return mSpring.mDampingRatio > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************ Below are private APIs *************************/
|
||||||
|
|
||||||
|
private void sanityCheck() {
|
||||||
|
if (mSpring == null) {
|
||||||
|
throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
|
||||||
|
+ " position or a spring force needs to be set.");
|
||||||
|
}
|
||||||
|
double finalPosition = mSpring.getFinalPosition();
|
||||||
|
if (finalPosition > mMaxValue) {
|
||||||
|
throw new UnsupportedOperationException("Final position of the spring cannot be greater"
|
||||||
|
+ " than the max value.");
|
||||||
|
} else if (finalPosition < mMinValue) {
|
||||||
|
throw new UnsupportedOperationException("Final position of the spring cannot be less"
|
||||||
|
+ " than the min value.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean updateValueAndVelocity(long deltaT) {
|
||||||
|
// If user had requested end, then update the value and velocity to end state and consider
|
||||||
|
// animation done.
|
||||||
|
if (mEndRequested) {
|
||||||
|
if (mPendingPosition != UNSET) {
|
||||||
|
mSpring.setFinalPosition(mPendingPosition);
|
||||||
|
mPendingPosition = UNSET;
|
||||||
|
}
|
||||||
|
mValue = mSpring.getFinalPosition();
|
||||||
|
mVelocity = 0;
|
||||||
|
mEndRequested = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPendingPosition != UNSET) {
|
||||||
|
// Approximate by considering half of the time spring position stayed at the old
|
||||||
|
// position, half of the time it's at the new position.
|
||||||
|
MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
|
||||||
|
mSpring.setFinalPosition(mPendingPosition);
|
||||||
|
mPendingPosition = UNSET;
|
||||||
|
|
||||||
|
massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
|
||||||
|
mValue = massState.mValue;
|
||||||
|
mVelocity = massState.mVelocity;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
|
||||||
|
mValue = massState.mValue;
|
||||||
|
mVelocity = massState.mVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
mValue = Math.max(mValue, mMinValue);
|
||||||
|
mValue = Math.min(mValue, mMaxValue);
|
||||||
|
|
||||||
|
if (isAtEquilibrium(mValue, mVelocity)) {
|
||||||
|
mValue = mSpring.getFinalPosition();
|
||||||
|
mVelocity = 0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
float getAcceleration(float value, float velocity) {
|
||||||
|
return mSpring.getAcceleration(value, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isAtEquilibrium(float value, float velocity) {
|
||||||
|
return mSpring.isAtEquilibrium(value, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setValueThreshold(float threshold) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.internal.dynamicanimation.animation;
|
||||||
|
|
||||||
|
import android.annotation.FloatRange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Force defines the characteristics of the spring being used in the animation.
|
||||||
|
* <p>
|
||||||
|
* By configuring the stiffness and damping ratio, callers can create a spring with the look and
|
||||||
|
* feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring
|
||||||
|
* is, the harder it is to stretch it, the faster it undergoes dampening.
|
||||||
|
* <p>
|
||||||
|
* Spring damping ratio describes how oscillations in a system decay after a disturbance.
|
||||||
|
* When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position
|
||||||
|
* without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
|
||||||
|
* return to equilibrium within the shortest amount of time. When damping ratio is less than 1
|
||||||
|
* (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any
|
||||||
|
* damping (i.e. damping ratio = 0), the mass will oscillate forever.
|
||||||
|
*/
|
||||||
|
public final class SpringForce implements Force {
|
||||||
|
/**
|
||||||
|
* Stiffness constant for extremely stiff spring.
|
||||||
|
*/
|
||||||
|
public static final float STIFFNESS_HIGH = 10_000f;
|
||||||
|
/**
|
||||||
|
* Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
|
||||||
|
*/
|
||||||
|
public static final float STIFFNESS_MEDIUM = 1500f;
|
||||||
|
/**
|
||||||
|
* Stiffness constant for a spring with low stiffness.
|
||||||
|
*/
|
||||||
|
public static final float STIFFNESS_LOW = 200f;
|
||||||
|
/**
|
||||||
|
* Stiffness constant for a spring with very low stiffness.
|
||||||
|
*/
|
||||||
|
public static final float STIFFNESS_VERY_LOW = 50f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Damping ratio for a very bouncy spring. Note for under-damped springs
|
||||||
|
* (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
|
||||||
|
*/
|
||||||
|
public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
|
||||||
|
/**
|
||||||
|
* Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
|
||||||
|
* force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
|
||||||
|
* the more bouncy the spring.
|
||||||
|
*/
|
||||||
|
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
|
||||||
|
/**
|
||||||
|
* Damping ratio for a spring with low bounciness. Note for under-damped springs
|
||||||
|
* (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
|
||||||
|
*/
|
||||||
|
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
|
||||||
|
/**
|
||||||
|
* Damping ratio for a spring with no bounciness. This damping ratio will create a critically
|
||||||
|
* damped spring that returns to equilibrium within the shortest amount of time without
|
||||||
|
* oscillating.
|
||||||
|
*/
|
||||||
|
public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
|
||||||
|
|
||||||
|
// 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 double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0;
|
||||||
|
|
||||||
|
// Natural frequency
|
||||||
|
double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM);
|
||||||
|
// Damping ratio.
|
||||||
|
double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY;
|
||||||
|
|
||||||
|
// Value to indicate an unset state.
|
||||||
|
private static final double UNSET = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
// Indicates whether the spring has been initialized
|
||||||
|
private boolean mInitialized = false;
|
||||||
|
|
||||||
|
// Threshold for velocity and value to determine when it's reasonable to assume that the spring
|
||||||
|
// is approximately at rest.
|
||||||
|
private double mValueThreshold;
|
||||||
|
private double mVelocityThreshold;
|
||||||
|
|
||||||
|
// Intermediate values to simplify the spring function calculation per frame.
|
||||||
|
private double mGammaPlus;
|
||||||
|
private double mGammaMinus;
|
||||||
|
private double mDampedFreq;
|
||||||
|
|
||||||
|
// Final position of the spring. This must be set before the start of the animation.
|
||||||
|
private double mFinalPosition = UNSET;
|
||||||
|
|
||||||
|
// Internal state to hold a value/velocity pair.
|
||||||
|
private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a spring force. Note that final position of the spring must be set through
|
||||||
|
* {@link #setFinalPosition(float)} before the spring animation starts.
|
||||||
|
*/
|
||||||
|
public SpringForce() {
|
||||||
|
// No op.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a spring with a given final rest position.
|
||||||
|
*
|
||||||
|
* @param finalPosition final position of the spring when it reaches equilibrium
|
||||||
|
*/
|
||||||
|
public SpringForce(float finalPosition) {
|
||||||
|
mFinalPosition = finalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to
|
||||||
|
* the object attached when the spring is not at the final position. Default stiffness is
|
||||||
|
* {@link #STIFFNESS_MEDIUM}.
|
||||||
|
*
|
||||||
|
* @param stiffness non-negative stiffness constant of a spring
|
||||||
|
* @return the spring force that the given stiffness is set on
|
||||||
|
* @throws IllegalArgumentException if the given spring stiffness is not positive
|
||||||
|
*/
|
||||||
|
public SpringForce setStiffness(
|
||||||
|
@FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
|
||||||
|
if (stiffness <= 0) {
|
||||||
|
throw new IllegalArgumentException("Spring stiffness constant must be positive.");
|
||||||
|
}
|
||||||
|
mNaturalFreq = Math.sqrt(stiffness);
|
||||||
|
// All the intermediate values need to be recalculated.
|
||||||
|
mInitialized = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stiffness of the spring.
|
||||||
|
*
|
||||||
|
* @return the stiffness of the spring
|
||||||
|
*/
|
||||||
|
public float getStiffness() {
|
||||||
|
return (float) (mNaturalFreq * mNaturalFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring damping ratio describes how oscillations in a system decay after a disturbance.
|
||||||
|
* <p>
|
||||||
|
* When damping ratio > 1 (over-damped), the object will quickly return to the rest position
|
||||||
|
* without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
|
||||||
|
* return to equilibrium within the shortest amount of time. When damping ratio is less than 1
|
||||||
|
* (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without
|
||||||
|
* any damping (i.e. damping ratio = 0), the mass will oscillate forever.
|
||||||
|
* <p>
|
||||||
|
* Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}.
|
||||||
|
*
|
||||||
|
* @param dampingRatio damping ratio of the spring, it should be non-negative
|
||||||
|
* @return the spring force that the given damping ratio is set on
|
||||||
|
* @throws IllegalArgumentException if the {@param dampingRatio} is negative.
|
||||||
|
*/
|
||||||
|
public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) {
|
||||||
|
if (dampingRatio < 0) {
|
||||||
|
throw new IllegalArgumentException("Damping ratio must be non-negative");
|
||||||
|
}
|
||||||
|
mDampingRatio = dampingRatio;
|
||||||
|
// All the intermediate values need to be recalculated.
|
||||||
|
mInitialized = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the damping ratio of the spring.
|
||||||
|
*
|
||||||
|
* @return damping ratio of the spring
|
||||||
|
*/
|
||||||
|
public float getDampingRatio() {
|
||||||
|
return (float) mDampingRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the rest position of the spring.
|
||||||
|
*
|
||||||
|
* @param finalPosition rest position of the spring
|
||||||
|
* @return the spring force that the given final position is set on
|
||||||
|
*/
|
||||||
|
public SpringForce setFinalPosition(float finalPosition) {
|
||||||
|
mFinalPosition = finalPosition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the rest position of the spring.
|
||||||
|
*
|
||||||
|
* @return rest position of the spring
|
||||||
|
*/
|
||||||
|
public float getFinalPosition() {
|
||||||
|
return (float) mFinalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************** Below are private APIs *********************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getAcceleration(float lastDisplacement, float lastVelocity) {
|
||||||
|
|
||||||
|
lastDisplacement -= getFinalPosition();
|
||||||
|
|
||||||
|
double k = mNaturalFreq * mNaturalFreq;
|
||||||
|
double c = 2 * mNaturalFreq * mDampingRatio;
|
||||||
|
|
||||||
|
return (float) (-k * lastDisplacement - c * lastVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAtEquilibrium(float value, float velocity) {
|
||||||
|
if (Math.abs(velocity) < mVelocityThreshold
|
||||||
|
&& Math.abs(value - getFinalPosition()) < mValueThreshold) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the string by doing the necessary pre-calculation as well as some sanity check
|
||||||
|
* on the setup.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the final position is not yet set by the time the spring
|
||||||
|
* animation has started
|
||||||
|
*/
|
||||||
|
private void init() {
|
||||||
|
if (mInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mFinalPosition == UNSET) {
|
||||||
|
throw new IllegalStateException("Error: Final position of the spring must be"
|
||||||
|
+ " set before the animation starts");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDampingRatio > 1) {
|
||||||
|
// Over damping
|
||||||
|
mGammaPlus = -mDampingRatio * mNaturalFreq
|
||||||
|
+ mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
|
||||||
|
mGammaMinus = -mDampingRatio * mNaturalFreq
|
||||||
|
- mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
|
||||||
|
} else if (mDampingRatio >= 0 && mDampingRatio < 1) {
|
||||||
|
// Under damping
|
||||||
|
mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
mInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal only call for Spring to calculate the spring position/velocity using
|
||||||
|
* an analytical approach.
|
||||||
|
*/
|
||||||
|
DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
|
||||||
|
long timeElapsed) {
|
||||||
|
init();
|
||||||
|
|
||||||
|
double deltaT = timeElapsed / 1000d; // unit: seconds
|
||||||
|
lastDisplacement -= mFinalPosition;
|
||||||
|
double displacement;
|
||||||
|
double currentVelocity;
|
||||||
|
if (mDampingRatio > 1) {
|
||||||
|
// Overdamped
|
||||||
|
double coeffA = lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
|
||||||
|
/ (mGammaMinus - mGammaPlus);
|
||||||
|
double coeffB = (mGammaMinus * lastDisplacement - lastVelocity)
|
||||||
|
/ (mGammaMinus - mGammaPlus);
|
||||||
|
displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
|
||||||
|
+ coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
|
||||||
|
currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
|
||||||
|
+ coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
|
||||||
|
} else if (mDampingRatio == 1) {
|
||||||
|
// Critically damped
|
||||||
|
double coeffA = lastDisplacement;
|
||||||
|
double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
|
||||||
|
displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
|
||||||
|
currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
|
||||||
|
* -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
|
||||||
|
} else {
|
||||||
|
// Underdamped
|
||||||
|
double cosCoeff = lastDisplacement;
|
||||||
|
double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
|
||||||
|
* lastDisplacement + lastVelocity);
|
||||||
|
displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
|
||||||
|
* (cosCoeff * Math.cos(mDampedFreq * deltaT)
|
||||||
|
+ sinCoeff * Math.sin(mDampedFreq * deltaT));
|
||||||
|
currentVelocity = displacement * -mNaturalFreq * mDampingRatio
|
||||||
|
+ Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
|
||||||
|
* (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
|
||||||
|
+ mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
|
||||||
|
}
|
||||||
|
|
||||||
|
mMassState.mValue = (float) (displacement + mFinalPosition);
|
||||||
|
mMassState.mVelocity = (float) currentVelocity;
|
||||||
|
return mMassState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This threshold defines how close the animation value needs to be before the animation can
|
||||||
|
* finish. This default value is based on the property being animated, e.g. animations on alpha,
|
||||||
|
* scale, translation or rotation would have different thresholds. This value should be small
|
||||||
|
* enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that
|
||||||
|
* animations take seconds to finish.
|
||||||
|
*
|
||||||
|
* @param threshold the difference between the animation value and final spring position that
|
||||||
|
* is allowed to end the animation when velocity is very low
|
||||||
|
*/
|
||||||
|
void setValueThreshold(double threshold) {
|
||||||
|
mValueThreshold = Math.abs(threshold);
|
||||||
|
mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user