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

Android自定义控件实现圆形进度CircleProgressBar

程序员文章站 2022-05-14 17:46:02
近日有朋友问我有没有如下图效果的开源控件相信大家无论是用ios还是android,都对这种效果不陌生,很多主流app都会有这样或类似的效果,之前也打算研究一下这类控件的代码,苦于一直不知道应该怎么搜索...

近日有朋友问我有没有如下图效果的开源控件

Android自定义控件实现圆形进度CircleProgressBar

相信大家无论是用ios还是android,都对这种效果不陌生,很多主流app都会有这样或类似的效果,之前也打算研究一下这类控件的代码,苦于一直不知道应该怎么搜索这种效果(就是关键词)或者所搜的结果不是自己想要的,所以就一直搁置了下来。

正好朋友需要这种效果,所以就忙里偷闲写了一个类似的、更加常见和适用范围更多的控件,效果如下图所示:

Android自定义控件实现圆形进度CircleProgressBar

自定义上图所示效果的控件时,其实就是用canvas绘制不同效果,比如渐变圆弧背景、圆周白色分割线、中间文字等,这篇博客也根据绘制的顺序依次阐述。

1.自定义circleprogressbar,继承view,并实现响应的构造函数

代码如下:

/**
 * created by wangchunlei on 2016.1.16
 * e-mail:wcl_android@163.com
 */
public class gradientprogressbar extends view {
 public gradientprogressbar(context context) {
 super(context);
 init();
 }

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

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

其中init方法是对相关画笔进行初始化的方法,init方法代码如下:

private void init() {
 backcirclepaint = new paint();
 backcirclepaint.setstyle(paint.style.stroke);
 backcirclepaint.setantialias(true);
 backcirclepaint.setcolor(color.ltgray);
 backcirclepaint.setstrokewidth(circleborderwidth);
// backcirclepaint.setmaskfilter(new blurmaskfilter(20, blurmaskfilter.blur.outer));

 gradientcirclepaint = new paint();
 gradientcirclepaint.setstyle(paint.style.stroke);
 gradientcirclepaint.setantialias(true);
 gradientcirclepaint.setcolor(color.ltgray);
 gradientcirclepaint.setstrokewidth(circleborderwidth);

 linepaint = new paint();
 linepaint.setcolor(color.white);
 linepaint.setstrokewidth(5);

 textpaint = new paint();
 textpaint.setantialias(true);
 textpaint.settextsize(textsize);
 textpaint.setcolor(color.black);
 }

2.测量控件的宽高-onmeasure

onmeasure是自定义控件的第一步,目的就是测量得到该控件应该占有的宽高尺寸。其中onmeasure方法的代码如下:

@override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 int measurewidth = measurespec.getsize(widthmeasurespec);
 int measureheight = measurespec.getsize(heightmeasurespec);
 setmeasureddimension(math.min(measurewidth, measureheight), math.min(measurewidth, measureheight));
 }

贴上onmeasure的代码后,大家估计是很少见过测量过程这么简单的onmeasure,不要介意,有兴趣的同僚们可以细化一下这个测量过程,对不同的测量模式分别进行处理和测量,让控件适配效果更好更完善!

onmeasure方法中,分别获取期望的宽度和高度,并取其中较小的尺寸作为该控件的宽和高。

3.依次绘制不同的控件组成部分。

因为控件是直接继承自view,所以不需要再处理onlayout方法,这也是自定义view的难度远小于自定义viewgroup的原因,但继承viewgroup也并不一定要重写onmeasure。
要实现如图所示的效果,需要分以下步骤依次实现

(1)绘制灰色空心圆环
(2)绘制颜色渐变的圆环
(3)绘制圆环上分割的白色线条
(4)绘制百分比文字等。

绘制过程过,后绘制的内容如果与之前绘制的内容存在交集,则后绘制的内容会覆盖掉之前绘制的内容。

按照上述步骤依次介绍

在绘制过程中,会产生以下成员变量,下文中会用到:

/*圆弧线宽*/
 private float circleborderwidth = typedvalue.applydimension(typedvalue.complex_unit_dip, 20, getresources().getdisplaymetrics());
 /*内边距*/
 private float circlepadding = typedvalue.applydimension(typedvalue.complex_unit_dip, 20, getresources().getdisplaymetrics());
 /*字体大小*/
 private float textsize = typedvalue.applydimension(typedvalue.complex_unit_sp, 50, getresources().getdisplaymetrics());
 /*绘制圆周的画笔*/
 private paint backcirclepaint;
 /*绘制圆周白色分割线的画笔*/
 private paint linepaint;
 /*绘制文字的画笔*/
 private paint textpaint;
 /*百分比*/
 private int percent = 0;
 /*渐变圆周颜色数组*/
 private int[] gradientcolorarray = new int[]{color.green, color.parsecolor("#fe751a"), color.parsecolor("#13be23"), color.green};
 private paint gradientcirclepaint;

