Implement native Animated value listeners on Android

Summary:
Adds support for `Animated.Value#addListener` for native driven nodes on Android. This is based on work by skevy in the exponent RN fork. Also adds a UIExplorer example.

** Test plan **
Run unit tests

Tested that by adding a listener to a native driven animated node and checked that the listener callback is called properly.

Also tested that it doesn't crash on iOS that doesn't support this yet.
Closes https://github.com/facebook/react-native/pull/8844

Differential Revision: D3670906

fbshipit-source-id: 15700ed7b93db140d907ce80af4dae6be3102135
This commit is contained in:
Janic Duplessis
2016-08-04 13:11:37 -07:00
committed by Facebook Github Bot 7
parent 30677e7193
commit 158d435f36
9 changed files with 266 additions and 15 deletions

View File

@@ -11,6 +11,7 @@
*/
'use strict';
var DeviceEventEmitter = require('RCTDeviceEventEmitter');
var InteractionManager = require('InteractionManager');
var Interpolation = require('Interpolation');
var React = require('React');
@@ -634,6 +635,7 @@ class AnimatedValue extends AnimatedWithChildren {
_animation: ?Animation;
_tracking: ?Animated;
_listeners: {[key: string]: ValueListenerCallback};
__nativeAnimatedValueListener: ?any;
constructor(value: number) {
super();
@@ -652,6 +654,14 @@ class AnimatedValue extends AnimatedWithChildren {
return this._value + this._offset;
}
__makeNative() {
super.__makeNative();
if (Object.keys(this._listeners).length) {
this._startListeningToNativeValueUpdates();
}
}
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
@@ -693,15 +703,49 @@ class AnimatedValue extends AnimatedWithChildren {
addListener(callback: ValueListenerCallback): string {
var id = String(_uniqueId++);
this._listeners[id] = callback;
if (this.__isNative) {
this._startListeningToNativeValueUpdates();
}
return id;
}
removeListener(id: string): void {
delete this._listeners[id];
if (this.__isNative && Object.keys(this._listeners).length === 0) {
this._stopListeningForNativeValueUpdates();
}
}
removeAllListeners(): void {
this._listeners = {};
if (this.__isNative) {
this._stopListeningForNativeValueUpdates();
}
}
_startListeningToNativeValueUpdates() {
if (this.__nativeAnimatedValueListener ||
!NativeAnimatedHelper.supportsNativeListener()) {
return;
}
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
this.__nativeAnimatedValueListener = DeviceEventEmitter.addListener('onAnimatedValueUpdate', (data) => {
if (data.tag !== this.__getNativeTag()) {
return;
}
this._updateValue(data.value, false /* flush */);
});
}
_stopListeningForNativeValueUpdates() {
if (!this.__nativeAnimatedValueListener ||
!NativeAnimatedHelper.supportsNativeListener()) {
return;
}
this.__nativeAnimatedValueListener.remove();
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
}
/**
@@ -1204,7 +1248,7 @@ class AnimatedStyle extends AnimatedWithChildren {
if (value instanceof Animated) {
if (!value.__isNative) {
// We cannot use value of natively driven nodes this way as the value we have access from JS
// may not be up to date
// may not be up to date.
style[key] = value.__getValue();
}
} else {
@@ -1296,9 +1340,9 @@ class AnimatedProps extends Animated {
for (var key in this._props) {
var value = this._props[key];
if (value instanceof Animated) {
if (!value.__isNative) {
if (!value.__isNative || value instanceof AnimatedStyle) {
// We cannot use value of natively driven nodes this way as the value we have access from JS
// may not be up to date
// may not be up to date.
props[key] = value.__getValue();
}
} else {

View File

@@ -30,6 +30,14 @@ var API = {
assertNativeAnimatedModule();
NativeAnimatedModule.createAnimatedNode(tag, config);
},
startListeningToAnimatedNodeValue: function(tag: number) {
assertNativeAnimatedModule();
NativeAnimatedModule.startListeningToAnimatedNodeValue(tag);
},
stopListeningToAnimatedNodeValue: function(tag: number) {
assertNativeAnimatedModule();
NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag);
},
connectAnimatedNodes: function(parentTag: number, childTag: number): void {
assertNativeAnimatedModule();
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag);
@@ -144,6 +152,11 @@ function assertNativeAnimatedModule(): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
}
// TODO: remove this when iOS supports native listeners.
function supportsNativeListener(): bool {
return !!NativeAnimatedModule.startListeningToAnimatedNodeValue;
}
module.exports = {
API,
validateProps,
@@ -153,4 +166,5 @@ module.exports = {
generateNewNodeTag,
generateNewAnimationId,
assertNativeAnimatedModule,
supportsNativeListener,
};