原生标签

编辑

学习如何在 Expo Router 中使用原生标签布局。


使用 Expo Router 的液态玻璃标签
使用 Expo Router 的液态玻璃标签

学习如何在 iOS 上使用原生标签创建液态玻璃标签,使用 Expo Router。

原生标签是 SDK 54 及更高版本中的实验性特性,其 API 可能会改变。

标签是一种常见的方式,用于在应用的不同部分之间导航。在 Expo Router 中,您可以根据需要使用不同的标签布局。本指南涵盖原生标签。与 其他标签布局 不同,原生标签使用原生系统标签栏。

有关其他标签布局,请参见:

自定义标签

如果您的应用需要无法使用系统标签实现的完全自定义设计,请参见自定义标签。

JavaScript 标签

如果您已经使用 React Navigation 的标签,请参见 JavaScript 标签。

开始使用

您可以使用基于文件的路由创建标签布局。以下是一个示例文件结构:

app
_layout.tsx
index.tsx
settings.tsx

上述文件结构在屏幕底部生成一个带标签栏的布局。标签栏将有两个标签:首页设置

您可以使用 app/_layout.tsx 文件定义应用的根布局,使用标签。这是标签栏和每个标签的主要布局文件。在其中,您可以控制标签栏和每个标签项的外观和行为。

app/_layout.tsx
import { NativeTabs, Icon, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <Label>首页</Label> <Icon sf="house.fill" drawable="custom_android_drawable" /> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Icon sf="gear" drawable="custom_settings_drawable" /> <Label>设置</Label> </NativeTabs.Trigger> </NativeTabs> ); }

最后,您有两个标签文件,构成标签的内容:app/index.tsxapp/settings.tsx

app/index.tsx and app/settings.tsx
import { View, Text, StyleSheet } from 'react-native'; export default function Tab() { return ( <View style={styles.container}> <Text>Tab [Home|Settings]</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });

名为 index.tsx 的标签文件是在应用加载时的默认标签。第二个标签文件 settings.tsx 显示如何将更多标签添加到标签栏。

与栈导航器相比,标签不会自动添加到标签栏。您需要在布局文件中使用 NativeTabs.Trigger 显式添加它们。

自定义标签栏项

当您想要自定义标签栏项时,我们建议使用为此目的设计的组件 API。目前,您可以自定义:

  • 图标:在标签栏项中显示的图标。
  • 标签:在标签栏项中显示的标签。
  • 徽章:在标签栏项中显示的徽章。

图标

您可以使用 Icon 组件自定义在标签栏项中显示的图标。Icon 组件接受用于 Android drawable 的 drawable 属性,用于 Apple 的 SF Symbols 图标的 sf 属性,或用于自定义图像的 src 属性。

另外,您可以将 {default: ..., selected: ...} 传递给 sfsrc 属性,以指定默认和选定状态的不同图标。

要在 Android 上使用 drawable 属性,您可以使用 内置 drawable 或添加 自定义 drawable

app/_layout.tsx
import { NativeTabs, Icon } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <Icon sf={{ default: 'house', selected: 'house.fill' }} drawable="custom_home_drawable" /> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Icon src={require('../../../assets/setting_icon.png')} /> </NativeTabs.Trigger> </NativeTabs> ); }

iOS 上的液态玻璃会自动根据背景色的亮度变化颜色。没有此类回调,因此您需要使用 PlatformColor 来设置图标的颜色。

app/_layout.tsx
import { DynamicColorIOS } from 'react-native'; import { NativeTabs, Icon } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs labelStyle={{ // 文本颜色 color: DynamicColorIOS({ dark: 'white', light: 'black', }), }} // 选定图标颜色 tintColor={DynamicColorIOS({ dark: 'white', light: 'black', })}> <NativeTabs.Trigger name="index"> <Icon sf={{ default: 'house', selected: 'house.fill' }} drawable="custom_home_drawable" /> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Icon src={{ default: require('../../../assets/setting_icon.png'), selected: require('../../../assets/selected_setting_icon.png'), }} /> </NativeTabs.Trigger> </NativeTabs> ); }

标签

您可以使用 Label 组件自定义在标签栏项中显示的标签。Label 组件接受作为子项传递的字符串标签。如果未提供标签,标签栏项将使用路由名称作为标签。

如果您不想显示标签,可以使用 hidden 属性来隐藏标签。

app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <Label>Home</Label> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Label hidden /> </NativeTabs.Trigger> </NativeTabs> ); }

徽章

您可以使用 Badge 组件自定义标签栏项的徽章。徽章是标签上的附加标记,用于显示通知或未读消息计数。

app/_layout.tsx
import { NativeTabs, Badge } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="messages"> <Badge>9+</Badge> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Badge /> </NativeTabs.Trigger> </NativeTabs> ); }

自定义标签栏

由于原生标签布局的外观因平台而异,定制选项也不同。有关所有自定义选项,请参见 NativeTabs 的 API 参考

高级

有条件地隐藏标签

如果您想根据条件隐藏标签,可以删除触发器或将 hidden 属性传递给 NativeTabs.Trigger 组件。

