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

本文详细描述了如何从头开始,通过继承`View`并重写`onDraw`方法,在Android中实现一个支持`warp_content`和`padding`的环形进度条。作者介绍了关键步骤,如绘制环形、确定尺寸以及处理进度更新等。
摘要由CSDN通过智能技术生成

[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)
        }

    }
}

最终效果

在这里插入图片描述

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值