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

Android中PathMeasure仿支付宝支付动画

程序员文章站 2023-12-04 18:38:52
前言 在 android 自定义 view 中,path 可能用的比较多,pathmeasure 可能用的比较少,就我而言,以前也没有使用过 pathmeasure 这个...

前言

在 android 自定义 view 中,path 可能用的比较多,pathmeasure 可能用的比较少,就我而言,以前也没有使用过 pathmeasure 这个 api,看到别人用 pathmeasure 和 valueanimator 结合在一起完成了很好的动画效果,于是我也学习下 pathmeasure ,此处记录下。

pathmeasure

构造器:

Android中PathMeasure仿支付宝支付动画

forceclosed 含义:

// 创建一个 path 对象
path = new path();
path.moveto(20, 20);
path.lineto(200, 20);
path.lineto(200, 400);

在ondraw(canvas canvas) 中绘制 path

@override
 protected void ondraw(canvas canvas) {
  destpath.reset();
  destpath.lineto(0, 0);
  pathmeasure.setpath(path, true);
  log.e("debug", "pathmeasure.getlength() = " + pathmeasure.getlength());
  pathmeasure.getsegment(0, pathmeasure.getlength() * curvalue, destpath, true);
canvas.drawpath(destpath, paint); // 绘制线段路径

 }

当 pathmeasure.setpath(path,false) 时:

Android中PathMeasure仿支付宝支付动画

Android中PathMeasure仿支付宝支付动画

当 pathmeasure.setpath(path,true) 时:

Android中PathMeasure仿支付宝支付动画

Android中PathMeasure仿支付宝支付动画

可以看到:当 forceclosed = true 时, path 进行了闭合,相应的 path 长度也变长了,即 算上了斜边的长度。

仿支付宝支付动画 view - loadingview

效果:

Android中PathMeasure仿支付宝支付动画

思路:

绘制对号,叉号,主要是通过 valueanimator 结合 getsegment() 不断绘制新的弧形段,其中,叉号由两个 path 组成,在第一个 path 绘制完成时,需要调用 pathmeasure.nextcontour() 跳转到另一个 path。

getsegment() 将获取的片段填充到 destpath 中,在 android 4.4 及以下版本中,不能绘制,需要调用 destpath.reset(),destpath.line(0,0)

loadingview 完整代码:

public class loadingview extends view {

  private final int default_color = color.black; // 默认圆弧颜色

  private final int default_stroke_width = dp2px(2); // 默认圆弧宽度

  private final boolean default_is_show_result = false; // 默认不显示加载结果

  private final int default_view_width = dp2px(50); // 控件默认宽度

  private final int default_view_height = dp2px(50); // 控件默认高度

  private int color; // 圆弧颜色

  private int strokewidth;  // 圆弧宽度

  private boolean isshowresult;  // 是否显示加载结果状态

  private paint paint; // 画笔

  private int mwidth; // 控件宽度

  private int mheight; // 控件高度

  private int radius;  // 圆弧所在圆的半径

  private int halfstrokewidth; // 画笔宽度的一半


  private int rotatedelta = 4;

  private int curangle = 0;

  private int minangle = -90;

  private int startangle = -90; // 上方顶点

  private int endangle = 0;

  private rectf rectf;

  private stateenum stateenum = stateenum.loading;

  private path successpath;

  private path rightfailpath;

  private path leftfailpath;

  private valueanimator successanimator;

  private valueanimator rightfailanimator;

  private valueanimator leftfailanimator;

  private pathmeasure pathmeasure;

  private float successvalue;

  private float rightfailvalue;

  private float leftfailvalue;

  private path destpath;

  private animatorset animatorset;

  public loadingview(context context) {
    this(context, null);
  }

  public loadingview(context context, attributeset attrs) {
    super(context, attrs);
    init(context, attrs);
  }


  private void init(context context, attributeset attrs) {
    typedarray typedarray = null;
    try {
      typedarray = context.obtainstyledattributes(attrs, r.styleable.loadingview);
      color = typedarray.getcolor(r.styleable.loadingview_color, default_color);
      strokewidth = (int) typedarray.getdimension(r.styleable.loadingview_storkewidth, default_stroke_width);
      isshowresult = typedarray.getboolean(r.styleable.loadingview_isshowresult, default_is_show_result);
    } catch (exception e) {
      e.printstacktrace();
    } finally {
      if (typedarray != null) {
        typedarray.recycle();
      }
    }
    paint = createpaint(color, strokewidth, paint.style.stroke);
  }


  @override
  protected void onsizechanged(int w, int h, int oldw, int oldh) {
    super.onsizechanged(w, h, oldw, oldh);
    mwidth = w;
    mheight = h;
    log.i("debug", "getmeasurewidth() = " + getmeasuredwidth());
    log.i("debug", "getmeasureheight() = " + getmeasuredheight());

    radius = math.min(mwidth, mheight) / 2;
    halfstrokewidth = strokewidth / 2;

    rectf = new rectf(halfstrokewidth - radius, halfstrokewidth - radius,
        radius - halfstrokewidth, radius - halfstrokewidth);
    // success path
    successpath = new path();
    successpath.moveto(-radius * 2 / 3f, 0f);
    successpath.lineto(-radius / 8f, radius / 2f);
    successpath.lineto(radius / 2, -radius / 3);
    // fail path ,right top to left bottom
    rightfailpath = new path();
    rightfailpath.moveto(radius / 3f, -radius / 3f);
    rightfailpath.lineto(-radius / 3f, radius / 3f);

    // fail path, left top to right bottom
    leftfailpath = new path();
    leftfailpath.moveto(-radius / 3f, -radius / 3f);
    leftfailpath.lineto(radius / 3f, radius / 3f);

    pathmeasure = new pathmeasure();

    destpath = new path();

    initsuccessanimator();
    initfailanimator();
  }

