Android RecyclerView预加载实战演练
一、概述
由于项目需要要对主页列表执行预加载操作,也就是列表可以一直滑动并且让用户感知不到数据在加载(ps:弱网环境还是可以感知到)
给大家分享一下原理:
1.在RecyclerView滑动过程中发现快到底部了就执行网络加载数据
2.加载完成不能立马更新列表,需要等recyclerView滑动停止再更新数据(ps:滑动过程中更新列表会出现卡顿和速滑现象)
3.更新数据位置a.滑动停止数据还没加载好,b.滑动还没停止数据已加载好
二、解决问题步骤
1.网上现成的方案【地址】
这个方案有个问题,如果是单独的RecyclerView这样处理会收获不错的效果,但是如果和NestedScrollView结合使用,此方案不生效
2.github上现成的一个方案【地址】
这个方案再RecyclerView+NestedScrollView的情况下依然不生效
3.采用方案
值得一提的是:上述方案一、和方案二即使能生效,在滑动过程中也需要做平滑处理
下面是目前采用的方案:
1.改造方案2的LoadMoreModule,让其即支持单独的RecyclerView也支持RecyclerView+NestedScrollView,threshold是一个阀值,即距离底部还要多少的时候执行预加载,这个阀值也可以根据情况任意设置如:百分比、列表个数等等,下面是核心代码
public void onAttachedToNestedScrollView(final NestedScrollView scrollView, RecyclerView recyclerView) { scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { @Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (scrollY > oldScrollY) {//向下滚动 int threshold = v.getChildAt(0).getMeasuredHeight() - scrollY - v.getMeasuredHeight(); KLog.e("checkPreload:" + threshold); if (threshold < 1500) { KLog.e("checkPreload:已经达到预定阀值,执行加载更多任务"); if (null != mLoadMoreListener && !mIsLoadingMore) { mIsLoadingMore = true; mLoadMoreListener.onLoadMore(LoadMoreModule.this); } } } } }); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) {//当滑动停止,更新列表 isIdle = true; if (null != mLoadMoreListener) { mLoadMoreListener.onRefreshData(); } }else{ isIdle = false; } } }); }
2.当滑动停止onRefreshData方法刷新数据,但是有可能会刷不到,但是不要紧,第三步会完善
//预加载 loadMoreModule = LoadMoreModule(object : LoadMoreModule.OnLoadMoreListener { override fun onLoadMore(loadMoreModule: LoadMoreModule?) { KLog.e("checkPreload:滑动到指定标记位了") nsvCircleRec.stopNestedScroll() loadMoreData() MainHandler.getInstance().postDelayed({ loadMoreModule?.finishLoad()//停止标记 }, 3000) } override fun onRefreshData() {//当滑动停止时刷新数据 KLog.e("checkPreload:滑动已停止,刷新数据") loadMoreRefreshData() } }).apply { onAttachedToNestedScrollView(nsvCircleRec, recycleView) }
3.当数据加载完成,且滑动停止的时候在此刷新数据,ps:防止步骤2中没有刷到的情况
4.完成工具类如下:
import androidx.annotation.NonNull;
import androidx.core.widget.NestedScrollView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.weidu.weidulibrary.klog.KLog;
/**
* Created by march on 16/6/8.
* 预加载列表更多
* ps:不能嵌套NestedScrollView 否则不会工作
*/
public class LoadMoreModule {
private OnLoadMoreListener mLoadMoreListener;
private boolean mIsLoadingMore;//代表什么时候让其停止
private int preLoadNum = 0;//表示提前多少个Item触发预加载,未到达底部时,距离底部preLoadNum个Item开始加载
private boolean isEnding = false;
private float mPer = 0.6f;//ScrollView滑动时的加载时机,根据百分比算
private boolean isIdle = true;//列表是否是滚动状态,默认不滚动
public LoadMoreModule(int preLoadNum, OnLoadMoreListener mLoadMoreListener) {
this.preLoadNum = preLoadNum;
this.mLoadMoreListener = mLoadMoreListener;
}
public LoadMoreModule(float per, OnLoadMoreListener mLoadMoreListener) {
this.mPer = per;
this.mLoadMoreListener = mLoadMoreListener;
}
public LoadMoreModule(OnLoadMoreListener mLoadMoreListener) {
this.mLoadMoreListener = mLoadMoreListener;
}
public void onAttachedToNestedScrollView(final NestedScrollView scrollView, RecyclerView recyclerView) {
scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY > oldScrollY) {//向下滚动
int threshold = v.getChildAt(0).getMeasuredHeight() - scrollY - v.getMeasuredHeight();
KLog.e("checkPreload:" + threshold);
if (threshold < 1500) {
KLog.e("checkPreload:已经达到预定阀值,执行加载更多任务");
if (null != mLoadMoreListener && !mIsLoadingMore) {
mIsLoadingMore = true;
mLoadMoreListener.onLoadMore(LoadMoreModule.this);
}
}
}
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {//当滑动停止,更新列表
isIdle = true;
if (null != mLoadMoreListener) {
mLoadMoreListener.onRefreshData();
}
}else{
isIdle = false;
}
}
});
}
public void onAttachedToRecyclerView(final RecyclerView mRecyclerView, final RecyclerView.Adapter mAttachAdapter) {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && isEnding) {
if (null != mLoadMoreListener && !mIsLoadingMore) {
mIsLoadingMore = true;
mLoadMoreListener.onLoadMore(LoadMoreModule.this);
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (null != mLoadMoreListener && dy > 0) {
int lastVisiblePosition = getLastVisiblePosition(mRecyclerView);
isEnding = lastVisiblePosition + 1 + preLoadNum >= mAttachAdapter.getItemCount();
KLog.e("checkPreload-onScrolled:" + lastVisiblePosition + 1 + preLoadNum, "," + mAttachAdapter.getItemCount() + "," + preLoadNum + "," + isEnding);
}
}
});
}
/**
* 获取最后一条展示的位置
*
* @return pos
*/
private int getLastVisiblePosition(RecyclerView mRecyclerView) {
int position;
RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
position = ((GridLayoutManager) manager).findLastVisibleItemPosition();
} else if (manager instanceof LinearLayoutManager) {
position = ((LinearLayoutManager) manager).findLastVisibleItemPosition();
} else if (manager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager;
int[] lastPositions = layoutManager.findLastVisibleItemPositions(new int[layoutManager.getSpanCount()]);
position = getMaxPosition(lastPositions);
} else {
position = manager.getItemCount() - 1;
}
return position;
}
/**
* 获得最大的位置
*
* @param positions 位置
* @return pos
*/
private int getMaxPosition(int[] positions) {
int maxPosition = Integer.MIN_VALUE;
for (int position : positions) {
maxPosition = Math.max(maxPosition, position);
}
return maxPosition;
}
public void finishLoad() {
this.mIsLoadingMore = false;
}
/**
* 返回ScrollView的滚动状态
* @return
*/
public boolean isIdle(){
return isIdle;
}
public interface OnLoadMoreListener {
void onLoadMore(LoadMoreModule loadMoreModule);
void onRefreshData();
}
}
浙公网安备 33010602011771号