基于react native的ScrollView左右页面手势切换,以及每个单页面FlatList列表数据上下滚动,以及可以在Flat List中再次实现轮播效果的左右切换效果

基于react native的ScrollView左右页面手势切换,以及每个单页面FlatList列表数据上下滚动,以及可以在Flat List中再次实现轮播效果的左右切换效果


此效果仅适用Android app,ios不行(需例外写效果)

效果示例图

在这里插入图片描述

示例代码

import React, {useRef, useState} from 'react';
import {
  ScrollView,
  StyleSheet,
  View,
  Dimensions,
  Animated,
  Text,
  PanResponder,
  FlatList,
} from 'react-native';
import {pxToPd} from '../../common/js/device';

const TabOne = () => {
  //手势滑动区域节点
  const animatedViewRef = useRef(null);
  //单个切换页面的宽度
  const deviceWidth = Dimensions.get('window').width;
  // 默认显示下标的页面
  let currentIndexRef = useRef(0);
  //滑动的距离
  const defaultMove = -currentIndexRef.current * deviceWidth;
  const pan = useRef(new Animated.Value(defaultMove)).current;

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => 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;
        }
        if (Math.abs(gestureState.dx) < 50) {
          return false;
        }
        pan.setValue(moveX + gestureState.dx);
      },
      //处理手势释放时的逻辑
      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;
          pan.setValue(moveX);
        } else {
          pan.setValue(-currentIndexRef.current * deviceWidth);
        }
      },
    }),
  ).current;

  return (
    <>
      <View style={styles.pagesWrap}>
        <ScrollView>
          <View style={styles.pagesHeader}>
            <Animated.View
              ref={animatedViewRef}
              style={{
                width: deviceWidth * 4,
                flex: 1,
                flexDirection: 'row',
                transform: [{translateX: pan}],
                onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
              }}
              {...panResponder.panHandlers}>
              <View style={{width: deviceWidth, backgroundColor: 'red'}}>
                <Text>item One</Text>
              </View>
              <View style={{width: deviceWidth}}>
                <Text>item Two</Text>
              </View>
              <View style={{width: deviceWidth, backgroundColor: 'green'}}>
                <Text>item Three</Text>
              </View>
              <View style={{width: deviceWidth}}>
                <Text>item Four</Text>
              </View>
            </Animated.View>
          </View>
          <View style={styles.pagesBody}></View>
          <View style={styles.pagesFooter}></View>
        </ScrollView>
      </View>
    </>
  );
};

const TabTwo = () => {
  const [dataList, setDataList] = useState([
    {
      id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
      title: 'First Item',
    },
    {
      id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
      title: 'Second Item',
    },
    {
      id: '58694a0f-3da1-471f-bd96-145571e29d72',
      title: 'Third Item',
    },
    {
      id: '58694a0f-3da1-471f-bd96-145571e29d73',
      title: 'Four Item',
    },
    {
      id: '58694a0f-3da1-471f-bd96-145571e29d74',
      title: 'Five Item',
    },
    {
      id: '58694a0f-3da1-471f-bd96-145571e29d75',
      title: 'Six Item',
    },
    {
      id: '58694a0f-3da1-471f-bd96-145571e29d76',
      title: 'Seven Item',
    },
    {
      id: '58694a0f-3da1-471f-bd96-145571e29d77',
      title: 'eight Item',
    },
  ]);
  return (
    <>
      <View style={styles.pagesWrap}>
        <View style={styles.pagesHeader}>
          <Text>two</Text>
        </View>
        <FlatList
          data={dataList}
          renderItem={({item}) => (
            <View style={styles.item}>
              <Text style={styles.title}>{item.title}</Text>
            </View>
          )}
          keyExtractor={item => item.id}
          ListHeaderComponent={ListHeaderComponent}
          ListFooterComponent={ListFooterComponent}
        />
      </View>
    </>
  );
};

const ListHeaderComponent = () => {
  //手势滑动区域节点
  const animatedViewRef = useRef(null);
  //单个切换页面的宽度
  const deviceWidth = Dimensions.get('window').width;
  // 默认显示下标的页面
  let currentIndexRef = useRef(0);
  //滑动的距离
  const defaultMove = -currentIndexRef.current * deviceWidth;
  const pan = useRef(new Animated.Value(defaultMove)).current;

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        console.log('[sdf]');
        // 判断是否为滑动事件
        return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
      },
      //处理手势移动事件,其中使用了`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;
        }
        if (Math.abs(gestureState.dx) < 50) {
          return false;
        }
        pan.setValue(moveX + gestureState.dx);
      },
      //处理手势释放时的逻辑
      onPanResponderRelease: (_, gestureState) => {
        if (Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5) {
          // 如果移动距离小于5,则判断为点击事件
          console.log('Click Event', currentIndexRef.current);
        } else {
          // 否则,判断为滑动事件
          console.log('Swipe Event');
        }

        //获取当前滚动区域有几个孩子节点
        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;
          pan.setValue(moveX);
        } else {
          pan.setValue(-currentIndexRef.current * deviceWidth);
        }
      },
    }),
  ).current;

  return (
    <>
      <View style={styles.pagesFooter}>
        <Animated.View
          ref={animatedViewRef}
          style={{
            width: deviceWidth * 4,
            flex: 1,
            flexDirection: 'row',
            transform: [{translateX: pan}],
            onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
          }}
          {...panResponder.panHandlers}>
          <View style={{width: deviceWidth, backgroundColor: 'red'}}>
            <Text>header item One</Text>
          </View>
          <View style={{width: deviceWidth}}>
            <Text>header item Two</Text>
          </View>
          <View style={{width: deviceWidth, backgroundColor: 'green'}}>
            <Text>header item Three</Text>
          </View>
          <View style={{width: deviceWidth}}>
            <Text>header item Four</Text>
          </View>
        </Animated.View>
      </View>
    </>
  );
};

