From 7994eda0f802c122b6c82818c207fa8f216b48ec Mon Sep 17 00:00:00 2001 From: Alexander Pataridze Date: Mon, 30 Oct 2023 18:29:40 +0400 Subject: [PATCH] feat(Android): add ios like slide animation (#1945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR adds iOS like slide in animation to Android. But why? iOS like slide in animations are extremely popular in Android apps. Twitter, Telegram, Slack and many other apps stack navigation resembles or flat out copies iOS behaviour. `react-navigation` non native stack gives us ability to have reimplementation of iOS slide in behavior. However, `expo-router` doesn't support non native stack. Besides that if you want native performance and iOS like behavior you are kinda stuck 🤷. ## Changes - [x] https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md - [x] https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md - [x] https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx - [x] https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx - [x] Ensured that CI passes --- Example/src/screens/Animations.tsx | 1 + Example/src/screens/Events.tsx | 1 + android/src/main/java/com/swmansion/rnscreens/Screen.kt | 2 +- .../src/main/java/com/swmansion/rnscreens/ScreenStack.kt | 5 ++++- .../main/java/com/swmansion/rnscreens/ScreenViewManager.kt | 1 + .../src/main/res/base/anim/rns_slide_in_from_left_ios.xml | 5 +++++ .../src/main/res/base/anim/rns_slide_in_from_right_ios.xml | 6 ++++++ .../src/main/res/base/anim/rns_slide_out_to_left_ios.xml | 5 +++++ .../src/main/res/base/anim/rns_slide_out_to_right_ios.xml | 6 ++++++ guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 1 + ios/RNSConvert.mm | 3 ++- ios/RNSScreen.mm | 1 + native-stack/README.md | 1 + src/fabric/ScreenNativeComponent.ts | 3 ++- src/native-stack/types.tsx | 1 + src/types.tsx | 4 +++- windows/RNScreens/Screen.h | 3 ++- 17 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 android/src/main/res/base/anim/rns_slide_in_from_left_ios.xml create mode 100644 android/src/main/res/base/anim/rns_slide_in_from_right_ios.xml create mode 100644 android/src/main/res/base/anim/rns_slide_out_to_left_ios.xml create mode 100644 android/src/main/res/base/anim/rns_slide_out_to_right_ios.xml diff --git a/Example/src/screens/Animations.tsx b/Example/src/screens/Animations.tsx index 1850f0f79c..14fb71a00a 100644 --- a/Example/src/screens/Animations.tsx +++ b/Example/src/screens/Animations.tsx @@ -52,6 +52,7 @@ const MainScreen = ({ 'slide_from_bottom', 'slide_from_right', 'slide_from_left', + 'ios', 'none', ]} /> diff --git a/Example/src/screens/Events.tsx b/Example/src/screens/Events.tsx index e24c2301bb..5623c88da8 100644 --- a/Example/src/screens/Events.tsx +++ b/Example/src/screens/Events.tsx @@ -83,6 +83,7 @@ const MainScreen = ({ 'slide_from_bottom', 'slide_from_right', 'slide_from_left', + 'ios', 'none', ]} /> diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index b9acc11e90..a7d28f98ab 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -272,7 +272,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex } enum class StackAnimation { - DEFAULT, NONE, FADE, SLIDE_FROM_BOTTOM, SLIDE_FROM_RIGHT, SLIDE_FROM_LEFT, FADE_FROM_BOTTOM + DEFAULT, NONE, FADE, SLIDE_FROM_BOTTOM, SLIDE_FROM_RIGHT, SLIDE_FROM_LEFT, FADE_FROM_BOTTOM, IOS } enum class ReplaceAnimation { diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index b1815e5bb3..46d86c83c6 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -144,6 +144,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { R.anim.rns_slide_in_from_bottom, R.anim.rns_no_animation_medium ) StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_fade_from_bottom, R.anim.rns_no_animation_350) + StackAnimation.IOS -> it.setCustomAnimations(R.anim.rns_slide_in_from_right_ios, R.anim.rns_slide_out_to_left_ios) } } else { when (stackAnimation) { @@ -156,6 +157,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { R.anim.rns_no_animation_medium, R.anim.rns_slide_out_to_bottom ) StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_no_animation_250, R.anim.rns_fade_to_bottom) + StackAnimation.IOS -> it.setCustomAnimations(R.anim.rns_slide_in_from_left_ios, R.anim.rns_slide_out_to_right_ios) } } } @@ -331,6 +333,7 @@ class ScreenStack(context: Context?) : ScreenContainer(context) { private fun needsDrawReordering(fragmentWrapper: ScreenFragmentWrapper): Boolean = fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || - fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM + fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM || + fragmentWrapper.screen.stackAnimation === StackAnimation.IOS } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt index 8c70876ef4..5f77aa14e0 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -87,6 +87,7 @@ class ScreenViewManager : ViewGroupManager(), RNSScreenManagerInterface< "slide_from_left" -> Screen.StackAnimation.SLIDE_FROM_LEFT "slide_from_bottom" -> Screen.StackAnimation.SLIDE_FROM_BOTTOM "fade_from_bottom" -> Screen.StackAnimation.FADE_FROM_BOTTOM + "ios" -> Screen.StackAnimation.IOS else -> throw JSApplicationIllegalArgumentException("Unknown animation type $animation") } } diff --git a/android/src/main/res/base/anim/rns_slide_in_from_left_ios.xml b/android/src/main/res/base/anim/rns_slide_in_from_left_ios.xml new file mode 100644 index 0000000000..66a611fc91 --- /dev/null +++ b/android/src/main/res/base/anim/rns_slide_in_from_left_ios.xml @@ -0,0 +1,5 @@ + + diff --git a/android/src/main/res/base/anim/rns_slide_in_from_right_ios.xml b/android/src/main/res/base/anim/rns_slide_in_from_right_ios.xml new file mode 100644 index 0000000000..e6875848c4 --- /dev/null +++ b/android/src/main/res/base/anim/rns_slide_in_from_right_ios.xml @@ -0,0 +1,6 @@ + + diff --git a/android/src/main/res/base/anim/rns_slide_out_to_left_ios.xml b/android/src/main/res/base/anim/rns_slide_out_to_left_ios.xml new file mode 100644 index 0000000000..0789fbed24 --- /dev/null +++ b/android/src/main/res/base/anim/rns_slide_out_to_left_ios.xml @@ -0,0 +1,5 @@ + + diff --git a/android/src/main/res/base/anim/rns_slide_out_to_right_ios.xml b/android/src/main/res/base/anim/rns_slide_out_to_right_ios.xml new file mode 100644 index 0000000000..1073ae6283 --- /dev/null +++ b/android/src/main/res/base/anim/rns_slide_out_to_right_ios.xml @@ -0,0 +1,6 @@ + + diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index b92baefecf..a71c85b37c 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -203,6 +203,7 @@ Allows for the customization of how the given screen should appear/disappear whe - `"slide_from_bottom"` - slide in the new screen from bottom to top - `"slide_from_right"` - slide in the new screen from right to left (Android only, resolves to default transition on iOS) - `"slide_from_left"` - slide in the new screen from left to right (Android only, resolves to default transition on iOS) +- `"ios"` - iOS like slide in animation (Android only, resolves to default transition on iOS) - `"none"` – the screen appears/disappears without an animation ### `stackPresentation` diff --git a/ios/RNSConvert.mm b/ios/RNSConvert.mm index 655d8fba43..70a1f39e2f 100644 --- a/ios/RNSConvert.mm +++ b/ios/RNSConvert.mm @@ -27,9 +27,10 @@ + (RNSScreenStackPresentation)RNSScreenStackPresentationFromCppEquivalent: + (RNSScreenStackAnimation)RNSScreenStackAnimationFromCppEquivalent:(react::RNSScreenStackAnimation)stackAnimation { switch (stackAnimation) { - // these three are intentionally grouped + // these four are intentionally grouped case react::RNSScreenStackAnimation::Slide_from_right: case react::RNSScreenStackAnimation::Slide_from_left: + case react::RNSScreenStackAnimation::Ios: case react::RNSScreenStackAnimation::Default: return RNSScreenStackAnimationDefault; case react::RNSScreenStackAnimation::Flip: diff --git a/ios/RNSScreen.mm b/ios/RNSScreen.mm index 1ed4914729..740d48c738 100644 --- a/ios/RNSScreen.mm +++ b/ios/RNSScreen.mm @@ -1543,6 +1543,7 @@ @implementation RCTConvert (RNSScreen) @"slide_from_bottom" : @(RNSScreenStackAnimationSlideFromBottom), @"slide_from_right" : @(RNSScreenStackAnimationDefault), @"slide_from_left" : @(RNSScreenStackAnimationDefault), + @"ios" : @(RNSScreenStackAnimationDefault), }), RNSScreenStackAnimationDefault, integerValue) diff --git a/native-stack/README.md b/native-stack/README.md index a844586c50..947201fae2 100644 --- a/native-stack/README.md +++ b/native-stack/README.md @@ -287,6 +287,7 @@ How the given screen should appear/disappear when pushed or popped at the top of - `slide_from_bottom` – performs a slide from bottom animation - `slide_from_right` - slide in the new screen from right to left (Android only, resolves to default transition on iOS) - `slide_from_left` - slide in the new screen from left to right (Android only, resolves to default transition on iOS) +- `ios` - iOS like slide in animation (Android only, resolves to default transition on iOS) - `none` - the screen appears/disappears without an animation. Defaults to `default`. diff --git a/src/fabric/ScreenNativeComponent.ts b/src/fabric/ScreenNativeComponent.ts index a9a03a3b62..e7c33807e6 100644 --- a/src/fabric/ScreenNativeComponent.ts +++ b/src/fabric/ScreenNativeComponent.ts @@ -50,7 +50,8 @@ type StackAnimation = | 'slide_from_right' | 'slide_from_left' | 'slide_from_bottom' - | 'fade_from_bottom'; + | 'fade_from_bottom' + | 'ios'; type SwipeDirection = 'vertical' | 'horizontal'; diff --git a/src/native-stack/types.tsx b/src/native-stack/types.tsx index 8acd8bfb7e..261af8b02c 100644 --- a/src/native-stack/types.tsx +++ b/src/native-stack/types.tsx @@ -391,6 +391,7 @@ export type NativeStackNavigationOptions = { * - "slide_from_bottom" – performs a slide from bottom animation * - "slide_from_right" - slide in the new screen from right to left (Android only, resolves to default transition on iOS) * - "slide_from_left" - slide in the new screen from left to right (Android only, resolves to default transition on iOS) + * - "ios" - iOS like slide in animation (Android only, resolves to default transition on iOS) * - "none" – the screen appears/dissapears without an animation */ stackAnimation?: ScreenProps['stackAnimation']; diff --git a/src/types.tsx b/src/types.tsx index 379d8b35d1..46e633b61f 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -33,7 +33,8 @@ export type StackAnimationTypes = | 'simple_push' | 'slide_from_bottom' | 'slide_from_right' - | 'slide_from_left'; + | 'slide_from_left' + | 'ios'; export type BlurEffectTypes = | 'extraLight' | 'light' @@ -319,6 +320,7 @@ export interface ScreenProps extends ViewProps { * - `slide_from_bottom` – performs a slide from bottom animation * - "slide_from_right" - slide in the new screen from right to left (Android only, resolves to default transition on iOS) * - "slide_from_left" - slide in the new screen from left to right (Android only, resolves to default transition on iOS) + * - "ios" - iOS like slide in animation (Android only, resolves to default transition on iOS) * - "none" – the screen appears/dissapears without an animation */ stackAnimation?: StackAnimationTypes; diff --git a/windows/RNScreens/Screen.h b/windows/RNScreens/Screen.h index 35c43b1536..fd0264129d 100644 --- a/windows/RNScreens/Screen.h +++ b/windows/RNScreens/Screen.h @@ -10,7 +10,8 @@ enum class StackAnimation { FADE, SIMPLE_FROM_BOTTOM, SLIDE_FROM_RIGHT, - SLIDE_FROM_LEFT + SLIDE_FROM_LEFT, + IOS }; enum class ReplaceAnimation { PUSH, POP };