React Native Modal结合Reanimated回弹动画失效问题求助
React Native Modal下拉回弹动画无效问题修复
问题概述
使用React Native + react-native-gesture-handler 2.14 + react-native-reanimated 3.6.2实现Modal下拉关闭功能,拖拽超过阈值能正常关闭,但拖拽距离不足时,Modal无法通过弹簧动画回弹到初始位置(transformY=0),其他组件的Reanimated插值动画正常,怀疑Modal原生动画冲突。
问题代码
// Drag/Pan gesture handler const dragGesture = Gesture.Pan() .activeOffsetY(100) // 拖拽超过100px才触发手势 .enabled(isAtTop) // 仅在页面顶部时监听手势 .onStart(() => { isDragging.value = true }) .onUpdate((event) => { if (event.translationY >= 0) { translateY.value = event.translationY } }) .onEnd((event) => { // 拖拽超过阈值则关闭Modal if (translateY.value > windowHeight / 2 || (event.velocityY > 500 && isAtTop)) { runOnJS(setModalVisible)(false) } else { // 拖拽距离不足时回弹至初始位置(动画无效) translateY.value = withSpring(0, { damping: 20, stiffness: 90, }) } isDragging.value = false }) const composedGestures = Gesture.Simultaneous(dragGesture, nativeGesture) const modalAnimatedStyle = useAnimatedStyle(() => { console.log(translateY.value) return { transform: [{ translateY: translateY.value }], } }, [translateY.value]) return ( <Modal animationType="slide" transparent={true} visible={modalVisible} onRequestClose={() => { setModalVisible(!modalVisible) }} onDismiss={() => { translateY.value = 0 }}> <Animated.View style={[styles.modalView, modalAnimatedStyle]}> <HomeListingsExpanded navigation={navigation} route={{ params: { listing, timezoneData } }} setModalVisible={setModalVisible} setIsAtTop={setIsAtTop} composedGestures={composedGestures} /> </Animated.View> </Modal> )
修复方案
1. 移除Modal原生动画
Modal的animationType="slide"会启动原生动画,和Reanimated的自定义动画产生冲突,导致回弹动画被覆盖。将animationType改为none或者直接删除该属性:
<Modal animationType="none" // 关键修改:禁用原生动画 transparent={true} visible={modalVisible} // 其他属性保持不变 >
2. 在UI线程执行弹簧动画
在Gesture的onEnd worklet中,必须通过runOnUI来触发Reanimated动画,否则动画可能无法正确执行:
.onEnd((event) => { if (translateY.value > windowHeight / 2 || (event.velocityY > 500 && isAtTop)) { runOnJS(setModalVisible)(false) } else { // 用runOnUI包裹动画执行逻辑 runOnUI(() => { translateY.value = withSpring(0, { damping: 20, stiffness: 90, }) })() } isDragging.value = false })
3. 拖拽开始时暂停现有动画
避免拖拽操作和未完成的回弹动画冲突,在onStart中添加cancelAnimation:
.onStart(() => { isDragging.value = true cancelAnimation(translateY) // 取消当前可能在运行的动画 })
4. 简化useAnimatedStyle依赖
useAnimatedStyle不需要手动传入translateY.value作为依赖,Reanimated会自动追踪共享值的变化,简化代码:
const modalAnimatedStyle = useAnimatedStyle(() => { return { transform: [{ translateY: translateY.value }], } })
完整修复后的代码
// Drag/Pan gesture handler const dragGesture = Gesture.Pan() .activeOffsetY(100) .enabled(isAtTop) .onStart(() => { isDragging.value = true cancelAnimation(translateY) // 取消现有动画 }) .onUpdate((event) => { if (event.translationY >= 0) { translateY.value = event.translationY } }) .onEnd((event) => { if (translateY.value > windowHeight / 2 || (event.velocityY > 500 && isAtTop)) { runOnJS(setModalVisible)(false) } else { runOnUI(() => { translateY.value = withSpring(0, { damping: 20, stiffness: 90, }) })() } isDragging.value = false }) const composedGestures = Gesture.Simultaneous(dragGesture, nativeGesture) const modalAnimatedStyle = useAnimatedStyle(() => { return { transform: [{ translateY: translateY.value }], } }) return ( <Modal animationType="none" // 禁用原生动画 transparent={true} visible={modalVisible} onRequestClose={() => { setModalVisible(!modalVisible) }} onDismiss={() => { translateY.value = 0 }}> <Animated.View style={[styles.modalView, modalAnimatedStyle]}> <HomeListingsExpanded navigation={navigation} route={{ params: { listing, timezoneData } }} setModalVisible={setModalVisible} setIsAtTop={setIsAtTop} composedGestures={composedGestures} /> </Animated.View> </Modal> )
内容的提问来源于stack exchange,提问作者Deland
相关产品推荐
相关产品推荐

