[Android自定义View]实现一个环形进度条控件

news2025/1/12 20:47:07

[Android View]实现一个环形进度条

在这里插入图片描述

导言

之前的文章里我们已经介绍了自定义View相关的知识了,本篇文章我们就来实战一下,从零到一,实现一个环形进度条的控件。

具体实现

大体框架

我们说过,如果要实现一个自定义控件的话一般有两种继承方式:

  1. 继承View:重写onDraw,还需要支持warp_content等属性。
  2. 继承系统已有控件:重写onDraw 或者绘制中的其他方法,一般用于拓展已有控件的功能。

如果要实现环形进度条的话,目前应该是没有系统已有的控件可以拓展,都需要较大程度的改动,所以我们直接继承View来实现即可。其次我们再来梳理一下要实现环形进度条的几个关键点:

  1. 绘制环形(根据进度,显示的颜色)
  2. 支持warp_content属性和padding属性
    实际上也并不难,主要就是根据进度的不同绘制环形这一步,完成这一步,环形进度条也大致完毕。

确定尺寸

这里我们就先不考虑warp_content,只考虑padding这个特殊情况:

//考虑padding之后的尺寸边界
val mSizeWithPadding
    get() = RectF(0f+paddingLeft,0f+paddingTop,
        width.toFloat()-paddingRight-paddingLeft,height.toFloat()-paddingBottom-paddingTop)
//绘制内容的宽度
private val contentWidth
    get() = width-paddingLeft-paddingRight
//绘制内容的高度
private val contentHeight
    get() = height-paddingTop-paddingBottom

绘制环形&文字

我们先来介绍最重要的一点:如何绘制环形。这一片绘制view的内容我强烈建议大家可以去学习朱凯老师(扔物线)的课程,基本上涵盖了我们常用的绘制内容。

这里来简单介绍一下使用到的,Canavs绘制相关的API:
在这里插入图片描述
要实现环形进度条我们用这两个绘制方法就可以了。

具体绘制

既然是进度条那么就应该有当前进度值和最大进度值,这两个进度只要是为了确定在绘制的时候我们需要绘制弧度为多少的圆弧,我们将最大进度值设置为100,用以下代码表示:

class CircleLineWithText @JvmOverloads constructor(
    mContext: Context, attributeSet: AttributeSet? = null, defStyle:Int = 0
):View(mContext,attributeSet,defStyle) { 
    ....
    //更新进度
    fun updateProgress(progressIn100:Int) {
        val tragetRad = progressIn100 * 360 / 100
        currentRad = tragetRad.toFloat()
        invalidate()
    }
}

这里我定义了一个updateProgress方法来更新当前进度,我们都知道一圈圆为360度,所以说当前的目标弧度值为
(当前进度值 / 100) * 360 ,但是在整形中我们显然不能这么做,所以我们先乘以360再除以100;并在最后调用invalidate方法来触发View的重新绘制。
现在有了弧度值我们再来看具体的绘制方法(过程):

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    //着色器 -- 辐射渐变
    val shader = SweepGradient((paddingLeft+contentWidth/2-paddingRight).toFloat(),
        (paddingTop+contentHeight/2-paddingBottom).toFloat(),
            mLineColor,
            Color.RED
        )
    
    mLinePaint.color = mLineColor
    mLinePaint.shader = shader
    //测试用的数据
    Log.d(TAG, "onDraw: x:${contentWidth},y:${contentHeight},r:${min(contentWidth,contentHeight).toFloat()/2}")
    //根据目标弧度值绘制弧形
    canvas.drawArc(paddingLeft.toFloat()+mLinePaint.strokeWidth,
        paddingTop.toFloat()+mLinePaint.strokeWidth,
        paddingLeft+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingRight,
        paddingTop+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingBottom,
        0f,currentRad,false,mLinePaint
        )
    //绘制文字
    mTextPaint.color = mTextColor
    mTextPaint.textSize = mTextSize
    mTextPaint.style = Paint.Style.FILL
    mTextPaint.isUnderlineText = true
    //需要显示在环形进度条中间的字符串
    contentString = (currentRad/360*100).toInt().toString() + "%"
    val lengthOfString = contentString.length * 25
    canvas.drawText(contentString,
        min(contentWidth,contentHeight).toFloat()/2-lengthOfString/2,
        min(contentWidth,contentHeight).toFloat()/2+lengthOfString/10,
        mTextPaint)
    mTextPaint.style = Paint.Style.STROKE
    //测试用
    if (Debug) {
        canvas.drawRect(mSizeWithPadding,mTextPaint)
        canvas.drawRect(paddingLeft.toFloat()+mLinePaint.strokeWidth,
            paddingTop.toFloat()+mLinePaint.strokeWidth,
            paddingLeft+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingRight,
            paddingTop+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingBottom,
            mTextPaint)
    }

}

