haohao

RecyclerView 加载与刷新功能实现

Cover

坚持就是进步

最近 Kotlin Android 开发貌似要火起来,Kotlin 具有脚本语言风格,能与 Java 无缝兼容,并且有 Google 有意栽培。

RecyclerView 加载与刷新功能实现

RecyclerView 功能及灵活性强大到令人发指。在开发中,完全可以用它代替 ListView 和 GridView ,并且还具有瀑布流展示功能。

本文主要对 RecyclerView 进行简单的封装,来实现加载、刷新以及异常状态展示多种功能,实现及其简单,代码量很少。

效果展示

jc-a
jc-b

Demo 下载

源码地址: https://github.com/githubhaohao/JCRecyclerView

具体实现

JCRecyclerView 继承 RecyclerView ,保留 RecyclerView 的所有特性。

内部类 JCAdapter 对外部的 RecyclerView Adapter 进行拦截改造,实现根据配置在 position = 0 的位置加载 Ad-Slot View (广告位),加载时在底部显示 Bottom View 表视加载正在进行。

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
private class JCAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter adapter;
private static final int TYPE_ADSLOT = 0x10;
private static final int TYPE_NORMAL = 0x11;
private static final int TYPE_BOTTOM = 0x12;
public JCAdapter(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public void onViewAttachedToWindow(ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if(layoutParams != null){
if(adSlotView != null) {
if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams && holder.getLayoutPosition() == 0){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
p.setFullSpan(true);
}
}
if (bottomView != null && isLoading) {
if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams && holder.getLayoutPosition() == getItemCount() - 1){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
p.setFullSpan(true);
}
}
}
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager)
layoutManager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
boolean spanResult = false;
if(adSlotView != null && bottomView != null) {
if (isLoading) {
spanResult = (position == 0 || position == getItemCount() - 1);
} else {
spanResult = (position == 0);
}
} else if (adSlotView != null) {
spanResult = (position==0);
} else if (bottomView != null && isLoading) {
spanResult = (position == getItemCount() - 1);
}
return spanResult
? gridManager.getSpanCount():1;
}
});
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ADSLOT) {
return new JCViewHolder(adSlotView);
} else if (viewType == TYPE_BOTTOM) {
return new JCViewHolder(bottomView);
}
return adapter.onCreateViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (getItemViewType(0) == TYPE_ADSLOT) {
if (position == 0) return;
int newPosition = --position;
if (adapter != null) {
if (newPosition < adapter.getItemCount()) {
adapter.onBindViewHolder(holder, newPosition);
}
}
return;
} else if (getItemViewType(position) == TYPE_BOTTOM) {
return;
}
adapter.onBindViewHolder(holder, position);
}
@Override
public int getItemCount() {
int count = adapter.getItemCount();
if (adSlotView != null) {
count ++;
}
if (bottomView != null && isLoading) {
count ++;
}
return count;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return adSlotView == null ? TYPE_NORMAL : TYPE_ADSLOT;
} else if (position == getItemCount() - 1 && isLoading) {
return bottomView == null ? TYPE_NORMAL : TYPE_BOTTOM;
} else {
return TYPE_NORMAL;
}
}
}

向外暴露一个接口 OnLoadMoreListener ,完成加载时的回调。

1
2
3
public interface OnLoadMoreListener {
void onLoadMore();
}

