React Native FlatList 滚动触发方法与滚动方向监听实现咨询
实现可行性说明
两类需求完全可落地,基于FlatList原生提供的滚动事件回调能力即可实现,无需引入额外第三方依赖,搭配合理的性能优化配置后可以做到流畅无卡顿。
前置准备
在组件内先定义必要的引用与状态,避免滚动过程中频繁触发重渲染带来的性能问题:
- 使用
useRef存储不会触发UI重渲染的临时值,包括上次滚动偏移量、触顶/触底的防重复触发标记 - 使用普通state存储需要驱动UI变化的值,比如顶部栏的显隐状态
- 配置滚动事件的触发节流参数,避免事件过于频繁造成卡顿
分模块实现逻辑
1. 滚动位置触顶/触底触发逻辑
核心是通过滚动事件回传的三个关键参数做判断:
contentOffset.y:列表当前的垂直滚动偏移距离(距离列表顶部的距离)contentSize.height:列表所有内容的总高度layoutMeasurement.height:列表可视区域的高度
具体判断规则:- 触顶判断:当
contentOffset.y <= 顶部触发阈值(一般设10px容错),且之前没有触发过function1时,执行目标方法,同时把防重复标记设为已触发;当滚动偏移离开顶部范围时,重置防重复标记,避免停在顶部时反复执行方法 - 触底判断:当
contentOffset.y + layoutMeasurement.height >= contentSize.height - 底部触发阈值(一般设20px容错),且之前没有触发过底部自定义方法时,执行对应逻辑,同样配置防重复标记
2. 顶部栏滚动联动显隐逻辑
核心是对比相邻两次滚动的偏移量差值判断滚动方向:
- 记录每次滚动的偏移值,和上一次存储的偏移做差值对比,差值小于5px时不做判断(避免轻微手抖导致的显隐抖动)
- 当当前偏移值 > 上一次存储的偏移值:说明用户向下滚动内容,将顶部栏设为隐藏状态
- 当当前偏移值 < 上一次存储的偏移值:说明用户向上滚动内容,将顶部栏设为显示状态
- 顶部栏可以搭配Transform动画做平滑过渡,避免生硬的显隐跳变
改造后的完整参考代码
import { useRef, useState } from 'react'; import { FlatList, View } from 'react-native'; // 阈值常量 const TOP_TRIGGER_THRESHOLD = 10; // 触顶触发容错阈值 const BOTTOM_TRIGGER_THRESHOLD = 20; // 触底触发容错阈值 const SCROLL_DIRECTION_THRESHOLD = 5; // 滚动方向判断的最小差值 // 自定义方法 const function1 = () => { console.log('触顶执行的逻辑'); }; const bottomCustomFunction = () => { console.log('触底执行的自定义逻辑'); }; const ListPage = ({ data }) => { // 临时引用值(不触发重渲染) const lastScrollOffset = useRef(0); const isTopTriggered = useRef(false); const isBottomTriggered = useRef(false); // UI状态值 const [topBarVisible, setTopBarVisible] = useState(true); // 滚动事件统一处理 const handleScroll = (event) => { const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent; const currentOffsetY = contentOffset.y; // 计算滚动方向差值 const scrollOffsetDiff = currentOffsetY - lastScrollOffset.current; // 触顶触发逻辑 if (currentOffsetY <= TOP_TRIGGER_THRESHOLD) { if (!isTopTriggered.current) { isTopTriggered.current = true; function1(); } } else { // 离开顶部范围后重置触发标记 isTopTriggered.current = false; } // 触底触发逻辑 const isReachBottom = currentOffsetY + layoutMeasurement.height >= contentSize.height - BOTTOM_TRIGGER_THRESHOLD; if (isReachBottom) { if (!isBottomTriggered.current) { isBottomTriggered.current = true; bottomCustomFunction(); } } else { // 离开底部范围后重置触发标记 isBottomTriggered.current = false; } // 顶部栏联动逻辑 // 差值足够大才判断方向,避免抖动 if (Math.abs(scrollOffsetDiff) >= SCROLL_DIRECTION_THRESHOLD) { if (scrollOffsetDiff > 0 && topBarVisible) { // 向下滚动,隐藏顶部栏 setTopBarVisible(false); } else if (scrollOffsetDiff < 0 && !topBarVisible) { // 向上滚动,显示顶部栏 setTopBarVisible(true); } } // 更新上一次的偏移值 lastScrollOffset.current = currentOffsetY; }; return ( <View style={{ flex: 1 }}> {/* 顶部栏根据topBarVisible控制显隐即可 */} {/* <View style={{ height: 60, backgroundColor: '#fff' }}>页面顶部栏</View> */} <FlatList data={data} keyExtractor={(key) => key.id} numColumns={2} // 关键配置:设置滚动事件节流,单位ms,16ms对应60帧流畅触发 scrollEventThrottle={16} onScroll={handleScroll} renderItem={({ item, index }) => { return ( <View style={{ alignItems: 'center', justifyContent: 'center', flex: 1, marginTop: index == 0 || 1 ? 33 : 13, paddingBottom: 20, }} > {selectCard(item)} </View> ); }} /> </View> ); };
常见踩坑提醒
- 双列布局的FlatList内容高度计算和单列无差异,触底判断逻辑不需要特殊适配
- 滚动过程中的临时值(偏移量、触发标记)必须用
useRef存储,不要存在useState里,否则每16ms就触发一次重渲染,会导致列表滚动卡顿 - 必须配置
scrollEventThrottle,默认值是0,会导致滚动事件触发过于滞后,显隐联动不跟手 - 所有触发阈值不要设为完全的0值/满高度值,留10-20px的容错可以避免滚动惯性带来的抖动、重复触发问题
内容的提问来源于stack exchange,提问作者rnn
相关产品推荐
相关产品推荐

