重学 Android 自定义 View 系列(三):自定义步数进度条

前言

本篇文章主要是实现仿QQ步数View,很老的一个View了,但技术永不落后,开搂!

最终效果如下:

在这里插入图片描述

1. 结构分析


QQStepView 主要由三个元素组成:

  • 显示一个圆环进度条,通过外环和内环的角度变化来表示进度。
  • 支持自定义外环颜色、内环颜色、进度文本颜色和文本大小,这些都可以通过 XML 属性来配置。
  • 支持动态更新进度,通过方法 setStep(int step) 可以更新当前进度。

2. 定义自定义属性


为了使该控件在 XML 布局文件中可配置,我们需要定义一些自定义属性,例如外圈颜色、内圈颜色、边框宽度、文本大小和文本颜色。这些属性可以通过 res/values/attrs.xml 文件来定义:

<declare-styleable name="QQStepView"><attr name="outerColor" format="color" /><attr name="innerColor" format="color" /><attr name="borderWidth" format="dimension" /><attr name="stepTextSize" format="dimension" /><attr name="stepTextColor" format="color" />
</declare-styleable>

3. 初始化视图元素


通过 TypedArray 获取用户在 XML 中设置的属性。

public QQStepView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);mBorderWidth = array.getDimensionPixelSize(R.styleable.QQStepView_borderWidth, mBorderWidth);mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);array.recycle();
}

初始化画笔(Paint)

mOuterPaint = new Paint();
mOuterPaint.setColor(mOuterColor);
mOuterPaint.setAntiAlias(true);
mOuterPaint.setStyle(Paint.Style.STROKE); // 实心
mOuterPaint.setStrokeWidth(mBorderWidth);
mOuterPaint.setStrokeCap(Paint.Cap.ROUND);mInnerPaint = new Paint();
mInnerPaint.setColor(mInnerColor);
mInnerPaint.setAntiAlias(true);
mInnerPaint.setStyle(Paint.Style.STROKE);
mInnerPaint.setStrokeWidth(mBorderWidth);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);mTextPaint = new Paint();
mTextPaint.setColor(mStepTextColor);
mTextPaint.setTextSize(mStepTextSize);
mTextPaint.setAntiAlias(true);

4. 测量视图尺寸(onMeasure)


在这里,我们确保视图是正方形,即宽高相等。如果布局中的宽高为 wrap_content,我们通过 MeasureSpec 获取最大的尺寸,使用最小值来确保宽高相等:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int size = Math.min(width, height);setMeasuredDimension(size, size); // 确保视图为正方形
}

5. 绘制视图内容(onDraw)


我们逐步绘制外圆弧、内圆弧和文本。

本文重点概念就是canvas.drawArc,能理解这个方法的使用就行了。

画了一张简图,配合着源码,接下来逐个分析其参数使用。

在这里插入图片描述

public void drawArc (RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)
  1. RectF oval:定义弧形的外接矩形。
    RectF 是一个矩形类,用来表示一个矩形的边界。弧形是通过外接矩形来确定的,所以这个矩形将决定弧形的大小和位置。oval 定义了一个圆形的外接矩形。就是上图的正方形。

  2. float startAngle:弧形的起始角度,单位是度(°)。
    这个角度表示弧形起始的方向,是从矩形的水平坐标轴开始计算的。即上图中的绿色线就是
    由上图可知,我们的圆弧起始角度为 135 度。

  3. float sweepAngle:弧形的扫过角度,单位是度(°)。
    这个角度表示弧形从起始角度开始扫过的角度,决定了弧形的弯曲度。如果是正数,表示顺时针方向绘制弧形;如果是负数,表示逆时针方向。扫过的角度为 360 - 90 = 270度。

  4. boolean useCenter:是否绘制弧形的中心线。
    如果 useCenter 为 true,那么弧形会从矩形的中心到达弧形的起始和终止角度。如果 useCenter 为 false,则弧形的边缘将仅仅通过 startAngle 和 sweepAngle 绘制出来,而不会连到中心点。就是上图下面那两根连接中点的黑线。在我们代码中 useCenter 设置为 false,意味着我们只绘制一个环形弧,而不是一个扇形。

  5. @NonNull Paint paint:用于定义弧形样式的 Paint 对象
    Paint 用来设置绘制图形的颜色、样式、宽度等属性。

5.1 绘制外圆弧

首先,我们绘制外圆弧,这是显示总步数的背景。外圆弧的起始角度为135°,覆盖270°:

