I'm currently working on a ReactNative app that has 2 screens. The goal is to animate the screens such that they push each other up or down depending on which screen you're coming from. The GIF below is the transition I am trying to achieve.
Currently I'm using createAnimatedSwitchNavigator to create the effect, the problem is that my current transition only pushes the screens up. Is there a way to detect which screen I'm currently on within the animated switch navigator so that I can change the direction of the transition?
const MySwitch = createAnimatedSwitchNavigator(
{
Home: {
screen: Home
},
Page2: {
screen: Page2
},
},
{
transition: (
<Transition.Together>
<Transition.Out
type="slide-top"
durationMs={400}
interpolation="easeIn"
/>
<Transition.In type="slide-bottom" durationMs={500} />
</Transition.Together>
)
}
);
Kindly guide me for this.
Thanks
Did some researching and was able to get this working with the following code:
const AppNavigator = createStackNavigator({
Home: {
screen: Home
},
Page2: {
screen: Page2
}
},
{
initialRouteName: "Home",
headerMode: "none",
defaultNavigationOptions: {
gesturesEnabled: false
},
transitionConfig: () => ({
transitionSpec: {
duration: 1400,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps;
const { index } = scene;
const height = layout.initHeight;
const translateY = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [height, 0, -height],
});
const opacity = position.interpolate({
inputRange: [index - 1, index - 0.99, index],
outputRange: [0, 1, 1],
});
return { opacity, transform: [{ translateY }] };
},
}),
});
The hardest part was understanding interpolation as there seems to be a lot of seemingly arbitrary values. I didn't find the documentation the greatest, however was able to come up with the following understanding:
Take the code below for example. Here the first thing is setting the type of translation, which in this case is translateY
const translateY = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [height, 0, -height],
});
The next confusing part is the input and output ranges, what do these numbers mean? Well the input range actually maps to the output range. Looking something like this:
inputRange: [newScreen, currentScreen, previousScreen]
In inputRange we specify which screens we want to animate, then in output range we specify what we want to do with those screens. Modifying outputRange[0] would modify the newScreen, etc.
Since we already set the translation type to translateY we know the screen is either moving up or down.
outputRange: [height, 0, -height]
This is now telling the new screen to move up to the top of the screen and the old screen to also move up, beyond the top of the screen (hence the -height, which would be the same as -100vh in CSS).
Here is a guide for react-navigation-stack 2.0 alpha/react-navigation 5
https://callstack.com/blog/custom-screen-transitions-in-react-navigation/
Related
I am developing a React Native application using REACT NAVIGATION V5. I would like to add a custom transition for my navigation:
fade out the current screen -> a white background -> fade in the new screen.
I have tried cardStyleInterpolator but it's not working as expected. I have the fade effect but the current screen overlaps the new one. Here is my fade implementation:
export const forFade = ({ current, next, index, closing }) => {
const opacity = current.progress.interpolate({
inputRange: [0, index],
outputRange: [0, 1],
});
return {
cardStyle: {
opacity,
},
};
};
Any advice would be appreciated.
I am a beginner and i was wondering how i could go from screen 3 to screen 1 instead its going with my custom transitions from screen 3 to 2 and then 1 all in transition. I want it to go from screen 3 to screen 1 : this is the button i use on screen 3. I overwrote the goBack() because i wanted it to go directly to screen 1. But it doesn't work. How can this be fixed?
<HeaderBackButton
onPress={() => {
navigation.navigate("Profile");
}}
/>
I hope someone can give me an answer and can give me different options. I am a beginner so explain it basic to me please.
Thanks for your time
Edit 17 October!
const screensChosenConfig = {
duration: 0
};
const standardScreensConfig = {
duration: 1000,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
useNativeDriver: true
};
transitionConfig: sceneProps => ({
transitionSpec:
sceneProps.scene.route.routeName === "Account" ||
sceneProps.scene.route.routeName === "ChangePassword"
? screensChosenConfig
: standardScreensConfig,
screenInterpolator: sceneProps => {
const { position, layout, scene } = sceneProps;
const thisSceneIndex = scene.index;
const width = layout.initWidth;
const translateX = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex, thisSceneIndex + 1],
outputRange: [width, 0, 0]
});
const opacity = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex],
outputRange: [0, 1]
});
const translateWithOpacity = {
opacity,
transform: [{ translateX }]
};
return translateWithOpacity;
}
})
I did a bit of testing but wasn't able to achieve a non-painful solution.
This first solution only works if the screen you are navigating to is the initalRouteName of the stackNavigator. If used on any other screen, it will disable the fade-in animation to that screen.
Simply, add the screen you are navigating back to the ones you are giving screensChosenConfig:
sceneProps.scene.route.routeName === "Account" ||
sceneProps.scene.route.routeName === "ChangePassword"||
sceneProps.scene.route.routeName ==="ProfileScreen"
? screensChosenConfig
: standardScreensConfig
This is how it behaves:
ProfileScreen appears without animation.
The SecondScreen animates in.
The ThirdScreen animates in.
Inside the focused screen you call navigate("ProfileScreen")
ProfileScreen appears without the fading Out animation of the other screens.
EDIT.
If you need to navigate back to another screen, that's NOT the initialRouteName, the solution i tested was to add, inside EVERY screen of the stack, this componentDidMount:
componentDidMount = () => {
this.props.navigation.setParams({ animatedIn: true })
}
With this i could simply check it inside the transitionSpec:
transitionSpec:
sceneProps.scene.route.routeName === "Account" ||
sceneProps.scene.route.routeName === "ChangePassword"||
(sceneProps.scene.route.params&&sceneProps.scene.route.params.animatedIn)
? screensChosenConfig
: standardScreensConfig,
Note that this will disable animations for simple goBack() through header
I wanted to create a zoom out effect when a button is pressed for my next screen, a custom transition like when apps are pressed from the homescreen and splash screen comes out of it.
I have tried transitionConfig in stackNavigator and used a custom transition with following code
let sharedElementTransition = (index, position,touchPosition, height) => {
const inputRange = [index - 1, index, index + 1];
const outputRange = [0.1, 1, 1];
const opacity = position.interpolate({
inputRange,
outputRange: [0, 1, 1],
});
const scale = position.interpolate({
inputRange:[0, 0.01, 0.99, 1],
outputRange:[0.3, 0.3, 1, 1],
});
const translateX = position.interpolate({
inputRange,
outputRange: [touchPosition.x, 0, 0]
})
const translateY = position.interpolate({
inputRange,
outputRange: [touchPosition.y, 0, 0]
})
return {
opacity,
transform: [
{scale},
{ translateX },
{ translateY },
]
};
};
The problem I am facing is scaling starts from middle of the screen , so if i pass the position as (237,16) top right corner of the screen , I can't get translateX and translateY to animate properly as the scale of the next screen is smaller the screen size (we are animating the next screen from the button pressed in the last screen) so its co-ordinate system doesn't have a point (237,16).
This is not a shared element animation problem, I also tried https://github.com/fram-x/FluidTransitions recommended in the react navigation docs, but under this one (from its example, there is no extensive documentation) my screens which are under FluidNavigator never seems to have a header.
I want to apply different transitions to scenes in my StackNavigator in React Navigation, for my React Native (iOS) app.
export const AppNavigator = StackNavigator({
Home: { screen: Home },
NewChild: { screen: NewChild },
Journal: { screen: Journal }
});
I might want NewChild to load from the bottom up, like a modal. Or I might want it to slide in from right to left. It would great if this could be customised. From the documentation, it seems you can only set your StackNavigation to either card or modal for the whole thing.
Thanks to everyone in the thread, it worked for me.
I have many scenes managed by only one StackNavigator and I wanted to make a different animation for some of them.
First of all, I made myself a constant in my router which included every scenes I wanted to display with horizontal transition :
const horizontalTransitionScenes = [
AddContact,
];
After that, I designed my app router with StackNavigator :
const App = StackNavigator({
AddContact: {
screen: AddContact,
},
EditContact: {
screen: EditContact,
},
});
I used CardStackStyleInterpolator to modify the transitions between scenes. I can be imported like this : import CardStackStyleInterpolator from 'react-navigation/src/views/CardStack/CardStackStyleInterpolator';
We can now add transitionConfig params to our StackNavigator to display a different animation depending on our scenes :
const App = StackNavigator(
{
AddContact: {
screen: AddContact
},
EditContact: {
screen: EditContact
}
},
{
transitionConfig: (currentState: any) => {
if (
currentState.scenes[
currentState.scenes.length - 1
].route.routeName.includes(horizontalTransitionScenes)
) {
return {
screenInterpolator: CardStackStyleInterpolator.forHorizontal
};
}
return {
screenInterpolator: CardStackStyleInterpolator.forVertical
};
}
}
);
With that StackNavigator implementation, you will be able to display different kind of animations for the scenes you want.
Have a good day 🎉
As you already saw in the docs, it's not possible right now. You can track this issue and this issue requesting this feature. It's on the roadmap for v2, but since v1 isn't even released yet, your best bet is probably to either:
Implement your own transitionConfig that somehow handles this per screen. I'm not sure if this is possible.
Fork an implement something using the ideas from the main issue. Or simple make your fork specific to your code and maintain it until that issue is resolved.
I was able to accomplish this by doing some conditional logic, we're on react-navigation 3.3.2.
You have to import Easing and Animated from react-native and then you can conditionally render transitions for a specific screen by name, or also by index (below is by name).
By declaring the config outside the navigator, and toggling transitionSpec with an {} it will only trigger on that particular screen -- same with the conditional for screenInterpolator.
Screen1 to Screen2 has the animation, but none of the other screens have that animation.
const screen2Config = {
duration: 300,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
};
export const ScreenStack = createStackNavigator({
Screen1: {
screen: Screen1,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Screen1',
headerTitleAllowFontScaling: false,
}),
},
Screen2: {
screen: Screen2,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Screen2',
headerTitleAllowFontScaling: false,
tabBarVisible: false,
}),
},
Screen3: {
screen: Screen3,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Screen3',
headerTitleAllowFontScaling: false,
}),
},
Screen4: {
screen: Screen4,
navigationOptions: ({ navigation }) => ({
headerTitle: 'Screen4',
headerTitleAllowFontScaling: false,
}),
},
}, {
headerMode: 'float',
mode: 'modal',
transitionConfig: sceneProps => ({
transitionSpec: sceneProps.scene.route.routeName === 'Screen2' ? screen2Config : {},
screenInterpolator: (sceneProps) => {
if (sceneProps.scene.route.routeName === 'Screen2') {
const { layout, position, scene } = sceneProps;
const { index } = scene;
const width = layout.initWidth;
const translateX = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [width, 0, 0],
});
const opacity = position.interpolate({
inputRange: [index - 1, index - 0.99, index],
outputRange: [0, 1, 1],
});
return { opacity, transform: [{ translateX }] };
}
},
}),
});
I'm trying to push a new screen onto a StackNavigator, but without animation. I need the effect to be instant. I'm looking through the docs, but I'm having a hard time discerning how to configure the transition for a StackNavigator. I only need do disable animation for one specific route.
In the StackNavigatorConfig section of this page I see some config objects outlined such as transitionConfig that seem potentially promising..? but how do I find a description of how to use these objects?
According to issue 1120, currently animation cannot be disabled.
And transitionConfig is not well documented, its definition can be found here
export type NavigationTransitionSpec = {
duration?: number,
// An easing function from `Easing`.
easing?: (t: number) => number,
// A timing function such as `Animated.timing`.
timing?: (value: AnimatedValue, config: any) => any,
};
/**
* Describes a visual transition from one screen to another.
*/
export type TransitionConfig = {
// The basics properties of the animation, such as duration and easing
transitionSpec?: NavigationTransitionSpec,
// How to animate position and opacity of the screen
// based on the value generated by the transitionSpec
screenInterpolator?: (props: NavigationSceneRendererProps) => Object,
};
Example FYI:
// custom Modal transition animation
transitionConfig: () => ({
transitionSpec: {
duration: 250,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps
const { index } = scene
const height = layout.initHeight
const translateY = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [height, 0, 0],
})
const opacity = position.interpolate({
inputRange: [index - 1, index - 0.99, index],
outputRange: [0, 1, 1],
})
return { opacity, transform: [{ translateY }] }
},
}),