Skip to content

Commit

Permalink
feat: add slide animations on Android (#648)
Browse files Browse the repository at this point in the history
Added `slide_from_left` and `slide_from_right` to the list of available transition animations on Android. They are mapped to default transition on iOS. Also, the ordering of the screens adding/removing in `ScreenStack.java`  was changed because the custom animations need to be set first before any screens are added/removed.
  • Loading branch information
jiong-shen-cb authored Nov 23, 2020
1 parent 9358d45 commit 6d110d6
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 30 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ Allows for the customization of how the given screen should appear/disappear whe
- `"default"` – uses a platform default animation
- `"fade"` – fades screen in or out
- `"flip"` – flips the screen, requires `stackPresentation: "modal"` (iOS only)
- `"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)
- `"none"` – the screen appears/disappears without an animation

#### `stackPresentation`
Expand Down
46 changes: 46 additions & 0 deletions TestsExample/src/Test648.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';
import { Button, ScrollView } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from 'react-native-screens/native-stack';

const AppStack = createNativeStackNavigator();

export default function App() {
return (
<NavigationContainer>
<AppStack.Navigator
screenOptions={{
headerLargeTitle: true,
stackAnimation: 'slide_from_right'
}}>
<AppStack.Screen name="First" component={First} />
<AppStack.Screen
name="Second"
component={Second}
/>
</AppStack.Navigator>
</NavigationContainer>
);
}

function First({navigation}) {
return (
<ScrollView>
<Button
title="Tap me for second screen"
onPress={() => navigation.navigate('Second')}
/>
</ScrollView>
);
}

function Second({navigation}) {
return (
<ScrollView>
<Button
title="Tap me for first screen"
onPress={() => navigation.navigate('First')}
/>
</ScrollView>
);
}
4 changes: 3 additions & 1 deletion android/src/main/java/com/swmansion/rnscreens/Screen.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ public enum StackPresentation {
public enum StackAnimation {
DEFAULT,
NONE,
FADE
FADE,
SLIDE_FROM_RIGHT,
SLIDE_FROM_LEFT
}

public enum ReplaceAnimation {
Expand Down
81 changes: 53 additions & 28 deletions android/src/main/java/com/swmansion/rnscreens/ScreenStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,6 @@ protected boolean hasScreen(ScreenFragment screenFragment) {

@Override
protected void performUpdate() {
// remove all screens previously on stack
for (ScreenStackFragment screen : mStack) {
if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) {
getOrCreateTransaction().remove(screen);
}
}

// When going back from a nested stack with a single screen on it, we may hit an edge case
// when all screens are dismissed and no screen is to be displayed on top. We need to gracefully
Expand All @@ -171,26 +165,7 @@ protected void performUpdate() {
}
}

for (ScreenStackFragment screen : mScreenFragments) {
// detach all screens that should not be visible
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
getOrCreateTransaction().remove(screen);
}
}
// attach "below top" screen if set
if (belowTop != null && !belowTop.isAdded()) {
final ScreenStackFragment top = newTop;
getOrCreateTransaction().add(getId(), belowTop).runOnCommit(new Runnable() {
@Override
public void run() {
top.getScreen().bringToFront();
}
});
}

if (newTop != null && !newTop.isAdded()) {
getOrCreateTransaction().add(getId(), newTop);
}
boolean customAnimation = false;

if (!mStack.contains(newTop)) {
// if new top screen wasn't on stack we do "open animation" so long it is not the very first screen on stack
Expand All @@ -209,21 +184,71 @@ public void run() {
case FADE:
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
break;
case SLIDE_FROM_RIGHT:
customAnimation = true;
getOrCreateTransaction().setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left);
break;
case SLIDE_FROM_LEFT:
customAnimation = true;
getOrCreateTransaction().setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right);
break;
}

if (!customAnimation) {
getOrCreateTransaction().setTransition(transition);
}
getOrCreateTransaction().setTransition(transition);
}
} else if (mTopScreen != null && !mTopScreen.equals(newTop)) {
// otherwise if we are performing top screen change we do "back animation"
int transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;

switch (mTopScreen.getScreen().getStackAnimation()) {
case NONE:
transition = FragmentTransaction.TRANSIT_NONE;
break;
case FADE:
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
break;
case SLIDE_FROM_RIGHT:
customAnimation = true;
getOrCreateTransaction().setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right);
break;
case SLIDE_FROM_LEFT:
customAnimation = true;
getOrCreateTransaction().setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left);
break;
}

if (!customAnimation) {
getOrCreateTransaction().setTransition(transition);
}
}

// remove all screens previously on stack
for (ScreenStackFragment screen : mStack) {
if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) {
getOrCreateTransaction().remove(screen);
}
}

for (ScreenStackFragment screen : mScreenFragments) {
// detach all screens that should not be visible
if (screen != newTop && screen != belowTop && !mDismissed.contains(screen)) {
getOrCreateTransaction().remove(screen);
}
getOrCreateTransaction().setTransition(transition);
}
// attach "below top" screen if set
if (belowTop != null && !belowTop.isAdded()) {
final ScreenStackFragment top = newTop;
getOrCreateTransaction().add(getId(), belowTop).runOnCommit(new Runnable() {
@Override
public void run() {
top.getScreen().bringToFront();
}
});
}
if (newTop != null && !newTop.isAdded()) {
getOrCreateTransaction().add(getId(), newTop);
}

mTopScreen = newTop;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public void setStackAnimation(Screen view, String animation) {
view.setStackAnimation(Screen.StackAnimation.NONE);
} else if ("fade".equals(animation)) {
view.setStackAnimation(Screen.StackAnimation.FADE);
} else if ("slide_from_right".equals(animation)) {
view.setStackAnimation(Screen.StackAnimation.SLIDE_FROM_RIGHT);
} else if ("slide_from_left".equals(animation)) {
view.setStackAnimation(Screen.StackAnimation.SLIDE_FROM_LEFT);
}
}

Expand Down
5 changes: 5 additions & 0 deletions android/src/main/res/anim/rns_slide_in_from_left.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="-100%"
android:toXDelta="0%" />
5 changes: 5 additions & 0 deletions android/src/main/res/anim/rns_slide_in_from_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%"
android:toXDelta="0%" />
5 changes: 5 additions & 0 deletions android/src/main/res/anim/rns_slide_out_to_left.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:toXDelta="-100%"/>
5 changes: 5 additions & 0 deletions android/src/main/res/anim/rns_slide_out_to_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:toXDelta="100%"/>
2 changes: 2 additions & 0 deletions createNativeStackNavigator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ How the given screen should appear/disappear when pushed or popped at the top of
- `default` - Uses a platform default animation.
- `fade` - Fades screen in or out.
- `flip` – Flips the screen, requires stackPresentation: `modal` (iOS only).
- `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)
- `none` - The screen appears/disappears without an animation.

