Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Android): restore default Android animations #2110

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

tboba
Copy link
Member

@tboba tboba commented Apr 24, 2024

Description

TL;DR: Default Android animations are finally there! \o/

Currently, in React Native Screens we're using XML-based Android custom animations, even for the default transitions, provided by system. The main reason behind it was the fact, that during the times when this PR was introduced, events related to the screen lifecycle (will appear, appear, will disappear, disappear) were really unstable and it was painful to ensure that onAppear won't be called twice.
This PR restores default Android transitions for animations: default, none, fade and fixes earlier problems, related to the lifecycle by dispatching those events in Animator.

Fixes #2100, fixes #1968 and should invalidate all of the previous issues: #1223 #1640 #1879.

Changes

  • Restored system animations by using setTransition on FragmentTransaction
  • Added onCreateAnimator in ScreenStackFragment

Screenshots / GIFs

Will add later 😁

Before

After

Test code and steps to reproduce

You can use Test1072.tsx to check how the animations look like.

Checklist

  • Ensured that CI passes

@efstathiosntonas
Copy link

efstathiosntonas commented Apr 26, 2024

pasting the patch here just in case someone wants to check fast without installing this PR:

react-native-screens+3.31.1.patch

Click me
diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt
index b31e378..acfe9ad 100644
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt
+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt
@@ -278,4 +278,9 @@ class Screen(context: ReactContext?) : FabricEnabledViewGroup(context) {
     enum class WindowTraits {
         ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED, NAVIGATION_BAR_COLOR, NAVIGATION_BAR_HIDDEN
     }
+    companion object {
+        fun isSystemAnimation(stackAnimation: StackAnimation): Boolean {
+            return stackAnimation === StackAnimation.DEFAULT || stackAnimation === StackAnimation.FADE || stackAnimation === StackAnimation.NONE
+        }
+    }
 }
diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt
index 3915dd6..2fc1ac4 100644
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt
+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt
@@ -272,7 +272,7 @@ open class ScreenFragment : Fragment, ScreenFragmentWrapper {
             // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root
             // view. We override an appropriate method of the StackFragment's
             // root view in order to achieve this.
-            if (isResumed) {
+            if (isResumed || screen.container?.topScreen === screen) {
                 // Android dispatches the animation start event for the fragment that is being added first
                 // however we want the one being dismissed first to match iOS. It also makes more sense from
                 // a navigation point of view to have the disappear event first.
diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
index 458465c..e12136e 100644
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
@@ -4,6 +4,7 @@ import android.content.Context
 import android.graphics.Canvas
 import android.os.Build
 import android.view.View
+import androidx.fragment.app.FragmentTransaction
 import com.facebook.react.bridge.ReactContext
 import com.facebook.react.uimanager.UIManagerHelper
 import com.swmansion.rnscreens.Screen.StackAnimation
@@ -139,9 +140,9 @@ class ScreenStack(context: Context?) : ScreenContainer(context) {
             if (stackAnimation != null) {
                 if (shouldUseOpenAnimation) {
                     when (stackAnimation) {
-                        StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_enter_in, R.anim.rns_default_enter_out)
-                        StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20)
-                        StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out)
+                        StackAnimation.DEFAULT -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+                        StackAnimation.NONE -> it.setTransition(FragmentTransaction.TRANSIT_NONE)
+                        StackAnimation.FADE -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                         StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left)
                         StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right)
                         StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations(
@@ -152,9 +153,9 @@ class ScreenStack(context: Context?) : ScreenContainer(context) {
                     }
                 } else {
                     when (stackAnimation) {
-                        StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_exit_in, R.anim.rns_default_exit_out)
-                        StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20)
-                        StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out)
+                        StackAnimation.DEFAULT -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
+                        StackAnimation.NONE -> it.setTransition(FragmentTransaction.TRANSIT_NONE)
+                        StackAnimation.FADE -> it.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                         StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right)
                         StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left)
                         StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations(
diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
index 48ff684..cdf2896 100644
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
@@ -1,5 +1,9 @@
 package com.swmansion.rnscreens
 
+import android.animation.Animator
+import android.animation.AnimatorInflater
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
 import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.Color
@@ -16,6 +20,9 @@ import android.view.animation.Transformation
 import android.widget.LinearLayout
 import androidx.appcompat.widget.Toolbar
 import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.fragment.R
+import androidx.fragment.app.FragmentTransaction
+import com.facebook.react.bridge.UiThreadUtil
 import com.facebook.react.uimanager.PixelUtil
 import com.google.android.material.appbar.AppBarLayout
 import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
@@ -86,6 +93,53 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper {
         notifyViewAppearTransitionEnd()
     }
 
+    // Similarly to ScreensCoordinatorLayout, this method listens only for the phases of default Android
+    // transitions (default/none/fade), since `ScreensCoordinatorLayout#startAnimation` is being
+    // called only for custom animations.
+    override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator? {
+        val listener = object : AnimatorListenerAdapter() {
+            override fun onAnimationStart(animation: Animator) {
+                onViewAnimationStart()
+                super.onAnimationStart(animation)
+            }
+
+            override fun onAnimationEnd(animation: Animator) {
+                onViewAnimationEnd()
+                super.onAnimationEnd(animation)
+            }
+        }
+
+        // If there's custom animation set, use default onCreateAnimator implementation, as event
+        // handling will be handled by ScreensCoordinatorLayout.
+        if (!Screen.isSystemAnimation(screen.stackAnimation)) {
+            return super.onCreateAnimator(transit, enter, nextAnim)
+        }
+
+        // When fragment is being removed or there's no transition selected, we simply
+        // return AnimatorSet without any animation.
+        if (isRemoving || transit == FragmentTransaction.TRANSIT_NONE) {
+            return AnimatorSet().apply {
+                addListener(listener)
+            }
+        }
+
+        var selectedNextAnim = nextAnim
+        if (nextAnim == 0) {
+            selectedNextAnim = when (transit) {
+                FragmentTransaction.TRANSIT_FRAGMENT_OPEN -> if (enter) R.animator.fragment_open_enter else R.animator.fragment_open_exit
+                FragmentTransaction.TRANSIT_FRAGMENT_CLOSE -> if (enter) R.animator.fragment_close_enter else R.animator.fragment_close_exit
+                FragmentTransaction.TRANSIT_FRAGMENT_FADE -> if (enter) R.animator.fragment_fade_enter else R.animator.fragment_fade_exit
+                else -> 0
+            }
+        }
+
+        val animator = AnimatorInflater.loadAnimator(context, selectedNextAnim).apply {
+            addListener(listener)
+        }
+
+        return animator
+    }
+
     private fun notifyViewAppearTransitionEnd() {
         val screenStack = view?.parent
         if (screenStack is ScreenStack) {
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_enter_in.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_enter_in.xml
deleted file mode 100644
index 4398c7e..0000000
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_enter_in.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <alpha
-        android:interpolator="@android:interpolator/accelerate_decelerate"
-        android:fromAlpha="0"
-        android:toAlpha="1.0"
-        android:startOffset="100"
-        android:duration="100"/>
-    <scale
-        android:interpolator="@android:interpolator/accelerate_decelerate"
-        android:fromXScale="0.85"
-        android:toXScale="1"
-        android:fromYScale="0.85"
-        android:toYScale="1"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:duration="200"/>
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_enter_out.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_enter_out.xml
deleted file mode 100644
index 84c9175..0000000
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_enter_out.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <alpha
-        android:fromAlpha="1"
-        android:toAlpha="0.4"
-        android:startOffset="100"
-        android:duration="100"
-        android:interpolator="@android:interpolator/accelerate_decelerate" />
-
-    <scale
-        android:interpolator="@android:interpolator/accelerate_decelerate"
-        android:fromXScale="1"
-        android:toXScale="1.15"
-        android:fromYScale="1"
-        android:toYScale="1.15"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:duration="200"/>
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_exit_in.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_exit_in.xml
deleted file mode 100644
index 6d6fa02..0000000
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_exit_in.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-    <alpha
-        android:fromAlpha="0.0"
-        android:toAlpha="1"
-        android:startOffset="50"
-        android:duration="100"/>
-    <scale
-        android:fromXScale="1.15"
-        android:toXScale="1"
-        android:fromYScale="1.15"
-        android:toYScale="1"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:duration="200"/>
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_exit_out.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_exit_out.xml
deleted file mode 100644
index b20a184..0000000
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_default_exit_out.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false"
-    android:zAdjustment="top">
-    <alpha
-        android:fromAlpha="1"
-        android:toAlpha="0.0"
-        android:startOffset="50"
-        android:duration="100"/>
-    <scale
-        android:fromXScale="1"
-        android:toXScale="0.85"
-        android:fromYScale="1"
-        android:toYScale="0.85"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:duration="200"/>
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_in.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_in.xml
deleted file mode 100644
index c78ea61..0000000
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_in.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--Duration taken from debugging source code-->
-<alpha xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="0.0"
-    android:toAlpha="1.0"
-    android:duration="150"
-    />  <!--Remember to change duration in the corresponding xml when modifying it-->
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_to_bottom.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_to_bottom.xml
index 2334521..3b7e7a1 100644
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_to_bottom.xml
+++ b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_fade_to_bottom.xml
@@ -2,14 +2,14 @@
 <!--Android Nougat exit animation-->
 <!--http://aosp.opersys.com/xref/android-7.1.2_r37/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml-->
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+     android:shareInterpolator="false">
     <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-        android:interpolator="@android:interpolator/linear"
-        android:startOffset="100"
-        android:duration="150"/>
+           android:interpolator="@android:interpolator/linear"
+           android:startOffset="100"
+           android:duration="150"/>
     <translate android:fromYDelta="0%" android:toYDelta="8%"
-        android:interpolator="@android:interpolator/accelerate_quint"
-        android:duration="250"/>  <!--we use rns_no_animation_250.xml as the other animation for
+               android:interpolator="@android:interpolator/accelerate_quint"
+               android:duration="250"/>  <!--we use rns_no_animation_250.xml as the other animation for
         this transition since we want both of them to end at the same time. Remember to change
         duration in both files when modifying it-->
 </set>
diff --git a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_no_animation_20.xml b/node_modules/react-native-screens/android/src/main/res/base/anim/rns_no_animation_20.xml
deleted file mode 100644
index 5cc0d23..0000000
--- a/node_modules/react-native-screens/android/src/main/res/base/anim/rns_no_animation_20.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<alpha xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="1.0"
-    android:toAlpha="1.0"
-    android:duration="20"
-    />  <!-- non-zero duration ensures transition events are triggered correctly -->
diff --git a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_enter_in.xml b/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_enter_in.xml
deleted file mode 100644
index 1767203..0000000
--- a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_enter_in.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-
-    <alpha
-        android:fromAlpha="0.0"
-        android:toAlpha="1.0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:anim/linear_interpolator"
-        android:startOffset="50"
-        android:duration="83" />
-
-    <translate
-        android:fromXDelta="10%"
-        android:toXDelta="0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:duration="450" />
-
-    <extend
-        android:fromExtendLeft="10%"
-        android:fromExtendTop="0"
-        android:fromExtendRight="0"
-        android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
-        android:toExtendTop="0"
-        android:toExtendRight="0"
-        android:toExtendBottom="0"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml b/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml
deleted file mode 100644
index e7dd72b..0000000
--- a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="1.0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@anim/rns_standard_accelerate_interpolator"
-        android:startOffset="0"
-        android:duration="450" />
-
-    <translate
-        android:fromXDelta="0"
-        android:toXDelta="-10%"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-
-    <extend
-        android:fromExtendLeft="0"
-        android:fromExtendTop="0"
-        android:fromExtendRight="10%"
-        android:fromExtendBottom="0"
-        android:toExtendLeft="0"
-        android:toExtendTop="0"
-        android:toExtendRight="10%"
-        android:toExtendBottom="0"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_exit_in.xml b/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_exit_in.xml
deleted file mode 100644
index 949ebb7..0000000
--- a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_exit_in.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="1.0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:interpolator/linear"
-        android:startOffset="0"
-        android:duration="450" />
-
-    <translate
-        android:fromXDelta="-10%"
-        android:toXDelta="0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-
-    <extend
-        android:fromExtendLeft="0"
-        android:fromExtendTop="0"
-        android:fromExtendRight="10%"
-        android:fromExtendBottom="0"
-        android:toExtendLeft="0"
-        android:toExtendTop="0"
-        android:toExtendRight="10%"
-        android:toExtendBottom="0"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-</set>
diff --git a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_exit_out.xml b/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_exit_out.xml
deleted file mode 100644
index ba4d84d..0000000
--- a/node_modules/react-native-screens/android/src/main/res/v33/anim-v33/rns_default_exit_out.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:interpolator/linear"
-        android:startOffset="35"
-        android:duration="83" />
-
-    <translate
-        android:fromXDelta="0"
-        android:toXDelta="10%"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-
-    <extend
-        android:fromExtendLeft="10%"
-        android:fromExtendTop="0"
-        android:fromExtendRight="0"
-        android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
-        android:toExtendTop="0"
-        android:toExtendRight="0"
-        android:toExtendBottom="0"
-        android:interpolator="@android:interpolator/fast_out_extra_slow_in"
-        android:startOffset="0"
-        android:duration="450" />
-</set>

@tboba
Copy link
Member Author

tboba commented Apr 26, 2024

Hi @efstathiosntonas, thanks for passing this! Have you checked locally if this change works for you? What do you think about it?

Nevermind, I saw your comment in the issue 😄 Great to hear this works correctly!

@tboba
Copy link
Member Author

tboba commented Apr 26, 2024

@efstathiosntonas One more question, have you tried listening to screen lifecycle events (transitionStart/transitionEnd with changing the closing parameter)? I can observe they should work in correct order, but maybe you have different feeling about them? It's a key point for us to keep the order the same as previous animations, since otherwise this could be a breaking change 👀

@efstathiosntonas
Copy link

efstathiosntonas commented Apr 26, 2024

@tboba tested it also on S22 A14 and Redmi Note 8 A13 and works fine, feels way better.

ps. yarn prepare inside the node_modules/react-native-screens is gonna be a pain 😅

@efstathiosntonas One more question, have you tried listening to screen lifecycle events (transitionStart/transitionEnd with changing the closing parameter)? I can observe they should work in correct order, but maybe you have different feeling about them? It's a key point for us to keep the order the same as previous animations, since otherwise this could be a breaking change 👀

never tried this, can you give or point me to a minimal example?

@tboba
Copy link
Member Author

tboba commented Apr 26, 2024

@efstathiosntonas Yeah! On every screen, you can define listeners prop that will serve as an object of your desired listeners. We've got two interesting events there: transitionStart and transitionEnd (each of them have closing prop inside the data, which defines whether screen disappears (true) or appears (false).

For example, you can define listeners inside the stack definition like this:

          <Stack.Screen
            name="First"
            component={First}
            listeners={{
              transitionStart: ({ data }) => {
                console.log(
                  `screen A: transition start (closing: ${data.closing})`,
                  data,
                );
              },
              transitionEnd: ({ data }) => {
                console.log(
                  `screen A: transition end (closing: ${data.closing}`,
                  data,
                );
              },
            }}
          />
          <Stack.Screen
            name="Second"
            component={Second}
            listeners={{
              transitionStart: ({ data }) => {
                console.log(
                  `screen B: transition start (closing: ${data.closing})`,
                  data,
                );
              },
              transitionEnd: ({ data }) => {
                console.log(
                  `screen B: transition end (closing: ${data.closing}`,
                  data,
                );
              },
            }}
          />

@arasrezaei
Copy link

when available ? 👀

@tboba
Copy link
Member Author

tboba commented May 27, 2024

@arasrezaei Hey! This PR is currently blocked with the issue about the wrong order of disappearing screens from the native-stack. Previously, while the next screen was sending onWillAppear events after onWillDisappear events, it's now being sent after the full disappear of the previous screen. If you're willing to help us with this feature, don't hesitate to cut out from this branch and add further changes! 🙏

@arasrezaei
Copy link

@arasrezaei Hey! This PR is currently blocked with the issue about the wrong order of disappearing screens from the native-stack. Previously, while the next screen was sending onWillAppear events after onWillDisappear events, it's now being sent after the full disappear of the previous screen. If you're willing to help us with this feature, don't hesitate to cut out from this branch and add further changes! 🙏

i wish i could help :))
i don't understand c++

@dhruv-00
Copy link

stack screen transition is not by default native
you can check I've android 14 and this is what twitter's default stack navigation transition looks like

WhatsApp.Video.June.13.1.mp4

and this other app is currently I'm building with expo 51 and react native 0.74 with react-native-screen @3.31.1

WhatsApp.Video.June.13.mp4

you can clearly see that second one is not default native at all and kind of feels a little bit laggy compared to how it was in react native screens @~3.29.0

before this pr #2019

@kirillzyusko
Copy link
Contributor

@dhruv-00 twitter app definitely has custom transitions. It doesn't look like Android 14 default transitions. If you want to see how native transitions look like, then you can open Settings app and check there - and these transactions look very close to transitions from your second video.

@dhruvpvx
Copy link

@dhruv-00 twitter app definitely has custom transitions. It doesn't look like Android 14 default transitions. If you want to see how native transitions look like, then you can open Settings app and check there - and these transactions look very close to transitions from your second video.

I've checked settings app and it is nothing close to as second video it is the same as the first video

@kirillzyusko
Copy link
Contributor

I've checked settings app and it is nothing close to as second video it is the same as the first video

This is how it looks on my Pixel 7 Pro with stock firmware:

screen-20240621-235315.mp4

@dhruvpvx
Copy link

I've checked settings app and it is nothing close to as second video it is the same as the first video

This is how it looks on my Pixel 7 Pro with stock firmware:

screen-20240621-235315.mp4
Record_2024-06-22-02-35-38.mp4

This is how it looks in my OnePlus 12 settings

@kirillzyusko
Copy link
Contributor

@dhruvpvx I think Oppo/Xiaomi/Samsung and many other companies are creating their own version of UI (MIUI/One UI etc.) and it is varying significantly. And transitions that you see most likely are also a modification (i. e. it's not default Android transitions). If you want to check original Android transitions - you may check transitions in emulator for specific version.

From what I know - it's impossible to get transitions specific to a particular fork of Android, because typically transitions are overriden on app level (and not on OS level). I. e. Settings app on OnePlus device just uses their own transitions and these transitions are private and can not be retrieved on OS level.

@tboba correct me if I'm wrong please 😅

@tboba
Copy link
Member Author

tboba commented Jun 24, 2024

@kirillzyusko Yeah, that's correct - from what I can see is that Samsung/OPPO/any other distros are oftenly overriding the transitions to the custom one. I think the best source to check the original Android Transition is the official Material documentation - for example, take a look on Forward and backward section 😄

However, I know that Material 3 also introduces their own animations, which we may try to introduce yet in this PR (Applying Easing and Duration). Still, the main blocker is the way how we can't overlap entering and existing animations in current implementation.

@arasrezaei
Copy link

please, we need this PR (●'◡'●)

@arasrezaei
Copy link

I am so waiting for this, are there any plans ?

@tboba
Copy link
Member Author

tboba commented Oct 18, 2024

Hey hey @arasrezaei, unfortunately this PR is blocked by the wrong order of incoming events related to screen mounting/unmounting, which leads to the breaking change we definitely don't want by merging this. Also, if you look frame by frame how screens are being mounted, you can see that transition doesn't overlay from one screen to another (and so far I can't find any solutions how this could be fixed). If you want this change really badly - go ahead and patch-package react-native-screens! We don't bite for that 😄 But we surely don't want this on production for now, sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants