React Native未来导航者:react-navigation 使用详解(进阶篇)

前端之家收集整理的这篇文章主要介绍了React Native未来导航者:react-navigation 使用详解(进阶篇)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

刚创建的React Native 微信公众号,欢迎微信扫描关注订阅号,每天定期会分享react native 技术文章移动技术干货,精彩文章技术推送。同时可以扫描我的微信加入react-native技术交流微信群。欢迎各位大牛,React Native技术爱好者加入交流!


本篇内容为react-navigation的进阶内容以及高级用法


(1)适配顶部导航栏标题
测试中发现,在iphone上标题栏的标题为居中状态,而在Android上则是居左对齐。所以需要我们修改源码,进行适配。
【node_modules -- react-navigation -- src -- views -- Header.js】的326行代码处,修改为如下:
  1. title: {
  2. bottom: 0,left: TITLE_OFFSET,right: TITLE_OFFSET,top: 0,position: 'absolute',alignItems: 'center',}

上面方法通过修改源码的方式其实略有弊端,毕竟扩展性不好。还有另外一种方式就是,在navigationOptions中设置headerTitleStyle的alignSelf为 ' center '即可解决

(2)去除返回键文字显示

【node_modules -- react-navigation -- src -- views -- HeaderBackButton.js】的91行代码处,修改为如下即可。
  1. {Platform.OS === 'ios' &&
  2. title &&
  3. <Text
  4. onLayout={this._onTextLayout}
  5. style={[styles.title,{ color: tintColor }]}
  6. numberOfLines={1}
  7. >
  8. {backButtonTitle}
  9. </Text>}

将上述代码删除即可。

(3)动态设置头部按钮事件:

当我们在头部设置左右按钮时,肯定避免不了要设置按钮的单击事件,但是此时会有一个问题,navigationOptions是被修饰为static类型的,所以我们在按钮的onPress的方法中不能直接通过this来调用Component中的方法。如何解决呢?在官方文档中,作者给出利用设置params的思想来动态设置头部标题。那么我们可以利用这种方式,将单击回调函数以参数的方式传递到params,然后在navigationOption中利用navigation来取出设置到onPress即可:


  1. componentDidMount () {
  2. /**
  3. * 将单击回调函数作为参数传递
  4. */
  5. this.props.navigation.setParams({
  6. switch: () => this.switchView()
  7. });
  8. }
  1. /**
  2. * 切换视图
  3. */
  4. switchView() {
  5. alert('切换')
  6. }
  1. static navigationOptions = ({navigation,screenProps}) => ({
  2. headerTitle: '企业服务',headerTitleStyle: CommonStyles.headerTitleStyle,headerRight: (
  3. <NavigatorItem icon={ Images.ic_navigator } onPress={ ()=> navigation.state.params.switch() }/>
  4. ),headerStyle: CommonStyles.headerStyle
  5. });
  1. componentDidMount () {
  2. /**
  3. * 将单击回调函数作为参数传递
  4. */
  5. this.props.navigation.setParams({
  6. switch: () => this.switchView()
  7. });
  8. }
  1. /**
  2. * 切换视图
  3. */
  4. switchView() {
  5. alert('切换')
  6. }
  1. static navigationOptions = ({navigation,headerStyle: CommonStyles.headerStyle
  2. });

(4)结合BackHandler处理返回和点击返回键两次退出App效果

点击返回键两次退出App效果的需求屡见不鲜。相信很多人在react-navigation下实现该功能都遇到了很多问题,例如,其他界面不能返回。也就是手机本身返回事件在react-navigation之前拦截了。如何结合react-natigation实现呢?和大家分享两种实现方式:

(1)在注册StackNavigator的界面中,注册BackHandler:

  1. componentWillMount(){
  2. BackHandler.addEventListener('hardwareBackPress',this._onBackAndroid );
  3. }
  4.  
  5.  
  6. componentUnWillMount(){
  7. BackHandler.addEventListener('hardwareBackPress',this._onBackAndroid);
  8. }
  9.  
  10. _onBackAndroid=()=>{
  11. let now = new Date().getTime();
  12. if(now - lastBackPressed < 2500) {
  13. return false;
  14. }
  15. lastBackPressed = now;
  16. ToastAndroid.show('再点击一次退出应用',ToastAndroid.SHORT);
  17. return true;
  18. }

