如何在 React Native Expo 中配置 TabBar

65 阅读5分钟

在 React Native Expo 中配置 TabBar(底部 / 顶部标签栏),核心依赖 expo-router 的 Tabs 组件(推荐)或 React Navigation 的 createBottomTabNavigator,以下是 expo-router 主流方案(适配 iOS / 安卓、支持自定义样式 / 图标 / 交互)的完整配置指南:

一、核心前提

  1. 确保已安装依赖(Expo 项目默认集成,无需额外安装):
expo install expo-router @expo/vector-icons react-native-screens react-native-safe-area-context
  1. 基础文件结构(expo-router 推荐的路由分组)
app/
├── (tabs)/          # Tabs 分组(括号避免路由路径污染)
│   ├── _layout.tsx  # Tabs 配置文件(核心)
│   ├── home.tsx     # 首页 Tab
│   ├── explore.tsx  # 发现 Tab
│   └── profile.tsx  # 我的 Tab
├── _layout.tsx      # 根布局(Stack 包裹 Tabs)
└── index.tsx        # 入口页(可选,可重定向到 Tabs)

二、基础配置(默认样式)

1. Tabs 核心配置文件(app/(tabs)/_layout.tsx

import { Tabs } from "expo-router";
import { Ionicons } from "@expo/vector-icons"; // 推荐使用 Expo 内置图标
import { Platform, StyleSheet, TouchableOpacity } from "react-native";

export default function TabsLayout() {
  return (
    <Tabs
      // 全局 TabBar 配置所有 Tab 共享screenOptions={{
        // 1. 基础样式
        tabBarStyle: styles.tabBar, // TabBar 容器样式
        tabBarItemStyle: styles.tabItem, // 单个 Tab 项样式
        tabBarLabelStyle: styles.tabLabel, // Tab 文字样式
        tabBarIconStyle: styles.tabIcon, // Tab 图标样式

        // 2. 交互/视觉
        tabBarActiveTintColor: "#007AFF", // 选中态颜色文字/图标tabBarInactiveTintColor: "#8E8E93", // 未选中态颜色
        tabBarShowLabel: true, // 是否显示文字默认 truetabBarAllowFontScaling: false, // 禁用字体缩放安卓适配headerShown: false, // 隐藏每个 Tab 页的顶部导航栏如需显示可单独开启)
        
        // 安卓取消水波纹效果
        tabBarButton: (props) => (
          // @ts-ignore
          <TouchableOpacity
            activeOpacity={1}
            style={[props.style, { backgroundColor: 'transparent' }]}
            {...props}
          />
        )
      }}
    >
      {/* 首页 Tab */}
      <Tabs.Screen
        name="home" // 对应 app/(tabs)/home.tsx
        options={{
          title: "首页", // Tab 文字
          // 自定义图标根据选中状态切换tabBarIcon: ({ color, size }) => (
            <Ionicons name="home-outline" color={color} size={size} />
          ),
          // 单独覆盖当前 Tab 的样式(可选)
          tabBarLabelStyle: { fontSize: 12 },
        }}
      />

      {/* 发现 Tab */}
      <Tabs.Screen
        name="explore"
        options={{
          title: "发现",
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="compass-outline" color={color} size={size} />
          ),
          // 可选:隐藏当前 Tab 的文字(仅显示图标)
          // tabBarShowLabel: false,
        }}
      />

      {/* 我的 Tab */}
      <Tabs.Screen
        name="profile"
        options={{
          title: "我的",
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person-outline" color={color} size={size} />
          ),
          // 可选:自定义选中态颜色(覆盖全局)
          // tabBarActiveTintColor: "#FF3B30",
        }}
      />
    </Tabs>
  );
}

// 样式封装(适配 iOS/安卓)
const styles = StyleSheet.create({
  // TabBar 容器
  tabBar: {
    height: Platform.OS === "ios" ? 60 : 66, // 适配安卓高度
    paddingVertical: 5, // 内容垂直居中
    justifyContent: "center", // 子元素居中
    borderTopWidth: 0, // 移除顶部边框(可选)
    elevation: 0, // 移除安卓阴影(可选)
    shadowOpacity: 0, // 移除 iOS 阴影(可选)
  },
  // 单个 Tab 项
  tabItem: {
    paddingVertical: 0, // 清空默认内边距(避免内容偏移)
    justifyContent: "center", // 垂直居中
  },
  // Tab 文字
  tabLabel: {
    fontSize: 12,
    marginTop: 2, // 图标与文字间距
    fontWeight: "400",
  },
  // Tab 图标
  tabIcon: {
    size: 24,
  },
});

