Skip to content

Commit b48f7e5

Browse files
kmagierafacebook-github-bot
authored andcommittedFeb 16, 2018
Support for animated tracking in native driver
Summary: This PR adds support for Animated tracking to Animated Native Driver implementation on Android and iOS. Animated tracking allows for animation to be started with a "dynamic" end value. Instead of passing a fixed number as end value we can pass a reference to another Animated.Value. Then when that value changes, the animation will be reconfigured to drive the animation to the new destination point. What is important is that animation will keep its state in the process of updating "toValue". That is if it is a spring animation and the end value changes while the previous animation still hasn't settled the new animation will start from the current position and will inherit current velocity. This makes end value transitions very smooth. Animated tracking is available in JS implementation of Animated library but not in the native implementation. Therefore until now, it wasn't possible to utilize native driver when using animated tracking. Offloading animation from JS thread turns out to be crucial for gesture driven animations. This PR is a step forward towards feature parity between JS and native implementations of Animated. Here is a link to example video that shows how tracking can be used to implement chat heads effect: https://twitter.com/kzzzf/status/958362032650244101 In addition this PR fixes an issue with frames animation driver on Android that because of rounding issues was taking one extra frame to start. Because of that change I had to update a number of Android unit tests that were relying on that behavior and running that one additional animation step prior to performing checks. As a part of this PR I'm adding three unit tests for each of the platforms that verifies most important aspects of this implementation. Please refer to the code and look at the test cases top level comments to learn what they do. I'm also adding a section to "Native Animated Example" screen in RNTester app that provides a test case for tracking. In the example we have blue square that fallows the red line drawn on screen. Line uses Animated.Value for it's position while square is connected via tracking spring animation to that value. So it is ought to follow the line. When user taps in the area surrounding the button new position for the red line is selected at random and the value updates. Then we can watch blue screen animate to that position. You can also refer to this video that I use to demonstrate how tracking can be linked with native gesture events using react-native-gesture-handler lib: https://twitter.com/kzzzf/status/958362032650244101 [GENERAL][FEATURE][Native Animated] - Added support for animated tracking to native driver. Now you can use `useNativeDriver` flag with animations that track other Animated.Values Closes #17896 Differential Revision: D6974170 Pulled By: hramos fbshipit-source-id: 50e918b36ee10f80c1deb866c955661d4cc2619b
1 parent 574c70e commit b48f7e5

22 files changed

+899
-121
lines changed
 

‎Libraries/Animated/src/nodes/AnimatedTracking.js

+37
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
const AnimatedValue = require('./AnimatedValue');
1616
const AnimatedNode = require('./AnimatedNode');
17+
const {
18+
generateNewAnimationId,
19+
shouldUseNativeDriver,
20+
} = require('../NativeAnimatedHelper');
1721

1822
import type {EndCallback} from '../animations/Animation';
1923