3.1绘制灰色空心圆环

代码如下:

//1.绘制灰色背景圆环
 canvas.drawarc(
 new rectf(circlepadding * 2, circlepadding * 2,
  getmeasuredwidth() - circlepadding * 2, getmeasuredheight() - circlepadding * 2), -90, 360, false, backcirclepaint);

其中,-90为绘制圆弧的起始角度,360是圆弧绘制的角度,即sweepangle.

3.2绘制颜色渐变的圆环

//2.绘制颜色渐变圆环
 lineargradient lineargradient = new lineargradient(circlepadding, circlepadding,
 getmeasuredwidth() - circlepadding,
 getmeasuredheight() - circlepadding,
 gradientcolorarray, null, shader.tilemode.mirror);
 gradientcirclepaint.setshader(lineargradient);
 gradientcirclepaint.setshadowlayer(10, 10, 10, color.red);
 canvas.drawarc(
 new rectf(circlepadding * 2, circlepadding * 2,
  getmeasuredwidth() - circlepadding * 2, getmeasuredheight() - circlepadding * 2), -90, (float) (percent / 100.0) * 360, false, gradientcirclepaint);

其中,lineargradient是paint的shadow,是为了圆弧的颜色渐变效果的而需要设置的,日常开发中应用频率不高,但的确是可以实现非常理想的颜色渐变效果。

3.3绘制圆环上分割的白色线条

绘制圆弧上的白色线条时,需要进行一些简单的运算,比如线条的起始坐标startx,starty和线条的终止坐标stopx,stopy等,利用简单的三角函数还是很容易去计算出来的。
效果中,将圆弧使用白色线条平分成100分,每一个的阶级为1,可以满足int类型的百分比与效果图比例的一致。

//半径
float radius = (getmeasuredwidth() - circlepadding * 3) / 2;
 //x轴中点坐标
 int centerx = getmeasuredwidth() / 2;

 //3.绘制100份线段,切分空心圆弧
 for (float i = 0; i < 360; i += 3.6) {
 double rad = i * math.pi / 180;
 float startx = (float) (centerx + (radius - circleborderwidth) * math.sin(rad));
 float starty = (float) (centerx + (radius - circleborderwidth) * math.cos(rad));

 float stopx = (float) (centerx + radius * math.sin(rad) + 1);
 float stopy = (float) (centerx + radius * math.cos(rad) + 1);

 canvas.drawline(startx, starty, stopx, stopy, linepaint);
 }

3.4绘制百分比文字等

最后绘制百分比文字。
绘制文字时,为了保持文字的中心点和圆弧的原点一致,需要先测量得到要显示文字的宽度和高度,然后再进行一些简单的运算,原理不再赘述,相信大家数学一定都比我好。

//4.绘制文字
float textwidth = textpaint.measuretext(percent + "%");
int textheight = (int) (math.ceil(textpaint.getfontmetrics().descent - textpaint.getfontmetrics().ascent) + 2);
 canvas.drawtext(percent + "%", centerx - textwidth / 2, centerx + textheight / 4, textpaint);

最后,暴漏一个公共的方法供改变显示的百分比,代码如下:

/**
 * 设置百分比
 *
 * @param percent
 */
 public void setpercent(int percent) {
 if (percent < 0) {
 percent = 0;
 } else if (percent > 100) {
 percent = 100;
 }

 this.percent = percent;
 invalidate();
 }

至此,所有绘制过程简述完毕,130行代码就能实现很炫酷的效果有木有?

最后,贴上项目完整代码,供懒得看实现过程的同僚们使用,o(∩_∩)o哈哈~

package com.example.myview;

import android.content.context;
import android.graphics.*;
import android.util.attributeset;
import android.util.typedvalue;
import android.view.view;

/**
 * created by wangchunlei on 2016.1.16
 * e-mail:wcl_android@163.com
 */