2. 根布局包裹 Tabs(app/_layout.tsx

import { Stack } from "expo-router";

export default function RootLayout() {
  return (
    <Stack>
      {/* 包裹 Tabs 分组,隐藏根导航栏(如需显示可调整) */}
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      {/* 其他页面(如模态页、详情页) */}
      <Stack.Screen name="detail/[id]" options={{ title: "详情页" }} />
    </Stack>
  );
}

三、进阶配置(自定义增强)

1. 自定义 TabBar 组件(完全自定义样式)

若默认样式无法满足需求,可替换为自定义 TabBar 组件(支持任意布局 / 交互):

// app/(tabs)/_layout.tsx
import { Tabs, useRouter } from "expo-router";
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
import { Ionicons } from "@expo/vector-icons";

// 自定义 TabBar 组件
const CustomTabBar = ({ state, descriptors, navigation }) => {
  const router = useRouter();

  // Tab 列表(与页面名一一对应)
  const tabs = [
    { name: "home", title: "首页", icon: "home-outline" },
    { name: "explore", title: "发现", icon: "compass-outline" },
    { name: "profile", title: "我的", icon: "person-outline" },
  ];

  return (
    <View style={styles.customTabBar}>
      {tabs.map((tab, index) => {
        // 当前 Tab 是否选中
        const isFocused = state.index === index;
        const { options } = descriptors[`/(tabs)/${tab.name}`];

        // 点击 Tab 跳转
        const onPress = () => {
          const event = navigation.emit({
            type: "tabPress",
            target: tab.name,
            canPreventDefault: true,
          });
          if (!isFocused && !event.defaultPrevented) {
            router.push(`/(tabs)/${tab.name}`);
          }
        };

        return (
          <TouchableOpacity
            key={tab.name}
            onPress={onPress}
            style={styles.customTabItem}
          >
            {/* 图标 */}
            <Ionicons
              name={tab.icon}
              size={24}
              color={isFocused ? "#007AFF" : "#8E8E93"}
            />
            {/* 文字 */}
            <Text
              style={[
                styles.customTabLabel,
                { color: isFocused ? "#007AFF" : "#8E8E93" },
              ]}
            >
              {tab.title}
            </Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

export default function TabsLayout() {
  return (
    <Tabs
      // 替换为自定义 TabBar
      tabBar={(props) => <CustomTabBar {...props} />}
      screenOptions={{
        headerShown: false,
      }}
    >
      <Tabs.Screen name="home" options={{ title: "首页" }} />
      <Tabs.Screen name="explore" options={{ title: "发现" }} />
      <Tabs.Screen name="profile" options={{ title: "我的" }} />
    </Tabs>
  );
}

const styles = StyleSheet.create({
  customTabBar: {
    flexDirection: "row",
    height: 60,
    backgroundColor: "#fff",
    borderTopWidth: 1,
    borderTopColor: "#eee",
    justifyContent: "space-around",
    alignItems: "center",
  },
  customTabItem: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  customTabLabel: {
    fontSize: 12,
    marginTop: 2,
  },
});

2. 配置顶部 TabBar(而非底部)

只需修改 screenOptions 中的 tabBarPosition

<Tabs
  screenOptions={{
    tabBarPosition: "top", // 改为顶部 TabBar
    tabBarStyle: {
      height: 50, // 顶部 TabBar 高度
      borderBottomWidth: 1,
      borderTopWidth: 0,
    },
    // 其他配置不变
  }}
>
  {/* ... Tab 项 ... */}
</Tabs>

3. 隐藏 / 显示指定 Tab

  • 临时隐藏:通过 tabBarButton 设置为 () => null
<Tabs.Screen
  name="profile"
  options={{
    tabBarButton: () => null, // 隐藏该 Tab(仍可通过路由跳转)
  }}
/>
  • 永久移除:直接删除对应的 Tabs.Screen 配置。

4. 适配安卓底部安全区

import Constants from "expo-constants";

const styles = StyleSheet.create({
  tabBar: {
    height: Platform.OS === "ios" ? 60 : 66,
    paddingBottom: Platform.OS === "android" ? Constants.statusBarHeight / 2 : 0,
    // 其他样式
  },
});

四、常见问题与避坑

  1. TabBar 文字 / 图标不居中

    • 清空 tabBarItemStyle.paddingVertical,设置 justifyContent: center
    • 安卓需调整 tabBar.height,避免默认内边距导致偏移。
  2. Tab 跳转无响应

    • 确保 Tabs.Screen 的 name 与页面文件名完全一致(如 home 对应 home.tsx);
    • 自定义 TabBar 需绑定 onPress 事件并调用 router.push
  3. iOS 底部 TabBar 有阴影

    • 设置 tabBarStyle.shadowOpacity: 0 和 elevation: 0
  4. TabBar 样式覆盖不生效

    • 页面级 options 优先级 > 全局 screenOptions,检查是否有局部覆盖;
    • 自定义 TabBar 需完全自己控制样式,默认配置不再生效。

五、扩展功能(可选)

1. 带角标的 Tab

<Tabs.Screen
  name="home"
  options={{
    tabBarBadge: 99, // 角标数字
    tabBarBadgeStyle: { backgroundColor: "#FF3B30" }, // 角标样式
  }}
/>

2. 切换 Tab 时的动画

<Tabs
  screenOptions={{
    tabBarAnimation: "fade", // 切换动画:fade/slide/none
  }}
>
  {/* ... */}
</Tabs>

3. 禁止 Tab 重复点击

在自定义 TabBar 的 onPress 中判断是否已选中:

const onPress = () => {
  if (isFocused) return; // 已选中则不处理
  // 跳转逻辑
};

总结

Expo Router 的 Tabs 组件是配置 TabBar 的最优方式,核心步骤:

  1. 按 (tabs) 分组组织页面;
  2. 在 (tabs)/_layout.tsx 中配置全局样式和 Tab 项;
  3. 按需自定义 TabBar 组件(完全控制样式 / 交互);
  4. 适配 iOS / 安卓的高度、安全区、样式差异。

以上配置覆盖 90% 的业务场景,如需更复杂的交互(如中间凸起按钮、渐变样式),可基于自定义 TabBar 组件扩展。