模态框

编辑页面

了解如何在 Expo Router 中使用模态框。


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

使用 Expo Router 的模态窗口
使用 Expo Router 的模态窗口

了解如何在应用的其余部分之上显示内容的不同方式。

模态窗口是移动应用中常见的用户界面模式。它们用于在现有屏幕之上展示内容,并可用于不同目的,例如显示确认提示或独立表单。你可以使用以下方法在应用中创建模态窗口:

  • 使用 React Native 的 Modal 组件。
  • 使用 Expo Router 特殊的基于文件的语法,在应用的导航系统中创建一个模态屏幕。

每种方式都有其特定的使用场景。了解何时使用哪种方法,对于创造良好的用户体验非常重要。

React Native 的 Modal 组件

Modal 组件是 React Native 核心 API 的一部分。常见使用场景包括:

  • 独立交互,例如不需要成为导航系统一部分的自包含任务。
  • 临时提示或确认对话框,适合快速交互。

下面是一个自定义 Modal 组件的示例,它会在不同平台上覆盖当前屏幕:

对于大多数使用场景,你都可以使用 Modal 组件,并根据应用的用户界面需求进行自定义。有关如何使用 Modal 组件及其 props 的详细信息,请参阅 React Native 文档

使用 Expo Router 的模态屏幕

模态屏幕是在 src/app 目录中创建的文件,并作为现有堆栈中的一个路由使用。它适用于需要成为导航系统一部分的复杂交互,例如多步骤表单,在流程完成后你可以链接到特定屏幕。

下面是模态屏幕在不同平台上如何工作的示例:

用法

要实现模态路由,请在 src/app 目录中创建一个名为 modal.tsx 的屏幕。下面是一个示例文件结构:

src
app
  _layout.tsx
  index.tsx
  modal.tsx

上述文件结构会生成一种布局,其中 index 是堆栈中的第一个路由。在根布局文件(src/app/_layout.tsx)中,你可以将 modal 路由添加到堆栈中。要将其呈现为模态窗口,请将该路由的 presentation 选项设置为 modal

src/app/_layout.tsx
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'modal', }} /> </Stack> ); }

你可以使用 Link 组件从 index.tsx 文件导航到模态屏幕。

src/app/index.tsx
import { Link } from 'expo-router'; import { StyleSheet, Text, View } from 'react-native'; export default function Home() { return ( <View style={styles.container}> <Text>首页屏幕</Text> <Link href="/modal" style={styles.link}> 打开模态框 </Link> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, link: { paddingTop: 20, fontSize: 20, }, });

modal.tsx 显示模态框的内容。

src/app/modal.tsx
import { StyleSheet, Text, View } from 'react-native'; export default function Modal() { return ( <View style={styles.container}> <Text>模态屏幕</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });

模态框的呈现与关闭行为

当模态框作为导航器中的当前屏幕并以独立屏幕的形式呈现时,它会失去之前的上下文。其呈现和关闭行为在不同平台上有所不同:

  • 在 Android 上,模态框会从当前屏幕上方滑入。要关闭它,请使用返回按钮返回到前一个屏幕。
  • 在 iOS 上,模态框会从当前屏幕底部滑入。要关闭它,请从顶部向下滑动。
  • 在 web 上,模态框会作为一个单独的路由呈现,关闭行为必须使用 router.canGoBack() 手动提供。以下是关闭模态框的示例:
src/app/modal.tsx
import { Link, router} from 'expo-router'; import { StyleSheet, Text, View } from 'react-native'; export default function Modal() { const isPresented = router.canGoBack(); return ( <View style={styles.container}> <Text>模态框屏幕</Text> {isPresented && <Link href="../">关闭模态框</Link>} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });

在 iOS 上更改状态栏外观

默认情况下,在 iOS 上,模态框具有深色背景,这会隐藏状态栏。要更改状态栏外观,你可以使用 Platform API 检查当前平台是否为 iOS,然后在 modal.tsx 文件中使用 StatusBar 组件来更改外观。

src/app/modal.tsx
import { StyleSheet, Text, View, Platform } from 'react-native'; import { StatusBar } from 'expo-status-bar'; export default function Modal() { return ( <View style={styles.container}> <Text>模态屏幕</Text> <StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });

处理深度链接模态框

在使用堆栈或嵌套堆栈导航器时,模态框需要进行锚定以确保正确的导航行为。当深度链接到模态路由时,这一点尤为重要。如果不进行锚定,模态框后面的屏幕会被清除,从而失去导航上下文。