public class gradientprogressbar extends view {
 /*圆弧线宽*/
 private float circleborderwidth = typedvalue.applydimension(typedvalue.complex_unit_dip, 20, getresources().getdisplaymetrics());
 /*内边距*/
 private float circlepadding = typedvalue.applydimension(typedvalue.complex_unit_dip, 20, getresources().getdisplaymetrics());
 /*字体大小*/
 private float textsize = typedvalue.applydimension(typedvalue.complex_unit_sp, 50, getresources().getdisplaymetrics());
 /*绘制圆周的画笔*/
 private paint backcirclepaint;
 /*绘制圆周白色分割线的画笔*/
 private paint linepaint;
 /*绘制文字的画笔*/
 private paint textpaint;
 /*百分比*/
 private int percent = 0;
 /*渐变圆周颜色数组*/
 private int[] gradientcolorarray = new int[]{color.green, color.parsecolor("#fe751a"), color.parsecolor("#13be23"), color.green};
 private paint gradientcirclepaint;

 public gradientprogressbar(context context) {
 super(context);
 init();
 }

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

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

 private void init() {
 backcirclepaint = new paint();
 backcirclepaint.setstyle(paint.style.stroke);
 backcirclepaint.setantialias(true);
 backcirclepaint.setcolor(color.ltgray);
 backcirclepaint.setstrokewidth(circleborderwidth);
// backcirclepaint.setmaskfilter(new blurmaskfilter(20, blurmaskfilter.blur.outer));

 gradientcirclepaint = new paint();
 gradientcirclepaint.setstyle(paint.style.stroke);
 gradientcirclepaint.setantialias(true);
 gradientcirclepaint.setcolor(color.ltgray);
 gradientcirclepaint.setstrokewidth(circleborderwidth);

 linepaint = new paint();
 linepaint.setcolor(color.white);
 linepaint.setstrokewidth(5);

 textpaint = new paint();
 textpaint.setantialias(true);
 textpaint.settextsize(textsize);
 textpaint.setcolor(color.black);
 }


 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 int measurewidth = measurespec.getsize(widthmeasurespec);
 int measureheight = measurespec.getsize(heightmeasurespec);
 setmeasureddimension(math.min(measurewidth, measureheight), math.min(measurewidth, measureheight));
 }

 @override
 protected void ondraw(canvas canvas) {
 super.ondraw(canvas);
 //1.绘制灰色背景圆环
 canvas.drawarc(
 new rectf(circlepadding * 2, circlepadding * 2,
  getmeasuredwidth() - circlepadding * 2, getmeasuredheight() - circlepadding * 2), -90, 360, false, backcirclepaint);
 //2.绘制颜色渐变圆环
 lineargradient lineargradient = new lineargradient(circlepadding, circlepadding,
 getmeasuredwidth() - circlepadding,
 getmeasuredheight() - circlepadding,
 gradientcolorarray, null, shader.tilemode.mirror);
 gradientcirclepaint.setshader(lineargradient);
 gradientcirclepaint.setshadowlayer(10, 10, 10, color.red);
 canvas.drawarc(
 new rectf(circlepadding * 2, circlepadding * 2,
  getmeasuredwidth() - circlepadding * 2, getmeasuredheight() - circlepadding * 2), -90, (float) (percent / 100.0) * 360, false, gradientcirclepaint);

 //半径
 float radius = (getmeasuredwidth() - circlepadding * 3) / 2;
 //x轴中点坐标
 int centerx = getmeasuredwidth() / 2;

 //3.绘制100份线段,切分空心圆弧
 for (float i = 0; i < 360; i += 3.6) {
 double rad = i * math.pi / 180;
 float startx = (float) (centerx + (radius - circleborderwidth) * math.sin(rad));
 float starty = (float) (centerx + (radius - circleborderwidth) * math.cos(rad));

 float stopx = (float) (centerx + radius * math.sin(rad) + 1);
 float stopy = (float) (centerx + radius * math.cos(rad) + 1);

 canvas.drawline(startx, starty, stopx, stopy, linepaint);
 }

 //4.绘制文字
 float textwidth = textpaint.measuretext(percent + "%");
 int textheight = (int) (math.ceil(textpaint.getfontmetrics().descent - textpaint.getfontmetrics().ascent) + 2);
 canvas.drawtext(percent + "%", centerx - textwidth / 2, centerx + textheight / 4, textpaint);
 }

 /**
 * 设置百分比
 *
 * @param percent
 */
 public void setpercent(int percent) {
 if (percent < 0) {
 percent = 0;
 } else if (percent > 100) {
 percent = 100;
 }

 this.percent = percent;
 invalidate();
 }
}

最后,贴上自定义控件代码(自定义控件、activity,布局文件)下载地址: android圆形进度circleprogressbar

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