Android 下拉刷新,上拉加载更多控件--支持ListView,GridView和ScrollView
由于这个文章比较长点,这里简单贴点代码,详细说明还是看原文吧
主要源码
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
|
package
com.miloisbadboy.view;
import
android.content.Context;
import
android.util.AttributeSet;
import
android.util.DisplayMetrics;
import
android.util.Log;
import
android.view.LayoutInflater;
import
android.view.MotionEvent;
import
android.view.View;
import
android.view.ViewGroup;
import
android.view.animation.LinearInterpolator;
import
android.view.animation.RotateAnimation;
import
android.widget.AdapterView;
import
android.widget.ImageView;
import
android.widget.LinearLayout;
import
android.widget.ProgressBar;
import
android.widget.ScrollView;
import
android.widget.TextView;
import
com.miloisbadboy.R;
public
class
PullToRefreshView
extends
LinearLayout {
private
static
final
String TAG =
"PullToRefreshView"
;
// refresh states
private
static
final
int
PULL_TO_REFRESH =
2
;
private
static
final
int
RELEASE_TO_REFRESH =
3
;
private
static
final
int
REFRESHING =
4
;
// pull state
private
static
final
int
PULL_UP_STATE =
0
;
private
static
final
int
PULL_DOWN_STATE =
1
;
/**
* last y
*/
private
int
mLastMotionY;
/**
* lock
*/
private
boolean
mLock;
/**
* header view
*/
private
View mHeaderView;
/**
* footer view
*/
private
View mFooterView;
/**
* list or grid
*/
private
AdapterView<?> mAdapterView;
/**
* scrollview
*/
private
ScrollView mScrollView;
/**
* header view height
*/
private
int
mHeaderViewHeight;
/**
* footer view height
*/
private
int
mFooterViewHeight;
/**
* header view image
*/
private
ImageView mHeaderImageView;
/**
* footer view image
*/
private
ImageView mFooterImageView;
/**
* header tip text
*/
private
TextView mHeaderTextView;
/**
* footer tip text
*/
private
TextView mFooterTextView;
/**
* header refresh time
*/
private
TextView mHeaderUpdateTextView;
/**
* footer refresh time
*/
// private TextView mFooterUpdateTextView;
/**
* header progress bar
*/
private
ProgressBar mHeaderProgressBar;
/**
* footer progress bar
*/
private
ProgressBar mFooterProgressBar;
/**
* layout inflater
*/
private
LayoutInflater mInflater;
/**
* header view current state
*/
private
int
mHeaderState;
/**
* footer view current state
*/
private
int
mFooterState;
/**
* pull state,pull up or pull down;PULL_UP_STATE or PULL_DOWN_STATE
*/
private
int
mPullState;
/**
* 变为向下的箭头,改变箭头方向
*/
private
RotateAnimation mFlipAnimation;
/**
* 变为逆向的箭头,旋转
*/
private
RotateAnimation mReverseFlipAnimation;
/**
* footer refresh listener
*/
private
OnFooterRefreshListener mOnFooterRefreshListener;
/**
* footer refresh listener
*/
private
OnHeaderRefreshListener mOnHeaderRefreshListener;
/**
* last update time
*/
private
String mLastUpdateTime;
public
PullToRefreshView(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
public
PullToRefreshView(Context context) {
super
(context);
init();
}
/**
* init
*
* @description
* @param context
*/
private
void
init() {
// Load all of the animations we need in code rather than through XML
mFlipAnimation =
new
RotateAnimation(
0
, -
180
, RotateAnimation.RELATIVE_TO_SELF,
0
.5f,
RotateAnimation.RELATIVE_TO_SELF,
0
.5f);
mFlipAnimation.setInterpolator(
new
LinearInterpolator());
mFlipAnimation.setDuration(
250
);
mFlipAnimation.setFillAfter(
true
);
mReverseFlipAnimation =
new
RotateAnimation(-
180
,
0
, RotateAnimation.RELATIVE_TO_SELF,
0
.5f, RotateAnimation.RELATIVE_TO_SELF,
0
.5f);
mReverseFlipAnimation.setInterpolator(
new
LinearInterpolator());
mReverseFlipAnimation.setDuration(
250
);
mReverseFlipAnimation.setFillAfter(
true
);
mInflater = LayoutInflater.from(getContext());
// header view 在此添加,保证是第一个添加到linearlayout的最上端
addHeaderView();
}
private
void
addHeaderView() {
// header view
mHeaderView = mInflater.inflate(R.layout.refresh_header,
this
,
false
);
mHeaderImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_refresh_image);
mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);
mHeaderUpdateTextView = (TextView) mHeaderView
.findViewById(R.id.pull_to_refresh_updated_at);
mHeaderProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);
// header layout
measureView(mHeaderView);
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
LayoutParams params =
new
LayoutParams(LayoutParams.MATCH_PARENT, mHeaderViewHeight);
// 设置topMargin的值为负的header View高度,即将其隐藏在最上方
params.topMargin = -(mHeaderViewHeight);
// mHeaderView.setLayoutParams(params1);
addView(mHeaderView, params);
}
private
void
addFooterView() {
// footer view
mFooterView = mInflater.inflate(R.layout.refresh_footer,
this
,
false
);
mFooterImageView = (ImageView) mFooterView.findViewById(R.id.pull_to_load_image);
mFooterTextView = (TextView) mFooterView.findViewById(R.id.pull_to_load_text);
mFooterProgressBar = (ProgressBar) mFooterView.findViewById(R.id.pull_to_load_progress);
// footer layout
measureView(mFooterView);
mFooterViewHeight = mFooterView.getMeasuredHeight();
LayoutParams params =
new
LayoutParams(LayoutParams.MATCH_PARENT, mFooterViewHeight);
//int top = getHeight();
//params.topMargin =getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0;
// getHeight()什么时候会赋值,稍候再研究一下
// 由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏
addView(mFooterView, params);
}
@Override
protected
void
onFinishInflate() {
super
.onFinishInflate();
// footer view 在此添加保证添加到linearlayout中的最后
addFooterView();
initContentAdapterView();
}
/**
* init AdapterView like ListView,GridView and so on;or init ScrollView
*
*/
private
void
initContentAdapterView() {
int
count = getChildCount();
if
(count <
3
) {
throw
new
IllegalArgumentException(
"this layout must contain 3 child views,and AdapterView or ScrollView must in the second position!"
);
}
View view =
null
;
for
(
int
i=
0
;i<count-
1
;++i){
view = getChildAt(i);
if
(view
instanceof
AdapterView<?>) {
mAdapterView = (AdapterView<?>) view;
}
if
(view
instanceof
ScrollView) {
// finish later
mScrollView = (ScrollView)view;
}
}
if
(mAdapterView ==
null
&& mScrollView==
null
) {
throw
new
IllegalArgumentException(
"must contain a AdapterView or ScrollView in this layout!"
);
}
}
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);
}
@Override
public
boolean
onInterceptTouchEvent(MotionEvent e) {
int
y = (
int
) e.getRawY();
switch
(e.getAction()) {
case
MotionEvent.ACTION_DOWN:
// 首先拦截down事件,记录y坐标
mLastMotionY = y;
break
;
case
MotionEvent.ACTION_MOVE:
// deltaY > 0 是向下运动,< 0是向上运动
int
deltaY = y - mLastMotionY;
if
(isRefreshViewScroll(deltaY)) {
return
true
;
}
break
;
case
MotionEvent.ACTION_UP:
case
MotionEvent.ACTION_CANCEL:
break
;
}
return
false
;
}
/*
* 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return
* false)则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理)
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mLock) {
return true;
}
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// onInterceptTouchEvent已经记录
// mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaY = y - mLastMotionY;
if (mPullState == PULL_DOWN_STATE) {
// PullToRefreshView执行下拉
Log.i(TAG, " pull down!parent view move!");
headerPrepareToRefresh(deltaY);
// setHeaderPadding(-mHeaderViewHeight);
} else if (mPullState == PULL_UP_STATE) {
// PullToRefreshView执行上拉
Log.i(TAG, "pull up!parent view move!");
footerPrepareToRefresh(deltaY);
}
mLastMotionY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
int topMargin = getHeaderTopMargin();
if (mPullState == PULL_DOWN_STATE) {
if (topMargin >= 0) {
// 开始刷新
headerRefreshing();
} else {
// 还没有执行刷新,重新隐藏
setHeaderTopMargin(-mHeaderViewHeight);
}
} else if (mPullState == PULL_UP_STATE) {
if(Math.abs(topMargin)>=mHeaderViewHeight+mFooterViewHeight){
// 开始执行footer 刷新
footerRefreshing();
} else {
// 还没有执行刷新,重新隐藏
setHeaderTopMargin(-mHeaderViewHeight);
}
}
break;
}
return super.onTouchEvent(event);
}
/**
* 是否应该到了父View,即PullToRefreshView滑动
*
* @param deltaY
* , deltaY > 0 是向下运动,< 0是向上运动
* @return
*/
private boolean isRefreshViewScroll(int deltaY) {
if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {
return false;
}
//对于ListView 以及GridView
if (mAdapterView != null) {
View child = mAdapterView.getChildAt(0);
if (child == null) {
// 如果mAdapterView中没有数据,不拦截
// 可以考虑返回true
return false;
}
// 子view(ListView or GridView)滑动到最顶端
if (deltaY > 0 && child.getTop() == 0) {
mPullState = PULL_DOWN_STATE;
return true;
} else if (deltaY < 0) {
View lastChild = mAdapterView.getChildAt(mAdapterView.getChildCount() - 1);
if (lastChild == null) {
// 如果mAdapterView中没有数据,不拦截
return false;
}
// 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,
// 等于父View的高度说明mAdapterView已经滑动到最后
if (lastChild.getBottom() <= getHeight()) {
mPullState = PULL_UP_STATE;
return true;
}
}
}
//对于ScrollView
if(mScrollView!=null){
// 子scroll view滑动到最顶端
View child = mScrollView.getChildAt(0);
if (deltaY > 0 && mScrollView.getScrollY() == 0) {
mPullState = PULL_DOWN_STATE;
return true;
} else if (deltaY < 0 && child.getMeasuredHeight()<=getHeight()+mScrollView.getScrollY()) {
mPullState = PULL_UP_STATE;
return true;
}
}
return false;
}
/**
* header 准备刷新,手指移动过程,还没有释放
*
* @param deltaY
* ,手指滑动的距离
*/
private void headerPrepareToRefresh(int deltaY) {
int newTopMargin = changingHeaderViewTopMargin(deltaY);
// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态
if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {
mHeaderTextView.setText(R.string.pull_to_refresh_release_label);
mHeaderUpdateTextView.setVisibility(View.VISIBLE);
mHeaderImageView.clearAnimation();
mHeaderImageView.startAnimation(mFlipAnimation);
mHeaderState = RELEASE_TO_REFRESH;
}
}
/**
* footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view
* 高度是一样,都是通过修改header view的topmargin的值来达到
*
* @param deltaY
* ,手指滑动的距离
*/
private void footerPrepareToRefresh(int deltaY) {
int newTopMargin = changingHeaderViewTopMargin(deltaY);
//如果header view topMargin 的绝对值大于或等于header + footer 的高度
//说明footer view 完全显示出来了,修改footer view 的提示状态
if(Math.abs(newTopMargin)>=(mHeaderViewHeight+mFooterViewHeight)
&& mFooterState != RELEASE_TO_REFRESH){
mFooterTextView.setText(R.string.pull_to_refresh_footer_release_label);
mFooterImageView.clearAnimation();
mFooterImageView.startAnimation(mFlipAnimation);
mFooterState = RELEASE_TO_REFRESH;
}
}
/**
* 修改Header view top margin的值
* @description
* @param deltaY
* @return
*/
private int changingHeaderViewTopMargin(int deltaY){
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
float newTopMargin = params.topMargin + deltaY * 0.3f;
params.topMargin = (int) newTopMargin;
mHeaderView.setLayoutParams(params);
invalidate();
return params.topMargin;
}
/**
* header refreshing
*
*/
private void headerRefreshing() {
mHeaderState = REFRESHING;
setHeaderTopMargin(0);
mHeaderImageView.setVisibility(View.GONE);
mHeaderImageView.clearAnimation();
mHeaderImageView.setImageDrawable(null);
mHeaderProgressBar.setVisibility(View.VISIBLE);
mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);
if (mOnHeaderRefreshListener != null) {
mOnHeaderRefreshListener.onHeaderRefresh(this);
}
}
/**
* footer refreshing
*
*/
private void footerRefreshing() {
mFooterState = REFRESHING;
int top = mHeaderViewHeight + mFooterViewHeight;
setHeaderTopMargin(-top);
mFooterImageView.setVisibility(View.GONE);
mFooterImageView.clearAnimation();
mFooterImageView.setImageDrawable(null);
mFooterProgressBar.setVisibility(View.VISIBLE);
mFooterTextView.setText(R.string.pull_to_refresh_footer_refreshing_label);
if (mOnFooterRefreshListener != null) {
mOnFooterRefreshListener.onFooterRefresh(this);
}
}
/**
* 设置header view 的topMargin的值
*
* @description
* @param topMargin
* ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了
*/
private void setHeaderTopMargin(int topMargin) {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
params.topMargin = topMargin;
mHeaderView.setLayoutParams(params);
invalidate();
}
/**
* header view 完成更新后恢复初始状态
*
*/
public void onHeaderRefreshComplete() {
setHeaderTopMargin(-mHeaderViewHeight);
mHeaderImageView.setVisibility(View.VISIBLE);
mHeaderImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);
mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);
mHeaderProgressBar.setVisibility(View.GONE);
// mHeaderUpdateTextView.setText("");
mHeaderState = PULL_TO_REFRESH;
}
/**
* Resets the list to a normal state after a refresh.
* @param lastUpdated Last updated at.
*/
public void onHeaderRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onHeaderRefreshComplete();
}
/**
* footer view 完成更新后恢复初始状态
*/
public void onFooterRefreshComplete() {
setHeaderTopMargin(-mHeaderViewHeight);
mFooterImageView.setVisibility(View.VISIBLE);
mFooterImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow_up);
mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);
mFooterProgressBar.setVisibility(View.GONE);
// mHeaderUpdateTextView.setText("");
mFooterState = PULL_TO_REFRESH;
}
/**
* Set a text to represent when the list was last updated.
* @param lastUpdated Last updated at.
*/
public void setLastUpdated(CharSequence lastUpdated) {
if (lastUpdated != null) {
mHeaderUpdateTextView.setVisibility(View.VISIBLE);
mHeaderUpdateTextView.setText(lastUpdated);
} else {
mHeaderUpdateTextView.setVisibility(View.GONE);
}
}
/**
* 获取当前header view 的topMargin
*
* @description
*/
private int getHeaderTopMargin() {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
return params.topMargin;
}
/**
* lock
*
*/
private void lock() {
mLock = true;
}
/**
* unlock
*
*/
private void unlock() {
mLock = false;
}
/**
* set headerRefreshListener
*
* @description
* @param headerRefreshListener
*/
public void setOnHeaderRefreshListener(OnHeaderRefreshListener headerRefreshListener) {
mOnHeaderRefreshListener = headerRefreshListener;
}
public void setOnFooterRefreshListener(OnFooterRefreshListener footerRefreshListener) {
mOnFooterRefreshListener = footerRefreshListener;
}
/**
* Interface definition for a callback to be invoked when list/grid footer
* view should be refreshed.
*/
public interface OnFooterRefreshListener {
public void onFooterRefresh(PullToRefreshView view);
}
/**
* Interface definition for a callback to be invoked when list/grid header
* view should be refreshed.
*/
public
interface
OnHeaderRefreshListener {
public
void
onHeaderRefresh(PullToRefreshView view);
}
}
|