堆叠工具栏

学习如何在 Expo Router 中使用 Stack 导航的原生工具栏。


For the complete documentation index, see llms.txt. Use this file to discover all available pages.

重要 Stack.Toolbar 是一个在 Expo SDK 56 及更高版本的 Android 和 Expo SDK 55 及更高版本的 iOS 上可用的 alpha API。该 API 可能会有破坏性更改。

Stack.Toolbar 让你可以在 Android 和 iOS 上的 Stack 屏幕中添加原生工具栏项。你可以将按钮、菜单和自定义视图放置在标题栏(左侧或右侧)或底部工具栏中。

添加头部按钮

🌐 Adding header buttons

Stack.Toolbar 中使用 Stack.Toolbar.Button 并配合 placement="right"placement="left" 来向导航头添加按钮。这对于收藏、分享或编辑内容等操作非常有用。

🌐 Use Stack.Toolbar.Button within Stack.Toolbar with placement="right" or placement="left" to add buttons to the navigation header. This is useful for actions like favoriting, sharing, or editing content.

src/app/notes/[id].tsx
import { useState } from 'react'; import { Stack } from 'expo-router'; import { View, Text, Alert } from 'react-native'; export default function NoteScreen() { const [isFavorite, setIsFavorite] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button // Replace with your own icons icon={isFavorite ? require('./assets/star-filled.png') : require('./assets/star.png')} onPress={() => setIsFavorite(!isFavorite)} /> <Stack.Toolbar.Button icon={require('./assets/share.png')} onPress={() => Alert.alert('Share')} /> </Stack.Toolbar> <Stack.Toolbar placement="left"> <Stack.Toolbar.Button icon={require('./assets/sidebar.png')} onPress={() => Alert.alert('Sidebar')} /> </Stack.Toolbar> <View style={{ flex: 1, padding: 16 }}> <Text>Note content...</Text> </View> </> ); }
src/app/notes/[id].tsx
import { useState } from 'react'; import { Stack } from 'expo-router'; import { View, Text, Alert } from 'react-native'; export default function NoteScreen() { const [isFavorite, setIsFavorite] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button icon={isFavorite ? 'star.fill' : 'star'} onPress={() => setIsFavorite(!isFavorite)} /> <Stack.Toolbar.Button icon="square.and.arrow.up" onPress={() => Alert.alert('Share')} /> </Stack.Toolbar> <Stack.Toolbar placement="left"> <Stack.Toolbar.Button icon="sidebar.left" onPress={() => Alert.alert('Sidebar')} /> </Stack.Toolbar> <View style={{ flex: 1, padding: 16 }}> <Text>Note content...</Text> </View> </> ); }

图标

🌐 Icons

工具栏按钮支持 SF 符号(仅 iOS)和自定义图片(Android 和 iOS)。

🌐 Toolbar buttons accept SF Symbols (iOS only) and custom images (Android and iOS).

SF 符号(仅限 iOS)

🌐 SF Symbols (iOS only)

在 iOS 上添加图标最简单的方法是使用 SF Symbols,苹果内置的图标库。直接将符号名称传递给 icon 属性:

🌐 The easiest way to add icons on iOS is to use SF Symbols, Apple's built-in icon library. Pass the symbol name directly to the icon prop:

<Stack.Toolbar.Button icon="star.fill" onPress={() => {}} /> <Stack.Toolbar.Button icon="square.and.arrow.up" onPress={() => {}} /> <Stack.Toolbar.Menu icon="ellipsis.circle">{/* ... */}</Stack.Toolbar.Menu>

你可以在 Apple 的 SF Symbols 应用中浏览可用的符号。

🌐 You can browse available symbols in Apple's SF Symbols app.

信息 SF 符号是仅限 iOS 的功能。

自定义图片

🌐 Custom images

你也可以使用自定义图片。传递它们的 API 因平台而异:

🌐 You can also use custom images. The API for passing them differs by platform:

将图片传递给 icon 属性:

🌐 Pass an image to the icon prop:

import { Stack } from 'expo-router'; export default function Page() { return ( <> <Stack.Toolbar> <Stack.Toolbar.Button icon={require('./assets/expo.png')} onPress={() => {}} /> </Stack.Toolbar> {/* Screen content */} </> ); }

图片来源图标默认会被工具栏的色调颜色染色(iconRenderingMode 默认是 'template')。传入 iconRenderingMode="original" 可以保持源图片的原始颜色,这对于多色图标很有用:

🌐 Image-source icons are tinted with the toolbar's tint color by default (iconRenderingMode defaults to 'template'). Pass iconRenderingMode="original" to keep the source's original colors, useful for multi-color icons:

import { Stack } from 'expo-router'; export default function Page() { return ( <> <Stack.Toolbar> <Stack.Toolbar.Button icon={require('./assets/expo.png')} iconRenderingMode="original" onPress={() => {}} /> </Stack.Toolbar> {/* Screen content */} </> ); }

iOS 根据位置使用两种不同的 API:将图片源直接传递给用于头部工具栏的 icon,并使用带有 image 属性的 useImage 用于底部工具栏。

🌐 iOS uses two different APIs depending on placement: pass an image source directly to icon for header toolbars, and use useImage with the image prop for the bottom toolbar.

信息 在头部位置的子菜单(Stack.Toolbar.Menu)中使用自定义图片需要 react-native-screens 4.24.0 或更高版本。SDK 55 打包了 ~4.23.0,因此需要手动安装 react-native-screens@~4.24.0 才能使用此功能。SDK 56 默认打包了兼容版本。

import { Stack } from 'expo-router'; export default function Page() { return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button icon={require('./assets/expo.png')} onPress={() => {}} /> </Stack.Toolbar> {/* Screen content */} </> ); }

在底部工具栏中,使用来自 expo-imageuseImage Hook,并将结果传递给 image 属性:

🌐 In the bottom toolbar, use the useImage hook from expo-image and pass the result to the image prop:

import { Stack } from 'expo-router'; import { useImage } from 'expo-image'; export default function Page() { const customIcon = useImage('https://simpleicons.org/icons/expo.svg', { maxWidth: 24, maxHeight: 24, }); return ( <> <Stack.Toolbar> <Stack.Toolbar.Button image={customIcon} onPress={() => {}} /> </Stack.Toolbar> {/* Screen content */} </> ); }

信息 底部工具栏自定义图片的 useImageimage 属性模式仅适用于 iOS,并且是一个临时 API,可能会在未来版本中发生变化。

构建操作菜单

🌐 Building action menus

对于具有多个操作的屏幕,请使用 Stack.Toolbar.Menu 将它们分组到下拉菜单中:

🌐 For screens with multiple actions, use Stack.Toolbar.Menu to group them into a dropdown menu:

信息 一些 Stack.Toolbar.MenuStack.Toolbar.MenuAction 属性仅限 iOS 使用。有关每个属性的平台可用性,请参阅 API 参考。

src/app/mail/[id].tsx
import { useState } from 'react'; import { Stack } from 'expo-router'; import { Alert } from 'react-native'; export default function EmailScreen() { const [isArchived, setIsArchived] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Menu icon={require('./assets/menu.png')}> <Stack.Toolbar.MenuAction icon={require('./assets/reply.png')} onPress={() => Alert.alert('Reply')}> Reply </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon={require('./assets/forward.png')} onPress={() => Alert.alert('Forward')}> Forward </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon={isArchived ? require('./assets/unarchive.png') : require('./assets/archive.png')} isOn={isArchived} onPress={() => setIsArchived(!isArchived)}> {isArchived ? 'Unarchive' : 'Archive'} </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon={require('./assets/trash.png')} destructive onPress={() => Alert.alert('Delete')}> Delete </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* Email content */} </> ); }
src/app/mail/[id].tsx
import { useState } from 'react'; import { Stack } from 'expo-router'; import { Alert } from 'react-native'; export default function EmailScreen() { const [isArchived, setIsArchived] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Menu icon="ellipsis.circle"> <Stack.Toolbar.MenuAction icon="arrowshape.turn.up.left" onPress={() => Alert.alert('Reply')}> Reply </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon="arrowshape.turn.up.right" onPress={() => Alert.alert('Forward')}> Forward </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon={isArchived ? 'tray.full' : 'archivebox'} isOn={isArchived} onPress={() => setIsArchived(!isArchived)}> {isArchived ? 'Unarchive' : 'Archive'} </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon="trash" destructive onPress={() => Alert.alert('Delete')}> Delete </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* Email content */} </> ); }

嵌套子菜单

🌐 Nested submenus

对于更复杂的菜单,可以将 Stack.Toolbar.Menu 嵌套在另一个菜单中。使用 inline 属性可以直接显示子菜单项,而不折叠:

🌐 For more complex menus, nest Stack.Toolbar.Menu inside another menu. Use the inline prop to display submenu items directly without collapsing:

import { useState } from 'react'; import { Stack } from 'expo-router'; export default function EmailScreen() { const [sortBy, setSortBy] = useState<'name' | 'date' | 'size'>('name'); const [showHiddenFiles, setShowHiddenFiles] = useState(false); return ( <> <Stack.Toolbar> <Stack.Toolbar.Menu icon={require('./assets/menu.png')}> {/* Inline submenu - options appear directly in the menu */} <Stack.Toolbar.Menu inline title="Sort By"> <Stack.Toolbar.MenuAction isOn={sortBy === 'name'} onPress={() => setSortBy('name')}> Name </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction isOn={sortBy === 'date'} onPress={() => setSortBy('date')}> Date </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction isOn={sortBy === 'size'} onPress={() => setSortBy('size')}> Size </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> {/* Nested submenu - opens as a separate menu */} <Stack.Toolbar.Menu title="Preferences"> <Stack.Toolbar.MenuAction isOn={showHiddenFiles} onPress={() => setShowHiddenFiles(!showHiddenFiles)}> Show Hidden Files </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* Email content */} </> ); }
import { useState } from 'react'; import { Stack } from 'expo-router'; export default function EmailScreen() { const [sortBy, setSortBy] = useState<'name' | 'date' | 'size'>('name'); const [showHiddenFiles, setShowHiddenFiles] = useState(false); return ( <> <Stack.Toolbar> <Stack.Toolbar.Menu icon="ellipsis.circle"> {/* Inline submenu - options appear directly in the menu */} <Stack.Toolbar.Menu inline title="Sort By"> <Stack.Toolbar.MenuAction isOn={sortBy === 'name'} onPress={() => setSortBy('name')}> Name </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction isOn={sortBy === 'date'} onPress={() => setSortBy('date')}> Date </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction isOn={sortBy === 'size'} onPress={() => setSortBy('size')}> Size </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> {/* Nested submenu - opens as a separate menu */} <Stack.Toolbar.Menu title="Preferences"> <Stack.Toolbar.MenuAction isOn={showHiddenFiles} onPress={() => setShowHiddenFiles(!showHiddenFiles)}> Show Hidden Files </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* Email content */} </> ); }

使用底部工具栏

🌐 Using the bottom toolbar

在 iOS 上,底部工具栏通常用于主屏幕操作,例如照片和邮件应用中的工具栏。要添加一个,请使用 Stack.Toolbar 而不使用 placement 属性,它默认是 "bottom"

🌐 Bottom toolbars are commonly used on iOS for primary screen actions, such as the toolbars in the Photos and Mail apps. To add one, use Stack.Toolbar without a placement prop, it defaults to "bottom":

src/app/photos/index.tsx
import { Stack } from 'expo-router'; import { Alert } from 'react-native'; export default function PhotosScreen() { return ( <> <Stack.Toolbar> <Stack.Toolbar.Button icon={require('./assets/select.png')} onPress={() => Alert.alert('Select')} /> <Stack.Toolbar.Spacer width={24} /> <Stack.Toolbar.Button icon={require('./assets/plus.png')} onPress={() => Alert.alert('Add')} /> </Stack.Toolbar> </> ); }
src/app/photos/index.tsx
import { Stack } from 'expo-router'; import { Alert } from 'react-native'; export default function PhotosScreen() { return ( <> <Stack.Toolbar> <Stack.Toolbar.Button icon="photo.on.rectangle" onPress={() => Alert.alert('Select')}> Select </Stack.Toolbar.Button> <Stack.Toolbar.Spacer /> <Stack.Toolbar.Button icon="plus" onPress={() => Alert.alert('Add')}> Add </Stack.Toolbar.Button> </Stack.Toolbar> </> ); }

信息 底部工具栏只能在页面组件内使用,不能在布局文件中使用。

间隔件

🌐 Spacers

使用 Stack.Toolbar.Spacer 在工具栏项目之间添加空格。不同平台上的行为有所不同:

🌐 Use Stack.Toolbar.Spacer to add space between toolbar items. Behavior differs by platform:

  • AndroidStack.Toolbar.Spacer 总是需要一个明确的 width。目前没有灵活填充的间隔器。
  • iOS: 一个没有 widthStack.Toolbar.Spacer 会在项目之间创建可伸缩的空间,将它们推向相对的两侧。这对于工具栏两端都有按钮的布局非常有用。传入 width 可以实现固定大小的间距。

为按钮添加徽章(仅限 iOS)

🌐 Adding badges to buttons (iOS only)

在标题工具栏中,你可以添加徽章以显示计数或状态。使用 Stack.Toolbar.IconStack.Toolbar.LabelStack.Toolbar.Badge 来组合按钮内容:

🌐 In header toolbars, you can add badges to indicate counts or status. Use Stack.Toolbar.Icon, Stack.Toolbar.Label, and Stack.Toolbar.Badge to compose the button content:

src/app/inbox.tsx
import { Stack } from 'expo-router'; export default function InboxScreen() { const unreadCount = 5; return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button onPress={() => {}}> <Stack.Toolbar.Icon sf="bell" /> <Stack.Toolbar.Label>Notifications</Stack.Toolbar.Label> {unreadCount > 0 && <Stack.Toolbar.Badge>{String(unreadCount)}</Stack.Toolbar.Badge>} </Stack.Toolbar.Button> </Stack.Toolbar> {/* Screen content */} </> ); }

信息 徽章仅在 iOS 头部位置(leftright)有效,不适用于底部工具栏或 Android。

嵌入自定义视图

🌐 Embedding custom views

当你需要超越按钮和菜单的功能时,使用 Stack.Toolbar.View 来嵌入任何 React Native 组件:

🌐 When you need something beyond buttons and menus, use Stack.Toolbar.View to embed any React Native component:

src/app/search.tsx
import { Stack } from 'expo-router'; import { Pressable, Alert } from 'react-native'; import { SymbolView } from 'expo-symbols'; export default function SearchScreen() { return ( <> <Stack.Toolbar> <Stack.Toolbar.View> <Pressable style={{ width: 32, height: 32, justifyContent: 'center', alignItems: 'center' }} onPress={() => { Alert.alert('Filter pressed'); }}> <SymbolView name={{ ios: 'line.3.horizontal.decrease.circle', android: 'filter_list', }} size={24} /> </Pressable> </Stack.Toolbar.View> </Stack.Toolbar> {/* Screen content */} </> ); }

动态显示和隐藏项目

🌐 Showing and hiding items dynamically

使用 hidden 属性根据状态切换工具栏项:

🌐 Use the hidden prop to toggle toolbar items based on state:

src/app/document.tsx
import { useState } from 'react'; import { Stack } from 'expo-router'; export default function DocumentScreen() { const [isEditing, setIsEditing] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button hidden={isEditing} icon={require('./assets/pencil.png')} onPress={() => setIsEditing(true)} /> <Stack.Toolbar.Button hidden={!isEditing} icon={require('./assets/check.png')} onPress={() => setIsEditing(false)} /> </Stack.Toolbar> {/* Document content */} </> ); }
src/app/document.tsx
import { useState } from 'react'; import { Stack } from 'expo-router'; export default function DocumentScreen() { const [isEditing, setIsEditing] = useState(false); return ( <> <Stack.Toolbar placement="right"> <Stack.Toolbar.Button hidden={isEditing} icon="pencil" onPress={() => setIsEditing(true)} /> <Stack.Toolbar.Button hidden={!isEditing} onPress={() => setIsEditing(false)}> Done </Stack.Toolbar.Button> </Stack.Toolbar> {/* Document content */} </> ); }

常见问题

🌐 Common problems

在 iOS 26 的夜间模式下,液体玻璃工具栏按钮会闪烁

在 iOS 26 的夜间模式下,当在屏幕之间切换时,带有液态玻璃样式的工具栏按钮的背景可能会闪烁或闪现。这是因为 React Navigation 的默认主题与系统夜间模式不匹配,导致液态玻璃渲染出现视觉瑕疵。

🌐 Toolbar buttons with liquid glass styling may flicker or flash their background when navigating between screens in dark mode on iOS 26. This happens because React Navigation's default theme doesn't match the system dark mode, causing visual artifacts in the liquid glass rendering.

要解决此问题,请使用适当的主题,将你的根布局用 @react-navigation/native<ThemeProvider> 封装起来:

🌐 To fix this, wrap your root layout with <ThemeProvider> from @react-navigation/native using the appropriate theme:

src/app/_layout.tsx
import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; export default function RootLayout() { const colorScheme = useColorScheme(); return ( <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> <Stack /> </ThemeProvider> ); }
在屏幕之间切换时背景会闪白

屏幕切换之间出现白色闪光通常意味着导航栈使用的是浅色背景,而你的应用使用的是深色主题。当屏幕包含工具栏项目时,这种闪光尤为明显,因为它与工具栏的样式形成了对比。

🌐 A white flash between screen transitions usually means the navigation stack is using a light background while your app uses a dark theme. This is especially noticeable when screens contain toolbar items, as the flash contrasts with the toolbar styling.

要解决此问题,请使用 React Navigation 的 <ThemeProvider> 封装你的根布局,并传入适当的主题:

🌐 To fix this, wrap your root layout with React Navigation's <ThemeProvider> and pass the appropriate theme:

src/app/_layout.tsx
import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; export default function RootLayout() { const colorScheme = useColorScheme(); return ( <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> <Stack /> </ThemeProvider> ); }
大标题在滚动时不会收缩

headerLargeTitle: true(或 <Stack.Title large>)与 Stack.Toolbar 一起使用时,大标题在滚动时可能不会折叠。这种情况发生在可滚动视图不是屏幕组件的直接第一个子组件时。

🌐 When using headerLargeTitle: true (or <Stack.Title large>) alongside Stack.Toolbar, the large title may not collapse on scroll. This happens when the scrollable view is not the direct first child of the screen component.

为了解决此问题,请确保 ScrollViewFlatList 是你的屏幕组件渲染的第一个子元素。如果需要一个封装器,请在其上设置 collapsable={false}

🌐 To fix this, ensure ScrollView or FlatList is the first child rendered by your screen component. If you need a wrapper, set collapsable={false} on it:

src/app/index.tsx
import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; export default function Home() { return ( <ScrollView> <Stack.Title large>Home</Stack.Title> <Text>Content here</Text> </ScrollView> ); }

如果你需要封装 ScrollView,请在封装器上设置 collapsable={false}

🌐 If you need to wrap the ScrollView, set collapsable={false} on the wrapper:

src/app/index.tsx
import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; export default function Home() { return ( <View collapsable={false}> <ScrollView> <Stack.Title large>Home</Stack.Title> <Text>Content here</Text> </ScrollView> </View> ); }

已知的限制

🌐 Known limitations

仅限本地人

Stack.Toolbar 仅在 Android 和 iOS 上呈现。Web 没有标准工具栏,因此如果你在 Web 上需要工具栏功能,你需要自己实现。

自定义图标是 Android 上唯一支持的图标类型

在安卓上,icon 必须是一个 ImageSourcePropType。例如 require('./icon.png'){ uri: '...' }

🌐 On Android, icon must be an ImageSourcePropType. For example require('./icon.png') or { uri: '...' }.

你也可以使用 Stack.Toolbar.Iconsrc 属性来提供跨平台图标。

🌐 You can also use Stack.Toolbar.Icon with the src prop to provide cross-platform icons.

Spacer 在 Android 上需要明确的宽度

灵活的间隔器(<Stack.Toolbar.Spacer /> 没有 width)仅适用于 iOS。在 Android 上,没有 widthStack.Toolbar.Spacer 不会渲染任何内容——在每个位置都传递一个固定的 width,例如 <Stack.Toolbar.Spacer width={24} />

🌐 Flexible spacers (<Stack.Toolbar.Spacer /> with no width) are iOS-only. On Android, a Stack.Toolbar.Spacer without a width renders nothing — pass a fixed width such as <Stack.Toolbar.Spacer width={24} /> in every placement.

仅在页面组件中显示底部工具栏

底部工具栏只能在页面组件中使用,不能在布局文件中使用。这是因为底部工具栏需要与特定屏幕的内容关联。

🌐 The bottom toolbar can only be used inside page components, not in layout files. This is because the bottom toolbar needs to be associated with a specific screen's content.

无法嵌套工具栏

你不能将 Stack.Toolbar 组件嵌套在彼此内部。

🌐 You cannot nest Stack.Toolbar components inside each other.

仅在页眉位置显示徽章

Stack.Toolbar.Badge 仅在使用 placement="left"placement="right" 时受支持。徽章不会显示在底部工具栏中。

Android 不支持徽章和标签原语

在 Android 上,Stack.Toolbar.Button 只渲染它的图标 — Stack.Toolbar.BadgeStack.Toolbar.Label 子组件会被忽略。如果你在 Android 上需要徽章样式的界面,可以使用 Stack.Toolbar.View 嵌入自定义组件。

🌐 On Android, Stack.Toolbar.Button renders only its icon — the Stack.Toolbar.Badge and Stack.Toolbar.Label children are dropped. If you need badge-like UI on Android, embed a custom component using Stack.Toolbar.View.

SearchBarSlot 在 Android 上不受支持

Stack.Toolbar.SearchBarSlot 在安卓上无任何显示。使用 Stack.SearchBar 以支持跨平台搜索栏。

了解更多

🌐 Learn more

有关完整的 API 文档,包括所有可用属性,请参阅 Stack.Toolbar API 参考

🌐 For complete API documentation, including all available props, see the Stack.Toolbar API reference.