mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-02 06:45:02 +08:00
Support for spring animations
Summary: This change adds support for spring animations to be run off the JS thread on android. The implementation is based on the android spring implementation from Rebound (http://facebook.github.io/rebound/) but since only a small subset of the library is used the relevant parts are copied instead of making RN to import the whole library. **Test Plan** Run java tests: `buck test ReactAndroid/src/test/java/com/facebook/react/animated` Add `useNativeDriver: true` to spring animation in animated example app, run it on android Closes https://github.com/facebook/react-native/pull/8860 Differential Revision: D3676436 fbshipit-source-id: 3a4b1b006725a938562712989b93dd4090577c48
This commit is contained in:
committed by
Facebook Github Bot 2
parent
0222107170
commit
8f75d7346f
@@ -0,0 +1,218 @@
|
||||
package com.facebook.react.animated;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AnimationDriver} providing support for spring animations. The
|
||||
* implementation has been copied from android implementation of Rebound library (see
|
||||
* <a href="http://facebook.github.io/rebound/">http://facebook.github.io/rebound/</a>)
|
||||
*/
|
||||
/*package*/ class SpringAnimation extends AnimationDriver {
|
||||
|
||||
// maximum amount of time to simulate per physics iteration in seconds (4 frames at 60 FPS)
|
||||
private static final double MAX_DELTA_TIME_SEC = 0.064;
|
||||
// fixed timestep to use in the physics solver in seconds
|
||||
private static final double SOLVER_TIMESTEP_SEC = 0.001;
|
||||
|
||||
// storage for the current and prior physics state while integration is occurring
|
||||
private static class PhysicsState {
|
||||
double position;
|
||||
double velocity;
|
||||
}
|
||||
|
||||
private long mLastTime;
|
||||
private boolean mSpringStarted;
|
||||
|
||||
// configuration
|
||||
private double mSpringFriction;
|
||||
private double mSpringTension;
|
||||
private boolean mOvershootClampingEnabled;
|
||||
|
||||
// all physics simulation objects are final and reused in each processing pass
|
||||
private final PhysicsState mCurrentState = new PhysicsState();
|
||||
private final PhysicsState mPreviousState = new PhysicsState();
|
||||
private final PhysicsState mTempState = new PhysicsState();
|
||||
private double mStartValue;
|
||||
private double mEndValue;
|
||||
// thresholds for determining when the spring is at rest
|
||||
private double mRestSpeedThreshold;
|
||||
private double mDisplacementFromRestThreshold;
|
||||
private double mTimeAccumulator = 0;
|
||||
|
||||
SpringAnimation(ReadableMap config) {
|
||||
mSpringFriction = config.getDouble("friction");
|
||||
mSpringTension = config.getDouble("tension");
|
||||
mCurrentState.velocity = config.getDouble("initialVelocity");
|
||||
mEndValue = config.getDouble("toValue");
|
||||
mRestSpeedThreshold = config.getDouble("restSpeedThreshold");
|
||||
mDisplacementFromRestThreshold = config.getDouble("restDisplacementThreshold");
|
||||
mOvershootClampingEnabled = config.getBoolean("overshootClamping");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAnimationStep(long frameTimeNanos) {
|
||||
long frameTimeMillis = frameTimeNanos / 1000000;
|
||||
if (!mSpringStarted) {
|
||||
mStartValue = mCurrentState.position = mAnimatedValue.mValue;
|
||||
mLastTime = frameTimeMillis;
|
||||
mSpringStarted = true;
|
||||
}
|
||||
advance((frameTimeMillis - mLastTime) / 1000.0);
|
||||
mLastTime = frameTimeMillis;
|
||||
mAnimatedValue.mValue = mCurrentState.position;
|
||||
mHasFinished = isAtRest();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the displacement from rest for a given physics state
|
||||
* @param state the state to measure from
|
||||
* @return the distance displaced by
|
||||
*/
|
||||
private double getDisplacementDistanceForState(PhysicsState state) {
|
||||
return Math.abs(mEndValue - state.position);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current state is at rest
|
||||
* @return is the spring at rest
|
||||
*/
|
||||
private boolean isAtRest() {
|
||||
return Math.abs(mCurrentState.velocity) <= mRestSpeedThreshold &&
|
||||
(getDisplacementDistanceForState(mCurrentState) <= mDisplacementFromRestThreshold ||
|
||||
mSpringTension == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the spring is overshooting beyond its target.
|
||||
* @return true if the spring is overshooting its target
|
||||
*/
|
||||
private boolean isOvershooting() {
|
||||
return mSpringTension > 0 &&
|
||||
((mStartValue < mEndValue && mCurrentState.position > mEndValue) ||
|
||||
(mStartValue > mEndValue && mCurrentState.position < mEndValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* linear interpolation between the previous and current physics state based on the amount of
|
||||
* timestep remaining after processing the rendering delta time in timestep sized chunks.
|
||||
* @param alpha from 0 to 1, where 0 is the previous state, 1 is the current state
|
||||
*/
|
||||
private void interpolate(double alpha) {
|
||||
mCurrentState.position = mCurrentState.position * alpha + mPreviousState.position *(1-alpha);
|
||||
mCurrentState.velocity = mCurrentState.velocity * alpha + mPreviousState.velocity *(1-alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* advance the physics simulation in SOLVER_TIMESTEP_SEC sized chunks to fulfill the required
|
||||
* realTimeDelta.
|
||||
* The math is inlined inside the loop since it made a huge performance impact when there are
|
||||
* several springs being advanced.
|
||||
* @param time clock time
|
||||
* @param realDeltaTime clock drift
|
||||
*/
|
||||
private void advance(double realDeltaTime) {
|
||||
|
||||
if (isAtRest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able
|
||||
// to catch up in a subsequent advance if necessary.
|
||||
double adjustedDeltaTime = realDeltaTime;
|
||||
if (realDeltaTime > MAX_DELTA_TIME_SEC) {
|
||||
adjustedDeltaTime = MAX_DELTA_TIME_SEC;
|
||||
}
|
||||
|
||||
mTimeAccumulator += adjustedDeltaTime;
|
||||
|
||||
double tension = mSpringTension;
|
||||
double friction = mSpringFriction;
|
||||
|
||||
double position = mCurrentState.position;
|
||||
double velocity = mCurrentState.velocity;
|
||||
double tempPosition = mTempState.position;
|
||||
double tempVelocity = mTempState.velocity;
|
||||
|
||||
double aVelocity, aAcceleration;
|
||||
double bVelocity, bAcceleration;
|
||||
double cVelocity, cAcceleration;
|
||||
double dVelocity, dAcceleration;
|
||||
|
||||
double dxdt, dvdt;
|
||||
|
||||
// iterate over the true time
|
||||
while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) {
|
||||
/* begin debug
|
||||
iterations++;
|
||||
end debug */
|
||||
mTimeAccumulator -= SOLVER_TIMESTEP_SEC;
|
||||
|
||||
if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) {
|
||||
// This will be the last iteration. Remember the previous state in case we need to
|
||||
// interpolate
|
||||
mPreviousState.position = position;
|
||||
mPreviousState.velocity = velocity;
|
||||
}
|
||||
|
||||
// Perform an RK4 integration to provide better detection of the acceleration curve via
|
||||
// sampling of Euler integrations at 4 intervals feeding each derivative into the calculation
|
||||
// of the next and taking a weighted sum of the 4 derivatives as the final output.
|
||||
|
||||
// This math was inlined since it made for big performance improvements when advancing several
|
||||
// springs in one pass of the BaseSpringSystem.
|
||||
|
||||
// The initial derivative is based on the current velocity and the calculated acceleration
|
||||
aVelocity = velocity;
|
||||
aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity;
|
||||
|
||||
// Calculate the next derivatives starting with the last derivative and integrating over the
|
||||
// timestep
|
||||
tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5;
|
||||
tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
|
||||
bVelocity = tempVelocity;
|
||||
bAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
|
||||
|
||||
tempPosition = position + bVelocity * SOLVER_TIMESTEP_SEC * 0.5;
|
||||
tempVelocity = velocity + bAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
|
||||
cVelocity = tempVelocity;
|
||||
cAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
|
||||
|
||||
tempPosition = position + cVelocity * SOLVER_TIMESTEP_SEC;
|
||||
tempVelocity = velocity + cAcceleration * SOLVER_TIMESTEP_SEC;
|
||||
dVelocity = tempVelocity;
|
||||
dAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
|
||||
|
||||
// Take the weighted sum of the 4 derivatives as the final output.
|
||||
dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity);
|
||||
dvdt = 1.0/6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration);
|
||||
|
||||
position += dxdt * SOLVER_TIMESTEP_SEC;
|
||||
velocity += dvdt * SOLVER_TIMESTEP_SEC;
|
||||
}
|
||||
|
||||
mTempState.position = tempPosition;
|
||||
mTempState.velocity = tempVelocity;
|
||||
|
||||
mCurrentState.position = position;
|
||||
mCurrentState.velocity = velocity;
|
||||
|
||||
if (mTimeAccumulator > 0) {
|
||||
interpolate(mTimeAccumulator / SOLVER_TIMESTEP_SEC);
|
||||
}
|
||||
|
||||
// End the spring immediately if it is overshooting and overshoot clamping is enabled.
|
||||
// Also make sure that if the spring was considered within a resting threshold that it's now
|
||||
// snapped to its end value.
|
||||
if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) {
|
||||
// Don't call setCurrentValue because that forces a call to onSpringUpdate
|
||||
if (tension > 0) {
|
||||
mStartValue = mEndValue;
|
||||
mCurrentState.position = mEndValue;
|
||||
} else {
|
||||
mEndValue = mCurrentState.position;
|
||||
mStartValue = mEndValue;
|
||||
}
|
||||
mCurrentState.velocity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user