React-navigation5.x(createBottomTabNavigator)底部导航栏

4,170 阅读5分钟

前言

自己本身也是一个刚接触react-native的新手,甚至对于RN的书写也是基于函数组件来写搭配react hooks的钩子函数,在配合react-navigation5.x的最新版本慢慢熟路整个开发。这样搭配是对我这样的新手来说最舒服的。

写这个文档,也是基于网上很少关于react-navigation5.x的信息,也是借这个机会和大家分享一下自己的学习经历(欢迎讨论,指错,一起进步)

image

前期准备

  1. 你得了解过React(这是基本的),安装好RN的开发环境,并且可以跑起来.
  2. 安装最新版本的react-navigation5.x,这里面有点小坑,这是我之前写的安装过程(juejin.cn/post/684490…),凑乎着看看-。-。
  3. emm..跑起来

React-navigation5.x的createBottomTabNavigator

React-navigation5.x有很多导航的方式,今天就介绍一种(我就看了这一种)

官方地址

reactnavigation.org/docs/bottom…

安装(没错还要安装)

npm install @react-navigation/bottom-tabs

引入官方示例

//router.js
//注意引入
import 'react-native-gesture-handler';
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="My" component={SettingsScreen} />
    </Tab.Navigator>
  );
}
//别忘把你的router.js 在index.js中引入
//只要能跑起来就行,每个人都有自己的写法
import {AppRegistry} from 'react-native';
import App from './router';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);

大概这样(下面的文字不一样(Home,My))

imag

Tab.Navigator属性

  • initialRouteName:初始化时(第一次需要显示的页面)路由的名称,没有的话按顺序(第一个作为首先显示的页面)
<Tab.Navigator initialRouteName="Home">
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="My" component={SettingsScreen} />
</Tab.Navigator>
  • screenOptions:作用于屏幕的选项(createStackNavigator中的这个属性是对自定义标题栏的修改)

To set a custom header for all the screens in the navigator, you can specify this option in the screenOptions prop of the navigator

  • backBehavior:后退的按钮处理行为
    • initialRoute :返回初始的选项卡
    • order:返回上一个选项卡(按照他们的顺序)
    • history:返回上次访问的选项卡
    • none :无操作行为
<Tab.Navigator backBehavior="none">
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="My" component={SettingsScreen} />
</Tab.Navigator>
  • lazy:懒加载(啥时候激活啥时候渲染,默认为true)
    • true
    • false
  • tabBar:自定义导航栏(不满足他们的样式,自己定义样式和事件),他是一个方

法,返回的是React element,本文会进行梳理此属性(这里不做过多解释)

  • tabBarOptions:对底部导航栏的组件的选项管理

    • activeTintColor:图标和文字的选中的颜色
    • activeBackgroundColor:当前所选中标签的背景色
    • inactiveTintColor:没有选中的图标和文字的颜色
    • inactiveBackgroundColor :没有选中的标签背景色
    • showLabel :是否显示标签的文字
    • showIcon :是否显示icon图标
    • style :选项卡的样式对象(可以定义添加样式)
    • labelStyle :对标签的文字的样式修改(字体啥的)
    • labelPosition :icon图标和文字的位置
      • beside-icon:左右排列
      • below-icon:上下排列
    • tabStyle:单个标签的样式对象(和style属性的区别就是一个单个作用范围,一个整个底部的范围)
      imag
    • allowFontScaling :标签文字的是否支持缩放,适应屏幕(默认为true)
    • adaptive :标签图标和文字的对齐是否应该根据屏幕大小而改变?iOS 11默认为true。如果是假的,选项卡图标和标签会一直垂直对齐。如果是,则选项卡图标和标签在tablet上水平对齐
    • safeAreaInset :安全区域(在使用带有缺口的屏幕时[iPhoneX],保证内容可以显示出来)

    Override the forceInset prop for . Defaults to { bottom: 'always', top: 'never' }. Available keys are top | bottom | left | right provided with the values 'always' | 'never'

    再附一个地址对SafeAreaView的介绍使用

    • keyboardHidesTabBar:是否在键盘弹起时隐藏底部tab栏 (默认false)

    Tab.Screen的options属性

    • title:通用标题(headerTitle和tabBarLabel的备用)
    • tabBarVisible:是否显示此标签(默认为true)
    • tabBarIcon:标签的自定义icon,我这边是图片,返回一个React.Node 参数{focused: boolean, color: string, size: number}
    //IndexIcon
    function IndexIcon(props) {
        if (props.focused) {
            return (
                <Image source={require('./assets/images/home.png')} style={props.style}></Image>
            )
        } else {
            return (
                <Image source={require('./assets/images/no_home.png')} style={props.style}></Image>
            )
        }
    }
    
    <Tab.Screen name="Index" component={Index} options={{
        tabBarLabel: '主页',
        tabBarIcon: ({ focused,color, size }) => (
            <IndexIcon style={{ width: size, height: size }} focused={focused} />
        )
    }} title='666' />
    
    • tabBarLabel:标签文字(可以是字符串,也可以是React.node)
    • tabBarButton:可以自定义单个标签栏,包括图标和文字,返回的是React element
    • tabBarTestID:标签定位的id
    • unmountOnBlur:当切换标签时是否保留当前屏幕的状态(默认为false)

