服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android PullToRefreshLayout下拉刷新控件的终结者

Android PullToRefreshLayout下拉刷新控件的终结者

2021-06-26 22:45陈靖_ Android

这篇文章主要介绍了Android自定义控件实战中下拉刷新控件终结者PullToRefreshLayout的实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

       说到下拉刷新控件,网上版本有很多,很多软件也都有下拉刷新功能。有一个叫xlistview的,我看别人用过,没看过是咋实现的,看这名字估计是继承自listview修改的,不过效果看起来挺丑的,也没什么扩展性,太单调了。看了qq2014的列表下拉刷新,发现挺好看的,我喜欢,贴一下图看一下qq的下拉刷新效果:

                                          Android PullToRefreshLayout下拉刷新控件的终结者

    不错吧?嗯,是的。一看就知道实现方式不一样。咱们今天就来实现一个下拉刷新控件。由于有时候不仅仅是listview需要下拉刷新,expandablelistview和gridview也有这个需求,由于listview,gridview都是abslistview的子类,expandablelistview是listview的子类所以也是abslistview的子类。所以我的思路是自定义一个对所有abslistview的子类通用的下拉管理布局,叫pulltorefreshlayout,如果需要gridview,只需要在布局文件里将listview换成gridview就行了,expandablelistview也一样,不需要再继承什么gridview啊listview啊乱七八糟的。

Android PullToRefreshLayout下拉刷新控件的终结者

看上图,主要工作就是定义黑色大布局,红色部分是不下拉的时候的可见部分,可以是任意的abslistview的子类(gridview,listview,expandablelistview等等)。其实我已经写好了,先看一下效果图:

正常拉法:

               Android PullToRefreshLayout下拉刷新控件的终结者           

强迫症拉法:

                 Android PullToRefreshLayout下拉刷新控件的终结者

上面是listview的,下面是gridview的

                  Android PullToRefreshLayout下拉刷新控件的终结者

再来看一下expandablelistview的下拉刷新效果:

                              Android PullToRefreshLayout下拉刷新控件的终结者

可以看到,点击事件和长按事件都能正常触发而不会误触发,在使用expandablelistview的时候需要注意禁止展开时自动滚动,否则会出现bug。后面会提供demo源码下载,可以根据自己的需求去修改。

下面讲解pulltorefreshlayout的实现,在贴完整的源码之前先理解整个类的大概思路:

?
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
public class pulltorefreshlayout extends relativelayout implements ontouchlistener
{
  
 // 下拉的距离
 public float movedeltay = 0;
 // 是否可以下拉
 private boolean canpull = true;
  
  
 private void hidehead()
 {
  // 在这里开始异步隐藏下拉头,在松手的时候或这刷新完毕的时候隐藏
 }
 
  
 public void refreshfinish(int refreshresult)
 {
  // 完成刷新操作,显示刷新结果
 }
 
 private void changestate(int to)
 {
  // 改变当前所处的状态,有四个状态:下拉刷新、释放刷新、正在刷新、刷新完成
 }
 
 /*
  * (非 javadoc)由父控件决定是否分发事件,防止事件冲突
  *
  * @see android.view.viewgroup#dispatchtouchevent(android.view.motionevent)
  */
 @override
 public boolean dispatchtouchevent(motionevent ev)
 {
  switch (ev.getactionmasked())
  {
  case motionevent.action_down:
   /*手指按下的时候,无法判断是否将要下拉,所以这时候break让父类把down事件分发给子view
   记录按下的坐标*/
   break;
  case motionevent.action_move:
   /*如果往上滑动且movedetay==0则说明不在下拉,break继续将move事件分发给子view
   如果往下拉,则计算下拉的距离movedeltay,根据movedeltay重新layout子控件。但是
   由于down事件传到了子view,如果不清除子view的事件,会导致子view误触发长按事件和点击事件。所以在这里清除子view的事件回调。
   下拉超过一定的距离时,改变当前状态*/
   break;
  case motionevent.action_up:
   //根据当前状态执行刷新操作或者hidehead
  default:
   break;
  }
  // 事件分发交给父类
  return super.dispatchtouchevent(ev);
 }
 
  
 
