RecyclerView 下拉刷新上拉加载
步骤:
- 首先直接定义一个XRecyclerView继承RecyclerView,重写他的三个构造方法。
- init(Context mContext)方法用来初始化底部加载的view
- 回到XRecyclerView,实现init
- 判断是否滑动到底部,并且进行加载
- 自定义一个adapter来把底部布局加进去。
- 重写Adapter,通过状态判断是否显示“正在加载”
- 定义一个mDataObserver
1. 首先直接定义一个XRecyclerView继承RecyclerView,重写他的三个构造方法。
public XRecylcerView(Context context) { this(context, null); } public XRecylcerView(Context context, AttributeSet attrs) {
this(context, attrs, 0); } public XRecylcerView(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); init(context); }
2. init(Context mContext)方法用来初始化底部加载的view
先自定义一个底部布局LoadingMoreFooter继承Linearlayout,里面是一个居中显示的ProgressBar和一个TextView,添加一个方法setState(int state),来判定当前刷新的状态
public void setState(int state) { switch (state) { // 刷新中 case STATE_LAODING:
progressCon.setVisibility(View.VISIBLE); mText.setText("正在刷新"); this.setVisibility(View.VISIBLE); break; // 刷新完成 case STATE_COMPLETE: mText.setText("刷新完成"); this.setVisibility(View.GONE); break;
// 没有更多数据 case STATE_NOMORE: mText.setText("没有更多数据啦");
progressCon.setVisibility(View.GONE);
this.setVisibility(View.VISIBLE); break; } }
3. 回到XRecyclerView,实现init
private void init(Context context) {
mContext = context;
// loadingMoreEnabled为下拉的开关
if (loadingMoreEnabled) {
LoadingMoreFooter footerView = new LoadingMoreFooter(mContext);
addFootView(footerView);
mFootViews.get(0).setVisibility(GONE);
}
}
RecyclerView的上拉加载,原理很简单,无非就是当滑动到底部的时候,如果有数据,并且允许加载,就请求数据添加到adapter,而RecyclerView需要做的就是当加载的时候,在底部显示正在加载来提醒用户。
4.判断是否滑动到底部,并且进行加载
1 /**
2 * 监听滑动,来定位当前滑动到哪个地方
3 *
4 * @param state
5 */
6 @Override
7 public void onScrollStateChanged(int state) {
8 super.onScrollStateChanged(state);
9 if (state == RecyclerView.SCROLL_STATE_IDLE
10 && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) {
11 LayoutManager layoutManager = getLayoutManager();
12 int lastVisibleItemPosition;
13 if (layoutManager instanceof GridLayoutManager) {
14 lastVisibleItemPosition =
15 ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
16 } else if (layoutManager instanceof StaggeredGridLayoutManager) {
17 int[] into =
18 new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
19 ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
20 lastVisibleItemPosition = findMax(into);
21 } else {
22 lastVisibleItemPosition =
23 ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
24 }
25 if (layoutManager.getChildCount() > 0
26 && lastVisibleItemPosition >= layoutManager.getItemCount() - 1
27 && layoutManager.getItemCount() > layoutManager.getChildCount()
28 && !isnomore) {
29 View footView = mFootViews.get(0);
30 isLoadingData = true;
31 if (footView instanceof LoadingMoreFooter) {
32 ((LoadingMoreFooter) footView).setState(
33 LoadingMoreFooter.STATE_LAODING);
34 } else {
35 footView.setVisibility(View.VISIBLE);
36 }
37 mLoadingListener.onLoadMore();
38 // 一个回调接口,用来加载数据
39 }
40 }
41 }
写到这个地方,基本的上拉加载的逻辑就搞定了,然后就是处理细节。我们需要把底部布局LoadingMoreFooter加载到RecyclerView,这时候重写setAdapter(Adapter adapter)方法来添加一个adapter。
5. 自定义一个adapter来把底部布局加进去。自定义的Adapter如下:
1 private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {
2 private RecyclerView.Adapter adapter;
3 private ArrayList<View> mFootViews;
4 private int headerPosition = 0;
5
6 public WrapAdapter(ArrayList<View> footViews, RecyclerView.Adapter adapter) {
7 this.adapter = adapter;
8 this.mFootViews = footViews;
9 }
10
11 @Override
12 public void onAttachedToRecyclerView(RecyclerView recyclerView) {
13 super.onAttachedToRecyclerView(recyclerView);
14 RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
15 if (manager instanceof GridLayoutManager) {
16 final GridLayoutManager gridManager = ((GridLayoutManager) manager);
17 gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
18 @Override
19 public int getSpanSize(int position) {
20 return (isFooter(position)) ? gridManager.getSpanCount() : 1;
21 }
22 });
23 }
24 }
25
26 @Override
27 public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
28 super.onViewAttachedToWindow(holder);
29 ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
30 if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isFooter(holder.getLayoutPosition()))
31 {
32 StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
33 p.setFullSpan(true);
34 }
35 }
36
37 public boolean isFooter(int position) {
38 return position < getItemCount() && position >= getItemCount() - mFootViews.size();
39 }
40
41 public int getFootersCount() {
42 return mFootViews.size();
43 }
44
45 @Override
46 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
47 if (viewType == TYPE_FOOTER) {
48 return new SimpleViewHolder(mFootViews.get(0));
49 }
50 return adapter.onCreateViewHolder(parent, viewType);
51 }
52
53 @Override
54 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
55 if (isHeader(position)) {
56 return;
57 }
58 int adjPosition = position;
59 int adapterCount;
60 if (adapter != null) {
61 adapterCount = adapter.getItemCount();
62 if (adjPosition < adapterCount) {
63 adapter.onBindViewHolder(holder, adjPosition);
64 return;
65 }
66 }
67 }
68
69 @Override
70 public int getItemCount() {
71 if (adapter != null) {
72 return getFootersCount() + adapter.getItemCount();
73 } else {
74 return getFootersCount();
75 }
76 }
77
78 @Override
79 public int getItemViewType(int position) {
80 if (isFooter(position)) {
81 return TYPE_FOOTER;
82 }
83 int adjPosition = position;
84 int adapterCount;
85 if (adapter != null) {
86 adapterCount = adapter.getItemCount();
87 if (adjPosition < adapterCount) {
88 return adapter.getItemViewType(adjPosition);
89 }
90 }
91 return TYPE_NORMAL;
92 }
93
94 @Override
95 public long getItemId(int position) {
96 if (adapter != null) {
97 int adjPosition = position - getHeadersCount();
98 int adapterCount = adapter.getItemCount();
99 if (adjPosition < adapterCount) {
100 return adapter.getItemId(adjPosition);
101 }
102 }
103 return -1;
104 }
105
106 private class SimpleViewHolder extends RecyclerView.ViewHolder {
107 public SimpleViewHolder(View itemView) {
108 super(itemView);
109 }
110 }
111 }
就是一个继承自RecyclerView.Adapter的adapter,主要用于根据类型加载不同的布局,普通的itemView和“正在加载”的底部提示。 定义一个mDataObserver
6. 回到setAdapter(Adapter adapter)方法
/**
* 重写Adapter,通过状态判断是否显示“正在加载”
*
* @param adapter
*/
@Override
public void setAdapter(Adapter adapter) {
this.mAdapter = adapter;
this.mWrapAdapter = new WrapAdapter(mFootViews, mAdapter);// 定义WrapAdapter
super.setAdapter(mWrapAdapter);// 通过父类方法将自定义的Adapter重新设置进去
mAdapter.registerAdapterDataObserver(mDataObserver);//请看下面分析
}
查看super.setAdapter()方法,会找到adapter.registerAdapterDataObserver(mObserver)方法,当adapter里面的数据发生改变时会即时监听并且更新。
那为什么还要把mAdapter再设置一遍呢?其实当我们调用通过我们自定义的RecyclerView来调用setAdapter方法时,只有当WrapAdapter数据改变的时候,才会有更新,而当我们仅仅只更新mAdapter里面的数据的时候,如果不监听,我们看到的itemView并没有改变。
7.定义一个mDataObserver
private final RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mWrapAdapter.notifyDataSetChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
}
};
都是直接调用父类的方法就可以。
这样一个RecyclerView的上拉加载逻辑就全部搞定了,这是极其简单的封装方法,所以逻辑没有多么的复杂。
好的,来回顾一下逻辑:重写RecyclerView进行滑动监听,当滑动到底部的时候通过重写setAdapter来将底部视图加载出来,最后对mAdapter进行数据更改的监听。
上拉刷新的逻辑更简单,因为有谷歌的SwipeRefreshLayout,所以实现起来就简单很多,首先来看布局文件
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<com.baiyyyhjl.pullrecyclerview.recyclerview.XRecylcerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.SwipeRefreshLayout>
直接用SwipeRefreshLayout将我们刚才自定义的RecyclerView包裹起来,然后swipeRefreshLayout.setOnRefreshListener(this)进行监听,实现onRefresh()接口来实现加载的逻辑。
就这样,一个简单实用的RecyclerView上拉加载,下拉刷新就实现了。没有多余的布局文件,极其简便。
当我们项目中有多个RecyclerView并且要求上拉加载,下拉刷新的时候,我们可以定义一个抽象类,只通过修改itemView的布局就能实现。