这段就是绘制环形进度条的核心代码,实际上逻辑非常简单,主要就是我们需要对Canavs相关的API有所了解,其中关于绘制的线条的颜色,我使用到了shade着色器的辐射渐变模式,这样绘制的线条颜色就会随着绘制的位置改变。

完整代码:

class CircleLineWithText @JvmOverloads constructor(
    mContext: Context, attributeSet: AttributeSet? = null, defStyle:Int = 0
):View(mContext,attributeSet,defStyle) {



    companion object {
        private const val TAG = "CircleLineWithText"
        private const val Debug = true
    }
    var contentString = "Enjoy Your Life Cmf"
    private val Color_TransParent
        get() = resources.getColor(android.R.color.transparent)

    var mTextColor = Color.BLACK
    var mLineColor = Color.BLACK
    var mTextSize = 18f
    var currentRad = 0f


    init {
        //自定义属性
        val typeArray = mContext.obtainStyledAttributes(attributeSet, R.styleable.CircleLineWithText)
        mTextColor = typeArray.getColor(R.styleable.CircleLineWithText_text_Color,Color.BLACK)
        mLineColor = typeArray.getColor(R.styleable.CircleLineWithText_line_Color,Color.BLACK)
        mTextSize = typeArray.getFloat(R.styleable.CircleLineWithText_text_Size,18f)
        currentRad = typeArray.getFloat(R.styleable.CircleLineWithText_current_Radius,0f)
    }

    private val mLinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        strokeCap = Paint.Cap.ROUND
        style = Paint.Style.STROKE
        strokeWidth = 30f
        color = Color_TransParent
    }

    private val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color_TransParent
        style = Paint.Style.STROKE
        strokeWidth = 0f
    }

    val mSizeWithPadding
        get() = RectF(0f+paddingLeft,0f+paddingTop,
            width.toFloat()-paddingRight-paddingLeft,height.toFloat()-paddingBottom-paddingTop)

    private val contentWidth
        get() = width-paddingLeft-paddingRight

    private val contentHeight
        get() = height-paddingTop-paddingBottom

    fun updateProgress(progressIn100:Int) {
        val tragetRad = progressIn100 * 360 / 100
        currentRad = tragetRad.toFloat()
        invalidate()
    }

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val shader = SweepGradient((paddingLeft+contentWidth/2-paddingRight).toFloat(),
            (paddingTop+contentHeight/2-paddingBottom).toFloat(),
                mLineColor,
                Color.RED
            )

        mLinePaint.color = mLineColor
        mLinePaint.shader = shader
        Log.d(TAG, "onDraw: x:${contentWidth},y:${contentHeight},r:${min(contentWidth,contentHeight).toFloat()/2}")
        canvas.drawArc(paddingLeft.toFloat()+mLinePaint.strokeWidth,
            paddingTop.toFloat()+mLinePaint.strokeWidth,
            paddingLeft+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingRight,
            paddingTop+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingBottom,
            0f,currentRad,false,mLinePaint
            )


        mTextPaint.color = mTextColor
        mTextPaint.textSize = mTextSize
        mTextPaint.style = Paint.Style.FILL
        mTextPaint.isUnderlineText = true
        contentString = (currentRad/360*100).toInt().toString() + "%"
        val lengthOfString = contentString.length * 25
        canvas.drawText(contentString,
            min(contentWidth,contentHeight).toFloat()/2-lengthOfString/2,
            min(contentWidth,contentHeight).toFloat()/2+lengthOfString/10,
            mTextPaint)

        mTextPaint.style = Paint.Style.STROKE

        if (Debug) {
            canvas.drawRect(mSizeWithPadding,mTextPaint)
            canvas.drawRect(paddingLeft.toFloat()+mLinePaint.strokeWidth,
                paddingTop.toFloat()+mLinePaint.strokeWidth,
                paddingLeft+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingRight,
                paddingTop+min(contentHeight,contentWidth).toFloat()-mLinePaint.strokeWidth-paddingBottom,
                mTextPaint)
        }

    }
}

