react-native实现多列表左右滑动+滚动TabBar悬停

react-native实现多列表左右滑动+滚动TabBar悬停

用到的组件

rn版本:0.70.6

"react-native-tab-view": "3.5.2",

页面效果

初始状态:

滚动后:

左滑动到右边列表状态:

实现思路

使用react-native-tab-view实现左右切换,但是不能使用renderTabBar,因为tabBar会始终显示在列表顶部,不能跟着列表进行滚动,顶部蓝色区域+红色tabbar总高度400(tabbar高度50),然后通过Animated动画+FlatList的onScroll设置动画的值,让顶部区域网屏幕外移出或移入,当滚动超过350(总高度400-tabbar高度50)就将已经准备好的另一个tabbar显示出来,这样就可以实现这样的滚动悬停效果

完整实现代码,可直接使用,需要集成前置组件react-native-tab-view

javascript 复制代码
import React, {useRef, useState, useCallback} from 'react';
import {View, Animated, Easing, Dimensions, Text} from 'react-native';
import {TabView, SceneMap, TabBar} from 'react-native-tab-view';

const {width} = Dimensions.get('window');

const ChatFlatList = () => {
  const [tabId, setTabId] = useState(0);
  const [routes] = useState([
    {key: 'tab1', title: 'Tab 1'},
    {key: 'tab2', title: 'Tab 2'},
  ]);
  const [sy1, setScrollY1] = useState(0);
  const [sy2, setScrollY2] = useState(0);
  const listRef1 = useRef<any>(null);
  const listRef2 = useRef<any>(null);
  const [tabBarVisible, setTabBarVisible] = useState(false);

  const scrollY = useRef(new Animated.Value(0)).current;

  // 顶部 View 的 translateY
  const headerTranslateY = scrollY.interpolate({
    inputRange: [0, 400],
    outputRange: [0, -400],
    extrapolate: 'clamp',
  });

  const tabViewMarginTop = scrollY.interpolate({
    inputRange: [0, 400],
    outputRange: [400, 0], // 当 scrollY=0 时 marginTop=400;当 scrollY=400 时 marginTop=0
    extrapolate: 'clamp',
  });

  const renderTabContent = useCallback(
    (data: any, tid: number) => (
      <Animated.FlatList
        ref={tid == 1 ? listRef1 : listRef2}
        data={data}
        onScroll={Animated.event(
          [{nativeEvent: {contentOffset: {y: scrollY}}}],
          {
            useNativeDriver: true,
            // ✅ 安全地监听滚动,不影响性能
            listener: (event: any) => {
              const y = event.nativeEvent.contentOffset.y;
              setTabBarVisible(y >= 350);
              if (tid == 1) {
                setScrollY1(y);
              } else {
                setScrollY2(y);
              }
            },
          },
        )}
        scrollEventThrottle={16}
        renderItem={({item, index}) => (
          <View
            style={[
              {height: 60, backgroundColor: '#f0f0f0', marginVertical: 2},
              index == 0 && {marginTop: 400},
            ]}>
            <Text>Item {item}</Text>
          </View>
        )}
        keyExtractor={item => item.toString()}
      />
    ),
    [scrollY],
  );

  const renderScene = useCallback(
    SceneMap({
      tab1: () => renderTabContent([...Array(50).keys()], 1),
      tab2: () => renderTabContent([...Array(30).keys()], 2),
    }),
    [],
  );

  return (
    <>
      {/* 顶部可滚动的 View */}
      <Animated.View
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          right: 0,
          height: 400,
          backgroundColor: 'skyblue',
          zIndex: 1,
          transform: [{translateY: headerTranslateY}],
        }}>
        {/* <Text style={{fontSize: 20, marginTop: 200, textAlign: 'center'}}>
          滚动时会上移
        </Text> */}
        <View style={{height: 350, width: '100%'}}></View>
        <View
          style={{height: 50, width: '100%', backgroundColor: 'red'}}></View>
      </Animated.View>
      <View
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          height: 50,
          width: '100%',
          backgroundColor: 'red',
          zIndex: 10,
          opacity: tabBarVisible ? 1 : 0,
        }}></View>

      {/* TabView */}
      <TabView
        navigationState={{index: tabId, routes}}
        renderScene={renderScene}
        onIndexChange={(index: number) => {
          console.log('index', index, 'sy1', sy1, 'sy2', sy2);
          //切换到当前第一个列表
          if (index == 0) {
            //如果第二个列表滚动位置小于400,滚动到第二个列表的位置
            if (sy2 < 400) {
              listRef1?.current?.scrollToOffset({offset: sy2, animated: false});
            } else if (sy1 < 400) {
              //第二个列表滚动超过400,需要判断第一个列表滚动位置是否小于400,如果小于400则滚动到400,否则不动
              listRef1?.current?.scrollToOffset({offset: 400, animated: false});
            }
          } else if (index == 1) {
            //切换到当前第二个列表
            //如果第一个列表滚动位置小于400,滚动到第一个列表的位置
            if (sy1 < 400) {
              listRef2?.current?.scrollToOffset({offset: sy1, animated: false});
            } else if (sy2 < 400) {
              //第一个列表滚动超过400,需要判断第二个列表滚动位置是否小于400,如果小于400则滚动到400,否则不动
              listRef2?.current?.scrollToOffset({offset: 400, animated: false});
            }
          }
          setTabId(index);
        }}
        initialLayout={{width}}
        renderTabBar={props => null}
      />
    </>
  );
};

export default ChatFlatList;
相关推荐
King禅5 分钟前
JavaScript 调试实战指南:从 console.log 到 DevTools 深度运用
javascript
全栈项目管理程序猿10 分钟前
ArcGIS JS 基础教程(28):图层渲染顺序管理
前端·javascript
扶风ff31 分钟前
练题簿在线免费刷题:创建个人任务,把刷题、听题和模考目标放到每天的学习中
开发语言·javascript·学习·小程序
asong42 分钟前
没有源代码,AI 也能拆解软件:这个开源项目已有 3.7 万 Star
javascript·后端·github
用户17335980753743 分钟前
纯前端 PDF 压平避坑指南:压平后表单字段变了?
前端·javascript·vue.js
用户298698530141 小时前
React 中实现 Word 与 Markdown 双向转换的实践记录
javascript·react.js·markdown
OpsEye2 小时前
大模型会话里的手机号、身份证,怎么自动脱敏?
javascript·ai编程
爱喝水的小周2 小时前
实验十 Vue‑Router 王者荣耀战绩查询 APP 开发实践
前端·javascript·vue.js
yexianglunbai2 小时前
Vue 3 全面详解:从核心特性到工程化实践
前端·javascript·vue.js
孟陬2 小时前
JavaScript 反引号(` backtick)和双引号的不完全等价 ≠ 场景
javascript·node.js·bun