基于react native的自定义轮播图

基于react native的自定义轮播图

效果示例图

示例代码

复制代码
import React, {useEffect, useRef, useState} from 'react';
import {
  Animated,
  PanResponder,
  StyleSheet,
  Text,
  View,
  Dimensions,
} from 'react-native';
import {pxToPd} from '../../common/js/device';

const TestSwiper = () => {
  //动态获取的值
  const [tempList, setTempList] = useState([
    {id: 1},
    {id: 2},
    {id: 3},
    {id: 4},
  ]);

  const [swiperList, setSwiperList] = useState([]);
  const swiperListRef = useRef([]);
  //定时器手柄
  const intervalHandleRef = useRef(null);
  //手势滑动区域节点
  const animatedViewRef = useRef(null);
  //单个切换页面的宽度
  const deviceWidth = Dimensions.get('window').width;
  // 默认显示下标的页面
  let currentIndexRef = useRef(0);
  const panResponderEnabled = useRef(true);

  //滑动的距离
  const defaultMove = -currentIndexRef.current * deviceWidth;
  const pan = useRef(new Animated.Value(defaultMove)).current;

  //手势操作
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderGrant: () => {
        clearInterval(intervalHandleRef.current); // 暂停自动轮播
        panResponderEnabled.current = true;
      },
      //处理手势移动事件,其中使用了`dx`参数来表示在x轴上的移动距离
      onPanResponderMove: (evt, gestureState) => {
        //获取当前滚动区域有几个孩子节点
        const count = animatedViewRef.current._children.length;
        //每次移动的距离
        const moveX = -currentIndexRef.current * deviceWidth;
        //当移动到最左侧或者最右侧时,禁止拖动
        const start = currentIndexRef.current == 0 && gestureState.dx > 0;
        const end = currentIndexRef.current == count - 1 && gestureState.dx < 0;
        if (start || end) {
          // 禁止继续拖动
          return false;
        }
        pan.setValue(moveX + gestureState.dx);
        if (panResponderEnabled.current) {
          panResponderEnabled.current = false; // 防止多次暂停自动轮播
          clearInterval(intervalHandleRef.current); // 暂停自动轮播
        }
      },
      //处理手势释放时的逻辑
      onPanResponderRelease: (_, gestureState) => {
        //获取当前滚动区域有几个孩子节点
        const count = animatedViewRef.current._children.length;
        //当手指拖动区域大于100的时候,开始切换页面
        if (Math.abs(gestureState.dx) > 100) {
          let newPageIndex = currentIndexRef.current;
          if (gestureState.dx > 0) {
            newPageIndex = Math.max(0, currentIndexRef.current - 1);
          } else {
            newPageIndex = Math.min(count - 1, currentIndexRef.current + 1);
          }
          const moveX = -newPageIndex * deviceWidth;
          currentIndexRef.current = newPageIndex;

          Animated.timing(pan, {
            toValue: moveX,
            duration: 300,
            useNativeDriver: true,
          }).start(() => {
            if (newPageIndex == count - 1) {
              currentIndexRef.current = 0;
              pan.setValue(0);
            }
            autoPlayAPI(); // 继续自动轮播
          });
        } else {
          pan.setValue(-currentIndexRef.current * deviceWidth);
        }
        if (!panResponderEnabled.current) {
          autoPlayAPI(); // 继续自动轮播
        }
      },
    }),
  ).current;

  //自动轮播
  const autoPlayAPI = () => {
    const max = swiperListRef.current.length - 1;
    if (intervalHandleRef.current) {
      clearInterval(intervalHandleRef.current);
    }
    intervalHandleRef.current = setInterval(() => {
      let newPageIndex = 0;
      if (currentIndexRef.current == max) {
        newPageIndex = 0;
      } else {
        newPageIndex = currentIndexRef.current + 1;
      }
      const moveX = -newPageIndex * deviceWidth;
      currentIndexRef.current = newPageIndex;
      Animated.timing(pan, {
        toValue: moveX,
        duration: 300,
        useNativeDriver: true,
      }).start(() => {
        if (newPageIndex == max) {
          currentIndexRef.current = 0;
          pan.setValue(0);
          autoPlayAPI();
        }
      });
    }, 3000);
  };

  //初始化
  const initFunction = () => {
    let tempArr = [...tempList];
    let firstArr = tempArr[0];
    let contactArr = tempArr.concat(firstArr);
    swiperListRef.current = contactArr;
    setSwiperList(() => contactArr);
    autoPlayAPI();
  };

  useEffect(() => {
    initFunction();

    return () => {
      clearInterval(intervalHandleRef.current);
    };
  }, []);
  return (
    <>
      <View style={styles.swiperWrap}>
        <Animated.View
          ref={animatedViewRef}
          style={{
            width: deviceWidth * swiperList.length,
            flex: 1,
            flexDirection: 'row',
            transform: [{translateX: pan}],
            onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
          }}
          {...panResponder.panHandlers}>
          {swiperList.map((item, index) => (
            <View key={'swiperItem' + index} style={{width: deviceWidth}}>
              <View style={styles.swiperItem}>
                <Text>item {item.id}</Text>
              </View>
            </View>
          ))}
        </Animated.View>
      </View>
    </>
  );
};

const styles = StyleSheet.create({
  swiperWrap: {
    borderColor: 'red',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    width: '100%',
    height: pxToPd(400),
  },
  swiperItem: {
    borderColor: 'red',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    borderRadius: pxToPd(12),
    width: '93.6%',
    marginLeft: '3.2%',
    height: '100%',
  },
});

export default TestSwiper;
相关推荐
福兮说3 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
落魄大学生之流水线上谋生计4 小时前
Remotion:用 React 写视频,让代码成为剪辑师
react.js
今年下半年6 小时前
【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)
前端·javascript·vue.js
可乐鸡翅yeah_8 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
kyriewen8 小时前
我实测了前端日期的 5 个坑:差 8 小时只是开始
前端·javascript·程序员
anew___8 小时前
《从零手写操作系统 (11):文件系统初探——initrd与VFS抽象层》
java·javascript·算法
今年下半年9 小时前
【VUE3】移动端:使用腾讯地图显示区域统计与放大显示资产点位
javascript·前端框架·vue·地图
a努力。9 小时前
LangChain Agent消息链路全解析
java·前端·javascript
今年下半年9 小时前
【前端】ant-design-vue 表格「行、列都动态」的处理方案
前端·javascript·vue.js
默_笙10 小时前
🚕 缺料就出门买:给 RAG 装上"信息够不够"的判断力
前端·javascript