- 前端
- 移动开发
- UI组件
- 跨平台
【免费下载链接】react-native-bottom-sheet
A performant interactive bottom sheet with fully configurable options 🚀
导读
BottomSheetBackdrop是 react-native-bottom-sheet 内置的预置背板(backdrop)组件,用于在底部弹层展开时覆盖其背后的内容区域,起到视觉隔离与交互拦截的作用。本指南以 v4 版本文档为骨架,结合仓库源码逐项拆解它的全部可配置 Props、默认值与按压行为语义,并演示如何通过backdropComponent将其挂载到BottomSheet/BottomSheetModal上,读完即可在真实业务中直接落地使用。
说明:本指南面向 v4 版本的 bottomsheetbackdrop.md 编写,当前仓库主分支的最新版本文档位于 website/docs/components/bottomsheetbackdrop.md,两者在 Props 定义上保持一致,示例代码略有差异(最新版增加了
GestureHandlerRootView与enableDynamicSizing),正文中会一并标注。
组件定位:一个"开箱即用"的背板实现
BottomSheetBackdrop是由 react-native-bottom-sheet 提供的内置组件,本质上是"预构建的 BottomSheet 背板实现",通过可配置的 Props 控制其外观与行为(参见 官方文档说明)。它不需要你从零编写遮罩层,只需在BottomSheet上通过backdropComponent传入即可获得完整的淡入淡出动画与点击反馈。
从源码结构看,背板的挂载链路是:
- BottomSheetBackdrop.tsx:背板组件本体,负责动画、手势与无障碍实现;
- BottomSheetBackdropContainer.tsx:容器组件,负责把
animatedIndex、animatedPosition与style透传给背板组件; - constants.ts:集中定义全部默认值;
- types.d.ts:定义 Props 与
BackdropPressBehavior类型。
在 BottomSheet.tsx 的渲染树中,BottomSheetBackdropContainer被置于BottomSheetContainer之前,并通过内部共享值将animatedIndex(当前快照点索引)与animatedPosition(当前快照点位置)注入背板——这正是背板能随弹层位置同步淡入淡出的数据来源。
Props 全解:从类型、默认值到源码验证
BottomSheetBackdrop继承react-native的ViewProps(至少包含style),并在此基础上追加以下 Props。下表汇总了全部配置项及其类型、默认值与必填性:
| Prop | 类型 | 默认值 | 必填 |
|---|---|---|---|
animatedIndex | Animated.SharedValue<number> | 0 | YES |
animatedPosition | Animated.SharedValue<number> | 0 | YES |
opacity | number | 0.5 | NO |
appearsOnIndex | number | 1 | NO |
disappearsOnIndex | number | 0 | NO |
enableTouchThrough | boolean | false | NO |
pressBehavior | BackdropPressBehavior \| number | 'close' | NO |
onPress | function | undefined | NO |
其中animatedIndex与animatedPosition是必填的共享值,由BottomSheetBackdropContainer自动注入,因此你在自定义渲染函数中通常只需{...props}展开即可(详见下文示例)。其余默认值均可在 constants.ts 中找到一一对应的实现。
animatedIndex:当前快照点索引
弹层当前所处的快照点(snap point)索引,类型为Animated.SharedValue<number>。背板动画以此为基础进行插值,源码中的核心逻辑(BottomSheetBackdrop.tsx)如下:
const containerAnimatedStyle = useAnimatedStyle( () => ({ opacity: interpolate( animatedIndex.value, [-1, disappearsOnIndex, appearsOnIndex], [0, 0, opacity], Extrapolation.CLAMP ), flex: 1, }), [animatedIndex, appearsOnIndex, disappearsOnIndex, opacity] );也就是说,背板透明度在-1 → disappearsOnIndex → appearsOnIndex三段取值区间内被映射为0 → 0 → opacity,并以Extrapolation.CLAMP钳制边界:索引低于disappearsOnIndex时完全不显示,达到appearsOnIndex后保持全量透明度,中间过程随索引连续过渡。值得注意的是animatedIndex只有在index到达某个快照点时才会变为该值,拖动过程中连续变化的是animatedPosition。
animatedPosition:当前快照点位置
弹层当前所处的位置(像素值),类型为Animated.SharedValue<number>。它是实现"跟随拖拽实时呈现背板"的关键,因为在拖动过程中索引并不变化。从源码结构看,背板组件目前主要消费animatedIndex,而animatedPosition由容器统一注入(BottomSheetBackdropContainer.tsx),供需要按位置精确驱动动画的自定义背板使用——若你自定义背板并需要像素级动画,应基于此共享值做插值。
opacity:背板不透明度
控制背板整体透明度,类型number,默认0.5。需要更强遮罩时调大(如0.8),需要轻量提示时调小(如0.2)。注意该值作为插值动画的目标值参与计算,而不是直接静态赋值。
appearsOnIndex / disappearsOnIndex:出现与消失的索引阈值
appearsOnIndex(默认1):弹层展开到该索引时背板开始出现;disappearsOnIndex(默认0):弹层收起到该索引时背板消失。
两者共同决定了背板的"生命周期区间"。官方示例中常把两者设为相邻索引,例如disappearsOnIndex={1}、appearsOnIndex={2},使背板仅在最高快照点出现。此外,disappearsOnIndex还参与两处行为逻辑:
- 点击行为中的
collapse模式会调用snapToIndex(disappearsOnIndex)(见下文 pressBehavior); useAnimatedReaction(BottomSheetBackdrop.tsx)在animatedIndex.value <= disappearsOnIndex时通过runOnJS将容器的pointerEvents置为'none',即背板不可见时自动禁用其触摸响应,避免遮挡上层交互。
useAnimatedReaction( () => animatedIndex.value <= disappearsOnIndex, (shouldDisableTouchability, previous) => { if (shouldDisableTouchability === previous) return; runOnJS(handleContainerTouchability)(shouldDisableTouchability); }, [disappearsOnIndex] );enableTouchThrough:是否允许点击穿透
布尔值,默认false。为true时背板不再拦截触摸事件,用户可以直接点到背板下方的页面内容。源码通过pointerEvents实现(BottomSheetBackdrop.tsx):
const [pointerEvents, setPointerEvents] = useState<ViewProps['pointerEvents']>( enableTouchThrough ? 'none' : 'auto' );pointerEvents初始值取决于该 Prop,随后由useAnimatedReaction在背板不可见时动态切换为'none'。
pressBehavior:点击背板时的行为
定义用户按下背板时发生什么,默认'close'。可选值:
'none':什么都不做,且onPress会被忽略;'close':关闭弹层(收起到底部之外,完全关闭);'collapse':收起弹层到disappearsOnIndex对应的索引;N(数字):直接将弹层吸附到第N个快照点。
其类型定义为BackdropPressBehavior = 'none' | 'close' | 'collapse' | number(见 types.d.ts)。对应实现(BottomSheetBackdrop.tsx):
const handleOnPress = useCallback(() => { onPress?.(); if (pressBehavior === 'close') { close(); } else if (pressBehavior === 'collapse') { snapToIndex(disappearsOnIndex as number); } else if (typeof pressBehavior === 'number') { snapToIndex(pressBehavior); } }, [snapToIndex, close, disappearsOnIndex, pressBehavior, onPress]);细节要点:
close()与snapToIndex()均来自useBottomSheet()钩子(BottomSheetBackdrop.tsx),与弹层实例的方法调用等价;- 点击手势通过
react-native-gesture-handler的Gesture.Tap()注册,并在 UI 线程结束后用runOnJS(handleOnPress)()回到 JS 线程执行(BottomSheetBackdrop.tsx); - 当
pressBehavior === 'none'时组件不会包裹GestureDetector(BottomSheetBackdrop.tsx),即彻底关闭手势监听,这也解释了为何onPress会被忽略。
onPress:自定义点击回调
类型function,默认undefined,非必填。按下背板时会先执行onPress,再执行pressBehavior定义的动作——这在源码中体现为先调用onPress?.(),随后再进入close/collapse/ 数字分支。典型用途包括:统计埋点、播放音效、弹出自定义提示等。
无障碍(Accessibility)默认值
除文档列出的 Props 外,组件还内置了一套无障碍默认值(constants.ts):
| 属性 | 默认值 |
|---|---|
accessible | true |
accessibilityRole | 'button' |
accessibilityLabel | 'Bottom sheet backdrop' |
accessibilityHint | 动态生成:Tap to ${pressBehavior === 'number' ? 'move' : pressBehavior} the Bottom Sheet |
accessibilityHint会根据pressBehavior自动生成描述文案(BottomSheetBackdrop.tsx),方便读屏器用户理解背板的点击后果,也是该组件在无障碍层面"开箱即用"的体现。
完整示例:在 BottomSheet 上挂载背板
以下是 v4 版本文档提供的标准用法(原文档示例),将背板配置为"仅在最高快照点(索引 2)出现,收起即消失":
import React, { useCallback, useMemo, useRef } from "react"; import { View, Text, StyleSheet } from "react-native"; import BottomSheet, { BottomSheetBackdrop } from "@gorhom/bottom-sheet"; const App = () => { // ref const bottomSheetRef = useRef<BottomSheet>(null); // variables const snapPoints = useMemo(() => ["25%", "50%", "75%"], []); // callbacks const handleSheetChanges = useCallback((index: number) => { console.log("handleSheetChanges", index); }, []); // renders const renderBackdrop = useCallback( (props) => ( <BottomSheetBackdrop {...props} disappearsOnIndex={1} appearsOnIndex={2} /> ), [] ); return ( <View style={styles.container}> <BottomSheet ref={bottomSheetRef} index={1} snapPoints={snapPoints} backdropComponent={renderBackdrop} onChange={handleSheetChanges} > <View style={styles.contentContainer}> <Text>Awesome 🎉</Text> </View> </BottomSheet> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 24, backgroundColor: "grey", }, contentContainer: { flex: 1, alignItems: "center", }, }); export default App;示例要点拆解
backdropComponent={renderBackdrop}:BottomSheet会在渲染时调用该函数,并自动传入animatedIndex、animatedPosition与style,因此自定义渲染函数必须以{...props}或(props) => <BottomSheetBackdrop {...props} .../>的形式透传;disappearsOnIndex={1}+appearsOnIndex={2}:snapPoints为["25%", "50%", "75%"],索引 0/1/2 分别对应 25%/50%/75%。背板在索引 1(50%)及以下完全透明且禁用触摸,展开到索引 2(75%)时淡入——典型的"仅在完全展开时显示遮罩"场景;index={1}:弹层初始停在 50%,此时背板不可见,符合上述阈值设定;- 渲染函数必须用
useCallback包裹:否则每次渲染都会新建函数引用,可能引起BottomSheet不必要的重渲染。
最新版差异(当前仓库主分支)
主分支的 最新版本文档 对示例做了两处更新,适配新版 API:
- 用
GestureHandlerRootView包裹根视图,确保手势系统正常工作; - 内容区改用
BottomSheetView包裹,并在BottomSheet上显式设置enableDynamicSizing={false},避免动态尺寸模式下的歧义。
如果你的项目基于最新版库,建议直接采用该写法:
import { GestureHandlerRootView } from 'react-native-gesture-handler'; import BottomSheet, { BottomSheetView, BottomSheetBackdrop } from "@gorhom/bottom-sheet"; // ... <GestureHandlerRootView style={styles.container}> <BottomSheet ref={bottomSheetRef} index={1} snapPoints={snapPoints} backdropComponent={renderBackdrop} enableDynamicSizing={false} onChange={handleSheetChanges} > <BottomSheetView style={styles.contentContainer}> <Text>Awesome 🎉</Text> </BottomSheetView> </BottomSheet> </GestureHandlerRootView>进阶玩法:动态切换 pressBehavior 与 Modal 集成
运行时切换点击行为
官方示例应用 example/src/screens/advanced/BackdropExample.tsx 演示了如何用 state 在none → close → collapse间循环切换pressBehavior,并配套expand()/collapse()/close()按钮控制弹层:
const [backdropPressBehavior, setBackdropPressBehavior] = useState< 'none' | 'close' | 'collapse' >('collapse'); const renderBackdrop = useCallback( props => ( <BottomSheetBackdrop {...props} pressBehavior={backdropPressBehavior} /> ), [backdropPressBehavior] );注意:由于renderBackdrop依赖backdropPressBehavior,切换行为会触发新的渲染函数引用,BottomSheet随之重新渲染背板——这是"受控配置"的常规做法,也提醒我们所有背板配置变化都必须通过useCallback依赖数组正确传递。
与 BottomSheetModal 集成
背板同样适用于BottomSheetModal。参考 example/src/screens/modal/BackdropExample.tsx,其用法几乎一致,仅需把BottomSheet换成BottomSheetModal并提供present()触发入口,同时可显式标注 Props 类型以获得类型提示:
const renderBackdrop = useCallback( (props: BottomSheetBackdropProps) => ( <BottomSheetBackdrop {...props} pressBehavior={backdropPressBehavior} /> ), [backdropPressBehavior] ); <BottomSheetModal ref={bottomSheetRef} snapPoints={snapPoints} enableDynamicSizing={false} handleComponent={renderHeaderHandle} backdropComponent={renderBackdrop} onDismiss={handleDismiss} > <ContactList type="View" count={5} /> </BottomSheetModal>BottomSheetBackdropProps类型同样从包入口导出,可直接用于自定义渲染函数的参数注解(见 types.d.ts)。
自定义背板:不满足预置时的扩展路径
如果BottomSheetBackdrop的样式不满足需求(例如需要背景渐变、毛玻璃或自定义子元素),有两个方向:
- 叠加子元素:
BottomSheetBackdropProps支持children(types.d.ts),组件会将其渲染在背板内部(BottomSheetBackdrop.tsx),同时保留全部动画与手势能力; - 完全自研:编写自己的背板组件,接收容器注入的
animatedIndex/animatedPosition/style三个参数即可无缝替换。可以参照仓库中 CustomBackgroundExample 之类的自定义组件思路,自行实现useAnimatedStyle驱动的透明度插值。
常见问题与踩坑提示
- 背板不出现?检查
appearsOnIndex/disappearsOnIndex是否与snapPoints索引匹配。若弹层最高只到索引 1,而appearsOnIndex设为 2,背板永远不会显示。 - 点击背板没反应?确认
pressBehavior不是'none'(该模式下onPress也会被忽略);同时确认背板不可见时pointerEvents已被置为'none'——若想强制拦截,需保证索引高于disappearsOnIndex。 - 想点击穿透又保留动画?将
enableTouchThrough设为true,背板只负责视觉遮罩,不拦截任何触摸。 - 点击背板后先执行自定义逻辑?把逻辑放进
onPress,它会先于pressBehavior的动作执行,无需手动管理时序。
结语
BottomSheetBackdrop用一套精简的 Props 覆盖了背板最常见的全部诉求:出现/消失阈值、透明度、点击穿透、按压行为与无障碍。配合BottomSheetBackdropContainer自动注入的动画共享值,它既能"零配置"即插即用,也能通过children、style或完全自定义组件轻松扩展。对照 组件源码 与 示例应用 阅读,可以更清晰地理解其动画插值与手势处理的底层实现。
- 前端
- 移动开发
- UI组件
- 跨平台
【免费下载链接】react-native-bottom-sheet
A performant interactive bottom sheet with fully configurable options 🚀
相关推荐
react-native-bottom-sheet 的 BottomSheetBackdrop 组件完全指南:Props、按压行为与自定义实现
react native bottom sheet 的 BottomSheetBackdrop 组件完全指南:Props、按压行为与自定义实现 导读 Botto
前端移动开发UI组件跨平台react-native-bottom-sheet 组件 Props 全解:从吸附点、手势到键盘与动画的完整配置指南
react native bottom sheet 组件 Props 全解:从吸附点、手势到键盘与动画的完整配置指南 本篇指南以 react native bo
前端移动开发UI组件跨平台WeKan 持久化作业设计:检查点、租约与幂等重放的重启安全作业契约
WeKan 持久化作业设计:检查点、租约与幂等重放的重启安全作业契约 本文基于 WeKan 仓库中 Admin Panel → Problems 下的设计文档
前端移动开发UI组件跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考