 /*
  * (非 javadoc)绘制阴影效果,颜色值可以修改
  *
  * @see android.view.viewgroup#dispatchdraw(android.graphics.canvas)
  */
 @override
 protected void dispatchdraw(canvas canvas)
 {
  //在这里用一个渐变绘制分界线阴影
 }
 
  
 
 @override
 protected void onlayout(boolean changed, int l, int t, int r, int b)
 {
  //这个方法就是重新layout子view了,根据movedeltay来定位子view的位置
 }
 
  
 @override
 public boolean ontouch(view v, motionevent event)
 {
  //这个是ontouchlistener的方法,只判断abslistview的状态来决定是否canpull,除此之外不做其他处理
 }
}

可以看到,这里复写了viewgroup的dispatchtouchevent,这样就可以掌控事件的分发,如果不了解这个方法可以看一下这篇android事件分发、view事件listener全解析。之所以要控制事件分发是因为我们不可能知道手指down在abslistview上之后将往上滑还是往下拉,所以down事件会分发给abslistview的,但是在move的时候就需要看情况了,因为我们不想在下拉的同时abslistview也在滑动,所以在下拉的时候不分发move事件,但这样问题又来了,前面abslistview已经接收了down事件,如果这时候不分发move事件给它,它会触发长按事件或者点击事件,所以在这里还需要清除abslistview消息列表中的callback。
onlayout用于重新布置下拉头和abslistview的位置的,这个不难理解。

理解了大概思路之后,看一下pulltorefreshlayout完整的源码吧~

?
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
package com.jingchen.pulltorefresh;
 
import java.lang.reflect.field;
import java.util.timer;
import java.util.timertask;
 
import android.content.context;
import android.graphics.canvas;
import android.graphics.lineargradient;
import android.graphics.paint;
import android.graphics.paint.style;
import android.graphics.rectf;
import android.graphics.shader.tilemode;
import android.os.handler;
import android.os.message;
import android.util.attributeset;
import android.util.log;
import android.view.motionevent;
import android.view.view;
import android.view.view.ontouchlistener;
import android.view.viewgroup;
import android.view.animation.animationutils;
import android.view.animation.linearinterpolator;
import android.view.animation.rotateanimation;
import android.widget.abslistview;
import android.widget.relativelayout;
import android.widget.textview;
 
/**
 * 整个下拉刷新就这一个布局,用来管理两个子控件,其中一个是下拉头,另一个是包含内容的contentview(可以是abslistview的任何子类)
 *
 * @author 陈靖
 */
