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

  

posted on 2022-10-21 09:48  飘杨......  阅读(969)  评论(0)    收藏  举报