随笔 - 632  文章 - 17  评论 - 54  阅读 - 92万

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();
    }
}

  

posted on   飘杨......  阅读(865)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型
历史上的今天:
2013-10-21 Android cannot be cast to android.app.Fragment
< 2025年2月 >
26 27 28 29 30 31 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 1
2 3 4 5 6 7 8

点击右上角即可分享
微信分享提示