You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native Banner手动滚动与自动滚动冲突问题求助

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);

改动说明

  1. 状态管理:新增currentIndex保存当前Banner索引,autoScrollTimerRef统一管理定时器,避免内存泄漏。
  2. 滚动监听:通过onScroll计算滚动位置对应的索引,onMomentumScrollEnd和onScrollEndDrag确保手动滚动完成后重启自动滚动。
  3. 逻辑重构:自动滚动基于currentIndex计算下一个位置,彻底解决固定初始值导致的跳转问题。
  4. 边界处理:计算索引时加入范围校验,避免索引超出数组有效范围。

内容的提问来源于stack exchange,提问作者Harsh nahta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 00:20:19