【React Native】布局文件-底部TabBar

发布于:2025-07-18 ⋅ 阅读:(33) ⋅ 点赞:(0)

布局文件-底部tabBar

内容配置

export default function Layout() {
  return (
    <Tabs />
  );
}

默认会将布局文件是将与它在同一个目录的所有文件,包括下级目录的文件,全都配置成Tab了。:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样做显然不对,正确的做法是

  • app目录里新建一个(tabs)文件夹,注意了,名字上有一对小括号。
  • 里面新建一个_layout.js布局文件,这里就专门放TabBar配置。
  • 然后将index.js,挪动到(tabs)里面。
  • (tabs)里,再新建一个videos.jsusers.js文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

app/_layout.js

import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack
      screenOptions={{
        title: '',                      // 默认标题为空
        headerTitleAlign: 'center',     // 安卓标题栏居中
        animation: 'slide_from_right',  // 安卓使用左右切屏
        headerTintColor: '#1f99b0',     // 导航栏中文字、按钮、图标的颜色
        headerTitleStyle: {             // 标题组件的样式
          fontWeight: '400',
          color: '#2A2929',
          fontSize: 16,
        },
        headerBackButtonDisplayMode: 'minimal', // 设置返回按钮只显示箭头,不显示文字
      }}
    >
      {/* Tabs */}
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />

      {/* Cards */}
      <Stack.Screen name="articles/index" options={{ title: '通知' }} />
      <Stack.Screen name="settings/index" options={{ title: '设置' }} />
      <Stack.Screen name="courses/[id]" options={{ title: '课程详情' }} />
      <Stack.Screen name="search/index" options={{ title: '搜索' }} />
    </Stack>
  );
}
  • TabBar的配置:

    • 注意这里有个headerShown,这是因为TabBar也会自带一个导航栏。
    • 如果不隐藏,它会和Stack的导航栏同时出现,这就会出来两个导航栏了。
  • 底下给各个页面都添加上了title

  • Stack里页面是有两种形式的:

    • 这种页面左右滑动跳转的就叫Cards
    • 另一种页面从屏幕底部弹出的,叫做模态(Modal)

app/(tabs)/_layout.js

import { Link, Tabs } from 'expo-router'
import { Image } from 'expo-image'
import { SimpleLineIcons } from '@expo/vector-icons'
import { StyleSheet, TouchableOpacity } from 'react-native'

/**
 * 导航栏 Logo 组件
 */
function LogoTitle() {
  return <Image style={style.logo} contentFit="contain" source={require('../../assets/logo-light.png')}/>
}

/**
 * 导航栏按钮组件
 * @param props
 */
function HeaderButton({ href, ...rest }) {
  return (
    <Link href={href} asChild>
      <TouchableOpacity>
        <SimpleLineIcons size={20} color="#1f99b0" {...rest} />
      </TouchableOpacity>
    </Link>
  )
}

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        headerTitleAlign: 'center',       // 安卓标题栏居中
        headerTitle: props => <LogoTitle {...props} />,
        headerLeft: () => <HeaderButton name="bell" href="/articles" style={style.headerLeft} />,
        headerRight: () => (
          <>
            <HeaderButton name="magnifier" href="/search" style={style.headerRight} />
            <HeaderButton name="options" href="/settings" style={style.headerRight} />
          </>
        ),
      }}
    >
      <Tabs.Screen
        name="index"
        options={{ title: '首页' }}
      />
      <Tabs.Screen
        name="videos"
        options={{ title: '视频课程' }}
      />
      <Tabs.Screen
        name="users"
        options={{ title: '我的' }}
      />
    </Tabs>
  );
}

const style = StyleSheet.create({
  logo: {
    width: 130,
    height: 30,
  },
  headerLeft: {
    marginLeft: 15,
  },
  headerRight: {
    marginRight: 15,
  },
});

