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.
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 have a Stack Navigator that uses either a modal or slide transition depending on the screen.
i.e. a transition sequence might look like:
HomeScreen -Slide> Screen1 -Modal From Bottom> Screen2
This used to not be a problem, but the updated modal transitions cause there to be noticeable "glitches" due to the varying card interpolation styles. I know I can fix this by adjusting the cardStyle interpolation values based on the current/previous screen.
Is there any way to access the navigation object for the Stack Navigator when creating the cardStyleInterpolator?
EDIT:
cardStyleInterpolator (incomplete):
export const cardStyleInterpolator = ({
index,
current,
next,
inverted,
layouts: { screen },
insets,
}) => {
const isLandscape = screen.width > screen.height;
const topOffset = isLandscape ? 0 : 10;
const statusBarHeight = insets.top;
const aspectRatio = screen.height / screen.width;
const progress = add(current.progress, next ? next.progress : 0);
const translateY = multiply(
progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
screen.height,
index === 0 || index === 1 ? 0 : topOffset,
(index === 0 || index === 1 ? statusBarHeight : 0) - topOffset * aspectRatio,
],
}),
inverted
);
const overlayOpacity = progress.interpolate({
inputRange: [0, 1, 1.0001, 2],
outputRange: [0, 0.3, 1, 1],
});
const scale = isLandscape
? 1
: progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
1,
1,
screen.width ? 1 - (topOffset * 2) / screen.width : 1,
],
});
const borderRadius = isLandscape
? 0
: index === 0 || index === 1
? progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [0, 0, 10],
})
: 10;
return {
cardStyle: {
overflow: 'hidden',
borderTopLeftRadius: borderRadius,
borderTopRightRadius: borderRadius,
marginTop: index === 0 || index === 1 ? 0 : statusBarHeight,
transform: [{ translateY }, { scale }],
},
overlayStyle: { opacity: overlayOpacity },
};
};
This cardStyleInterpolator is a modified version of the forModalPresentationIOS. The modification of adding index === 1 to the conditionals helps the transition from Screen2 -> Screen1. Since we pushed from HomeScreen to Screen1, when we nav back to Screen1 we want it to animate as if it was the initial route and not another modal.
The current forModalPresentationIOS assumes that index 0 is always the index where we use the modal transition from, but when overriding the cardStyleInterpolator of index 1 to use the slide transition causes this assumption to be invalid.
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/
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'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 }] }
},
}),