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
This commit is contained in:
Felix Oghina 2016-08-19 07:06:29 -07:00 committed by Facebook Github Bot 5
parent 37ab1c8844
commit 2a7f4be8f8
5 changed files with 161 additions and 3 deletions

View File

@ -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',

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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<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);