/** * Handles an updateView call. If a view transitions from being layout-only to not (or vice-versa) * this could result in some number of additional createView and manageChildren calls. If the view * is layout only, no updateView call will be dispatched to the native hierarchy. */ public void handleUpdateView( ReactShadowNode node, String className, CatalystStylesDiffMap props) { if (!ENABLED) { mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props); return; } boolean needsToLeaveLayoutOnly = node.isLayoutOnly() && !isLayoutOnlyAndCollapsable(props); if (needsToLeaveLayoutOnly) { transitionLayoutOnlyViewToNativeView(node, props); } else if (!node.isLayoutOnly()) { mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props); } }
/** * Determines the location on screen, width, and height of the given view and returns the values * via an async callback. */ public void measure(int reactTag, Callback callback) { // This method is called by the implementation of JS touchable interface (see Touchable.js for // more details) at the moment of touch activation. That is after user starts the gesture from // a touchable view with a given reactTag, or when user drag finger back into the press // activation area of a touchable view that have been activated before. mOperationsQueue.enqueueMeasure(reactTag, callback); }
private void applyLayoutRecursive(ReactShadowNode toUpdate, int x, int y) { if (!toUpdate.isLayoutOnly() && toUpdate.getNativeParent() != null) { int tag = toUpdate.getReactTag(); mUIViewOperationQueue.enqueueUpdateLayout( toUpdate.getNativeParent().getReactTag(), tag, x, y, toUpdate.getScreenWidth(), toUpdate.getScreenHeight()); return; } for (int i = 0; i < toUpdate.getChildCount(); i++) { ReactShadowNode child = toUpdate.getChildAt(i); int childTag = child.getReactTag(); if (mTagsWithLayoutVisited.get(childTag)) { continue; } mTagsWithLayoutVisited.put(childTag, true); int childX = child.getScreenX(); int childY = child.getScreenY(); childX += x; childY += y; applyLayoutRecursive(child, childX, childY); } }
/** * Handles a manageChildren call. This may translate into multiple manageChildren calls for * multiple other views. * * <p>NB: the assumption for calling this method is that all corresponding ReactShadowNodes have * been updated **but tagsToDelete have NOT been deleted yet**. This is because we need to use the * metadata from those nodes to figure out the correct commands to dispatch. This is unlike all * other calls on this class where we assume all operations on the shadow hierarchy have already * completed by the time a corresponding method here is called. */ public void handleManageChildren( ReactShadowNode nodeToManage, int[] indicesToRemove, int[] tagsToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete) { if (!ENABLED) { mUIViewOperationQueue.enqueueManageChildren( nodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete); return; } // We operate on tagsToRemove instead of indicesToRemove because by the time this method is // called, these views have already been removed from the shadow hierarchy and the indices are // no longer useful to operate on for (int i = 0; i < tagsToRemove.length; i++) { int tagToRemove = tagsToRemove[i]; boolean delete = false; for (int j = 0; j < tagsToDelete.length; j++) { if (tagsToDelete[j] == tagToRemove) { delete = true; break; } } ReactShadowNode nodeToRemove = mShadowNodeRegistry.getNode(tagToRemove); removeNodeFromParent(nodeToRemove, delete); } for (int i = 0; i < viewsToAdd.length; i++) { ViewAtIndex toAdd = viewsToAdd[i]; ReactShadowNode nodeToAdd = mShadowNodeRegistry.getNode(toAdd.mTag); addNodeToNode(nodeToManage, nodeToAdd, toAdd.mIndex); } }
public void setJSResponder(int reactTag, boolean blockNativeResponder) { assertViewExists(reactTag, "setJSResponder"); ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); while (node.isVirtual() || node.isLayoutOnly()) { node = node.getParent(); } mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder); }
/** Handles a createView call. May or may not actually create a native view. */ public void handleCreateView( ReactShadowNode node, int rootViewTag, @Nullable CatalystStylesDiffMap initialProps) { if (!ENABLED) { int tag = node.getReactTag(); mUIViewOperationQueue.enqueueCreateView(rootViewTag, tag, node.getViewClass(), initialProps); return; } boolean isLayoutOnly = node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME) && isLayoutOnlyAndCollapsable(initialProps); node.setIsLayoutOnly(isLayoutOnly); if (!isLayoutOnly) { mUIViewOperationQueue.enqueueCreateView( rootViewTag, node.getReactTag(), node.getViewClass(), initialProps); } }
private void addNonLayoutNodeToNonLayoutNode( ReactShadowNode parent, ReactShadowNode child, int index) { parent.addNativeChildAt(child, index); mUIViewOperationQueue.enqueueManageChildren( parent.getReactTag(), null, new ViewAtIndex[] {new ViewAtIndex(child.getReactTag(), index)}, null); }
/** Invoked when native view that corresponds to a root node has its size changed. */ public void updateRootNodeSize( int rootViewTag, int newWidth, int newHeight, EventDispatcher eventDispatcher) { ReactShadowNode rootCSSNode = mShadowNodeRegistry.getNode(rootViewTag); rootCSSNode.setStyleWidth(newWidth); rootCSSNode.setStyleHeight(newHeight); // If we're in the middle of a batch, the change will automatically be dispatched at the end of // the batch. As all batches are executed as a single runnable on the event queue this should // always be empty, but that calling architecture is an implementation detail. if (mOperationsQueue.isEmpty()) { dispatchViewUpdates(eventDispatcher, -1); // -1 = no associated batch id } }
/** * Handles an updateLayout call. All updateLayout calls are collected and dispatched at the end of * a batch because updateLayout calls to layout-only nodes can necessitate multiple updateLayout * calls for all its children. */ public void handleUpdateLayout(ReactShadowNode node) { if (!ENABLED) { mUIViewOperationQueue.enqueueUpdateLayout( Assertions.assertNotNull(node.getParent()).getReactTag(), node.getReactTag(), node.getScreenX(), node.getScreenY(), node.getScreenWidth(), node.getScreenHeight()); return; } applyLayoutBase(node); }
/** * Registers a root node with a given tag, size and ThemedReactContext and adds it to a node * registry. */ public void registerRootView( SizeMonitoringFrameLayout rootView, int tag, int width, int height, ThemedReactContext context) { final ReactShadowNode rootCSSNode = new ReactShadowNode(); rootCSSNode.setReactTag(tag); rootCSSNode.setThemedContext(context); rootCSSNode.setStyleWidth(width); rootCSSNode.setStyleHeight(height); rootCSSNode.setViewClassName("Root"); mShadowNodeRegistry.addRootNode(rootCSSNode); // register it within NativeViewHierarchyManager mOperationsQueue.addRootView(tag, rootView, context); }
/** * For handling node removal from manageChildren. In the case of removing a layout-only node, we * need to instead recursively remove all its children from their native parents. */ private void removeNodeFromParent(ReactShadowNode nodeToRemove, boolean shouldDelete) { ReactShadowNode nativeNodeToRemoveFrom = nodeToRemove.getNativeParent(); if (nativeNodeToRemoveFrom != null) { int index = nativeNodeToRemoveFrom.indexOfNativeChild(nodeToRemove); nativeNodeToRemoveFrom.removeNativeChildAt(index); mUIViewOperationQueue.enqueueManageChildren( nativeNodeToRemoveFrom.getReactTag(), new int[] {index}, null, shouldDelete ? new int[] {nodeToRemove.getReactTag()} : null); } else { for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { removeNodeFromParent(nodeToRemove.getChildAt(i), shouldDelete); } } }
/** Invoked at the end of the transaction to commit any updates to the node hierarchy. */ public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) { for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) { int tag = mShadowNodeRegistry.getRootTag(i); ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag); notifyOnBeforeLayoutRecursive(cssRoot); SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") .arg("rootTag", tag) .flush(); try { cssRoot.calculateLayout(mLayoutContext); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher); } mNativeViewHierarchyOptimizer.onBatchComplete(); mOperationsQueue.dispatchViewUpdates(batchId); }
private void transitionLayoutOnlyViewToNativeView( ReactShadowNode node, @Nullable CatalystStylesDiffMap props) { ReactShadowNode parent = node.getParent(); if (parent == null) { node.setIsLayoutOnly(false); return; } // First, remove the node from its parent. This causes the parent to update its native children // count. The removeNodeFromParent call will cause all the view's children to be detached from // their native parent. int childIndex = parent.indexOf(node); parent.removeChildAt(childIndex); removeNodeFromParent(node, false); node.setIsLayoutOnly(false); // Create the view since it doesn't exist in the native hierarchy yet mUIViewOperationQueue.enqueueCreateView( node.getRootNode().getReactTag(), node.getReactTag(), node.getViewClass(), props); // Add the node and all its children as if we are adding a new nodes parent.addChildAt(node, childIndex); addNodeToNode(parent, node, childIndex); for (int i = 0; i < node.getChildCount(); i++) { addNodeToNode(node, node.getChildAt(i), i); } // Update layouts since the children of the node were offset by its x/y position previously. // Bit of a hack: we need to update the layout of this node's children now that it's no longer // layout-only, but we may still receive more layout updates at the end of this batch that we // don't want to ignore. Assertions.assertCondition(mTagsWithLayoutVisited.size() == 0); applyLayoutBase(node); for (int i = 0; i < node.getChildCount(); i++) { applyLayoutBase(node.getChildAt(i)); } mTagsWithLayoutVisited.clear(); }
public void setViewHierarchyUpdateDebugListener( @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) { mOperationsQueue.setViewHierarchyUpdateDebugListener(listener); }
public void onHostPause() { mOperationsQueue.pauseFrameCallback(); }
public void onHostResume() { mOperationsQueue.resumeFrameCallback(); }
public void sendAccessibilityEvent(int tag, int eventType) { mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType); }
/** * Show a PopupMenu. * * @param reactTag the tag of the anchor view (the PopupMenu is displayed next to this view); this * needs to be the tag of a native view (shadow views can not be anchors) * @param items the menu items as an array of strings * @param error will be called if there is an error displaying the menu * @param success will be called with the position of the selected item as the first argument, or * no arguments if the menu is dismissed */ public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) { assertViewExists(reactTag, "showPopupMenu"); mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success); }
public void clearJSResponder() { mOperationsQueue.enqueueClearJSResponder(); }
/** Removes an existing Animation, canceling it if it was in progress. */ public void removeAnimation(int reactTag, int animationID) { assertViewExists(reactTag, "removeAnimation"); mOperationsQueue.enqueueRemoveAnimation(animationID); }
/** Registers a new Animation that can then be added to a View using {@link #addAnimation}. */ public void registerAnimation(Animation animation) { mOperationsQueue.enqueueRegisterAnimation(animation); }
/** Unregisters a root node with a given tag. */ public void removeRootView(int rootViewTag) { mShadowNodeRegistry.removeRootNode(rootViewTag); mOperationsQueue.enqueueRemoveRootView(rootViewTag); }
public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) { assertViewExists(reactTag, "dispatchViewManagerCommand"); mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs); }
/** * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it */ public void addAnimation(int reactTag, int animationID, Callback onSuccess) { assertViewExists(reactTag, "addAnimation"); mOperationsQueue.enqueueAddAnimation(reactTag, animationID, onSuccess); }
/** * Find the touch target child native view in the supplied root view hierarchy, given a react * target location. * * <p>This method is currently used only by Element Inspector DevTool. * * @param reactTag the tag of the root view to traverse * @param targetX target X location * @param targetY target Y location * @param callback will be called if with the identified child view react ID, and measurement * info. If no view was found, callback will be invoked with no data. */ public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) { mOperationsQueue.enqueueFindTargetForTouch(reactTag, targetX, targetY, callback); }