Android自定义控件实现圆形进度CircleProgressBar
作者:不踢足球的王大雷 发布时间:2023-04-16 08:24:50
近日有朋友问我有没有如下图效果的开源控件
相信大家无论是用IOS还是Android,都对这种效果不陌生,很多主流APP都会有这样或类似的效果,之前也打算研究一下这类控件的代码,苦于一直不知道应该怎么搜索这种效果(就是关键词)或者所搜的结果不是自己想要的,所以就一直搁置了下来。
正好朋友需要这种效果,所以就忙里偷闲写了一个类似的、更加常见和适用范围更多的控件,效果如下图所示:
自定义上图所示效果的控件时,其实就是用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
来源:https://blog.csdn.net/wangchunlei123/article/details/50478913


猜你喜欢
- IDEA安装后,前进 后退快捷按钮默认不在工具栏显示,需要手动将其添加到工具栏*按照图一选中Toolbar Run Actions ,点击右
- 一 前言最近网上比较火的代码生成器,知识追寻者抽空试试了一下,感觉不是友好,只能说功能比较呆板吧,还需要自己玩填空题,修修补补,然后再次打开
- 前言前段时间碰到了中转文件的需求,需要使用HttpClient中转一下文件,过程为:在实现这个需求的过程中就用得到了MultipartFil
- 一:什么是SparkSQL?(一)SparkSQL简介Spark SQL是Spark的一个模块,用于处理结构化的数据,它提供了一个数据抽象D
- 本文实例为大家分享了java连接SQL Server数据库的具体代码,供大家参考,具体内容如下操作系统:windows 10 64位java
- InputStreamReader和OutputStreamWriter源码分析1. InputStreamReader 源码(基于jdk1
- Spring @Configuration 和 @Component 区别一句话概括就是 @Configuration 中所有带 @Bean
- 本文介绍了ImageView 实现Android colorPikcer 选择器的示例代码,分享给大家,具体如下:Android color
- 数据加解密的实现方式多种多样,在mybatis环境中数据加解密变得非常简单易用,本文旨在提供参考,在生产中应尽可能完成单元测试,开展足够的覆
- Java IO中File的使用是比较频繁的,在文件的上传和删除中都会用到的。比如我们在写管理系统的时候有可能会用到图片的上传,和删除。那么我
- 本文实例讲述了Java设计模式之工厂模式实现方法。分享给大家供大家参考,具体如下:工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体
- 图片解析:1.生成字节码文件的过程可能产生编译时异常(checked),由字节码文件到在内存中加载、运行类此过程可能产生运行时异常(unch
- 场景在任何一个Form表单的操作页面或者数据台账的查询页面,基本都会看到一个清除的按钮,其功能就是用来清除我们需要抛弃的已经写入到控件内的数
- 开发中最让人头疼的是应用突然 * ,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误
- JAVA反射机制JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方
- 先看看下面的代码能不能编译通过:public static void main(String[] args) {List l1 = new
- 背景环境需要设置代理才能够访问外部网络,如果只是运行java程序来访问网络,我们可以通过java -jar test.jar -Dproxy
- 之前在使用SpringBoot进行文件上传时,遇到了很多问题。于是在翻阅了很多的博文之后,总算将上传功能进行了相应的完善,便在这里记录下来,
- 前言Java中容器对象主要用来存储其他对象,根据实现原理不同,主要有3类常用的容器对象:1、ArrayList 使用数组结构存储容器中的元素
- 按官方修改的示例:#MidServerClient.javaimport feign.Param;import org.springfram