`
xumingrencai
  • 浏览: 1179802 次
文章分类
社区版块
存档分类
最新评论

android中listView下拉刷新

 
阅读更多

转自:http://www.cnblogs.com/xiaoran1129/archive/2012/07/04/2576221.html

Android的ListView是应用最广的一个组件,功能强大,扩展性灵活(不局限于ListView本身一个类),前面的文章有介绍分组,拖拽,3D立体,游标,圆角,而今天我们要介绍的是另外一个扩展ListView:下拉刷新的ListView。
下拉刷新界面最初流行于iphone应用界面,如图:


然后在Android中也逐渐被应用,比如微博,资讯类。
所以,今天要实现的结果应该也是类似的,先贴出最终完成效果,如下图,接下来我们一步一步实现。

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
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ProgressBarandroid: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>
<ImageViewandroid: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"/>
<ImageViewandroid: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"/>
<RelativeLayoutandroid:layout_width="180dip"
android:layout_height="wrap_content">
<TextViewandroid: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"/>
<TextViewandroid: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
privateLinearLayout mHeaderLinearLayout = null;
privateTextView mHeaderTextView = null;
privateTextView mHeaderUpdateText = null;
privateImageView mHeaderPullDownImageView = null;
privateImageView mHeaderReleaseDownImageView = null;
privateProgressBar mHeaderProgressBar = null;
publicRefreshListView(Context context) {
this(context, null);
}
publicRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
voidinit(finalContext 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);
  setOnScrollListener(this);
}默认就显示完成了。

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一下
privatevoidmeasureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if(p == null) {
p = newViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
intchildWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+ 0, p.width);
intlpHeight = p.height;
intchildHeightSpec;
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
privateintmHeaderHeight;
voidinit(finalContext context) {
... ...
measureView(mHeaderLinearLayout);
mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
}
  后面我们就会用到这个mHeaderHeight.

5. scrollState监听记录
scrollState有3种,使用onScrollStateChanged()方法监听记录。

1
2
3
4
5
privateintmCurrentScrollState;
@Override
publicvoidonScrollStateChanged(AbsListView view, intscrollState) {
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
privatefinalstaticintNONE_PULL_REFRESH = 0; //正常状态
privatefinalstaticintENTER_PULL_REFRESH = 1; //进入下拉刷新状态
privatefinalstaticintOVER_PULL_REFRESH = 2; //进入松手刷新状态
privatefinalstaticintEXIT_PULL_REFRESH = 3; //松手后反弹后加载状态
privateintmPullRefreshState = 0; //记录刷新状态
@Override
publicvoidonScroll(AbsListView view, intfirstVisibleItem, intvisibleItemCount, inttotalItemCount) {
if(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem == 0
&& (mHeaderLinearLayout.getBottom() >= 0&& mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
//进入且仅进入下拉刷新状态
if(mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = ENTER_PULL_REFRESH;
}
} elseif(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);
}
} elseif(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {
//不刷新了
if(mPullRefreshState == ENTER_PULL_REFRESH) {
mPullRefreshState = NONE_PULL_REFRESH;
}
} elseif(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
privatefloatmDownY;
privatefloatmMoveY;
@Override
publicbooleanonTouchEvent(MotionEvent ev) {
switch(ev.getAction()) {
caseMotionEvent.ACTION_DOWN:
//记下按下位置
//改变
mDownY = ev.getY();
break;
caseMotionEvent.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;
caseMotionEvent.ACTION_UP:
... ...
break;
}
returnsuper.onTouchEvent(ev);
}
//重复贴出下面这段需要注意的代码
@Override
publicvoidonScroll(AbsListView view, intfirstVisibleItem, intvisibleItemCount, inttotalItemCount) {
... ...
elseif(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数据处理,为方便我们定义如下常量
privatefinalstaticintREFRESH_BACKING = 0; //反弹中
privatefinalstaticintREFRESH_BACED = 1; //达到刷新界限,反弹结束后
privatefinalstaticintREFRESH_RETURN = 2; //没有达到刷新界限,返回
privatefinalstaticintREFRESH_DONE = 3; //加载数据结束
@Override
publicbooleanonTouchEvent(MotionEvent ev) {
switch(ev.getAction()) {
... ...
caseMotionEvent.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) {
newThread() {
publicvoidrun() {
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;
}
returnsuper.onTouchEvent(ev);
}
privateHandler mHandler = newHandler(){
@Override
publicvoidhandleMessage(Message msg) {
switch(msg.what) {
caseREFRESH_BACKING:
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(int) (mHeaderLinearLayout.getPaddingTop()*0.75f),
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
break;
caseREFRESH_BACED:
mHeaderTextView.setText("正在加载...");
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderPullDownImageView.setVisibility(View.GONE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mPullRefreshState = EXIT_PULL_REFRESH;
newThread() {
publicvoidrun() {
sleep(2000);//处理后台加载数据
Message msg = mHandler.obtainMessage();
msg.what = REFRESH_DONE;
//通知主线程加载数据完成
mHandler.sendMessage(msg);
};
}.start();
break;
caseREFRESH_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;
caseREFRESH_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(newDate())));
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
//定义接口
publicinterfaceRefreshListener {
Object refreshing(); //加载数据
voidrefreshed(Object obj); //外部可扩展加载完成后的操作
}
//注入接口
privateObject mRefreshObject = null; //传值
privateRefreshListener mRefreshListener = null;
publicvoidsetOnRefreshListener(RefreshListener refreshListener) {
this.mRefreshListener = refreshListener;
}
//我们需要重写上面的mHandler如下代码
caseREFRESH_BACED:
... ...
newThread() {
publicvoidrun() {
if(mRefreshListener != null) {
mRefreshObject = mRefreshListener.refreshing();
}
Message msg = mHandler.obtainMessage();
msg.what = REFRESH_DONE;
mHandler.sendMessage(msg);
};
}.start();
break;
caseREFRESH_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
publicxxx implementsRefreshListener{
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//类似如下
((RefreshListView) listView).setOnRefreshListener(this);
}
@Override
publicObject refreshing() {
String result = null;
//result = FileUtils.readTextFile(file);
returnresult;
}
@Override
publicvoidrefreshed(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
packagecom.tianxia.lib.baseworld.widget;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.util.AttributeSet;
importandroid.view.LayoutInflater;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.AbsListView;
importandroid.widget.AbsListView.OnScrollListener;
importandroid.widget.ImageView;
importandroid.widget.LinearLayout;
importandroid.widget.ListAdapter;
importandroid.widget.ListView;
importandroid.widget.ProgressBar;
importandroid.widget.TextView;
importcom.tianxia.lib.baseworld.R;
/**
* 下拉刷新,底部更多
*
*/
publicclassRefreshListView extendsListView implementsOnScrollListener{
privatefloatmDownY;
privatefloatmMoveY;
privateintmHeaderHeight;
privateintmCurrentScrollState;
privatefinalstaticintNONE_PULL_REFRESH = 0; //正常状态
privatefinalstaticintENTER_PULL_REFRESH = 1; //进入下拉刷新状态
privatefinalstaticintOVER_PULL_REFRESH = 2; //进入松手刷新状态
privatefinalstaticintEXIT_PULL_REFRESH = 3; //松手后反弹和加载状态
privateintmPullRefreshState = 0; //记录刷新状态
privatefinalstaticintREFRESH_BACKING = 0; //反弹中
privatefinalstaticintREFRESH_BACED = 1; //达到刷新界限,反弹结束后
privatefinalstaticintREFRESH_RETURN = 2; //没有达到刷新界限,返回
privatefinalstaticintREFRESH_DONE = 3; //加载数据结束
privateLinearLayout mHeaderLinearLayout = null;
privateLinearLayout mFooterLinearLayout = null;
privateTextView mHeaderTextView = null;
privateTextView mHeaderUpdateText = null;
privateImageView mHeaderPullDownImageView = null;
privateImageView mHeaderReleaseDownImageView = null;
privateProgressBar mHeaderProgressBar = null;
privateTextView mFooterTextView = null;
privateProgressBar mFooterProgressBar = null;
privateSimpleDateFormat mSimpleDateFormat;
privateObject mRefreshObject = null;
privateRefreshListener mRefreshListener = null;
publicvoidsetOnRefreshListener(RefreshListener refreshListener) {
this.mRefreshListener = refreshListener;
}
publicRefreshListView(Context context) {
this(context, null);
}
publicRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
voidinit(finalContext 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(newOnClickListener() {
@Override
publicvoidonClick(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 = newSimpleDateFormat("yyyy-MM-dd hh:mm");
mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(newDate())));
}
@Override
publicbooleanonTouchEvent(MotionEvent ev) {
switch(ev.getAction()) {
caseMotionEvent.ACTION_DOWN:
mDownY = ev.getY();
break;
caseMotionEvent.ACTION_MOVE:
mMoveY = ev.getY();
if(mPullRefreshState == OVER_PULL_REFRESH) {
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(int)((mMoveY - mDownY)/3),
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
}
break;
caseMotionEvent.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) {
newThread() {
publicvoidrun() {
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;
}
returnsuper.onTouchEvent(ev);
}
@Override
publicvoidonScroll(AbsListView view, intfirstVisibleItem, intvisibleItemCount, inttotalItemCount) {
if(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& firstVisibleItem == 0
&& (mHeaderLinearLayout.getBottom() >= 0&& mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
//进入且仅进入下拉刷新状态
if(mPullRefreshState == NONE_PULL_REFRESH) {
mPullRefreshState = ENTER_PULL_REFRESH;
}
} elseif(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);//显示向上的箭头
}
} elseif(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {
//不刷新了
if(mPullRefreshState == ENTER_PULL_REFRESH) {
mPullRefreshState = NONE_PULL_REFRESH;
}
} elseif(mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) {
//飞滑状态,不能显示出header,也不能影响正常的飞滑
//只在正常情况下才纠正位置
if(mPullRefreshState == NONE_PULL_REFRESH) {
setSelection(1);
}
}
}
@Override
publicvoidonScrollStateChanged(AbsListView view, intscrollState) {
mCurrentScrollState = scrollState;
}
@Override
publicvoidsetAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
setSelection(1);
}
privatevoidmeasureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if(p == null) {
p = newViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
intchildWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+ 0, p.width);
intlpHeight = p.height;
intchildHeightSpec;
if(lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else{
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
privateHandler mHandler = newHandler(){
@Override
publicvoidhandleMessage(Message msg) {
switch(msg.what) {
caseREFRESH_BACKING:
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
(int) (mHeaderLinearLayout.getPaddingTop()*0.75f),
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
break;
caseREFRESH_BACED:
mHeaderTextView.setText("正在加载...");
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderPullDownImageView.setVisibility(View.GONE);
mHeaderReleaseDownImageView.setVisibility(View.GONE);
mPullRefreshState = EXIT_PULL_REFRESH;
newThread() {
publicvoidrun() {
if(mRefreshListener != null) {
mRefreshObject = mRefreshListener.refreshing();
}
Message msg = mHandler.obtainMessage();
msg.what = REFRESH_DONE;
mHandler.sendMessage(msg);
};
}.start();
break;
caseREFRESH_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;
caseREFRESH_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(newDate())));
mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
0,
mHeaderLinearLayout.getPaddingRight(),
mHeaderLinearLayout.getPaddingBottom());
mPullRefreshState = NONE_PULL_REFRESH;
setSelection(1);
if(mRefreshListener != null) {
mRefreshListener.refreshed(mRefreshObject);
}
break;
default:
break;
}
}
};
publicinterfaceRefreshListener {
Object refreshing();
voidrefreshed(Object obj);
voidmore();
}
publicvoidfinishFootView() {
mFooterProgressBar.setVisibility(View.GONE);
mFooterTextView.setText(R.string.app_list_footer_more);
}
publicvoidaddFootView() {
if(getFooterViewsCount() == 0) {
addFooterView(mFooterLinearLayout);
}
}
publicvoidremoveFootView() {
removeFooterView(mFooterLinearLayout);
}
}
11.小结

这个只是一个原型,无论代码风格和逻辑处理,我觉得还有改进的空间,我会在后续逐渐改善的。
以下是例子效果
https://github.com/openproject/world/blob/master/baseworld/src/com/tianxia/lib/baseworld/widget/RefreshListView.java
https://github.com/openproject/world/blob/master/healthworld/src/com/tianxia/app/healthworld/infomation/InfomationTabActivity.java
期待有建设性的意见改善这个实现。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics