当前位置: 移动技术网 > IT编程>移动开发>Android > Android自定义ListView实现下拉刷新

Android自定义ListView实现下拉刷新

2019年07月24日  | 移动技术网IT编程  | 我要评论

吴忠才在哪,ca1109,魏晨的照片

首先呈上效果图


当今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工程就不提供了,很简单的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网