我们日常开发的时候,经常会遇到上拉加载更多的需求,原因很简单,数据量大的时候分批次加载可提高加载效率,为用户省点流量和时间。
最近在使用RN0.35实现上拉加载时,遇到了一些坑,总结之后决定分享给大家。希望能给正在使用低版本RN的你提供一些帮助。
ListView实现上拉加载
1. 实现简单的上拉加载
ListView中提供了onEndReached
方法,触发时机为当所有行都已渲染且列表已滚动到底部。如此说来,在ListView中实现上拉加载直接使用onEndReached
就可以完成,如下(在loadMoreData中完成上拉加载功能):
<ListView onEndReached={this.loadMoreData}/>
2. 坑来了【划重点】
上述过程已经实现了上拉加载,但你会发现存在一个问题:当列表内容小于列表高度时,onEndReached
被频繁触发,为什么呢?我们来一起看下RN的源码(ListView.js中的_getDistanceFromEnd
和_maybeCallOnEndReached
方法):
_maybeCallOnEndReached: function(event?: Object) {
if(... // 省略
&& this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold
... // 省略) {
this.props.onEndReached(event);
}
},
_getDistanceFromEnd:function _getDistanceFromEnd(scrollProperties){
return scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset;
},
从上述代码中,我们提取到,onEndReached
被触发的重要条件是:
contentLength - visibleLength - offset < onEndReachedThreshold
其中contentLength
是列表内容的长度,visibleLength
是列表的高,offset
是滑动的距离,onEndReachedThreshold
是默认值为1000的可控常量。回到之前的问题,当列表内容小于列表高度时(contentLength < visibleLength),offset一定是0(因为这会儿滑不动。。)。也就是此时说onEndReached
被触发的条件是:
- onEndReachedThreshold为负数,小于0且大于contentLength - visibleLength
那么onEndReachedThreshold
的含义是什么?可以设置为负数吗?
答案是当然不行!设置onEndReachedThreshold
的原因是在我们实现上拉加载的时候,如果到了底部再加载,用户看起来会比较慢,设置onEndReachedThreshold
可以使得onEndReached
提前触发,(距离列表底部onEndReachedThreshold
pixel时触发。可自行画图理解其中关系)
到这你可能已经明白了,我们只需要控制仅当内容大于或等于列表高度时(contentLength >= visibleLength),触发上拉加载,如下:
handleEndReached = () => {
const { contentLength, visibleLength } = this.listView.scrollProperties;
if (contentLength >= visibleLength) {
this.loadMoreData();
}
}
<ListView
ref={(component) => { this.listView = component; }}
onEndReached={this.handleEndReached}
onEndReachedThreshold={20}
/>
ScrollView实现上拉加载
细心的你一定已经发现,ScrollView并没有提供onEndReached
方法,因为ScrollView是不会限制长度的,内容是无限长的。如果因为业务原因必须使用ScrollView,又需要上拉加载,怎么办呢?
ScrollView提供了onScrollEndDrag
方法,onScrollEndDrag
会在每次结束滚动后触发,我们要做的就是,在这个时机中,判断是否滑动到了底部,如果到了底部,则上拉加载。方法和ListView类似,我们依然可以找到内容高度,列表高度,以及滑动的距离来判断是否需要加载。如下:
handleScrollEnd = (event) => {
const contentHeight = event.nativeEvent.contentSize.height;
const scrollViewHeight = event.nativeEvent.layoutMeasurement.height;
const scrollOffset = event.nativeEvent.contentOffset.y;
const isEndReached = scrollOffset + scrollViewHeight >= contentHeight; // 是否滑动到底部
const isContentFillPage = contentHeight >= scrollViewHeight; // 内容高度是否大于列表高度
if (isContentFillPage && isEndReached) {
this.loadMoreData();
}
};
<ScrollView
onScrollEndDrag={this.handleScrollEnd}
/>
如果你有别的问题或对本文存在疑问,评论区等你哈