Events

  • tabPress:标签栏点击事件监听
React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabPress', e => {
    // Prevent default behavior
    e.preventDefault();

    // Do something manually
    // ...
  });

  return unsubscribe;
}, [navigation]);
  • tabLongPress:长按事件监听
React.useEffect(() => {
  const unsubscribe = navigation.addListener('tabLongPress', e => {
    // Do something
  });

  return unsubscribe;
}, [navigation]);

自定义(定制)底部标签栏

如果对于官方提供的底部标签的UI不满意,可以进行定制化修改,也就是Tab.NavigatortabBar属性

<Tab.Navigator tabBar={props => <MyTabBar {...props} />}>
//MyTabBar部分
function MyTabBar({ state, descriptors, navigation }) {
//state,descriptors
    return (
        <View style={{ flexDirection: 'row' }}>
            {state.routes.map((route, index) => {
                const { options } = descriptors[route.key];
                const isFocused = state.index === index;
                const label =
                    options.tabBarLabel !== undefined
                        ? options.tabBarLabel
                        : options.title !== undefined
                            ? options.title
                            : route.name;
                const onPress=function(){...}
                return (
                    <TouchableOpacity
                        accessibilityRole="button"
                        accessibilityStates={isFocused ? ['selected'] : []}
                        accessibilityLabel={options.tabBarAccessibilityLabel}
                        testID={options.tabBarTestID}
                        style={{ flex: 1, borderWidth: 1, borderColor: 'red' }}
                        onPress={onPress}
                    >
                        <Text style={{ color: isFocused ? '#673ab7' : '#222' }}>
                            {label}
                        </Text>
                    </TouchableOpacity>
                );
            })}
        </View>
    );
}

打印出state的值

{
     "stale": false,
     "type": "tab",
     "key": "tab-n3rBjaO1X",
     "index": 0,
     "routeNames": [
          "Index",
          "My"
     ],
     "history": [
          {
               "type": "route",
               "key": "Index-k0BnDQf7M"
          }
     ],
     "routes": [
          {
               "name": "Index",
               "key": "Index-k0BnDQf7M"
          },
          {
               "name": "My",
               "key": "My-5atljuCzM"
          }
     ]
}

打印descriptors的值

{
     "Index-k0BnDQf7M": {
          "navigation": {},
          "options": {
               "title": "6666",
               "headerStyle": {
                    "height": 10
               },
               "tabBarLabel": "主页"
          }
     },
     "My-5atljuCzM": {
          "navigation": {},
          "options": {
               "title": "6666",
               "headerStyle": {
                    "height": 10
               },
               "tabBarLabel": "我的"
          }
     }
}

so,这两个值打印出来之后,我们就可以从这两个值里面取出我们需要的部分,现在展示出来的样子大概是这样(有点丑,不过没关系,样式啥可以自己发挥)

img

接下来就是点击事件这个东西

function MyTabBar({ state, descriptors, navigation }) {
//state,descriptors
    return (
        <View style={{ flexDirection: 'row' }}>
            {state.routes.map((route, index) => {
            ...
                const onPress = () => {
                //给当前点击的标签发送```tabPress```事件
                  const event = navigation.emit({
                    type: 'tabPress',
                    target: route.key,
                  });
                //判断是否已经在当前标签页面
                  if (!isFocused && !event.defaultPrevented) {
                    navigation.navigate(route.name);
                  }
                };
            })}
        </View>
    );
}