RecyclerView 自带的上拉加载更多

RecyclerView 大家肯定很熟悉,平常使用的时候也难免会遇到下拉刷新和上拉加载更多,网上相关的控件也是多如牛毛。我特别期待谷歌什么时候能够自己开发一个,可惜一直到现在,也只有一个SwipeRefreshLayout下拉刷新控件,上拉加载连个影子都没。。。

如果不想使用第三方的控件,又想有上拉加载更多功能怎么办呢?于是上网查了相关资料,发现RecyclerView有一个监听方法addOnScrollListener,可以监听到RecyclerView的滑动状态,欣喜若狂,好好研究了一番,并且自己封装了下

Demo 地址
https://github.com/linqinen708/MyDatabindingRecyclerView

在这里插入图片描述

使用方式:

首先在xml布局中使用

 <MyRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

然后在Activity中初始化,大功告成
其中adapter 就是RecyclerView的adapter

private void initRefreshLayout() {
        mRefreshLayout.setAdapter(mAdapter);

        mRefreshLayout.setRefreshListener(new MyRefreshLayout.RefreshListener() {
            @Override
            public void loadMore() {
                
            }

            @Override
            public void refresh() {
                
            }
        });
    }

那么其中的原理是什么呢?
下面简单说一下:

/**最后一个可见的item的索引*/
private int lastVisibleItemPosition;
/**
 * 是否正在上拉加载更多
 */
private boolean isLoadingMore;
***
***
***
private void initLoadMoreListener() {
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//                    LogT.i("停止滑动:");
                    int visibleItemCount = mLayoutManager.getChildCount();
                    int totalItemCount = mLayoutManager.getItemCount();
                    LogT.i("visibleItemCount:" + visibleItemCount + ", totalItemCount:" + totalItemCount + ",lastVisibleItemPosition:" + lastVisibleItemPosition);
                    if (!isLoadingMore && lastVisibleItemPosition == totalItemCount - 1) {
                        isLoadingMore = true;
                    }
                }
            }

            /**仅对LinearLayoutManager 有效,其他LayoutManager未验证*/
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();

            }
        });
    }

首先需要通过onScrolled方法,获得屏幕中可见的最后一个item的position,然后在onScrollStateChanged方法中,判断RecyclerView是否已经停止滚动,也就是newState == RecyclerView.SCROLL_STATE_IDLE,
然后判断最后一个item的position是否是总数的最后一个lastVisibleItemPosition == totalItemCount - 1,如果是,则说明滑到底部了,整个逻辑就是这么简单粗暴
在这里插入图片描述
然后我就自己封装了下RecyclerView,变成了LoadMoreRecyclerView

public class LoadMoreRecyclerView extends RecyclerView {

    private LinearLayoutManager mLayoutManager;

    private int lastVisibleItemPosition;


    /**
     * 是否可以上拉加载更多
     * 比如当暂无更多数据之后,就没必要再支持上拉加载更多了
     * 可以关闭上拉功能
     */
    private boolean isLoadMoreEnable = true;

    public boolean isLoadMoreEnable() {
        return isLoadMoreEnable;
    }

    public void setLoadMoreEnable(boolean loadMoreEnable) {
        isLoadMoreEnable = loadMoreEnable;
    }

    /**
     * 是否正在上拉加载更多,
     * 如果不做额外判断,而用户连续快速上拉,则会出现多次请求
     */
    private boolean isLoadingMore;

    public boolean isLoadingMore() {
        return isLoadingMore;
    }

    public void completeLoadMore() {
        isLoadingMore = false;
    }

    private LoadMoreListener mLoadMoreListener;

    public void setLoadMoreListener(LoadMoreListener loadMoreListener) {
        init();
        mLoadMoreListener = loadMoreListener;
    }

    public interface LoadMoreListener {
        void loadMore();
    }

    public LoadMoreRecyclerView(@NonNull Context context) {
        super(context);
        initView();
    }

