如何在用户滚动时禁用react-native-reanimated 2的日历组件动画
解决ScrollView滚动触发日历切换动画的问题
核心思路是仅在周/月视图切换阶段启用动画,滚动时完全禁用,通过父组件控制动画开关状态,子组件根据状态决定是否加载动画配置。
步骤1:父组件维护动画开关状态
在父组件中新增状态跟踪动画是否启用,同时监听ScrollView滚动事件,滚动时立即禁用动画;切换视图时临时启用动画,待动画周期结束后自动关闭。
import { useState } from 'react'; import { ScrollView, Button } from 'react-native'; const CalendarContainer = () => { const [mode, setMode] = useState('week'); // 控制动画是否启用的状态 const [isAnimationEnabled, setIsAnimationEnabled] = useState(false); // 跟踪是否正在滚动 const [isScrolling, setIsScrolling] = useState(false); // 周/月视图切换逻辑 const toggleCalendarMode = () => { // 切换时启用动画 setIsAnimationEnabled(true); setMode(prev => prev === 'week' ? 'month' : 'week'); // 根据最长动画时长(这里取500ms),动画结束后禁用 setTimeout(() => setIsAnimationEnabled(false), 500); }; // 滚动开始时禁用动画 const handleScrollStart = () => { setIsScrolling(true); setIsAnimationEnabled(false); }; // 滚动结束后保持禁用状态 const handleScrollEnd = () => { setIsScrolling(false); }; const renderItem = (dayItem) => { const itemRepresents = mode === 'month' ? ITEM_REPRESENTS.MONTH : ITEM_REPRESENTS.WEEK; // key包含mode,确保切换视图时组件重新挂载,触发入场动画 const itemKey = `${mode}_day_${dayItem.id}`; return ( <MiniCalendarItem animationsEnabled={isAnimationEnabled && !isScrolling} key={itemKey} mode={mode} itemRepresents={itemRepresents} day={dayItem} weekIndex={dayItem.weekIndex} /> ); }; return ( <> <Button onPress={toggleCalendarMode} title="切换周/月视图" /> <ScrollView onScroll={handleScrollStart} onScrollEndDrag={handleScrollEnd} onMomentumScrollEnd={handleScrollEnd} > {yourDayData.map(renderItem)} </ScrollView> </> ); };
步骤2:子组件根据状态条件加载动画
在MiniCalendarItem中,仅当animationsEnabled为true时才生成动画配置,否则传入undefined(Reanimated会自动跳过动画播放)。
import Animated, { FadeInDown, FadeInUp, SlideOutUp, SlideOutDown, } from 'react-native-reanimated'; const MiniCalendarItem = ({ animationsEnabled, itemRepresents, day, weekIndex }) => { let animationEnter; let animationExit; // 仅在动画启用时生成动画配置 if (animationsEnabled) { if (itemRepresents === ITEM_REPRESENTS.MONTH) { animationEnter = dayIsPartOfCurrentWeek(day) ? FadeInUp : FadeInUp.delay(weekIndex * 150).duration(350); animationExit = SlideOutDown.duration(400); } else { animationEnter = FadeInDown.duration(500); animationExit = SlideOutUp.duration(400); } } else { // 禁用动画,传入undefined跳过播放 animationEnter = undefined; animationExit = undefined; } // 注意:dayIsPartOfCurrentWeek需要根据你的业务逻辑实现 function dayIsPartOfCurrentWeek(day) { // 你的判断逻辑 return true; } return ( <Animated.View entering={animationEnter} exiting={animationExit} > {/* 日期内容渲染 */} <Text>{day.date}</Text> </Animated.View> ); };
关键细节说明
- key值包含mode:确保切换视图时,React会重新渲染所有日历项,触发入场动画;滚动时复用组件但动画已禁用,不会意外播放。
- 滚动事件监听:覆盖
onScroll、onScrollEndDrag、onMomentumScrollEnd三个事件,确保滚动过程中始终禁用动画。 - 动画自动关闭:通过
setTimeout根据最长动画时长设置自动关闭,避免动画开关一直处于开启状态。
内容的提问来源于stack exchange,提问作者SudoPlz
相关产品推荐
相关产品推荐

