From dedf9372cae89d119930a8a2c33b337b6658bff2 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Mon, 9 Mar 2020 15:53:19 -0700 Subject: [PATCH] Track animations and flush them Summary: Changelog: [Internal] Track delete animations and when `manageChildren` is called on a certain view tag, finish all pending deletion animations before manipulating children Reviewed By: JoshuaGross Differential Revision: D20319824 fbshipit-source-id: b594d0e6e9b6fecc5eca2938f284be631494e55c --- .../uimanager/NativeViewHierarchyManager.java | 3 ++ .../LayoutAnimationController.java | 41 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) 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 3e70a3553b6..93ccf116e3e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -363,6 +363,8 @@ public class NativeViewHierarchyManager { @Nullable int[] tagsToDelete) { UiThreadUtil.assertOnUiThread(); + mLayoutAnimator.cancelAnimationsForViewTag(tag); + final ViewGroup viewToManage = (ViewGroup) mTagsToViews.get(tag); final ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(tag); if (viewToManage == null) { @@ -445,6 +447,7 @@ public class NativeViewHierarchyManager { if (mLayoutAnimationEnabled && mLayoutAnimator.shouldAnimateLayout(viewToDestroy)) { mLayoutAnimator.deleteView( + tag, viewToDestroy, new LayoutAnimationListener() { @Override 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 046d757e2af..148b9a4c8e7 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 @@ -17,6 +17,7 @@ import androidx.annotation.Nullable; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; +import java.util.ArrayList; import javax.annotation.concurrent.NotThreadSafe; /** @@ -31,6 +32,8 @@ public class LayoutAnimationController { private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation(); private final AbstractLayoutAnimation mLayoutDeleteAnimation = new LayoutDeleteAnimation(); private final SparseArray mLayoutHandlers = new SparseArray<>(0); + private final SparseArray> mDeleteAnimationsByParentTag = + new SparseArray<>(); private boolean mShouldAnimateLayout; private long mMaxAnimationDuration = -1; @@ -113,6 +116,7 @@ public class LayoutAnimationController { // Update an ongoing animation if possible, otherwise the layout update would be ignored as // the existing animation would still animate to the old layout. + // Note the view is already inserted into the view hierarchy. LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag); if (existingAnimation != null) { existingAnimation.onLayoutUpdate(x, y, width, height); @@ -164,11 +168,14 @@ public class LayoutAnimationController { * Animate a view deletion using the layout animation configuration supplied during * initialization. * + * @param parentReactTag tag of parent view of @param view. used to associate animation with for + * canceling * @param view The view to animate. * @param listener Called once the animation is finished, should be used to completely remove the * view. */ - public void deleteView(final View view, final LayoutAnimationListener listener) { + public void deleteView( + final int parentReactTag, final View view, final LayoutAnimationListener listener) { UiThreadUtil.assertOnUiThread(); Animation animation = @@ -188,6 +195,10 @@ public class LayoutAnimationController { @Override public void onAnimationEnd(Animation anim) { + ArrayList animations = mDeleteAnimationsByParentTag.get(parentReactTag); + if (animations != null) { + animations.remove(anim); + } listener.onAnimationEnd(); } }); @@ -198,7 +209,16 @@ public class LayoutAnimationController { mMaxAnimationDuration = animationDuration; } + // Update our tracking list of delete animations + ArrayList deleteAnimations = mDeleteAnimationsByParentTag.get(parentReactTag); + if (deleteAnimations == null) { + deleteAnimations = new ArrayList<>(); + mDeleteAnimationsByParentTag.put(parentReactTag, deleteAnimations); + } + deleteAnimations.add(animation); + view.startAnimation(animation); + } else { listener.onAnimationEnd(); } @@ -225,4 +245,23 @@ public class LayoutAnimationController { sCompletionHandler.postDelayed(mCompletionRunnable, delayMillis); } } + + /** + * Animate a view deletion using the layout animation configuration supplied during + * initialization. + * + * @param viewTag tag of parent view that we're going to cancel all child animations. + */ + public void cancelAnimationsForViewTag(int viewTag) { + ArrayList animations = mDeleteAnimationsByParentTag.get(viewTag); + if (animations == null) { + return; + } + + for (int i = 0; i < animations.size(); i++) { + animations.get(i).cancel(); + } + + mDeleteAnimationsByParentTag.remove(viewTag); + } }