diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/FrameBasedAnimationDriver.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/FrameBasedAnimationDriver.java deleted file mode 100644 index b13a9741c4b0c5..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/FrameBasedAnimationDriver.java +++ /dev/null @@ -1,114 +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.common.logging.FLog; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.build.ReactBuildConfig; - -/** - * Implementation of {@link AnimationDriver} which provides a support for simple time-based - * animations that are pre-calculate on the JS side. For each animation frame JS provides a value - * from 0 to 1 that indicates a progress of the animation at that frame. - */ -class FrameBasedAnimationDriver extends AnimationDriver { - - // 60FPS - private static final double FRAME_TIME_MILLIS = 1000d / 60d; - - private long mStartFrameTimeNanos; - private double[] mFrames; - private double mToValue; - private double mFromValue; - private int mIterations; - private int mCurrentLoop; - private int logCount = 0; - - FrameBasedAnimationDriver(ReadableMap config) { - resetConfig(config); - } - - @Override - public void resetConfig(ReadableMap config) { - ReadableArray frames = config.getArray("frames"); - int numberOfFrames = frames.size(); - if (mFrames == null || mFrames.length != numberOfFrames) { - mFrames = new double[numberOfFrames]; - } - for (int i = 0; i < numberOfFrames; i++) { - mFrames[i] = frames.getDouble(i); - } - if (config.hasKey("toValue")) { - mToValue = config.getType("toValue") == ReadableType.Number ? config.getDouble("toValue") : 0; - } else { - mToValue = 0; - } - if (config.hasKey("iterations")) { - mIterations = - config.getType("iterations") == ReadableType.Number ? config.getInt("iterations") : 1; - } else { - mIterations = 1; - } - mCurrentLoop = 1; - mHasFinished = mIterations == 0; - mStartFrameTimeNanos = -1; - } - - @Override - public void runAnimationStep(long frameTimeNanos) { - if (mStartFrameTimeNanos < 0) { - mStartFrameTimeNanos = frameTimeNanos; - if (mCurrentLoop == 1) { - // initiate start value when animation runs for the first time - mFromValue = mAnimatedValue.nodeValue; - } - } - - long timeFromStartMillis = (frameTimeNanos - mStartFrameTimeNanos) / 1000000; - int frameIndex = (int) Math.round(timeFromStartMillis / FRAME_TIME_MILLIS); - if (frameIndex < 0) { - String message = - "Calculated frame index should never be lower than 0. Called with frameTimeNanos " - + frameTimeNanos - + " and mStartFrameTimeNanos " - + mStartFrameTimeNanos; - if (ReactBuildConfig.DEBUG) { - throw new IllegalStateException(message); - } else { - if (logCount < 100) { - FLog.w(ReactConstants.TAG, message); - logCount++; - } - return; - } - } else if (mHasFinished) { - // nothing to do here - return; - } - - double nextValue; - if (frameIndex >= mFrames.length - 1) { - if (mIterations == -1 || mCurrentLoop < mIterations) { - // Use last frame value, just in case it's different from mToValue - nextValue = mFromValue + mFrames[mFrames.length - 1] * (mToValue - mFromValue); - mStartFrameTimeNanos = -1; - mCurrentLoop++; - } else { - // animation has completed, no more frames left - nextValue = mToValue; - mHasFinished = true; - } - } else { - nextValue = mFromValue + mFrames[frameIndex] * (mToValue - mFromValue); - } - mAnimatedValue.nodeValue = nextValue; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/FrameBasedAnimationDriver.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/FrameBasedAnimationDriver.kt new file mode 100644 index 00000000000000..6fb5cee28eb90b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/FrameBasedAnimationDriver.kt @@ -0,0 +1,111 @@ +/* + * 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.common.logging.FLog +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableType +import com.facebook.react.common.ReactConstants +import com.facebook.react.common.build.ReactBuildConfig + +/** + * Implementation of [AnimationDriver] which provides a support for simple time-based animations + * that are pre-calculate on the JS side. For each animation frame JS provides a value from 0 to 1 + * that indicates a progress of the animation at that frame. + */ +internal class FrameBasedAnimationDriver(config: ReadableMap) : AnimationDriver() { + private var startFrameTimeNanos: Long = -1 + private var frames: DoubleArray = DoubleArray(0) + private var toValue = 0.0 + private var fromValue = 0.0 + private var iterations = 1 + private var currentLoop = 1 + private var logCount = 0 + + init { + resetConfig(config) + } + + override fun resetConfig(config: ReadableMap) { + val framesConfig = config.getArray("frames") + if (framesConfig != null) { + val numberOfFrames = framesConfig.size() + if (frames.size != numberOfFrames) { + frames = DoubleArray(numberOfFrames) + } + for (i in 0 until numberOfFrames) { + frames[i] = framesConfig.getDouble(i) + } + } + toValue = + if (config.hasKey("toValue")) { + if (config.getType("toValue") == ReadableType.Number) config.getDouble("toValue") else 0.0 + } else { + 0.0 + } + iterations = + if (config.hasKey("iterations")) { + if (config.getType("iterations") == ReadableType.Number) config.getInt("iterations") + else 1 + } else { + 1 + } + currentLoop = 1 + mHasFinished = iterations == 0 + startFrameTimeNanos = -1 + } + + override fun runAnimationStep(frameTimeNanos: Long) { + if (startFrameTimeNanos < 0) { + startFrameTimeNanos = frameTimeNanos + if (currentLoop == 1) { + // initiate start value when animation runs for the first time + fromValue = mAnimatedValue.nodeValue + } + } + val timeFromStartMillis = (frameTimeNanos - startFrameTimeNanos) / 1000000 + val frameIndex = Math.round(timeFromStartMillis / FRAME_TIME_MILLIS).toInt() + if (frameIndex < 0) { + val message = + ("Calculated frame index should never be lower than 0. Called with frameTimeNanos " + + frameTimeNanos + + " and mStartFrameTimeNanos " + + startFrameTimeNanos) + check(!ReactBuildConfig.DEBUG) { message } + if (logCount < 100) { + FLog.w(ReactConstants.TAG, message) + logCount++ + } + return + } else if (mHasFinished) { + // nothing to do here + return + } + val nextValue: Double + if (frameIndex >= frames.size - 1) { + if (iterations == -1 || currentLoop < iterations) { + // Use last frame value, just in case it's different from mToValue + nextValue = fromValue + frames[frames.size - 1] * (toValue - fromValue) + startFrameTimeNanos = -1 + currentLoop++ + } else { + // animation has completed, no more frames left + nextValue = toValue + mHasFinished = true + } + } else { + nextValue = fromValue + frames[frameIndex] * (toValue - fromValue) + } + mAnimatedValue.nodeValue = nextValue + } + + companion object { + // 60FPS + private const val FRAME_TIME_MILLIS = 1000.0 / 60.0 + } +}