重学 Android 自定义 View 系列(六):环形进度条
目标
自定义一个环形进度条,可以自定义其最大值、当前进度、背景色、进度色,宽度等信息。
最终效果如下(GIF展示纯色有点问题):

1. 结构分析
- 背景圆环:表示进度条的背景。
- 进度圆环:表示当前进度,根据进度值动态绘制圆环。
- 进度值文本:在圆环中间展示进度。
2. 实现思路
- 定义自定义属性:在 res/values/attrs.xml 中定义自定义属性,以便通过 XML 配置自定义的视图样式。
- 初始化视图元素:在构造函数中,根据传入的属性初始化各种画笔、尺寸和视图元素。
- 绘制视图内容:重写 onDraw 方法,使用画笔绘制背景圆环、进度圆环和文本。
- 支持动态更新进度:提供 setProgress 方法,允许外部动态设置进度,触发视图重绘。
3. 关键技术点解析
我们首先要知道,画圆的基础是有个正方形,或者说有一个正方形的坐标,还要考虑到画笔的宽度,所以我们首先就要确定矩形的大小:
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);int padding = circleWidth / 2;rectF.set(padding, padding, w - padding, h - padding);}
circleWidth 为圆环的宽,也为圆环画笔的宽。
canvas.drawArc是用于绘制弧形(圆弧)的一种方法,具体参数在前几篇文章已介绍,再次不做赘述。这里主要自定义了进度开始的角度,即 startAngle 属性。
textPaint.setTextAlign(Paint.Align.CENTER)
重点关注下文本画笔的这个属性,在前几篇文章中都是通过计算基线的方式确保文本水平居中的,本篇文章的实现方式不同,先来看下setTextAlign 的作用:
textPaint.setTextAlign(Paint.Align.CENTER) 是 Paint 类中的一个设置文本对齐方式的方法。它用于控制文本在指定位置绘制时的对齐方式,具体来说,它影响了绘制文本时文本的起始点(x,y 坐标)如何被确定。
作用:
Paint.Align 是一个枚举,定义了文本绘制时如何对齐相对于给定的 x 和 y 坐标。常用的对齐方式有三个选项:
-
Paint.Align.LEFT:- 文本绘制时,文本的起始位置在
x坐标上。 - 也就是说,文本的左边缘会对齐到
x坐标。
- 文本绘制时,文本的起始位置在
-
Paint.Align.CENTER:- 文本绘制时,文本的中心位置与
x坐标对齐。 - 这意味着文本的中点(水平中心)会与
x坐标对齐,从而实现水平居中。
- 文本绘制时,文本的中心位置与
-
Paint.Align.RIGHT:- 文本绘制时,文本的右边缘会对齐到
x坐标。 - 也就是说,文本的右边缘会对齐到指定的
x坐标,x坐标即为文本的终点。
- 文本绘制时,文本的右边缘会对齐到
所以说现在我们不用计算基线,而是给drawText一个中心的 x 坐标,文字就能做到水平居中了!
4. 定义自定义属性
<declare-styleable name="CircularProgressBar"><!-- 进度条的最大值 --><attr name="maxProgress" format="integer"/><!-- 当前进度 --><attr name="progress" format="integer"/><!-- 环形进度条的背景色 --><attr name="circleBackgroundColor" format="color"/><!-- 进度条的颜色 --><attr name="progressColor" format="color"/><!-- 进度条的宽度 --><attr name="circleWidth" format="dimension"/><!-- 显示进度文本 --><attr name="showProgressText" format="boolean"/><!-- 进度文本的颜色 --><attr name="progressTextColor" format="color"/><!-- 进度文本的大小 --><attr name="progressTextSize" format="dimension"/><!-- 开始角度 --><attr name="startAngle" format="enum"><enum name="angle0" value="0"/><enum name="angle90" value="90"/><enum name="angle180" value="180"/><enum name="angle270" value="270"/></attr></declare-styleable>
5. 完整代码
public class CircularProgressBar extends View {private Paint backgroundPaint;private Paint progressPaint;private Paint textPaint;private RectF rectF;private int maxProgress = 100;private int progress = 0;private int circleBackgroundColor = Color.GRAY;private int progressColor = Color.GREEN;private int circleWidth = 20;private boolean showProgressText = true;private int progressTextColor = Color.BLACK;private int progressTextSize = 50;private int startAngle = 0; // 起始角度,默认从0开始public CircularProgressBar(Context context) {this(context, null);}public CircularProgressBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 初始化自定义属性if (attrs != null) {TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.CircularProgressBar,0, 0);try {maxProgress = typedArray.getInt(R.styleable.CircularProgressBar_maxProgress, 100);progress = typedArray.getInt(R.styleable.CircularProgressBar_progress, 0);circleBackgroundColor = typedArray.getColor(R.styleable.CircularProgressBar_circleBackgroundColor, Color.GRAY);progressColor = typedArray.getColor(R.styleable.CircularProgressBar_progressColor, Color.GREEN);circleWidth = typedArray.getDimensionPixelSize(R.styleable.CircularProgressBar_circleWidth, 20);showProgressText = typedArray.getBoolean(R.styleable.CircularProgressBar_showProgressText, true);progressTextColor = typedArray.getColor(R.styleable.CircularProgressBar_progressTextColor, Color.BLACK);progressTextSize = typedArray.getDimensionPixelSize(R.styleable.CircularProgressBar_progressTextSize, 50);// 获取自定义的起始角度int angleValue = typedArray.getInt(R.styleable.CircularProgressBar_startAngle, 0);switch (angleValue) {case 90:startAngle = 90;break;case 180:startAngle = 180;break;case 270:startAngle = 270;break;default:startAngle = 0;break;}} finally {typedArray.recycle();}}// 设置画笔属性backgroundPaint = new Paint();backgroundPaint.setColor(circleBackgroundColor);backgroundPaint.setStyle(Paint.Style.STROKE);backgroundPaint.setStrokeWidth(circleWidth);backgroundPaint.setAntiAlias(true);progressPaint = new Paint();progressPaint.setColor(progressColor);progressPaint.setStyle(Paint.Style.STROKE);progressPaint.setStrokeWidth(circleWidth);progressPaint.setStrokeCap(Paint.Cap.ROUND);//圆角progressPaint.setAntiAlias(true);textPaint = new Paint();textPaint.setColor(progressTextColor);textPaint.setTextSize(progressTextSize);textPaint.setTextAlign(Paint.Align.CENTER);//文本对齐方式textPaint.setAntiAlias(true);rectF = new RectF();}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);int padding = circleWidth / 2;rectF.set(padding, padding, w - padding, h - padding);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 绘制背景圆环canvas.drawArc(rectF, 0f, 360f, false, backgroundPaint);// 绘制进度圆环float sweepAngle = 360f * progress / (float) maxProgress;// 根据起始角度调整起始位置float adjustedStartAngle = 0f;switch (startAngle) {case 90:adjustedStartAngle = 0f;break;case 180:adjustedStartAngle = 90f;break;case 270:adjustedStartAngle = 180f;break;default:adjustedStartAngle = -90f; // 默认从0度(顶部)开始break;}canvas.drawArc(rectF, adjustedStartAngle, sweepAngle, false, progressPaint);// 绘制进度文本if (showProgressText) {String progressText = progress + "%";// 使用视图的中心来计算x和y坐标float x = getWidth() / 2f;float y = getHeight() / 2f - (textPaint.descent() + textPaint.ascent()) / 2f;// 绘制文本canvas.drawText(progressText, x, y, textPaint);}}// 设置进度public void setProgress(int progress) {if (progress > maxProgress) {this.progress = maxProgress;} else {this.progress = Math.max(progress, 0);}invalidate(); // 重新绘制}// 获取当前进度public int getProgress() {return progress;}
}
6. 使用示例
xml:
<com.xaye.example.CircularProgressBarandroid:id="@+id/circularProgressBar"android:layout_width="140dp"android:layout_height="140dp"app:maxProgress="100"app:circleBackgroundColor="#DDDDDD"app:progressColor="#00B8D4"app:circleWidth="15dp"app:showProgressText="true"app:progressTextColor="#000000"app:progressTextSize="30sp"app:startAngle="angle0"/>
Activity:
val animator = ValueAnimator.ofInt(0, 80)animator.setDuration(2000)animator.interpolator = LinearInterpolator()animator.addUpdateListener { animation ->val value = animation.animatedValue as IntmBind.circularProgressBar.setProgress(value)}animator.start()
7. 最后
本篇没什么难度,主要是介绍点新的东西,再熟悉熟悉手感,再会。
另外给喜欢记笔记的同学安利一款好用的云笔记软件,对比大部分国内的这个算还不错的,免费好用:wolai
相关文章:
重学 Android 自定义 View 系列(六):环形进度条
目标 自定义一个环形进度条,可以自定义其最大值、当前进度、背景色、进度色,宽度等信息。 最终效果如下(GIF展示纯色有点问题): 1. 结构分析 背景圆环:表示进度条的背景。进度圆环:表示当前…...
nodejs 020: React语法规则 props和state
props和state 在 React 中,props 和 state 是管理数据流的两种核心机制。理解它们之间的区别和用途是构建 React 应用程序的基础。 一、props 和 state的区别 特性propsstate定义方式由父组件传递给子组件的数据组件内部管理的本地数据是否可修改不可变ÿ…...
STM32问题集
这里写目录标题 一、烧录1、 Can not connect to target!【ST-LINK烧录】 一、烧录 1、 Can not connect to target!【ST-LINK烧录】 烧录突然 If the target is in low power mode, please enable “Debug in Low Power mode” option from Target->settings menu 然后就&…...
SwiftUI(十二)- 容器组件 布局与结构的基石
引言 在用户界面开发中,布局是设计一个应用程序的视觉层次和交互体验的核心之一。无论是设计简单的按钮排布,还是复杂的多层次页面,合理的布局和结构可以极大地提升用户体验。而容器组件,作为将多个视图整合、组织、排列的工具&a…...
想租用显卡训练自己的网络?AutoDL保姆级使用教程(PyCharm版)
各位小伙伴们大家好~ 不知道各位同学在科研过程中是否有这样的苦恼 电脑无显卡。难不成我要用CPU跑实验吗?救救我吧电脑显卡算力太低。训练过程慢慢慢慢慢,等半天都出不来结果电脑显卡显存不够,batchsize稍微高一点点,就要爆显存…...
LeetCode【0039】组合总和
本文目录 1 中文题目2 求解方法:回溯法2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 给定一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#…...
AscendC从入门到精通系列(一)初步感知AscendC
1 什么是AscendC Ascend C是CANN针对算子开发场景推出的编程语言,原生支持C和C标准规范,兼具开发效率和运行性能。基于Ascend C编写的算子程序,通过编译器编译和运行时调度,运行在昇腾AI处理器上。使用Ascend C,开发者…...
PostgreSQL中的COPY命令:高效数据导入与导出
在PostgreSQL数据库中,数据导入和导出是日常工作中常见的操作。传统的插入(INSERT)方法虽然可以实现数据的导入,但在处理大量数据时效率较低。而COPY命令则提供了一个快速、高效的方式来完成这一任务。COPY命令不仅可以用于将数据…...
【HAL库】STM32F105VCTx多通道ADC+DMA方式的【STM32CubeMX】配置及代码实现
相关代码编写 配置好后点击生成代码,在生成代码的adc.c文件中的初始化函数MX_ADC1_Init中添加如下代码: HAL_ADCEx_Calibration_Start(&hadc1); /* 校准ADC */HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC_Value,ADC_DMA_…...
[SaaS] 数禾科技 AIGC生成营销素材
https://zhuanlan.zhihu.com/p/923637935https://zhuanlan.zhihu.com/p/923637935...
vue3中查找字典列表中某个元素的值对应的列表索引值
vue3中查找字典列表中某个元素的值对应的列表索引值 目录思路方法代码实现示例解释说明 目录 思路方法 要获取字典列表中某个元素的值对应的列表索引值,可以使用数组的 findIndex 方法。这个方法返回数组中满足提供的测试函数的第一个元素的索引。如果没有找到&am…...
爱普生机器人EPSON RC
爱普生机器人Epson RC系列,搭配其专用的Epson RC编程语言和软件环境,为用户提供了一个直观且功能强大的机器人控制和编程解决方案。以下是对Epson RC及爱普生机器人的一些详细介绍: Epson RC 定义:Epson RC 是爱普生机器人技术中…...
Linux探秘坊-------1.系统核心的低语:基础指令的奥秘解析(1)
1.Linux的背景介绍 Linux 操作系统的发展历程充满了激情与创新喵~🎀 萌芽期 (1983 - 1991):Linux 的历史可追溯到 1983 年,理查德斯托曼 (Richard Stallman) 发起 GNU 计划,目标是创建一个自由软件操作系统。1987 年发…...
❤React-JSX语法认识和使用
1、JSX基本使用 JSX是React的核心 JSX是ES的扩展 jsx语法 -> 普通的JavaScript代码 -> babel React可以使用JSX的前提和原因: React生态系统支持: 脚手架通常用于构建React应用程序,而JSX是React框架的核心语法之一。因此…...
51单片机应用开发(进阶)---定时器应用(电子时钟)
实现目标 1、巩固定时器的配置流程; 2、掌握按键、数码管与定时器配合使用; 3、功能1:(1)简单显示时间。显示格式:88-88-88(时-分-秒) 4、功能2:(1&#…...
JavaScript中的对象-栈内存和堆内存以及this指向的两种情况(后续会出进阶)
1.1 栈内存和堆内存 我们知道程序是需要加载到内存中来执行的,我们可以将内存划分为两个区域:栈内存和堆内存 原始类型占据的空间是在栈内存中分配的对象类型占据的空间是在堆内存中分配的 1.1.1 值类型和引用类型 原始类型的保存方式:在变量中保存的是…...
shell脚本使用curl上传FTP
背景:要求使用curl通过shell脚本实现上传文件到FTP的功能,同时对远程目录不存在的时候,主动创建目录并上传文件,shell脚本如下: #!/bin/bash# FTP服务器的地址 FTP_SERVER"ftp://1.1.1.1:2121" # FTP用户名…...
【漏洞分析】Fastjson最新版本RCE漏洞
01漏洞编号 CVE-2022-25845CNVD-2022-40233CNNVD-202206-1037二、Fastjson知多少 万恶之源AutoType Fastjson的主要功能是将Java Bean序列化为JSON字符串,这样得到的字符串就可以通过数据库等方式进行持久化了。 但是,Fastjson在序列化及反序列化的过…...
【项目开发 | 跨域认证】JSON Web Token(JWT)
未经许可,不得转载。 文章目录 JWT设计背景:跨域认证JWT 原理JWT 结构JWT 使用方式注意JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,本文介绍它的原理、结构及用法。 JWT设计背景:跨域认证 互联网服务的用户认证流程是现代应用中的核心组成部分,通常的流程…...
杨中科 .Net Core 笔记 DI 依赖注入2
ServiceCollection services new ServiceCollection();//定义一个承放服务的集合 services.AddScoped<iGetRole, GetRole>();using (ServiceProvider serviceProvider services.BuildServiceProvider()) {var list serviceProvider.GetServices(typeof(iGetRole));//获…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
