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

Android自定义View实现渐变色仪表盘

程序员文章站 2023-10-29 13:09:46
前言:最近一直在学自定义view的相关知识,感觉这在android中还是挺难的一块,当然这也是每个程序员必经之路,正好公司项目要求实现类似仪表盘的效果用于直观的显示公司数据...

前言:最近一直在学自定义view的相关知识,感觉这在android中还是挺难的一块,当然这也是每个程序员必经之路,正好公司项目要求实现类似仪表盘的效果用于直观的显示公司数据,于是就简单的写了个demo,记录实现的过程。上篇《android自定义view实现圆弧进度效果》简单记录了圆弧及文字的绘制,渐变色的仪表盘效果将更加升入的介绍canvas及paint的使用(如画布旋转,paint的渐变色设置等)。

知识梳理

1.圆弧渐变色(sweepgradient)

2.圆弧上刻度绘制

3.指针指示当前数据位置(bitmap)

4.数据文本跟随弧度显示(drawtextonpath)

效果图:

Android自定义View实现渐变色仪表盘

1.继承自view

(1)重写构造方法,初始化paint

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

public dashboardview(context context, attributeset attrs) {
 this(context, attrs, 0);
}

public dashboardview(context context, attributeset attrs, int defstyleattr) {
 super(context, attrs, defstyleattr);
 init();
}

初始化相关paint

/**
 * 初始化paint
 */
private void init() {
 //设置默认宽高值
 defaultsize = dp2px(260);

 //设置图片线条的抗锯齿
 mpaintflagsdrawfilter = new paintflagsdrawfilter
   (0, paint.anti_alias_flag | paint.filter_bitmap_flag);

 //最外层圆环渐变画笔设置
 moutergradientpaint = new paint(paint.anti_alias_flag);
 //设置圆环渐变色渲染
 moutergradientpaint.setxfermode(new porterduffxfermode(porterduff.mode.src_atop));
 float position[] = {0.1f, 0.3f, 0.8f};
 shader mshader = new sweepgradient(width / 2, radius, mcolors, position);
 moutergradientpaint.setshader(mshader);
 moutergradientpaint.setstrokecap(paint.cap.round);
 moutergradientpaint.setstyle(paint.style.stroke);
 moutergradientpaint.setstrokewidth(30);

 //最外层圆环刻度画笔设置
 mcalibrationpaint = new paint(paint.anti_alias_flag);
 mcalibrationpaint.setcolor(color.white);
 mcalibrationpaint.setstyle(paint.style.stroke);

 //中间圆环画笔设置
 mmiddlepaint = new paint(paint.anti_alias_flag);
 mmiddlepaint.setstyle(paint.style.stroke);
 mmiddlepaint.setstrokecap(paint.cap.round);
 mmiddlepaint.setstrokewidth(5);
 mmiddlepaint.setcolor(gray_color);

 //内层圆环画笔设置
 minnerpaint = new paint(paint.anti_alias_flag);
 minnerpaint.setstyle(paint.style.stroke);
 minnerpaint.setstrokecap(paint.cap.round);
 minnerpaint.setstrokewidth(4);
 minnerpaint.setcolor(gray_color);
 patheffect mpatheffect = new dashpatheffect(new float[]{5, 5, 5, 5}, 1);
 minnerpaint.setpatheffect(mpatheffect);

 //外层圆环文本画笔设置
 mtextpaint = new paint(paint.anti_alias_flag);
 mtextpaint.setcolor(gray_color);
 mtextpaint.settextsize(dp2px(12));

 //中间文字画笔设置
 mcentertextpaint = new paint(paint.anti_alias_flag);
 mcentertextpaint.settextalign(paint.align.center);

 //中间圆环进度画笔设置
 mmiddleprogresspaint = new paint(paint.anti_alias_flag);
 mmiddleprogresspaint.setcolor(green_color);
 mmiddleprogresspaint.setstrokecap(paint.cap.round);
 mmiddleprogresspaint.setstrokewidth(5);
 mmiddleprogresspaint.setstyle(paint.style.stroke);

 //指针图片画笔
 mpointerbitmappaint = new paint(paint.anti_alias_flag);
 mpointerbitmappaint.setcolor(green_color);

 //获取指针图片及宽高
 mbitmap = bitmapfactory.decoderesource(getresources(), r.mipmap.pointer);
 mbitmapheight = mbitmap.getheight();
 mbitmapwidth = mbitmap.getwidth();
}

注:

a、最外层圆弧的渐变色使用的是sweepgradient类实现的,sweepgradient继承自shader;

b、注意渐变色的开始角度问题,如果跟圆弧起始角度不一致,记得使用矩阵转换进行旋转,再让paint去设置shader;

c、sweepgradient的第3个参数int[] colors必须包含两个及以上颜色值,不然会报错;

d、sweepgradient的第四个参数的数组大小必须和第三个参数的数组大小一样,也可以填入null。