(2)监听react-navigation的Router
  1. /**
  2. * 处理安卓返回键
  3. */
  4. const defaultStateAction = AppNavigator.router.getStateForAction;
  5. AppNavigator.router.getStateForAction = (action,state) => {
  6. if(state && action.type === NavigationActions.BACK && state.routes.length === 1) {
  7. if (lastBackPressed + 2000 < Date.now()) {
  8. ToastAndroid.show(Constant.hint_exit,ToastAndroid.SHORT);
  9. lastBackPressed = Date.now();
  10. const routes = [...state.routes];
  11. return {
  12. ...state,...state.routes,index: routes.length - 1,};
  13. }
  14. }
  15. return defaultStateAction(action,state);
  16. };

(5)实现Android中界面跳转左右切换动画

react-navigation在Android中默认的界面切换动画是上下。如何实现左右切换呢?很简单的配置即可:

  1. import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';
然后在StackNavigator的配置下添加如下代码
  1. transitionConfig:()=>({
  2. screenInterpolator: CardStackStyleInterpolator.forHorizontal,})

(6)解决快速点击多次跳转

当我们快速点击跳转时,会开启多个重复的界面,如何解决呢。其实在官方git中也有提示解决这个问题需要修改react-navigation源码:

找到src文件夹中的addNavigationHelpers.js文件,替换为如下文本即可:

  1. export default function<S: *>(navigation: NavigationProp<S,NavigationAction>) {
  2. // 添加点击判断
  3. let debounce = true;
  4. return {
  5. ...navigation,goBack: (key?: ?string): boolean =>
  6. navigation.dispatch(
  7. NavigationActions.back({
  8. key: key === undefined ? navigation.state.key : key,}),),navigate: (routeName: string,params?: NavigationParams,action?: NavigationAction,): boolean => {
  9. if (debounce) {
  10. debounce = false;
  11. navigation.dispatch(
  12. NavigationActions.navigate({
  13. routeName,params,action,);
  14. setTimeout(
  15. () => {
  16. debounce = true;
  17. },500,);
  18. return true;
  19. }
  20. return false;
  21. },/**
  22. * For updating current route params. For example the nav bar title and
  23. * buttons are based on the route params.
  24. * This means `setParams` can be used to update nav bar for example.
  25. */
  26. setParams: (params: NavigationParams): boolean =>
  27. navigation.dispatch(
  28. NavigationActions.setParams({
  29. params,key: navigation.state.key,};
  30. }

(7)解决goBack,根据路由名称返回指定界面

react-navigation默认不支持根据路由名返回指定界面,官方只提供了根据Key来做goBack的指定返回。解决这个问题同样需要修改react-navigation源码,在Navigation.goBack条件下添加对路由名的支持。找到/node_modules/react-navigation/src/routers/StackRouter.js,全局搜索backRoute,将条件判断语句替换为如下代码

  1. if (action.type === NavigationActions.BACK) {
  2. const key = action.key;
  3. let backRouteIndex = null;
  4. if (key) {
  5. const backRoute = null;
  6. if(key.indexOf('id') >= 0) {
  7. backRoute = state.routes.find((route: *) => route.key === action.key);
  8. } else {
  9. backRoute = state.routes.find(route => route.routeName === action.key);
  10. }
  11. backRouteIndex = state.routes.indexOf(backRoute);
  12. }
  13. if (backRouteIndex == null) {
  14. return StateUtils.pop(state);
  15. }
  16. if (backRouteIndex > 0) {
  17. return {
  18. ...state,routes: state.routes.slice(0,backRouteIndex),index: backRouteIndex - 1,};
  19. }
  20. }

(8)自定义Tab

  1. import React,{ Component } from 'react';
  2. import {
  3. AppRegistry,Platform,StyleSheet,Text,View,TouchableOpacity,NativeModules,ImageBackground,DeviceEventEmitter
  4. } from 'react-native';
  5.  
  6. export default class Tab extends Component {
  7. renderItem = (route,index) => {
  8. const {
  9. navigation,jumpToIndex,} = this.props;
  10.  
  11. const focused = index === navigation.state.index;
  12. const color = focused ? this.props.activeTintColor : this.props.inactiveTintColor;
  13. let TabScene = {
  14. focused:focused,route:route,tintColor:color
  15. };
  16.  
  17. if(index==1){
  18. return (<View style={[styles.tabItem,{backgroundColor:'transparent'}]}>
  19. </View>
  20. );
  21. }
  22.  
  23. return (
  24. <TouchableOpacity
  25. key={route.key}
  26. style={styles.tabItem}
  27. onPress={() => jumpToIndex(index)}
  28. >
  29. <View
  30. style={styles.tabItem}>
  31. {this.props.renderIcon(TabScene)}
  32. <Text style={{ ...styles.tabText,marginTop:SCALE(10),color }}>{this.props.getLabel(TabScene)}</Text>
  33. </View>
  34. </TouchableOpacity>
  35. );
  36. };
  37. render(){
  38. const {navigation,jumpToIndex} = this.props;
  39. const {routes,} = navigation.state;
  40. const focused = 1 === navigation.state.index;
  41. const color = focused ? this.props.activeTintColor : this.props.inactiveTintColor;
  42. let TabScene = {
  43. focused:focused,route:routes[1],tintColor:color
  44. };
  45. return (
  46. <View style={{width:WIDTH}}>
  47. <View style={styles.tab}>
  48. {routes && routes.map((route,index) => this.renderItem(route,index))}
  49. </View>
  50. <TouchableOpacity
  51. key={"centerView"}
  52. style={[styles.tabItem,{position:'absolute',bottom:0,left:(WIDTH-SCALE(100))/2,right:WIDTH-SCALE(100),height:SCALE(120)}]}
  53. onPress={() => jumpToIndex(1)}
  54. >
  55. <View
  56. style={styles.tabItem}>
  57. {this.props.renderIcon(TabScene)}
  58. <Text style={{ ...styles.tabText,color }}>{this.props.getLabel(TabScene)}</Text>
  59. </View>
  60. </TouchableOpacity>
  61. </View>
  62. );
  63. }
  64. }
  65. const styles = {
  66. tab:{
  67. width:WIDTH,backgroundColor:'transparent',flexDirection:'row',justifyContent:'space-around',alignItems:'flex-end'
  68. },tabItem:{
  69. height:SCALE(80),width:SCALE(100),alignItems:'center',justifyContent:'center'
  70. },tabText:{
  71. marginTop:SCALE(13),fontSize:FONT(10),color:Color.C7b7b7b
  72. },tabTextChoose:{
  73. color:Color.f3474b
  74. },tabImage:{
  75. width:SCALE(42),height:SCALE(42),},}
  1. componentDidMount () {
  2. /**
  3. * 将单击回调函数作为参数传递
  4. */
  5. this.props.navigation.setParams({
  6. switch: () => this.switchView()
  7. });
  8. }
  1. /**
  2. * 切换视图
  3. */
  4. switchView() {
  5. alert('切换')
  6. }
  1. static navigationOptions = ({navigation,headerStyle: CommonStyles.headerStyle
  2. });


(9)如何在屏幕控件之外的模块获取当前界面及navigation实例

很多情况下,我们都需要处理登录token失效的情况。例如:在当前设备登录后不退出,此时在另一台设备登录,导致第一个设备用户登录状态失效,此时在第一台设备操作网络请求时,需要提醒用户登录失效,跳转登录界面,并重新登录

这种需求很常见,关于网络请求我们一般会封装为一个HttpUtil。然后在Component中去调用。此时如果需要处理登录失效的跳转逻辑,需要写在HttpUtil,那么在HttpUtil中就没办法获取navigation来做跳转,那么如何解决呢?下面提供一种方案,很实用:

定义一个Component的基类,包含当前显示的Component实例:screen,以及导航函数

  1. import React,{Component} from 'react';
  2.  
  3. export default class Base extends Component {
  4. static screen;
  5.  
  6. constructor(props) {
  7. super(props);
  8. Base.screen = this;
  9. }
  10.  
  11. nav() {
  12. return this.props.navigation;
  13. }
  14. }

在其他组件/模块中,我可以调用它来导航到不同的屏幕:

  1. Base.screen.nav().navigate(...);

这样不管在哪个屏幕上,并且可以随时获取导航对象以在需要时重定向用户


(10)react-navigation高级用法:实现自定义Tab切换效果

react-navigation 库中提供了实现自定义Router切换的方式,需要用到的组件如下:

  1. TabRouter,createNavigator,createNavigationContainer

1. TabRouter用来自定义路由栈

2.createNavigator用来创建导航组件

3.createNavigationContainer作为导航组件的容器组件

自定义Router切换的流程大致如下:

1. 创建StackNavigator

2. 创建TabRouter

3. 定义导航样式

4. 定义整体路由切换组件

5. 创建Navigator

来看核心代码

  1. // 界面组件
  2. import FirstPage from './scene/FirstPage';
  3. import SecondPage from './scene/SecondPage';
  4. import ThirdPage from './scene/ThirdPage';
  5. import DetailPage from './scene/DetailPage';
  1. // 引入 react-navigation 核心组件
  2. import {
  3. TabRouter,StackNavigator,addNavigationHelpers,createNavigationContainer,} from 'react-navigation';
  1. // 创建 3个 StackNavigator
  2. const FirstScreen = StackNavigator(
  3. {
  4. First: {
  5. screen: FirstPage
  6. },Detail: {
  7. screen: DetailPage
  8. }
  9. }
  10. );
  11.  
  12. const SecondScreen = StackNavigator(
  13. {
  14. Second: {
  15. screen: SecondPage
  16. }
  17. }
  18. );
  19.  
  20. const ThirdScreen = StackNavigator(
  21. {
  22. Third: {
  23. screen: ThirdPage
  24. }
  25. }
  26. );
  1. // 定义 TabRouter
  2.  
  3. const FirstScene = ({ navigation }) => (
  4. <FirstScreen />
  5. );
  6. const SecondScene = ({ navigation }) => (
  7. <SecondScreen />
  8. );
  9. const ThirdScene = ({ navigation }) => (
  10. <ThirdScreen />
  11. );
  12.  
  13. const CustomTabRouter = TabRouter(
  14. {
  15. First: {
  16. screen: FirstScene,path: 'firstScene'
  17. },Second: {
  18. screen: SecondScene,path: 'secondScene'
  19. },Third: {
  20. screen: ThirdScene,path: 'thirdScene'
  21. },{
  22. initialRouteName: 'First'
  23. }
  24. );
  1. // 定义TabBar
  2. const CustomTabBar = ({ navigation,activeRouteName }) => {
  3. const { routes } = navigation.state;
  4. return (
  5. <View style={ styles.tabContainer }>
  6. <ScrollView>
  7. {
  8. routes.map((route,index)=>(
  9. <TouchableOpacity
  10. key={ index }
  11. onPress={() => navigation.navigate(route.routeName)}>
  12. <Text style={[
  13. styles.tabbarText,activeRouteName === route.routeName ? styles.active : styles.inactive
  14. ]}>
  15. { route.routeName }
  16. </Text>
  17. </TouchableOpacity>
  18. ))
  19. }
  20. </ScrollView>
  21. </View>
  22. )
  23. }
  1. // 定义TabView
  2. const CustomTabView = ({ router,navigation }) => {
  3. const { routes,index } = navigation.state;
  4. const activeRouteName = routes[index].routeName;
  5. const ActiveScreen = router.getComponentForRouteName(activeRouteName);
  6. return(
  7. <View style={ styles.container }>
  8. <CustomTabBar
  9. navigation={ navigation }
  10. activeRouteName={ activeRouteName } />
  11. <ActiveScreen
  12. navigation={
  13. addNavigationHelpers(
  14. {
  15. dispath: navigation.dispatch,state: routes[index]
  16. }
  17. )
  18. }
  19. />
  20. </View>
  21. )
  22. }
  1. // 创建Navigator
  2. const CustomTabs = createNavigationContainer(
  3. createNavigator(CustomTabRouter)(CustomTabView)
  4. )
  5. export default CustomTabs;
  1. // Style 样式
  2. const styles = StyleSheet.create({
  3. tabContainer: {
  4. width: 86,zIndex: 888,flexDirection:'column',justifyContent:'center',backgroundColor: '#e7e7e7',borderRightWidth:1,borderColor: '#e0e0e0'
  5. },tabbarText: {
  6. fontSize: 18,fontWeight: 'bold',marginTop: 20,marginBottom: 20,color: 'black'
  7. },active: {
  8. color: 'red',inactive: {
  9. color: 'black',container: {
  10. flexDirection:'row',flex: 1,}
  11. });
通过上述代码,我们就可以创建出类似于饿了么App中商品分类的模块切换效果

(11)定义某个界面的切换动画效果

有时候产品会存在某个界面的切换动画和其他不同,那么如何实现呢?很简单,只需要在StackNavigator中配置参数下声明以下代码

  1. transitionConfig:()=>({
  2. screenInterpolator:
  3. (props)=> {
  4. const { scene } = props
  5. if (scene.route.routeName === 'VIPDetailPage') {
  6. return CardStackStyleInterpolator.forFade
  7. } else {
  8. return CardStackStyleInterpolator.forHorizontal(props)
  9. }
  10. }
  11. })

效果


自定义TabRouter:

猜你在找的React相关文章