React Navigation: Navigate Back To Root using NavigationActions.reset, goBack and getStateForAction

前端 未结 7 666
灰色年华
灰色年华 2020-12-28 15:05

Say I\'ve navigated through 4 screens in my StackNavigator App and now I want to go back to the first screen. There seems to be three different ways to do this and they do n

相关标签:
7条回答
  • 2020-12-28 16:03

    I think I got a solution as described in this article https://medium.com/async-la/custom-transitions-in-react-navigation-2f759408a053 that explains how navigation works pretty well (would strongly recommend reading it as it's a pretty quick read and helped me get a better understanding of how transitions work).

    Here's a gif of a sample navigator that has smooth transitions for moving multiple screens back and forth: Navigator Gif (Sorry it's a link, I don't have enough rep yet to embed a video so I had to do a link). However, this shows how the navigator whose source code is below behaves.

    Essentially what happens is if you want to go from screen 4 on your stack to 1, you can set the opacity of 2 and 3 to 0, so then the transition will appear to go from 4 to 1.

    Here's the source code for the navigator:

    import { createAppContainer } from 'react-navigation';
    import { createStackNavigator } from 'react-navigation-stack';
    import ScreenA from './ScreenA';
    import ScreenB from './ScreenB';
    import ScreenC from './ScreenC';
    import ScreenD from './ScreenD';
    
    const OuterNavigator = createStackNavigator(
    {
        ScreenA: {
            screen: ScreenA,
            navigationOptions: {
                //Set header stuff here
                headerTintColor: "#fff",
                headerStyle: {
                    backgroundColor: '#4444aa',
                },
                headerTitle: "Screen A"
            }
        },
        ScreenB: {
            screen: ScreenB,
            navigationOptions: {
                headerTintColor: "#fff",
                headerStyle: {
                    backgroundColor: '#44aa44',
                },
                headerTitle: "Screen B"
            }
        },
        ScreenC: {
            screen: ScreenC,
    
            navigationOptions: {
                headerTintColor: "#fff",
                headerStyle: {
                    backgroundColor: '#aa4444',
                },
                headerTitle: "Screen C"
            }
        },
        ScreenD: {
            screen: ScreenD,
    
            navigationOptions: {
                headerTintColor: "#fff",
                headerStyle: {
                    backgroundColor: '#44aaaa',
                },
                headerTitle: "Screen D"
            }
        },
    },
    
    {
        // Sets initial screen to screen A
        initialRouteName: 'ScreenA',
    
        // Can be changed to whatever you prefer
        headerMode: 'float',
    
        // This line makes a transparent background so the navigator can be wrapped in a background image
        // (this was an issue I struggled with and figured there's a change someone else might be as well)
        cardStyle: { backgroundColor: '00000000', shadowColor: '000000' },
        transitionConfig: () => ({
            // This link exlpains this in detail: 
            // https://medium.com/async-la/custom-transitions-in-react-navigation-2f759408a053
            containerStyle: {
                // This also has to do with having a background image
                backgroundColor: '00000000',
            },
            transitionSpec: {
                // Sets speed for transition (lower -> faster)
                duration: 500,
                useNativeDriver: true,
            },
            screenInterpolator: sceneProps => {
                // Parses scene props
                const { position, layout, scene, index, scenes } = sceneProps
    
                // Grabs height and width of screen
                const toIndex = index
                const thisSceneIndex = scene.index
                const height = layout.initHeight
                const width = layout.initWidth
    
                // Grab index of last screen
                const lastSceneIndex = scenes[scenes.length - 1].index
    
                // Calculates change in indices
                const deltaScene = (lastSceneIndex - toIndex == 0) ? 1 : (lastSceneIndex - toIndex);
    
                let translateX = position.interpolate({
                    inputRange: [thisSceneIndex - 1, thisSceneIndex],
                    // Adjusts how the output get scaled
                    outputRange: [width / deltaScene, 0],
                });
    
                const translateY = position.interpolate({
                    // Not used, but in the link they use this for vertical transitions
                    // If you're interested in that this would do it
                    inputRange: [0, thisSceneIndex],
                    outputRange: [height, 0]
                });
    
                // MAGIC HAPPENS HERE:
                // Test whether we're skipping back more than one screen
                // and slide from bottom if true
                if (lastSceneIndex - toIndex > 1) {
                    // If you want the screen to which you are transitioning to not move
                    // have return; in this if block (see link). If you want behaviour as 
                    // shown in the GIF then leave this
                    if (scene.index === toIndex) {
                        return { transform: [{ translateX }] }
                    }
                    // BIG MAGIC HERE
                    // Hide all screens in between
                    if (scene.index !== lastSceneIndex) return { opacity: 0 }
    
                }
                return { transform: [{ translateX }] }
            },
        }),
    }
    );
    

    So, for example, let's say your stack is something like [A, B, C, D] (so D is on top). lastSceneIndex grabs the index of the last scene (in this case D). DeltaScene calculates how much the scenes are changing. DeltaScene will only be != 1 when one goes back more than 1 screen. If you aren't familiar with how translateX works, I would strongly recommend reading the afore mentioned link; the reason why the output range is [width/deltaScene, 0] is before the animation starts, the screens are stacked on top of each other and then the animation scales will spread them out like [A][B][C][D] where each screen is width/deltaScene away. In this example, deltaScene is 3, so B is width / 3 to the right of A, C is 2 * width / 3 to the right of A, and D is 3 * width / 3 = width to the right of A, which is what we want. Without deltaScene, D would fly off to the right 3 times faster than A comes onto the screen, which makes for an ugly transition.

    Also, I doubt anyone needs to see this but just in case, here's how it gets used in App.js (the provider is for redux, so that can probably be omitted if you don't want to deal with that)

    <Provider store={store}>
      <ImageBackground source={require('./assets/background.png')} style={styles.backgroundImage} resizeMode='cover'>
        <AppNavigator />
      </ImageBackground>
    </Provider>
    ...
    const styles = StyleSheet.create({
      backgroundImage: {
        flex: 1,
      }
    });
    

    I really hope this helps! I'm still pretty new to react native and whatnot, so if I made any mistakes or there's any room for further improvement or explanation, please say so in the comments for me/anyone else that might see this!

    0 讨论(0)
提交回复
热议问题