布局文件-底部tabBar
内容配置
export default function Layout() {
return (
<Tabs />
);
}
默认会将布局文件是将与它在同一个目录的所有文件,包括下级目录的文件,全都配置成Tab
了。:
这样做显然不对,正确的做法是
- 在
app
目录里新建一个(tabs)
文件夹,注意了,名字上有一对小括号。 - 里面新建一个
_layout.js
布局文件,这里就专门放TabBar
配置。 - 然后将
index.js
,挪动到(tabs)
里面。 - 在
(tabs)
里,再新建一个videos.js
和users.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.js
的URL
,依然还是/index
,就像还在app
目录里一样,它依然还是首页
。videos.js
文件的URL
,其实是/videos
,而不是/(tabs)/videos
,同理。
底下的和刚才不同,刚才的布局文件里使用的是
Stack
、Stack.Screen
。这里要用Tabs
和Tabs.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,
},
});