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

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.animated;
/**
* Interface used to listen to {@link ValueAnimatedNode} updates.
*/
public interface AnimatedNodeValueListener {
void onValueUpdate(double value);
}

View File

@@ -7,8 +7,8 @@ android_library(
]),
deps = [
react_native_target('java/com/facebook/react/bridge:bridge'),
react_native_target('java/com/facebook/react/modules/core:core'),
react_native_target('java/com/facebook/react/uimanager:uimanager'),
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
react_native_dep('third-party/java/jsr-305:jsr-305'),

View File

@@ -12,6 +12,7 @@ package com.facebook.react.animated;
import javax.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.OnBatchCompleteListener;
@@ -19,6 +20,8 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.GuardedChoreographerFrameCallback;
import com.facebook.react.uimanager.ReactChoreographer;
import com.facebook.react.uimanager.UIImplementation;
@@ -190,6 +193,36 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
});
}
@ReactMethod
public void startListeningToAnimatedNodeValue(final int tag) {
final AnimatedNodeValueListener listener = new AnimatedNodeValueListener() {
public void onValueUpdate(double value) {
WritableMap onAnimatedValueData = Arguments.createMap();
onAnimatedValueData.putInt("tag", tag);
onAnimatedValueData.putDouble("value", value);
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onAnimatedValueUpdate", onAnimatedValueData);
}
};
mOperations.add(new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
animatedNodesManager.startListeningToAnimatedNodeValue(tag, listener);
}
});
}
@ReactMethod
public void stopListeningToAnimatedNodeValue(final int tag) {
mOperations.add(new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
animatedNodesManager.stopListeningToAnimatedNodeValue(tag);
}
});
}
@ReactMethod
public void dropAnimatedNode(final int tag) {
mOperations.add(new UIThreadOperation() {

View File

@@ -89,6 +89,24 @@ import javax.annotation.Nullable;
mAnimatedNodes.remove(tag);
}
public void startListeningToAnimatedNodeValue(int tag, AnimatedNodeValueListener listener) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException("Animated node with tag " + tag +
" does not exists or is not a 'value' node");
}
((ValueAnimatedNode) node).setValueListener(listener);
}
public void stopListeningToAnimatedNodeValue(int tag) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
throw new JSApplicationIllegalArgumentException("Animated node with tag " + tag +
" does not exists or is not a 'value' node");
}
((ValueAnimatedNode) node).setValueListener(null);
}
public void setAnimatedNodeValue(int tag, double value) {
AnimatedNode node = mAnimatedNodes.get(tag);
if (node == null || !(node instanceof ValueAnimatedNode)) {
@@ -324,6 +342,10 @@ import javax.annotation.Nullable;
// Send property updates to native view manager
((PropsAnimatedNode) nextNode).updateView(mUIImplementation);
}
if (nextNode instanceof ValueAnimatedNode) {
// Potentially send events to JS when the node's value is updated
((ValueAnimatedNode) nextNode).onValueUpdate();
}
if (nextNode.mChildren != null) {
for (int i = 0; i < nextNode.mChildren.size(); i++) {
AnimatedNode child = nextNode.mChildren.get(i);

View File

@@ -11,13 +11,15 @@ package com.facebook.react.animated;
import com.facebook.react.bridge.ReadableMap;
import javax.annotation.Nullable;
/**
* Basic type of animated node that maps directly from {@code Animated.Value(x)} of Animated.js
* library.
*/
/*package*/ class ValueAnimatedNode extends AnimatedNode {
/*package*/ double mValue = Double.NaN;
private @Nullable AnimatedNodeValueListener mValueListener;
public ValueAnimatedNode() {
// empty constructor that can be used by subclasses
@@ -26,4 +28,15 @@ import com.facebook.react.bridge.ReadableMap;
public ValueAnimatedNode(ReadableMap config) {
mValue = config.getDouble("value");
}
public void onValueUpdate() {
if (mValueListener == null) {
return;
}
mValueListener.onValueUpdate(mValue);
}
public void setValueListener(@Nullable AnimatedNodeValueListener listener) {
mValueListener = listener;
}
}