Android自定义ListView实现下拉刷新
首先呈上效果图
当今app,哪个没有点滑动刷新功能,简直就太落伍了。正因为需求多,因此自然而然开源的也就多。但是若想引用开源库,则很麻烦,比如pulltorefreshview这个库,如果把开源代码都移植到项目中,这是件很繁琐的事,如果用依赖功能的话,对于强迫症的我,又很不爽。现在也有各种自定义listview实现pulltorefreshlistview的控件,无非就是在header加入一个控件,通过setpadding的方式来改变显示效果。效果已经太out了,如意中发现google自带的swiperefreshlayout实现的效果挺不错,但是我发现这个控件在部分手机上的效果不一样,估计和v7包相关。因此就有了这篇文章自定义这个喜欢的效果。
首先大概描述一下实现原理:
1、重写listview的ontouchevent,在方法中根据手指滑动的距离与临界值判断,决定当前的状态,分为四个状态:release_to_refresh、pull_to_refresh、refreshing、done四个状态,分别代表释放刷新、拉动刷新、正在刷新、默认状态。
2、重写listview的ondraw方法,根据不同的状态值,显示不同的图形表示。
3、根据滑动距离不同,显示不同的透明度、圆弧角度值、整体图形的坐标等等。
4、图形的变化分为两种:1、手动触发,滑动一点距离就更新一点坐标。比如pull_to_refresh状态,适合在ontouchevent中的action_move中触发。2、动画自动触发,比如refreshing状态和done状态,适合在ontouchevent中的action_up方法中触发,手指一松开就自动触发动画效果。
5、必须在设置了刷新监听器才可以滑动,否则就是一个普通的listview。
代码很简单,只有两个文件,并且有很详细的注释:
pulltorefreshlistview类:
package cc.wxf.view.pull; import android.content.context; import android.graphics.canvas; import android.util.attributeset; import android.view.motionevent; import android.view.view; import android.widget.abslistview; import android.widget.listview; /** * created by ccwxf on 2016/3/30. */ public class pulltorefreshlistview extends listview implements abslistview.onscrolllistener { public final static int release_to_refresh = 0; public final static int pull_to_refresh = 1; public final static int refreshing = 2; public final static int done = 3; // 达到刷新条件的滑动距离 public final static int touch_slop = 160; // 判断是否记录了最开始按下时的y坐标 private boolean isrecored; // 记录最开始按下时的y坐标 private int starty; // listview第一个item private int firstitemindex; // 当前状态 private int state; // 是否可刷新,只有设置了监听器才能刷新 private boolean isrefreshable; // 刷新标记 private pullmark mark; private onrefreshlistener refreshlistener; private onscrollbuttomlistener scrollbuttomlistener; public pulltorefreshlistview(context context) { super(context); init(context); } public pulltorefreshlistview(context context, attributeset attrs) { super(context, attrs); init(context); } private void init(context context) { //关闭硬件加速,否则pullmark的阴影不会出现 setlayertype(view.layer_type_software, null); setonscrolllistener(this); mark = new pullmark(this); state = done; isrefreshable = false; } @override public void onscrollstatechanged(abslistview view, int scrollstate) { if (scrollbuttomlistener != null) { if (scrollstate == onscrolllistener.scroll_state_idle) { if (view.getlastvisibleposition() == view.getadapter().getcount() - 1) { scrollbuttomlistener.onscrolltobuttom(); } } } } @override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { firstitemindex = firstvisibleitem; } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); mark.ondraw(canvas); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int width = measurespec.getsize(widthmeasurespec); mark.setcenterx(width / 2); super.onmeasure(widthmeasurespec, heightmeasurespec); } @override public boolean ontouchevent(motionevent event) { if (!isrefreshable) { return super.ontouchevent(event); } switch (event.getaction()) { case motionevent.action_down: handleactiondown(event); break; case motionevent.action_up: handleactionup(); break; case motionevent.action_move: handleactionmove(event); break; default: break; } return super.ontouchevent(event); } private void handleactionmove(motionevent event) { int tempy = (int) event.gety(); if (!isrecored && firstitemindex == 0) { isrecored = true; starty = tempy; } if (state != refreshing && isrecored) { if (state == release_to_refresh) { setselection(0); if ((tempy - starty < touch_slop) && (tempy - starty) > 0) { state = pull_to_refresh; } } if (state == pull_to_refresh) { setselection(0); if (tempy - starty >= touch_slop) { state = release_to_refresh; } else if (tempy - starty <= 0) { state = done; } } if (state == done) { if (tempy - starty > 0) { state = pull_to_refresh; } } mark.change(state, tempy - starty); } } private void handleactionup() { if (state == pull_to_refresh) { state = done; mark.changebyanimation(state); } else if (state == release_to_refresh) { state = refreshing; mark.changebyanimation(state); onrefresh(); } isrecored = false; } private void handleactiondown(motionevent event) { if (firstitemindex == 0 && !isrecored) { isrecored = true; starty = (int) event.gety(); } } private void onrefresh() { if (refreshlistener != null) { refreshlistener.onrefresh(); } } public void startrefresh() { state = refreshing; mark.changebyanimation(state); onrefresh(); } public void stoprefresh() { state = done; mark.changebyanimation(state); } public void setonrefreshlistener(onrefreshlistener refreshlistener) { this.refreshlistener = refreshlistener; isrefreshable = true; } /** * 刷新监听器 */ public interface onrefreshlistener { public void onrefresh(); } public void setonscrollbuttomlistener(onscrollbuttomlistener scrollbuttomlistener) { this.scrollbuttomlistener = scrollbuttomlistener; } /** * 滑动到最低端触发监听器 */ public interface onscrollbuttomlistener { public void onscrolltobuttom(); } }
刷新标志类:
package cc.wxf.view.pull; import android.graphics.canvas; import android.graphics.color; import android.graphics.paint; import android.graphics.rectf; import android.os.handler; /** * created by ccwxf on 2016/3/30. */ public class pullmark { //背景面板的半径、颜色 private static final int radius_pan = 40; private static final int color_pan = color.parsecolor("#fafafa"); //面板阴影的半径、颜色 private static final int radius_shadow = 5; private static final int color_shadow = color.parsecolor("#d9d9d9"); //面板中间的圆弧的半径、颜色、粗度、开始绘制角度 private static final int radius_arrows = 20; private static final int color_arrows = color.green; private static final int bound_arrows = 6; private static final int start_angle = 0; // 开始绘制角度的变化率、总体绘制角度、总体绘制透明度 private static final int ratio_satrt_angle = 3; private static final int all_angle = 270; private static final int all_alpha = 255; // 动画的高度渐变比率、时间刷新间隔 private static final float ratio_touch_slop = 7f; private static final long ratio_animation_duration = 10; private pulltorefreshlistview listview; // 中点的x、y坐标、初始隐藏时的y坐标 private float donecentery = -(radius_pan + radius_shadow) / 2; private float centerx; private float centery = donecentery; // 开始绘制的角度、需要绘制的角度、透明度 private int startangle = start_angle; private int sweepangle = startangle; private int alpha; // 弧度变化比率,根据总体高度与总体弧度角度的比例决定 private float radioangle = all_angle * 1.0f / pulltorefreshlistview.touch_slop; // 透明度变化比率,根据总体高度与总体透明度的比例决定 private float radioalpha = all_alpha * 1.0f / pulltorefreshlistview.touch_slop; // pulltorefreshlistview的状态 private int state; // 当前手指滑动的距离 private float mtouchlength; // 是否启动旋转动画 private boolean isrotateanimation = false; // 画笔 private paint mpaint = new paint(paint.anti_alias_flag); private handler handler = new handler(); public pullmark(pulltorefreshlistview listview) { this.listview = listview; } /** * 设置绘制的中点x坐标,在pulltorefreshlistview的onmeasure中实现 * @param centerx */ public void setcenterx(int centerx){ this.centerx = centerx; } /** * 表示一次普通的数据变化,在ontouchevent中的action_move中触发 * @param state * @param mtouchlength */ public void change(int state, float mtouchlength){ this.state = state; this.mtouchlength = mtouchlength; // 改变绘制的y坐标 centery = donecentery + mtouchlength; // 改变绘制的透明度 alpha = (int) (mtouchlength * radioalpha); if(alpha > all_alpha){ alpha = all_alpha; }else if(alpha < 0){ alpha = 0; } //改变绘制的起始角度 startangle = startangle + ratio_satrt_angle; if(startangle >= 360){ startangle = 0; } //改变绘制的弧度角度 sweepangle = (int) (mtouchlength * radioangle); if(sweepangle > all_angle){ sweepangle = all_angle; }else if(sweepangle < 0){ sweepangle = 0; } listview.invalidate(); } /** * 表示一次动画的变化,在ontouchevent的action_up中或者手动startrefresh以及手动stoprefresh中触发 * @param state */ public void changebyanimation(final int state){ this.state = state; if(state == pulltorefreshlistview.done){ //结束旋转动画(关闭正在刷新的效果) isrotateanimation = false; } //慢慢变化到起始位置 handler.postdelayed(new runnablemove(state), ratio_animation_duration); } /** * 启动移动的处理 */ public class runnablemove implements runnable{ private int state; private int destination; private float slop; public runnablemove(int state) { this.state = state; if(state == pulltorefreshlistview.done){ destination = 0; slop = ratio_touch_slop; }else if(state == pulltorefreshlistview.refreshing){ destination = pulltorefreshlistview.touch_slop; slop = ratio_touch_slop * 5; } } @override public void run() { if(mtouchlength > destination){ mtouchlength -= slop; change(state, mtouchlength); handler.postdelayed(this, ratio_animation_duration); }else{ if(state == pulltorefreshlistview.done){ // 直接将坐标初始化,否则会有一点点误差 centery = donecentery; listview.invalidate(); }else if(state == pulltorefreshlistview.refreshing){ //启动旋转的动画效果 isrotateanimation = true; handler.postdelayed(new runnablerotate(), ratio_animation_duration); } } } } /** * 旋转动画的处理 */ public class runnablerotate implements runnable{ @override public void run() { if(isrotateanimation){ //启动动画旋转效果 startangle = startangle + ratio_satrt_angle; if(startangle >= 360){ startangle = 0; } listview.invalidate(); handler.postdelayed(this, ratio_animation_duration); }else{ //回到初始位置 handler.postdelayed(new runnablemove(state), ratio_animation_duration); } } } /** * 绘制刷新图标的标志 * @param mcanvas */ public void ondraw(canvas mcanvas){ //绘制背景圆盘和阴影 mpaint.setstyle(paint.style.fill); mpaint.setcolor(color_pan); mpaint.setshadowlayer(radius_shadow, 0, 0, color_shadow); mcanvas.drawcircle(centerx, centery, radius_pan, mpaint); //绘制圆弧 mpaint.setstyle(paint.style.stroke); mpaint.setcolor(color_arrows); mpaint.setstrokewidth(bound_arrows); mpaint.setalpha(alpha); mcanvas.drawarc(new rectf(centerx - radius_arrows, centery - radius_arrows, centerx + radius_arrows, centery + radius_arrows), startangle, sweepangle, false, mpaint); } }
使用的时候,必须要设置了监听器才能有效的滑动:
final pulltorefreshlistview listview = (pulltorefreshlistview) findviewbyid(r.id.listview); arrayadapter<string> adapter = new arrayadapter<string>(this, android.r.layout.simple_list_item_1, new string[]{ "测试1","测试2","测试3","测试4","测试5","测试6", }); listview.setadapter(adapter); listview.setonrefreshlistener(new pulltorefreshlistview.onrefreshlistener() { @override public void onrefresh() { new handler().postdelayed(new runnable() { @override public void run() { listview.stoprefresh(); } }, 2000); } });
两个源代码文件就搞定了,demo工程就不提供了,很简单的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Android开发登陆案例