1. 首页

手摸手带你如何自定义React Native底部导航栏

如果你觉得 React Navigation 默认 Tab 组件看起来太平淡,或者想创造一些更现代的东西,那么你想法就和我一样。 在本指南中,我将向你演示如何创建自定义标签栏以并与 React Navigation 一起使用。

源码已发布到 github,如果有需要,请点击这里

这是最终完成的样子:

JS中文网 - 前端进阶资源分享

首先,让我们初始化一个新项目并安装几个依赖项。在终端运行如下命令:


$ react-native init CustomTabBar $ cd CustomTabBar $ npm install react-navigation react-native-gesture-handler react-native-pose

React Navigation 从 V3 开始需要依赖 react-native-gesture-handler 库,react-native-pose 是一个很棒的库,我们将用它来制作非常简单的动画。

react-native-gesture-handler 需要通过 link 命令将一些配置自动关联到原生中。


react-native link react-native-gesture-handler

现在我们可以启动应用程序了。

首先——我们创建如下一个目录结构,方便代码管理:


/android /ios ... /src /AppEntry.js /router /router.js /index.js /components /screens /index.js

首先,我们将创建一个 src 目录,将我们的代码与项目根目录中的其他文件(package.json,app.json,.gitignore 等)分开。 screenscomponentsrouter 目录是知名其意的。

我们从项目的根目录中删除默认的App.js文件,并在 index.js 中写入import /src/AppEntry.js


/* /index.js */ import { AppRegistry } from "react-native"; import App from "./src/AppEntry"; import { name as appName } from "./app.json"; AppRegistry.registerComponent(appName, () => App);

现在我们想要使用 react-navigation 创建路由器,但是首先我们需要创建一些 screen(就是页面)。我们将创建一个通用的 Screen 组件,它接受一个名称并显示它来模拟多个 Screen

/src/screens/index.js 添加如下内容:

码农进阶题库,每天一道面试题 or Js小知识


/* /src/screens/index.js */ import React from "react" import Screen from "./Screen" export const HomeScreen = () => <Screen name="Home"/> export const SearchScreen = () => <Screen name="Search" /> export const FavoritesScreen = () => <Screen name="Favorites" /> export const ProfileScreen = () => <Screen name="Profile" />;

现在我们创建 Screen 组件。


/* /src/screens/Screen.js */ import React from "react"; import { Text, View, StyleSheet } from "react-native"; const S = StyleSheet.create({ container: { flex: 1, backgroundColor: "#bbbbbb", justifyContent: "center", alignItems: "center" }, text: { fontSize: 28, color: "#222222", textAlign: "center" } }); const Screen = ({ name }) => ( <View style={S.container}> <Text style={S.text}>This is the "{name}" screen</Text> </View> ); export default Screen;

接着创建路由,首先在 /src/router/index.js 在添加如下内容:


/* /src/router/index.js */ export { default as Router } from "./router";

现在让我们在 router.js 中创建基本的 BottomTabNavigator。 我们将导入 screens 并使用createBottomTabNavigator 创建默认选项卡导航器。


/* /src/router/router.js */ import { createAppContainer, createBottomTabNavigator } from "react-navigation"; import { HomeScreen, SearchScreen, FavoritesScreen, ProfileScreen } from "../screens"; const TabNavigator = createBottomTabNavigator({ HomeScreen, SearchScreen, FavoritesScreen, ProfileScreen }); export default createAppContainer(TabNavigator);

现在我们在 AppEntry.js 中渲染路由:


/* /src/AppEntry.js */ import React from "react"; import { Router } from "./router"; export default () => <Router />;

当我们重新加载应用程序时,应该会如下内容:

码农进阶题库,每天一道面试题 or Js小知识

默认标签栏支持图标,我们将在本教程中使用 ascii 字符,当然在实际应用中可以使用 react-native-vector-icons 或自定义图标字体。

让我们创建一个 Icon 组件,接受参数为 namecolor 并返回图标。


/* /src/components/index.js */ export { default as Icon } from "./Icon";

/ /src/components/Icon.js /


import React from "react"; import { Text } from "react-native"; const iconMap = { home: "♡", search: "♢", favorites: "♧", profile: "♤" }; const Icon = ({ name, color, style, ...props }) => { const icon = iconMap[name]; return <Text style={[{ fontSize: 26, color }, style]}>{icon}</Text>; }; export default Icon;

现在我们可以在路由器中使用这个组件。我们在 router.js 中更改 screens ,以接受带有navigationOptions 配置的对象。默认选项卡栏将 tintColor 传递给图标组件,因此我们使用它来设置图标颜色。


