LJ的Blog

学海无涯苦做舟

0%

实现RecyclerView下拉刷新

写在前面

最近项目里有需求要用到下拉刷新,以前写好的代码最好别动太多~在github上发现了一个XRecyclerView能满足的需求,而且也不必对我原来的代码做多大的修改。
但是光会用还不够,万一碰到啥满足不了自己需求或者是坑自己还得有办法解决啊。所以打算通过自己写一个,以此来加深自己对XRecyclerView的理解。

当然了,我只是抱着学习的目的实现一下,重复造轮子是不好的~这篇文主要是理一下自己的思路的,代码不会贴非常完整的,完整代码地址文末有。最后自己也稍微完善了一下代码,权当是练习一下自己的所学所得吧。

简单的实现部分逻辑

首先想一下我最初的需求,希望尽量少的改动自己的代码,XRecyclerView做到了这一点,他内部是有一个WrapAdapter继承于RecyclerView.Adapter。并且XRecyclerView重写了setAdapter这个方法,当你调用setAdapter方法时,会先在WrapAdapter内部持有一个adapter的引用,之后调用RecyclerView的setAdapter将这个WrapAdapter设置给RecyclerView,这么做有一个好处,就是可以自己先在onCreateViewHolder和onBindViewHolder方法里对刷新顶部和你添加的头部布局进行处理和绑定,在这之后再调用adapter的相应方法。

如果你对于RecyclerView的工作机制还不是很熟悉,可能会疑惑我以上的这段话。可以先简单的理解为:onCreateViewHolder是生成整个列表中的一个个元素的,而onBindViewHolder则是在这个元素被滑动到可见(进入屏幕)的时候被调用,你可以在这个方法进行具体的元素视图的设置操作。如果这么说你还不明白,我的这篇探究RecyclerView的ViewHolder复用可能会对你有所帮助。

既然知道了这么写的好处,那么我自然也得弄一个:

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
private class AdapterWrapper extends Adapter {

private Adapter mAdapter;

AdapterWrapper(Adapter adapter) {
this.mAdapter = adapter;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}


@Override
public void onBindViewHolder(ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
return 100;
}

@Override
public int getItemViewType(int position) {

}
}

接下来当然是看看这玩意能不能用了,写一个RecyclerVIew的子类,就叫做MRecyclerView吧:

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
package com.xiasuhuei321.test;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
* Created by xiasuhuei321 on 2017/1/4.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class MRecyclerView extends RecyclerView {
private Context mContext;

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

public MRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public MRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAdapter(new AdapterWrapper(null));
this.mContext = context;
}

private class AdapterWrapper extends Adapter {

private Adapter mAdapter;

AdapterWrapper(Adapter adapter) {
this.mAdapter = adapter;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new HeadViewHolder(LayoutInflater.from(mContext)
.inflate(R.layout.item_test, parent, false));
}


@Override
public void onBindViewHolder(ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
return 100;
}

@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
}

class HeadViewHolder extends ViewHolder {

public HeadViewHolder(View itemView) {
super(itemView);
}
}
}

// 设置LayoutManage的代码
MRecyclerView mr = (MRecyclerView) findViewById(R.id.mrv);
mr.setLayoutManager(new LinearLayoutManager(this));

这里暂时只是简单的实践,所以没有更进一步的实现,先看一下效果是怎样的:

效果图

还行,感觉思路应该是对的。接着尝试加上一个刷新的头布局。这个可以通过设置ViewType和不同的ViewHolder来解决,首先复写AdapterWrap内的getItemViewType方法,这里因为暂时不考虑外部adapter的设置,所以代码比较简单:

1
2
3
4
5
6
private static final int REFRESH_HEADER = 10086;

@Override
public int getItemViewType(int position) {
return position == 0 ? REFRESH_HEADER : 0;
}

接着将onCreateViewHolder的逻辑改改:

1
2
3
4
5
6
7
8
9
10
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == REFRESH_HEADER) {
return new HeadViewHolder(LayoutInflater.from(mContext)
.inflate(R.layout.item_refresh_header, parent, false));
} else {
return new HeadViewHolder(LayoutInflater.from(mContext)
.inflate(R.layout.item_test, parent, false));
}
}

看下啥样子

添加一个下拉刷新

恩,不错,有点样子了,不过这里只是添加了一个item进去而已,并没有平时常见的下拉刷新的效果。关于如何实现这种效果,我刚开始以为是将这个refresh head放到屏幕之外,以前也尝试过ListView下拉刷新,不过忘得差不多了就是……看了一下XRecyclerView的实现方式,很巧妙,是将refresh head的初始高度设置为0,随着下拉高度而逐渐展现refrsh head。这个好啊~我果断也用这种方式实现了。

不过还是说明下,现在只是简单的实现,并非完善的代码,各位通过这个代码简单的了解如何实现就好了:

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
package com.xiasuhuei321.test;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

