React Native Banner手动滚动与自动滚动冲突问题求助
问题
手动滚动Banner到指定位置后,自动滚动会强制切回之前的索引(比如滑到第3个Banner,自动滚动会跳回第1个),交互表现不符合预期。核心需求是获取手动滚动后的当前活跃slide索引,以此修正自动滚动逻辑。
解决方案
关键改动点
- 新增状态跟踪当前显示的Banner索引
- 监听ScrollView滚动事件,通过滚动位置计算当前索引
- 手动滚动时清除原有定时器,滚动结束后重启自动滚动
- 自动滚动逻辑基于当前索引推进,而非固定初始值
修改后的完整代码
import React, { memo, useEffect, useMemo, useRef, useState, useCallback, } from 'react'; import {View, StyleSheet, ScrollView, Dimensions} from 'react-native'; import Item from './Item'; import {clientQuery} from 'apolloClient/index'; import KEYS from 'apolloClient/constants'; import {useIsFocused} from '@react-navigation/native'; import {useApp} from 'provider/Main'; const screen = Dimensions.get('screen'); type TBannerProps = { GET_KEY: (typeof KEYS)[keyof typeof KEYS]; }; const Index = (props: TBannerProps) => { const {updateValue} = useApp(); const isFocused = useIsFocused(); const scrollRef: any = useRef(null); const {GET_KEY} = props; const [result, setResult] = useState([]); // 跟踪当前显示的Banner索引 const [currentIndex, setCurrentIndex] = useState(0); // 统一管理自动滚动定时器 const autoScrollTimerRef = useRef<NodeJS.Timeout | null>(null); const fetchBanner = useCallback(async () => { updateValue('showLoadingModel', true); const {data, error} = await clientQuery(GET_KEY); if (!error) { if (data?.getBannerSettingList?.length) { setResult(data.getBannerSettingList); setCurrentIndex(0); } } updateValue('showLoadingModel', false); }, [GET_KEY, updateValue]); useEffect(() => { if (isFocused && !result.length) { fetchBanner(); } }, [fetchBanner, isFocused, result]); // 重构自动滚动逻辑,基于当前索引推进 const startAutoScroll = useCallback(() => { if (autoScrollTimerRef.current) { clearTimeout(autoScrollTimerRef.current); } autoScrollTimerRef.current = setTimeout(() => { const nextIndex = currentIndex >= result.length - 1 ? 0 : currentIndex + 1; setCurrentIndex(nextIndex); scrollRef?.current?.scrollTo({ x: (screen.width - 47) * nextIndex, animated: true, }); startAutoScroll(); }, 4000); }, [currentIndex, result.length]); useEffect(() => { if (result.length) { startAutoScroll(); } return () => { if (autoScrollTimerRef.current) { clearTimeout(autoScrollTimerRef.current); } }; }, [result.length, startAutoScroll]); // 监听滚动事件,计算当前索引 const handleScroll = useCallback((event: any) => { const scrollX = event.nativeEvent.contentOffset.x; const index = Math.round(scrollX / (screen.width - 47)); const validIndex = Math.max(0, Math.min(index, result.length - 1)); setCurrentIndex(validIndex); }, [result.length]); // 手动滚动结束后重启自动滚动 const handleScrollEnd = useCallback(() => { startAutoScroll(); }, [startAutoScroll]); const scrollBlock = useMemo( () => ( <ScrollView ref={scrollRef} style={page.scrollView} horizontal={true} showsHorizontalScrollIndicator={false} pagingEnabled={true} pinchGestureEnabled={true} removeClippedSubviews={true} automaticallyAdjustContentInsets={true} onScroll={handleScroll} onMomentumScrollEnd={handleScrollEnd} onScrollEndDrag={handleScrollEnd}> <View style={page.myElement}> {result.map((item: any) => { if (item?.category?.banner) { return ( <Item key={item.id} name={item.category.name} banner={item.category.banner['banner-1']} /> ); } })} </View> </ScrollView> ), [result, handleScroll, handleScrollEnd] ); if (!result.length) { return null; } return scrollBlock; }; const page = StyleSheet.create({ scrollView: { marginVertical: 10, marginTop: 15, }, myElement: { display: 'flex', flexDirection: 'row', }, }); export default memo(Index);
改动说明
- 状态管理:新增
currentIndex保存当前Banner索引,autoScrollTimerRef统一管理定时器,避免内存泄漏。 - 滚动监听:通过
onScroll计算滚动位置对应的索引,onMomentumScrollEnd和onScrollEndDrag确保手动滚动完成后重启自动滚动。 - 逻辑重构:自动滚动基于
currentIndex计算下一个位置,彻底解决固定初始值导致的跳转问题。 - 边界处理:计算索引时加入范围校验,避免索引超出数组有效范围。
内容的提问来源于stack exchange,提问作者Harsh nahta
相关产品推荐
相关产品推荐

