TabNavigator 即 Tab 选项卡

TabNavigator(RouteConfigs, TabNavigatorConfig)
 
  • 1

api和 StackNavigator 类似,参数 RouteConfigs 是路由配置,参数 TabNavigatorConfigTab选项卡配置。

如果要实现底部选项卡切换功能,可以直接使用react-navigation提供的createBottomTabNavigator接口,并且此导航器需要使用createAppContainer函数包裹后才能作为React组件被正常调用。例如:

import React, {PureComponent} from 'react';
import {StyleSheet, Image} from 'react-native';
import {createAppContainer, createBottomTabNavigator} from 'react-navigation'

import Home from './tab/HomePage'   
import Mine from './tab/MinePage'

const BottomTabNavigator = createBottomTabNavigator(
    {
        Home: {
            screen: Home,
            navigationOptions: () => ({
                tabBarLabel: '首页',
                tabBarIcon:({focused})=>{
                    if(focused){
                        return(
                          <Image/>   //选中的图片
                        )
                    }else{
                        return(
                           <Image/>   //默认图片                      
  )
                    }
                }
            }),
        },
        Mine: {
            screen: Mine,
            navigationOptions: () => ({
                tabBarLabel: '我的',
                tabBarIcon:({focused})=>{}
            })
        }
    }, {  //默认参数设置
        initialRouteName: 'Home',
        tabBarPosition: 'bottom',
        showIcon: true,
        showLabel: true,
        pressOpacity: 0.8,
        tabBarOptions: {
            activeTintColor: 'green',
            style: {
                backgroundColor: '#fff',
            },
        }
    }
);

const AppContainer = createAppContainer(BottomTabNavigator);

export default class TabBottomNavigatorPage extends PureComponent {
    render() {
        return (
            <AppContainer/>
        );
    }
}
 

RouteConfigs 路由配置

路由配置和 StackNavigator 中是一样的,配置路由以及对应的 screen 页面,navigationOptions 为对应路由页面的配置选项:

  • title - Tab标题,可用作headerTitle 和 tabBarLabel 回退标题;
  • tabBarVisible -Tab是否可见,没有设置的话默认为 true;
  • tabBarIcon - Tabicon组件,可以根据 {focused:boolean, tintColor: string} 方法来返回一个icon组件;
  • tabBarLabel -Tab中显示的标题字符串或者组件,也可以根据 { focused: boolean, tintColor: string };方法返回一个组件;

代码示例:

Mine: {
   screen: MineScene,
   navigationOptions: ({ navigation }) => ({
       tabBarLabel: '我的',
       tabBarIcon: ({ focused, tintColor }) => (
           <TabBarItem
               tintColor={tintColor}
               focused={focused}
               normalImage={require('./img/tabbar/pfb_tabbar_mine@2x.png')}
               selectedImage={require('./img/tabbar/pfb_tabbar_mine_selected@2x.png')}
           />
       )
   }),
},
 

TabBarItem自定义组件

class TabBarItem extends PureComponent {
    render() {
        let selectedImage = this.props.selectedImage ? this.props.selectedImage : this.props.normalImage
        return (
            <Image
                source={this.props.focused
                    ? selectedImage
                    : this.props.normalImage}
                style={{ tintColor: this.props.tintColor, width: 25, height: 25 }}
            />
        );
    }
}
 

TabNavigatorConfig Tab选项卡配置

  • tabBarComponent - Tab选项卡组件,有 TabBarBottom 和 TabBarTop 两个值,在iOS中默认为 TabBarBottom ,在Android中默认为 TabBarTop 。

    1. TabBarTop - 在页面顶部;
    2. TabBarBottom - 在页面底部;
  • tabBarPosition - Tab选项卡的位置,有topbottom两个值

    1. top:上面
    2. bottom:下面
  • swipeEnabled - 是否可以滑动切换Tab选项卡;

  • animationEnabled - 点击Tab选项卡切换界面是否需要动画;

  • lazy - 是否懒加载页面;

  • initialRouteName - 初始显示的Tab对应的页面路由名称;

  • order - 用路由名称数组来表示Tab选项卡的顺序,默认为路由配置顺序;

  • paths - 路径配置;

  • backBehavior - android点击返回键时的处理,有 initialRoute 和 none 两个值:

    1. initailRoute - 返回初始界面;
    2. none - 退出;
  • tabBarOptions - Tab配置属性,用在TabBarTopTabBarBottom时有些属性不一致:

用于 TabBarTop 时:

  • activeTintColor - 选中的文字颜色;
  • inactiveTintColor - 未选中的文字颜色;
  • showIcon -是否显示图标,默认显示;
  • showLabel - 是否显示标签,默认显示;
  • upperCaseLabel - 是否使用大写字母,默认使用;
  • pressColor - android 5.0以上的MD风格波纹颜色;
  • pressOpacity - android5.0以下或者iOS按下的透明度;
  • scrollEnabled - 是否可以滚动;
  • tabStyle - 单个Tab的样式;
  • indicatorStyle - 指示器的样式;
  • labelStyle - 标签的样式;
  • iconStyle - icon的样式;
  • style -整个TabBar的样式;

用于 TabBarBottom 时:

  • activeTintColor - 选中Tab的文字颜色;
  • inactiveTintColor - 未选中Tab的的文字颜色;
  • activeBackgroundColor - 选中Tab的背景颜色;
  • inactiveBackgroundColor -未选中Tab的背景颜色;
  • showLabel - 是否显示标题,默认显示;
  • style - 整个TabBar的样式;
  • labelStyle -标签的样式;
  • tabStyle - 单个Tab的样式;

使用底部选项卡:

import React, {Component} from 'react';
import {StackNavigator, TabBarBottom, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
import TabBarItem from "./index18/TabBarItem";
export default class MainComponent extends Component {
    render() {
        return (
            <Navigator/>
        );
    }
}

const TabRouteConfigs = {
    Home: {
        screen: HomeScreen,
        navigationOptions: ({navigation}) => ({
            tabBarLabel: '首页',
            tabBarIcon: ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_homepage_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_homepage_selected_2x.png')}
                />
            ),
        }),
    },
    NearBy: {
        screen: NearByScreen,
        navigationOptions: {
            tabBarLabel: '附近',
            tabBarIcon: ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_merchant_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_merchant_selected_2x.png')}
                />
            ),
        },
    }
    ,
    Mine: {
        screen: MineScreen,
        navigationOptions: {
            tabBarLabel: '我的',
            tabBarIcon: ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_mine_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_mine_selected_2x.png')}
                />
            ),
        },
    }
};
const TabNavigatorConfigs = {
    initialRouteName: 'Home',
    tabBarComponent: TabBarBottom,
    tabBarPosition: 'bottom',
    lazy: true,
};
const Tab = TabNavigator(TabRouteConfigs, TabNavigatorConfigs);
const StackRouteConfigs = {
    Tab: {
        screen: Tab,
    }
};
const StackNavigatorConfigs = {
    initialRouteName: 'Tab',
    navigationOptions: {
        title: '标题',
        headerStyle: {backgroundColor: '#5da8ff'},
        headerTitleStyle: {color: '#333333'},
    }
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);
 

使用顶部选项卡:

import React, {Component} from "react";
import {StackNavigator, TabBarTop, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
export default class MainComponent extends Component {
    render() {
        return (
            <Navigator/>
        );
    }
}

const TabRouteConfigs = {
    Home: {
        screen: HomeScreen,
        navigationOptions: ({navigation}) => ({
            tabBarLabel: '首页',
        }),
    },
    NearBy: {
        screen: NearByScreen,
        navigationOptions: {
            tabBarLabel: '附近',
        },
    }
    ,
    Mine: {
        screen: MineScreen,
        navigationOptions: {
            tabBarLabel: '我的',
        },
    }
};
const TabNavigatorConfigs = {
    initialRouteName: 'Home',
    tabBarComponent: TabBarTop,
    tabBarPosition: 'top',
    lazy: true,
    tabBarOptions: {}
};
const Tab = TabNavigator(TabRouteConfigs, TabNavigatorConfigs);
const StackRouteConfigs = {
    Tab: {
        screen: Tab,
    }
};
const StackNavigatorConfigs = {
    initialRouteName: 'Tab',
    navigationOptions: {
        title: '标题',
        headerStyle: {backgroundColor: '#5da8ff'},
        headerTitleStyle: {color: '#333333'},
    }
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);
 

当然,除了支持创建底部选项卡之外,react-navigation还支持创建顶部选项卡,此时只需要使用react-navigation提供的createMaterialTopTabNavigator即可。如果要使用实现抽屉式菜单功能,还可以使用react-navigation提供的createDrawerNavigator

DrawerNavigator 抽屉导航

有一些APP都会采用侧滑抽屉来实现主页面导航,利用 DrawerNavigator 在RN中可以很方便实现抽屉导航。

DrawerNavigator(RouteConfigs, DrawerNavigatorConfig)
 
  • 1

TabNavigator的构造函数一样,参数配置也类似。

RouteConfigs
抽屉导航的路由配置 RouteConfigs ,和 TabNavigator 的路由配置完全一样, screen 对应路由页面配置, navigationOptions 对应页面的抽屉配置:

  • title - 抽屉标题,和 headerTitle 、 drawerLabel 一样;
  • drawerLabel -标签字符串,或者自定义组件, 可以根据 { focused: boolean, tintColor: string }函数来返回一个自定义组件作为标签;
  • drawerIcon - 抽屉icon,可以根据 { focused: boolean,tintColor: string } 函数来返回一个自定义组件作为icon ;

DrawerNavigatorConfig 属性配置

  • drawerWidth - 抽屉宽度,可以使用Dimensions获取屏幕宽度,实现动态计算;
    drawerPosition -抽屉位置,可以是 left 或者 right
  • contentComponent - 抽屉内容组件,可以自定义侧滑抽屉中的所有内容,默认为DrawerItems
  • contentOptions - 用来配置抽屉内容的属性。当用来配置 DrawerItems 是配置属性选项:
    1. items - 抽屉栏目的路由名称数组,可以被修改;
    2. activeItemKey - 当前选中页面的key id
    3. activeTintColor - 选中条目状态的文字颜色;
    4. activeBackgroundColor - 选中条目的背景色;
    5. inactiveTintColor - 未选中条目状态的文字颜色;
    6. inactiveBackgroundColor - 未选中条目的背景色
    7. onItemPress(route) - 条目按下时会调用此方法;
  • style - 抽屉内容的样式;
    labelStyle -抽屉的条目标题/标签样式;
  • initialRouteName - 初始化展示的页面路由名称;
  • order -抽屉导航栏目顺序,用路由名称数组表示;
  • paths - 路径;
  • backBehavior -android点击返回键时的处理,有initialRoutenone两个值:
    1. initailRoute:返回初始界面;
    2. none :退出

抽屉导航示例:

import React, {Component} from 'react';
import {DrawerNavigator, StackNavigator, TabBarBottom, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
import TabBarItem from "./index18/TabBarItem";
export default class MainComponent extends Component {
    render() {
        return (
            <Navigator/>
        );
    }
}
const DrawerRouteConfigs = {
    Home: {
        screen: HomeScreen,
        navigationOptions: ({navigation}) => ({
            drawerLabel : '首页',
            drawerIcon : ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_homepage_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_homepage_selected_2x.png')}
                />
            ),
        }),
    },
    NearBy: {
        screen: NearByScreen,
        navigationOptions: {
            drawerLabel : '附近',
            drawerIcon : ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_merchant_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_merchant_selected_2x.png')}
                />
            ),
        },
    },
    Mine: {
        screen: MineScreen,
        navigationOptions: {
            drawerLabel : '我的',
            drawerIcon : ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_mine_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_mine_selected_2x.png')}
                />
            ),
        },
    }
};
const DrawerNavigatorConfigs = {
    initialRouteName: 'Home',
    tabBarComponent: TabBarBottom,
    tabBarPosition: 'bottom',
    lazy: true,
    tabBarOptions: {}
};
const Drawer = DrawerNavigator(DrawerRouteConfigs, DrawerNavigatorConfigs);
const StackRouteConfigs = {
    Drawer: {
        screen: Drawer,
    }
};
const StackNavigatorConfigs = {
    initialRouteName: 'Drawer',
    navigationOptions: {
        title: '标题',
        headerStyle: {backgroundColor: '#5da8ff'},
        headerTitleStyle: {color: '#333333'},
    }
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);
 

扩展功能

默认DrawerView不可滚动。要实现可滚动视图,必须使用contentComponent自定义容器:

{  
  drawerWidth:200,  
  抽屉位置:“对”  
  contentComponent:props => <ScrollView> 
  <DrawerItems {... props}></DrawerItems> </ ScrollView>  
}
 

可以覆盖导航使用的默认组件,使用DrawerItems自定义导航组件:

import {DrawerItems} from 'react-navigation';  

const CustomDrawerContentComponent = (props) => (  
  <View style = {style.container}>  
    <DrawerItems {... props} />  
  </View>    
); 
 

嵌套抽屉导航

如果嵌套DrawerNavigation,抽屉将显示在父导航下方。

自定义react-navigation适配顶部导航栏标题
测试中发现,在iphone上标题栏的标题为居中状态,而在Android上则是居左对齐。所以需要修改源码,进行适配。
【node_modules – react-navigation – src – views – Header.js】的326行代码处,修改为如下:

title: {  
   bottom: 0,  
   left: TITLE_OFFSET,  
   right: TITLE_OFFSET,  
   top: 0,  
   position: 'absolute',  
   alignItems: 'center',  
 } 
 

上面方法通过修改源码的方式其实略有弊端,毕竟扩展性不好。还有另外一种方式就是,在navigationOptions中设置headerTitleStyle的alignSelf为 ’ center ‘即可解决。
去除返回键文字显示

【node_modules – react-navigation – src – views – HeaderBackButton.js】的91行代码处,修改为如下即可。

 {
   Platform.OS === 'ios' &&  
     title &&  
     <Text  
       onLayout={this._onTextLayout}  
       style={[styles.title, { color: tintColor }]}  
       numberOfLines={1}  
     >  
       {backButtonTitle}  
     </Text>
  } 
动态设置头部按钮事件

当我们在头部设置左右按钮时,肯定避免不了要设置按钮的单击事件,但是此时会有一个问题,navigationOptions是被修饰为static类型的,所以在按钮的onPress方法中不能直接通过this来调用Component中的方法。如何解决呢?

在官方文档中,作者给出利用设置params的思想来动态设置头部标题。我们可以利用这种方式,将单击回调函数以参数的方式传递到params,然后在navigationOption中利用navigation来取出,并设置到onPress即可:

componentDidMount () {  
   /**  
    * 将单击回调函数作为参数传递  
    */  
   this.props.navigation.setParams({  
           switch: () => this.switchView()  
   });
}  

  /**  
 * 切换视图  
 */  
switchView() {  
    alert('切换')  
}  

static navigationOptions = ({navigation,screenProps}) => ({  
    headerTitle: '企业服务',  
    headerTitleStyle: CommonStyles.headerTitleStyle,  
    headerRight: (  
        <NavigatorItem icon={ Images.ic_navigator } onPress={ ()=> navigation.state.params.switch() }/>  
    ),  
    headerStyle: CommonStyles.headerStyle  
}); 
 

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

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

componentWillMount(){  
    BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid );  
}  

componentUnWillMount(){  
    BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid);  
}  

_onBackAndroid=()=>{  
    let now = new Date().getTime();  
    if(now - lastBackPressed < 2500) {  
        return false;  
    }  
    lastBackPressed = now;  
    ToastAndroid.show('再点击一次退出应用',ToastAndroid.SHORT);  
    return true;  
} 
 

(2)监听react-navigation的Router

/** 
 * 处理安卓返回键 
 */  
const defaultStateAction = AppNavigator.router.getStateForAction;  
AppNavigator.router.getStateForAction = (action,state) => {  
    if(state && action.type === NavigationActions.BACK && state.routes.length === 1) {  
        if (lastBackPressed + 2000 < Date.now()) {  
            ToastAndroid.show(Constant.hint_exit,ToastAndroid.SHORT);  
            lastBackPressed = Date.now();  
            const routes = [...state.routes];  
            return {  
                ...state,  
                ...state.routes,  
                index: routes.length - 1,  
            };  
        }  
    }  
    return defaultStateAction(action,state);  
}; 
 

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

import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';  
 
  • 1

然后在StackNavigator配置下添加如下代码:

transitionConfig:()=>({  
    screenInterpolator: CardStackStyleInterpolator.forHorizontal,  
}) 
 
  • 1
  • 2
  • 3

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

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

export default function<S: *>(navigation: NavigationProp<S, NavigationAction>) {  
  // 添加点击判断  
  let debounce = true;  
  return {  
      ...navigation,  
      goBack: (key?: ?string): boolean =>  
          navigation.dispatch(  
              NavigationActions.back({  
                  key: key === undefined ? navigation.state.key : key,  
              }),  
          ),  
      navigate: (routeName: string,  
                 params?: NavigationParams,  
                 action?: NavigationAction,): boolean => {  
          if (debounce) {  
              debounce = false;  
              navigation.dispatch(  
                  NavigationActions.navigate({  
                      routeName,  
                      params,  
                      action,  
                  }),  
              );  
              setTimeout(  
                  () => {  
                      debounce = true;  
                  },  
              500,  
              );  
              return true;  
          }  
          return false;  
      },  
    /** 
     * For updating current route params. For example the nav bar title and 
     * buttons are based on the route params. 
     * This means `setParams` can be used to update nav bar for example. 
     */  
    setParams: (params: NavigationParams): boolean =>  
      navigation.dispatch(  
        NavigationActions.setParams({  
          params,  
          key: navigation.state.key,  
        }),  
      ),  
  };  
}