diff --git a/FabricExample/android/app/src/main/java/com/fabricexample/MainApplication.kt b/FabricExample/android/app/src/main/java/com/fabricexample/MainApplication.kt index 6d8b249bc1..009bd5a716 100644 --- a/FabricExample/android/app/src/main/java/com/fabricexample/MainApplication.kt +++ b/FabricExample/android/app/src/main/java/com/fabricexample/MainApplication.kt @@ -5,10 +5,7 @@ import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactHost import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost -import com.facebook.react.soloader.OpenSourceMergedSoMapping -import com.facebook.soloader.SoLoader class MainApplication : Application(), ReactApplication { diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index eb3e7182a9..96f3b48f15 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -398,27 +398,21 @@ class Screen( return } isBeingRemoved = false - endTransitionRecursive(this) + endViewTransition() } - private fun endTransitionRecursive(parent: ViewGroup) { - parent.children.forEach { childView -> - parent.endViewTransition(childView) - - if (childView is ScreenStackHeaderConfig) { - endTransitionRecursive(childView.toolbar) - } - - if (childView is ViewGroup) { - endTransitionRecursive(childView) - } - } - } + private val transitioningViews = mutableListOf>() + /** + * Called when a screen gets removed. This is to mark all children as in transition, + * so they are not removed from the view hierarchy until endRemovalTransition is called. + * This is needed for the screen transition animation to work properly (otherwise the children + * would instantly disappear from the screen). + */ private fun startTransitionRecursive(parent: ViewGroup?) { - parent?.let { - for (i in 0 until it.childCount) { - val child = it.getChildAt(i) + parent?.let { parentView -> + for (i in 0 until parentView.childCount) { + val child = parentView.getChildAt(i) if (parent is SwipeRefreshLayout && child is ImageView) { // SwipeRefreshLayout class which has CircleImageView as a child, @@ -427,9 +421,12 @@ class Screen( // wrong index if we called `startViewTransition` on the views on new arch. // We add a simple View to bump the number of children to make it work. // TODO: find a better way to handle this scenario - it.addView(View(context), i) + parentView.addView(View(context), i) } else { - child?.let { view -> it.startViewTransition(view) } + child?.let { childView -> + parentView.startViewTransition(childView) + transitioningViews.add(Pair(parentView, childView)) + } } if (child is ScreenStackHeaderConfig) { @@ -445,6 +442,21 @@ class Screen( } } + /** + * Called when the removal transition is finished. This will clear the transition state + * from all children and allow them to be removed from the view hierarchy and their mParent + * field to be set to null. + */ + private fun endViewTransition() { + // IMPORTANT: Reverse order is needed, inner children first! + // Otherwise parents will call dispatchOnDetachedFromWindow on all their children, + // which will cause endViewTransition to have no effect on them anymore. + transitioningViews.asReversed().forEach { (parent, child) -> + parent.endViewTransition(child) + } + transitioningViews.clear() + } + // We do not want to perform any action, therefore do not need to override the associated method. @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent?): Boolean = diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 9894cd4be0..22d27e0727 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -73,6 +73,10 @@ class ScreenStack( } override fun endViewTransition(view: View) { + if (view is ScreensCoordinatorLayout) { + view.fragment.screen.endRemovalTransition() + } + super.endViewTransition(view) disappearingTransitioningChildren.remove(view) diff --git a/apps/src/tests/TestRecyclingViews.tsx b/apps/src/tests/TestRecyclingViews.tsx new file mode 100644 index 0000000000..9edd3a9543 --- /dev/null +++ b/apps/src/tests/TestRecyclingViews.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import {Button, requireNativeComponent, View} from 'react-native'; +import {NavigationContainer, useNavigation} from '@react-navigation/native'; +import {createNativeStackNavigator} from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +let CustomView = requireNativeComponent('CustomView'); + +function HomeScreen() { + const navigation = useNavigation(); + return ( + +