@@ -9,76 +9,14 @@ import com.facebook.react.uimanager.UIManagerHelper
99import com.swmansion.rnscreens.Screen.StackAnimation
1010import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
1111import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
12+ import com.swmansion.rnscreens.stack.views.ChildrenDrawingOrderStrategy
13+ import com.swmansion.rnscreens.stack.views.ReverseFromIndex
14+ import com.swmansion.rnscreens.stack.views.ReverseOrder
15+ import com.swmansion.rnscreens.stack.views.ScreensCoordinatorLayout
1216import com.swmansion.rnscreens.utils.setTweenAnimations
13- import java.util.Collections
1417import kotlin.collections.ArrayList
1518import kotlin.math.max
1619
17- internal interface ChildDrawingOrderStrategy {
18- /* *
19- * Mutates the list of draw operations **in-place**.
20- */
21- fun apply (drawingOperations : MutableList <ScreenStack .DrawingOp >)
22-
23- /* *
24- * Enables the given strategy. When enabled - the strategy **might** mutate the operations
25- * list passed to `apply` method.
26- */
27- fun enable ()
28-
29- /* *
30- * Disables the given strategy - even when `apply` is called it **must not** produce
31- * any side effect (it must not manipulate the drawing operations list passed to `apply` method).
32- */
33- fun disable ()
34-
35- fun isEnabled (): Boolean
36- }
37-
38- internal abstract class ChildDrawingOrderStrategyBase (
39- var enabled : Boolean = false ,
40- ) : ChildDrawingOrderStrategy {
41- override fun enable () {
42- enabled = true
43- }
44-
45- override fun disable () {
46- enabled = false
47- }
48-
49- override fun isEnabled () = enabled
50- }
51-
52- internal class SwapLastTwo : ChildDrawingOrderStrategyBase () {
53- override fun apply (drawingOperations : MutableList <ScreenStack .DrawingOp >) {
54- if (! isEnabled()) {
55- return
56- }
57- if (drawingOperations.size >= 2 ) {
58- Collections .swap(drawingOperations, drawingOperations.lastIndex, drawingOperations.lastIndex - 1 )
59- }
60- }
61- }
62-
63- internal class ReverseOrderInRange (
64- val range : IntRange ,
65- ) : ChildDrawingOrderStrategyBase() {
66- override fun apply (drawingOperations : MutableList <ScreenStack .DrawingOp >) {
67- if (! isEnabled()) {
68- return
69- }
70-
71- var startRange = range.start
72- var endRange = range.endInclusive
73-
74- while (startRange < endRange) {
75- Collections .swap(drawingOperations, startRange, endRange)
76- startRange + = 1
77- endRange - = 1
78- }
79- }
80- }
81-
8220class ScreenStack (
8321 context : Context ? ,
8422) : ScreenContainer(context) {
@@ -88,9 +26,9 @@ class ScreenStack(
8826 private var drawingOps: MutableList <DrawingOp > = ArrayList ()
8927 private var topScreenWrapper: ScreenStackFragmentWrapper ? = null
9028 private var removalTransitionStarted = false
91- private var previousChildrenCount = 0
9229
93- private var childDrawingOrderStrategy: ChildDrawingOrderStrategy ? = null
30+ private var childrenDrawingOrderStrategy: ChildrenDrawingOrderStrategy ? = null
31+ private var disappearingTransitioningChildren: MutableList <View > = ArrayList ()
9432
9533 var goingForward = false
9634
@@ -122,14 +60,25 @@ class ScreenStack(
12260 }
12361
12462 override fun startViewTransition (view : View ) {
63+ check(view is ScreensCoordinatorLayout ) { " [RNScreens] Unexpected type of ScreenStack direct subview ${view.javaClass} " }
12564 super .startViewTransition(view)
126- childDrawingOrderStrategy?.enable()
65+ if (view.fragment.isRemoving) {
66+ disappearingTransitioningChildren.add(view)
67+ }
68+ if (disappearingTransitioningChildren.isNotEmpty()) {
69+ childrenDrawingOrderStrategy?.enable()
70+ }
12771 removalTransitionStarted = true
12872 }
12973
13074 override fun endViewTransition (view : View ) {
13175 super .endViewTransition(view)
132- childDrawingOrderStrategy?.disable()
76+
77+ disappearingTransitioningChildren.remove(view)
78+
79+ if (disappearingTransitioningChildren.isEmpty()) {
80+ childrenDrawingOrderStrategy?.disable()
81+ }
13382 if (removalTransitionStarted) {
13483 removalTransitionStarted = false
13584 dispatchOnFinishTransitioning()
@@ -172,14 +121,17 @@ class ScreenStack(
172121 var visibleBottom: ScreenFragmentWrapper ? = null
173122
174123 // reset, to not use previously set strategy by mistake
175- childDrawingOrderStrategy = null
124+ childrenDrawingOrderStrategy = null
176125
177126 // Determine new first & last visible screens.
178127 val notDismissedWrappers =
179128 screenWrappers
180129 .asReversed()
181130 .asSequence()
182- .filter { ! dismissedWrappers.contains(it) && it.screen.activityState != = Screen .ActivityState .INACTIVE }
131+ .filter {
132+ ! dismissedWrappers.contains(it) &&
133+ it.screen.activityState != = Screen .ActivityState .INACTIVE
134+ }
183135
184136 newTop = notDismissedWrappers.firstOrNull()
185137 visibleBottom =
@@ -226,16 +178,16 @@ class ScreenStack(
226178 needsDrawReordering(newTop, stackAnimation) &&
227179 visibleBottom == null
228180 ) {
229- // When using an open animation in which two screens overlap (eg. fade_from_bottom or
230- // slide_from_bottom), we want to draw the previous screen under the new one,
231- // which is apparently not the default option. Android always draws the disappearing view
181+ // When using an open animation in which screens overlap (eg. fade_from_bottom or
182+ // slide_from_bottom), we want to draw the previous screens under the new one,
183+ // which is apparently not the default option. Android always draws the disappearing views
232184 // on top of the appearing one. We then reverse the order of the views so the new screen
233- // appears on top of the previous one . You can read more about in the comment
185+ // appears on top of the previous ones . You can read more about in the comment
234186 // for the code we use to change that behavior:
235187 // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18
236188 // Note: This should not be set in case there is only a single screen in stack or animation `none` is used.
237189 // Atm needsDrawReordering implementation guards that assuming that first screen on stack uses `NONE` animation.
238- childDrawingOrderStrategy = SwapLastTwo ()
190+ childrenDrawingOrderStrategy = ReverseOrder ()
239191 } else if (newTop != null &&
240192 newTopAlreadyInStack &&
241193 topScreenWrapper?.isTranslucent() == true &&
@@ -253,8 +205,8 @@ class ScreenStack(
253205 it.isTranslucent()
254206 }.count()
255207 if (dismissedTransparentScreenApproxCount > 1 ) {
256- childDrawingOrderStrategy =
257- ReverseOrderInRange (max(stack.lastIndex - dismissedTransparentScreenApproxCount + 1 , 0 ).. stack.lastIndex )
208+ childrenDrawingOrderStrategy =
209+ ReverseFromIndex (max(stack.lastIndex - dismissedTransparentScreenApproxCount + 1 , 0 ))
258210 }
259211 }
260212
@@ -351,13 +303,7 @@ class ScreenStack(
351303 override fun dispatchDraw (canvas : Canvas ) {
352304 super .dispatchDraw(canvas)
353305
354- // check the view removal is completed (by comparing the previous children count)
355- if (drawingOps.size < previousChildrenCount) {
356- childDrawingOrderStrategy = null
357- }
358- previousChildrenCount = drawingOps.size
359-
360- childDrawingOrderStrategy?.apply (drawingOps)
306+ childrenDrawingOrderStrategy?.apply (drawingOps)
361307
362308 drawAndRelease()
363309 }
@@ -407,7 +353,7 @@ class ScreenStack(
407353 fragmentWrapper : ScreenFragmentWrapper ,
408354 resolvedStackAnimation : StackAnimation ? ,
409355 ): Boolean {
410- val stackAnimation = if ( resolvedStackAnimation != null ) resolvedStackAnimation else fragmentWrapper.screen.stackAnimation
356+ val stackAnimation = resolvedStackAnimation ? : fragmentWrapper.screen.stackAnimation
411357 // On Android sdk 33 and above the animation is different and requires draw reordering.
412358 // For React Native 0.70 and lower versions, `Build.VERSION_CODES.TIRAMISU` is not defined yet.
413359 // Hence, we're comparing numerical version here.
0 commit comments