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:
parent
37ab1c8844
commit
2a7f4be8f8
|
@ -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,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,8 +384,12 @@ class DecayAnimation extends Animation {
|
|||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
this._startTime = Date.now();
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate(): void {
|
||||
var now = Date.now();
|
||||
|
|
|
@ -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 @@ 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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue