1. 流程分析
下拉刷新最主要的流程是:
(1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户"下拉刷新"
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以"松手刷新"了,效果上允许用户继续下拉
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户"正在加载"。
(4). 加载完成后,隐藏提示头部界面。
示意图如下:
->
->
2. 实现分析
当前我们要实现上述流程,是基于ListView的,所以对应ListView本身的功能我们来分析一下实现原理:
(1). 下拉,显示提示头部界面,这个过程提示用户"下拉刷新"
a. 下拉的操作,首先是监听滚动,ListView提供了onScroll()方法
b. 与下拉类似一个动作向下飞滑,所以ListView的scrollState有3种值:SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING,意思容易理解,而我们要下拉的触发条件是SCROLL_STATE_TOUCH_SCROLL。判断当前的下拉操作状态,ListView提供了public void onScrollStateChanged(AbsListView view, int scrollState) {}。
c. 下拉的过程中,我们可能还需要下拉到多少的边界值处理,重写onTouchEvent(MotionEvent ev){}方法,可依据ACTION_DOWN,ACTION_MOVE,ACTION_UP实现更精细的判断。
(2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以"松手刷新"了,效果上允许用户继续下拉
a. 达到下拉刷新界限,一般指达到header的高度的,所以有两步,第一,获取header的高度,第二,当header.getBottom()>=header的高度时,我们认为就达到了刷新界限值
b. 继续允许用户下拉,当header完全下拉后,默认无法继续下拉,但是可以增加header的PaddingTop实现这种效果
(3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户"正在加载"。
a. 松手后反弹,这个不能一下子弹回去,看上去太突然,需要一步一步柔性的弹回去,像弹簧一样,我们可以new一个Thread循环计算减少PaddingTop,直到PaddingTop为0,反弹结束。
b. 正在加载,在子线程里处理后台任务
(4). 加载完成后,隐藏提示头部界面。
a. 后台任务完成后,我们需要隐藏header,setSelection(1)即实现了从第2项开始显示,间接隐藏了header。
上面我们分析了实现过程的轮廓,接下来,通过细节说明和代码具体实现。
3. 初始化
一切状态显示都是用HeaderView显示的,所以我们需要一个HeaderView的layout,使用addHeaderView方法添加到ListView中。
同时,默认状态下,HeaderView是不显示的,只是在下拉后才显示,所以我们需要隐藏HeaderView且不影响后续的下拉显示,用setSelection(1)。
refresh_list_header.xml布局如下:
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
|
<?
xml
version="1.0" encoding="utf-8"?>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center">
<
ProgressBar
android:id="@+id/refresh_list_header_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="?android:attr/progressBarStyleSmall"
android:visibility="gone">
</
ProgressBar
>
<
ImageView
android:id="@+id/refresh_list_header_pull_down"
android:layout_width="9dip"
android:layout_height="25dip"
android:layout_gravity="center"
android:src="@drawable/refresh_list_pull_down" />
<
ImageView
android:id="@+id/refresh_list_header_release_up"
android:layout_width="9dip"
android:layout_height="25dip"
android:layout_gravity="center"
android:src="@drawable/refresh_list_release_up"
android:visibility="gone" />
<
RelativeLayout
android:layout_width="180dip"
android:layout_height="wrap_content">
<
TextView
android:id="@+id/refresh_list_header_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_alignParentTop="true"
android:textSize="12dip"
android:textColor="#192F06"
android:paddingTop="8dip"
android:text="@string/app_list_header_refresh_down"/>
<
TextView
android:id="@+id/refresh_list_header_last_update"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_below="@id/refresh_list_header_text"
android:textSize="12dip"
android:textColor="#192F06"
android:paddingBottom="8dip"
android:text="@string/app_list_header_refresh_last_update"/>
</
RelativeLayout
>
</
LinearLayout
>
|
代码中在构造函数中添加init()方法加载如下:
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
|
private
LinearLayout mHeaderLinearLayout =
null
;
private
TextView mHeaderTextView =
null
;
private
TextView mHeaderUpdateText =
null
;
private
ImageView mHeaderPullDownImageView =
null
;
private
ImageView mHeaderReleaseDownImageView =
null
;
private
ProgressBar mHeaderProgressBar =
null
;
public
RefreshListView(Context context) {
this
(context,
null
);
}
public
RefreshListView(Context context, AttributeSet attrs) {
super
(context, attrs);
init(context);
}
void
init(
final
Context context) {
mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header,
null
);
addHeaderView(mHeaderLinearLayout);
mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);
mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);
mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);
mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);
mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);
setSelection(
1
);
}
|
默认就显示完成了。
4. HeaderView的默认高度测量
因为下拉到HeaderView全部显示出来,就由提示"下拉刷新"变为"松手刷新",全部显示的出来的测量标准就是header.getBottom()>=header的高度。
所以,首先我们需要测量HeaderView的默认高度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//因为是在构造函数里测量高度,应该先measure一下
private
void
measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if
(p ==
null
) {
p =
new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int
childWidthSpec = ViewGroup.getChildMeasureSpec(
0
,
0
+
0
, p.width);
int
lpHeight = p.height;
int
childHeightSpec;
if
(lpHeight >
0
) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
}
else
{
childHeightSpec = MeasureSpec.makeMeasureSpec(
0
,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
|
然后在init的上述代码后面加上调用measureView后,使用getMeasureHeight()方法获取header的高度:
1
2
3
4
5
6
|
private
int
mHeaderHeight;
void
init(
final
Context context) {
... ...
measureView(mHeaderLinearLayout);
mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
}
|
后面我们就会用到这个mHeaderHeight.
5. scrollState监听记录
scrollState有3种,使用onScrollStateChanged()方法监听记录。
1
2
3
4
5
|
private
int
mCurrentScrollState;
@Override
public
void
onScrollStateChanged(AbsListView view,
int
scrollState) {
mCurrentScrollState = scrollState;
}
|
然后即可使用mCurrentScrollState作为后面判断的条件了。
6. 刷新状态分析
因为一些地方需要知道我们处在正常状态下还是进入下拉刷新状态还是松手反弹状态,比如,
(1). 在非正常的状态下,我们不小心飞滑了一下(松手的瞬间容易出现这种情况),我们不能setSelection(1)的,否则总是松手后header跳的一下消失掉了。
(2). 下拉后要做一个下拉效果的特殊处理,需要用到OVER_PULL_REFRESH(松手刷新状态下)
(3). 松手反弹后要做一个反弹效果的特殊处理,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。
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
|
private
final
static
int
NONE_PULL_REFRESH =
0
;
//正常状态
private
final
static
int
ENTER_PULL_REFRESH =
1
;
//进入下拉刷新状态
private
final
static
int
OVER_PULL_REFRESH =
2
;
//进入松手刷新状态
private
final
static
int
EXIT_PULL_REFRESH =
3
;
//松手后反弹后加载状态
private
int
mPullRefreshState =
0
;
//记录刷新状态
@Override
public
void
onScroll(AbsListView view,
int
firstVisibleItem,
int
visibleItemCount,
int
totalItemCount) {
if
(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >=
0
&& mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
//进入且仅进入下拉刷新状态
if
(mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = ENTER_PULL_REFRESH;
}
}
else
if
(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {
//下拉达到界限,进入松手刷新状态
if
(mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = OVER_PULL_REFRESH;
//下面是进入松手刷新状态需要做的一个显示改变
mDownY = mMoveY;
//用于后面的下拉特殊效果
mHeaderTextView.setText(
"松手刷新"
);
mHeaderPullDownImageView.setVisibility(View.GONE);
mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);
}
}
else
if
(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem !=
0
) {
//不刷新了
if
(mPullRefreshState == ENTER_PULL_REFRESH) {
mPullRefreshState = NONE_PULL_REFRESH;
}
}
else
if
(mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem ==
0
) {
//飞滑状态,不能显示出header,也不能影响正常的飞滑
//只在正常情况下才纠正位置
if
(mPullRefreshState == NONE_PULL_REFRESH) {
setSelection(
1
);
}
}
}
|
mPullRefreshState将是后面我们处理边界的重要变量。
6. 下拉效果的特殊处理
所谓的特殊处理,当header完全显示后,下拉只按下拉1/3的距离下拉,给用户一种艰难下拉,该松手的弹簧感觉。
这个在onTouchEvent里处理比较方便:
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
|
private
float
mDownY;
private
float
mMoveY;
@Override
public
boolean
onTouchEvent(MotionEvent ev) {
switch
(ev.getAction()) {
case
MotionEvent.ACTION_DOWN:
//记下按下位置
//改变
mDownY = ev.getY();
break
;
case
MotionEvent.ACTION_MOVE:
//移动时手指的位置
mMoveY = ev.getY();
if
(mPullRefreshState == OVER_PULL_REFRESH) {
//注意下面的mDownY在onScroll的第二个else中被改变了
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(
int
)((mMoveY - mDownY)/
3
),
//1/3距离折扣
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
}
break
;
case
MotionEvent.ACTION_UP:
... ...
break
;
}
return
super
.onTouchEvent(ev);
}
//重复贴出下面这段需要注意的代码
@Override
public
void
onScroll(AbsListView view,
int
firstVisibleItem,
int
visibleItemCount,
int
totalItemCount) {
... ...
else
if
(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {
//下拉达到界限,进入松手刷新状态
if
(mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = OVER_PULL_REFRESH;
mDownY = mMoveY;
//为下拉1/3折扣效果记录开始位置
mHeaderTextView.setText(
"松手刷新"
);
//显示松手刷新
mHeaderPullDownImageView.setVisibility(View.GONE);
//隐藏"下拉刷新"
mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);
//显示向上的箭头
}
}
... ...
}
|
onScroll里监听到了进入松手刷新状态,onTouchEvent就开始在ACTION_MOVE中处理1/3折扣问题。
7. 反弹效果的特殊处理
松手后我们需要一个柔性的反弹效果,意味着我们弹回去的过程需要分一步步走,我的解决方案是:
在子线程里计算PaddingTop,并减少到原来的3/4,循环通知主线程,直到PaddingTop小于1(这个值取一个小值,合适即可)。
松手后,当然是在onTouchEvent的ACTION_UP条件下处理比较方便:
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
|
//因为涉及到handler数据处理,为方便我们定义如下常量
private
final
static
int
REFRESH_BACKING =
0
;
//反弹中
private
final
static
int
REFRESH_BACED =
1
;
//达到刷新界限,反弹结束后
private
final
static
int
REFRESH_RETURN =
2
;
//没有达到刷新界限,返回
private
final
static
int
REFRESH_DONE =
3
;
//加载数据结束
@Override
public
boolean
onTouchEvent(MotionEvent ev) {
switch
(ev.getAction()) {
... ...
case
MotionEvent.ACTION_UP:
//when you action up, it will do these:
//1. roll back util header topPadding is 0
//2. hide the header by setSelection(1)
if
(mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) {
new
Thread() {
public
void
run() {
Message msg;
while
(mHeaderLinearLayout.getPaddingTop() >
1
) {
msg = mHandler.obtainMessage();
msg.what = REFRESH_BACKING;
mHandler.sendMessage(msg);
try
{
sleep(
5
);
//慢一点反弹,别一下子就弹回去了
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
msg = mHandler.obtainMessage();
if
(mPullRefreshState == OVER_PULL_REFRESH) {
msg.what = REFRESH_BACED;
//加载数据完成,结束返回
}
else
{
msg.what = REFRESH_RETURN;
//未达到刷新界限,直接返回
}
mHandler.sendMessage(msg);
};
}.start();
}
break
;
}
return
super
.onTouchEvent(ev);
}
private
Handler mHandler =
new
Handler(){
@Override
public
void
handleMessage(Message msg) {
switch
(msg.what) {
case
REFRESH_BACKING:
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(
int
) (mHeaderLinearLayout.getPaddingTop()*
0
.75f),
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
break
;
case
REFRESH_BACED:
mHeaderTextView.setText(
"正在加载..."
);
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderPullDownImageView.setVisibility(View.GONE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mPullRefreshState = EXIT_PULL_REFRESH;
new
Thread() {
public
void
run() {
sleep(
2000
);
//处理后台加载数据
Message msg = mHandler.obtainMessage();
msg.what = REFRESH_DONE;
//通知主线程加载数据完成
mHandler.sendMessage(msg);
};
}.start();
break
;
case
REFRESH_RETURN:
//未达到刷新界限,返回
mHeaderTextView.setText(
"下拉刷新"
);
mHeaderProgressBar.setVisibility(View.INVISIBLE);
mHeaderPullDownImageView.setVisibility(View.VISIBLE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
0
,
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
mPullRefreshState = NONE_PULL_REFRESH;
setSelection(
1
);
break
;
case
REFRESH_DONE:
//刷新结束后,恢复原始默认状态
mHeaderTextView.setText(
"下拉刷新"
);
mHeaderProgressBar.setVisibility(View.INVISIBLE);
mHeaderPullDownImageView.setVisibility(View.VISIBLE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,
mSimpleDateFormat.format(
new
Date())));
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
0
,
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
mPullRefreshState = NONE_PULL_REFRESH;
setSelection(
1
);
break
;
default
:
break
;
}
}
};
|
为了一下子看的明确,我把效果中的数据处理代码也贴出来了。
8. 切入数据加载过程
上面数据后台处理我们用sleep(2000)来处理,实际处理中,作为公共组件,我们也不好把具体代码直接写在这里,我们需要一个更灵活的分离:
(1). 定义接口
(2). 注入接口
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
|
//定义接口
public
interface
RefreshListener {
Object refreshing();
//加载数据
void
refreshed(Object obj);
//外部可扩展加载完成后的操作
}
//注入接口
private
Object mRefreshObject =
null
;
//传值
private
RefreshListener mRefreshListener =
null
;
public
void
setOnRefreshListener(RefreshListener refreshListener) {
this
.mRefreshListener = refreshListener;
}
//我们需要重写上面的mHandler如下代码
case
REFRESH_BACED:
... ...
new
Thread() {
public
void
run() {
if
(mRefreshListener !=
null
) {
mRefreshObject = mRefreshListener.refreshing();
}
Message msg = mHandler.obtainMessage();
msg.what = REFRESH_DONE;
mHandler.sendMessage(msg);
};
}.start();
break
;
case
REFRESH_DONE:
... ...
mPullRefreshState = NONE_PULL_REFRESH;
setSelection(
1
);
if
(mRefreshListener !=
null
) {
mRefreshListener.refreshed(mRefreshObject);
}
break
;
|
在其他地方我们就可以不修改这个listview组件的代码,使用如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
xxx
implements
RefreshListener{
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
//类似如下
((RefreshListView) listView).setOnRefreshListener(
this
);
}
@Override
public
Object refreshing() {
String result =
null
;
//result = FileUtils.readTextFile(file);
return
result;
}
@Override
public
void
refreshed(Object obj) {
if
(obj !=
null
) {
//扩展操作
}
};
}
|
很方便了。
9. 扩展"更多"功能
下拉刷新之外,我们也可以通过相同方法使用FooterView切入底部"更多"过程,这里我就不详细说明了
10. 源码
上面的每段代码都看做是"零部件",需要组合一下。
因为我们上面实现了下拉刷新,还增加了"更多"功能,我们直接命名这个类为RefreshListView吧:
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
package
com.tianxia.lib.baseworld.widget;
import
java.text.SimpleDateFormat;
import
java.util.Date;
import
android.content.Context;
import
android.os.Handler;
import
android.os.Message;
import
android.util.AttributeSet;
import
android.view.LayoutInflater;
import
android.view.MotionEvent;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.AbsListView;
import
android.widget.AbsListView.OnScrollListener;
import
android.widget.ImageView;
import
android.widget.LinearLayout;
import
android.widget.ListAdapter;
import
android.widget.ListView;
import
android.widget.ProgressBar;
import
android.widget.TextView;
import
com.tianxia.lib.baseworld.R;
/**
* 下拉刷新,底部更多
*
*/
public
class
RefreshListView
extends
ListView
implements
OnScrollListener{
private
float
mDownY;
private
float
mMoveY;
private
int
mHeaderHeight;
private
int
mCurrentScrollState;
private
final
static
int
NONE_PULL_REFRESH =
0
;
//正常状态
private
final
static
int
ENTER_PULL_REFRESH =
1
;
//进入下拉刷新状态
private
final
static
int
OVER_PULL_REFRESH =
2
;
//进入松手刷新状态
private
final
static
int
EXIT_PULL_REFRESH =
3
;
//松手后反弹和加载状态
private
int
mPullRefreshState =
0
;
//记录刷新状态
private
final
static
int
REFRESH_BACKING =
0
;
//反弹中
private
final
static
int
REFRESH_BACED =
1
;
//达到刷新界限,反弹结束后
private
final
static
int
REFRESH_RETURN =
2
;
//没有达到刷新界限,返回
private
final
static
int
REFRESH_DONE =
3
;
//加载数据结束
private
LinearLayout mHeaderLinearLayout =
null
;
private
LinearLayout mFooterLinearLayout =
null
;
private
TextView mHeaderTextView =
null
;
private
TextView mHeaderUpdateText =
null
;
private
ImageView mHeaderPullDownImageView =
null
;
private
ImageView mHeaderReleaseDownImageView =
null
;
private
ProgressBar mHeaderProgressBar =
null
;
private
TextView mFooterTextView =
null
;
private
ProgressBar mFooterProgressBar =
null
;
private
SimpleDateFormat mSimpleDateFormat;
private
Object mRefreshObject =
null
;
private
RefreshListener mRefreshListener =
null
;
public
void
setOnRefreshListener(RefreshListener refreshListener) {
this
.mRefreshListener = refreshListener;
}
public
RefreshListView(Context context) {
this
(context,
null
);
}
public
RefreshListView(Context context, AttributeSet attrs) {
super
(context, attrs);
init(context);
}
void
init(
final
Context context) {
mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header,
null
);
addHeaderView(mHeaderLinearLayout);
mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);
mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);
mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);
mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);
mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);
mFooterLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_footer,
null
);
addFooterView(mFooterLinearLayout);
mFooterProgressBar = (ProgressBar) findViewById(R.id.refresh_list_footer_progressbar);
mFooterTextView = (TextView) mFooterLinearLayout.findViewById(R.id.refresh_list_footer_text);
mFooterLinearLayout.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
if
(context.getString(R.string.app_list_footer_more).equals(mFooterTextView.getText())) {
mFooterTextView.setText(R.string.app_list_footer_loading);
mFooterProgressBar.setVisibility(View.VISIBLE);
if
(mRefreshListener !=
null
) {
mRefreshListener.more();
}
}
}
});
setSelection(
1
);
setOnScrollListener(
this
);
measureView(mHeaderLinearLayout);
mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
mSimpleDateFormat =
new
SimpleDateFormat(
"yyyy-MM-dd hh:mm"
);
mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(
new
Date())));
}
@Override
public
boolean
onTouchEvent(MotionEvent ev) {
switch
(ev.getAction()) {
case
MotionEvent.ACTION_DOWN:
mDownY = ev.getY();
break
;
case
MotionEvent.ACTION_MOVE:
mMoveY = ev.getY();
if
(mPullRefreshState == OVER_PULL_REFRESH) {
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(
int
)((mMoveY - mDownY)/
3
),
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
}
break
;
case
MotionEvent.ACTION_UP:
//when you action up, it will do these:
//1. roll back util header topPadding is 0
//2. hide the header by setSelection(1)
if
(mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) {
new
Thread() {
public
void
run() {
Message msg;
while
(mHeaderLinearLayout.getPaddingTop() >
1
) {
msg = mHandler.obtainMessage();
msg.what = REFRESH_BACKING;
mHandler.sendMessage(msg);
try
{
sleep(
5
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
msg = mHandler.obtainMessage();
if
(mPullRefreshState == OVER_PULL_REFRESH) {
msg.what = REFRESH_BACED;
}
else
{
msg.what = REFRESH_RETURN;
}
mHandler.sendMessage(msg);
};
}.start();
}
break
;
}
return
super
.onTouchEvent(ev);
}
@Override
public
void
onScroll(AbsListView view,
int
firstVisibleItem,
int
visibleItemCount,
int
totalItemCount) {
if
(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >=
0
&& mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
//进入且仅进入下拉刷新状态
if
(mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = ENTER_PULL_REFRESH;
}
}
else
if
(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem ==
0
&& (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {
//下拉达到界限,进入松手刷新状态
if
(mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = OVER_PULL_REFRESH;
mDownY = mMoveY;
//为下拉1/3折扣效果记录开始位置
mHeaderTextView.setText(
"松手刷新"
);
//显示松手刷新
mHeaderPullDownImageView.setVisibility(View.GONE);
//隐藏"下拉刷新"
mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);
//显示向上的箭头
}
}
else
if
(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem !=
0
) {
//不刷新了
if
(mPullRefreshState == ENTER_PULL_REFRESH) {
mPullRefreshState = NONE_PULL_REFRESH;
}
}
else
if
(mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem ==
0
) {
//飞滑状态,不能显示出header,也不能影响正常的飞滑
//只在正常情况下才纠正位置
if
(mPullRefreshState == NONE_PULL_REFRESH) {
setSelection(
1
);
}
}
}
@Override
public
void
onScrollStateChanged(AbsListView view,
int
scrollState) {
mCurrentScrollState = scrollState;
}
@Override
public
void
setAdapter(ListAdapter adapter) {
super
.setAdapter(adapter);
setSelection(
1
);
}
private
void
measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if
(p ==
null
) {
p =
new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int
childWidthSpec = ViewGroup.getChildMeasureSpec(
0
,
0
+
0
, p.width);
int
lpHeight = p.height;
int
childHeightSpec;
if
(lpHeight >
0
) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
}
else
{
childHeightSpec = MeasureSpec.makeMeasureSpec(
0
,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
private
Handler mHandler =
new
Handler(){
@Override
public
void
handleMessage(Message msg) {
switch
(msg.what) {
case
REFRESH_BACKING:
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(
int
) (mHeaderLinearLayout.getPaddingTop()*
0
.75f),
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
break
;
case
REFRESH_BACED:
mHeaderTextView.setText(
"正在加载..."
);
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderPullDownImageView.setVisibility(View.GONE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mPullRefreshState = EXIT_PULL_REFRESH;
new
Thread() {
public
void
run() {
if
(mRefreshListener !=
null
) {
mRefreshObject = mRefreshListener.refreshing();
}
Message msg = mHandler.obtainMessage();
msg.what = REFRESH_DONE;
mHandler.sendMessage(msg);
};
}.start();
break
;
case
REFRESH_RETURN:
mHeaderTextView.setText(
"下拉刷新"
);
mHeaderProgressBar.setVisibility(View.INVISIBLE);
mHeaderPullDownImageView.setVisibility(View.VISIBLE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
0
,
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
mPullRefreshState = NONE_PULL_REFRESH;
setSelection(
1
);
break
;
case
REFRESH_DONE:
mHeaderTextView.setText(
"下拉刷新"
);
mHeaderProgressBar.setVisibility(View.INVISIBLE);
mHeaderPullDownImageView.setVisibility(View.VISIBLE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,
mSimpleDateFormat.format(
new
Date())));
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
0
,
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
mPullRefreshState = NONE_PULL_REFRESH;
setSelection(
1
);
if
(mRefreshListener !=
null
) {
mRefreshListener.refreshed(mRefreshObject);
}
break
;
default
:
break
;
}
}
};
public
interface
RefreshListener {
Object refreshing();
void
refreshed(Object obj);
void
more();
}
public
void
finishFootView() {
mFooterProgressBar.setVisibility(View.GONE);
mFooterTextView.setText(R.string.app_list_footer_more);
}
public
void
addFootView() {
if
(getFooterViewsCount() ==
0
) {
addFooterView(mFooterLinearLayout);
}
}
public
void
removeFootView() {
removeFooterView(mFooterLinearLayout);
}
}
|