Add support for value listener

Summary:
Adds support for `Animated.Value#addListener` for native driven animated values. Same as #8844 but for iOS. This depends on some JS code in #8844 so only review the 2nd commit and let's wait for #8844 to land first.

**Test plan**
Tested using the UIExplorer example.
Closes https://github.com/facebook/react-native/pull/9194

Differential Revision: D3681749

fbshipit-source-id: 521a61e2221c1ad1f6f40c75dd2dc957361d0271
This commit is contained in:
Janic Duplessis 2016-08-11 18:10:16 -07:00 committed by Facebook Github Bot 6
parent 68d483e041
commit 0e204e1141
7 changed files with 72 additions and 25 deletions

View File

@ -347,7 +347,6 @@ exports.examples = [
}, },
{ {
title: 'Animated value listener', title: 'Animated value listener',
platform: 'android',
render: function() { render: function() {
return ( return (
<ValueListenerExample /> <ValueListenerExample />

View File

@ -11,7 +11,6 @@
*/ */
'use strict'; 'use strict';
var DeviceEventEmitter = require('RCTDeviceEventEmitter');
var InteractionManager = require('InteractionManager'); var InteractionManager = require('InteractionManager');
var Interpolation = require('Interpolation'); var Interpolation = require('Interpolation');
var React = require('React'); var React = require('React');
@ -750,13 +749,12 @@ class AnimatedValue extends AnimatedWithChildren {
} }
_startListeningToNativeValueUpdates() { _startListeningToNativeValueUpdates() {
if (this.__nativeAnimatedValueListener || if (this.__nativeAnimatedValueListener) {
!NativeAnimatedHelper.supportsNativeListener()) {
return; return;
} }
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
this.__nativeAnimatedValueListener = DeviceEventEmitter.addListener( this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
'onAnimatedValueUpdate', 'onAnimatedValueUpdate',
(data) => { (data) => {
if (data.tag !== this.__getNativeTag()) { if (data.tag !== this.__getNativeTag()) {
@ -768,8 +766,7 @@ class AnimatedValue extends AnimatedWithChildren {
} }
_stopListeningForNativeValueUpdates() { _stopListeningForNativeValueUpdates() {
if (!this.__nativeAnimatedValueListener || if (!this.__nativeAnimatedValueListener) {
!NativeAnimatedHelper.supportsNativeListener()) {
return; return;
} }

View File

@ -11,21 +11,24 @@
*/ */
'use strict'; 'use strict';
var NativeAnimatedModule = require('NativeModules').NativeAnimatedModule; const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
const NativeEventEmitter = require('NativeEventEmitter');
var invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
var __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
var __nativeAnimationIdCount = 1; /* used for started animations */ let __nativeAnimationIdCount = 1; /* used for started animations */
type EndResult = {finished: bool}; type EndResult = {finished: bool};
type EndCallback = (result: EndResult) => void; type EndCallback = (result: EndResult) => void;
let nativeEventEmitter;
/** /**
* Simple wrappers around NativeANimatedModule to provide flow and autocmplete support for * Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for
* the native module methods * the native module methods
*/ */
var API = { const API = {
createAnimatedNode: function(tag: number, config: Object): void { createAnimatedNode: function(tag: number, config: Object): void {
assertNativeAnimatedModule(); assertNativeAnimatedModule();
NativeAnimatedModule.createAnimatedNode(tag, config); NativeAnimatedModule.createAnimatedNode(tag, config);
@ -79,7 +82,7 @@ var API = {
* to be updated through the shadow view hierarchy (all non-layout properties). This list is limited * to be updated through the shadow view hierarchy (all non-layout properties). This list is limited
* to the properties that will perform best when animated off the JS thread. * to the properties that will perform best when animated off the JS thread.
*/ */
var PROPS_WHITELIST = { const PROPS_WHITELIST = {
style: { style: {
opacity: true, opacity: true,
transform: true, transform: true,
@ -91,7 +94,7 @@ var PROPS_WHITELIST = {
}, },
}; };
var TRANSFORM_WHITELIST = { const TRANSFORM_WHITELIST = {
translateX: true, translateX: true,
translateY: true, translateY: true,
scale: true, scale: true,
@ -152,11 +155,6 @@ function assertNativeAnimatedModule(): void {
invariant(NativeAnimatedModule, 'Native animated module is not available'); invariant(NativeAnimatedModule, 'Native animated module is not available');
} }
// TODO: remove this when iOS supports native listeners.
function supportsNativeListener(): bool {
return !!NativeAnimatedModule.startListeningToAnimatedNodeValue;
}
module.exports = { module.exports = {
API, API,
validateProps, validateProps,
@ -166,5 +164,10 @@ module.exports = {
generateNewNodeTag, generateNewNodeTag,
generateNewAnimationId, generateNewAnimationId,
assertNativeAnimatedModule, assertNativeAnimatedModule,
supportsNativeListener, get nativeEventEmitter() {
if (!nativeEventEmitter) {
nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule);
}
return nativeEventEmitter;
},
}; };

View File

@ -10,8 +10,17 @@
#import "RCTAnimatedNode.h" #import "RCTAnimatedNode.h"
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@class RCTValueAnimatedNode;
@protocol RCTValueAnimatedNodeObserver <NSObject>
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value;
@end
@interface RCTValueAnimatedNode : RCTAnimatedNode @interface RCTValueAnimatedNode : RCTAnimatedNode
@property (nonatomic, assign) CGFloat value; @property (nonatomic, assign) CGFloat value;
@property (nonatomic, weak) id<RCTValueAnimatedNodeObserver> valueObserver;
@end @end

View File

@ -11,4 +11,13 @@
@implementation RCTValueAnimatedNode @implementation RCTValueAnimatedNode
- (void)setValue:(CGFloat)value
{
_value = value;
if (_valueObserver) {
[_valueObserver animatedNode:self didUpdateValue:_value];
}
}
@end @end

View File

@ -7,7 +7,9 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import "RCTBridgeModule.h" #import "RCTBridgeModule.h"
#import "RCTValueAnimatedNode.h"
#import "RCTEventEmitter.h"
@interface RCTNativeAnimatedModule : NSObject <RCTBridgeModule> @interface RCTNativeAnimatedModule : RCTEventEmitter <RCTBridgeModule, RCTValueAnimatedNodeObserver>
@end @end

View File

@ -32,13 +32,12 @@
CADisplayLink *_displayLink; CADisplayLink *_displayLink;
} }
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge - (void)setBridge:(RCTBridge *)bridge
{ {
_bridge = bridge; [super setBridge:bridge];
_animationNodes = [NSMutableDictionary new]; _animationNodes = [NSMutableDictionary new];
_animationDrivers = [NSMutableDictionary new]; _animationDrivers = [NSMutableDictionary new];
_activeAnimations = [NSMutableSet new]; _activeAnimations = [NSMutableSet new];
@ -47,11 +46,17 @@ RCT_EXPORT_MODULE()
_propAnimationNodes = [NSMutableSet new]; _propAnimationNodes = [NSMutableSet new];
} }
- (dispatch_queue_t)methodQueue - (dispatch_queue_t)methodQueue
{ {
return dispatch_get_main_queue(); return dispatch_get_main_queue();
} }
- (NSArray<NSString *> *)supportedEvents
{
return @[@"onAnimatedValueUpdate"];
}
RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config) config:(NSDictionary<NSString *, id> *)config)
{ {
@ -198,6 +203,29 @@ RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag)
} }
} }
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
((RCTValueAnimatedNode *)node).valueObserver = self;
}
}
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
((RCTValueAnimatedNode *)node).valueObserver = nil;
}
}
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
{
[self sendEventWithName:@"onAnimatedValueUpdate"
body:@{@"tag": node.nodeTag, @"value": @(value)}];
}
#pragma mark -- Animation Loop #pragma mark -- Animation Loop
- (void)startAnimation - (void)startAnimation