From 8de2acd3a957a82239058a82d0a11effe93a2864 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Sun, 13 Dec 2015 15:54:52 -0800 Subject: [PATCH] Allow FlatShadowNode mouting to its own view Summary: @public This diff adds a `FlatShadowNode.forceMountToView()` method that will render its contents in it own `View`. Reviewed By: sriramramani Differential Revision: D2564502 --- .../com/facebook/react/flat/DrawView.java | 24 +++ .../flat/FlatNativeViewHierarchyManager.java | 20 ++- .../react/flat/FlatRootShadowNode.java | 95 ----------- .../facebook/react/flat/FlatShadowNode.java | 141 ++++++++++++++- .../react/flat/FlatUIViewOperationQueue.java | 41 +++++ .../facebook/react/flat/FlatViewGroup.java | 61 +++++++ .../facebook/react/flat/FlatViewManager.java | 25 +++ .../react/flat/RCTImageViewManager.java | 2 +- .../facebook/react/flat/RCTTextManager.java | 2 +- .../facebook/react/flat/RCTViewManager.java | 2 +- .../com/facebook/react/flat/StateBuilder.java | 161 ++++++++++++++++-- .../com/facebook/react/flat/ViewResolver.java | 16 ++ 12 files changed, 472 insertions(+), 118 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/DrawView.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/ViewResolver.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawView.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawView.java new file mode 100644 index 000000000..9ffc25823 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawView.java @@ -0,0 +1,24 @@ +/** + * 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 android.graphics.Canvas; + +/* package */ final class DrawView implements DrawCommand { + + /* package */ static DrawView INSTANCE = new DrawView(); + + private DrawView() {} + + @Override + public void draw(FlatViewGroup parent, Canvas canvas) { + parent.drawNextChild(canvas); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java index 964dd8464..abf558948 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java @@ -23,12 +23,18 @@ import com.facebook.react.uimanager.ViewManagerRegistry; * FlatNativeViewHierarchyManager is the only class that performs View manipulations. All of this * class methods can only be called from UI thread by {@link FlatUIViewOperationQueue}. */ -/* package */ final class FlatNativeViewHierarchyManager extends NativeViewHierarchyManager { +/* package */ final class FlatNativeViewHierarchyManager extends NativeViewHierarchyManager + implements ViewResolver { /* package */ FlatNativeViewHierarchyManager(ViewManagerRegistry viewManagers) { super(viewManagers); } + @Override + public View getView(int reactTag) { + return super.resolveView(reactTag); + } + @Override public void addRootView( int tag, @@ -60,6 +66,11 @@ import com.facebook.react.uimanager.ViewManagerRegistry; } } + /* package */ void updateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) { + FlatViewGroup view = (FlatViewGroup) resolveView(reactTag); + view.mountViews(this, viewsToAdd, viewsToDetach); + } + /** * Updates View bounds, possibly re-measuring and re-layouting it if the size changed. * @@ -85,4 +96,11 @@ import com.facebook.react.uimanager.ViewManagerRegistry; view.offsetTopAndBottom(top - view.getTop()); } } + + /* package */ void detachAllChildrenFromViews(int[] viewsToDetachAllChildrenFrom) { + for (int viewTag : viewsToDetachAllChildrenFrom) { + FlatViewGroup viewGroup = (FlatViewGroup) resolveView(viewTag); + viewGroup.detachAllViewsFromParent(); + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java index 621284650..942b59aff 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java @@ -14,35 +14,8 @@ package com.facebook.react.flat; */ /* package */ final class FlatRootShadowNode extends FlatShadowNode { - private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY; - private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY; - - private int mViewLeft; - private int mViewTop; - private int mViewRight; - private int mViewBottom; private boolean mIsUpdated; - @Override - public int getScreenX() { - return mViewLeft; - } - - @Override - public int getScreenY() { - return mViewTop; - } - - @Override - public int getScreenWidth() { - return mViewRight - mViewLeft; - } - - @Override - public int getScreenHeight() { - return mViewBottom - mViewTop; - } - /** * Returns true when this CSSNode tree needs to be re-laid out. If true, FlatUIImplementation * will request LayoutEngine to perform a layout pass to update node boundaries. This is used @@ -66,72 +39,4 @@ package com.facebook.react.flat; /* package */ void markUpdated(boolean isUpdated) { mIsUpdated = isUpdated; } - - /** - * Returns an array of DrawCommands to perform during the View's draw pass. - */ - /* package */ DrawCommand[] getDrawCommands() { - return mDrawCommands; - } - - /** - * Sets an array of DrawCommands to perform during the View's draw pass. StateBuilder uses old - * draw commands to compare to new draw commands and see if the View neds to be redrawn. - */ - /* package */ void setDrawCommands(DrawCommand[] drawCommands) { - mDrawCommands = drawCommands; - } - - /** - * Sets an array of AttachDetachListeners to call onAttach/onDetach when they are attached to or - * detached from a View that this shadow node maps to. - */ - /* package */ void setAttachDetachListeners(AttachDetachListener[] listeners) { - mAttachDetachListeners = listeners; - } - - /** - * Returns an array of AttachDetachListeners associated with this shadow node. - */ - /* package */ AttachDetachListener[] getAttachDetachListeners() { - return mAttachDetachListeners; - } - - /** - * Sets boundaries of the View that this node maps to relative to the parent left/top coordinate. - */ - /* package */ void setViewBounds(int left, int top, int right, int bottom) { - mViewLeft = left; - mViewTop = top; - mViewRight = right; - mViewBottom = bottom; - } - - /** - * Left position of the View this node maps to relative to the parent View. - */ - /* package */ int getViewLeft() { - return mViewLeft; - } - - /** - * Top position of the View this node maps to relative to the parent View. - */ - /* package */ int getViewTop() { - return mViewTop; - } - - /** - * Right position of the View this node maps to relative to the parent View. - */ - /* package */ int getViewRight() { - return mViewRight; - } - - /** - * Bottom position of the View this node maps to relative to the parent View. - */ - /* package */ int getViewBottom() { - return mViewBottom; - } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java index 7623caf0b..dc737fd1a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java @@ -21,6 +21,18 @@ import com.facebook.react.uimanager.ViewProps; */ /* package */ class FlatShadowNode extends LayoutShadowNode { + /* package */ static final FlatShadowNode[] EMPTY_ARRAY = new FlatShadowNode[0]; + + private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY; + private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY; + private FlatShadowNode[] mNativeChildren = FlatShadowNode.EMPTY_ARRAY; + private int mNativeParentTag; + private int mViewLeft; + private int mViewTop; + private int mViewRight; + private int mViewBottom; + private boolean mMountsToView; + private boolean mBackingViewIsCreated; private @Nullable DrawBackgroundColor mDrawBackground; /** @@ -48,14 +60,137 @@ import com.facebook.react.uimanager.ViewProps; invalidate(); } + @Override + public final int getScreenX() { + return mViewLeft; + } + + @Override + public final int getScreenY() { + return mViewTop; + } + + @Override + public final int getScreenWidth() { + return mViewRight - mViewLeft; + } + + @Override + public final int getScreenHeight() { + return mViewBottom - mViewTop; + } + /** * Marks root node as updated to trigger a StateBuilder pass to collect DrawCommands for the node * tree. Use it when FlatShadowNode is updated but doesn't require a layout pass (e.g. background * color is changed). */ protected final void invalidate() { - // getRootNode() returns an ReactShadowNode, which is guarantied to be a FlatRootShadowNode. - FlatRootShadowNode rootNode = (FlatRootShadowNode) getRootNode(); - rootNode.markUpdated(true); + ((FlatRootShadowNode) getRootNode()).markUpdated(true); + } + + /** + * Returns an array of DrawCommands to perform during the View's draw pass. + */ + /* package */ DrawCommand[] getDrawCommands() { + return mDrawCommands; + } + + /** + * Sets an array of DrawCommands to perform during the View's draw pass. StateBuilder uses old + * draw commands to compare to new draw commands and see if the View neds to be redrawn. + */ + /* package */ void setDrawCommands(DrawCommand[] drawCommands) { + mDrawCommands = drawCommands; + } + + /** + * Sets an array of AttachDetachListeners to call onAttach/onDetach when they are attached to or + * detached from a View that this shadow node maps to. + */ + /* package */ void setAttachDetachListeners(AttachDetachListener[] listeners) { + mAttachDetachListeners = listeners; + } + + /** + * Returns an array of AttachDetachListeners associated with this shadow node. + */ + /* package */ AttachDetachListener[] getAttachDetachListeners() { + return mAttachDetachListeners; + } + + /* package */ final FlatShadowNode[] getNativeChildren() { + return mNativeChildren; + } + + /* package */ final void setNativeChildren(FlatShadowNode[] nativeChildren) { + mNativeChildren = nativeChildren; + } + + /* package */ final int getNativeParentTag() { + return mNativeParentTag; + } + + /* package */ final void setNativeParentTag(int nativeParentTag) { + mNativeParentTag = nativeParentTag; + } + + /** + * Sets boundaries of the View that this node maps to relative to the parent left/top coordinate. + */ + /* package */ void setViewBounds(int left, int top, int right, int bottom) { + mViewLeft = left; + mViewTop = top; + mViewRight = right; + mViewBottom = bottom; + } + + /** + * Left position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewLeft() { + return mViewLeft; + } + + /** + * Top position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewTop() { + return mViewTop; + } + + /** + * Right position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewRight() { + return mViewRight; + } + + /** + * Bottom position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewBottom() { + return mViewBottom; + } + + /* package */ final void forceMountToView() { + if (!mMountsToView) { + mMountsToView = true; + if (getParent() != null) { + invalidate(); + } + } + } + + /* package */ final boolean mountsToView() { + return mMountsToView; + } + + /* package */ final boolean isBackingViewCreated() { + return mBackingViewIsCreated; + } + + /* package */ final void signalBackingViewIsCreated() { + mBackingViewIsCreated = true; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java index 6d51b71c0..e9106ddeb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java @@ -49,6 +49,24 @@ import com.facebook.react.uimanager.UIViewOperationQueue; } } + 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. */ @@ -74,6 +92,19 @@ import com.facebook.react.uimanager.UIViewOperationQueue; } } + public final class DetachAllChildrenFromViews implements UIViewOperationQueue.UIOperation { + private @Nullable int[] mViewsToDetachAllChildrenFrom; + + public void setViewsToDetachAllChildrenFrom(int[] viewsToDetachAllChildrenFrom) { + mViewsToDetachAllChildrenFrom = viewsToDetachAllChildrenFrom; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.detachAllChildrenFromViews(mViewsToDetachAllChildrenFrom); + } + } + public FlatUIViewOperationQueue( ReactApplicationContext reactContext, FlatNativeViewHierarchyManager nativeViewHierarchyManager) { @@ -92,10 +123,20 @@ import com.facebook.react.uimanager.UIViewOperationQueue; enqueueUIOperation(new UpdateMountState(reactTag, drawCommands, listeners)); } + public void enqueueUpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) { + enqueueUIOperation(new UpdateViewGroup(reactTag, viewsToAdd, viewsToDetach)); + } + /** * Enqueues a new UIOperation that will update View bounds for a View defined by reactTag. */ public void enqueueUpdateViewBounds(int reactTag, int left, int top, int right, int bottom) { enqueueUIOperation(new UpdateViewBounds(reactTag, left, top, right, bottom)); } + + public DetachAllChildrenFromViews enqueueDetachAllChildrenFromViews() { + DetachAllChildrenFromViews op = new DetachAllChildrenFromViews(); + enqueueUIOperation(op); + return op; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java index 87462df87..1c11ee18e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java @@ -15,7 +15,9 @@ import javax.annotation.Nullable; import android.content.Context; import android.graphics.Canvas; +import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; /** * A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over @@ -45,12 +47,18 @@ import android.view.ViewGroup; private @Nullable InvalidateCallback mInvalidateCallback; private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY; private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY; + private int mDrawChildIndex = 0; private boolean mIsAttached = false; /* package */ FlatViewGroup(Context context) { super(context); } + @Override + protected void detachAllViewsFromParent() { + super.detachAllViewsFromParent(); + } + @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); @@ -58,6 +66,19 @@ import android.view.ViewGroup; for (DrawCommand drawCommand : mDrawCommands) { drawCommand.draw(this, canvas); } + + if (mDrawChildIndex != getChildCount()) { + throw new RuntimeException( + "Did not draw all children: " + mDrawChildIndex + " / " + getChildCount()); + } + mDrawChildIndex = 0; + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + // suppress + // no drawing -> no invalidate -> return false + return false; } @Override @@ -90,6 +111,13 @@ import android.view.ViewGroup; dispatchOnDetached(mAttachDetachListeners); } + /* package */ void drawNextChild(Canvas canvas) { + View child = getChildAt(mDrawChildIndex); + super.drawChild(canvas, child, getDrawingTime()); + + ++mDrawChildIndex; + } + /* package */ void mountDrawCommands(DrawCommand[] drawCommands) { mDrawCommands = drawCommands; invalidate(); @@ -115,6 +143,32 @@ import android.view.ViewGroup; mAttachDetachListeners = listeners; } + /* package */ void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach) { + for (int viewToAdd : viewsToAdd) { + if (viewToAdd > 0) { + View view = ensureViewHasNoParent(viewResolver.getView(viewToAdd)); + addView(view, -1, ensureLayoutParams(view.getLayoutParams())); + } else { + View view = ensureViewHasNoParent(viewResolver.getView(-viewToAdd)); + attachViewToParent(view, -1, ensureLayoutParams(view.getLayoutParams())); + } + } + + for (int viewToDetach : viewsToDetach) { + removeDetachedView(viewResolver.getView(viewToDetach), false); + } + } + + private View ensureViewHasNoParent(View view) { + ViewParent oldParent = view.getParent(); + if (oldParent != null) { + throw new RuntimeException( + "Cannot add view " + view + " to " + this + " while it has a parent " + oldParent); + } + + return view; + } + private void dispatchOnAttached(AttachDetachListener[] listeners) { int numListeners = listeners.length; if (numListeners == 0) { @@ -139,4 +193,11 @@ import android.view.ViewGroup; listener.onDetached(); } } + + private ViewGroup.LayoutParams ensureLayoutParams(ViewGroup.LayoutParams lp) { + if (checkLayoutParams(lp)) { + return lp; + } + return generateDefaultLayoutParams(); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewManager.java new file mode 100644 index 000000000..d055d93f5 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewManager.java @@ -0,0 +1,25 @@ +/** + * 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 com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManager; + +abstract class FlatViewManager extends ViewManager { + + @Override + protected FlatViewGroup createViewInstance(ThemedReactContext reactContext) { + return new FlatViewGroup(reactContext); + } + + @Override + public void updateExtraData(FlatViewGroup root, Object extraData) { + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageViewManager.java index b1667ef76..82c121165 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageViewManager.java @@ -9,7 +9,7 @@ package com.facebook.react.flat; -/* package */ final class RCTImageViewManager extends VirtualViewManager { +/* package */ final class RCTImageViewManager extends FlatViewManager { @Override public String getName() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java index 751a9214f..b1794980f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java @@ -12,7 +12,7 @@ package com.facebook.react.flat; /** * ViewManager that creates instances of RCTText. */ -/* package */ final class RCTTextManager extends VirtualViewManager { +/* package */ final class RCTTextManager extends FlatViewManager { @Override public String getName() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java index aa860b616..3644eef90 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java @@ -12,7 +12,7 @@ package com.facebook.react.flat; /** * ViewManager that creates instances of RCTView. */ -/* package */ final class RCTViewManager extends VirtualViewManager { +/* package */ final class RCTViewManager extends FlatViewManager { @Override public String getName() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java index 1e0c7b69e..c59401da5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java @@ -9,6 +9,12 @@ package com.facebook.react.flat; +import java.util.ArrayList; + +import javax.annotation.Nullable; + +import com.facebook.react.uimanager.CatalystStylesDiffMap; + /** * Shadow node hierarchy by itself cannot display UI, it is only a representation of what UI should * be from JavaScript perspective. StateBuilder is a helper class that can walk the shadow node tree @@ -17,12 +23,21 @@ package com.facebook.react.flat; */ /* package */ final class StateBuilder { + private static final int[] EMPTY_INT_ARRAY = new int[0]; + private final FlatUIViewOperationQueue mOperationsQueue; private final ElementsList mDrawCommands = new ElementsList<>(DrawCommand.EMPTY_ARRAY); private final ElementsList mAttachDetachListeners = new ElementsList<>(AttachDetachListener.EMPTY_ARRAY); + private final ElementsList mNativeChildren = + new ElementsList<>(FlatShadowNode.EMPTY_ARRAY); + + private final ArrayList mViewsToDetachAllChildrenFrom = new ArrayList<>(); + private final ArrayList mViewsToDetach = new ArrayList<>(); + + private @Nullable FlatUIViewOperationQueue.DetachAllChildrenFromViews mDetachAllChildrenFromViews; /* package */ StateBuilder(FlatUIViewOperationQueue operationsQueue) { mOperationsQueue = operationsQueue; @@ -32,8 +47,24 @@ package com.facebook.react.flat; * Given a root of the laid-out shadow node hierarchy, walks the tree and generates an array of * DrawCommands that will then mount in UI thread to a root FlatViewGroup so that it can draw. */ - /* package*/ void applyUpdates(FlatRootShadowNode node) { - collectStateAndUpdateViewBounds(node, 0, 0); + /* package */ void applyUpdates(FlatShadowNode node) { + int tag = node.getReactTag(); + + float width = node.getLayoutWidth(); + float height = node.getLayoutHeight(); + collectStateForMountableNode(node, tag, width, height); + + float left = node.getLayoutX(); + float top = node.getLayoutY(); + updateViewBounds(node, tag, left, top, left + width, top + height); + + if (mDetachAllChildrenFromViews != null) { + int[] viewsToDetachAllChildrenFrom = collectViewTags(mViewsToDetachAllChildrenFrom); + mViewsToDetachAllChildrenFrom.clear(); + + mDetachAllChildrenFromViews.setViewsToDetachAllChildrenFrom(viewsToDetachAllChildrenFrom); + mDetachAllChildrenFromViews = null; + } } /** @@ -47,11 +78,27 @@ package com.facebook.react.flat; mAttachDetachListeners.add(listener); } + /* package */ void ensureBackingViewIsCreated( + FlatShadowNode node, + int tag, + @Nullable CatalystStylesDiffMap styles) { + if (node.isBackingViewCreated()) { + return; + } + + mOperationsQueue.enqueueCreateView(node.getThemedContext(), tag, node.getViewClass(), styles); + node.signalBackingViewIsCreated(); + } + + private void addNativeChild(FlatShadowNode nativeChild) { + mNativeChildren.add(nativeChild); + } + /** * Updates boundaries of a View that a give nodes maps to. */ private void updateViewBounds( - FlatRootShadowNode node, + FlatShadowNode node, int tag, float leftInParent, float topInParent, @@ -77,12 +124,13 @@ package com.facebook.react.flat; * Collects state (DrawCommands) for a given node that will mount to a View. */ private void collectStateForMountableNode( - FlatRootShadowNode node, + FlatShadowNode node, int tag, float width, float height) { mDrawCommands.start(node.getDrawCommands()); mAttachDetachListeners.start(node.getAttachDetachListeners()); + mNativeChildren.start(node.getNativeChildren()); collectStateRecursively(node, 0, 0, width, height); @@ -100,8 +148,72 @@ package com.facebook.react.flat; } if (shouldUpdateMountState) { - mOperationsQueue.enqueueUpdateMountState(tag, drawCommands, listeners); + mOperationsQueue.enqueueUpdateMountState( + tag, + drawCommands, + listeners); } + + final FlatShadowNode[] nativeChildren = mNativeChildren.finish(); + if (nativeChildren != null) { + updateNativeChildren(node, tag, node.getNativeChildren(), nativeChildren); + } + } + + private void updateNativeChildren( + FlatShadowNode node, + int tag, + FlatShadowNode[] oldNativeChildren, + FlatShadowNode[] newNativeChildren) { + + node.setNativeChildren(newNativeChildren); + + if (mDetachAllChildrenFromViews == null) { + mDetachAllChildrenFromViews = mOperationsQueue.enqueueDetachAllChildrenFromViews(); + } + + if (oldNativeChildren.length != 0) { + mViewsToDetachAllChildrenFrom.add(node); + } + + int numViewsToAdd = newNativeChildren.length; + final int[] viewsToAdd; + if (numViewsToAdd == 0) { + viewsToAdd = EMPTY_INT_ARRAY; + } else { + viewsToAdd = new int[numViewsToAdd]; + int i = 0; + for (FlatShadowNode child : newNativeChildren) { + if (child.getNativeParentTag() == tag) { + viewsToAdd[i] = -child.getReactTag(); + } else { + viewsToAdd[i] = child.getReactTag(); + } + // all views we add are first start detached + child.setNativeParentTag(-1); + ++i; + } + } + + // Populate an array of views to detach. + // These views still have their native parent set as opposed to being reset to -1 + for (FlatShadowNode child : oldNativeChildren) { + if (child.getNativeParentTag() == tag) { + // View is attached to old parent and needs to be removed. + mViewsToDetach.add(child); + child.setNativeParentTag(-1); + } + } + + final int[] viewsToDetach = collectViewTags(mViewsToDetach); + mViewsToDetach.clear(); + + // restore correct parent tag + for (FlatShadowNode child : newNativeChildren) { + child.setNativeParentTag(tag); + } + + mOperationsQueue.enqueueUpdateViewGroup(tag, viewsToAdd, viewsToDetach); } /** @@ -121,20 +233,15 @@ package com.facebook.react.flat; for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) { FlatShadowNode child = (FlatShadowNode) node.getChildAt(i); - - float childLeft = left + child.getLayoutX(); - float childTop = top + child.getLayoutY(); - float childRight = childLeft + child.getLayoutWidth(); - float childBottom = childTop + child.getLayoutHeight(); - collectStateRecursively(child, childLeft, childTop, childRight, childBottom); + processNodeAndCollectState(child, left, top); } } /** - * Collects state and updates View boundaries for a given root node. + * Collects state and updates View boundaries for a given node tree. */ - private void collectStateAndUpdateViewBounds( - FlatRootShadowNode node, + private void processNodeAndCollectState( + FlatShadowNode node, float parentLeft, float parentTop) { int tag = node.getReactTag(); @@ -147,8 +254,30 @@ package com.facebook.react.flat; float right = left + width; float bottom = top + height; - collectStateForMountableNode(node, tag, width, height); + if (node.mountsToView()) { + ensureBackingViewIsCreated(node, tag, null); - updateViewBounds(node, tag, left, top, right, bottom); + addNativeChild(node); + mDrawCommands.add(DrawView.INSTANCE); + + collectStateForMountableNode(node, tag, width, height); + updateViewBounds(node, tag, left, top, right, bottom); + } else { + collectStateRecursively(node, left, top, right, bottom); + } + } + + private static int[] collectViewTags(ArrayList views) { + int numViews = views.size(); + if (numViews == 0) { + return EMPTY_INT_ARRAY; + } + + int[] viewTags = new int[numViews]; + for (int i = 0; i < numViews; ++i) { + viewTags[i] = views.get(i).getReactTag(); + } + + return viewTags; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/ViewResolver.java b/ReactAndroid/src/main/java/com/facebook/react/flat/ViewResolver.java new file mode 100644 index 000000000..0ff867f5d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/ViewResolver.java @@ -0,0 +1,16 @@ +/** + * 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 android.view.View; + +public interface ViewResolver { + public View getView(int tag); +}