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.完成工具类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | 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(); } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型
2013-10-21 Android cannot be cast to android.app.Fragment