【问题标题】:React Navigation: Navigate Back To Root using NavigationActions.reset, goBack and getStateForActionReact Navigation:使用 NavigationActions.reset、goBack 和 getStateForAction 导航回根目录
【发布时间】:2017-10-12 06:53:57
【问题描述】:

假设我在 StackNavigator 应用程序中浏览了 4 个屏幕,现在我想返回第一个屏幕。似乎有三种不同的方法可以做到这一点,它们确实导航到我想要做的地方,但是每种方式都有一个动画,可以在每个前一个屏幕中循环。

有没有一种从SCREEN_D 导航到SCREEN_A 的简洁方法?

换句话说,当从SCREEN_D 向后导航时,我不想看到SCREEN_A 之前看到SCREEN_CSCREEN_B

navigation.navigate(SCREEN_A);
...
navigation.navigate(SCREEN_B);
...
navigation.navigate(SCREEN_C);
...
navigation.navigate(SCREEN_D);

三种方法:

1.

return this.props.navigation
               .dispatch(NavigationActions.reset(
                 {
                    index: 0,
                    actions: [NavigationActions.navigate({ routeName: 'SCREEN_A'})]
                  }));

2.

 const {SCREEN_B_KEY} = this.props.navigation.state.params
 this.props.navigation.goBack(SCREEN_B_KEY)

3.

const defaultGetStateForAction = Navigation.router.getStateForAction;

Navigation.router.getStateForAction = (action, state) =>{
  if(action.type === "Navigation/BACK"){
    const routes = [
      {routeName: 'First'},
    ];
    return {
      ...state,
      routes,
      index: 0,
    };
  }
  return defaultGetStateForAction (action,state);
}

【问题讨论】:

标签: javascript reactjs react-native react-navigation


【解决方案1】:

react-navigation 有 popToTop,我们可以像使用它一样使用它

this.props.navigation.popToTop()

【讨论】:

  • 这是最好的答案
  • 对我来说最好的答案!非常感谢!
  • 这是否会从堆栈中卸载所有屏幕?
  • 据官方Docit jump back to the top route in the stack, dismissing all other screens
【解决方案2】:

对于 2020 / react-navigation-stack 我在设置 StackNavigator 时使用以下代码:

import { createStackNavigator, CardStyleInterpolators } from 'react-navigation-stack';
import { Easing } from 'react-native';

const Navigator = createStackNavigator({
  // ...
}, {
  defaultNavigationOptions: ({ navigation }) => {
    const animation = navigation.getParam('_animation');
    return {
      // ...
      cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
      ...(animation === 'back' && {
        gestureDirection: 'horizontal-inverted',
      }),
      ...(animation === 'skip' && {
        transitionSpec: {
          open: { animation: 'timing', config: { duration: 0, easing: Easing.step1 }, },
          close: { animation: 'timing', config: { duration: 0, easing: Easing.step0 }, },
        },
      }),
    };
  },
});

以及用于覆盖动画样式的 _animation 参数

// replace to a route not in the stack normally creates a forward animation, but
// we want a backwards animation
this.props.navigation.dispatch(
  StackActions.replace({ routeName: 'Login', params: { _animation: 'back' } })
);

【讨论】:

【解决方案3】:

我想我得到了本文https://medium.com/async-la/custom-transitions-in-react-navigation-2f759408a053 中描述的解决方案,它解释了导航的工作原理(强烈建议阅读它,因为它的阅读速度非常快,并帮助我更好地理解了转换的工作原理)。

这是一个示例导航器的 gif 图像,该导航器具有平滑的过渡,可以来回移动多个屏幕: Navigator Gif (对不起,这是一个链接,我还没有足够的代表来嵌入视频,所以我不得不做一个链接)。但是,这显示了源代码在下面的导航器的行为。

基本上发生的情况是,如果您想从堆栈上的屏幕 4 转到 1,您可以将 2 和 3 的不透明度设置为 0,这样过渡看起来会从 4 转到 1。

这是导航器的源代码:

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 }] }
        },
    }),
}
);

因此,例如,假设您的堆栈类似于 [A, B, C, D](因此 D 在顶部)。 lastSceneIndex 获取最后一个场景的索引(在本例中为 D)。 DeltaScene 计算场景变化的程度。 DeltaScene 只会在 != 1 时返回超过 1 个屏幕。如果您不熟悉 translateX 的工作原理,我强烈建议您阅读上述链接;输出范围为 [width/deltaScene, 0] 的原因是在动画开始之前,屏幕相互堆叠,然后动画比例将它们展开成 [A][B][C][D ] 其中每个屏幕的宽度/deltaScene 距离。在本例中,deltaScene 为 3,因此 B 为 A 右侧的宽度/3,C 为 A 右侧的 2 * 宽度/3,D 为 A 右侧的 3 * 宽度/3 = 宽度,即是我们想要的。如果没有 deltaScene,D 向右飞的速度比 A 出现在屏幕上的速度快 3 倍,这会造成丑陋的过渡。