int center = getWidth() / 2; // 获取圆心位置
int radius = center - mBorderWidth / 2; // 半径要减去边框宽度的一半RectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);
canvas.drawArc(oval, 135, 270, false, mOuterPaint); // 绘制外圆弧

5.2 绘制内圆弧

内圆弧表示当前步数的进度。根据 mStepCurrent 和 mStepMax 计算内圆弧的弧度。如果步数为0,则不绘制内圆弧:

if (mStepCurrent <= 0) return;
float sweepAngle = (float) mStepCurrent / mStepMax * 270; // 计算当前步数对应的弧度
canvas.drawArc(oval, 135, sweepAngle, false, mInnerPaint); // 绘制内圆弧

5.3 绘制文本

最后,绘制步数文本。文本需要居中显示,因此我们计算文本的宽度和高度,然后调整其位置,使其垂直居中和水平居中。基线概念上篇文章已经介绍了,在这里直接使用,不理解的可以翻翻上一篇去查看。

String stepText = mStepCurrent + "";
Rect bounds = new Rect();
mTextPaint.getTextBounds(stepText, 0, stepText.length(), bounds);
int dx = getWidth() / 2 - bounds.width() / 2; // 水平偏移量Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom; // 垂直偏移量
int baseLine = center + dy; // y轴偏移量canvas.drawText(stepText, dx, baseLine, mTextPaint); // 绘制文本

6. 更新视图(setStep 和 setStepMax)


为了让步数进度条动态变化,我们提供了两个方法:setStep 用来设置当前步数,setStepMax 用来设置最大步数。在设置值后,调用 invalidate() 方法刷新视图:

public synchronized void setStep(int step) {mStepCurrent = step;invalidate(); // 刷新视图
}public synchronized void setStepMax(int stepMax) {mStepMax = stepMax;invalidate(); // 刷新视图
}

7. 使用


这里使用了属性动画使View更新更丝滑,和插值器使动画更自然。

安卓动画插值器(Interpolator)

 val myView = findViewById<QQStepView>(R.id.step_view)myView.setStepMax(5000)//属性动画val valueAnimator = ValueAnimator.ofInt(0, 3000);//表示从 0 到 3000 之间的整数值变化valueAnimator.setDuration(1000) //设置动画的持续时间为 1000 毫秒(1 秒)。这个动画会在 1 秒内从 0 平滑地增加到 3000。valueAnimator.interpolator = android.view.animation.DecelerateInterpolator()//开始时较快,结束时较慢valueAnimator.addUpdateListener { animation ->val value = animation.animatedValue as IntmyView.setStep(value)}findViewById<Button>(R.id.btn_start).clickNoRepeat {valueAnimator.start()}

8. 完整代码


public class QQStepView extends View {private int mOuterColor = Color.RED;private int mInnerColor = Color.BLUE;private int mBorderWidth = 20; //pxprivate int mStepTextSize;private int mStepTextColor;private Paint mOuterPaint;private Paint mInnerPaint;private Paint mTextPaint;private int mStepMax = 100;private int mStepCurrent = 0;public QQStepView(Context context) {this(context, null);}public QQStepView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 1.分析效果// 2.确定自定义属性 attr.xml// 3.在布局文件中使用// 4.在自定义View 中获取自定义属性TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);mBorderWidth = array.getDimensionPixelSize(R.styleable.QQStepView_borderWidth, mBorderWidth);mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize, mStepTextSize);mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor, mStepTextColor);array.recycle();mOuterPaint = new Paint();mOuterPaint.setColor(mOuterColor);mOuterPaint.setAntiAlias(true);mOuterPaint.setStyle(Paint.Style.STROKE); //实心mOuterPaint.setStrokeWidth(mBorderWidth);mOuterPaint.setStrokeCap(Paint.Cap.ROUND);mInnerPaint = new Paint();mInnerPaint.setColor(mInnerColor);mInnerPaint.setAntiAlias(true);mInnerPaint.setStyle(Paint.Style.STROKE);mInnerPaint.setStrokeWidth(mBorderWidth);mInnerPaint.setStrokeCap(Paint.Cap.ROUND);mTextPaint = new Paint();mTextPaint.setColor(mStepTextColor);mTextPaint.setTextSize(mStepTextSize);mTextPaint.setAntiAlias(true);// onMeasure// 6.画外圆弧 画内圆弧 画文字// 7.其他}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);// 布局可能是宽高 wrap_content//读取模式 AT_MOST 40dp// 宽高不一致 取最小值,确保是正方形int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int size = Math.min(width, height);setMeasuredDimension(size, size);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 1.画外圆弧int center = getWidth() / 2;int radius = center - mBorderWidth / 2;//RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paintRectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);canvas.drawArc(oval, 135, 270, false, mOuterPaint);// 2.画内圆弧if (mStepCurrent <= 0) return;float sweepAngle = (float) mStepCurrent / mStepMax * 270;canvas.drawArc(oval, 135, sweepAngle, false, mInnerPaint);// 3.画文字String stepText = mStepCurrent + "";//控件的一半 减去 文字的一半Rect bounds = new Rect();mTextPaint.getTextBounds(stepText, 0, stepText.length(), bounds);int dx = getWidth() / 2 - bounds.width() / 2; // x轴偏移量//基线Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;int baseLine = center + dy; // y轴偏移量canvas.drawText(stepText, dx, baseLine, mTextPaint);}// 动起来public synchronized void setStep(int step) {mStepCurrent = step;invalidate();}public synchronized void setStepMax(int stepMax) {mStepMax = stepMax;invalidate();}
}

