diff --git a/Examples/UIExplorer/js/NativeAnimationsExample.js b/Examples/UIExplorer/js/NativeAnimationsExample.js
index 12f317c72..209271b36 100644
--- a/Examples/UIExplorer/js/NativeAnimationsExample.js
+++ b/Examples/UIExplorer/js/NativeAnimationsExample.js
@@ -41,12 +41,16 @@ class Tester extends React.Component {
current = 0;
onPress = () => {
+ const animConfig = (
+ this.current && this.props.reverseConfig ? this.props.reverseConfig : this.props.config
+ );
this.current = this.current ? 0 : 1;
const config = {
- ...this.props.config,
+ ...animConfig,
toValue: this.current,
};
+ // $FlowIssue #0000000
Animated[this.props.type](this.state.native, { ...config, useNativeDriver: true }).start();
Animated[this.props.type](this.state.js, { ...config, useNativeDriver: false }).start();
};
@@ -344,6 +348,32 @@ exports.examples = [
);
},
+ },{
+ title: 'translateX => Animated.decay',
+ render: function() {
+ return (
+
+ {anim => (
+
+ )}
+
+ );
+ },
},
{
title: 'Animated value listener',
diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js
index 1639b745d..05b7ea05f 100644
--- a/Libraries/Animated/src/AnimatedImplementation.js
+++ b/Libraries/Animated/src/AnimatedImplementation.js
@@ -351,6 +351,7 @@ class DecayAnimation extends Animation {
_velocity: number;
_onUpdate: (value: number) => void;
_animationFrame: any;
+ _useNativeDriver: bool;
constructor(
config: DecayAnimationConfigSingle,
@@ -358,13 +359,24 @@ class DecayAnimation extends Animation {
super();
this._deceleration = config.deceleration !== undefined ? config.deceleration : 0.998;
this._velocity = config.velocity;
+ this._useNativeDriver = config.useNativeDriver !== undefined ? config.useNativeDriver : false;
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
}
+ __getNativeAnimationConfig() {
+ return {
+ type: 'decay',
+ deceleration: this._deceleration,
+ velocity: this._velocity,
+ };
+ }
+
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: ?EndCallback,
+ previousAnimation: ?Animation,
+ animatedValue: AnimatedValue,
): void {
this.__active = true;
this._lastValue = fromValue;
@@ -372,7 +384,11 @@ class DecayAnimation extends Animation {
this._onUpdate = onUpdate;
this.__onEnd = onEnd;
this._startTime = Date.now();
- this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
+ if (this._useNativeDriver) {
+ this.__startNativeAnimation(animatedValue);
+ } else {
+ this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
+ }
}
onUpdate(): void {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/DecayAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/animated/DecayAnimation.java
new file mode 100644
index 000000000..84dac0623
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/DecayAnimation.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.animated;
+
+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}.
+ */
+public class DecayAnimation extends AnimationDriver {
+
+ private final double mVelocity;
+ private final double mDeceleration;
+
+ private long mStartFrameTimeMillis = -1;
+ private double mFromValue;
+ private double mLastValue;
+
+ public DecayAnimation(ReadableMap config) {
+ mVelocity = config.getDouble("velocity");
+ mDeceleration = config.getDouble("deceleration");
+ }
+
+ @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;
+ mFromValue = mAnimatedValue.mValue;
+ mLastValue = mAnimatedValue.mValue;
+ }
+
+ final double value = mFromValue +
+ (mVelocity / (1 - mDeceleration)) *
+ (1 - Math.exp(-(1 - mDeceleration) * (frameTimeMillis - mStartFrameTimeMillis)));
+
+ if (Math.abs(mLastValue - value) < 0.1) {
+ mHasFinished = true;
+ return;
+ }
+
+ mLastValue = value;
+ mAnimatedValue.mValue = value;
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java
index 67a735e16..fe6613e03 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java
@@ -139,6 +139,8 @@ import javax.annotation.Nullable;
animation = new FrameBasedAnimationDriver(animationConfig);
} else if ("spring".equals(type)) {
animation = new SpringAnimation(animationConfig);
+ } else if ("decay".equals(type)) {
+ animation = new DecayAnimation(animationConfig);
} else {
throw new JSApplicationIllegalArgumentException("Unsupported animation type: " + type);
}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
index a499aa26a..2f5227d8e 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
@@ -17,7 +17,6 @@ import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.UIImplementation;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -259,6 +258,63 @@ public class NativeAnimatedNodeTraversalTest {
verifyNoMoreInteractions(mUIImplementationMock);
}
+ @Test
+ public void testDecayAnimation() {
+ createSimpleAnimatedViewWithOpacity(1000, 0d);
+
+ Callback animationCallback = mock(Callback.class);
+ mNativeAnimatedNodesManager.startAnimatingNode(
+ 1,
+ 1,
+ JavaOnlyMap.of(
+ "type",
+ "decay",
+ "velocity",
+ 0.5d,
+ "deceleration",
+ 0.998d),
+ animationCallback);
+
+ ArgumentCaptor stylesCaptor =
+ ArgumentCaptor.forClass(ReactStylesDiffMap.class);
+
+ reset(mUIImplementationMock);
+ mNativeAnimatedNodesManager.runUpdates(nextFrameTime());
+ verify(mUIImplementationMock, atMost(1))
+ .synchronouslyUpdateViewOnUIThread(eq(1000), stylesCaptor.capture());
+ double previousValue = stylesCaptor.getValue().getDouble("opacity", Double.NaN);
+ double previousDiff = Double.POSITIVE_INFINITY;
+ /* run 3 secs of animation */
+ for (int i = 0; i < 3 * 60; i++) {
+ reset(mUIImplementationMock);
+ mNativeAnimatedNodesManager.runUpdates(nextFrameTime());
+ verify(mUIImplementationMock, atMost(1))
+ .synchronouslyUpdateViewOnUIThread(eq(1000), stylesCaptor.capture());
+ double currentValue = stylesCaptor.getValue().getDouble("opacity", Double.NaN);
+ double currentDiff = currentValue - previousValue;
+ // verify monotonicity
+ // greater *or equal* because the animation stops during these 3 seconds
+ assertThat(currentValue).as("on frame " + i).isGreaterThanOrEqualTo(previousValue);
+ // verify decay
+ if (i > 3) {
+ // i > 3 because that's how long it takes to settle previousDiff
+ if (i % 3 != 0) {
+ // i % 3 != 0 because every 3 frames we go a tiny
+ // bit faster, because frame length is 16.(6)ms
+ assertThat(currentDiff).as("on frame " + i).isLessThanOrEqualTo(previousDiff);
+ } else {
+ assertThat(currentDiff).as("on frame " + i).isGreaterThanOrEqualTo(previousDiff);
+ }
+ }
+ previousValue = currentValue;
+ previousDiff = currentDiff;
+ }
+ // should be done in 3s
+ reset(mUIImplementationMock);
+ mNativeAnimatedNodesManager.runUpdates(nextFrameTime());
+ verifyNoMoreInteractions(mUIImplementationMock);
+ }
+
@Test
public void testAnimationCallbackFinish() {
createSimpleAnimatedViewWithOpacity(1000, 0d);