/**
* Created by xiasuhuei321 on 2017/1/4.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class MRecyclerView extends RecyclerView {
private Context mContext;
private static final int REFRESH_HEADER = 10086;

private float mLastY = -1;
private int mMeasuredHeight = -1;
private HeadViewHolder h;
private boolean releaseToRefresh = false;
private RefreshListener r;
private AnimationDrawable drawable;

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

public MRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public MRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAdapter(new AdapterWrapper(null));
this.mContext = context;
// LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,0);

}

private class AdapterWrapper extends Adapter {

private Adapter mAdapter;

AdapterWrapper(Adapter adapter) {
this.mAdapter = adapter;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == REFRESH_HEADER) {
h = new HeadViewHolder(LayoutInflater.from(mContext)
.inflate(R.layout.item_refresh_header, parent, false));
LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
h.itemView.setLayoutParams(lp);
return h;
} else {
return new HeadViewHolder(LayoutInflater.from(mContext)
.inflate(R.layout.item_test, parent, false));
}
}


@Override
public void onBindViewHolder(ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
return 100;
}

@Override
public int getItemViewType(int position) {
return position == 0 ? REFRESH_HEADER : 0;
}

}

@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLastY == -1) {
mLastY = e.getRawY();
}
if (mMeasuredHeight == -1) {
mMeasuredHeight = h.getMeasureHeight();
}
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:

break;
case MotionEvent.ACTION_MOVE:
float deltaY = e.getRawY() - mLastY;
mLastY = e.getRawY();
onMove(deltaY / 3);
break;
default:
mLastY = -1;
if (releaseToRefresh) refresh();
else refreshComplete();
break;
}
return super.onTouchEvent(e);
}

class HeadViewHolder extends ViewHolder {

public HeadViewHolder(View itemView) {
super(itemView);

}

public int getMeasureHeight() {
itemView.measure(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
return itemView.getMeasuredHeight();
}

public void setVisibleHeight(int height) {
if (height < 0) height = 0;
LayoutParams lp = (LayoutParams) itemView.getLayoutParams();
lp.height = height;
itemView.setLayoutParams(lp);

}

public int getVisibleHeight() {
LayoutParams lp = (LayoutParams) itemView.getLayoutParams();
return lp.height;
}

public ImageView getArrowImg() {
return (ImageView) itemView.findViewById(R.id.iv_progress);
}

public ImageView getRefreshImg() {
return (ImageView) itemView.findViewById(R.id.iv_refresh);
}

public void changeText(String text) {
TextView refreshTitle = (TextView) itemView.findViewById(R.id.tv_refresh_text);
refreshTitle.setText(text);
}
}

public void refreshComplete() {
if (releaseToRefresh) {
ImageView arrowImg = h.getArrowImg();
ObjectAnimator rotate = ObjectAnimator.ofFloat(arrowImg, "rotation", 180f, 0f);
rotate.setDuration(300);
rotate.start();
}
releaseToRefresh = false;
handler.sendEmptyMessageDelayed(1, 800);
}

Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
h.setVisibleHeight(0);
h.changeText("下拉刷新");
break;
case 2:
if (drawable != null) drawable.stop();
ImageView refreshImg = h.getRefreshImg();
ImageView arrowImg = h.getArrowImg();
arrowImg.setVisibility(VISIBLE);
refreshImg.setVisibility(GONE);
h.changeText("刷新完成");
refreshComplete();
break;
default:

break;
}
}
};

public void onMove(float deltaY) {
h.setVisibleHeight((int) deltaY + h.getVisibleHeight());
if (h.getVisibleHeight() > mMeasuredHeight) {
if (!releaseToRefresh) {
//旋转箭头
ImageView arrowImg = h.getArrowImg();
ObjectAnimator rotate = ObjectAnimator.ofFloat(arrowImg, "rotation", 0f, 180f);
h.changeText("释放刷新");
rotate.setDuration(300);
rotate.start();
releaseToRefresh = true;
}
}
}

private void refresh() {
ImageView refreshImg = h.getRefreshImg();
ImageView arrowImg = h.getArrowImg();
arrowImg.setVisibility(GONE);
refreshImg.setVisibility(VISIBLE);
drawable = (AnimationDrawable) refreshImg.getDrawable();
drawable.start();
h.changeText("刷新中...");
handler.sendEmptyMessageDelayed(2,3000);
}

public void setPullToRefreshListener(RefreshListener r) {
this.r = r;
}

public interface RefreshListener {
void refresh();
}


}

看看效果图:

效果图

后记

还有很多的细节没有完善和处理,比如适配不同的layoutmanage之类的,操作刷新布局也都是通过ViewHolder来的,其实是有必要抽出一个类的,真的还有非常多不完善的地方,不过只是简单实现和了解一下下拉刷新而已。我自己也简单的封装了一个MRecyclerView,感兴趣的可以看看。

https://github.com/ForgetAll/MRecyclerView

当然了,由于并非是想要做一个完善的三方,所以只是简单的实现,很多情况并没有考虑到其中。