另外给喜欢记笔记的同学安利一款好用的云笔记软件,对比大部分国内的这个算还不错的,免费好用:wolai

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/58925.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring中的过滤器和拦截器

Spring中的过滤器和拦截器 一、引言 在Spring框架中&#xff0c;过滤器&#xff08;Filter&#xff09;和拦截器&#xff08;Interceptor&#xff09;是实现请求处理的两种重要机制。它们都基于AOP&#xff08;面向切面编程&#xff09;思想&#xff0c;用于在请求的生命周期…

查缺补漏----用户上网过程(HTTP,DNS与ARP)

&#xff08;1&#xff09;HTTP 来自湖科大计算机网络微课堂&#xff1a; ① HTTP/1.0采用非持续连接方式。在该方式下&#xff0c;每次浏览器要请求一个文件都要与服务器建立TCP连接当收到响应后就立即关闭连接。 每请求一个文档就要有两倍的RTT的开销。若一个网页上有很多引…

C++之vector类的模拟实现

片头 嗨~小伙伴们&#xff0c;今天我们来一起学习关于C的vector类的模拟实现&#xff0c;准备好了吗&#xff1f;咱们开始咯~ 一、基本框架 namespace bit {template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;// 针对const修…

流体力学ansys Fluent二次开发scheme_eval模块剖析

在ANSYS Fluent的二次开发中&#xff0c;scheme_eval 是 Scheme 编程语言中一个非常重要的模块&#xff0c;它允许用户执行动态的 Scheme 表达式和函数&#xff0c;从而扩展 Fluent 的功能。scheme_eval 模块通常与 Fluent 的计算和自定义脚本操作紧密结合。下面我们会对这个模…

前端入门一之DOM、获取元素、DOM核心、事件高级、操作元素、事件基础、节点操作

前言 JS是前端三件套之一&#xff0c;也是核心&#xff0c;本人将会更新JS基础、JS对象、DOM、BOM、ES6等知识点&#xff0c;这篇是DOM;这篇文章是本人大一学习前端的笔记&#xff1b;欢迎点赞 收藏 关注&#xff0c;本人将会持续更新。 文章目录 DOMDOM简介1.1、什么是DOM1…

ubuntu 22.04 server 安装 和 初始化 LTS

ubuntu 22.04 server 安装 和 初始化 下载地址 https://releases.ubuntu.com/jammy/ 使用的镜像是 ubuntu-22.04.5-live-server-amd64.iso usb 启动盘制作工具 https://rufus.ie/zh/ rufus-4.6p.exe 需要主板 支持 UEFI 启动 Ubuntu22.04.4-server安装 流程 https://b…

【elkb】kibana后台删除索引

打开kibana后台 点击 Management ---> Index Management 找到要删除的所以点击 点击delete index 删除成功

【计网】实现reactor反应堆模型 --- 多线程方案优化 ,OTOL方案

没有一颗星&#xff0c; 会因为追求梦想而受伤&#xff0c; 当你真心渴望某样东西时&#xff0c; 整个宇宙都会来帮忙。 --- 保罗・戈埃罗 《牧羊少年奇幻之旅》--- 实现Reactor反应堆模型 1 重新认识Reactor2 普通线程池3 OTOL方案3.1 多进程版3.2 多线程版 1 重新认识Re…

