Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
163 changes: 89 additions & 74 deletions Libraries/Animated/src/AnimatedImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ class Animated {
__getNativeConfig(): Object {
throw new Error('This JS animated node type cannot be used as native animated node');
}
// Called before listeners are triggered.
// Should update internal value so __getValue returns the most recently received value
__updateValueFromListener(data: Object): void {}
toJSON(): any { return this.__getValue(); }
}

Expand Down Expand Up @@ -144,10 +147,13 @@ class Animation {

class AnimatedWithChildren extends Animated {
_children: Array<Animated>;
_listeners: {[key: string]: AnimatedListenerCallback};
__nativeAnimatedValueListener: ?any;

constructor() {
super();
this._children = [];
this._listeners = {};
}

__makeNative() {
Expand All @@ -157,6 +163,9 @@ class AnimatedWithChildren extends Animated {
child.__makeNative();
NativeAnimatedAPI.connectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
}
if (Object.keys(this._listeners).length) {
this._startListeningToNativeValueUpdates();
}
}
}

Expand Down Expand Up @@ -190,6 +199,74 @@ class AnimatedWithChildren extends Animated {
__getChildren(): Array<Animated> {
return this._children;
}

addListener(callback: AnimatedListenerCallback): 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(): void {
if (this.__nativeAnimatedValueListener) {
return;
}

NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
'onAnimatedValueUpdate',
(data) => {
if (data.tag !== this.__getNativeTag()) {
return;
}

this.__updateValueFromListener(data);
// Native 'onAnimatedValueUpdate' is triggered on all child nodes,
// so we don't need event propagation
this.triggerListeners(false /*eventPropagation*/);
}
);
}

/* Typically used internally */
triggerListeners(eventPropagation?: bool = true): void {
for (var key in this._listeners) {

Choose a reason for hiding this comment

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

keyword-spacing: Expected space(s) after "if".

Choose a reason for hiding this comment

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

keyword-spacing: Expected space(s) after "if".

this._listeners[key]({value: this.__getValue()});
}

Choose a reason for hiding this comment

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

keyword-spacing: Expected space(s) after "if".

Choose a reason for hiding this comment

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

keyword-spacing: Expected space(s) after "if".

if (eventPropagation) {
for (var child of this._children) {
if (child instanceof AnimatedWithChildren) {
child.triggerListeners();
}
}
}
}

_stopListeningForNativeValueUpdates() {
if (!this.__nativeAnimatedValueListener) {
return;
}

this.__nativeAnimatedValueListener.remove();
this.__nativeAnimatedValueListener = null;
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
}
}

/**
Expand Down Expand Up @@ -683,7 +760,7 @@ class SpringAnimation extends Animation {
}
}

type ValueListenerCallback = (state: {value: number}) => void;
type AnimatedListenerCallback = (state: Object) => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason for this change? I'd rather keep the more precise flow type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the callbacks are now added by the parent class (AnimatedWithChildren), the arguments are now up to the extending classes, which means AnimatedValueXY takes {x: number, y:number} while AnimatedValue takes {value: number}.
I'd like to keep the types but I'm not familiar enough with Flow to know how that would work. Should I investigate further?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, does keeping (state: {value: number}) => void in AnimatedWithChildren and using a different type for AnimatedValueXY works? If not I don't really have a better idea :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, after the flow fixes your suggestion may work (listeners for AnimatedValue and ~XY were decoupled). I'll brb with a fix and change the name back as well, to keep the changes minimal.


var _uniqueId = 1;

Expand All @@ -698,15 +775,12 @@ class AnimatedValue extends AnimatedWithChildren {
_offset: number;
_animation: ?Animation;
_tracking: ?Animated;
_listeners: {[key: string]: ValueListenerCallback};
__nativeAnimatedValueListener: ?any;

constructor(value: number) {
super();
this._value = value;
this._offset = 0;
this._animation = null;
this._listeners = {};
}

__detach() {
Expand All @@ -720,10 +794,6 @@ class AnimatedValue extends AnimatedWithChildren {

__makeNative() {
super.__makeNative();

if (Object.keys(this._listeners).length) {
this._startListeningToNativeValueUpdates();
}
}

/**
Expand Down Expand Up @@ -779,61 +849,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.
*/
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) {
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

Choose a reason for hiding this comment

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

parameter data Missing annotation

Choose a reason for hiding this comment

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

parameter data Missing annotation

* final value after stopping the animation, which is useful for updating
Expand Down Expand Up @@ -901,14 +916,16 @@ class AnimatedValue extends AnimatedWithChildren {
this._tracking = tracking;
}

__updateValueFromListener(data: Object): void {
this._value = data.value;
}

_updateValue(value: number, flush: bool): void {
this._value = value;
if (flush) {
_flush(this);
}
for (var key in this._listeners) {
this._listeners[key]({value: this.__getValue()});
}
this.triggerListeners();
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's prefix this method with an underscore since it's not really public API.

}

__getNativeConfig(): Object {
Expand All @@ -920,8 +937,6 @@ class AnimatedValue extends AnimatedWithChildren {
}
}

type ValueXYListenerCallback = (value: {x: number, y: number}) => void;

/**
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* API to normal `Animated.Value`, but multiplexed. Contains two regular
Expand Down Expand Up @@ -965,7 +980,7 @@ type ValueXYListenerCallback = (value: {x: number, y: number}) => void;
class AnimatedValueXY extends AnimatedWithChildren {
x: AnimatedValue;
y: AnimatedValue;
_listeners: {[key: string]: {x: string, y: string}};
_jointListeners: {[key: string]: {x: string, y: string}};

constructor(valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue}) {
super();
Expand All @@ -983,7 +998,7 @@ class AnimatedValueXY extends AnimatedWithChildren {
this.x = value.x;
this.y = value.y;
}
this._listeners = {};
this._jointListeners = {};
}

setValue(value: {x: number, y: number}) {
Expand Down Expand Up @@ -1019,12 +1034,12 @@ class AnimatedValueXY extends AnimatedWithChildren {
callback && callback(this.__getValue());
}

addListener(callback: ValueXYListenerCallback): string {
addListener(callback: AnimatedListenerCallback): string {
var id = String(_uniqueId++);
var jointCallback = ({value: number}) => {
callback(this.__getValue());
};
this._listeners[id] = {
this._jointListeners[id] = {
x: this.x.addListener(jointCallback),
y: this.y.addListener(jointCallback),
};
Expand All @@ -1034,13 +1049,13 @@ class AnimatedValueXY extends AnimatedWithChildren {
removeListener(id: string): void {
this.x.removeListener(this._listeners[id].x);
this.y.removeListener(this._listeners[id].y);
delete this._listeners[id];
delete this._jointListeners[id];
}

removeAllListeners(): void {
this.x.removeAllListeners();
this.y.removeAllListeners();
this._listeners = {};
this._jointListeners = {};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class NavigationCardStackPanResponder extends NavigationAbstractPanResponder {
).start();
}

_addNativeListener(animatedValue) {
_addNativeListener(animatedValue: Object) {
if (!animatedValue.__isNative) {
return;
}
Expand Down