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

Android自定义view实现水波纹进度球效果

程序员文章站 2024-03-07 18:47:57
今天我们要实现的这个view没有太多交互性的view,所以就继承view。 自定义view的套路,套路很深      ...

今天我们要实现的这个view没有太多交互性的view,所以就继承view。

自定义view的套路,套路很深

      1、获取我们自定义属性attrs(可省略)

      2、重写onmeasure方法,计算控件的宽和高

      3、重写ondraw方法,绘制我们的控件

这么看来,自定义view的套路很清晰嘛。

我们看下今天的效果图,其中一个是放慢的效果(时间调的长)

Android自定义view实现水波纹进度球效果

Android自定义view实现水波纹进度球效果

我们按照套路来。

一.自定义属性

 <declare-styleable name="waveprogressview">
  <attr name="radius" format="dimension|reference" />
  <attr name="radius_color" format="color|reference" />
  <attr name="progress_text_color" format="color|reference" />
  <attr name="progress_text_size" format="dimension|reference" />
  <attr name="progress_color" format="color|reference" />
  <attr name="progress" format="float" />
  <attr name="maxprogress" format="float" />
 </declare-styleable>

看下效果图我们就知道因该需要哪些属性。就不说了。

然后就是获取我们的这些属性,就是用typedarray来获取。当然是在构造中获取,一般我们会复写构造方法,少参数调用参数多的,然后走到参数最多的那个。

typedarray a = getcontext().obtainstyledattributes(attrs, r.styleable.waveprogressview, defstyleattr, r.style.waveprogressviewdefault);
  radius = (int) a.getdimension(r.styleable.waveprogressview_radius, radius);
  textcolor = a.getcolor(r.styleable.waveprogressview_progress_text_color, 0);
  textsize = a.getdimensionpixelsize(r.styleable.waveprogressview_progress_text_size, 0);
  progresscolor = a.getcolor(r.styleable.waveprogressview_progress_color, 0);
  radiuscolor = a.getcolor(r.styleable.waveprogressview_radius_color, 0);
  progress = a.getfloat(r.styleable.waveprogressview_progress, 0);
  maxprogress = a.getfloat(r.styleable.waveprogressview_maxprogress, 100);
  a.recycle();

注: r.style.waveprogressviewdefault是这个控件的默认样式。

二.onmeasure测量

我们重写这个方法主要是更具父看见的宽和高来设置自己的宽和高。

 @override 
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  //计算宽和高
  int exceptw = getpaddingleft() + getpaddingright() + 2 * radius;
  int excepth = getpaddingtop() + getpaddingbottom() + 2 * radius;
  int width = resolvesize(exceptw, widthmeasurespec);
  int height = resolvesize(excepth, heightmeasurespec);
  int min = math.min(width, height);

  this.width = this.height = min;

  //计算半径,减去padding的最小值
  int minlr = math.min(getpaddingleft(), getpaddingright());
  int mintb = math.min(getpaddingtop(), getpaddingbottom());
  minpadding = math.min(minlr, mintb);
  radius = (min - minpadding * 2) / 2;

  setmeasureddimension(min, min);
 }

首先该控件的宽和高肯定是一样的,因为是个圆嘛。其实是宽和高与半径和内边距有关,这里的内边距,我们取上下左右最小的一个。宽和高也选择取最小的。

this.width = this.height = min; 包含左右边距。

resolvesize这个方法很好的为我们实现了我们想要的宽和高我慢看下源码。

 public static int resolvesizeandstate(int size, int measurespec, int childmeasuredstate) {
  final int specmode = measurespec.getmode(measurespec);
  final int specsize = measurespec.getsize(measurespec);
  final int result;
  switch (specmode) {
   case measurespec.at_most:
    if (specsize < size) {
     result = specsize | measured_state_too_small;
    } else {
     result = size;
    }
    break;
   case measurespec.exactly:
    result = specsize;
    break;
   case measurespec.unspecified:
   default:
    result = size;
  }
  return result | (childmeasuredstate & measured_state_mask);
 }

如果我们自己写也是这样写。

最后通过setmeasureddimension设置宽和高。

三.ondraw绘制

关于绘制有很多android 提供了很多api,这里就不多说了。

绘制首先就是一些画笔的初始化。

需要提一下绘制path路径的画笔设置为porterduff.mode.src_in模式,这个模式只显示重叠的部分。

 pathpaint = new paint(paint.anti_alias_flag);
  pathpaint.setcolor(progresscolor);
  pathpaint.setdither(true);
  pathpaint.setxfermode(new porterduffxfermode(porterduff.mode.src_in));

我们要将所有的绘制 绘制到一个透明的bitmap上,然后将这个bitmap绘制到canvas上。