另外,我怀疑任何人都需要看到这一点,但以防万一,这是它在 App.js 中的使用方式(提供程序用于 redux,因此如果您不想处理它,可以省略它)

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

我真的希望这会有所帮助! 我对本机反应还是很陌生,所以如果我犯了任何错误或有任何进一步改进或解释的空间,请在 cmets 中为我/任何可能看到此内容的人说出来!

【讨论】:

    【解决方案4】:

    您可以使用Navigation prop 中的popToTop 方法来执行此操作。另外,如果你使用的是 redux,你应该更新你的Redux integration

    希望这会有所帮助!

    【讨论】:

      【解决方案5】:

      这是我在不创建新路由的情况下重置回家(root)的工作解决方案

      if(this.categoriesNav.state.nav.index >0){
          let k = this.categoriesNav.state.nav.routes[1].key;
          this.categoriesNav.dispatch(NavigationActions.back({key: k})); }
      

      categoriesNav 引用到我的堆栈导航器

      【讨论】:

        【解决方案6】:

        也花了一些时间,让我总结一下我的发现,有多种解决方案/解决方法:

        1) 使用CardStackStyleInterpolator

        Cristiano Santos 提到的pull request 似乎被合并了。所以你可以通过这个导入加载CardStackStyleInterpolator

        import CardStackStyleInterpolator from 'react-navigation/src/views/CardStack/CardStackStyleInterpolator'
        

        像这样应用它:

        const YourStackNavigator = StackNavigator({
            Screen1: { screen: Screen1 },
            Screen2: { screen: Screen2 },
        }, {
            transitionConfig: () => ({
                screenInterpolator: (props) => CardStackStyleInterpolator.forHorizontal(props)
            })
        });
        

        就我而言,我只是跳到下一个屏幕,例如:

        this.props.navigation.navigate('Modal_AddGoodClass');
        

        但是在我的减速器中,当Modal_AddGoodClass 屏幕被触发时,我重置了导航器:

        const NewExportReceiptNavigationReducer = (state, action) => {
            // Default stuff
        
            let newStateBuffer = newState || state;
        
            if (action) {
                if (action.type === 'Navigation/NAVIGATE') {
                    if (action.routeName === 'Modal_AddGoodClass') {
                        newStateBuffer = {
                            index:  0,
                            routes: [
                                newStateBuffer.routes[newStateBuffer.routes.length - 1]
                            ]
                        };
                    }
                }
            }
        
            return newStateBuffer;
        };
        
        module.exports = NewExportReceiptNavigationReducer;
        

        这很好用,除了事实上仍然使用“后退”动画而不是“前进”动画。

        您还可以找到here 一些使用CardStackStyleInterpolator 的示例代码。

        2) 覆盖getStateForAction:

        正如Fendrian 提到的here,您可以覆盖路由器的getStateForAction,以避免导航器返回。除了 iOS 上的“向后滑动”手势外,这似乎可行:

        Nav = StackNavigator(navScreens, navOptions);
        const defaultGetStateForAction = Nav.router.getStateForAction;
        Nav.router.getStateForAction = (action, state) => {
          if (
            state &&
            action.type === NavigationActions.BACK &&
            (
              state.routes[state.index].routeName === 'Login' ||
              state.routes[state.index].routeName === 'Main'
            )
          ) {
            // Returning null indicates stack end, and triggers exit
            return null;
          }
          return defaultGetStateForAction(action, state);
        };
        

        【讨论】:

          【解决方案7】:

          这里有一个快速修复。 这将在导航(向前或向后)时删除所有转换。

          const Nav = StackNavigator({
            Screens
          },{
            transitionConfig,
            navigationOptions
          });
          

          在transitionConfig中添加:

          const transitionConfig = () => ({
              transitionSpec: {
                duration: 0,
                timing: Animated.timing,
                easing: Easing.step0,
              },
            })
          

          transitionConfig 是一个函数,它返回一个覆盖默认屏幕转换的对象。 https://reactnavigation.org/docs/navigators/stack

          • 如果有人知道改变单次导航动画的方法,我很想听听如何!

          【讨论】:

            猜你喜欢
            • 2018-12-01
            • 1970-01-01
            • 2019-03-02
            • 1970-01-01
            • 2018-01-11
            • 1970-01-01
            • 2020-06-03
            • 2021-09-04
            • 2018-04-08
            相关资源
            最近更新 更多