模态

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


在 Expo Router 中使用模态
在 Expo Router 中使用模态

了解在应用的其他部分上显示内容的不同方式。

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

🌐 Modals are a common user interface pattern in mobile apps. They are used to present content on top of the existing screen and is used for different purposes, such as displaying confirmation alerts or standalone forms. You can create modals in your app using the following methods:

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

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

🌐 Each approach has its specific use case. Understanding when to use each method is important for creating a positive user experience.

React 服务器功能

🌐 React Native's Modal component

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

🌐 The Modal component is part of React Native's core API. Common use cases include:

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

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

🌐 Below is an example of a custom Modal component that overlays the current screen on different platforms:

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

🌐 For most use cases, you can use the Modal component and customize it according to your app's user interface requirements. For details on how to use the Modal component and its props, see the React Native documentation.

使用 Expo Router 的模态屏幕

🌐 Modal screen using Expo Router

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

🌐 A modal screen is a file created inside the app directory and is used as a route within the existing stack. It is used for complex interactions that need to be part of the navigation system, such as multi-step forms where you can link to a specific screen after the process completes.

下面是一个模态屏幕在不同平台上的工作方式的示例:

🌐 Below is an example of how a modal screen works on different platforms:

用法

🌐 Usage

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

🌐 To implement a modal route, create a screen called modal.tsx inside the app directory. Here's an example file structure:

app
_layout.tsx
index.tsx
modal.tsx

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

🌐 The above file structure produces a layout where the index is the first route in the stack. Inside the root layout file (app/_layout.tsx), you can add the modal route in the stack. To present it as a modal, set the presentation option to modal on the route.

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 文件导航到模态屏幕。

🌐 You can use the Link component to navigate to the modal screen from the index.tsx file.

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>Home screen</Text> <Link href="/modal" style={styles.link}> Open modal </Link> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, link: { paddingTop: 20, fontSize: 20, }, });

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

🌐 The modal.tsx presents the contents of the modal.

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

模态渲染和关闭行为

🌐 Modal presentation and dismiss behavior

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

🌐 A modal loses its previous context when it is the current screen in the navigator and is presented as a standalone screen. Its presentation and dismissal behavior are different on each platform:

  • 在 Android 上,模态窗口会滑动到当前屏幕之上。要关闭它,请使用返回按钮返回到前一个屏幕。
  • 在 iOS 上,模态窗口会从当前屏幕底部滑出。要关闭它,请从顶部向下滑动。
  • 在网页上,模态框作为一个独立的路由呈现,并且必须通过 router.canGoBack() 手动提供关闭行为。以下是如何关闭模态框的示例:
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>Modal screen</Text> {isPresented && <Link href="../">Dismiss modal</Link>} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });

更改 iOS 上的状态栏外观

🌐 Change status bar appearance on iOS

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

🌐 By default on iOS, the modal has a dark background which hides the status bar. To change the status bar appearance, you can use the Platform API to check if the current platform is iOS and then use the StatusBar component to change the appearance inside the modal.tsx file.

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>Modal screen</Text> <StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, });

处理深度链接模态窗口

🌐 Handle deep-linked modals

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

🌐 While working with stack or nested stack navigators, modals need to be anchored to ensure correct navigation behavior. This is essential when deep-linking to modal routes. Without anchoring, the screen behind the modal will be wiped away, leaving no navigation context.

一个_锚点_作为模态窗口的基础。在复杂的应用中,当你有嵌套堆栈时,必须为嵌套堆栈定义锚点,其值将成为堆栈的初始路由。

🌐 An anchor serves as the base for the modal. In complex apps, when you have nested stacks, the anchor must be defined for the nested stack, and its value becomes the initial route of the stack.

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

🌐 You can configure an anchor by exporting unstable_settings from your stack's layout file:

