React Navigation:使用NavigationActions.reset,goBack和getStateForAction导航回Root

时间:2017-05-12 21:04:29

标签: javascript reactjs react-native react-navigation

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

是否有一种干净的方式从SCREEN_D导航到SCREEN_A

换句话说,在SCREEN_C向后导航时看到SCREEN_B之前,我不希望看到SCREEN_ASCREEN_D一分钟。 p>

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

7 个答案:

答案 0 :(得分: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

  • 如果有人知道改变单一导航动画的方法,我很乐意听到!

答案 1 :(得分:5)

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

希望这有帮助!

答案 2 :(得分:4)

本机导航具有popToTop,我们可以像打击一样使用

this.props.navigation.popToTop()

答案 3 :(得分:3)

对于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' } })
);

答案 4 :(得分:0)

还花了一些时间在这上面,让我总结一下我发现的内容,有多种解决方案/解决方法:

1)使用CardStackStyleInterpolator

pull request提到的Cristiano Santos似乎已合并。因此,您可以使用此导入加载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');

但是在我的reducer中,我在触发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);
};

nov-05-2017 11-34-07

答案 5 :(得分:0)

这是我的工作解决方案,无需创建新路径

即可重置回主页(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 :(得分:0)

我认为我得到了本文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计算场景的变化量。当一个人返回多于1个屏幕时,DeltaScene只会是= 1。如果您不熟悉translateX的工作方式,强烈建议您阅读上述链接;输出范围为[width / deltaScene,0]的原因是在动画开始之前,屏幕相互堆叠,然后动画缩放将像[A] [B] [C] [D ],其中每个屏幕均以width / deltaScene隔开。在此示例中,deltaScene为3,因此B为A右侧的宽度/ 3,C为2 * A右侧的宽度/ 3,D为3 * width / 3 = A右侧的宽度,其中这就是我们想要的。如果没有deltaScene,D会向右飞3倍,快于A出现在屏幕上的速度,这会带来难看的过渡。

此外,我怀疑有人需要看到它,但以防万一,这是在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,
  }
});

我真的希望这会有所帮助! 我仍然对本机和其他内容还很陌生,因此,如果我犯了任何错误,或者还有进一步改进或解释的空间,请在对我/其他可能看到此情况的人的评论中说出来!