diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index a87602943a0b11..ef61834f169bac 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -7772,6 +7772,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro protected fun dispatchSetPressed (Z)V public fun draw (Landroid/graphics/Canvas;)V protected fun drawChild (Landroid/graphics/Canvas;Landroid/view/View;J)Z + public fun endViewTransition (Landroid/view/View;)V protected fun getChildDrawingOrder (II)I public fun getClippingRect (Landroid/graphics/Rect;)V public fun getHitSlopRect ()Landroid/graphics/Rect; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt index da19e945b52a9b..2f2453e97a1d0d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt @@ -62,9 +62,6 @@ public abstract class ReactClippingViewManager : ViewGroupMa if (removeClippedSubviews) { val child = getChildAt(parent, index) if (child != null) { - if (child.parent != null) { - parent.removeView(child) - } parent.removeViewWithSubviewClippingEnabled(child) } } else { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index ce6eb2c838b71e..a245f7ffae273b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -138,6 +138,7 @@ public void shutdown() { private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper; private float mBackfaceOpacity; private String mBackfaceVisibility; + private @Nullable Set mChildrenRemovedWhileTransitioning; /** * Creates a new `ReactViewGroup` instance. @@ -172,6 +173,7 @@ private void initView() { mDrawingOrderHelper = null; mBackfaceOpacity = 1.f; mBackfaceVisibility = "visible"; + mChildrenRemovedWhileTransitioning = null; } /* package */ void recycleView() { @@ -354,6 +356,7 @@ public void setRemoveClippedSubviews(boolean removeClippedSubviews) { return; } mRemoveClippedSubviews = removeClippedSubviews; + mChildrenRemovedWhileTransitioning = null; if (removeClippedSubviews) { mClippingRect = new Rect(); ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect); @@ -408,6 +411,26 @@ public void updateClippingRect() { updateClippingToRect(mClippingRect); } + @Override + public void endViewTransition(View view) { + super.endViewTransition(view); + if (mChildrenRemovedWhileTransitioning != null) { + mChildrenRemovedWhileTransitioning.remove(view.getId()); + } + } + + private void trackChildViewTransition(int childId) { + if (mChildrenRemovedWhileTransitioning == null) { + mChildrenRemovedWhileTransitioning = new HashSet<>(); + } + mChildrenRemovedWhileTransitioning.add(childId); + } + + private boolean isChildRemovedWhileTransitioning(View child) { + return mChildrenRemovedWhileTransitioning != null + && mChildrenRemovedWhileTransitioning.contains(child.getId()); + } + private void updateClippingToRect(Rect clippingRect) { Assertions.assertNotNull(mAllChildren); mInSubviewClippingLoop = true; @@ -573,6 +596,12 @@ public void onViewRemoved(View child) { } else { setChildrenDrawingOrderEnabled(false); } + + // The parent might not be null in case the child is transitioning. + if (child.getParent() != null) { + trackChildViewTransition(child.getId()); + } + super.onViewRemoved(child); } @@ -745,6 +774,7 @@ private boolean isViewClipped(View view, @Nullable Integer index) { return (boolean) tag; } ViewParent parent = view.getParent(); + boolean transitioning = isChildRemovedWhileTransitioning(view); if (index != null) { ReactSoftExceptionLogger.logSoftException( "ReactViewGroup.isViewClipped", @@ -754,10 +784,12 @@ private boolean isViewClipped(View view, @Nullable Integer index) { + " parentNull=" + (parent == null) + " parentThis=" - + (parent == this))); + + (parent == this) + + " transitioning=" + + transitioning)); } - // fallback - parent *should* be null if the view was removed - if (parent == null) { + // fallback - should be transitioning or have no parent if the view was removed + if (parent == null || transitioning) { return true; } else { Assertions.assertCondition(parent == this);