欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义ListView实现下拉刷新

程序员文章站 2024-03-04 17:45:48
首先呈上效果图 当今app,哪个没有点滑动刷新功能,简直就太落伍了。正因为需求多,因此自然而然开源的也就多。但是若想引用开源库,则很麻烦,比如pulltorefr...

首先呈上效果图

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

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