/* /src/router/router.js */ import { createAppContainer, createBottomTabNavigator } from "react-navigation"; import React from "react"; import { HomeScreen, SearchScreen, FavoritesScreen, ProfileScreen } from "../screens"; import {Icon} from '../components' const TabNavigator = createBottomTabNavigator({ HomeScreen: { screen: HomeScreen, navigationOptions: { tabBarIcon: ({ tintColor }) => <Icon name="home" color={tintColor} /> } }, SearchScreen: { screen: SearchScreen, navigationOptions: { tabBarIcon: ({ tintColor }) => <Icon name="search" color={tintColor} /> } }, FavoritesScreen: { screen: FavoritesScreen, navigationOptions: { tabBarIcon: ({ tintColor }) => <Icon name="favorites" color={tintColor} /> } }, ProfileScreen: { screen: ProfileScreen, navigationOptions: { tabBarIcon: ({ tintColor }) => <Icon name="profile" color={tintColor} /> } } }); export default createAppContainer(TabNavigator);

运行效果如下:

码农进阶题库,每天一道面试题 or Js小知识

现在我们的标签栏看起来好一点,但它仍然是 react-navigation 的默认标签栏。 接下来,我们将添加实际的自定义标签栏组件。

让我们从创建一个自定义 TabBar 组件开始,该组件只渲染一些文本并打印传递过来的 props ,这样我们就可以看到我们从导航器中得到了什么 props


/* /src/components/index.js */ export { default as Icon } from "./Icon"; export { default as TabBar } from "./TabBar";

/* /src/components/TabBar.js */ import React from "react"; import { Text } from "react-native"; const TabBar = props => { console.log("Props", props); return <Text>Custom Tab Bar</Text>; }; export default TabBar;

使用自定义标签栏需要配置 createBottomTabNavigator 第二个参数, 我们可以添加以下配置作为createBottomTabNavigator 的第二个参数。

如果我们查看标签栏打印了什么,我们会看到导航栏中有 navigation.state状态,其中也包含路由。还有 renderIcon 函数,onTabPress 和很多我们可能需要的东西。此外,我们还注意到我们在路由器配置中 tabBarOptions 是如何被注入到组件中的。

现在重新编写 TabBar 组件。首先,让我们尝试重新创建默认选项卡栏。我们将在容器上设置一些样式,以便将选项卡按钮排成一行,并为每个路由呈现一个选项卡按钮。我们可以使用 renderIcon 函数来渲染正确的图标——通过查看源代码,该函数需要传入一个对象参数: { route, focused, tintColor }。我们添加了onPress 处理程序、易访问性标签,这样就有了默认的选项卡栏。


/* /src/components/TabBar.js */ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; const S = StyleSheet.create({ container: { flexDirection: "row", height: 52, elevation: 2 }, tabButton: { flex: 1, justifyContent: "center", alignItems: "center" } }); const TabBar = props => { const { renderIcon, getLabelText, activeTintColor, inactiveTintColor, onTabPress, onTabLongPress, getAccessibilityLabel, navigation } = props; const { routes, index: activeRouteIndex } = navigation.state; return ( <View style={S.container}> {routes.map((route, routeIndex) => { const isRouteActive = routeIndex === activeRouteIndex; const tintColor = isRouteActive ? activeTintColor : inactiveTintColor; return ( <TouchableOpacity key={routeIndex} style={S.tabButton} onPress={() => { onTabPress({ route }); }} onLongPress={() => { onTabLongPress({ route }); }} accessibilityLabel={getAccessibilityLabel({ route })} > {renderIcon({ route, focused: isRouteActive, tintColor })} <Text>{getLabelText({ route })}</Text> </TouchableOpacity> ); })} </View> ); }; export default TabBar;

运行后,效果如下:

码农进阶题库,每天一道面试题 or Js小知识

现在我们知道我们可以灵活地创建自己的标签栏,因此我们可以开始实际扩展它。 我们将使用 react-native-pose 创建一个动画视图,该视图将突出显示活动路径 – 我们将此视图称为聚光灯。

首先我们可以去掉标签。然后我们在标签栏后面添加一个绝对视图,它将显示聚光灯效果。我们使用Dimensions API 计算聚光灯的偏移量。

/* /src/components/TabBar.js */

import React from "react";
import { View, Text, StyleSheet, TouchableOpacity, Dimensions } from "react-native";
import posed from "react-native-pose";

const windowWidth = Dimensions.get("window").width;
const tabWidth = windowWidth / 4;

const SpotLight = posed.View({
  route0: { x: 0 },
  route1: { x: tabWidth },
  route2: { x: tabWidth * 2 },
  route3: { x: tabWidth * 3 }
});

const S = StyleSheet.create({
  container: { flexDirection: "row", height: 52, elevation: 2 },
  tabButton: { flex: 1, justifyContent: "center", alignItems: "center" },
  spotLight: {
    width: tabWidth,
    height: "100%",
    backgroundColor: "rgba(128,128,255,0.2)",
    borderRadius: 8
  }
});