最终效果

在这里插入图片描述

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

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

相关文章

XCTF-Misc1 pcap1 misc2-1

pcap1 附件是一个xxxxx.pcap流量包 分析 1. 流量包筛选 统计协议分级,发现主要是TCP协议 查看TCP协议的流量包,筛选关键词tcp contains flag 跟踪TCP流,发现是python代码,将其导出 2. 导出的代码分析 导出的代码&#xff…

AlexNet论文翻译与精读

1:该论文解决了什么问题? 图像分类问题 2:该论文的创新点? 1:使用了大的深的卷积神经网络进行图像分类; 2:采用了两块GPU进行分布式训练; 3:采用了Relu进行训练加速; 4:采用局部归一化提高模型泛化能…

夺命追问带你深入了解ArrayList与LinkedList

目录 一、ArrayList 问题1:说一下JDK1.7与1.8 ArrayList有什么区别? 问2:说一下ArrayList的扩容机制? 问3:下面这段代码会将数组扩容到多少? 问4:说说迭代器Iterator的两种规则&#xff1a…

如何理解线程池中的参数设计

如何理解线程池中的参数设计 你的线程池的参数怎么配置?线程数量设置多少合理?如何确定一个线程池中的人物已经完成了为什么不建议使用java自带的Executors创建线程池线程池里面的阻塞队列设置多少合理? 考察:了解你对技术的掌握…

如何查看SSL证书到期时间

随着互联网的发展,SSL证书在保护网站安全方面发挥着越来越重要的作用,SSL证书过期之后带给网站的损失也比较大,许多网站管理员可能会忽略SSL证书到期的问题,从而导致网站安全漏洞。今天,就随SSL盾小编了解有什么办法查…

开源云真机平台-Sonic平台-python自定义脚本(持续更新中)

开源云真机平台-Sonic平台-python自定义脚本(持续更新中) 1、获取设备序列号 udId sys.argv[1:][1] 如: # -*- coding: utf-8 -*- import os,sys,json udId sys.argv[1:][1] 2、获取全局参数 text1 sys.argv[1:][2] 如: # -*- coding: utf…

邻接矩阵、可达性矩阵、完全关联矩阵、可达性矩阵的计算

邻接矩阵:很简单,就是两个点有关系就是1,没有关系就是0 可达性矩阵:非常简单,两点之间有路为1,没有路为0 可发行矩阵的计算:有n个元素,初始可达性矩阵为A,那么最终的矩阵…

【软考中级-软件设计师】day7:图

概述 1-2道选择 图的存储 图的遍历 图的最小生成树 prim算法 kruscal算法 2135476 选7不选6是因为4的先删除(vi必须在vj之前)跟4有关的删了以后,入度为0的结点只剩下7,所以选7再6

烟火识别系统、ai视频烟火自动报警系统、烟火监测系统、烟火报警