锚点 作为模态框的基础。在复杂应用中,当你有嵌套堆栈时,必须为嵌套堆栈定义锚点,并且它的值会成为该堆栈的初始路由。

你可以通过从堆栈的布局文件中导出 unstable_settings 来配置锚点:

export const unstable_settings = { anchor: 'index', // 锚定到 index 路由 };

在上面的示例中,anchor: 'index' 告诉 Expo Router 在展示模态框时,应在后台保留指定的锚点路由。

表单式片段展示

表单式片段将模态框作为底部抽屉呈现,应用用户可以在不同高度之间拖动它(称为 detent)。这对于需要部分屏幕覆盖且支持交互式调整大小的内容非常有用。

基本用法

要使用 form sheet,请在模态屏幕上将 presentation 选项设置为 formSheet

src/app/_layout.tsx
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'formSheet', }} /> </Stack> ); }

配置片段 detent

Detent 定义了片段可以停留的高度。使用 sheetAllowedDetents 来配置它们:

  • 数值数组 (number[]): 将吸附位置指定为屏幕高度 0 到 1 之间的分数。例如,[0.25, 0.5, 1] 会在 25%、50% 和全屏高度处创建三个吸附点。值必须按升序排序。

  • 适应内容 ('fitToContents'): 片段会根据其内容自动调整大小。使用此选项时,你必须提供明确的内容尺寸,因为不支持 flex: 1,这是因为片段需要知道内容的实际大小来确定其高度。

Android 最多支持 3 个 detent。iOS 接受任意数量的 detent。
src/app/_layout.tsx
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'formSheet', sheetAllowedDetents: [0.25, 0.5, 1], sheetInitialDetentIndex: 1, }} /> </Stack> ); }

其他片段选项

选项类型描述
sheetInitialDetentIndexnumber | 'last'片段打开时所在的 detent 索引(默认值:0)。
sheetGrabberVisibleboolean在片段顶部显示一个拖拽手柄(仅 iOS)。
sheetCornerRadiusnumber片段的圆角半径,单位为像素。
sheetLargestUndimmedDetentIndexnumber | 'none' | 'last'保持背景不变暗的最大 detent 索引。
src/app/_layout.tsx
import { Stack } from 'expo-router'; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'formSheet', sheetAllowedDetents: [0.25, 0.5, 1], sheetInitialDetentIndex: 0, sheetGrabberVisible: true, sheetCornerRadius: 24, sheetLargestUndimmedDetentIndex: 1, }} /> </Stack> ); }

片段底部栏(Android)

unstable_sheetFooter 是仅限 Android 的 experimental 功能,未来版本中可能会发生变化。

你可以使用 React 组件为片段添加一个在所有 detent 位置都保持可见的底部栏:

src/app/_layout.tsx
import { Stack } from 'expo-router'; import { View, Button } from 'react-native'; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'formSheet', sheetAllowedDetents: [0.5, 1], unstable_sheetFooter: () => ( <View style={{ padding: 16, backgroundColor: 'white' }}> <Button title="Confirm" onPress={() => {}} /> </View> ), }} /> </Stack> ); }

在自定义 detent 中使用 flex: 1

在 SDK 55 及更高版本中,当使用自定义数值 detent 时,iOS 上的 flex: 1 可以正常工作。这在 fitToContents 模式下不适用,在该模式下你必须提供明确的内容尺寸。

当使用数值 detent 时,你的模态内容可以使用 flex: 1 来填充片段内可用空间:

src/app/modal.tsx
import { StyleSheet, Text, View } from 'react-native'; export default function Modal() { return ( <View style={styles.container}> <Text>模态内容</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'white', }, });

其他信息

展示选项

在 Android 和 iOS 上,可以使用 presentation 选项以不同方式展示模态屏幕。

选项描述
card新屏幕将被推入堆栈。Android 上的默认动画会因 OS 版本和主题而异。iOS 上,它将从侧边滑入。
modal新屏幕将以模态方式展示,允许在屏幕内渲染嵌套堆栈。
transparentModal新屏幕将以模态方式展示,同时保留之前的屏幕可见。如果屏幕具有半透明背景,则仍然可以看到下方内容。
containedModal在 Android 上,回退为 modal。在 iOS 上,使用 UIModalPresentationCurrentContext 模态样式。
containedTransparentModal在 Android 上,回退为 transparentModal。在 iOS 上,使用 UIModalPresentationOverCurrentContext 模态样式。
fullScreenModal在 Android 上,回退为 modal。在 iOS 上,使用 UIModalPresentationFullScreen 模态样式。
formSheet展示一个带有可配置 detent 的底部抽屉。详情参见 FormSheet presentation