public class pulltorefreshlayout extends relativelayout implements ontouchlistener
{
 public static final string tag = "pulltorefreshlayout";
 // 下拉刷新
 public static final int pull_to_refresh = 0;
 // 释放刷新
 public static final int release_to_refresh = 1;
 // 正在刷新
 public static final int refreshing = 2;
 // 刷新完毕
 public static final int done = 3;
 // 当前状态
 private int state = pull_to_refresh;
 // 刷新回调接口
 private onrefreshlistener mlistener;
 // 刷新成功
 public static final int refresh_succeed = 0;
 // 刷新失败
 public static final int refresh_fail = 1;
 // 下拉头
 private view headview;
 // 内容
 private view contentview;
 // 按下y坐标,上一个事件点y坐标
 private float downy, lasty;
 // 下拉的距离
 public float movedeltay = 0;
 // 释放刷新的距离
 private float refreshdist = 200;
 private timer timer;
 private mytimertask mtask;
 // 回滚速度
 public float move_speed = 8;
 // 第一次执行布局
 private boolean islayout = false;
 // 是否可以下拉
 private boolean canpull = true;
 // 在刷新过程中滑动操作
 private boolean istouchinrefreshing = false;
 // 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化
 private float radio = 2;
 // 下拉箭头的转180°动画
 private rotateanimation rotateanimation;
 // 均匀旋转动画
 private rotateanimation refreshinganimation;
 // 下拉的箭头
 private view pullview;
 // 正在刷新的图标
 private view refreshingview;
 // 刷新结果图标
 private view stateimageview;
 // 刷新结果:成功或失败
 private textview statetextview;
 /**
  * 执行自动回滚的handler
  */
 handler updatehandler = new handler()
 {
 
  @override
  public void handlemessage(message msg)
  {
   // 回弹速度随下拉距离movedeltay增大而增大
   move_speed = (float) (8 + 5 * math.tan(math.pi / 2 / getmeasuredheight() * movedeltay));
   if (state == refreshing && movedeltay <= refreshdist && !istouchinrefreshing)
   {
    // 正在刷新,且没有往上推的话则悬停,显示"正在刷新..."
    movedeltay = refreshdist;
    mtask.cancel();
   }
   if (canpull)
    movedeltay -= move_speed;
   if (movedeltay <= 0)
   {
    // 已完成回弹
    movedeltay = 0;
    pullview.clearanimation();
    // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态
    if (state != refreshing)
     changestate(pull_to_refresh);
    mtask.cancel();
   }
   // 刷新布局,会自动调用onlayout
   requestlayout();
  }
 
 };
 
 public void setonrefreshlistener(onrefreshlistener listener)
 {
  mlistener = listener;
 }
 
 public pulltorefreshlayout(context context)
 {
  super(context);
  initview(context);
 }
 
 public pulltorefreshlayout(context context, attributeset attrs)
 {
  super(context, attrs);
  initview(context);
 }
 
 public pulltorefreshlayout(context context, attributeset attrs, int defstyle)
 {
  super(context, attrs, defstyle);
  initview(context);
 }
 
 private void initview(context context)
 {
  timer = new timer();
  mtask = new mytimertask(updatehandler);
  rotateanimation = (rotateanimation) animationutils.loadanimation(context, r.anim.reverse_anim);
  refreshinganimation = (rotateanimation) animationutils.loadanimation(context, r.anim.rotating);
  // 添加匀速转动动画
  linearinterpolator lir = new linearinterpolator();
  rotateanimation.setinterpolator(lir);
  refreshinganimation.setinterpolator(lir);
 }
 
 private void hidehead()
 {
  if (mtask != null)
  {
   mtask.cancel();
   mtask = null;
  }
  mtask = new mytimertask(updatehandler);
  timer.schedule(mtask, 0, 5);
 }
 
 /**
  * 完成刷新操作,显示刷新结果
  */
 public void refreshfinish(int refreshresult)
 {
  refreshingview.clearanimation();
  refreshingview.setvisibility(view.gone);
  switch (refreshresult)
  {
  case refresh_succeed:
   // 刷新成功
   stateimageview.setvisibility(view.visible);
   statetextview.settext(r.string.refresh_succeed);
   stateimageview.setbackgroundresource(r.drawable.refresh_succeed);
   break;
  case refresh_fail:
   // 刷新失败
   stateimageview.setvisibility(view.visible);
   statetextview.settext(r.string.refresh_fail);
   stateimageview.setbackgroundresource(r.drawable.refresh_failed);
   break;
  default:
   break;
  }
  // 刷新结果停留1秒
  new handler()
  {
   @override
   public void handlemessage(message msg)
   {
    state = pull_to_refresh;
    hidehead();
   }
  }.sendemptymessagedelayed(0, 1000);
 }
 