在现代社会,烟火(包括烟雾和火焰)的及时识别对于防灾减灾具有至关重要的作用。随着人工智能技术的发展,基于AI的视频烟火识别系统已成为研究和应用的热点。这些系统通过分析监控视频,能够迅速准确地识别出烟雾和火焰&a…

内存分区模型---C++

目录 内存分区模型1.1 程序运行前1.2 程序运行后1.2.1 new操作符 内存分区模型 C程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统进行管理的;全局区:存放全局变量和静态变量以及常…

SpringBoot学习(五)-Spring Security配置与应用

注:此为笔者学习狂神说SpringBoot的笔记,其中包含个人的笔记和理解,仅做学习笔记之用,更多详细资讯请出门左拐B站:狂神说!!! Spring Security Spring Security是一个基于Java的开源框架,用于在Java应用程…

网络传输(TCP)

前言 我们tcpdump抓包时会看到除报文数据外,前面还有一段其他的数据,这段数据分为两部分,ip包头(一般20字节)和tcp包头(一般20字节),一般这两个头长度和为40,我们直接跳…

HarmonyOS 开发基础(九)forEach

HarmonyOS 开发基础(九)forEach 一、基础使用 Entry Component struct Index {// 创建一个变量,用来存储图片网络网址imageUrl: string https://gw.alicdn.com/imgextra/i2/2201227850912/O1CN01B7gVvP1Ibk6HMiDRz_!!2201227850912.jpg_Q75.…

父类,父类的分类、子类 同时重写方法,调用问题

做一个猜想,当父类、父类的分类、子类 都写了同一个方法,那么在调用的时候会优先调用哪个方法呢?我们可以先写一个简单的demo implementation Person (void)test {NSLog("%",self.class); }end然后是Person的分类 #import "…

git 使用 submodule 如何指定分支

写在前面, 作为一个前端我是不喜欢使用 submodule的, 我更喜欢 npm 包的管理方式。 首次添加子模块 git submodule add -b <branch> <remote> <path> 不指定分支就不传 -b <branch> <branch> 分支名<remote> 仓库地址<path> 子模块…

零样本学习研究方向sci四区期刊总结

APPLIED OPTICS sci 四区 非OA 出版商:OPTICA 期刊官方网站: http://www.opticsinfobase.org/ao/home.cfm 期刊投稿网址: http://www.opticsinfobase.org/ao/journal/ao/author.cfm#submit 虽然有zsl的名字但是这是全息图像专刊&#xff0c;跟我的方向应该不是太相关。 MO…

C# 微信小程序获取群id

前提 有个需求&#xff0c;需要限制小程序的抽奖只能在某个群内&#xff0c;需要知道谁在群里面&#xff0c;但是微信并没有提供谁在群里面的方法&#xff0c;不过提供了获取群id的方法&#xff0c;这样加上限制分享就能保证群里的参加&#xff0c;即时分享出去了&#xff0c;…

网络层详解

目录 前言 一、IP协议 1、IP协议报头 2、协议字段理解 &#xff08;1&#xff09;4位版本 &#xff08;2&#xff09;4位首部长度 &#xff08;3&#xff09;8位服务类型 &#xff08;4&#xff09;16位总长度 &#xff08;5&#xff09;标识、标志与片偏移 &#xf…

【自控实验】2. 采样控制系统特性

本科课程实验报告&#xff0c;有太多公式和图片了&#xff0c;干脆直接转成图片了 仅分享和记录&#xff0c;不保证全对 通过对二阶连续系统、二阶采样系统和具有保持器的二阶采样系统仿真实验&#xff0c;比较三种系统的特性&#xff0c;加深对采样控制系统的了解 使用matl…

阿里云OSS上传视频,可分片上传

uniappH5实现 阿里云OSS上传视频 示例图&#xff1a; 上传视频完整示例代码&#xff1a; 使用npm安装SDK开发包&#xff0c;安装命令为 npm install ali-oss --save accessKeyId 和 accessKeySecret 还有 bucket 替换成你的就行。 multipartUpload 的第一个入参是&#x…