(2)重写onmeasure,用于测量view宽高

onmeasure方法:

@override
protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 setmeasureddimension(remeasure(widthmeasurespec, defaultsize),
   remeasure(heightmeasurespec, defaultsize));
}

remeasure方法:

/**
 * 根据传入的值进行重新测量
 */
public int remeasure(int measurespec, int defaultsize) {

 int result;
 int specsize = measurespec.getsize(measurespec);
 switch (measurespec.getmode(measurespec)) {
  case measurespec.unspecified:
   //未指定
   result = defaultsize;
   break;
  case measurespec.at_most:
   //设置warp_content时设置默认值
   result = math.min(specsize, defaultsize);
   break;
  case measurespec.exactly:
   //设置math_parent 和设置了固定宽高值
   result=specsize;
   break;
  default:
   result = defaultsize;
 }
 return result;
}

(3)重写onchange,用于获取view宽高

在onchange方法中获取当前view的宽高及获取圆弧的半径,初始化圆弧的rectf等

@override
protected void onsizechanged(int w, int h, int oldw, int oldh) {
 super.onsizechanged(w, h, oldw, oldh);

 //确定view宽高
 width = w;
 height = h;

 //圆环半径
 radius = width / 2;

 //外层圆环
 float oval1 = radius - moutergradientpaint.getstrokewidth() * 0.5f;
 mouterrectf = new rectf(-oval1, -oval1, oval1, oval1);

 //中间和内层圆环
 float oval2 = radius * 5 / 8;
 float oval3 = radius * 3 / 4;
 minnerrectf = new rectf(-oval2 + dp2px(5), -oval2 + dp2px(5), oval2 - dp2px(5), oval2 - dp2px(5));
 mmiddlerectf = new rectf(-oval3 + dp2px(10), -oval3 + dp2px(10), oval3 - dp2px(10), oval3 - dp2px(10));

 //中间进度圆环
 oval4 = radius * 6 / 8;
 mmiddleprogressrectf = new rectf(-oval4+ dp2px(10), -oval4+ dp2px(10), oval4- dp2px(10), oval4- dp2px(10));
}

(4)重写ondraw方法,用于绘制view

@suppresslint("drawallocation")
@override
protected void ondraw(canvas canvas) {
 //设置画布绘图无锯齿
 canvas.setdrawfilter(mpaintflagsdrawfilter);
 //绘制圆弧
 drawarc(canvas);
 //绘制圆弧上的刻度
 drawcalibration(canvas);
 //绘制跟随圆弧path的文字
 drawarctext(canvas);
 //绘制圆弧中心文字
 drawcentertext(canvas);
 //绘制当前bitmap指针指示进度
 drawbitmapprogress(canvas);
}

2.canvas绘制view

mstartangle=105f,mendangle=250f

(1)绘制圆弧

/**
 * 分别绘制外层 中间 内层圆环
 */
private void drawarc(canvas canvas) {

 canvas.save();
 canvas.translate(width / 2, height / 2);
 //画布旋转140°
 canvas.rotate(140);
 //最外层的渐变圆环
 canvas.drawarc(mouterrectf, -mstartangle, -mendangle, false, moutergradientpaint);

 //绘制内层虚线圆弧
 canvas.drawarc(minnerrectf, -mstartangle, -mendangle, false, minnerpaint);
 //绘制中间圆弧
 canvas.drawarc(mmiddlerectf, -mstartangle, -mendangle, false, mmiddlepaint);
 canvas.restore();
}

(2)绘制渐变色圆弧上的大小刻度

/**
 * 绘制外层渐变色圆弧上的大小刻度线
 */
private void drawcalibration(canvas canvas) {
 int dst = (int) (2 * radius - moutergradientpaint.getstrokewidth());
 for (int i = 0; i <= 40; i++) {
  canvas.save();
  canvas.rotate(-(-30 + 6 * i), radius, radius);
  if (i % 10 == 0) {
   mcalibrationpaint.setstrokewidth(4);
   //绘制大刻度
   canvas.drawline(dst, radius, 2 * radius, radius, mcalibrationpaint);
  } else {
   //小刻度
   mcalibrationpaint.setstrokewidth(1);
   canvas.drawline(dst, radius, 2 * radius, radius, mcalibrationpaint);
  }
  canvas.restore();
 }
}

注:

a、圆弧的总弧度为240f,循环40次

b、小刻度每次旋转6弧度,每绘制10次小刻度就会绘制一次大刻度,即大刻度每次旋转60弧度

(3)绘制跟随圆弧弧度描述文字

/**
 * 绘制跟随圆弧弧度的文本
 */