 private void changestate(int to)
 {
  state = to;
  switch (state)
  {
  case pull_to_refresh:
   // 下拉刷新
   stateimageview.setvisibility(view.gone);
   statetextview.settext(r.string.pull_to_refresh);
   pullview.clearanimation();
   pullview.setvisibility(view.visible);
   break;
  case release_to_refresh:
   // 释放刷新
   statetextview.settext(r.string.release_to_refresh);
   pullview.startanimation(rotateanimation);
   break;
  case refreshing:
   // 正在刷新
   pullview.clearanimation();
   refreshingview.setvisibility(view.visible);
   pullview.setvisibility(view.invisible);
   refreshingview.startanimation(refreshinganimation);
   statetextview.settext(r.string.refreshing);
   break;
  default:
   break;
  }
 }
 
 /*
  * (非 javadoc)由父控件决定是否分发事件,防止事件冲突
  *
  * @see android.view.viewgroup#dispatchtouchevent(android.view.motionevent)
  */
 @override
 public boolean dispatchtouchevent(motionevent ev)
 {
  switch (ev.getactionmasked())
  {
  case motionevent.action_down:
   downy = ev.gety();
   lasty = downy;
   if (mtask != null)
   {
    mtask.cancel();
   }
   /*
    * 触碰的地方位于下拉头布局,由于我们没有对下拉头做事件响应,这时候它会给咱返回一个false导致接下来的事件不再分发进来。
    * 所以我们不能交给父类分发,直接返回true
    */
   if (ev.gety() < movedeltay)
    return true;
   break;
  case motionevent.action_move:
   // canpull这个值在底下ontouch中会根据listview是否滑到顶部来改变,意思是是否可下拉
   if (canpull)
   {
    // 对实际滑动距离做缩小,造成用力拉的感觉
    movedeltay = movedeltay + (ev.gety() - lasty) / radio;
    if (movedeltay < 0)
     movedeltay = 0;
    if (movedeltay > getmeasuredheight())
     movedeltay = getmeasuredheight();
    if (state == refreshing)
    {
     // 正在刷新的时候触摸移动
     istouchinrefreshing = true;
    }
   }
   lasty = ev.gety();
   // 根据下拉距离改变比例
   radio = (float) (2 + 2 * math.tan(math.pi / 2 / getmeasuredheight() * movedeltay));
   requestlayout();
   if (movedeltay <= refreshdist && state == release_to_refresh)
   {
    // 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新
    changestate(pull_to_refresh);
   }
   if (movedeltay >= refreshdist && state == pull_to_refresh)
   {
    changestate(release_to_refresh);
   }
   if (movedeltay > 8)
   {
    // 防止下拉过程中误触发长按事件和点击事件
    clearcontentviewevents();
   }
   if (movedeltay > 0)
   {
    // 正在下拉,不让子控件捕获事件
    return true;
   }
   break;
  case motionevent.action_up:
   if (movedeltay > refreshdist)
    // 正在刷新时往下拉释放后下拉头不隐藏
    istouchinrefreshing = false;
   if (state == release_to_refresh)
   {
    changestate(refreshing);
    // 刷新操作
    if (mlistener != null)
     mlistener.onrefresh();
   } else
   {
 
   }
   hidehead();
  default:
   break;
  }
  // 事件分发交给父类
  return super.dispatchtouchevent(ev);
 }
 
 /**
  * 通过反射修改字段去掉长按事件和点击事件
  */
 private void clearcontentviewevents()
 {
  try
  {
   field[] fields = abslistview.class.getdeclaredfields();
   for (int i = 0; i < fields.length; i++)
    if (fields[i].getname().equals("mpendingcheckforlongpress"))
    {
     // mpendingcheckforlongpress是abslistview中的字段,通过反射获取并从消息列表删除,去掉长按事件
     fields[i].setaccessible(true);
     contentview.gethandler().removecallbacks((runnable) fields[i].get(contentview));
    } else if (fields[i].getname().equals("mtouchmode"))
    {
     // touch_mode_rest = -1, 这个可以去除点击事件
     fields[i].setaccessible(true);
     fields[i].set(contentview, -1);
    }
   // 去掉焦点
   ((abslistview) contentview).getselector().setstate(new int[]
   { 0 });
  } catch (exception e)
  {
   log.d(tag, "error : " + e.tostring());
  }
 }
 
