mirror of
https://github.com/status-im/react-native.git
synced 2025-02-25 23:55:23 +00:00
Revert D5556439: [react-native][PR] Native Animated - Restore default values when removing props on Android
Differential Revision: D5556439 fbshipit-source-id: dc0e4c1db25ec7f3631e6f684f9497962f2adc7b
This commit is contained in:
parent
aa9a40f68f
commit
259eac8c30
@ -17,20 +17,17 @@ import com.facebook.infer.annotation.Assertions;
|
|||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
import com.facebook.react.bridge.Callback;
|
import com.facebook.react.bridge.Callback;
|
||||||
import com.facebook.react.bridge.LifecycleEventListener;
|
import com.facebook.react.bridge.LifecycleEventListener;
|
||||||
|
import com.facebook.react.bridge.OnBatchCompleteListener;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
|
||||||
import com.facebook.react.module.annotations.ReactModule;
|
import com.facebook.react.module.annotations.ReactModule;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
import com.facebook.react.modules.core.ReactChoreographer;
|
import com.facebook.react.modules.core.ReactChoreographer;
|
||||||
import com.facebook.react.uimanager.GuardedFrameCallback;
|
import com.facebook.react.uimanager.GuardedFrameCallback;
|
||||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
|
||||||
import com.facebook.react.uimanager.UIBlock;
|
|
||||||
import com.facebook.react.uimanager.UIManagerModule;
|
import com.facebook.react.uimanager.UIManagerModule;
|
||||||
import com.facebook.react.uimanager.UIManagerModuleListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that exposes interface for creating and managing animated nodes on the "native" side.
|
* Module that exposes interface for creating and managing animated nodes on the "native" side.
|
||||||
@ -75,7 +72,7 @@ import com.facebook.react.uimanager.UIManagerModuleListener;
|
|||||||
*/
|
*/
|
||||||
@ReactModule(name = NativeAnimatedModule.NAME)
|
@ReactModule(name = NativeAnimatedModule.NAME)
|
||||||
public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
||||||
LifecycleEventListener, UIManagerModuleListener {
|
OnBatchCompleteListener, LifecycleEventListener {
|
||||||
|
|
||||||
protected static final String NAME = "NativeAnimatedModule";
|
protected static final String NAME = "NativeAnimatedModule";
|
||||||
|
|
||||||
@ -83,10 +80,11 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
void execute(NativeAnimatedNodesManager animatedNodesManager);
|
void execute(NativeAnimatedNodesManager animatedNodesManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Object mOperationsCopyLock = new Object();
|
||||||
private final GuardedFrameCallback mAnimatedFrameCallback;
|
private final GuardedFrameCallback mAnimatedFrameCallback;
|
||||||
private final ReactChoreographer mReactChoreographer;
|
private final ReactChoreographer mReactChoreographer;
|
||||||
private ArrayList<UIThreadOperation> mOperations = new ArrayList<>();
|
private ArrayList<UIThreadOperation> mOperations = new ArrayList<>();
|
||||||
private ArrayList<UIThreadOperation> mPreOperations = new ArrayList<>();
|
private volatile @Nullable ArrayList<UIThreadOperation> mReadyOperations = null;
|
||||||
|
|
||||||
private @Nullable NativeAnimatedNodesManager mNodesManager;
|
private @Nullable NativeAnimatedNodesManager mNodesManager;
|
||||||
|
|
||||||
@ -97,9 +95,26 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
mAnimatedFrameCallback = new GuardedFrameCallback(reactContext) {
|
mAnimatedFrameCallback = new GuardedFrameCallback(reactContext) {
|
||||||
@Override
|
@Override
|
||||||
protected void doFrameGuarded(final long frameTimeNanos) {
|
protected void doFrameGuarded(final long frameTimeNanos) {
|
||||||
NativeAnimatedNodesManager nodesManager = getNodesManager();
|
if (mNodesManager == null) {
|
||||||
if (nodesManager.hasActiveAnimations()) {
|
UIManagerModule uiManager = getReactApplicationContext()
|
||||||
nodesManager.runUpdates(frameTimeNanos);
|
.getNativeModule(UIManagerModule.class);
|
||||||
|
mNodesManager = new NativeAnimatedNodesManager(uiManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<UIThreadOperation> operations;
|
||||||
|
synchronized (mOperationsCopyLock) {
|
||||||
|
operations = mReadyOperations;
|
||||||
|
mReadyOperations = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operations != null) {
|
||||||
|
for (int i = 0, size = operations.size(); i < size; i++) {
|
||||||
|
operations.get(i).execute(mNodesManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mNodesManager.hasActiveAnimations()) {
|
||||||
|
mNodesManager.runUpdates(frameTimeNanos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Would be great to avoid adding this callback in case there are no active animations
|
// TODO: Would be great to avoid adding this callback in case there are no active animations
|
||||||
@ -115,10 +130,7 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
ReactApplicationContext reactCtx = getReactApplicationContext();
|
getReactApplicationContext().addLifecycleEventListener(this);
|
||||||
UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class);
|
|
||||||
reactCtx.addLifecycleEventListener(this);
|
|
||||||
uiManager.addUIManagerListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -127,33 +139,25 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void willDispatchViewUpdates(final UIManagerModule uiManager) {
|
public void onBatchComplete() {
|
||||||
if (mOperations.isEmpty() && mPreOperations.isEmpty()) {
|
// Note: The order of executing onBatchComplete handler (especially in terms of onBatchComplete
|
||||||
return;
|
// from the UIManagerModule) doesn't matter as we only enqueue operations for the UI thread to
|
||||||
}
|
// be executed from here. Thanks to ReactChoreographer all the operations from here are going
|
||||||
final ArrayList<UIThreadOperation> preOperations = mPreOperations;
|
// to be executed *after* all the operations enqueued by UIManager as the callback type that we
|
||||||
final ArrayList<UIThreadOperation> operations = mOperations;
|
// use for ReactChoreographer (CallbackType.NATIVE_ANIMATED_MODULE) is run after callbacks that
|
||||||
mPreOperations = new ArrayList<>();
|
// UIManager uses.
|
||||||
|
ArrayList<UIThreadOperation> operations = mOperations.isEmpty() ? null : mOperations;
|
||||||
|
if (operations != null) {
|
||||||
mOperations = new ArrayList<>();
|
mOperations = new ArrayList<>();
|
||||||
uiManager.prependUIBlock(new UIBlock() {
|
synchronized (mOperationsCopyLock) {
|
||||||
@Override
|
if (mReadyOperations == null) {
|
||||||
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
mReadyOperations = operations;
|
||||||
NativeAnimatedNodesManager nodesManager = getNodesManager();
|
} else {
|
||||||
for (UIThreadOperation operation : preOperations) {
|
mReadyOperations.addAll(operations);
|
||||||
operation.execute(nodesManager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
uiManager.addUIBlock(new UIBlock() {
|
|
||||||
@Override
|
|
||||||
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
|
||||||
NativeAnimatedNodesManager nodesManager = getNodesManager();
|
|
||||||
for (UIThreadOperation operation : operations) {
|
|
||||||
operation.execute(nodesManager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostPause() {
|
public void onHostPause() {
|
||||||
@ -170,15 +174,6 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
return NAME;
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NativeAnimatedNodesManager getNodesManager() {
|
|
||||||
if (mNodesManager == null) {
|
|
||||||
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
|
|
||||||
mNodesManager = new NativeAnimatedNodesManager(uiManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mNodesManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearFrameCallback() {
|
private void clearFrameCallback() {
|
||||||
Assertions.assertNotNull(mReactChoreographer).removeFrameCallback(
|
Assertions.assertNotNull(mReactChoreographer).removeFrameCallback(
|
||||||
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
|
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
|
||||||
@ -191,11 +186,6 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
mAnimatedFrameCallback);
|
mAnimatedFrameCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public void setNodesManager(NativeAnimatedNodesManager nodesManager) {
|
|
||||||
mNodesManager = nodesManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void createAnimatedNode(final int tag, final ReadableMap config) {
|
public void createAnimatedNode(final int tag, final ReadableMap config) {
|
||||||
mOperations.add(new UIThreadOperation() {
|
mOperations.add(new UIThreadOperation() {
|
||||||
@ -346,12 +336,6 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void disconnectAnimatedNodeFromView(final int animatedNodeTag, final int viewTag) {
|
public void disconnectAnimatedNodeFromView(final int animatedNodeTag, final int viewTag) {
|
||||||
mPreOperations.add(new UIThreadOperation() {
|
|
||||||
@Override
|
|
||||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
||||||
animatedNodesManager.restoreDefaultValues(animatedNodeTag, viewTag);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mOperations.add(new UIThreadOperation() {
|
mOperations.add(new UIThreadOperation() {
|
||||||
@Override
|
@Override
|
||||||
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
||||||
|
@ -92,7 +92,7 @@ import javax.annotation.Nullable;
|
|||||||
} else if ("value".equals(type)) {
|
} else if ("value".equals(type)) {
|
||||||
node = new ValueAnimatedNode(config);
|
node = new ValueAnimatedNode(config);
|
||||||
} else if ("props".equals(type)) {
|
} else if ("props".equals(type)) {
|
||||||
node = new PropsAnimatedNode(config, this, mUIImplementation);
|
node = new PropsAnimatedNode(config, this);
|
||||||
} else if ("interpolation".equals(type)) {
|
} else if ("interpolation".equals(type)) {
|
||||||
node = new InterpolationAnimatedNode(config);
|
node = new InterpolationAnimatedNode(config);
|
||||||
} else if ("addition".equals(type)) {
|
} else if ("addition".equals(type)) {
|
||||||
@ -289,7 +289,11 @@ import javax.annotation.Nullable;
|
|||||||
"of type " + PropsAnimatedNode.class.getName());
|
"of type " + PropsAnimatedNode.class.getName());
|
||||||
}
|
}
|
||||||
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
||||||
propsAnimatedNode.connectToView(viewTag);
|
if (propsAnimatedNode.mConnectedViewTag != -1) {
|
||||||
|
throw new JSApplicationIllegalArgumentException("Animated node " + animatedNodeTag + " is " +
|
||||||
|
"already attached to a view");
|
||||||
|
}
|
||||||
|
propsAnimatedNode.mConnectedViewTag = viewTag;
|
||||||
mUpdatedNodes.put(animatedNodeTag, node);
|
mUpdatedNodes.put(animatedNodeTag, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,24 +308,11 @@ import javax.annotation.Nullable;
|
|||||||
"of type " + PropsAnimatedNode.class.getName());
|
"of type " + PropsAnimatedNode.class.getName());
|
||||||
}
|
}
|
||||||
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
||||||
propsAnimatedNode.disconnectFromView(viewTag);
|
if (propsAnimatedNode.mConnectedViewTag != viewTag) {
|
||||||
|
throw new JSApplicationIllegalArgumentException("Attempting to disconnect view that has " +
|
||||||
|
"not been connected with the given animated node");
|
||||||
}
|
}
|
||||||
|
propsAnimatedNode.mConnectedViewTag = -1;
|
||||||
public void restoreDefaultValues(int animatedNodeTag, int viewTag) {
|
|
||||||
AnimatedNode node = mAnimatedNodes.get(animatedNodeTag);
|
|
||||||
// Restoring default values needs to happen before UIManager operations so it is
|
|
||||||
// possible the node hasn't been created yet if it is being connected and
|
|
||||||
// disconnected in the same batch. In that case we don't need to restore
|
|
||||||
// default values since it will never actually update the view.
|
|
||||||
if (node == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(node instanceof PropsAnimatedNode)) {
|
|
||||||
throw new JSApplicationIllegalArgumentException("Animated node connected to view should be" +
|
|
||||||
"of type " + PropsAnimatedNode.class.getName());
|
|
||||||
}
|
|
||||||
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
|
||||||
propsAnimatedNode.restoreDefaultValues();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap eventMapping) {
|
public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap eventMapping) {
|
||||||
@ -522,7 +513,7 @@ import javax.annotation.Nullable;
|
|||||||
if (nextNode instanceof PropsAnimatedNode) {
|
if (nextNode instanceof PropsAnimatedNode) {
|
||||||
// Send property updates to native view manager
|
// Send property updates to native view manager
|
||||||
try {
|
try {
|
||||||
((PropsAnimatedNode) nextNode).updateView();
|
((PropsAnimatedNode) nextNode).updateView(mUIImplementation);
|
||||||
} catch (IllegalViewOperationException e) {
|
} catch (IllegalViewOperationException e) {
|
||||||
// An exception is thrown if the view hasn't been created yet. This can happen because views are
|
// An exception is thrown if the view hasn't been created yet. This can happen because views are
|
||||||
// created in batches. If this particular view didn't make it into a batch yet, the view won't
|
// created in batches. If this particular view didn't make it into a batch yet, the view won't
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
package com.facebook.react.animated;
|
package com.facebook.react.animated;
|
||||||
|
|
||||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|
||||||
import com.facebook.react.bridge.JavaOnlyMap;
|
import com.facebook.react.bridge.JavaOnlyMap;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
@ -28,78 +27,47 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
/*package*/ class PropsAnimatedNode extends AnimatedNode {
|
/*package*/ class PropsAnimatedNode extends AnimatedNode {
|
||||||
|
|
||||||
private int mConnectedViewTag = -1;
|
/*package*/ int mConnectedViewTag = -1;
|
||||||
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
|
|
||||||
private final UIImplementation mUIImplementation;
|
|
||||||
private final Map<String, Integer> mPropNodeMapping;
|
|
||||||
// This is the backing map for `mDiffMap` we can mutate this to update it instead of having to
|
|
||||||
// create a new one for each update.
|
|
||||||
private final JavaOnlyMap mPropMap;
|
|
||||||
private final ReactStylesDiffMap mDiffMap;
|
|
||||||
|
|
||||||
PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager, UIImplementation uiImplementation) {
|
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
|
||||||
|
private final Map<String, Integer> mPropMapping;
|
||||||
|
|
||||||
|
PropsAnimatedNode(ReadableMap config, NativeAnimatedNodesManager nativeAnimatedNodesManager) {
|
||||||
ReadableMap props = config.getMap("props");
|
ReadableMap props = config.getMap("props");
|
||||||
ReadableMapKeySetIterator iter = props.keySetIterator();
|
ReadableMapKeySetIterator iter = props.keySetIterator();
|
||||||
mPropNodeMapping = new HashMap<>();
|
mPropMapping = new HashMap<>();
|
||||||
while (iter.hasNextKey()) {
|
while (iter.hasNextKey()) {
|
||||||
String propKey = iter.nextKey();
|
String propKey = iter.nextKey();
|
||||||
int nodeIndex = props.getInt(propKey);
|
int nodeIndex = props.getInt(propKey);
|
||||||
mPropNodeMapping.put(propKey, nodeIndex);
|
mPropMapping.put(propKey, nodeIndex);
|
||||||
}
|
}
|
||||||
mPropMap = new JavaOnlyMap();
|
|
||||||
mDiffMap = new ReactStylesDiffMap(mPropMap);
|
|
||||||
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
|
mNativeAnimatedNodesManager = nativeAnimatedNodesManager;
|
||||||
mUIImplementation = uiImplementation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connectToView(int viewTag) {
|
public final void updateView(UIImplementation uiImplementation) {
|
||||||
if (mConnectedViewTag != -1) {
|
|
||||||
throw new JSApplicationIllegalArgumentException("Animated node " + mTag + " is " +
|
|
||||||
"already attached to a view");
|
|
||||||
}
|
|
||||||
mConnectedViewTag = viewTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disconnectFromView(int viewTag) {
|
|
||||||
if (mConnectedViewTag != viewTag) {
|
|
||||||
throw new JSApplicationIllegalArgumentException("Attempting to disconnect view that has " +
|
|
||||||
"not been connected with the given animated node");
|
|
||||||
}
|
|
||||||
|
|
||||||
mConnectedViewTag = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restoreDefaultValues() {
|
|
||||||
ReadableMapKeySetIterator it = mPropMap.keySetIterator();
|
|
||||||
while(it.hasNextKey()) {
|
|
||||||
mPropMap.putNull(it.nextKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
mUIImplementation.synchronouslyUpdateViewOnUIThread(
|
|
||||||
mConnectedViewTag,
|
|
||||||
mDiffMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void updateView() {
|
|
||||||
if (mConnectedViewTag == -1) {
|
if (mConnectedViewTag == -1) {
|
||||||
return;
|
throw new IllegalStateException("Node has not been attached to a view");
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, Integer> entry : mPropNodeMapping.entrySet()) {
|
JavaOnlyMap propsMap = new JavaOnlyMap();
|
||||||
|
for (Map.Entry<String, Integer> entry : mPropMapping.entrySet()) {
|
||||||
@Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue());
|
@Nullable AnimatedNode node = mNativeAnimatedNodesManager.getNodeById(entry.getValue());
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
throw new IllegalArgumentException("Mapped property node does not exists");
|
throw new IllegalArgumentException("Mapped property node does not exists");
|
||||||
} else if (node instanceof StyleAnimatedNode) {
|
} else if (node instanceof StyleAnimatedNode) {
|
||||||
((StyleAnimatedNode) node).collectViewUpdates(mPropMap);
|
((StyleAnimatedNode) node).collectViewUpdates(propsMap);
|
||||||
} else if (node instanceof ValueAnimatedNode) {
|
} else if (node instanceof ValueAnimatedNode) {
|
||||||
mPropMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue());
|
propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue());
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unsupported type of node used in property node " +
|
throw new IllegalArgumentException("Unsupported type of node used in property node " +
|
||||||
node.getClass());
|
node.getClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Reuse propsMap and stylesDiffMap objects - note that in subsequent animation steps
|
||||||
mUIImplementation.synchronouslyUpdateViewOnUIThread(
|
// for a given node most of the time we will be creating the same set of props (just with
|
||||||
|
// different values). We can take advantage on that and optimize the way we allocate property
|
||||||
|
// maps (we also know that updating view props doesn't retain a reference to the styles object).
|
||||||
|
uiImplementation.synchronouslyUpdateViewOnUIThread(
|
||||||
mConnectedViewTag,
|
mConnectedViewTag,
|
||||||
mDiffMap);
|
new ReactStylesDiffMap(propsMap));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -881,10 +881,6 @@ public class UIImplementation {
|
|||||||
mOperationsQueue.enqueueUIBlock(block);
|
mOperationsQueue.enqueueUIBlock(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prependUIBlock(UIBlock block) {
|
|
||||||
mOperationsQueue.prependUIBlock(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int resolveRootTagFromReactTag(int reactTag) {
|
public int resolveRootTagFromReactTag(int reactTag) {
|
||||||
if (mShadowNodeRegistry.isRootNode(reactTag)) {
|
if (mShadowNodeRegistry.isRootNode(reactTag)) {
|
||||||
return reactTag;
|
return reactTag;
|
||||||
|
@ -11,7 +11,6 @@ package com.facebook.react.uimanager;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -86,7 +85,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
|
|||||||
private final Map<String, Object> mModuleConstants;
|
private final Map<String, Object> mModuleConstants;
|
||||||
private final UIImplementation mUIImplementation;
|
private final UIImplementation mUIImplementation;
|
||||||
private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();
|
private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();
|
||||||
private final List<UIManagerModuleListener> mListeners = new ArrayList<>();
|
|
||||||
|
|
||||||
private int mNextRootViewTag = 1;
|
private int mNextRootViewTag = 1;
|
||||||
private int mBatchId = 0;
|
private int mBatchId = 0;
|
||||||
@ -535,9 +533,6 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
|
|||||||
SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchCompleteUI")
|
SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchCompleteUI")
|
||||||
.arg("BatchId", batchId)
|
.arg("BatchId", batchId)
|
||||||
.flush();
|
.flush();
|
||||||
for (UIManagerModuleListener listener : mListeners) {
|
|
||||||
listener.willDispatchViewUpdates(this);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
mUIImplementation.dispatchViewUpdates(batchId);
|
mUIImplementation.dispatchViewUpdates(batchId);
|
||||||
} finally {
|
} finally {
|
||||||
@ -575,28 +570,10 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
public void addUIBlock(UIBlock block) {
|
public void addUIBlock (UIBlock block) {
|
||||||
mUIImplementation.addUIBlock(block);
|
mUIImplementation.addUIBlock(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedule a block to be executed on the UI thread. Useful if you need to execute
|
|
||||||
* view logic before all currently queued view updates have completed.
|
|
||||||
*
|
|
||||||
* @param block that contains UI logic you want to execute.
|
|
||||||
*/
|
|
||||||
public void prependUIBlock(UIBlock block) {
|
|
||||||
mUIImplementation.prependUIBlock(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addUIManagerListener(UIManagerModuleListener listener) {
|
|
||||||
mListeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeUIManagerListener(UIManagerModuleListener listener) {
|
|
||||||
mListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a reactTag from a component, find its root node tag, if possible.
|
* Given a reactTag from a component, find its root node tag, if possible.
|
||||||
* Otherwise, this will return 0. If the reactTag belongs to a root node, this
|
* Otherwise, this will return 0. If the reactTag belongs to a root node, this
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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.uimanager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener used to hook into the UIManager update process.
|
|
||||||
*/
|
|
||||||
public interface UIManagerModuleListener {
|
|
||||||
/**
|
|
||||||
* Called right before view updates are dispatched at the end of a batch. This is useful if a
|
|
||||||
* module needs to add UIBlocks to the queue before it is flushed.
|
|
||||||
*/
|
|
||||||
void willDispatchViewUpdates(UIManagerModule uiManager);
|
|
||||||
}
|
|
@ -725,10 +725,6 @@ public class UIViewOperationQueue {
|
|||||||
mOperations.add(new UIBlockOperation(block));
|
mOperations.add(new UIBlockOperation(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prependUIBlock(UIBlock block) {
|
|
||||||
mOperations.add(0, new UIBlockOperation(block));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ void dispatchViewUpdates(final int batchId) {
|
/* package */ void dispatchViewUpdates(final int batchId) {
|
||||||
SystraceMessage.beginSection(
|
SystraceMessage.beginSection(
|
||||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||||
|
@ -19,7 +19,6 @@ rn_robolectric_test(
|
|||||||
react_native_target("java/com/facebook/react/animated:animated"),
|
react_native_target("java/com/facebook/react/animated:animated"),
|
||||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||||
react_native_target("java/com/facebook/react/common:common"),
|
react_native_target("java/com/facebook/react/common:common"),
|
||||||
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:uimanager"),
|
||||||
react_native_tests_target("java/com/facebook/react/bridge:testhelpers"),
|
react_native_tests_target("java/com/facebook/react/bridge:testhelpers"),
|
||||||
],
|
],
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.modules.core.ReactChoreographer;
|
|
||||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
|
||||||
import com.facebook.react.uimanager.UIBlock;
|
|
||||||
import com.facebook.react.uimanager.UIManagerModule;
|
|
||||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.InOrder;
|
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
import org.powermock.api.mockito.PowerMockito;
|
|
||||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
|
||||||
import org.powermock.core.classloader.annotations.PrepareEverythingForTest;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.inOrder;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
@PrepareEverythingForTest
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
|
|
||||||
public class NativeAnimatedModuleTest {
|
|
||||||
private NativeAnimatedModule mNativeAnimatedModule;
|
|
||||||
private NativeAnimatedNodesManager mNodeManager;
|
|
||||||
private UIManagerModule mUIManager;
|
|
||||||
private List<UIBlock> mUIBlocks;
|
|
||||||
|
|
||||||
private void flushUIBlocks() {
|
|
||||||
mNativeAnimatedModule.willDispatchViewUpdates(mUIManager);
|
|
||||||
for (UIBlock block : mUIBlocks) {
|
|
||||||
block.execute(mock(NativeViewHierarchyManager.class));
|
|
||||||
}
|
|
||||||
mUIBlocks.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
ReactApplicationContext context = mock(ReactApplicationContext.class);
|
|
||||||
mUIManager = mock(UIManagerModule.class);
|
|
||||||
PowerMockito.doReturn(mock(EventDispatcher.class)).when(mUIManager).getEventDispatcher();
|
|
||||||
PowerMockito.doAnswer(new Answer<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void answer(InvocationOnMock invocation) throws Throwable {
|
|
||||||
mUIBlocks.add(invocation.getArgumentAt(0, UIBlock.class));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).when(mUIManager).addUIBlock(any(UIBlock.class));
|
|
||||||
PowerMockito.doAnswer(new Answer<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void answer(InvocationOnMock invocation) throws Throwable {
|
|
||||||
mUIBlocks.add(0, invocation.getArgumentAt(0, UIBlock.class));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).when(mUIManager).prependUIBlock(any(UIBlock.class));
|
|
||||||
PowerMockito.doReturn(mUIManager).when(context).getNativeModule(UIManagerModule.class);
|
|
||||||
|
|
||||||
ReactChoreographer.initialize();
|
|
||||||
mNativeAnimatedModule = new NativeAnimatedModule(context);
|
|
||||||
mNativeAnimatedModule.initialize();
|
|
||||||
mNodeManager = mock(NativeAnimatedNodesManager.class);
|
|
||||||
mNativeAnimatedModule.setNodesManager(mNodeManager);
|
|
||||||
mUIBlocks = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNodeManagerSchedulingWaitsForFlush() {
|
|
||||||
// Make sure operations are not executed until UIManager has flushed it's queue.
|
|
||||||
mNativeAnimatedModule.connectAnimatedNodeToView(1, 1000);
|
|
||||||
verify(mNodeManager, never()).connectAnimatedNodeToView(anyInt(), anyInt());
|
|
||||||
flushUIBlocks();
|
|
||||||
verify(mNodeManager, times(1)).connectAnimatedNodeToView(anyInt(), anyInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNodeManagerSchedulingOperation() {
|
|
||||||
UIBlock otherBlock = mock(UIBlock.class);
|
|
||||||
mUIManager.addUIBlock(otherBlock);
|
|
||||||
mNativeAnimatedModule.connectAnimatedNodeToView(1, 1000);
|
|
||||||
flushUIBlocks();
|
|
||||||
|
|
||||||
// Connect should be called after other UI operations.
|
|
||||||
InOrder inOrder = inOrder(mNodeManager, otherBlock);
|
|
||||||
inOrder.verify(otherBlock, times(1)).execute(any(NativeViewHierarchyManager.class));
|
|
||||||
inOrder.verify(mNodeManager, times(1)).connectAnimatedNodeToView(1, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNodeManagerSchedulingPreOperation() {
|
|
||||||
UIBlock otherBlock = mock(UIBlock.class);
|
|
||||||
mUIManager.addUIBlock(otherBlock);
|
|
||||||
mNativeAnimatedModule.disconnectAnimatedNodeFromView(1, 1000);
|
|
||||||
flushUIBlocks();
|
|
||||||
|
|
||||||
// Disconnect should be called before other UI operations.
|
|
||||||
InOrder inOrder = inOrder(mNodeManager, otherBlock);
|
|
||||||
inOrder.verify(mNodeManager, times(1)).restoreDefaultValues(1, 1000);
|
|
||||||
inOrder.verify(otherBlock, times(1)).execute(any(NativeViewHierarchyManager.class));
|
|
||||||
inOrder.verify(mNodeManager, times(1)).disconnectAnimatedNodeFromView(1, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
@ -992,51 +992,4 @@ public class NativeAnimatedNodeTraversalTest {
|
|||||||
verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture());
|
verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture());
|
||||||
assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(10);
|
assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRestoreDefaultProps() {
|
|
||||||
int viewTag = 1000;
|
|
||||||
int propsNodeTag = 3;
|
|
||||||
mNativeAnimatedNodesManager.createAnimatedNode(
|
|
||||||
1,
|
|
||||||
JavaOnlyMap.of("type", "value", "value", 1d, "offset", 0d));
|
|
||||||
mNativeAnimatedNodesManager.createAnimatedNode(
|
|
||||||
2,
|
|
||||||
JavaOnlyMap.of("type", "style", "style", JavaOnlyMap.of("opacity", 1)));
|
|
||||||
mNativeAnimatedNodesManager.createAnimatedNode(
|
|
||||||
propsNodeTag,
|
|
||||||
JavaOnlyMap.of("type", "props", "props", JavaOnlyMap.of("style", 2)));
|
|
||||||
mNativeAnimatedNodesManager.connectAnimatedNodes(1, 2);
|
|
||||||
mNativeAnimatedNodesManager.connectAnimatedNodes(2, propsNodeTag);
|
|
||||||
mNativeAnimatedNodesManager.connectAnimatedNodeToView(propsNodeTag, viewTag);
|
|
||||||
|
|
||||||
JavaOnlyArray frames = JavaOnlyArray.of(0d, 0.5d, 1d);
|
|
||||||
Callback animationCallback = mock(Callback.class);
|
|
||||||
mNativeAnimatedNodesManager.startAnimatingNode(
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
JavaOnlyMap.of("type", "frames", "frames", frames, "toValue", 0d),
|
|
||||||
animationCallback);
|
|
||||||
|
|
||||||
ArgumentCaptor<ReactStylesDiffMap> stylesCaptor =
|
|
||||||
ArgumentCaptor.forClass(ReactStylesDiffMap.class);
|
|
||||||
|
|
||||||
reset(mUIImplementationMock);
|
|
||||||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime());
|
|
||||||
verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture());
|
|
||||||
assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(1);
|
|
||||||
|
|
||||||
for (int i = 0; i < frames.size(); i++) {
|
|
||||||
reset(mUIImplementationMock);
|
|
||||||
mNativeAnimatedNodesManager.runUpdates(nextFrameTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture());
|
|
||||||
assertThat(stylesCaptor.getValue().getDouble("opacity", Double.NaN)).isEqualTo(0);
|
|
||||||
|
|
||||||
reset(mUIImplementationMock);
|
|
||||||
mNativeAnimatedNodesManager.restoreDefaultValues(propsNodeTag, viewTag);
|
|
||||||
verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture());
|
|
||||||
assertThat(stylesCaptor.getValue().isNull("opacity"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user