@@ -23,6 +27,7 @@ class AnimatedTracking extends AnimatedNode {
2327
_callback: ?EndCallback;
2428
_animationConfig: Object;
2529
_animationClass: any;
30+
_useNativeDriver: boolean;
2631

2732
constructor(
2833
value: AnimatedValue,
@@ -36,16 +41,32 @@ class AnimatedTracking extends AnimatedNode {
3641
this._parent = parent;
3742
this._animationClass = animationClass;
3843
this._animationConfig = animationConfig;
44+
this._useNativeDriver = shouldUseNativeDriver(animationConfig);
3945
this._callback = callback;
4046
this.__attach();
4147
}
4248

49+
__makeNative() {
50+
this.__isNative = true;
51+
this._parent.__makeNative();
52+
super.__makeNative();
53+
this._value.__makeNative();
54+
}
55+
4356
__getValue(): Object {
4457
return this._parent.__getValue();
4558
}
4659

4760
__attach(): void {
4861
this._parent.__addChild(this);
62+
if (this._useNativeDriver) {
63+
// when the tracking starts we need to convert this node to a "native node"
64+
// so that the parent node will be made "native" too. This is necessary as
65+
// if we don't do this `update` method will get called. At that point it
66+
// may be too late as it would mean the JS driver has already started
67+
// updating node values
68+
this.__makeNative();
69+
}
4970
}
5071

5172
__detach(): void {
@@ -62,6 +83,22 @@ class AnimatedTracking extends AnimatedNode {
6283
this._callback,
6384
);
6485
}
86+
87+
__getNativeConfig(): any {
88+
const animation = new this._animationClass({
89+
...this._animationConfig,
90+
// remove toValue from the config as it's a ref to Animated.Value
91+
toValue: undefined,
92+
});
93+
const animationConfig = animation.__getNativeAnimationConfig();
94+
return {
95+
type: 'tracking',
96+
animationId: generateNewAnimationId(),
97+
animationConfig,
98+
toValue: this._parent.__getNativeTag(),
99+
value: this._value.__getNativeTag(),
100+
};
101+
}
65102
}
66103

67104
module.exports = AnimatedTracking;

‎Libraries/Animated/src/nodes/AnimatedValue.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const NativeAnimatedHelper = require('../NativeAnimatedHelper');
2020

2121
import type Animation, {EndCallback} from '../animations/Animation';
2222
import type {InterpolationConfigType} from './AnimatedInterpolation';
23+
import type AnimatedTracking from './AnimatedTracking';
2324

2425
const NativeAnimatedAPI = NativeAnimatedHelper.API;
2526

@@ -76,7 +77,7 @@ class AnimatedValue extends AnimatedWithChildren {
7677
_startingValue: number;
7778
_offset: number;
7879
_animation: ?Animation;
79-
_tracking: ?AnimatedNode;
80+
_tracking: ?AnimatedTracking;
8081
_listeners: {[key: string]: ValueListenerCallback};
8182
__nativeAnimatedValueListener: ?any;
8283

@@ -311,7 +312,7 @@ class AnimatedValue extends AnimatedWithChildren {
311312
/**
312313
* Typically only used internally.
313314
*/
314-
track(tracking: AnimatedNode): void {
315+
track(tracking: AnimatedTracking): void {
315316
this.stopTracking();
316317
this._tracking = tracking;
317318
}

‎Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
3333
- (void)startAnimation;
3434
- (void)stepAnimationWithTime:(NSTimeInterval)currentTime;
3535
- (void)stopAnimation;
36+
- (void)resetAnimationConfig:(NSDictionary *)config;
3637

3738
NS_ASSUME_NONNULL_END
3839

‎Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m

+14-9
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,27 @@ - (instancetype)initWithId:(NSNumber *)animationId
4141
callBack:(nullable RCTResponseSenderBlock)callback;
4242
{
4343
if ((self = [super init])) {
44-
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
45-
44+
_callback = [callback copy];
4645
_animationId = animationId;
46+
_valueNode = valueNode;
4747
_fromValue = 0;
4848
_lastValue = 0;
49-
_valueNode = valueNode;
50-
_callback = [callback copy];
51-
_velocity = [RCTConvert CGFloat:config[@"velocity"]];
52-
_deceleration = [RCTConvert CGFloat:config[@"deceleration"]];
53-
_iterations = iterations.integerValue;
54-
_currentLoop = 1;
55-
_animationHasFinished = iterations.integerValue == 0;
49+
_velocity = [RCTConvert CGFloat:config[@"velocity"]]; // initial velocity
50+
[self resetAnimationConfig:config];
5651
}
5752
return self;
5853
}
5954

55+
- (void)resetAnimationConfig:(NSDictionary *)config
56+
{
57+
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
58+
_fromValue = _lastValue;
59+
_deceleration = [RCTConvert CGFloat:config[@"deceleration"]];
60+
_iterations = iterations.integerValue;
61+
_currentLoop = 1;
62+
_animationHasFinished = iterations.integerValue == 0;
63+
}
64+
6065
RCT_NOT_IMPLEMENTED(- (instancetype)init)
6166

6267
- (void)startAnimation

‎Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m

+19-10
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ @implementation RCTFrameAnimation
3131
NSArray<NSNumber *> *_frames;
3232
CGFloat _toValue;
3333
CGFloat _fromValue;
34+
CGFloat _lastPosition;
3435
NSTimeInterval _animationStartTime;
3536
NSTimeInterval _animationCurrentTime;
3637
RCTResponseSenderBlock _callback;
@@ -44,23 +45,30 @@ - (instancetype)initWithId:(NSNumber *)animationId
4445
callBack:(nullable RCTResponseSenderBlock)callback;
4546
{
4647
if ((self = [super init])) {
47-
NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
48-
NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
49-
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
50-
5148
_animationId = animationId;
52-
_toValue = toValue.floatValue;
53-
_fromValue = valueNode.value;
49+
_lastPosition = _fromValue = valueNode.value;
5450
_valueNode = valueNode;
55-
_frames = [frames copy];
5651
_callback = [callback copy];
57-
_animationHasFinished = iterations.integerValue == 0;
58-
_iterations = iterations.integerValue;
59-
_currentLoop = 1;
52+
[self resetAnimationConfig:config];
6053
}
6154
return self;
6255
}
6356

57+
- (void)resetAnimationConfig:(NSDictionary *)config
58+
{
59+
NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
60+
NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
61+
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
62+
63+
_fromValue = _lastPosition;
64+
_toValue = toValue.floatValue;
65+
_frames = [frames copy];
66+
_animationStartTime = _animationCurrentTime = -1;
67+
_animationHasFinished = iterations.integerValue == 0;
68+
_iterations = iterations.integerValue;
69+
_currentLoop = 1;
70+
}
71+
6472
RCT_NOT_IMPLEMENTED(- (instancetype)init)
6573

6674
- (void)startAnimation
@@ -144,6 +152,7 @@ - (void)updateOutputWithFrameOutput:(CGFloat)frameOutput
144152
EXTRAPOLATE_TYPE_EXTEND,
145153
EXTRAPOLATE_TYPE_EXTEND);
146154

155+
_lastPosition = outputValue;
147156
_valueNode.value = outputValue;
148157
[_valueNode setNeedsUpdate];
149158
}

‎Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m

+24-20
Original file line numberDiff line numberDiff line change
@@ -57,33 +57,37 @@ - (instancetype)initWithId:(NSNumber *)animationId
5757
callBack:(nullable RCTResponseSenderBlock)callback
5858
{
5959
if ((self = [super init])) {
60-
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
61-
6260
_animationId = animationId;
63-
_toValue = [RCTConvert CGFloat:config[@"toValue"]];
64-
_fromValue = valueNode.value;
65-
_lastPosition = 0;
61+
_lastPosition = valueNode.value;
6662
_valueNode = valueNode;
67-
_overshootClamping = [RCTConvert BOOL:config[@"overshootClamping"]];
68-
_restDisplacementThreshold = [RCTConvert CGFloat:config[@"restDisplacementThreshold"]];
69-
_restSpeedThreshold = [RCTConvert CGFloat:config[@"restSpeedThreshold"]];
70-
_stiffness = [RCTConvert CGFloat:config[@"stiffness"]];
71-
_damping = [RCTConvert CGFloat:config[@"damping"]];
72-
_mass = [RCTConvert CGFloat:config[@"mass"]];
73-
_initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
74-
63+
_lastVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
7564
_callback = [callback copy];
76-
77-
_lastPosition = _fromValue;
78-
_lastVelocity = _initialVelocity;
79-
80-
_animationHasFinished = iterations.integerValue == 0;
81-
_iterations = iterations.integerValue;
82-
_currentLoop = 1;
65+
[self resetAnimationConfig:config];
8366
}
8467
return self;
8568
}
8669

70+
- (void)resetAnimationConfig:(NSDictionary *)config
71+
{
72+
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
73+
_toValue = [RCTConvert CGFloat:config[@"toValue"]];
74+
_overshootClamping = [RCTConvert BOOL:config[@"overshootClamping"]];
75+
_restDisplacementThreshold = [RCTConvert CGFloat:config[@"restDisplacementThreshold"]];
76+
_restSpeedThreshold = [RCTConvert CGFloat:config[@"restSpeedThreshold"]];
77+
_stiffness = [RCTConvert CGFloat:config[@"stiffness"]];
78+
_damping = [RCTConvert CGFloat:config[@"damping"]];
79+
_mass = [RCTConvert CGFloat:config[@"mass"]];
80+
_initialVelocity = _lastVelocity;
81+
_fromValue = _lastPosition;
82+
_fromValue = _lastPosition;
83+
_lastVelocity = _initialVelocity;
84+
_animationHasFinished = iterations.integerValue == 0;
85+
_iterations = iterations.integerValue;
86+
_currentLoop = 1;
87+
_animationStartTime = _animationCurrentTime = -1;
88+
_animationHasBegun = YES;
89+
}
90+
8791
RCT_NOT_IMPLEMENTED(- (instancetype)init)
8892

8993
- (void)startAnimation

‎Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h

+3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99

1010
#import <Foundation/Foundation.h>
1111

12+
@class RCTNativeAnimatedNodesManager;
13+
1214
@interface RCTAnimatedNode : NSObject
1315

1416
- (instancetype)initWithTag:(NSNumber *)tag
1517
config:(NSDictionary<NSString *, id> *)config NS_DESIGNATED_INITIALIZER;
1618

1719
@property (nonatomic, readonly) NSNumber *nodeTag;
20+
@property (nonatomic, weak) RCTNativeAnimatedNodesManager *manager;
1821
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *config;
1922

2023
@property (nonatomic, copy, readonly) NSMapTable<NSNumber *, RCTAnimatedNode *> *childNodes;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTAnimatedNode.h"
11+
12+
13+
@interface RCTTrackingAnimatedNode : RCTAnimatedNode
14+
15+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTTrackingAnimatedNode.h"
11+
#import "RCTValueAnimatedNode.h"
12+
#import "RCTNativeAnimatedNodesManager.h"
13+
14+
@implementation RCTTrackingAnimatedNode {
15+
NSNumber *_animationId;
16+
NSNumber *_toValueNodeTag;
17+
NSNumber *_valueNodeTag;
18+
NSMutableDictionary *_animationConfig;
19+
}
20+
21+
- (instancetype)initWithTag:(NSNumber *)tag
22+
config:(NSDictionary<NSString *, id> *)config
23+
{
24+
if ((self = [super initWithTag:tag config:config])) {
25+
_animationId = config[@"animationId"];
26+
_toValueNodeTag = config[@"toValue"];
27+
_valueNodeTag = config[@"value"];
28+
_animationConfig = [NSMutableDictionary dictionaryWithDictionary:config[@"animationConfig"]];
29+
}
30+
return self;
31+
}
32+
33+
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
34+
{
35+
[self.manager stopAnimation:_animationId];
36+
[super onDetachedFromNode:parent];
37+
}
38+
39+
- (void)performUpdate
40+
{
41+
[super performUpdate];
42+
43+
// change animation config's "toValue" to reflect updated value of the parent node
44+
RCTValueAnimatedNode *node = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:_toValueNodeTag];
45+
_animationConfig[@"toValue"] = @(node.value);
46+
47+
[self.manager startAnimatingNode:_animationId
48+
nodeTag:_valueNodeTag
49+
config:_animationConfig
50+
endCallback:nil];
51+
}
52+
53+
@end
54+

0 commit comments

Comments
 (0)
Please sign in to comment.