This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 55).
模态底部表单
一个 Jetpack Compose ModalBottomSheet 组件,用于从屏幕底部展示内容。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
Expo UI ModalBottomSheet 与官方 Jetpack Compose Bottom Sheet API 保持一致,并以从底部滑入的模态底部抽屉形式显示内容。
安装
- npx expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
用法
基础底部抽屉
在卸载组件之前,使用 ref.hide() 通过动画以编程方式关闭底部抽屉。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function BasicBottomSheetExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)}> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>来自底部抽屉的问候!</Text> <Text>你可以在这里添加更多内容。</Text> <Button onClick={hideSheet}> <Text>关闭</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
跳过部分展开状态
当设置了 skipPartiallyExpanded 时,底部抽屉会直接以完全展开状态打开,而不会先停在半高位置。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function SkipPartiallyExpandedExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} skipPartiallyExpanded> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>这个底部抽屉会跳过部分展开状态。</Text> <Text>它会直接以完全展开的位置打开。</Text> <Button onClick={hideSheet}> <Text>关闭</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
自定义颜色
使用 containerColor、contentColor 和 scrimColor 来自定义底部抽屉的外观。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function CustomColorsExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开彩色底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} containerColor="#1a1a2e" contentColor="#e0e0e0" scrimColor="#806200EE"> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>自定义样式的底部抽屉。</Text> <Text>深色容器配合紫色遮罩层。</Text> <Button onClick={hideSheet}> <Text>关闭</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
自定义拖拽把手
使用 ModalBottomSheet.DragHandle 插槽提供自定义拖拽把手,或者设置 showDragHandle={false} 以将其完全隐藏。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Box, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { background, clip, fillMaxWidth, height, padding, Shapes, width, } from '@expo/ui/jetpack-compose/modifiers'; export default function CustomDragHandleExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开自定义把手底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)}> <ModalBottomSheet.DragHandle> <Column horizontalAlignment="center" modifiers={[fillMaxWidth(), padding(0, 12, 0, 8)]}> <Box modifiers={[width(60), height(6), clip(Shapes.Circle), background('#6200EE')]} /> </Column> </ModalBottomSheet.DragHandle> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[padding(16, 16, 16, 16)]}> <Button onClick={hideSheet}> <Text>关闭</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
底部抽屉中的 React Native 内容
使用 RNHostView 将可交互的 React Native 视图嵌入 Compose 底部抽屉中。这样你就可以将 Compose 布局与 Pressable 和 Text 等 RN 组件混合使用。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, RNHostView, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { padding } from '@expo/ui/jetpack-compose/modifiers'; import { Pressable, Text as RNText, View } from 'react-native'; export default function RNContentBottomSheetExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开 RN 内容底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} skipPartiallyExpanded={false}> <Column verticalArrangement={{ spacedBy: 16 }} modifiers={[padding(16, 16, 16, 16)]}> <Text>在底部抽屉中混合使用 Compose + RN</Text> <RNHostView matchContents> <View> <RNText style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 8 }}> React Native 内容 </RNText> <Pressable style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8, alignItems: 'center', }} onPress={hideSheet}> <RNText style={{ color: 'white', fontWeight: '600' }}>关闭</RNText> </Pressable> </View> </RNHostView> </Column> </ModalBottomSheet> )} </Host> ); }
具有 flex 的 React Native 内容
不使用 matchContents 的 RNHostView 可以让 RN 视图填充底部抽屉中剩余的空间。将其与父级 Column 上固定的 height 修饰符结合使用,以控制底部抽屉尺寸。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, RNHostView, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { height, padding } from '@expo/ui/jetpack-compose/modifiers'; import { Text as RNText, View } from 'react-native'; export default function FlexRNContentExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开 Flex 内容底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} skipPartiallyExpanded> <Column modifiers={[height(400), padding(16, 16, 16, 16)]}> <Text>带有 flex: 1 的 RN 视图</Text> <RNHostView> <View style={{ flex: 1, backgroundColor: '#9B59B6', borderRadius: 10 }}> <RNText style={{ color: 'white', fontSize: 18, fontWeight: 'bold', padding: 16, }}> React Native 内容(flex: 1) </RNText> </View> </RNHostView> </Column> </ModalBottomSheet> )} </Host> ); }
不可关闭的底部抽屉
结合 properties、sheetGesturesEnabled 创建一个只能通过编程方式关闭的底部抽屉。
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function NonDismissibleExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>打开不可关闭底部抽屉</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} sheetGesturesEnabled={false} properties={{ shouldDismissOnBackPress: false, shouldDismissOnClickOutside: false, }}> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>这个底部抽屉不能通过滑动、返回键或点击外部来关闭。</Text> <Text>只有下面的按钮可以关闭它。</Text> <Button onClick={hideSheet}> <Text>关闭</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
API
import { ModalBottomSheet } from '@expo/ui/jetpack-compose';
Constants
Props
React.ReactNodeThe children of the ModalBottomSheet component.
Can include a ModalBottomSheet.DragHandle slot for a custom drag handle.
ColorValueThe preferred color of the content inside the bottom sheet.
() => voidCallback function that is called when the user dismisses the bottom sheet (via swipe, back press, or tapping outside the scrim).
ModalBottomSheetPropertiesProperties for the modal window behavior.
Ref<ModalBottomSheetRef>Can be used to imperatively hide the bottom sheet with an animation.
boolean • Default: trueWhether gestures (swipe to dismiss) are enabled on the bottom sheet.
boolean • Default: trueWhether to show the default drag handle at the top of the bottom sheet.
Ignored if a custom ModalBottomSheet.DragHandle slot is provided.
Types
| Property | Type | Description |
|---|---|---|
| shouldDismissOnBackPress(optional) | boolean | Whether the bottom sheet can be dismissed by pressing the back button. Default: true |
| shouldDismissOnClickOutside(optional) | boolean | Whether the bottom sheet can be dismissed by clicking outside (on the scrim). Default: true |
| Property | Type | Description |
|---|---|---|
| hide | () => Promise<void> | Programmatically hides the bottom sheet with an animation. The returned promise resolves after the dismiss animation completes. |