React Native Reanimated 3:点击按钮停止ScrollView滚动问题求助
问题分析
你当前代码的核心问题有两个:
useAnimatedReaction的硬编码滚动:每次scrollY变化时都会强制调用scrollTo(scrollRef, 0, 1000, true),这会直接触发滚动到底部的动画,完全覆盖了其他滚动操作。- 错误的停止逻辑:
cancelAnimation(scrollY)只能终止scrollY自身的动画,但无法中断scrollTo已经发起的滚动动画,也没有控制自动滚动的开关状态。
解决方案
我们需要新增一个状态变量控制自动滚动的启停,同时用Reanimated的动画API实现带条件的循环滚动,确保能随时终止动画。以下是修正后的代码:
import React from 'react'; import { Button, StyleSheet, View } from 'react-native'; import Animated, { scrollTo, useSharedValue, useAnimatedScrollHandler, useAnimatedReaction, useAnimatedRef, withRepeat, withSequence, withTiming, cancelAnimation, runOnJS, } from 'react-native-reanimated'; const AnimatedScrollView = () => { const scrollY = useSharedValue(0); const scrollRef = useAnimatedRef(); // 控制自动滚动的开关 const isAutoScrolling = useSharedValue(false); // 自动滚动方向:1向下,-1向上 const scrollDirection = useSharedValue(1); const scrollHandler = useAnimatedScrollHandler({ onScroll: ({ contentOffset: { y } }) => { scrollY.value = y; }, }); const startScroll = (direction = 1) => { isAutoScrolling.value = true; scrollDirection.value = direction; // 启动循环滚动动画 scrollY.value = withRepeat( withSequence( withTiming(scrollY.value + direction * 500, { duration: 1000 }), () => { // 滚动后更新ScrollView位置 scrollTo(scrollRef, 0, scrollY.value, false); // 检查是否到达边界(这里简化处理,实际需根据内容高度计算) const contentHeight = 1800; // 6个300px容器的总高度 const screenHeight = 600; // 假设屏幕高度,实际需用Dimensions获取 const isAtBottom = scrollY.value >= contentHeight - screenHeight; const isAtTop = scrollY.value <= 0; if (isAtBottom || isAtTop) { runOnJS(stopScroll)(); } } ), -1, // 无限重复 false ); }; const stopScroll = () => { isAutoScrolling.value = false; cancelAnimation(scrollY); }; // 监听自动滚动状态,确保状态变化时终止动画 useAnimatedReaction( () => isAutoScrolling.value, (isScrolling) => { if (!isScrolling) { cancelAnimation(scrollY); } } ); return ( <> <Button title="向下滚动" onPress={() => startScroll(1)} /> <Button title="向上滚动" onPress={() => startScroll(-1)} /> <Button title="停止滚动" onPress={stopScroll} /> <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16} ref={scrollRef} scrollEnabled={!isAutoScrolling.value} // 自动滚动时禁用手动滚动(可选) > {[...Array(6)].map((_, index) => ( <View key={index} style={[ styles.container, { backgroundColor: ['pink', 'blue', 'red'][index % 3] }, ]} /> ))} </Animated.ScrollView> </> ); }; export default AnimatedScrollView; const styles = StyleSheet.create({ container: { height: 300, width: '100%', }, });
关键改进点
isAutoScrolling开关:用这个状态变量控制自动滚动的启停,stopScroll时将其设为false,并通过useAnimatedReaction监听状态变化终止动画。- 条件循环滚动:使用
withRepeat实现无限滚动,但在每次滚动后检查是否到达边界,到达则自动停止。 - 方向控制:新增
scrollDirection变量支持上下滚动,适配你拖拽到顶部/底部触发不同方向滚动的需求。 - 修复硬编码滚动:移除原代码中强制滚动到底部的逻辑,改为基于
scrollY的动态偏移。
适配拖拽触发逻辑
针对你“拖拽容器到顶部/底部触发自动滚动”的需求,只需在拖拽结束时判断容器位置:
- 若容器拖拽至页面顶部,调用
startScroll(-1)触发向上滚动 - 若拖拽至页面底部,调用
startScroll(1)触发向下滚动 - 拖拽停止或容器离开边界时,调用
stopScroll()终止动画
内容的提问来源于stack exchange,提问作者Vic Rokx
相关产品推荐
相关产品推荐

