使用Animated.View包裹组件致触摸事件穿透下层组件的修复方案
问题
我有两个兄弟组件:
<MapContainer/><BottomUi>
<BottomUi>通过absolute定位覆盖在地图之上,样式如下:
bottomUi: { position: 'absolute', bottom: 0, width: '100%', alignItems: 'center', },
原本一切正常,但使用React Native的Animated API为<BottomUi>添加动画后,发现用Animated.View包裹组件会导致<BottomUi>上的触摸事件穿透到下层的<MapContainer>。
已尝试以下方法但无效:
- 在
<BottomUi>上使用onResponderStart={() => true} - 使用
pointerEvents,结果禁用了<BottomUi>的触摸事件,但触摸仍会穿透到下层组件 - 用
TouchableWithoutFeedback包裹组件
相关代码:
app.js
/** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow strict-local */ import React, {useContext, useEffect, useState} from 'react'; import { ActivityIndicator, Appearance, StyleSheet, Text, TextInput, View, } from 'react-native'; import BottomUi from './src/components/bottomUi.component'; import Busy from './src/components/busy.component'; import Card from './src/components/card.component'; import InfoCard from './src/components/infoCard.component'; import MapContainer from './src/components/mapContainer.component'; import TopUi from './src/components/topUi.component'; import {PathProvider} from './src/context/path.context'; import {PointProvider} from './src/context/points.context'; import useToggle from './src/hooks/toggle.hook'; import useStorage from './src/hooks/useStorage.hook'; import {getStyles} from './src/utils/getStyles'; const App = () => { const colorScheme = Appearance.getColorScheme(); const isDark = colorScheme === 'light' ? 'false' : 'true'; const [coords, setcoords] = React.useState({ latitude: 52.3727598, longitude: 4.8936041, }); const [darkTheme, toggleTheme] = useToggle(isDark); const [cardVisible, toggleCard] = useToggle(true); const [gettingData, setgettingData] = useState(false); const [hightlightPoint, sethightlightPoint] = useState(); const styles = getStyles(darkTheme); const {getObject} = useStorage(); useEffect(() => { async function getStoredLocation() { try { const location = await getObject('@coords'); console.log('location:::', location); if (!location || !location.latitude || !location.longitude) { return; } setcoords(location); } catch (error) { console.log(error); } } getStoredLocation(); }, []); return ( <View style={styles.home}> <PointProvider> <PathProvider> <MapContainer coords={coords} styles={styles} setgettingData={setgettingData} hightlightPoint={hightlightPoint} /> <TopUi styles={styles} setcoords={setcoords} useDarkTheme={darkTheme} setuseDarkTheme={toggleTheme} /> <BottomUi styles={styles} cardVisible={cardVisible}> { <Card styles={styles} toggleCard={toggleCard} sethightlightPoint={sethightlightPoint} gettingData={gettingData} cardVisible={cardVisible} /> } <InfoCard isVisible={gettingData} styles={styles} message={<Busy styles={styles} />} /> </BottomUi> </PathProvider> </PointProvider> </View> ); }; export default App;
<BottomUi>组件代码
import { View, Animated, TouchableWithoutFeedback, TouchableHighlight, } from 'react-native'; import React, {useEffect, useState} from 'react'; export default function BottomUi({styles, cardVisible, children}) { const [yPosition] = useState(new Animated.Value(10)); useEffect(() => { Animated.timing(yPosition, { toValue: cardVisible ? 0 : 200, duration: 250, useNativeDriver: true, }).start(); }, [cardVisible]); return ( <Animated.View style={{transform: [{translateY: yPosition}]}}> <View style={styles.bottomUi}>{children}</View> </Animated.View> ); }
解决办法
方案1:将动画与定位样式合并到Animated.View
问题根源在于当前Animated.View仅承担动画作用,定位样式放在内部View上,导致原生层触摸区域计算异常。把定位样式和动画合并到Animated.View,确保触摸区域正确识别:
修改<BottomUi>组件代码:
import { View, Animated } from 'react-native'; import React, { useEffect, useState } from 'react'; export default function BottomUi({styles, cardVisible, children}) { const [yPosition] = useState(new Animated.Value(10)); useEffect(() => { Animated.timing(yPosition, { toValue: cardVisible ? 0 : 200, duration: 250, useNativeDriver: true, }).start(); }, [cardVisible]); // 合并动画样式与定位样式 const animatedBottomUiStyle = { ...styles.bottomUi, transform: [{translateY: yPosition}] }; return ( <Animated.View style={animatedBottomUiStyle}> {children} </Animated.View> ); }
方案2:给Animated.View添加透明背景
原生驱动动画下,无背景的Animated.View可能无法正确捕获触摸事件,添加透明背景可以强制其识别触摸区域:
修改<BottomUi>的返回部分:
return ( <Animated.View style={{ transform: [{translateY: yPosition}], backgroundColor: 'transparent', // 添加透明背景 width: '100%', }} > <View style={styles.bottomUi}>{children}</View> </Animated.View> );
方案3:调整pointerEvents属性
给Animated.View设置pointerEvents="box-only",确保它自身捕获触摸事件,同时不影响子组件的交互:
return ( <Animated.View style={{transform: [{translateY: yPosition}]}} pointerEvents="box-only" > <View style={styles.bottomUi}>{children}</View> </Animated.View> );
以上方案均能解决触摸穿透问题,优先推荐方案1,因为它从结构上避免了触摸区域的歧义。
内容的提问来源于stack exchange,提问作者Hasan Aga
相关产品推荐
相关产品推荐