export const unstable_settings = { anchor: 'index', // Anchor to the index route };

在上面的示例中,anchor: 'index' 告诉 Expo 路由在显示模态窗口时应在后台保持指定的锚点路由。

🌐 In the above example, the anchor: 'index' tells the Expo Router that it should maintain the specified anchor route in the background when presenting a modal.

表格展示

🌐 Form sheet presentation

表单表单以底部滑出视图的形式呈现模态窗口,应用用户可以在不同高度(称为停靠点)之间拖动。这对于需要部分屏幕覆盖且可以交互调整大小的内容非常有用。

🌐 Form sheet presents a modal as a bottom sheet that app users can drag between different heights (called detents). This is useful for content that needs partial screen coverage with interactive sizing.

基本用法

🌐 Basic usage

要使用表单表,请在你的模态屏幕上将 presentation 选项设置为 formSheet

🌐 To use form sheet, set the presentation option to formSheet on your modal screen:

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> ); }

配置工作表止点

🌐 Configuring sheet detents

棘爪定义了工作板可以停留的高度。使用 sheetAllowedDetents 来配置它们:

🌐 Detents define the heights where the sheet can rest. Use sheetAllowedDetents to configure them:

  • 数值数组number[]):将吸附位置指定为屏幕高度的分数,范围在 0 到 1 之间。例如,[0.25, 0.5, 1] 会在屏幕高度的 25%、50% 和 100% 处创建三个吸附点。数值必须按升序排列。
  • 适应内容 ('fitToContents'):工作表会根据其内容自动调整大小。使用此选项时,必须提供明确的内容尺寸,因为 flex: 1 不受支持,工作表需要知道内容的实际大小才能确定其高度。

信息 Android 支持最多 3 个停点。iOS 可接受任意数量的停点。

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> ); }

额外的工作表选项

🌐 Additional sheet options

选项类型描述
sheetInitialDetentIndexnumber | 'last'表单打开时的挡块索引(默认值:0)。
sheetGrabberVisibleboolean在表单顶部显示抓手(仅限 iOS)。
sheetCornerRadiusnumber表单的圆角半径,单位为像素。
sheetLargestUndimmedDetentIndexnumber | 'none' | 'last'保持背景不变暗的最大挡块索引。
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> ); }

工作表页脚(安卓)

🌐 Sheet footer (Android)

重要 unstable_sheetFooter 是仅适用于 Android 的实验性功能,未来版本可能会有所更改。

你可以使用 React 组件在工作表中添加页脚,使其在所有固定位置都保持可见:

🌐 You can add a footer to the sheet that stays visible at all detent positions using a React component:

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="确认" onPress={() => {}} /> </View> ), }} /> </Stack> ); }

使用 flex: 1 配合自定义止动

🌐 Using flex: 1 with custom detents

信息 在 SDK 55 及更高版本中,使用自定义数值停止点时,flex: 1 在 iOS 上可以正常工作。对于 fitToContents,则无法实现,需要提供明确的内容尺寸。

在使用数字止档时,你的模态内容可以使用 flex: 1 来填充表单中的可用空间:

🌐 When using numeric detents, your modal content can use flex: 1 to fill the available space within the sheet:

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

附加信息

🌐 Additional information

演示选项

🌐 Presentation options

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

🌐 There are different options to present a modal screen using the presentation option on Android and iOS.

OptionDescription
cardThe new screen will be pushed onto a stack. The default animation on Android will vary depending on the OS version and theme. On iOS, it will slide from the side.
modalThe new screen will be presented modally, allowing for a nested stack to be rendered inside the screen.
transparentModalThe new screen will be presented modally, with the previous screen remaining visible. This allows the content below to still be seen if the screen has a translucent background.
containedModalOn Android, fallbacks to modal. On iOS, uses UIModalPresentationCurrentContext modal style.
containedTransparentModalOn Android, fallbacks to transparentModal. On iOS, uses UIModalPresentationOverCurrentContext modal style.
fullScreenModalOn Android, fallbacks to modal. On iOS, uses UIModalPresentationFullScreen modal style.
formSheetPresents a bottom sheet with configurable detents. See FormSheet presentation for details.