Defaults to `default`.
Expand Down
2 changes: 2 additions & 0 deletions ios/RNSScreen.m
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ @implementation RCTConvert (RNSScreen)
@"none": @(RNSScreenStackAnimationNone),
@"fade": @(RNSScreenStackAnimationFade),
@"flip": @(RNSScreenStackAnimationFlip),
@"slide_from_right": @(RNSScreenStackAnimationDefault),
@"slide_from_left": @(RNSScreenStackAnimationDefault),
}), RNSScreenStackAnimationDefault, integerValue)

RCT_ENUM_CONVERTER(RNSScreenReplaceAnimation, (@{
Expand Down
2 changes: 2 additions & 0 deletions native-stack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ How the given screen should appear/disappear when pushed or popped at the top of
- `default` - Uses a platform default animation.
- `fade` - Fades screen in or out.
- `flip` – Flips the screen, requires stackPresentation: `modal` (iOS only).
- `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)
- `none` - The screen appears/disappears without an animation.

Defaults to `default`.
Expand Down
10 changes: 9 additions & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ declare module 'react-native-screens' {
| 'containedTransparentModal'
| 'fullScreenModal'
| 'formSheet';
export type StackAnimationTypes = 'default' | 'fade' | 'flip' | 'none';
export type StackAnimationTypes =
| 'default'
| 'fade'
| 'flip'
| 'none'
| 'slide_from_right'
| 'slide_from_left';
export type BlurEffectTypes =
| 'extraLight'
| 'light'
Expand Down Expand Up @@ -90,6 +96,8 @@ declare module 'react-native-screens' {
* @type "default" – uses a platform default animation
* @type "fade" – fades screen in or out
* @type "flip" – flips the screen, requires stackPresentation: "modal" (iOS only)
* @type "slide_from_right" - slide in the new screen from right to left (Android only, resolves to default transition on iOS)
* @type "slide_from_left" - slide in the new screen from left to right (Android only, resolves to default transition on iOS)
* @type "none" – the screen appears/dissapears without an animation
*/
stackAnimation?: StackAnimationTypes;
Expand Down
2 changes: 2 additions & 0 deletions src/native-stack/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ export type NativeStackNavigationOptions = {
* - "default" – uses a platform default animation
* - "fade" – fades screen in or out
* - "flip" – flips the screen, requires stackPresentation: "modal" (iOS only)
* - "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)
* - "none" – the screen appears/dissapears without an animation
*/
stackAnimation?: ScreenProps['stackAnimation'];
Expand Down

0 comments on commit 6d110d6

Please sign in to comment.