langgraph_plan_and_execute

整体入门demo 教程概览 欢迎来到LangGraph教程&#xff01; 这些笔记本通过构建各种语言代理和应用程序&#xff0c;介绍了如何使用LangGraph。 快速入门&#xff08;Quick Start&#xff09; 快速入门部分通过一个全面的入门教程&#xff0c;帮助您从零开始构建一个代理&a…

UnixBench和Geekbench进行服务器跑分

1 概述 服务器的基准测试&#xff0c;常见的测试工具有UnixBench、Geekbench、sysbench等。本文主要介绍UnixBench和Geekbench。 1.1 UnixBench UnixBench是一款开源的测试UNIX系统基本性能的工具&#xff08;https://github.com/kdlucas/byte-unixbench&#xff09;&#x…

布谷直播源码部署服务器关于数据库配置的详细说明

布谷直播源码搭建部署配置接口数据库 /public/db.php&#xff08;2019年8月后的系统在该路径下配置数据库&#xff0c;老版本继续走下面的操作&#xff09; 在项目代码中执行命令安装依赖库&#xff08;⚠️注意&#xff1a;如果已经有了vendor内的依赖文件的就不用执行了&am…

Gen-RecSys——一个通过生成和大规模语言模型发展起来的推荐系统

概述 生成模型的进步对推荐系统的发展产生了重大影响。传统的推荐系统是 “狭隘的专家”&#xff0c;只能捕捉特定领域内的用户偏好和项目特征&#xff0c;而现在生成模型增强了这些系统的功能&#xff0c;据报道&#xff0c;其性能优于传统方法。这些模型为推荐的概念和实施带…

太速科技-440-基于XCVU440的多核处理器多输入芯片验证板卡

基于XCVU440的多核处理器多输入芯片验证板卡 一、板卡概述 本板卡系我司自主研发的基于6U CPCI处理板&#xff0c;适用于多核处理器多输入芯片验证的应用。芯片采用工业级设计。 基于XCVU440T的多核处理器多输入芯片验证板卡基于6U CPCI架构&#xff0c;是单机中的一个…

SpringBoot框架在共享汽车管理中的应用

3系统分析 3.1可行性分析 通过对本共享汽车管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本共享汽车管理系统采用SSM框架&#xff0c;JAVA作为开发语…

【数据分享】1901-2023年我国省市县镇四级的逐年降水数据(免费获取/Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月降水栅格数据和Shp和Excel格式的省市县四级逐月降水数据&#xff0c;原始的逐月降水栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据&#xff01;基于逐月数据我们采用求年累计值的方法得到逐年降水栅格数据&#…

Javaweb-book书籍借阅系统-开源计划-起源-003

效果视频&#xff1a; https://www.bilibili.com/video/BV1w5m6YkEW3/?spm_id_from333.999.0.0项目地址&#xff1a; https://gitee.com/lucky-six/Javaweb-book

基于springboot+vu的二手车交易系统(全套)

一、系统架构 前端&#xff1a;vue | element-ui | html 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven | nodejs 二、代码及数据库 三、功能介绍 01. web端-首页1 02. web端-首页2 03. web端-注册 04. web端-登录 05. w…

系统架构师2023版:习题

架构设计基础 计算机基础 目前处理器市场中存在 CPU 和 DSP 两种类型的处理器&#xff0c;分别用于不同的场景&#xff0c;这两种处理器具有不同的体系结构&#xff0c;DSP采用()。 A.冯诺依曼结构 B.哈佛结构 C.FPGA 结构 D.与 GPU 相同的结构 解析:…

C++ | Leetcode C++题解之第552题学生出勤记录II

题目&#xff1a; 题解&#xff1a; class Solution { public:static constexpr int MOD 1000000007;vector<vector<long>> pow(vector<vector<long>> mat, int n) {vector<vector<long>> ret {{1, 0, 0, 0, 0, 0}};while (n > 0) {…

智能化SCRM方案助力企业高效管理与营销转型

内容概要 现代企业面临着复杂多变的市场环境&#xff0c;传统的管理与营销方式常常无法满足日益增长的需求。这时&#xff0c;智能化SCRM方案便应运而生&#xff0c;为企业带来了新的机遇与挑战。智能化SCRM方案不仅仅是一个单一的工具&#xff0c;它更像是一个全面的解决方案…