Android自定义View实现渐变色仪表盘
前言:最近一直在学自定义view的相关知识,感觉这在android中还是挺难的一块,当然这也是每个程序员必经之路,正好公司项目要求实现类似仪表盘的效果用于直观的显示公司数据,于是就简单的写了个demo,记录实现的过程。上篇《android自定义view实现圆弧进度效果》简单记录了圆弧及文字的绘制,渐变色的仪表盘效果将更加升入的介绍canvas及paint的使用(如画布旋转,paint的渐变色设置等)。
知识梳理
1.圆弧渐变色(sweepgradient)
2.圆弧上刻度绘制
3.指针指示当前数据位置(bitmap)
4.数据文本跟随弧度显示(drawtextonpath)
效果图:
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实现圆弧的渐变色效果。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 抖音推送规律是什么(解读抖音推送算法)