Migrate DecayAnimation to Kotlin (#45719)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45719

# Changelog:
[Internal] -

As in the title.

Reviewed By: tdn120

Differential Revision: D60284323

fbshipit-source-id: 6cf47a9f9528067b397b0c52b763b416bf62ed9f
This commit is contained in:
Ruslan Shestopalyuk
2024-07-28 06:01:14 -07:00
committed by Facebook GitHub Bot
parent e86a5ee274
commit 9e7760210f
3 changed files with 75 additions and 83 deletions
@@ -1,79 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.animated;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReadableMap;
/**
* Implementation of {@link AnimationDriver} providing support for decay animations. The
* implementation is copied from the JS version in {@code AnimatedImplementation.js}.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
class DecayAnimation extends AnimationDriver {
private final double mVelocity;
private double mDeceleration;
private long mStartFrameTimeMillis;
private double mFromValue;
private double mLastValue;
private int mIterations;
private int mCurrentLoop;
public DecayAnimation(ReadableMap config) {
mVelocity = config.getDouble("velocity"); // initial velocity
resetConfig(config);
}
@Override
public void resetConfig(ReadableMap config) {
mDeceleration = config.getDouble("deceleration");
mIterations = config.hasKey("iterations") ? config.getInt("iterations") : 1;
mCurrentLoop = 1;
mHasFinished = mIterations == 0;
mStartFrameTimeMillis = -1;
mFromValue = 0;
mLastValue = 0;
}
@Override
public void runAnimationStep(long frameTimeNanos) {
long frameTimeMillis = frameTimeNanos / 1000000;
if (mStartFrameTimeMillis == -1) {
// since this is the first animation step, consider the start to be on the previous frame
mStartFrameTimeMillis = frameTimeMillis - 16;
if (mFromValue == mLastValue) { // first iteration, assign mFromValue based on mAnimatedValue
mFromValue = mAnimatedValue.nodeValue;
} else { // not the first iteration, reset mAnimatedValue based on mFromValue
mAnimatedValue.nodeValue = mFromValue;
}
mLastValue = mAnimatedValue.nodeValue;
}
final double value =
mFromValue
+ (mVelocity / (1 - mDeceleration))
* (1 - Math.exp(-(1 - mDeceleration) * (frameTimeMillis - mStartFrameTimeMillis)));
if (Math.abs(mLastValue - value) < 0.1) {
if (mIterations == -1 || mCurrentLoop < mIterations) { // looping animation, return to start
// set mStartFrameTimeMillis to -1 to reset instance variables on the next runAnimationStep
mStartFrameTimeMillis = -1;
mCurrentLoop++;
} else { // animation has completed
mHasFinished = true;
return;
}
}
mLastValue = value;
mAnimatedValue.nodeValue = value;
}
}
@@ -0,0 +1,71 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.animated
import com.facebook.react.bridge.ReadableMap
import kotlin.math.abs
import kotlin.math.exp
/**
* Implementation of [AnimationDriver] providing support for decay animations. The implementation is
* copied from the JS version in `AnimatedImplementation.js`.
*/
internal class DecayAnimation(config: ReadableMap) : AnimationDriver() {
private var velocity: Double = 0.0
private var deceleration: Double = 0.0
private var startFrameTimeMillis: Long = -1
private var fromValue: Double = 0.0
private var lastValue: Double = 0.0
private var iterations: Int = 1
private var currentLoop: Int = 1
init {
resetConfig(config)
}
public override fun resetConfig(config: ReadableMap): Unit {
velocity = config.getDouble("velocity")
deceleration = config.getDouble("deceleration")
startFrameTimeMillis = -1
fromValue = 0.0
lastValue = 0.0
iterations = if (config.hasKey("iterations")) config.getInt("iterations") else 1
currentLoop = 1
mHasFinished = iterations == 0
}
public override fun runAnimationStep(frameTimeNanos: Long) {
val frameTimeMillis = frameTimeNanos / 1000000
if (startFrameTimeMillis == -1L) {
// since this is the first animation step, consider the start to be on the previous frame
startFrameTimeMillis = frameTimeMillis - 16
if (fromValue == lastValue) { // first iteration, assign mFromValue based on mAnimatedValue
fromValue = mAnimatedValue.nodeValue
} else { // not the first iteration, reset mAnimatedValue based on mFromValue
mAnimatedValue.nodeValue = fromValue
}
lastValue = mAnimatedValue.nodeValue
}
val value =
(fromValue +
velocity / (1 - deceleration) *
(1 - exp(-(1 - deceleration) * (frameTimeMillis - startFrameTimeMillis))))
if (abs(lastValue - value) < 0.1) {
if (iterations == -1 || currentLoop < iterations) { // looping animation, return to start
// set mStartFrameTimeMillis to -1 to reset instance variables on the next runAnimationStep
startFrameTimeMillis = -1
currentLoop++
} else { // animation has completed
mHasFinished = true
return
}
}
lastValue = value
mAnimatedValue.nodeValue = value
}
}
@@ -1120,7 +1120,7 @@ class NativeAnimatedNodeTraversalTest {
}
// at this point we expect tracking value to be at 75
assertThat((nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).value)
assertThat((nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).getValue())
.isEqualTo(75.0)
// we update "toValue" again to 100 and expect the animation to restart from the current
@@ -1228,11 +1228,11 @@ class NativeAnimatedNodeTraversalTest {
// passes the final point (that is 1) while going backwards
var isBoucingBack: Boolean = false
var previousValue: Double =
(nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).value
(nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).getValue()
for (i in 500 downTo 0) {
nativeAnimatedNodesManager.runUpdates(nextFrameTime())
val currentValue: Double =
(nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).value
(nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).getValue()
if (previousValue >= 1.0 && currentValue < 1.0) {
isBoucingBack = true
break
@@ -1253,7 +1253,7 @@ class NativeAnimatedNodeTraversalTest {
for (i in 0 until 8 * 60) {
nativeAnimatedNodesManager.runUpdates(nextFrameTime())
val currentValue: Double =
(nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).value
(nativeAnimatedNodesManager.getNodeById(3) as ValueAnimatedNode).getValue()
if (!hasTurnedForward) {
if (currentValue <= previousValue) {
bounceBackInitialFrames++