const ListFooterComponent = () => {
  //手势滑动区域节点
  const animatedViewRef = useRef(null);
  //单个切换页面的宽度
  const deviceWidth = Dimensions.get('window').width;
  // 默认显示下标的页面
  let currentIndexRef = useRef(0);
  //滑动的距离
  const defaultMove = -currentIndexRef.current * deviceWidth;
  const pan = useRef(new Animated.Value(defaultMove)).current;

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        console.log('[sdf]');
        // 判断是否为滑动事件
        return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
      },
      //处理手势移动事件,其中使用了`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;
        }
        if (Math.abs(gestureState.dx) < 50) {
          return false;
        }
        pan.setValue(moveX + gestureState.dx);
      },
      //处理手势释放时的逻辑
      onPanResponderRelease: (_, gestureState) => {
        if (Math.abs(gestureState.dx) < 5 && Math.abs(gestureState.dy) < 5) {
          // 如果移动距离小于5,则判断为点击事件
          console.log('Click Event');
        } else {
          // 否则,判断为滑动事件
          console.log('Swipe Event');
        }

        //获取当前滚动区域有几个孩子节点
        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;
          pan.setValue(moveX);
        } else {
          pan.setValue(-currentIndexRef.current * deviceWidth);
        }
      },
    }),
  ).current;

  return (
    <>
      <View style={styles.pagesFooter}>
        <Animated.View
          ref={animatedViewRef}
          style={{
            width: deviceWidth * 4,
            flex: 1,
            flexDirection: 'row',
            transform: [{translateX: pan}],
            onStartShouldSetResponderCapture: () => false, // 禁止拦截触摸事件
          }}
          {...panResponder.panHandlers}>
          <View style={{width: deviceWidth, backgroundColor: 'red'}}>
            <Text>footer item One</Text>
          </View>
          <View style={{width: deviceWidth}}>
            <Text>footer item Two</Text>
          </View>
          <View style={{width: deviceWidth, backgroundColor: 'green'}}>
            <Text>footer item Three</Text>
          </View>
          <View style={{width: deviceWidth}}>
            <Text>footer item Four</Text>
          </View>
        </Animated.View>
      </View>
    </>
  );
};

const TestSectionList = () => {
  const scrollViewRef = useRef(null);
  let currentIndexRef = useRef(0);

  // 滚动视图的水平偏移量
  const scrollX = useState(new Animated.Value(0))[0];

  // 页面切换时的处理函数
  const handlePageChange = event => {
    const offsetX = event.nativeEvent.contentOffset.x;
    const pageIndex = Math.round(offsetX / Dimensions.get('window').width);
    currentIndexRef.current = pageIndex;
  };

  return (
    <>
      <View style={styles.sectionList}>
        <ScrollView
          ref={scrollViewRef}
          horizontal
          pagingEnabled
          bounces={false}
          showsHorizontalScrollIndicator={false}
          onScroll={Animated.event(
            [{nativeEvent: {contentOffset: {x: scrollX}}}],
            {
              useNativeDriver: false,
              listener: handlePageChange,
            },
          )}
          scrollEventThrottle={16}>
          <TabOne />
          <TabTwo />
        </ScrollView>
      </View>
    </>
  );
};

const styles = StyleSheet.create({
  sectionList: {
    borderColor: 'red',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    flex: 1,
  },
  pagesWrap: {
    borderColor: 'blue',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    flex: 1,
    width: Dimensions.get('window').width,
  },
  pagesHeader: {
    borderColor: 'blue',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    width: '100%',
    height: pxToPd(200),
    marginTop: pxToPd(20),
  },
  pagesBody: {
    borderColor: 'blue',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    width: '93.6%',
    marginLeft: '3.2%',
    height: pxToPd(1200),
    marginTop: pxToPd(20),
  },
  pagesFooter: {
    borderColor: 'blue',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    width: '100%',
    height: pxToPd(200),
    marginTop: pxToPd(20),
    marginBottom: pxToPd(20),
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 32,
  },
  pagesItem: {
    borderColor: 'blue',
    borderWidth: pxToPd(1),
    borderStyle: 'solid',
    width: Dimensions.get('window').width,
    backgroundColor: 'blue',
  },
});

export default TestSectionList;

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值