堆栈工具栏

编辑页面

了解如何在 Stack 导航中使用 Expo Router 的 iOS 工具栏。


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

重要 Stack.Toolbar 是一个 alpha API,仅在 Expo SDK 55 及更高版本中可用于 iOS。该 API 可能会发生破坏性变更。

Stack.Toolbar 允许你为 Stack 屏幕添加原生 iOS 工具栏项。你可以在标题栏(左侧或右侧)或底部工具栏区域放置按钮、菜单和自定义视图。

添加标题栏按钮

Stack.Toolbar 中使用 Stack.Toolbar.Button,并设置 placement="right"placement="left",即可向导航标题栏添加按钮。这对于收藏、分享或编辑内容等操作非常有用。

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('分享')} /> </Stack.Toolbar> <Stack.Toolbar placement="left"> <Stack.Toolbar.Button icon="sidebar.left" onPress={() => Alert.alert('侧边栏')} /> </Stack.Toolbar> <View style={{ flex: 1, padding: 16 }}> <Text>笔记内容...</Text> </View> </> ); }

图标

工具栏按钮支持两种图标类型:SF Symbols 和自定义图片。

SF Symbols

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

<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 应用中浏览可用符号。

自定义图片

你也可以使用自定义图片。在标题栏工具栏(placement="left"placement="right")中,将图片源直接传递给 icon 属性。

信息 在标题栏位置的子菜单(Stack.Toolbar.Menu)中使用自定义图片需要 react-native-screens 4.24.0 或更高版本。SDK 55 捆绑的是 ~4.23.0,因此你需要手动安装 react-native-screens@~4.24.0 才能使用此功能。

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> {/* 屏幕内容 */} </> ); }

在底部工具栏中,使用 expo-image 中的 useImage hook,并将结果传递给 image 属性:

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> {/* 屏幕内容 */} </> ); }

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

构建操作菜单

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

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 </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon="arrowshape.turn.up.right" onPress={() => Alert.alert('转发')}> Forward </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon={isArchived ? 'tray.full' : 'archivebox'} isOn={isArchived} onPress={() => setIsArchived(!isArchived)}> {isArchived ? '取消归档' : '归档'} </Stack.Toolbar.MenuAction> <Stack.Toolbar.MenuAction icon="trash" destructive onPress={() => Alert.alert('删除')}> Delete </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* 邮件内容 */} </> ); }

Stack.Toolbar.MenuAction 上的 isOn 属性会在操作旁显示一个对勾,这对于切换状态非常有用。destructive 属性会将该操作样式设为红色,以表示危险操作。

嵌套子菜单

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

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"> {/* 内联子菜单 - 选项会直接显示在菜单中 */} <Stack.Toolbar.Menu inline title="排序方式"> <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> {/* 嵌套子菜单 - 作为单独菜单打开 */} <Stack.Toolbar.Menu title="偏好设置"> <Stack.Toolbar.MenuAction isOn={showHiddenFiles} onPress={() => setShowHiddenFiles(!showHiddenFiles)}> 显示隐藏文件 </Stack.Toolbar.MenuAction> </Stack.Toolbar.Menu> </Stack.Toolbar.Menu> </Stack.Toolbar> {/* 邮件内容 */} </> ); }

使用底部工具栏

iOS 应用通常会在底部工具栏中放置主要操作。要添加底部工具栏,可以使用不带 placement 属性的 Stack.Toolbar(它默认是 "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="photo.on.rectangle" onPress={() => Alert.alert('选择')}> 选择 </Stack.Toolbar.Button> <Stack.Toolbar.Spacer /> <Stack.Toolbar.Button icon="plus" onPress={() => Alert.alert('添加')}> 添加 </Stack.Toolbar.Button> </Stack.Toolbar> </> ); }

Stack.Toolbar.Spacer 会在项目之间创建弹性空间,将它们推向相反两侧。这就是实现工具栏两端各放一个按钮这类布局的方法。

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

为按钮添加徽标

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

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>通知</Stack.Toolbar.Label> {unreadCount > 0 && <Stack.Toolbar.Badge>{String(unreadCount)}</Stack.Toolbar.Badge>} </Stack.Toolbar.Button> </Stack.Toolbar> {/* 屏幕内容 */} </> ); }

信息 徽标仅适用于标题栏位置(leftright),不适用于底部工具栏。

嵌入自定义视图

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

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('筛选按钮已按下'); }}> <SymbolView name="line.3.horizontal.decrease.circle" size={24} /> </Pressable> </Stack.Toolbar.View> </Stack.Toolbar> {/* 屏幕内容 */} </> ); }

动态显示和隐藏项目

使用 hidden 属性可以根据状态切换工具栏项目的显示与隐藏:

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)}> 完成 </Stack.Toolbar.Button> </Stack.Toolbar> {/* 文档内容 */} </> ); }

常见问题

iOS 26 深色模式下液态玻璃工具栏按钮闪烁

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

要修复此问题,请使用来自 @react-navigation/native<ThemeProvider> 包裹根布局,并使用合适的主题:

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> ); }
在屏幕之间导航时白色背景闪烁

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

要修复此问题,请使用 React Navigation 的 <ThemeProvider> 包裹根布局,并传入合适的主题:

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.Screen.Title large>)与 Stack.Toolbar 一起使用时,大标题可能不会在滚动时折叠。这通常发生在可滚动视图不是屏幕组件的直接第一个子元素时。

要修复此问题,请确保 ScrollViewFlatList 是由你的屏幕组件渲染的第一个子元素。如果需要包裹层,请在其上设置 collapsable={false}

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

如果你需要包裹 ScrollView,请在包裹层上设置 collapsable={false}

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.Screen.Title large>Home</Stack.Screen.Title> <Text>Content here</Text> </ScrollView> </View> ); }

已知限制

仅限 iOS

Stack.Toolbar 仅在 iOS 上可用。在 Android 和 web 上,该组件不会渲染。

底部工具栏仅可用于页面组件

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

不能嵌套工具栏

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

徽标仅适用于标题位置

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

了解更多

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