Files
react-native/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java
T
Ahmed El-Helw fc10d0d3bf Fix modals freezing on close with Nodes
Summary:
Modals were doing nothing (and sometimes crashing) when they were
being closed. The reason for this was due to the fact that the parent being
removed was not necessarily the view's parent. Consequently, trying to inform
said parent that its child was removed failed, because said parent wasn't a
view, and therefore had no record in mViewsToTags.

Reviewed By: sriramramani

Differential Revision: D3928850
2016-12-19 13:40:34 -08:00

531 lines
16 KiB
Java

/**
* 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.flat;
import javax.annotation.Nullable;
import java.util.ArrayList;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.NoSuchNativeViewException;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.TouchTargetHelper;
import com.facebook.react.uimanager.UIViewOperationQueue;
/**
* FlatUIViewOperationQueue extends {@link UIViewOperationQueue} to add
* FlatUIImplementation-specific methods that need to run in UI thread.
*/
/* package */ final class FlatUIViewOperationQueue extends UIViewOperationQueue {
private static final int[] MEASURE_BUFFER = new int[4];
private final FlatNativeViewHierarchyManager mNativeViewHierarchyManager;
private final ProcessLayoutRequests mProcessLayoutRequests = new ProcessLayoutRequests();
private final class ProcessLayoutRequests implements UIOperation {
@Override
public void execute() {
FlatViewGroup.processLayoutRequests();
}
}
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
*/
private final class UpdateMountState implements UIOperation {
private final int mReactTag;
private final @Nullable DrawCommand[] mDrawCommands;
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
private final @Nullable NodeRegion[] mNodeRegions;
private UpdateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) {
mReactTag = reactTag;
mDrawCommands = drawCommands;
mAttachDetachListeners = listeners;
mNodeRegions = nodeRegions;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateMountState(
mReactTag,
mDrawCommands,
mAttachDetachListeners,
mNodeRegions);
}
}
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
*/
private final class UpdateClippingMountState implements UIOperation {
private final int mReactTag;
private final @Nullable DrawCommand[] mDrawCommands;
private final SparseIntArray mDrawViewIndexMap;
private final float[] mCommandMaxBot;
private final float[] mCommandMinTop;
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
private final @Nullable NodeRegion[] mNodeRegions;
private final float[] mRegionMaxBot;
private final float[] mRegionMinTop;
private final boolean mWillMountViews;
private UpdateClippingMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] commandMaxBot,
float[] commandMinTop,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions,
float[] regionMaxBot,
float[] regionMinTop,
boolean willMountViews) {
mReactTag = reactTag;
mDrawCommands = drawCommands;
mDrawViewIndexMap = drawViewIndexMap;
mCommandMaxBot = commandMaxBot;
mCommandMinTop = commandMinTop;
mAttachDetachListeners = listeners;
mNodeRegions = nodeRegions;
mRegionMaxBot = regionMaxBot;
mRegionMinTop = regionMinTop;
mWillMountViews = willMountViews;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateClippingMountState(
mReactTag,
mDrawCommands,
mDrawViewIndexMap,
mCommandMaxBot,
mCommandMinTop,
mAttachDetachListeners,
mNodeRegions,
mRegionMaxBot,
mRegionMinTop,
mWillMountViews);
}
}
private final class UpdateViewGroup implements UIOperation {
private final int mReactTag;
private final int[] mViewsToAdd;
private final int[] mViewsToDetach;
private UpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
mReactTag = reactTag;
mViewsToAdd = viewsToAdd;
mViewsToDetach = viewsToDetach;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateViewGroup(mReactTag, mViewsToAdd, mViewsToDetach);
}
}
/**
* UIOperation that updates View bounds for a View defined by reactTag.
*/
public final class UpdateViewBounds implements UIOperation {
private final int mReactTag;
private final int mLeft;
private final int mTop;
private final int mRight;
private final int mBottom;
private UpdateViewBounds(int reactTag, int left, int top, int right, int bottom) {
mReactTag = reactTag;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateViewBounds(mReactTag, mLeft, mTop, mRight, mBottom);
}
}
private final class SetPadding implements UIOperation {
private final int mReactTag;
private final int mPaddingLeft;
private final int mPaddingTop;
private final int mPaddingRight;
private final int mPaddingBottom;
private SetPadding(
int reactTag,
int paddingLeft,
int paddingTop,
int paddingRight,
int paddingBottom) {
mReactTag = reactTag;
mPaddingLeft = paddingLeft;
mPaddingTop = paddingTop;
mPaddingRight = paddingRight;
mPaddingBottom = paddingBottom;
}
@Override
public void execute() {
mNativeViewHierarchyManager.setPadding(
mReactTag,
mPaddingLeft,
mPaddingTop,
mPaddingRight,
mPaddingBottom);
}
}
private final class DropViews implements UIOperation {
private final SparseIntArray mViewsToDrop;
private DropViews(ArrayList<Integer> viewsToDrop, ArrayList<Integer> parentsForViewsToDrop) {
SparseIntArray sparseIntArray = new SparseIntArray();
for (int i = 0, count = viewsToDrop.size(); i < count; i++) {
sparseIntArray.put(viewsToDrop.get(i), parentsForViewsToDrop.get(i));
}
mViewsToDrop = sparseIntArray;
}
@Override
public void execute() {
mNativeViewHierarchyManager.dropViews(mViewsToDrop);
}
}
private final class MeasureVirtualView implements UIOperation {
private final int mReactTag;
private final float mScaledX;
private final float mScaledY;
private final float mScaledWidth;
private final float mScaledHeight;
private final Callback mCallback;
private final boolean mRelativeToWindow;
private MeasureVirtualView(
int reactTag,
float scaledX,
float scaledY,
float scaledWidth,
float scaledHeight,
boolean relativeToWindow,
Callback callback) {
mReactTag = reactTag;
mScaledX = scaledX;
mScaledY = scaledY;
mScaledWidth = scaledWidth;
mScaledHeight = scaledHeight;
mCallback = callback;
mRelativeToWindow = relativeToWindow;
}
@Override
public void execute() {
try {
// Measure native View
if (mRelativeToWindow) {
// relative to the window
mNativeViewHierarchyManager.measureInWindow(mReactTag, MEASURE_BUFFER);
} else {
// relative to the root view
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
}
} catch (NoSuchNativeViewException noSuchNativeViewException) {
// Invoke with no args to signal failure and to allow JS to clean up the callback
// handle.
mCallback.invoke();
return;
}
float nativeViewX = MEASURE_BUFFER[0];
float nativeViewY = MEASURE_BUFFER[1];
float nativeViewWidth = MEASURE_BUFFER[2];
float nativeViewHeight = MEASURE_BUFFER[3];
// Calculate size of the virtual child inside native View.
float x = PixelUtil.toDIPFromPixel(mScaledX * nativeViewWidth + nativeViewX);
float y = PixelUtil.toDIPFromPixel(mScaledY * nativeViewHeight + nativeViewY);
float width = PixelUtil.toDIPFromPixel(mScaledWidth * nativeViewWidth);
float height = PixelUtil.toDIPFromPixel(mScaledHeight * nativeViewHeight);
if (mRelativeToWindow) {
mCallback.invoke(x, y, width, height);
} else {
mCallback.invoke(0, 0, width, height, x, y);
}
}
}
public final class DetachAllChildrenFromViews implements UIOperation {
private @Nullable int[] mViewsToDetachAllChildrenFrom;
public void setViewsToDetachAllChildrenFrom(int[] viewsToDetachAllChildrenFrom) {
mViewsToDetachAllChildrenFrom = viewsToDetachAllChildrenFrom;
}
@Override
public void execute() {
mNativeViewHierarchyManager.detachAllChildrenFromViews(mViewsToDetachAllChildrenFrom);
}
}
private final class FindTargetForTouchOperation implements UIOperation {
private final int mReactTag;
private final float mTargetX;
private final float mTargetY;
private final Callback mCallback;
private final int[] NATIVE_VIEW_BUFFER = new int[1];
private FindTargetForTouchOperation(
final int reactTag,
final float targetX,
final float targetY,
final Callback callback) {
super();
mReactTag = reactTag;
mTargetX = targetX;
mTargetY = targetY;
mCallback = callback;
}
@Override
public void execute() {
try {
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
} catch (IllegalViewOperationException e) {
mCallback.invoke();
return;
}
// Because React coordinates are relative to root container, and measure() operates
// on screen coordinates, we need to offset values using root container location.
final float containerX = (float) MEASURE_BUFFER[0];
final float containerY = (float) MEASURE_BUFFER[1];
View view = mNativeViewHierarchyManager.getView(mReactTag);
final int touchTargetReactTag = TouchTargetHelper.findTargetTagForTouch(
mTargetX,
mTargetY,
(ViewGroup) view,
NATIVE_VIEW_BUFFER);
try {
mNativeViewHierarchyManager.measure(
NATIVE_VIEW_BUFFER[0],
MEASURE_BUFFER);
} catch (IllegalViewOperationException e) {
mCallback.invoke();
return;
}
NodeRegion region = NodeRegion.EMPTY;
boolean isNativeView = NATIVE_VIEW_BUFFER[0] == touchTargetReactTag;
if (!isNativeView) {
// NATIVE_VIEW_BUFFER[0] is a FlatViewGroup, touchTargetReactTag is the touch target and
// isn't an Android View - try to get its NodeRegion
view = mNativeViewHierarchyManager.getView(NATIVE_VIEW_BUFFER[0]);
if (view instanceof FlatViewGroup) {
region = ((FlatViewGroup) view).getNodeRegionForTag(mReactTag);
}
}
int resultTag = region == NodeRegion.EMPTY ? touchTargetReactTag : region.mTag;
float x = PixelUtil.toDIPFromPixel(region.getLeft() + MEASURE_BUFFER[0] - containerX);
float y = PixelUtil.toDIPFromPixel(region.getTop() + MEASURE_BUFFER[1] - containerY);
float width = PixelUtil.toDIPFromPixel(isNativeView ?
MEASURE_BUFFER[2] : region.getRight() - region.getLeft());
float height = PixelUtil.toDIPFromPixel(isNativeView ?
MEASURE_BUFFER[3] : region.getBottom() - region.getTop());
mCallback.invoke(resultTag, x, y, width, height);
}
}
/**
* Used to delay view manager command dispatch until after the view hierarchy is updated.
* Mirrors command operation dispatch, but is only used in Nodes for view manager commands.
*/
public final class ViewManagerCommand implements UIOperation {
private final int mReactTag;
private final int mCommand;
private final @Nullable ReadableArray mArgs;
public ViewManagerCommand(
int reactTag,
int command,
@Nullable ReadableArray args) {
mReactTag = reactTag;
mCommand = command;
mArgs = args;
}
@Override
public void execute() {
mNativeViewHierarchyManager.dispatchCommand(mReactTag, mCommand, mArgs);
}
}
public FlatUIViewOperationQueue(
ReactApplicationContext reactContext,
FlatNativeViewHierarchyManager nativeViewHierarchyManager) {
super(reactContext, nativeViewHierarchyManager);
mNativeViewHierarchyManager = nativeViewHierarchyManager;
}
/**
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
*/
public void enqueueUpdateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) {
enqueueUIOperation(new UpdateMountState(
reactTag,
drawCommands,
listeners,
nodeRegions));
}
/**
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
*/
public void enqueueUpdateClippingMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] commandMaxBot,
float[] commandMinTop,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions,
float[] regionMaxBot,
float[] regionMinTop,
boolean willMountViews) {
enqueueUIOperation(new UpdateClippingMountState(
reactTag,
drawCommands,
drawViewIndexMap,
commandMaxBot,
commandMinTop,
listeners,
nodeRegions,
regionMaxBot,
regionMinTop,
willMountViews));
}
public void enqueueUpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
enqueueUIOperation(new UpdateViewGroup(reactTag, viewsToAdd, viewsToDetach));
}
/**
* Creates a new UIOperation that will update View bounds for a View defined by reactTag.
*/
public UpdateViewBounds createUpdateViewBounds(
int reactTag,
int left,
int top,
int right,
int bottom) {
return new UpdateViewBounds(reactTag, left, top, right, bottom);
}
public ViewManagerCommand createViewManagerCommand(
int reactTag,
int command,
@Nullable ReadableArray args) {
return new ViewManagerCommand(reactTag, command, args);
}
/* package */ void enqueueFlatUIOperation(UIOperation operation) {
enqueueUIOperation(operation);
}
public void enqueueSetPadding(
int reactTag,
int paddingLeft,
int paddingTop,
int paddingRight,
int paddingBottom) {
enqueueUIOperation(
new SetPadding(reactTag, paddingLeft, paddingTop, paddingRight, paddingBottom));
}
public void enqueueDropViews(
ArrayList<Integer> viewsToDrop,
ArrayList<Integer> parentsOfViewsToDrop) {
enqueueUIOperation(new DropViews(viewsToDrop, parentsOfViewsToDrop));
}
public void enqueueMeasureVirtualView(
int reactTag,
float scaledX,
float scaledY,
float scaledWidth,
float scaledHeight,
boolean relativeToWindow,
Callback callback) {
enqueueUIOperation(new MeasureVirtualView(
reactTag,
scaledX,
scaledY,
scaledWidth,
scaledHeight,
relativeToWindow,
callback));
}
public void enqueueProcessLayoutRequests() {
enqueueUIOperation(mProcessLayoutRequests);
}
public DetachAllChildrenFromViews enqueueDetachAllChildrenFromViews() {
DetachAllChildrenFromViews op = new DetachAllChildrenFromViews();
enqueueUIOperation(op);
return op;
}
@Override
public void enqueueFindTargetForTouch(
final int reactTag,
final float targetX,
final float targetY,
final Callback callback) {
enqueueUIOperation(
new FindTargetForTouchOperation(reactTag, targetX, targetY, callback));
}
}