(tabs)目录,专门用来放各个Tab页。名字上的这个小括号,叫做路由分组

  • 利用它,将一些相关的文件,放在一起。

  • 这种带小括号的目录名,在URL里不计算路径!

    • index.jsURL,依然还是/index,就像还在app目录里一样,它依然还是首页
    • videos.js文件的URL,其实是/videos,而不是/(tabs)/videos,同理。
  • 底下的和刚才不同,刚才的布局文件里使用的是StackStack.Screen。这里要用TabsTabs.Screen

  • 然后将顶部的按钮,配置到了最外层的screenOptions里,这样所有的Tab页在导航栏上,都会有Logo和按钮。

  • Tab页,是可以随意跳转到非Tab页的。从哪里都能跳过去,它就属于共享路由

  • 一个项目里,也可以有多个布局文件,布局文件只对和它同级或下级文件生效。

图标和样式

在上面的基础上增加图标和样式:

    /**
     * TabBar 图标组件
     * @param props
     */
    function TabBarIcon(props) {
      return <SimpleLineIcons size={25} {...props} />;
    }

      <Tabs . Screen
        name="users"
        options={{
          title: "我的",
          tabBarIcon: ({ color }) => <TabBarIcon name="user" color={color} />,
        }}
      />

    <Tabs
      screenOptions={{
        headerTitleAlign: "center", // 安卓标题栏居中
        headerTitle: (props) => <LogoTitle {...props} />,
        headerLeft: () => (
          <HeaderButton name="bell" href="/articles" style={style.headerLeft} />
        ),
        headerRight: () => (
          <>
            <HeaderButton
              name="magnifier"
              href="/search"
              style={style.headerRight}
            />
            <HeaderButton
              name="options"
              href="/settings"
              style={style.headerRight}
            />
          </>
        ),
        tabBarActiveTintColor: "#1f99b0", // 设置 TabBar 选中项的颜色
        tabBarStyle: {
          height: 80, // 设置 TabBar 的高度
        },
        tabBarLabelStyle: {
          marginTop: 4, // 设置 TabBar 文字与图标之间的间距
        },
      }}
    >

完整代码:

import { Link, Tabs } from "expo-router";
import { Image } from "expo-image";
import { SimpleLineIcons } from "@expo/vector-icons";
import { StyleSheet, TouchableOpacity } from "react-native";

/**
 * 导航栏 Logo 组件
 */
function LogoTitle() {
  return (
    <Image
      style={style.logo}
      contentFit="contain"
      source={require("../../assets/logo-light.png")}
    />
  );
}

/**
 * TabBar 图标组件
 * @param props
 */
function TabBarIcon(props) {
  return <SimpleLineIcons size={25} {...props} />;
}

/**
 * 导航栏按钮组件
 * @param props
 */
function HeaderButton({ href, ...rest }) {
  return (
    <Link href={href} asChild>
      <TouchableOpacity>
        <SimpleLineIcons size={20} color="#1f99b0" {...rest} />
      </TouchableOpacity>
    </Link>
  );
}

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        headerTitleAlign: "center", // 安卓标题栏居中
        headerTitle: (props) => <LogoTitle {...props} />,
        headerLeft: () => (
          <HeaderButton name="bell" href="/articles" style={style.headerLeft} />
        ),
        headerRight: () => (
          <>
            <HeaderButton
              name="magnifier"
              href="/search"
              style={style.headerRight}
            />
            <HeaderButton
              name="options"
              href="/settings"
              style={style.headerRight}
            />
          </>
        ),
        tabBarActiveTintColor: "#1f99b0", // 设置 TabBar 选中项的颜色
        tabBarStyle: {
          height: 80, // 设置 TabBar 的高度
        },
        tabBarLabelStyle: {
          marginTop: 4, // 设置 TabBar 文字与图标之间的间距
        },
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: "发现",
          tabBarIcon: ({ color }) => (
            <TabBarIcon name="compass" color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="videos"
        options={{
          title: "视频课程",
          tabBarIcon: ({ color }) => (
            <TabBarIcon name="camrecorder" color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="users"
        options={{
          title: "我的",
          tabBarIcon: ({ color }) => <TabBarIcon name="user" color={color} />,
        }}
      />
    </Tabs>
  );
}

const style = StyleSheet.create({
  logo: {
    width: 130,
    height: 30,
  },
  headerLeft: {
    marginLeft: 15,
  },
  headerRight: {
    marginRight: 15,
  },
});

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


网站公告

今日签到

点亮在社区的每一天
去签到