app/_layout.tsx
import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { const shouldHideMessagesTab = true; // 用您的条件替换 return ( <NativeTabs> <NativeTabs.Trigger name="messages" hidden={shouldHideMessagesTab} /> </NativeTabs> ); }
注意:将标签标记为 hidden 意味着无法以任何方式导航到它。

消失行为

目前这是仅适用于 iOS 的特性,但我们计划在将来将其添加到 Android。

默认情况下,点击一个已经活跃的标签会关闭该标签的所有屏幕并返回到根屏幕。您可以通过在 NativeTabs.Trigger 组件上设置 disablePopToTop 属性来禁用此功能。

app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index" disablePopToTop> <Label>Home</Label> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Label>Settings</Label> </NativeTabs.Trigger> </NativeTabs> ); }

滚动到顶部

目前这是仅适用于 iOS 的特性,但我们计划在将来将其添加到 Android。

默认情况下,点击一个已经活跃的标签并显示其根屏幕会将内容向上滚动回到顶部。您可以通过在 NativeTabs.Trigger 组件上设置 disableScrollToTop 属性来禁用此功能。

app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index" disableScrollToTop> <Label>Home</Label> </NativeTabs.Trigger> <NativeTabs.Trigger name="settings"> <Label>Settings</Label> </NativeTabs.Trigger> </NativeTabs> ); }

iOS 26 特性

要使用此部分中描述的特性,请使用 Xcode 26 或更高版本编译您的应用。

单独的搜索标签

要添加单独的搜索标签,将 role 的值设置为 search 的原生标签分配给要单独显示的标签。

app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <Label>Home</Label> </NativeTabs.Trigger> <NativeTabs.Trigger name="search" role="search"> <Label>Search</Label> </NativeTabs.Trigger> </NativeTabs> ); }

标签栏搜索输入

要在标签栏中添加搜索字段,将屏幕包装在栈导航器中并配置 headerSearchBarOptions

app
_layout.tsx
index.tsx
search
  _layout.tsx
  index.tsx
app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs> <NativeTabs.Trigger name="index"> <Label>Home</Label> </NativeTabs.Trigger> <NativeTabs.Trigger name="search" role="search"> <Label>Search</Label> </NativeTabs.Trigger> </NativeTabs> ); }
app/search/_layout.tsx
import { Stack } from 'expo-router'; export default function SearchLayout() { return ( <Stack> <Stack.Screen name="index" options={{ title: 'Search', headerSearchBarOptions: { placement: 'automatic', placeholder: 'Search', onChangeText: () => {}, }, }} /> </Stack> ); }
app/search/index.tsx
import { ScrollView } from 'react-native'; export default function SearchIndex() { return <ScrollView>{/* 屏幕内容 */}</ScrollView>; }

标签栏最小化行为

要实现标签栏的最小化行为,您可以使用 minimizeBehavior 属性在 NativeTabs 上。在下面的示例中,当向下滚动时,标签栏被最小化。

app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs minimizeBehavior="onScrollDown"> <NativeTabs.Trigger name="index"> <Label>Home</Label> </NativeTabs.Trigger> <NativeTabs.Trigger name="tab-1"> <Label>Tab 1</Label> </NativeTabs.Trigger> </NativeTabs> ); }

@expo/vector-icons 的集成

推荐:使用 iOS 上的 SF Symbols。与矢量图标相比,它们提供了更原生的平台感觉。

要使用 @expo/vector-icons 中的图标,您可以使用 VectorIcon 组件。

app/_layout.tsx
import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { NativeTabs, Icon, VectorIcon } from 'expo-router/unstable-native-tabs'; import { Platform } from 'react-native'; export default function TabLayout() { return ( <NativeTabs minimizeBehavior="onScrollDown"> <NativeTabs.Trigger name="index"> <Label>Home</Label> {Platform.select({ ios: <Icon sf="house.fill" />, android: <Icon src={<VectorIcon family={MaterialIcons} name="home" />} />, })} </NativeTabs.Trigger> </NativeTabs> ); }

从 JavaScript 标签迁移

原生标签不是为 JavaScript 标签 设计的直接替代品。原生标签受限于原生平台行为,而 JavaScript 标签可以更自由地定制。如果您对原生平台行为不感兴趣,您可以继续使用 JavaScript 标签。

使用 Trigger 替代 Screen

NativeTabs 引入了 Trigger 的概念,用于向布局添加路由。与自动添加样式的 Screen 不同,Trigger 系统为隐藏和移除标签提供了更好的控制。

使用 React 组件而不是属性

NativeTabs 采用了以 React 为先的 API,选择使用组件定义 UI,而不是属性对象。

- options={{ - tabBarIcon: ({ focused, color, size }) => ( - <Icon name="home" color={color} size={size} /> - ), - }} + <Icon sf="house" drawable="home_drawable" />

在标签内使用栈

JavaScript <Tabs /> 有一个伪栈头部,这在原生标签中不存在。相反,您应该在原生标签内部嵌套一个原生 <Stack /> 布局,以支持头部和推送屏幕。

已知限制

Android 上的标签限制为 5

在 Android 上,标签栏中最多只能有 5 个标签。这个限制来自该平台的 Material Tabs 组件。

不能测量标签栏的高度

标签会移动,有时在 iPad 上渲染时位于屏幕顶部,有时在 Apple Vision Pro 上运行时位于屏幕一侧,等等。我们正在努力提供更详细的布局信息的布局函数。

不支持嵌套原生标签

原生标签不能嵌套在其他原生标签中。您仍然可以在原生标签内部嵌套 JavaScript 标签

对 FlatList 的支持有限

FlatList 与原生标签的集成存在限制。像滚动到顶部和滚动时最小化这样的功能不受支持。此外,检测滚动边缘可能失败,导致标签栏透明。为了解决这个问题,使用 disableTransparentOnScrollEdge 属性。

app/_layout.tsx
import { NativeTabs, Label } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { return ( <NativeTabs disableTransparentOnScrollEdge> <NativeTabs.Trigger name="index"> <Label>Home</Label> </NativeTabs.Trigger> </NativeTabs> ); }