 /*
  * (非 javadoc)绘制阴影效果,颜色值可以修改
  *
  * @see android.view.viewgroup#dispatchdraw(android.graphics.canvas)
  */
 @override
 protected void dispatchdraw(canvas canvas)
 {
  super.dispatchdraw(canvas);
  if (movedeltay == 0)
   return;
  rectf rectf = new rectf(0, 0, getmeasuredwidth(), movedeltay);
  paint paint = new paint();
  paint.setantialias(true);
  // 阴影的高度为26
  lineargradient lineargradient = new lineargradient(0, movedeltay, 0, movedeltay - 26, 0x66000000, 0x00000000, tilemode.clamp);
  paint.setshader(lineargradient);
  paint.setstyle(style.fill);
  // 在movedeltay处往上变淡
  canvas.drawrect(rectf, paint);
 }
 
 private void initview()
 {
  pullview = headview.findviewbyid(r.id.pull_icon);
  statetextview = (textview) headview.findviewbyid(r.id.state_tv);
  refreshingview = headview.findviewbyid(r.id.refreshing_icon);
  stateimageview = headview.findviewbyid(r.id.state_iv);
 }
 
 @override
 protected void onlayout(boolean changed, int l, int t, int r, int b)
 {
  if (!islayout)
  {
   // 这里是第一次进来的时候做一些初始化
   headview = getchildat(0);
   contentview = getchildat(1);
   // 给abslistview设置ontouchlistener
   contentview.setontouchlistener(this);
   islayout = true;
   initview();
   refreshdist = ((viewgroup) headview).getchildat(0).getmeasuredheight();
  }
  if (canpull)
  {
   // 改变子控件的布局
   headview.layout(0, (int) movedeltay - headview.getmeasuredheight(), headview.getmeasuredwidth(), (int) movedeltay);
   contentview.layout(0, (int) movedeltay, contentview.getmeasuredwidth(), (int) movedeltay + contentview.getmeasuredheight());
  }else super.onlayout(changed, l, t, r, b);
 }
 
 class mytimertask extends timertask
 {
  handler handler;
 
  public mytimertask(handler handler)
  {
   this.handler = handler;
  }
 
  @override
  public void run()
  {
   handler.sendmessage(handler.obtainmessage());
  }
 
 }
 
 @override
 public boolean ontouch(view v, motionevent event)
 {
  // 第一个item可见且滑动到顶部
  abslistview alv = null;
  try
  {
   alv = (abslistview) v;
  } catch (exception e)
  {
   log.d(tag, e.getmessage());
   return false;
  }
  if (alv.getcount() == 0)
  {
   // 没有item的时候也可以下拉刷新
   canpull = true;
  } else if (alv.getfirstvisibleposition() == 0 && alv.getchildat(0).gettop() >= 0)
  {
   // 滑到abslistview的顶部了
   canpull = true;
  } else
   canpull = false;
  return false;
 }
}

代码中的注释已经写的很清楚了。
既然pulltorefreshlayout已经写好了,接下来就来使用这个layout实现下拉刷新~

首先得写个onrefreshlistener接口来回调刷新操作:

?
1
2
3
public interface onrefreshlistener {
 void onrefresh();
}

就一个刷新操作的方法,待会儿让activity实现这个接口就可以在activity中执行刷新操作了。

看一下mainactivity的布局:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<com.jingchen.pulltorefresh.pulltorefreshlayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/refresh_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >
 
 <include layout="@layout/refresh_head" />
  
 <!-- 支持abslistview的所有子类 -->
 <listview
  android:id="@+id/content_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/white"
  android:divider="@color/gray"
  android:dividerheight="1dp" >
 </listview>
 
</com.jingchen.pulltorefresh.pulltorefreshlayout>