const TabBar = props => {
  const {
    renderIcon,
    getLabelText,
    activeTintColor,
    inactiveTintColor,
    onTabPress,
    onTabLongPress,
    getAccessibilityLabel,
    navigation
  } = props;

  const { routes, index: activeRouteIndex } = navigation.state;

  return (
    <View style={S.container}>
      <View style={StyleSheet.absoluteFillObject}>
        <SpotLight style={S.spotLight} pose={`route${activeRouteIndex}`} />
      </View>
      {routes.map((route, routeIndex) => {
        const isRouteActive = routeIndex === activeRouteIndex;
        const tintColor = isRouteActive ? activeTintColor : inactiveTintColor;

        return (
          <TouchableOpacity
            key={routeIndex}
            style={S.tabButton}
            onPress={() => {
              onTabPress({ route });
            }}
            onLongPress={() => {
              onTabLongPress({ route });
            }}
            accessibilityLabel={getAccessibilityLabel({ route })}
          >
            {renderIcon({ route, focused: isRouteActive, tintColor })}

            <Text>{getLabelText({ route })}</Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

export default TabBar;



运行效果如下:

JS中文网 - 前端进阶资源分享

请注意,我们从未指定动画的持续时间和行为, Pos e负责使用合理的默认值。

现在我们将为选中图标添加一些缩放:


/* /src/components/TabBar.js */ ... const Scaler = posed.View({ active: { scale: 1.25 }, inactive: { scale: 1 } }); ...

现在我们可以像这样将图标包装在 Scaler 组件中。


/* /src/components/TabBar.js */ <Scaler style={S.scaler} pose={isRouteActive ? "active" : "inactive"}> {renderIcon({ route, focused: isRouteActive, tintColor })} </Scaler>

运行效果如下:

JS中文网 - 前端进阶资源分享

我们的标签栏开始看起来很不错。 剩下要做的就是稍微改善一下,改变配色方案,调整我们的聚光灯,我们的组件就完成了。

JS中文网 - 前端进阶资源分享

现在,我们可以在这里改进一些事情。 例如,当前的实现假设选项卡导航器中总会有 4 个 Screen,聚光灯颜色在选项卡栏组件中是写死。样式应该通过路由器上的 tabBarOptions 配置进行动态编写的,这边不会讲这些,大家自己动手做做。

TabBar 组件的完整代码:

/* /src/components/TabBar.js */

import React from "react";
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  Dimensions
} from "react-native";
import posed from "react-native-pose";

const windowWidth = Dimensions.get("window").width;
const tabWidth = windowWidth / 4;
const SpotLight = posed.View({
  route0: { x: 0 },
  route1: { x: tabWidth },
  route2: { x: tabWidth * 2 },
  route3: { x: tabWidth * 3 }
});

const Scaler = posed.View({
  active: { scale: 1.25 },
  inactive: { scale: 1 }
});

const S = StyleSheet.create({
  container: {
    flexDirection: "row",
    height: 52,
    elevation: 2,
    alignItems: "center"
  },
  tabButton: { flex: 1 },
  spotLight: {
    width: tabWidth,
    height: "100%",
    justifyContent: "center",
    alignItems: "center"
  },
  spotLightInner: {
    width: 48,
    height: 48,
    backgroundColor: "#ee0000",
    borderRadius: 24
  },
  scaler: { flex: 1, alignItems: "center", justifyContent: "center" }
});

const TabBar = props => {
  const {
    renderIcon,
    activeTintColor,
    inactiveTintColor,
    onTabPress,
    onTabLongPress,
    getAccessibilityLabel,
    navigation
  } = props;

  const { routes, index: activeRouteIndex } = navigation.state;

  return (
    <View style={S.container}>
      <View style={StyleSheet.absoluteFillObject}>
        <SpotLight style={S.spotLight} pose={`route${activeRouteIndex}`}>
          <View style={S.spotLightInner} />
        </SpotLight>
      </View>

      {routes.map((route, routeIndex) => {
        const isRouteActive = routeIndex === activeRouteIndex;
        const tintColor = isRouteActive ? activeTintColor : inactiveTintColor;

        return (
          <TouchableOpacity
            key={routeIndex}
            style={S.tabButton}
            onPress={() => {
              onTabPress({ route });
            }}
            onLongPress={() => {
              onTabLongPress({ route });
            }}
            accessibilityLabel={getAccessibilityLabel({ route })}
          >
            <Scaler
              pose={isRouteActive ? "active" : "inactive"}
              style={S.scaler}
            >
              {renderIcon({ route, focused: isRouteActive, tintColor })}
            </Scaler>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

export default TabBar;



路由器配置如下:

/ /src/router/router.js /


... const TabNavigator = createBottomTabNavigator( /* screen config ommited */, { tabBarComponent: TabBar, tabBarOptions: { activeTintColor: "#eeeeee", inactiveTintColor: "#222222" } } );

作者:前端小智
链接:https://segmentfault.com/a/1190000018873904

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:手摸手带你如何自定义React Native底部导航栏

链接:https://www.javascriptc.com/3431.html

« 程序员升级打怪[2019]:前端 UI-UX设计
前端加载优化实战,减少页面渲染时间 66%»
Flutter 中文教程资源

相关推荐

QR code