思路
实现LoadMore功能主要是从Adapter入手,通过在onAttachedToRecyclerView方法里给RecyclerView添加滑动监听来实现。一种思路是实现一个LoadMoreAdapter基类让其他的Adapter继承来获得load more的能力,但是这样的实现方式感觉有点侵入原有的Adapter代码,而且不够灵活,所以本文采用代理原有Adapter的方式来实现。
第一步先来实现FooterView,我们定义一个FooterView的接口
1 2 3
| public interface IFooterView { public void changState(LoadingState state); }
|
其中LoadingState是load more的状态,有如下几种
1 2 3 4 5 6
| public enum LoadingState { LOADING_STATE_NO_MORE, LOADING_STATE_LOADING, LOADING_STATE_ERROR, LOADING_STATE_NORMAL }
|
然后我们实现一个默认的FooterView
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
| 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相应的回调,如下所示方法
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
| @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()四个方法来实现
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
| @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); }
|
且对外提供一个方法来改变当前的状态
1 2 3 4 5 6 7
| public void updateLoadMoreState(LoadingState state) { if (!checkState(state)) { return; } mState = state; notifyDataSetChanged(); }
|
当然真正的LoadMore逻辑是在onAttachedToRecyclerView的时候给RecyclerView添加滑动监听来实现的,代码如下
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
| @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的方法。