整体思路就是这些,完整代码:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
public class JCRecyclerView extends RecyclerView {
private static final String TAG = JCRecyclerView.class.getSimpleName();
private LayoutManager layoutManager;
private ViewGroup adSlotView;
private ViewGroup stateView;
private ViewGroup bottomView;
private boolean isLoading = false;
private JCAdapter jcAdapter;
private OnLoadMoreListener onLoadMoreListener;
public void addOnLoadMoreListener(OnLoadMoreListener listener) {
this.onLoadMoreListener = listener;
this.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager();
int totalItemCount = recyclerView.getAdapter().getItemCount();
int[] lastVisibleItemPositions = new int[layoutManager.getSpanCount()];
layoutManager.findLastVisibleItemPositions(lastVisibleItemPositions);
int visibleItemCount = recyclerView.getChildCount();
int lastVisibleItemPosition = findMaxPosition(lastVisibleItemPositions);
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& lastVisibleItemPosition == totalItemCount - 1
&& visibleItemCount > 0) {
if (bottomView == null || isLoading || stateView != null) return;
isLoading = true;
jcAdapter.notifyDataSetChanged();
onLoadMoreListener.onLoadMore();
scrollToPosition(jcAdapter.getItemCount() - 1);
}
} else {
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
int totalItemCount = recyclerView.getAdapter().getItemCount();
int lastVisibleItemPosition = lm.findLastVisibleItemPosition();
int visibleItemCount = recyclerView.getChildCount();
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& lastVisibleItemPosition == totalItemCount - 1
&& visibleItemCount > 0) {
if (bottomView == null || isLoading || stateView != null) return;
isLoading = true;
jcAdapter.notifyDataSetChanged();
onLoadMoreListener.onLoadMore();
scrollToPosition(jcAdapter.getItemCount() - 1);
}
}
}
});
}
private int findMaxPosition(int[] positions) {
int max = positions[0];
for (int index = 1; index < positions.length; index++) {
if (positions[index] > max) {
max = positions[index];
}
}
return max;
}
public void setBottomView(ViewGroup view) {
if (bottomView == null) {
this.bottomView = view;
}
}
public void setLoading(boolean flag) {
if (!flag) {
isLoading = false;
jcAdapter.notifyDataSetChanged();
scrollToPosition(jcAdapter.getItemCount() - 1);
}
}
public void setAdSlotView(ViewGroup view) {
if (adSlotView == null) {
adSlotView = view;
if (jcAdapter != null) {
jcAdapter.notifyItemInserted(0);
scrollToPosition(0);
}
}
}
public void setStateView(ViewGroup view) {
if (stateView != null) return;
if (view == null) return;
if (adSlotView != null) {
scrollToPosition(0);
stateView = view;
Rect rect = new Rect();
getGlobalVisibleRect(rect);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, rect.bottom - rect.top - adSlotView.getHeight());
adSlotView.addView(stateView, layoutParams);
} else {
showToast("You should set the ad-slot view at first.");
}
}
public void removeAdSlotView() {
if (adSlotView != null && jcAdapter != null) {
adSlotView = null;
jcAdapter.notifyItemRemoved(0);
}
}
public void removeStateView() {
if (adSlotView != null && stateView != null && jcAdapter != null) {
adSlotView.removeView(stateView);
stateView = null;
}
}
@Override
public void setAdapter(Adapter adapter) {
this.jcAdapter = new JCAdapter(adapter);
super.setAdapter(this.jcAdapter);
}
public JCRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setLayoutManager(LayoutManager layoutManager) {
this.layoutManager = layoutManager;
super.setLayoutManager(layoutManager);
}
private class JCAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter adapter;
private static final int TYPE_ADSLOT = 0x10;
private static final int TYPE_NORMAL = 0x11;
private static final int TYPE_BOTTOM = 0x12;
public JCAdapter(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public void onViewAttachedToWindow(ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if(layoutParams != null){
if(adSlotView != null) {
if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams && holder.getLayoutPosition() == 0){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
p.setFullSpan(true);
}
}
if (bottomView != null && isLoading) {
if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams && holder.getLayoutPosition() == getItemCount() - 1){
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
p.setFullSpan(true);
}
}
}
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager)
layoutManager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
boolean spanResult = false;
if(adSlotView != null && bottomView != null) {
if (isLoading) {
spanResult = (position == 0 || position == getItemCount() - 1);
} else {
spanResult = (position == 0);
}
} else if (adSlotView != null) {
spanResult = (position==0);
} else if (bottomView != null && isLoading) {
spanResult = (position == getItemCount() - 1);
}
return spanResult
? gridManager.getSpanCount():1;
}
});
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ADSLOT) {
return new JCViewHolder(adSlotView);
} else if (viewType == TYPE_BOTTOM) {
return new JCViewHolder(bottomView);
}
return adapter.onCreateViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (getItemViewType(0) == TYPE_ADSLOT) {
if (position == 0) return;
int newPosition = --position;
if (adapter != null) {
if (newPosition < adapter.getItemCount()) {
adapter.onBindViewHolder(holder, newPosition);
}
}
return;
} else if (getItemViewType(position) == TYPE_BOTTOM) {
return;
}
adapter.onBindViewHolder(holder, position);
}
@Override
public int getItemCount() {
int count = adapter.getItemCount();
if (adSlotView != null) {
count ++;
}
if (bottomView != null && isLoading) {
count ++;
}
return count;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return adSlotView == null ? TYPE_NORMAL : TYPE_ADSLOT;
} else if (position == getItemCount() - 1 && isLoading) {
return bottomView == null ? TYPE_NORMAL : TYPE_BOTTOM;
} else {
return TYPE_NORMAL;
}
}
}
private class JCViewHolder extends RecyclerView.ViewHolder {
public JCViewHolder(View itemView) {
super(itemView);
}
}
private void showToast(String msg) {
Toast.makeText(getContext(),msg,Toast.LENGTH_SHORT).show();
}
public interface OnLoadMoreListener {
void onLoadMore();
}
}

200 多行代码,轻松实现 RecyclerView 刷新、加载以及异常状态的展示功能。



联系我

Wechat ID

公众号

生活不止于眼前的苟且, 还有诗和远方的田野