Android自定义View之酷炫圆环(二)

时间:2022-07-01 09:14:29

先看下最终的效果
静态:

Android自定义View之酷炫圆环(二)

动态:

Android自定义View之酷炫圆环(二)

一、开始实现
新建一个doughnutprogress继承view

?
1
2
3
public class doughnutprogress extends view {
 
}

先给出一些常量、变量以及公共方法的代码,方便理解后面的代码
   

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static final int default_min_width = 400; //view默认最小宽度
  private static final int red = 230, green = 85, blue = 35; //基础颜色,这里是橙红色
  private static final int min_alpha = 30; //最小不透明度
  private static final int max_alpha = 255; //最大不透明度
  private static final float doughnutraduispercent = 0.65f; //圆环外圆半径占view最大半径的百分比
  private static final float doughnutwidthpercent = 0.12f; //圆环宽度占view最大半径的百分比
  private static final float middle_wave_raduis_percent = 0.9f; //第二个圆出现时,第一个圆的半径百分比
  private static final float wave_width = 5f; //波纹圆环宽度
 
  //圆环颜色
  private static int[] doughnutcolors = new int[]{
      color.argb(max_alpha, red, green, blue),
      color.argb(min_alpha, red, green, blue),
      color.argb(min_alpha, red, green, blue)};
 
  private paint paint = new paint(); //画笔
  private float width; //自定义view的宽度
  private float height; //自定义view的高度
  private float currentangle = 0f; //当前旋转角度
  private float raduis; //自定义view的最大半径
  private float firstwaveraduis;
  private float secondwaveraduis;
 
  //
  private void resetparams() {
    width = getwidth();
    height = getheight();
    raduis = math.min(width, height)/2;
  }
 
  private void initpaint() {
    paint.reset();
    paint.setantialias(true);
  }

重写onmeasure方法,为什么要重写onmeasure方法可以看我的上一篇文章,点这里

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 当布局为wrap_content时设置默认长宽
 *
 * @param widthmeasurespec
 * @param heightmeasurespec
 */
@override
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  setmeasureddimension(measure(widthmeasurespec), measure(heightmeasurespec));
}
 
private int measure(int origin) {
  int result = default_min_width;
  int specmode = measurespec.getmode(origin);
  int specsize = measurespec.getsize(origin);
  if (specmode == measurespec.exactly) {
    result = specsize;
  } else {
    if (specmode == measurespec.at_most) {
      result = math.min(result, specsize);
    }
  }
  return result;
}

下面就是最重要的重写ondraw方法,大致流程如下
在开始绘制之前,先初始化width、height、raduis, 以及将view的中心作为原点

?
1
2
3
4
resetparams();
 
//将画布中心设为原点(0,0), 方便后面计算坐标
canvas.translate(width / 2, height / 2);

实现静态的渐变圆环
1、画渐变圆环

?
1
2
3
4
5
6
7
8
9
10
11
12
float doughnutwidth = raduis * doughnutwidthpercent;//圆环宽度
 //圆环外接矩形
 rectf rectf = new rectf(
 -raduis * doughnutraduispercent,
 -raduis * doughnutraduispercent,
 raduis * doughnutraduispercent,
 raduis * doughnutraduispercent);
 initpaint();
 paint.setstrokewidth(doughnutwidth);
 paint.setstyle(paint.style.stroke);
 paint.setshader(new sweepgradient(0, 0, doughnutcolors, null));
 canvas.drawarc(rectf, 0, 360, false, paint);

通过修改doughnutcolors可以实现不同的渐变效果
2、画圆环旋转头部的圆

?
1
2
3
4
5
//画旋转头部圆
 initpaint();
 paint.setstyle(paint.style.fill);
 paint.setcolor(color.argb(max_alpha, red, green, blue));
 canvas.drawcircle(raduis * doughnutraduispercent, 0, doughnutwidth / 2, paint);

此时运行代码得到效果如下图:

Android自定义View之酷炫圆环(二)

我们还可以在绘制圆环之前通过旋转画布得到不同初始状态
    canvas.rotate(-45, 0, 0);

Android自定义View之酷炫圆环(二)

    canvas.rotate(-180, 0, 0);

Android自定义View之酷炫圆环(二)

此时聪明的你应该已经想到怎么让这个圆环旋转起来了吧^_^

对!正如你所想的,就是通过canvas.rotate方法不停地旋转画布(这个“地”是这么用的吧o(╯□╰)o)

让圆环旋转起来
在绘制圆环之前加上下面的代码:

?
1
2
3
4
5
6
7
//转起来
canvas.rotate(-currentangle, 0, 0);
if (currentangle >= 360f){
  currentangle = currentangle - 360f;
} else{
  currentangle = currentangle + 2f;
}

然后再让一个线程循环刷新就好了

?
1
2
3
4
5
6
7
8
9
10
11
12
13
private thread thread = new thread(){
  @override
  public void run() {
    while(true){
      try {
        thread.sleep(10);
      } catch (interruptedexception e) {
        e.printstacktrace();
      }
      postinvalidate();
    }
  }
};

试试!转起来了吗o(∩_∩)o~

下面是比较有意思的部分,实现类似水波涟漪的效果
分析水波涟漪效果的实现原理(画了张草图方便理解):

Android自定义View之酷炫圆环(二)

假设淡黄色背景区域为整个view的大小

黑色圆圈为view内的最大圆(半径为r3)

橙色圆环代表渐变圆环

红色圆圈代表圆环的外圆(半径为r1)

紫色圆圈是干啥子的,待会儿再介绍~(半径为r2)

通过观察实现的最终效果,可以发现有个圆的半径从r1逐渐增大r3,不透明度逐渐减小到0。

那是不是这样周而复始就可以实现最终的效果了呢?

没那么简单。。。

仔细观察发现,第二个圆不是等到第一个圆的半径增大到r3才开始出现的,而是在将要消失的时候就出现了,有一段时间是两个圆同时存在的。

那么我们就假设当第一个圆的半径增大到r2,第二个圆开始出现。

开始想象两个圆的循环运行模型~~~

我的方案是:

绘制两个圆,每个圆的半径都从r1增大到r1+2x(r2-r1),不透明度还是从r1到r3的过程中逐渐变为0,也就是当圆的半径大于r3时,不透明度就为0了(不可见了),将第一个圆半径初始值设为r1,第二个圆半径初始值设为r2。这样两个圆半径同时逐渐增大,当半径大于 r1+2x(r2-r1)时又重新回到r1大小继续增大,就实现了类似水波涟漪的效果了。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//实现类似水波涟漪效果
initpaint();
paint.setstyle(paint.style.stroke);
paint.setstrokewidth(5);
secondwaveraduis = calculatewaveraduis(secondwaveraduis);
firstwaveraduis = calculatewaveraduis(secondwaveraduis + raduis*(middle_wave_raduis_percent - doughnutraduispercent) - raduis*doughnutwidthpercent/2);
paint.setcolor(color.argb(calculatewavealpha(secondwaveraduis), red, green, blue));
canvas.drawcircle(0, 0, secondwaveraduis, paint); //画第二个圆(初始半径较小的)
 
initpaint();
paint.setstyle(paint.style.stroke);
paint.setstrokewidth(5);
paint.setcolor(color.argb(calculatewavealpha(firstwaveraduis), red, green, blue));
canvas.drawcircle(0, 0, firstwaveraduis, paint); //画第一个圆(初始半径较大的)
 
 
/**
 * 计算波纹圆的半径
 * @param waveraduis
 * @return
 */
private float calculatewaveraduis(float waveraduis){
  if(waveraduis < raduis*doughnutraduispercent + raduis*doughnutwidthpercent/2){
    waveraduis = raduis*doughnutraduispercent + raduis*doughnutwidthpercent/2;
  }
  if(waveraduis > raduis*middle_wave_raduis_percent + raduis*(middle_wave_raduis_percent - doughnutraduispercent) - raduis*doughnutwidthpercent/2){
    waveraduis = waveraduis - (raduis*middle_wave_raduis_percent + raduis*(middle_wave_raduis_percent - doughnutraduispercent) - raduis*doughnutwidthpercent/2) + raduis*doughnutwidthpercent/2 + raduis*doughnutraduispercent;
  }
    waveraduis += 0.6f;
  return waveraduis;
}
 
/**
 * 根据波纹圆的半径计算不透明度
 * @param waveraduis
 * @return
 */
private int calculatewavealpha(float waveraduis){
  float percent = (waveraduis-raduis*doughnutraduispercent-raduis*doughnutwidthpercent/2)/(raduis-raduis*doughnutraduispercent-raduis*doughnutwidthpercent/2);
  if(percent >= 1f){
    return 0;
  }else{
    return (int) (min_alpha*(1f-percent));
  }
}

以上就是本文的全部内容,希望对大家的学习android软件编程有所帮助。