private void drawarctext(canvas canvas) {
 canvas.save();
 //每次旋转角度
 int rotateangle = 30;
 //旋转画布
 canvas.rotate(-118, radius - dp2px(26), radius-dp2px(103));
 for (int i = 0; i < valuelist.size(); i++) {
  //计算起始角度
  int startangle = 30 * i - 108;
  //设置数据跟着圆弧绘制
  path paths = new path();
  paths.addarc(minnerrectf, startangle, rotateangle);
  float textlen = mtextpaint.measuretext(valuelist.get(i));
  canvas.drawtextonpath(valuelist.get(i), paths, -textlen / 2 + dp2px(20), -dp2px(22), mtextpaint);
  //canvas.drawtext(text[i], radius - 10, radius * 3 / 16+dp2px(10), mtextpaint);
 }
 canvas.restore();
}

注:

a、drawtextonpath为文字随path路径显示,drawtextonpath的第3个参数hoffset为文字水平方向的偏移量,第4个参数voffset为文字垂直方向的偏移量;

b、重点是画布开始时的旋转角度及不同文字的起始角度

(4)绘制圆弧中心的数据及描述信息

/**
 * 绘制圆弧中间的文本内容
 */
private void drawcentertext(canvas canvas) {

 //绘制当前数据值
 mcentertextpaint.setcolor(green_color);
 mcentertextpaint.settextsize(dp2px(25));
 mcentertextpaint.setstyle(paint.style.stroke);
 canvas.drawtext(string.valueof(manimatorvalue), radius, radius, mcentertextpaint);

 //绘制当前数据描述
 mcentertextpaint.settextsize(dp2px(20));
 canvas.drawtext(mcurrentdes, radius, radius + dp2px(25), mcentertextpaint);

}

(5)绘制当前数值对应的圆弧及指针图片指示

/**
 * 绘制当前进度和指示图片
 */
private void drawbitmapprogress(canvas canvas) {
 //如果当前角度为0,则不绘制指示图片
 if (mcurrentangle==0f){
  return;
 }
 canvas.save();
 canvas.translate(radius, radius);
 canvas.rotate(270);
 //绘制对应的圆弧
 canvas.drawarc(mmiddleprogressrectf, -mstartangle-20, mcurrentangle+5, false, mmiddleprogresspaint);
 canvas.rotate(60 + mcurrentangle);
 //利用矩阵平移使图片指针方向始终指向刻度
 matrix matrix = new matrix();
 matrix.pretranslate(-oval4 - mbitmapwidth * 3 / 8 + 10, -mbitmapheight / 2);
 canvas.drawbitmap(mbitmap, matrix, mpointerbitmappaint);
 canvas.restore();
}

注:为了使指针图片的指针一直指向刻度盘上的刻度,这里使用了矩阵的平移。

3.添加动画及数据

(1)动画效果

/**
 * 当前数据对应弧度旋转及当前数据自增动画
 */
public void startrotateanim() {

 valueanimator mangleanim = valueanimator.offloat(mcurrentangle, mtotalangle);
 mangleanim.setinterpolator(new acceleratedecelerateinterpolator());
 mangleanim.setduration(2500);
 mangleanim.addupdatelistener(new valueanimator.animatorupdatelistener() {

  @override
  public void onanimationupdate(valueanimator valueanimator) {

   mcurrentangle = (float) valueanimator.getanimatedvalue();
   postinvalidate();
  }
 });
 mangleanim.start();

 valueanimator mnumanim = valueanimator.ofint(manimatorvalue, mcurrentvalue);
 mnumanim.setduration(2500);
 mnumanim.setinterpolator(new linearinterpolator());
 mnumanim.addupdatelistener(new valueanimator.animatorupdatelistener() {

  @override
  public void onanimationupdate(valueanimator valueanimator) {

   manimatorvalue = (int) valueanimator.getanimatedvalue();
   postinvalidate();
  }
 });
 mnumanim.start();
}

(2)设置数据及描述信息

/**
 * 设置数据
 */
public void setvalues(int values, list<string> valuelist) {
 this.valuelist=valuelist;
 if (values <= 0) {
  mcurrentvalue = values;
  mtotalangle = 0f;
  mcurrentdes = "";
 } else if (values <= 14000) {
  mcurrentvalue = values;
  mtotalangle = values / 14000f * 60-2;
  log.e("rcw","mtotalangle="+mtotalangle);
  mcurrentdes = "基础目标";
 } else if (values>14000&&values <= 17000) {
  mcurrentvalue = values;
  mcurrentdes = "测试目标";
  mtotalangle = values / 17000f * 120-2;
 } else if (values>17000&&values <= 21000) {
  mcurrentvalue = values;
  mtotalangle = values / 21000f * 180-2;
  mcurrentdes = "保底目标";
 } else {
  mcurrentvalue=values;
  float ratio=values / 21000f;
  if (ratio<20){
   mtotalangle = ratio+180;
  }else {
   mtotalangle = (float) (ratio*0.2+200);
  }
  mcurrentdes = "冲刺目标";
 }

 startrotateanim();
}

总结:自定义view实现仪表盘效果用到了canvas的旋转及矩阵平移;drawtextonpath使的文字跟随path绘制;sweepgradient实现圆弧的渐变色效果。

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