React Native Animated.ScrollView调用scrollTo安卓无法水平滚动
问题描述
使用Animated.ScrollView实现多页滚动功能时,iOS端运行完全正常,但Android端调用scrollTo()方法会出现滚动卡住、无法水平滑动的问题。
CustomPagerView组件代码如下:
import { StyleSheet, Animated, Dimensions, Platform} from 'react-native'; const {width, height} = Dimensions.get('screen'); export const CustomPagerView = ({ scrollViewRef, pageNumber, scrollX, setCurrentIndex, children, updateCurrentIndex, }) => { return ( <Animated.ScrollView ref={scrollViewRef} bounces={false} hozinontal={true} style={{flex: 1}} contentContainerStyle={{ width: width * pageNumber, flexDirection: 'row', flexGrow: 1, }} disableIntervalMomentum={false} decelerationRate={'normal'} showPageIndicator={false} pagingEnabled onMomentumScrollEnd={updateCurrentIndex} onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], { useNativeDriver: false, })} showsHorizontalScrollIndicator={false} scrollEnabled={false} onScrollEndDrag={() => { console.log('scrollViewRef.current>>>', scrollViewRef.current); setCurrentIndex(scrollViewRef.current); }} scrollEventThrottle={400}> {children} </Animated.ScrollView> ); }; const styles = StyleSheet.create({ container: { width, height, justifyContent: 'center', alignItems: 'center', paddingBottom: Platform.OS === 'android' ? 40 : 0, }, });
组件调用方式如下:
<CustomPagerView scrollViewRef={scrollViewRef} pageNumber={3} scrollX={scrollX} setCurrentIndex={setCurrentIndex} updateCurrentIndex={updateCurrentIndex}> <View key="0" style={{width, flex: 1}}> <Component1 /> </View> <View key="1" style={{width, flex: 1}}> <Component2 key="1" navigation={navigation} /> </View> <View key="2" style={{width, flex: 1}}> <Component3 navigation={navigation} /> </View> </CustomPagerView>
父组件中定义的相关状态和逻辑代码:
const scrollX = useRef(new Animated.Value(0)).current; const scrollViewRef = useRef(); const [currentIndex, setCurrentIndex] = useState(0); const {setTitle} = useContext(ReportTitleContext); const [currentPageNumber, setCurrentPageNumber] = useState(0); let pagerViewRef = useRef(); const updateCurrentIndex = (e) => { const contentOffsetX = Number(e.nativeEvent.contentOffset.x); const updatedCurrentIndex = Math.round(contentOffsetX / width); setCurrentIndex(updatedCurrentIndex); }; const onNextPageHandler = () => { setCurrentIndex(currentIndex + 1); setTitle(headerText[currentIndex + 1]); // scrollX.setValue((currentIndex + 1) * width); scrollViewRef.current.scrollTo({ x: (currentIndex + 1) * width, y: 0, animated: true, }); }; const onPreviousPageHandler = () => { setCurrentIndex(currentIndex - 1); setTitle(headerText[currentIndex - 1]); scrollViewRef.current.scrollTo({ x: (currentIndex - 1) * width, y: 0, animated: true, }); };
修复方案
代码中有3个直接导致Android端滚动异常的问题,按顺序修改即可:
- 属性拼写错误:
Animated.ScrollView的水平滚动属性是horizontal,代码中错写为hozinontal。iOS对属性拼写容错更高,但Android端识别不到该属性时会默认按垂直滚动处理,调用水平方向的scrollTo自然会卡住。 - 回调逻辑错误:
onScrollEndDrag回调中错误地将scrollViewRef.current(ScrollView组件实例对象)传给了setCurrentIndex,每次拖拽结束都会把原本存储页码的currentIndex状态覆盖为对象,后续计算滚动位置时(currentIndex + 1) * width会得到NaN,直接导致滚动位置异常。这段逻辑可以直接删除,页码更新已经在onMomentumScrollEnd绑定的updateCurrentIndex中完成。 - Android兼容配置问题:把
scrollEventThrottle的值从400改成16,匹配60fps的屏幕刷新频率;将onScroll动画的useNativeDriver设为true,避免JS线程拥堵导致动画卡顿;去掉contentContainerStyle中的flexGrow: 1,Android下该属性会导致水平滚动容器高度计算异常;翻页方法中增加空值判断和边界判断,避免组件未挂载或页码越界时调用方法报错。
修改后的核心代码参考:
// Animated.ScrollView属性修正 <Animated.ScrollView ref={scrollViewRef} bounces={false} horizontal={true} style={{flex: 1}} contentContainerStyle={{ width: width * pageNumber, flexDirection: 'row', }} disableIntervalMomentum={false} decelerationRate={'normal'} pagingEnabled onMomentumScrollEnd={updateCurrentIndex} onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], { useNativeDriver: true, })} showsHorizontalScrollIndicator={false} scrollEnabled={false} scrollEventThrottle={16}> {children} </Animated.ScrollView>
// 翻页方法修正 const onNextPageHandler = () => { if (!scrollViewRef.current || currentIndex >= 2) return; const nextIndex = currentIndex + 1; setCurrentIndex(nextIndex); setTitle(headerText[nextIndex]); scrollViewRef.current.scrollTo({ x: nextIndex * width, y: 0, animated: true, }); }; const onPreviousPageHandler = () => { if (!scrollViewRef.current || currentIndex <= 0) return; const prevIndex = currentIndex - 1; setCurrentIndex(prevIndex); setTitle(headerText[prevIndex]); scrollViewRef.current.scrollTo({ x: prevIndex * width, y: 0, animated: true, }); };
内容的提问来源于stack exchange,提问作者jalaliyev
相关产品推荐
相关产品推荐