  private void initsuccessanimator() {
//    pathmeasure.setpath(successpath, false);
    successanimator = valueanimator.offloat(0, 1f);
    successanimator.setduration(1000);
    successanimator.setinterpolator(new linearinterpolator());
    successanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {

      @override
      public void onanimationupdate(valueanimator animation) {
        successvalue = (float) animation.getanimatedvalue();
        invalidate();
      }
    });
  }


  private void initfailanimator() {
//    pathmeasure.setpath(rightfailpath, false);
    rightfailanimator = valueanimator.offloat(0, 1f);
    rightfailanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {

      @override
      public void onanimationupdate(valueanimator animation) {
        rightfailvalue = (float) animation.getanimatedvalue();
        invalidate();
      }
    });

//    pathmeasure.setpath(leftfailpath, false);
    leftfailanimator = valueanimator.offloat(0, 1f);
    leftfailanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {
      @override
      public void onanimationupdate(valueanimator animation) {
        leftfailvalue = (float) animation.getanimatedvalue();
        invalidate();
      }
    });

    animatorset = new animatorset();
    animatorset.play(leftfailanimator).after(rightfailanimator);
    animatorset.setduration(500);
    animatorset.setinterpolator(new linearinterpolator());


  }


  /**
   * 测量控件的宽高,当测量模式不是精确模式时,设置默认宽高
   *
   * @param widthmeasurespec
   * @param heightmeasurespec
   */
  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    int widthmode = measurespec.getmode(widthmeasurespec);
    int heightmode = measurespec.getmode(heightmeasurespec);
    if (widthmode == measurespec.at_most || widthmode == measurespec.unspecified) {
      widthmeasurespec = measurespec.makemeasurespec(default_view_width, measurespec.exactly);
    }
    if (heightmode == measurespec.at_most || heightmode == measurespec.unspecified) {
      heightmeasurespec = measurespec.makemeasurespec(default_view_height, measurespec.exactly);

    }
    super.onmeasure(widthmeasurespec, heightmeasurespec);
  }


  @override
  protected void ondraw(canvas canvas) {
    canvas.save();
    canvas.translate(mwidth / 2, mheight / 2);
    destpath.reset();
    destpath.lineto(0, 0);  // destpath
    if (stateenum == stateenum.loading) {
      if (endangle >= 300 || startangle > minangle) {
        startangle += 6;
        if (endangle > 20) {
          endangle -= 6;
        }
      }
      if (startangle > minangle + 300) {
        minangle = startangle;
        endangle = 20;
      }
      canvas.rotate(curangle += rotatedelta, 0, 0);//旋转rotatedelta=4的弧长
      canvas.drawarc(rectf, startangle, endangle, false, paint);
      // endangle += 6 放在 drawarc()后面,是防止刚进入时,突兀的显示了一段圆弧
      if (startangle == minangle) {
        endangle += 6;
      }
      invalidate();
    }
    if (isshowresult) {
      if (stateenum == stateenum.load_success) {
        pathmeasure.setpath(successpath, false);
        canvas.drawcircle(0, 0, radius - halfstrokewidth, paint);
        pathmeasure.getsegment(0, successvalue * pathmeasure.getlength(), destpath, true);
        canvas.drawpath(destpath, paint);
      } else if (stateenum == stateenum.load_failed) {
        canvas.drawcircle(0, 0, radius - halfstrokewidth, paint);
        pathmeasure.setpath(rightfailpath, false);
        pathmeasure.getsegment(0, rightfailvalue * pathmeasure.getlength(), destpath, true);
        if (rightfailvalue == 1) {
          pathmeasure.setpath(leftfailpath, false);
          pathmeasure.nextcontour();
          pathmeasure.getsegment(0, leftfailvalue * pathmeasure.getlength(), destpath, true);
        }
        canvas.drawpath(destpath, paint);
      }
    }
    canvas.restore();

  }


  public void updatestate(stateenum stateenum) {
    this.stateenum = stateenum;
    if (stateenum == stateenum.load_success) {
      successanimator.start();
    } else if (stateenum == stateenum.load_failed) {
      animatorset.start();
    }
  }


  public enum stateenum {
    loading, // 正在加载
    load_success,  // 加载成功,显示对号
    load_failed   // 加载失败,显示叉号
  }


  /**
   * 创建画笔
   *
   * @param color    画笔颜色
   * @param strokewidth 画笔宽度
   * @param style    画笔样式
   * @return
   */
  private paint createpaint(int color, int strokewidth, paint.style style) {
    paint paint = new paint(paint.anti_alias_flag);
    paint.setstrokecap(paint.cap.round);
    paint.setstrokejoin(paint.join.round);
    paint.setcolor(color);
    paint.setstrokewidth(strokewidth);
    paint.setstyle(style);
    return paint;
  }


  /**
   * dp 转换成 px
   *
   * @param dpvalue
   * @return
   */
  private int dp2px(int dpvalue) {
    return (int) typedvalue.applydimension(typedvalue.complex_unit_dip, dpvalue, getresources().getdisplaymetrics());
  }

}

github : https://github.com/xing16/loadingview

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