import{DynamicColorIOS}from'react-native';import{NativeTabs}from'expo-router/native-tabs';exportdefaultfunctionTabLayout(){return(<NativeTabslabelStyle={{// For the text color
color:DynamicColorIOS({
dark:'white',
light:'black',}),}}// For the selected icon colortintColor={DynamicColorIOS({
dark:'white',
light:'black',})}><NativeTabs.Triggername="index"><NativeTabs.Trigger.Iconsf={{default:'house', selected:'house.fill'}}md="home"/></NativeTabs.Trigger><NativeTabs.Triggername="settings"><NativeTabs.Trigger.Iconsrc={{default:require('../assets/setting_icon.png'),
selected:require('../assets/selected_setting_icon.png'),}}/></NativeTabs.Trigger></NativeTabs>);}
src/app/_layout.tsx
import{DynamicColorIOS}from'react-native';import{NativeTabs}from'expo-router/unstable-native-tabs';exportdefaultfunctionTabLayout(){return(<NativeTabslabelStyle={{// For the text color
color:DynamicColorIOS({
dark:'white',
light:'black',}),}}// For the selected icon colortintColor={DynamicColorIOS({
dark:'white',
light:'black',})}><NativeTabs.Triggername="index"><NativeTabs.Trigger.Iconsf={{default:'house', selected:'house.fill'}}md="home"/></NativeTabs.Trigger><NativeTabs.Triggername="settings"><NativeTabs.Trigger.Iconsrc={{default:require('../assets/setting_icon.png'),
selected:require('../assets/selected_setting_icon.png'),}}/></NativeTabs.Trigger></NativeTabs>);}
app/_layout.tsx
import{DynamicColorIOS}from'react-native';import{NativeTabs,Icon}from'expo-router/unstable-native-tabs';exportdefaultfunctionTabLayout(){return(<NativeTabslabelStyle={{// For the text color
color:DynamicColorIOS({
dark:'white',
light:'black',}),}}// For the selected icon colortintColor={DynamicColorIOS({
dark:'white',
light:'black',})}><NativeTabs.Triggername="index"><Iconsf={{default:'house', selected:'house.fill'}}drawable="custom_home_drawable"/></NativeTabs.Trigger><NativeTabs.Triggername="settings"><Iconsrc={{default:require('../assets/setting_icon.png'),
selected:require('../assets/selected_setting_icon.png'),}}/></NativeTabs.Trigger></NativeTabs>);}
import{NativeTabs}from'expo-router/native-tabs';exportdefaultfunctionTabLayout(){return(<NativeTabs>{/* Icon with original colors preserved (e.g., for gradient or multi-color icons) */}<NativeTabs.Triggername="colorful"><NativeTabs.Trigger.Iconsrc={require('../../../assets/colorful_icon.png')}renderingMode="original"/></NativeTabs.Trigger>{/* Icon rendered as a template (default behavior) */}<NativeTabs.Triggername="simple"><NativeTabs.Trigger.Iconsrc={require('../../../assets/simple_icon.png')}renderingMode="template"/></NativeTabs.Trigger></NativeTabs>);}
src/app/_layout.tsx
import{NativeTabs}from'expo-router/unstable-native-tabs';exportdefaultfunctionTabLayout(){return(<NativeTabs>{/* Icon with original colors preserved (e.g., for gradient or multi-color icons) */}<NativeTabs.Triggername="colorful"><NativeTabs.Trigger.Iconsrc={require('../../../assets/colorful_icon.png')}renderingMode="original"/></NativeTabs.Trigger>{/* Icon rendered as a template (default behavior) */}<NativeTabs.Triggername="simple"><NativeTabs.Trigger.Iconsrc={require('../../../assets/simple_icon.png')}renderingMode="template"/></NativeTabs.Trigger></NativeTabs>);}
app/_layout.tsx
import{NativeTabs,Icon}from'expo-router/unstable-native-tabs';exportdefaultfunctionTabLayout(){return(<NativeTabs>{/* Icon with original colors preserved (e.g., for gradient or multi-color icons) */}<NativeTabs.Triggername="colorful"><Iconsrc={require('../../../assets/colorful_icon.png')}renderingMode="original"/></NativeTabs.Trigger>{/* Icon rendered as a template (default behavior) */}<NativeTabs.Triggername="simple"><Iconsrc={require('../../../assets/simple_icon.png')}renderingMode="template"/></NativeTabs.Trigger></NativeTabs>);}
getImageSourceSync 是同步方法,因此应在模块作用域中计算一次图像源,而不是在每次渲染时计算。将 src 与 sf 结合使用,即可在 Android 上使用矢量图标、在 iOS 上使用 SF Symbols。在 iOS 上,sf 的优先级高于 src;在 Android 上,图标会回退到 src。
src/app/_layout.tsx
importMaterialDesignIconsfrom'@react-native-vector-icons/material-design-icons';import{NativeTabs}from'expo-router/native-tabs';const homeIcon =MaterialDesignIcons.getImageSourceSync('home',24,'black');const starOutlineIcon =MaterialDesignIcons.getImageSourceSync('star-outline',24,'black');const starIcon =MaterialDesignIcons.getImageSourceSync('star',24,'black');exportdefaultfunctionTabLayout(){return(<NativeTabs><NativeTabs.Triggername="index">{/* `sf` is used on iOS, `src` (the vector icon) on Android. */}<NativeTabs.Trigger.Iconsf="house"src={homeIcon}/></NativeTabs.Trigger><NativeTabs.Triggername="explore">{/* Outlined when unselected, filled when selected. */}<NativeTabs.Trigger.Iconsf={{default:'star', selected:'star.fill'}}src={{default: starOutlineIcon, selected: starIcon }}/></NativeTabs.Trigger></NativeTabs>);}
import{NativeTabs}from'expo-router/native-tabs';exportdefaultfunctionTabLayout(){const shouldHideMessagesTab =true;// Replace with your conditionreturn(<NativeTabs><NativeTabs.Triggername="messages"hidden={shouldHideMessagesTab}/></NativeTabs>);}
import{ useIsFocused }from'expo-router';import{View,ActivityIndicator,Text}from'react-native';exportdefaultfunctionSearchScreen(){const isFocused =useIsFocused();if(!isFocused){return(<Viewstyle={{ flex:1, alignItems:'center', justifyContent:'center'}}><ActivityIndicator/></View>);}return(<Viewstyle={{ flex:1}}><Text>Expensive content that only renders when this tab is focused</Text></View>);}
首次聚焦时加载一次
使用带有状态标志的 useFocusEffect,在标签页首次聚焦时加载内容,然后保持其挂载状态。
app/(tabs)/search.tsx
import{ useFocusEffect }from'expo-router';import{ useCallback, useState }from'react';import{View,ActivityIndicator,Text}from'react-native';exportdefaultfunctionSearchScreen(){const[hasActivated, setHasActivated]=useState(false);useFocusEffect(useCallback(()=>{setHasActivated(true);},[]));if(!hasActivated){return(<Viewstyle={{ flex:1, alignItems:'center', justifyContent:'center'}}><ActivityIndicator/></View>);}return(<Viewstyle={{ flex:1}}><Text>Content that loads once and stays mounted</Text></View>);}
自定义 Web 布局
原生标签页会在 Android 和 iOS 上渲染平台特定的标签栏,但 Web 上没有标准的系统标签栏。在 Web 上,原生标签页会回退到基于 iPad 设计的基本实现。你可以使用 expo-router/ui 中的无头标签页提供自定义 Web 布局,同时在移动设备上保留原生标签页。有两种设置方式。
平台特定的布局文件
在 _layout.tsx 旁边使用 _layout.web.tsx 文件。Web 文件会完全替代 Web 上的布局,因此每个平台都可以使用完全不同的布局。
import{NativeTabs}from'expo-router/native-tabs';exportdefaultfunctionTabLayout(){return(// `iconColor` applies to both states, so both icons render as templates<NativeTabsiconColor={{default:'gray', selected:'black'}}><NativeTabs.Triggername="settings"><NativeTabs.Trigger.Iconsrc={{default:require('../assets/setting_icon.png'),
selected:require('../assets/selected_setting_icon.png'),}}/></NativeTabs.Trigger></NativeTabs>);}
此限制不适用于 SF Symbols,因为系统始终会为其添加色调。
Android 上最多只能包含 5 个标签页
在 Android 上,标签栏最多只能包含 5 个标签页。此限制来自平台的 Material Tabs 组件。
无法测量标签栏高度
标签页会移动位置,例如在 iPad 上渲染时可能位于屏幕顶部,在 Apple Vision Pro 上运行时可能位于屏幕侧面,等等。我们正在开发布局函数,以便未来提供更详细的布局信息。