mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-12 17:33:15 +08:00
Add listener for non-value animated node (#22883)
Summary: Changelog: ---------- [Changed][General] Move callback-related logic to `AnimatedNode` class in order to make it possible to add the listener for other animated nodes than `AnimatedValue`. I observed that native code appears to be fully prepared for listening not only to animated value but animated nodes generally. Therefore I managed to modify js code for exposing `addListener` method from `AnimatedNode` class instead of `AnimatedValue`. It called for some minor changes, which are not breaking. If you're fine with these changes, I could add proper docs if needed. Pull Request resolved: https://github.com/facebook/react-native/pull/22883 Differential Revision: D14041747 Pulled By: cpojer fbshipit-source-id: 94c68024ceaa259d9bb145bf4b3107af0b15db88
This commit is contained in:
committed by
Facebook Github Bot
parent
2d8ad076fe
commit
68a5ceef31
@@ -834,6 +834,47 @@ describe('Animated tests', () => {
|
||||
expect(value1.__getValue()).toBe(1492);
|
||||
});
|
||||
|
||||
it('should get updates for derived animated nodes', () => {
|
||||
const value1 = new Animated.Value(40);
|
||||
const value2 = new Animated.Value(50);
|
||||
const value3 = new Animated.Value(0);
|
||||
const value4 = Animated.add(value3, Animated.multiply(value1, value2));
|
||||
const callback = jest.fn();
|
||||
const view = new Animated.__PropsOnlyForTests(
|
||||
{
|
||||
style: {
|
||||
transform: [
|
||||
{
|
||||
translateX: value4,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
callback,
|
||||
);
|
||||
const listener = jest.fn();
|
||||
const id = value4.addListener(listener);
|
||||
value3.setValue(137);
|
||||
expect(listener.mock.calls.length).toBe(1);
|
||||
expect(listener).toBeCalledWith({value: 2137});
|
||||
value1.setValue(0);
|
||||
expect(listener.mock.calls.length).toBe(2);
|
||||
expect(listener).toBeCalledWith({value: 137});
|
||||
expect(view.__getValue()).toEqual({
|
||||
style: {
|
||||
transform: [
|
||||
{
|
||||
translateX: 137,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
value4.removeListener(id);
|
||||
value1.setValue(40);
|
||||
expect(listener.mock.calls.length).toBe(2);
|
||||
expect(value4.__getValue()).toBe(2137);
|
||||
});
|
||||
|
||||
it('should removeAll', () => {
|
||||
const value1 = new Animated.Value(0);
|
||||
const listener = jest.fn();
|
||||
|
||||
@@ -11,11 +11,18 @@
|
||||
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const NativeAnimatedAPI = NativeAnimatedHelper.API;
|
||||
const invariant = require('invariant');
|
||||
|
||||
type ValueListenerCallback = (state: {value: number}) => mixed;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
// Note(vjeux): this would be better as an interface but flow doesn't
|
||||
// support them yet
|
||||
class AnimatedNode {
|
||||
_listeners: {[key: string]: ValueListenerCallback};
|
||||
__nativeAnimatedValueListener: ?any;
|
||||
__attach(): void {}
|
||||
__detach(): void {
|
||||
if (this.__isNative && this.__nativeTag != null) {
|
||||
@@ -36,11 +43,103 @@ class AnimatedNode {
|
||||
/* Methods and props used by native Animated impl */
|
||||
__isNative: boolean;
|
||||
__nativeTag: ?number;
|
||||
|
||||
constructor() {
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
if (!this.__isNative) {
|
||||
throw new Error('This node cannot be made a "native" animated node');
|
||||
}
|
||||
|
||||
if (this.hasListeners()) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an asynchronous listener to the value so you can observe updates from
|
||||
* animations. This is useful because there is no way to
|
||||
* synchronously read the value because it might be driven natively.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener
|
||||
*/
|
||||
addListener(callback: (value: any) => mixed): string {
|
||||
const id = String(_uniqueId++);
|
||||
this._listeners[id] = callback;
|
||||
if (this.__isNative) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener. The `id` param shall match the identifier
|
||||
* previously returned by `addListener()`.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener
|
||||
*/
|
||||
removeListener(id: string): void {
|
||||
delete this._listeners[id];
|
||||
if (this.__isNative && !this.hasListeners()) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all registered listeners.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners
|
||||
*/
|
||||
removeAllListeners(): void {
|
||||
this._listeners = {};
|
||||
if (this.__isNative) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
hasListeners(): boolean {
|
||||
return !!Object.keys(this._listeners).length;
|
||||
}
|
||||
|
||||
_startListeningToNativeValueUpdates() {
|
||||
if (this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
|
||||
'onAnimatedValueUpdate',
|
||||
data => {
|
||||
if (data.tag !== this.__getNativeTag()) {
|
||||
return;
|
||||
}
|
||||
this._onAnimatedValueUpdateReceived(data.value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_onAnimatedValueUpdateReceived(value: number) {
|
||||
this.__callListeners(value);
|
||||
}
|
||||
|
||||
__callListeners(value: number): void {
|
||||
for (const key in this._listeners) {
|
||||
this._listeners[key]({value});
|
||||
}
|
||||
}
|
||||
|
||||
_stopListeningForNativeValueUpdates() {
|
||||
if (!this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.__nativeAnimatedValueListener.remove();
|
||||
this.__nativeAnimatedValueListener = null;
|
||||
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
}
|
||||
|
||||
__getNativeTag(): ?number {
|
||||
NativeAnimatedHelper.assertNativeAnimatedModule();
|
||||
invariant(
|
||||
|
||||
@@ -20,10 +20,6 @@ import type AnimatedTracking from './AnimatedTracking';
|
||||
|
||||
const NativeAnimatedAPI = NativeAnimatedHelper.API;
|
||||
|
||||
type ValueListenerCallback = (state: {value: number}) => void;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
/**
|
||||
* Animated works by building a directed acyclic graph of dependencies
|
||||
* transparently when you render your Animated components.
|
||||
@@ -77,15 +73,12 @@ class AnimatedValue extends AnimatedWithChildren {
|
||||
_offset: number;
|
||||
_animation: ?Animation;
|
||||
_tracking: ?AnimatedTracking;
|
||||
_listeners: {[key: string]: ValueListenerCallback};
|
||||
__nativeAnimatedValueListener: ?any;
|
||||
|
||||
constructor(value: number) {
|
||||
super();
|
||||
this._startingValue = this._value = value;
|
||||
this._offset = 0;
|
||||
this._animation = null;
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
__detach() {
|
||||
@@ -97,14 +90,6 @@ 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.
|
||||
@@ -167,74 +152,6 @@ class AnimatedValue extends AnimatedWithChildren {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an asynchronous listener to the value so you can observe updates from
|
||||
* animations. This is useful because there is no way to
|
||||
* synchronously read the value because it might be driven natively.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener
|
||||
*/
|
||||
addListener(callback: ValueListenerCallback): string {
|
||||
const id = String(_uniqueId++);
|
||||
this._listeners[id] = callback;
|
||||
if (this.__isNative) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener. The `id` param shall match the identifier
|
||||
* previously returned by `addListener()`.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener
|
||||
*/
|
||||
removeListener(id: string): void {
|
||||
delete this._listeners[id];
|
||||
if (this.__isNative && Object.keys(this._listeners).length === 0) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all registered listeners.
|
||||
*
|
||||
* See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners
|
||||
*/
|
||||
removeAllListeners(): void {
|
||||
this._listeners = {};
|
||||
if (this.__isNative) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
_startListeningToNativeValueUpdates() {
|
||||
if (this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
|
||||
'onAnimatedValueUpdate',
|
||||
data => {
|
||||
if (data.tag !== this.__getNativeTag()) {
|
||||
return;
|
||||
}
|
||||
this._updateValue(data.value, false /* flush */);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_stopListeningForNativeValueUpdates() {
|
||||
if (!this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.__nativeAnimatedValueListener.remove();
|
||||
this.__nativeAnimatedValueListener = null;
|
||||
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any running animation or tracking. `callback` is invoked with the
|
||||
* final value after stopping the animation, which is useful for updating
|
||||
@@ -259,6 +176,10 @@ class AnimatedValue extends AnimatedWithChildren {
|
||||
this._value = this._startingValue;
|
||||
}
|
||||
|
||||
_onAnimatedValueUpdateReceived(value: number): void {
|
||||
this._updateValue(value, false /*flush*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates the value before updating the property, e.g. mapping 0-1 to
|
||||
* 0-10.
|
||||
@@ -321,9 +242,7 @@ class AnimatedValue extends AnimatedWithChildren {
|
||||
if (flush) {
|
||||
_flush(this);
|
||||
}
|
||||
for (const key in this._listeners) {
|
||||
this._listeners[key]({value: this.__getValue()});
|
||||
}
|
||||
super.__callListeners(this.__getValue());
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
|
||||
@@ -14,7 +14,7 @@ const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
type ValueXYListenerCallback = (value: {x: number, y: number}) => void;
|
||||
type ValueXYListenerCallback = (value: {x: number, y: number}) => mixed;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class AnimatedWithChildren extends AnimatedNode {
|
||||
);
|
||||
}
|
||||
}
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__addChild(child: AnimatedNode): void {
|
||||
@@ -69,6 +70,17 @@ class AnimatedWithChildren extends AnimatedNode {
|
||||
__getChildren(): Array<AnimatedNode> {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
__callListeners(value: number): void {
|
||||
super.__callListeners(value);
|
||||
if (!this.__isNative) {
|
||||
for (const child of this._children) {
|
||||
if (child.__getValue) {
|
||||
child.__callListeners(child.__getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedWithChildren;
|
||||
|
||||
Reference in New Issue
Block a user