@@ -68,6 +68,15 @@ private static class NodeIndexPair {
6868 private final ShadowNodeRegistry mShadowNodeRegistry ;
6969 private final SparseBooleanArray mTagsWithLayoutVisited = new SparseBooleanArray ();
7070
71+ public static void assertNodeSupportedWithoutOptimizer (ReactShadowNode node ) {
72+ // NativeKind.LEAF nodes require the optimizer. They are not ViewGroups so they cannot host
73+ // their native children themselves. Their native children need to be hoisted by the optimizer
74+ // to an ancestor which is a ViewGroup.
75+ Assertions .assertCondition (
76+ node .getNativeKind () != NativeKind .LEAF ,
77+ "Nodes with NativeKind.LEAF are not supported when the optimizer is disabled" );
78+ }
79+
7180 public NativeViewHierarchyOptimizer (
7281 UIViewOperationQueue uiViewOperationQueue ,
7382 ShadowNodeRegistry shadowNodeRegistry ) {
@@ -83,6 +92,7 @@ public void handleCreateView(
8392 ThemedReactContext themedContext ,
8493 @ Nullable ReactStylesDiffMap initialProps ) {
8594 if (!ENABLED ) {
95+ assertNodeSupportedWithoutOptimizer (node );
8696 int tag = node .getReactTag ();
8797 mUIViewOperationQueue .enqueueCreateView (
8898 themedContext ,
@@ -96,7 +106,7 @@ public void handleCreateView(
96106 isLayoutOnlyAndCollapsable (initialProps );
97107 node .setIsLayoutOnly (isLayoutOnly );
98108
99- if (! isLayoutOnly ) {
109+ if (node . getNativeKind () != NativeKind . NONE ) {
100110 mUIViewOperationQueue .enqueueCreateView (
101111 themedContext ,
102112 node .getReactTag (),
@@ -122,6 +132,7 @@ public void handleUpdateView(
122132 String className ,
123133 ReactStylesDiffMap props ) {
124134 if (!ENABLED ) {
135+ assertNodeSupportedWithoutOptimizer (node );
125136 mUIViewOperationQueue .enqueueUpdateProperties (node .getReactTag (), className , props );
126137 return ;
127138 }
@@ -151,6 +162,7 @@ public void handleManageChildren(
151162 ViewAtIndex [] viewsToAdd ,
152163 int [] tagsToDelete ) {
153164 if (!ENABLED ) {
165+ assertNodeSupportedWithoutOptimizer (nodeToManage );
154166 mUIViewOperationQueue .enqueueManageChildren (
155167 nodeToManage .getReactTag (),
156168 indicesToRemove ,
@@ -191,6 +203,7 @@ public void handleSetChildren(
191203 ReadableArray childrenTags
192204 ) {
193205 if (!ENABLED ) {
206+ assertNodeSupportedWithoutOptimizer (nodeToManage );
194207 mUIViewOperationQueue .enqueueSetChildren (
195208 nodeToManage .getReactTag (),
196209 childrenTags );
@@ -210,6 +223,7 @@ public void handleSetChildren(
210223 */
211224 public void handleUpdateLayout (ReactShadowNode node ) {
212225 if (!ENABLED ) {
226+ assertNodeSupportedWithoutOptimizer (node );
213227 mUIViewOperationQueue .enqueueUpdateLayout (
214228 Assertions .assertNotNull (node .getParent ()).getReactTag (),
215229 node .getReactTag (),
@@ -223,6 +237,12 @@ public void handleUpdateLayout(ReactShadowNode node) {
223237 applyLayoutBase (node );
224238 }
225239
240+ public void handleForceViewToBeNonLayoutOnly (ReactShadowNode node ) {
241+ if (node .isLayoutOnly ()) {
242+ transitionLayoutOnlyViewToNativeView (node , null );
243+ }
244+ }
245+
226246 /**
227247 * Processes the shadow hierarchy to dispatch all necessary updateLayout calls to the native
228248 * hierarchy. Should be called after all updateLayout calls for a batch have been handled.
@@ -231,16 +251,18 @@ public void onBatchComplete() {
231251 mTagsWithLayoutVisited .clear ();
232252 }
233253
234- private NodeIndexPair walkUpUntilNonLayoutOnly (
254+ private NodeIndexPair walkUpUntilNativeKindIsParent (
235255 ReactShadowNode node ,
236256 int indexInNativeChildren ) {
237- while (node .isLayoutOnly () ) {
257+ while (node .getNativeKind () != NativeKind . PARENT ) {
238258 ReactShadowNode parent = node .getParent ();
239259 if (parent == null ) {
240260 return null ;
241261 }
242262
243- indexInNativeChildren = indexInNativeChildren + parent .getNativeOffsetForChild (node );
263+ indexInNativeChildren = indexInNativeChildren +
264+ (node .getNativeKind () == NativeKind .LEAF ? 1 : 0 ) +
265+ parent .getNativeOffsetForChild (node );
244266 node = parent ;
245267 }
246268
@@ -249,8 +271,8 @@ private NodeIndexPair walkUpUntilNonLayoutOnly(
249271
250272 private void addNodeToNode (ReactShadowNode parent , ReactShadowNode child , int index ) {
251273 int indexInNativeChildren = parent .getNativeOffsetForChild (parent .getChildAt (index ));
252- if (parent .isLayoutOnly () ) {
253- NodeIndexPair result = walkUpUntilNonLayoutOnly (parent , indexInNativeChildren );
274+ if (parent .getNativeKind () != NativeKind . PARENT ) {
275+ NodeIndexPair result = walkUpUntilNativeKindIsParent (parent , indexInNativeChildren );
254276 if (result == null ) {
255277 // If the parent hasn't been attached to its native parent yet, don't issue commands to the
256278 // native hierarchy. We'll do that when the parent node actually gets attached somewhere.
@@ -260,20 +282,26 @@ private void addNodeToNode(ReactShadowNode parent, ReactShadowNode child, int in
260282 indexInNativeChildren = result .index ;
261283 }
262284
263- if (! child .isLayoutOnly () ) {
264- addNonLayoutNode (parent , child , indexInNativeChildren );
285+ if (child .getNativeKind () != NativeKind . NONE ) {
286+ addNativeChild (parent , child , indexInNativeChildren );
265287 } else {
266- addLayoutOnlyNode (parent , child , indexInNativeChildren );
288+ addNonNativeChild (parent , child , indexInNativeChildren );
267289 }
268290 }
269291
270292 /**
271- * For handling node removal from manageChildren. In the case of removing a layout-only node, we
272- * need to instead recursively remove all its children from their native parents.
293+ * For handling node removal from manageChildren. In the case of removing a node which isn't
294+ * hosting its own children (e.g. layout-only or NativeKind.LEAF), we need to recursively remove
295+ * all its children from their native parents.
273296 */
274297 private void removeNodeFromParent (ReactShadowNode nodeToRemove , boolean shouldDelete ) {
275- ReactShadowNode nativeNodeToRemoveFrom = nodeToRemove .getNativeParent ();
298+ if (nodeToRemove .getNativeKind () != NativeKind .PARENT ) {
299+ for (int i = nodeToRemove .getChildCount () - 1 ; i >= 0 ; i --) {
300+ removeNodeFromParent (nodeToRemove .getChildAt (i ), shouldDelete );
301+ }
302+ }
276303
304+ ReactShadowNode nativeNodeToRemoveFrom = nodeToRemove .getNativeParent ();
277305 if (nativeNodeToRemoveFrom != null ) {
278306 int index = nativeNodeToRemoveFrom .indexOfNativeChild (nodeToRemove );
279307 nativeNodeToRemoveFrom .removeNativeChildAt (index );
@@ -283,21 +311,17 @@ private void removeNodeFromParent(ReactShadowNode nodeToRemove, boolean shouldDe
283311 new int []{index },
284312 null ,
285313 shouldDelete ? new int []{nodeToRemove .getReactTag ()} : null );
286- } else {
287- for (int i = nodeToRemove .getChildCount () - 1 ; i >= 0 ; i --) {
288- removeNodeFromParent (nodeToRemove .getChildAt (i ), shouldDelete );
289- }
290314 }
291315 }
292316
293- private void addLayoutOnlyNode (
294- ReactShadowNode nonLayoutOnlyNode ,
295- ReactShadowNode layoutOnlyNode ,
317+ private void addNonNativeChild (
318+ ReactShadowNode nativeParent ,
319+ ReactShadowNode nonNativeChild ,
296320 int index ) {
297- addGrandchildren (nonLayoutOnlyNode , layoutOnlyNode , index );
321+ addGrandchildren (nativeParent , nonNativeChild , index );
298322 }
299323
300- private void addNonLayoutNode (
324+ private void addNativeChild (
301325 ReactShadowNode parent ,
302326 ReactShadowNode child ,
303327 int index ) {
@@ -307,30 +331,33 @@ private void addNonLayoutNode(
307331 null ,
308332 new ViewAtIndex []{new ViewAtIndex (child .getReactTag (), index )},
309333 null );
334+
335+ if (child .getNativeKind () != NativeKind .PARENT ) {
336+ addGrandchildren (parent , child , index + 1 );
337+ }
310338 }
311339
312340 private void addGrandchildren (
313341 ReactShadowNode nativeParent ,
314342 ReactShadowNode child ,
315343 int index ) {
316- Assertions .assertCondition (! nativeParent . isLayoutOnly () );
344+ Assertions .assertCondition (child . getNativeKind () != NativeKind . PARENT );
317345
318346 // `child` can't hold native children. Add all of `child`'s children to `parent`.
319347 int currentIndex = index ;
320348 for (int i = 0 ; i < child .getChildCount (); i ++) {
321349 ReactShadowNode grandchild = child .getChildAt (i );
322350 Assertions .assertCondition (grandchild .getNativeParent () == null );
323351
324- if (grandchild .isLayoutOnly ()) {
325- // Adding this child could result in adding multiple native views
326- int grandchildCountBefore = nativeParent .getNativeChildCount ();
327- addLayoutOnlyNode (nativeParent , grandchild , currentIndex );
328- int grandchildCountAfter = nativeParent .getNativeChildCount ();
329- currentIndex += grandchildCountAfter - grandchildCountBefore ;
352+ // Adding this child could result in adding multiple native views
353+ int grandchildCountBefore = nativeParent .getNativeChildCount ();
354+ if (grandchild .getNativeKind () == NativeKind .NONE ) {
355+ addNonNativeChild (nativeParent , grandchild , currentIndex );
330356 } else {
331- addNonLayoutNode (nativeParent , grandchild , currentIndex );
332- currentIndex ++;
357+ addNativeChild (nativeParent , grandchild , currentIndex );
333358 }
359+ int grandchildCountAfter = nativeParent .getNativeChildCount ();
360+ currentIndex += grandchildCountAfter - grandchildCountBefore ;
334361 }
335362 }
336363
@@ -349,7 +376,7 @@ private void applyLayoutBase(ReactShadowNode node) {
349376 int x = node .getScreenX ();
350377 int y = node .getScreenY ();
351378
352- while (parent != null && parent .isLayoutOnly () ) {
379+ while (parent != null && parent .getNativeKind () != NativeKind . PARENT ) {
353380 // TODO(7854667): handle and test proper clipping
354381 x += Math .round (parent .getLayoutX ());
355382 y += Math .round (parent .getLayoutY ());
@@ -361,7 +388,7 @@ private void applyLayoutBase(ReactShadowNode node) {
361388 }
362389
363390 private void applyLayoutRecursive (ReactShadowNode toUpdate , int x , int y ) {
364- if (! toUpdate .isLayoutOnly () && toUpdate .getNativeParent () != null ) {
391+ if (toUpdate .getNativeKind () != NativeKind . NONE && toUpdate .getNativeParent () != null ) {
365392 int tag = toUpdate .getReactTag ();
366393 mUIViewOperationQueue .enqueueUpdateLayout (
367394 toUpdate .getNativeParent ().getReactTag (),
0 commit comments