    public LoadMoreRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public LoadMoreRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    /**如果不取消动画,会导致数据不一致的报错
     * IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter
     * */
    private void initView(){
        setItemAnimator(null);
    }

    private void init() {
        LogT.i("初始化:" + getLayoutManager());
        if (getLayoutManager() != null && getLayoutManager() instanceof LinearLayoutManager) {
            mLayoutManager = (LinearLayoutManager) getLayoutManager();
        }
        if (mLayoutManager != null) {
            addOnScrollListener(new RecyclerView.OnScrollListener() {
                /**
                 * 当显示的item数量不够多,无法撑满屏幕高度时
                 * 无论是上拉还是下拉,都会触发该方法,
                 * 所以下拉需要满足条件
                 * 否则下拉也会触发加载更多
                 * 所以当 visibleItemCount < totalItemCount 才触发加载更多
                 * 即 item的数量够多,并超过屏幕高度时,才触发加载更多
                 * */
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    if (isLoadMoreEnable && newState == RecyclerView.SCROLL_STATE_IDLE) {
//                    LogT.i("停止滑动:");
                        int visibleItemCount = mLayoutManager.getChildCount();
                        int totalItemCount = mLayoutManager.getItemCount();
//                        LogT.i(":" + getAdapter().getItemViewType(0));
//                        LogT.i("isLoadingMore:"+isLoadingMore + ",visibleItemCount:" + visibleItemCount + ", totalItemCount:" + totalItemCount + ",lastVisibleItemPosition:" + lastVisibleItemPosition);
                        if (!isLoadingMore && visibleItemCount < totalItemCount && lastVisibleItemPosition == totalItemCount - 1) {
                            isLoadingMore = true;
                            if (getAdapter() != null && getAdapter() instanceof BaseBindingAdapter) {
                                LogT.i("加载更多:");
                                ((BaseBindingAdapter) getAdapter()).showLoadMore(true);
                            }
                            LogT.i("mLoadMoreListener:" + mLoadMoreListener);
                            if (mLoadMoreListener != null) {
                                mLoadMoreListener.loadMore();
                            }
                        }
                    }
                }

                /**仅对LinearLayoutManager 有效,其他LayoutManager未验证*/
                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
//                    LogT.i("dx:" +dx + ", dy:"  + dy);
                    if (isLoadMoreEnable) {
                        lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
                    }

                }
            });
        }
    }
}

使用方式:

mRecyclerView.setLoadMoreListener(new LoadMoreRecyclerView.LoadMoreListener() {
            @Override
            public void loadMore() {
                httpRequest();
            }
        });

不过这样子只完成了一半,还不是一个完整的控件,于是我自己也封装了一个控件MyRefreshLayout,整个控件非常简单,就是把SwipeRefreshLayout和LoadMoreRecyclerView 整合在一起,这样,就是一个简单的下拉刷新和上拉加载更多控件
在这里插入图片描述

public class MyRefreshLayout extends FrameLayout implements SwipeRefreshLayout.OnRefreshListener {

    private Context mContext;

    private SwipeRefreshLayout mSwipeRefreshLayout;

    private LoadMoreRecyclerView mRecyclerView;

    private BaseBindingAdapter mAdapter;

    private RefreshListener mRefreshListener;
    /**
     * 页数
     */
    private int page = 1;

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public void setRefreshListener(RefreshListener refreshListener) {
        mRefreshListener = refreshListener;
        mSwipeRefreshLayout.setEnabled(true);
        mSwipeRefreshLayout.setOnRefreshListener(this);
        mRecyclerView.setLoadMoreListener(new LoadMoreRecyclerView.LoadMoreListener() {
            @Override
            public void loadMore() {
                page++;
                if (mRefreshListener != null) {
                    mRefreshListener.loadMore();
                }
            }
        });
    }

    public interface RefreshListener {
        void loadMore();

        void refresh();
    }