pulltorefreshlayout只能包含两个子控件:refresh_head和content_view。
看一下refresh_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
<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/head_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@color/light_blue" >
 
 <relativelayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_alignparentbottom="true"
  android:paddingbottom="20dp"
  android:paddingtop="20dp" >
 
  <relativelayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_centerinparent="true" >
 
   <imageview
    android:id="@+id/pull_icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centervertical="true"
    android:layout_marginleft="60dp"
    android:background="@drawable/pull_icon_big" />
 
   <imageview
    android:id="@+id/refreshing_icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centervertical="true"
    android:layout_marginleft="60dp"
    android:background="@drawable/refreshing"
    android:visibility="gone" />
 
   <textview
    android:id="@+id/state_tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerinparent="true"
    android:text="@string/pull_to_refresh"
    android:textcolor="@color/white"
    android:textsize="16sp" />
 
   <imageview
    android:id="@+id/state_iv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centervertical="true"
    android:layout_marginright="8dp"
    android:layout_toleftof="@id/state_tv"
    android:visibility="gone" />
  </relativelayout>
 </relativelayout>
 
</relativelayout>

可以根据需要修改refresh_head的布局然后在pulltorefreshlayout中处理,但是相关view的id要和pulltorefreshlayout中用到的保持同步!

接下来是mainactivity的代码:

?
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
package com.jingchen.pulltorefresh;
 
import java.util.arraylist;
import java.util.list;
 
import android.app.activity;
import android.content.context;
import android.os.bundle;
import android.os.handler;
import android.os.message;
import android.support.v4.view.pageradapter;
import android.support.v4.view.viewpager;
import android.support.v4.view.viewpager.onpagechangelistener;
import android.view.layoutinflater;
import android.view.view;
import android.view.view.onclicklistener;
import android.view.viewgroup;
import android.view.animation.animationutils;
import android.view.animation.linearinterpolator;
import android.view.animation.rotateanimation;
import android.widget.abslistview;
import android.widget.adapterview;
import android.widget.adapterview.onitemclicklistener;
import android.widget.adapterview.onitemlongclicklistener;
import android.widget.baseexpandablelistadapter;
import android.widget.expandablelistview;
import android.widget.expandablelistview.onchildclicklistener;
import android.widget.expandablelistview.ongroupclicklistener;
import android.widget.listview;
import android.widget.textview;
import android.widget.toast;
 
/**
 * 除了下拉刷新,在contenview为listview的情况下我给listview增加了footerview,实现点击加载更多
 *
 * @author 陈靖
 *
 */
public class mainactivity extends activity implements onrefreshlistener, onclicklistener
{
 private abslistview alv;
 private pulltorefreshlayout refreshlayout;
 private view loading;
 private rotateanimation loadinganimation;
 private textview loadtextview;
 private myadapter adapter;
 private boolean isloading = false;
 
 @override
 protected void oncreate(bundle savedinstancestate)
 {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
  init();
 }
 
 private void init()
 {
  alv = (abslistview) findviewbyid(r.id.content_view);
  refreshlayout = (pulltorefreshlayout) findviewbyid(r.id.refresh_view);
  refreshlayout.setonrefreshlistener(this);
  initlistview();
 
  loadinganimation = (rotateanimation) animationutils.loadanimation(this, r.anim.rotating);
  // 添加匀速转动动画
  linearinterpolator lir = new linearinterpolator();
  loadinganimation.setinterpolator(lir);
 }
 
