为RecyclerView添加load more功能

思路

实现LoadMore功能主要是从Adapter入手,通过在onAttachedToRecyclerView方法里给RecyclerView添加滑动监听来实现。一种思路是实现一个LoadMoreAdapter基类让其他的Adapter继承来获得load more的能力,但是这样的实现方式感觉有点侵入原有的Adapter代码,而且不够灵活,所以本文采用代理原有Adapter的方式来实现。

实现FooterView

第一步先来实现FooterView,我们定义一个FooterView的接口

public interface IFooterView {
    public void changState(LoadingState state);
}

其中LoadingState是load more的状态,有如下几种

    public enum LoadingState {
        LOADING_STATE_NO_MORE, // 没有更多
        LOADING_STATE_LOADING, // 加载中
        LOADING_STATE_ERROR, // 加载错误
        LOADING_STATE_NORMAL // 不显示footer
    }

然后我们实现一个默认的FooterView

public class DefaultFooterView extends RelativeLayout implements IFooterView {

    private ProgressBar mLoadingView;
    private TextView mNoMoreView;
    private TextView mErrorView;

    private LoadMoreAdapter.LoadingState mState = LoadMoreAdapter.LoadingState.LOADING_STATE_NORMAL;

    public DefaultFooterView(Context context) {
        this(context, null);
    }

    public DefaultFooterView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DefaultFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        initView();
    }

    private void initView() {
        inflate(getContext(), R.layout.default_footer_layout, this);
        mLoadingView = (ProgressBar) findViewById(R.id.loading_view);
        mNoMoreView = (TextView) findViewById(R.id.no_more_view);
        mErrorView = (TextView) findViewById(R.id.error_view);
    }
    @Override
    public void changState(LoadMoreAdapter.LoadingState state) {
        mState = state;
        mLoadingView.setVisibility(mState == LoadMoreAdapter.LoadingState.LOADING_STATE_LOADING? VISIBLE: GONE);
        mNoMoreView.setVisibility(mState == LoadMoreAdapter.LoadingState.LOADING_STATE_NO_MORE? VISIBLE: GONE);
        mErrorView.setVisibility(mState == LoadMoreAdapter.LoadingState.LOADING_STATE_ERROR? VISIBLE: GONE);
    }
}

具体的布局就不展示了,各位自由发挥。如上所示,FooterView对外提供changeState方法来改变布局为对应的状态。

实现LoadMoreAdapter

LoadMoreAdapter用来代理一个真正的Adapter,将相关的系统回调传递给Adapter并实现load more的功能,而在外部RecyclerView使用LoadMoreAdapter而不是被代理的Adapter。

代理Adapter

创建LoadMoreAdapter需要传入一个Adapter,LoadMoreAdapter保存这个Adapter并在相关的系统回调方法里触发Adapter相应的回调,如下所示方法

    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
        mAdapter.setHasStableIds(hasStableIds);
    }

    @Override
    public long getItemId(int position) {
        if (position > mAdapter.getItemCount() - 1) {
            return super.getItemId(position);
        }
        return mAdapter.getItemId(position);
    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        if (holder instanceof FooterViewHolder) {
            super.onViewRecycled(holder);
            return;
        }
        mAdapter.onViewRecycled(holder);
    }

    @Override
    public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
        if (holder instanceof FooterViewHolder) {
            return super.onFailedToRecycleView(holder);
        }
        return mAdapter.onFailedToRecycleView(holder);
    }

    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        if (holder instanceof FooterViewHolder) {
            super.onViewAttachedToWindow(holder);
            return;
        }
        mAdapter.onViewAttachedToWindow(holder);
    }

    @Override
    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
        if (holder instanceof FooterViewHolder) {
            super.onViewDetachedFromWindow(holder);
        }
        mAdapter.onViewDetachedFromWindow(holder);
    }
    @Override
    public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
        super.registerAdapterDataObserver(observer);
        mAdapter.registerAdapterDataObserver(observer);
    }

    @Override
    public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
        super.unregisterAdapterDataObserver(observer);
        mAdapter.unregisterAdapterDataObserver(observer);
    }

其中mAdapter.registerAdapterDataObserver(observer);方法必须要写,因为只有这样才会在调用Adapter的notify方法的时候通知到RecyclerView来刷新数据。

实现LoadMore

LoadMoreAdapter内部保存一个当前的state,通过这个state来判断当前是否应该显示footer,主要是通过getItemCount()、getItemViewType()、onCreateViewHolder()、onBindViewHolder()四个方法来实现

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_FOOT) {
            IFooterView footView = (View) new DefaultFooterView(mContext);
            return new FooterViewHolder(footView);
        }
        return mAdapter.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof FooterViewHolder) {
            FooterViewHolder viewHolder = (FooterViewHolder) holder;
            viewHolder.bind(mState);
            return;
        }
        mAdapter.onBindViewHolder(holder, position);
    }
    
    @Override
    public int getItemCount() {
        return mAdapter.getItemCount() + (mState == LoadingState.LOADING_STATE_NORMAL? 0: 1);
    }

    @Override
    public int getItemViewType(int position) {
        if (position == mAdapter.getItemCount()) {
            return TYPE_FOOT;
        }
        return mAdapter.getItemViewType(position);
    }

且对外提供一个方法来改变当前的状态

    public void updateLoadMoreState(LoadingState state) {
        if (!checkState(state)) {
            return;
        }
        mState = state;
        notifyDataSetChanged();
    }

当然真正的LoadMore逻辑是在onAttachedToRecyclerView的时候给RecyclerView添加滑动监听来实现的,代码如下

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mAdapter.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
        mRecyclerView.addOnScrollListener(mScrollerListener);
    }
    private void initScrollListener() {
        mScrollerListener = new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                boolean showLoadMore = false;
                showLoadMore = !recyclerView.canScrollVertically(1) && dy > 0;
                if (showLoadMore) {
                    if (mLoadMoreListener != null && mState != LoadingState.LOADING_STATE_LOADING) {
                        mState = LoadingState.LOADING_STATE_LOADING;
                        notifyDataSetChanged();
                        mLoadMoreListener.onLoadMore();
                    }
                }
            }
        };
    }
    public interface OnLoadMoreListener {
        public void onLoadMore();
    }

在RecyclerView要向上滑动但不能向上滑的时候触发LoadMore(当前不处于loading的时候),后面就要求外部在加载结束后调用LoadMoreAdapter的updateLoadMoreState方法来更新状态。

实现过程中的坑

原来的思路是itemCount写死mAdapter.getItemCount + 1,然后在不现实footer的时候让FooterView内部的View都gone,然后设置高度为0,看起来就相当于没有FooterView。但是RecyclerView貌似会对布局中元素都是gone或者空布局做一些优化,导致canScrollVertically始终返回true,所以后来采用了更改itemCount的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值