diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index e6dda5a6c97d90..361c0906683e59 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -373,8 +373,9 @@ public synchronized void manageChildren( if (mLayoutAnimationEnabled && mLayoutAnimator.shouldAnimateLayout(viewToRemove) && arrayContains(tagsToDelete, viewToRemove.getId())) { - // The view will be removed and dropped by the 'delete' layout animation - // instead, so do nothing + // Only remove the view from the 'React' view hierarchy, it will be removed from native + // at the end of the layout delete animation. + viewManager.removeReactViewAt(viewToManage, indexToRemove); } else { viewManager.removeViewAt(viewToManage, indexToRemove); } @@ -423,7 +424,7 @@ public synchronized void manageChildren( mLayoutAnimator.deleteView(viewToDestroy, new LayoutAnimationListener() { @Override public void onAnimationEnd() { - viewManager.removeView(viewToManage, viewToDestroy); + viewManager.removeNativeView(viewToManage, viewToDestroy); dropView(viewToDestroy); } }); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java index 103529a0e96857..b018ad882add4e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java @@ -10,9 +10,6 @@ package com.facebook.react.uimanager; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.WeakHashMap; @@ -28,6 +25,9 @@ public abstract class ViewGroupManager extends BaseViewManager { private static WeakHashMap mZIndexHash = new WeakHashMap<>(); + // This is used to remove views from the React view hierarchy but keep them in the Native one. + // Mapping from the parent view to a list of its children. + private static WeakHashMap> mReactChildrenMap = new WeakHashMap<>(); @Override public LayoutShadowNode createShadowNodeInstance() { @@ -44,7 +44,19 @@ public void updateExtraData(T root, Object extraData) { } public void addView(T parent, View child, int index) { - parent.addView(child, index); + List reactChildren = mReactChildrenMap.get(parent); + if (reactChildren != null) { + reactChildren.add(index, child); + if (index == reactChildren.size() - 1) { + parent.addView(child); + } else { + View addBeforeView = reactChildren.get(index + 1); + int addBeforeIndex = parent.indexOfChild(addBeforeView); + parent.addView(child, addBeforeIndex - 1); + } + } else { + parent.addView(child, index); + } } /** @@ -69,15 +81,36 @@ public static void setViewZIndex(View view, int zIndex) { } public int getChildCount(T parent) { - return parent.getChildCount(); + List reactChildren = mReactChildrenMap.get(parent); + if (reactChildren != null) { + return reactChildren.size(); + } else { + return parent.getChildCount(); + } } public View getChildAt(T parent, int index) { - return parent.getChildAt(index); + List reactChildren = mReactChildrenMap.get(parent); + if (reactChildren != null) { + return reactChildren.get(index); + } else { + return parent.getChildAt(index); + } } public void removeViewAt(T parent, int index) { - parent.removeViewAt(index); + List reactChildren = mReactChildrenMap.get(parent); + if (reactChildren != null) { + View removedView = reactChildren.remove(index); + if (removedView != null) { + parent.removeView(removedView); + } + mReactChildrenMap.remove(removedView); + } else { + View removedView = parent.getChildAt(index); + parent.removeViewAt(index); + mReactChildrenMap.remove(removedView); + } } public void removeView(T parent, View view) { @@ -95,6 +128,35 @@ public void removeAllViews(T parent) { } } + /** + * Remove a view from the React view hierarchy but keep the actual native view there. Use + * {@link ViewGroupManager#removeNativeView} to remove it completely. + */ + public void removeReactViewAt(T parent, int index) { + List reactChildren = mReactChildrenMap.get(parent); + + // Create the React children mapping lazily the first time it diverges from native. + if (reactChildren == null) { + reactChildren = new ArrayList<>(parent.getChildCount()); + for (int i = 0; i < parent.getChildCount(); i++) { + reactChildren.add(parent.getChildAt(i)); + } + mReactChildrenMap.put(parent, reactChildren); + } + reactChildren.remove(index); + } + + /** + * Remove a view that was previously removed from the React view hierarchy but not from native. + */ + public void removeNativeView(T parent, View view) { + List reactChildren = mReactChildrenMap.get(parent); + if (reactChildren != null) { + reactChildren.remove(view); + } + parent.removeView(view); + } + /** * Returns whether this View type needs to handle laying out its own children instead of * deferring to the standard css-layout algorithm. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandleLayout.java similarity index 100% rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java rename to ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandleLayout.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java index d5a08e6b264ac5..c7a9c302337339 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java @@ -97,6 +97,7 @@ public void applyLayoutUpdate(View view, int x, int y, int width, int height) { view.layout(x, y, x + width, y + height); } if (animation != null) { + view.clearAnimation(); view.startAnimation(animation); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java index a7ad690023c500..86533910179969 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java @@ -15,10 +15,12 @@ static class OpacityAnimationListener implements AnimationListener { private final View mView; + private final float mEndOpacity; private boolean mLayerTypeChanged = false; - public OpacityAnimationListener(View view) { + public OpacityAnimationListener(View view, float endOpacity) { mView = view; + mEndOpacity = endOpacity; } @Override @@ -35,6 +37,7 @@ public void onAnimationEnd(Animation animation) { if (mLayerTypeChanged) { mView.setLayerType(View.LAYER_TYPE_NONE, null); } + mView.setAlpha(mEndOpacity); } @Override @@ -51,7 +54,7 @@ public OpacityAnimation(View view, float startOpacity, float endOpacity) { mStartOpacity = startOpacity; mDeltaOpacity = endOpacity - startOpacity; - setAnimationListener(new OpacityAnimationListener(view)); + setAnimationListener(new OpacityAnimationListener(view, endOpacity)); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 3700d201af8970..8141696db00911 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -206,7 +206,7 @@ public void addView(ReactViewGroup parent, View child, int index) { if (removeClippedSubviews) { parent.addViewWithSubviewClippingEnabled(child, index); } else { - parent.addView(child, index); + super.addView(parent, child, index); } } @@ -216,7 +216,7 @@ public int getChildCount(ReactViewGroup parent) { if (removeClippedSubviews) { return parent.getAllChildrenCount(); } else { - return parent.getChildCount(); + return super.getChildCount(parent); } } @@ -226,7 +226,7 @@ public View getChildAt(ReactViewGroup parent, int index) { if (removeClippedSubviews) { return parent.getChildAtWithSubviewClippingEnabled(index); } else { - return parent.getChildAt(index); + return super.getChildAt(parent, index); } } @@ -240,7 +240,7 @@ public void removeViewAt(ReactViewGroup parent, int index) { } parent.removeViewWithSubviewClippingEnabled(child); } else { - parent.removeViewAt(index); + super.removeViewAt(parent, index); } } @@ -250,7 +250,7 @@ public void removeAllViews(ReactViewGroup parent) { if (removeClippedSubviews) { parent.removeAllViewsWithSubviewClippingEnabled(); } else { - parent.removeAllViews(); + super.removeAllViews(parent); } } }