 /**
  * listview初始化方法
  */
 private void initlistview()
 {
  list<string> items = new arraylist<string>();
  for (int i = 0; i < 30; i++)
  {
   items.add("这里是item " + i);
  }
  // 添加head
  view headview = getlayoutinflater().inflate(r.layout.listview_head, null);
  ((listview) alv).addheaderview(headview, null, false);
  // 添加footer
  view footerview = getlayoutinflater().inflate(r.layout.load_more, null);
  loading = footerview.findviewbyid(r.id.loading_icon);
  loadtextview = (textview) footerview.findviewbyid(r.id.loadmore_tv);
  ((listview) alv).addfooterview(footerview, null, false);
  footerview.setonclicklistener(this);
  adapter = new myadapter(this, items);
  alv.setadapter(adapter);
  alv.setonitemlongclicklistener(new onitemlongclicklistener()
  {
 
   @override
   public boolean onitemlongclick(adapterview<?> parent, view view, int position, long id)
   {
    toast.maketext(mainactivity.this, "longclick on " + parent.getadapter().getitemid(position), toast.length_short).show();
    return true;
   }
  });
  alv.setonitemclicklistener(new onitemclicklistener()
  {
 
   @override
   public void onitemclick(adapterview<?> parent, view view, int position, long id)
   {
    toast.maketext(mainactivity.this, " click on " + parent.getadapter().getitemid(position), toast.length_short).show();
   }
  });
 }
 
 /**
  * gridview初始化方法
  */
 private void initgridview()
 {
  list<string> items = new arraylist<string>();
  for (int i = 0; i < 30; i++)
  {
   items.add("这里是item " + i);
  }
  adapter = new myadapter(this, items);
  alv.setadapter(adapter);
  alv.setonitemlongclicklistener(new onitemlongclicklistener()
  {
 
   @override
   public boolean onitemlongclick(adapterview<?> parent, view view, int position, long id)
   {
    toast.maketext(mainactivity.this, "longclick on " + parent.getadapter().getitemid(position), toast.length_short).show();
    return true;
   }
  });
  alv.setonitemclicklistener(new onitemclicklistener()
  {
 
   @override
   public void onitemclick(adapterview<?> parent, view view, int position, long id)
   {
    toast.maketext(mainactivity.this, " click on " + parent.getadapter().getitemid(position), toast.length_short).show();
   }
  });
 }
 
 /**
  * expandablelistview初始化方法
  */
 private void initexpandablelistview()
 {
  ((expandablelistview) alv).setadapter(new expandablelistadapter(this));
  ((expandablelistview) alv).setonchildclicklistener(new onchildclicklistener()
  {
 
   @override
   public boolean onchildclick(expandablelistview parent, view v, int groupposition, int childposition, long id)
   {
    toast.maketext(mainactivity.this, " click on group " + groupposition + " item " + childposition, toast.length_short).show();
    return true;
   }
  });
  ((expandablelistview) alv).setonitemlongclicklistener(new onitemlongclicklistener()
  {
 
   @override
   public boolean onitemlongclick(adapterview<?> parent, view view, int position, long id)
   {
    toast.maketext(mainactivity.this, "longclick on " + parent.getadapter().getitemid(position), toast.length_short).show();
    return true;
   }
  });
  ((expandablelistview) alv).setongroupclicklistener(new ongroupclicklistener()
  {
 
   @override
   public boolean ongroupclick(expandablelistview parent, view v, int groupposition, long id)
   {
    if (parent.isgroupexpanded(groupposition))
    {
     // 如果展开则关闭
     parent.collapsegroup(groupposition);
    } else
    {
     // 如果关闭则打开,注意这里是手动打开不要默认滚动否则会有bug
     parent.expandgroup(groupposition);
    }
    return true;
   }
  });
 }
 
 @override
 public void onrefresh()
 {
  // 下拉刷新操作
  new handler()
  {
   @override
   public void handlemessage(message msg)
   {
    refreshlayout.refreshfinish(pulltorefreshlayout.refresh_succeed);
   }
  }.sendemptymessagedelayed(0, 5000);
 }
 
 @override
 public void onclick(view v)
 {
  switch (v.getid())
  {
  case r.id.loadmore_layout:
   if (!isloading)
   {
    loading.setvisibility(view.visible);
    loading.startanimation(loadinganimation);
    loadtextview.settext(r.string.loading);
    isloading = true;
   }
   break;
  default:
   break;
  }
 
 }
 
