Permalink
Browse files

native decay animation

Summary: Add support for `useNativeDriver: true` to `Animated.decay`. Add example in Native Animated Example UIExplorer app.

Reviewed By: ritzau

Differential Revision: D3690127

fbshipit-source-id: eaa5e61293ed174191cec72255ea2677dbaa1757
  • Loading branch information...
1 parent 37ab1c8 commit 2a7f4be8f84bc0f023b8911b56b104a57c8ec64a @foghina foghina committed with Facebook Github Bot 5 Aug 19, 2016
@@ -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 = [
</Tester>
);
},
+ },{
+ title: 'translateX => Animated.decay',
+ render: function() {
+ return (
+ <Tester
+ type="decay"
+ config={{ velocity: 0.5 }}
+ reverseConfig={{ velocity: -0.5 }}
+ >
+ {anim => (
+ <Animated.View
+ style={[
+ styles.block,
+ {
+ transform: [
+ {
+ translateX: anim
+ },
+ ],
+ }
+ ]}
+ />
+ )}
+ </Tester>
+ );
+ },
},
{
title: 'Animated value listener',
@@ -351,28 +351,44 @@ class DecayAnimation extends Animation {
_velocity: number;
_onUpdate: (value: number) => void;
_animationFrame: any;
+ _useNativeDriver: bool;
constructor(
config: DecayAnimationConfigSingle,
) {
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;
this._fromValue = fromValue;
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 {
@@ -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;
+ }
+}
@@ -139,6 +139,8 @@ public void startAnimatingNode(
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);
}
@@ -17,7 +17,6 @@
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;
@@ -260,6 +259,63 @@ public void testSpringAnimation() {
}
@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<ReactStylesDiffMap> 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);

0 comments on commit 2a7f4be

Please sign in to comment.