Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Libraries/Animated/src/__tests__/Animated-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
95 changes: 95 additions & 0 deletions Libraries/Animated/src/nodes/AnimatedNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -36,11 +43,99 @@ 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 (Object.keys(this._listeners).length) {
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 && 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._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(
Expand Down
91 changes: 5 additions & 86 deletions Libraries/Animated/src/nodes/AnimatedValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -321,9 +242,7 @@ class AnimatedValue extends AnimatedWithChildren {
if (flush) {
_flush(this);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot call this._callListeners because property _callListeners is missing in AnimatedValue [1].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it's in superclass

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot call this._callListeners because property _callListeners is missing in AnimatedValue [1].

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot call this._callListeners because property _callListeners is missing in AnimatedValue [1].

}
for (const key in this._listeners) {
this._listeners[key]({value: this.__getValue()});
}
super._callListeners(this.__getValue());
}

__getNativeConfig(): Object {
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Animated/src/nodes/AnimatedValueXY.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
12 changes: 12 additions & 0 deletions Libraries/Animated/src/nodes/AnimatedWithChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AnimatedWithChildren extends AnimatedNode {
);
}
}
super.__makeNative();
}

__addChild(child: AnimatedNode): void {
Expand Down Expand Up @@ -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;