 class expandablelistadapter extends baseexpandablelistadapter
 {
  private string[] groupsstrings;// = new string[] { "这里是group 0",
          // "这里是group 1", "这里是group 2" };
  private string[][] groupitems;
  private context context;
 
  public expandablelistadapter(context context)
  {
   this.context = context;
   groupsstrings = new string[8];
   for (int i = 0; i < groupsstrings.length; i++)
   {
    groupsstrings[i] = new string("这里是group " + i);
   }
   groupitems = new string[8][8];
   for (int i = 0; i < groupitems.length; i++)
    for (int j = 0; j < groupitems[i].length; j++)
    {
     groupitems[i][j] = new string("这里是group " + i + "里的item " + j);
    }
  }
 
  @override
  public int getgroupcount()
  {
   return groupsstrings.length;
  }
 
  @override
  public int getchildrencount(int groupposition)
  {
   return groupitems[groupposition].length;
  }
 
  @override
  public object getgroup(int groupposition)
  {
   return groupsstrings[groupposition];
  }
 
  @override
  public object getchild(int groupposition, int childposition)
  {
   return groupitems[groupposition][childposition];
  }
 
  @override
  public long getgroupid(int groupposition)
  {
   return groupposition;
  }
 
  @override
  public long getchildid(int groupposition, int childposition)
  {
   return childposition;
  }
 
  @override
  public boolean hasstableids()
  {
   return true;
  }
 
  @override
  public view getgroupview(int groupposition, boolean isexpanded, view convertview, viewgroup parent)
  {
   view view = layoutinflater.from(context).inflate(r.layout.list_item_layout, null);
   textview tv = (textview) view.findviewbyid(r.id.name_tv);
   tv.settext(groupsstrings[groupposition]);
   return view;
  }
 
  @override
  public view getchildview(int groupposition, int childposition, boolean islastchild, view convertview, viewgroup parent)
  {
   view view = layoutinflater.from(context).inflate(r.layout.list_item_layout, null);
   textview tv = (textview) view.findviewbyid(r.id.name_tv);
   tv.settext(groupitems[groupposition][childposition]);
   return view;
  }
 
  @override
  public boolean ischildselectable(int groupposition, int childposition)
  {
   return true;
  }
 
 }
 
}

在mainactivity中判断contentview是listview的话给listview添加了footerview实现点击加载更多的功能。这只是一个演示pulltorefreshlayout使用的demo,可以参照一下修改。我已经在里面写了listview,gridview和expandablelistview的初始化方法,根据自己使用的是哪个来调用吧。那么这是listview的下拉刷新和加载更多。如果我要gridview也有下拉刷新功能呢?那就把mainactivity的布局换成这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<com.jingchen.pulltorefresh.pulltorefreshlayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/refresh_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >
 
 <include layout="@layout/refresh_head" />
 <!-- 支持abslistview的所有子类 -->
 <gridview
  android:id="@+id/content_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/white"
  android:columnwidth="90dp"
  android:gravity="center"
  android:horizontalspacing="10dp"
  android:numcolumns="auto_fit"
  android:stretchmode="columnwidth"
  android:verticalspacing="15dp" >
 </gridview>
 
</com.jingchen.pulltorefresh.pulltorefreshlayout>

如果是expandablelistview则把布局改成这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<com.jingchen.pulltorefresh.pulltorefreshlayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/refresh_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >
 
 <include layout="@layout/refresh_head" />
 <!-- 支持abslistview的所有子类 -->
 <expandablelistview
  android:id="@+id/content_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/white" >
 </expandablelistview>
 
</com.jingchen.pulltorefresh.pulltorefreshlayout>

怎么样?很简单吧?简单易用,不用再去继承修改了。

希望本文所述对大家学习android下拉刷新控件有所帮助。

延伸 · 阅读

精彩推荐