    public MyRefreshLayout(@NonNull Context context) {
        super(context);
        mContext = context;
        initView();
    }

    public MyRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initView();
    }

    public MyRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        initView();
    }

    /**
     * 初始化控件
     */
    private void initView() {
        mSwipeRefreshLayout = new SwipeRefreshLayout(mContext);
        mSwipeRefreshLayout.setEnabled(false);
        mRecyclerView = new LoadMoreRecyclerView(mContext);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
        /*因为自定义的LoadMoreListener 需要LinearLayoutManager支持
         * 所以setLoadMoreListener 需要在setLayoutManager 之后 设置
         * */


        mSwipeRefreshLayout.addView(mRecyclerView);

        addView(mSwipeRefreshLayout);
    }

    @Override
    public void onRefresh() {
        page = 1;
        if (mRefreshListener != null) {
            mRefreshListener.refresh();
        }
    }

    /**
     * 下拉刷新完成
     */
    public void completeRefresh() {
        /*如果下拉刷新后,mRecyclerView 自动加载到底部,则让其返回到顶部*/
        mRecyclerView.scrollToPosition(0);
        mSwipeRefreshLayout.setRefreshing(false);
    }

    /**
     * 上拉加载更多完成
     */
    public void completeLoadMore() {
        mRecyclerView.completeLoadMore();
    }

    public void complete() {
        completeLoadMore();
        completeRefresh();
    }

    public void completeHttpRequest(Collection collection) {
        if (mAdapter == null) {
            return;
        }
        if (page == 1) {
            mAdapter.getItems().clear();
            completeRefresh();
            mAdapter.toggleFootView(collection);
            setLoadMoreEnable(true);
        } else {
            completeLoadMore();
            setLoadMoreEnable(mAdapter.toggleLoadMore(collection));
        }
    }

    public void setAdapter(BaseBindingAdapter adapter) {
        mAdapter = adapter;
        mRecyclerView.setAdapter(adapter);
    }

    public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
        mRecyclerView.addItemDecoration(decor);
    }

    public void setLoadMoreEnable(boolean loadMoreEnable) {
        mRecyclerView.setLoadMoreEnable(loadMoreEnable);
    }
}

坑1:
在使用的过程当中,如果用的是notifyItemRangeInserted 或则 notifyItemRangeRemoved 刷新数据,而不是notifyDataSetChanged(),有可能会出错IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter,上网查了很多方法,解决的方式是mRecyclerView.setItemAnimator(null);真是一脸懵逼,加个动画居然还有问题。。。还是RecyclerView默认的自带动画。。。

坑2:
如果mAdapter.getItems().clear()之后,再加入数据,很有可能会发现RecyclerView仍然在底部,所以需要mRecyclerView.scrollToPosition(0); 返回顶部

坑3:
当显示的item数量不够多,无法撑满屏幕高度时无论是上拉还是下拉,都会触发onScrollStateChanged该方法,所以下拉需要满足条件否则下拉也会触发加载更多 所以当 visibleItemCount < totalItemCount 才触发加载更多 即 item的数量够多,并超过屏幕高度时,才触发加载更多

坑4:
如果有人连续上拉加载更多,可能会导致数据错乱,所以加一个boolean值isLoadMoreEnable,当上拉加载更多还没有结束时,不让其继续上拉加载更多

温馨提示:
自己封装的RefreshLayout采用的adapter 是自己封装的BaseBindingAdapter
我自己又额外封装了一个方法
当请求后台数据,获得List后,直接调用completeHttpRequest方法,
就可以动态的实现FooterView 是 “正在加载…” 还是 “暂无更多数据”
非常方便

mRefreshLayout.completeHttpRequest(bean.getRooms());

MVVM模式下RecyclerView与databinding的结合
MVVM模式下RecyclerView与databinding的结合(2)

参考资料 https://blog.csdn.net/weixin_37577039/article/details/79214663

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值