if (bitmap == null) {
   bitmap = bitmap.createbitmap(this.width, this.height, bitmap.config.argb_8888);
   bitmapcanvas = new canvas(bitmap);
  }

为了方便计算和绘制,我将坐标系平移padding的距离

 bitmapcanvas.save();
  //移动坐标系
  bitmapcanvas.translate(minpadding, minpadding);
 // .... some thing
 bitmapcanvas.restore();

3.1绘制圆

  bitmapcanvas.drawcircle(radius, radius, radius, circlepaint);

3.2绘制path 路径.

一是要实现波纹的左右飘,和上下的振幅慢慢的减小

绘制这个之前我们需要知道二阶贝塞尔曲线的大致原理。

简单的说就是知道:p1起始点,p2是终点,p1是控制点.利用塞尔曲线的公式就可以得道沿途的一些点,最后把点连起来就是喽。

下面这个图片来于网络:

Android自定义view实现水波纹进度球效果
二阶贝塞尔曲线

在android-sdk里提供了绘制贝塞尔曲线的函数rquadto方法

public void rquadto(float dx1, float dy1, float dx2, float dy2)

      dx1:控制点x坐标,表示相对上一个终点x坐标的位移坐标,可为负值,正值表示相加,负值表示相减;

      dy1:控制点y坐标,相对上一个终点y坐标的位移坐标。同样可为负值,正值表示相加,负值表示相减;

      dx2:终点x坐标,同样是一个相对坐标,相对上一个终点x坐标的位移值,可为负值,正值表示相加,负值表示相减;

      dy2:终点y坐标,同样是一个相对,相对上一个终点y坐标的位移值。可为负值,正值表示相加,负值表示相减;

这四个参数都是传递的都是相对值,相对上一个终点的位移值。

要实现振幅慢慢的减小我们可以调节控制点的y坐标即可,即:

float percent=progress * 1.0f / maxprogress;

就可以得到[0,1]的

一个闭区间,[0,1]这货好啊,我喜欢,可以来做很多事情。

这样我们就可以根据percent来调节控制点的y坐标了。

//根据直径计算绘制贝赛尔曲线的次数
   int count = radius * 4 / 60;
   //控制-控制点y的坐标
   float point = (1 - percent) * 15;
   for (int i = 0; i < count; i++) {
    path.rquadto(15, -point, 30, 0);
    path.rquadto(15, point, 30, 0);
   }

要实现左右波纹只需要控制闭合路径的左上角的x坐标即可,当然也是根据percent喽。

大家可以结合下面这个图来理解下上面的话。

Android自定义view实现水波纹进度球效果

path绘制的完整代码片段。

 //绘制path
  //重置绘制路线
  path.reset();
  float percent=progress * 1.0f / maxprogress;
  float y = (1 - percent) * radius * 2;
  //移动到右上边
  path.moveto(radius * 2, y);
  //移动到最右下方
  path.lineto(radius * 2, radius * 2);
  //移动到最左下边
  path.lineto(0, radius * 2);
  //移动到左上边
  // path.lineto(0, y);
  //实现左右波动,根据progress来平移
  path.lineto(-(1 -percent) * radius*2, y);
  if (progress != 0.0f) {
   //根据直径计算绘制贝赛尔曲线的次数
   int count = radius * 4 / 60;
   //控制-控制点y的坐标
   float point = (1 - percent) * 15;
   for (int i = 0; i < count; i++) {
    path.rquadto(15, -point, 30, 0);
    path.rquadto(15, point, 30, 0);
   }
  }
  //闭合
  path.close();
  bitmapcanvas.drawpath(path, pathpaint);

3.3绘制进度的文字

这个就比较简单了,绘制在控件的中间即可。关于文字的坐标计算还是很好理解的。

 //绘制文字
  string text = progress + "%";
  float textw = textpaint.measuretext(text);
  paint.fontmetrics fontmetrics = textpaint.getfontmetrics();
  float baseline = radius - (fontmetrics.ascent + fontmetrics.descent) / 2;
  bitmapcanvas.drawtext(text, radius - textw / 2, baseline, textpaint);

最后别忘了把我们的bitmap绘制到canvas上。

canvas.drawbitmap(bitmap, 0, 0, null);

哦,最后是实用方法,这里我们不用thread+handler,我们用属性动画。

你懂的!!!,like

 objectanimator objectanimator0 = objectanimator.offloat(waveprogressview_0, "progress", 0f, 100f);
  objectanimator0.setduration(3300);
  objectanimator0.setinterpolator(new linearinterpolator());
  objectanimator0.start();

结束语

至此,也就实现了我们的效果。以上就是本文的全部内容,希望本文的内容对大家开发android能有所帮助。