安卓实现类似汽车速度表的进度条

效果图

左边的黑色边框有一部分看不到,这是三星自带的录制屏幕软件的问题,请不要在意

这个控件主要分为5个部分:

1,外边框.2,进度条.3,隔离进度条和中间圆盘的部分.4,文本.5,中间圆盘

进度条

    进度条:如果没有将canvas先旋转90°的话会发现画出来的渐变效果是这样的

    

    就会发现右下角这都是什么玩意,代码:

val paint = Paint()
paint.shader = SweepGradient(width/ 2f,height / 2f,0xffffffff.toInt(),0xff000000.toInt())
paint.strokeWidth = 40f
paint.style = Paint.Style.STROKE
val rectF =RectF(20f,20f,width - 20f,height - 20f)
canvas.drawArc(rectF,120f,300f,false,paint)

    旋转之后在上面画字的时候就会发现,字都颠倒了

    

    所以这个时候需要使用图层.在画外边框的时候创建一个图层,画完再释放图层,这样就不会影响到别的要画的内容

    

//创建一个图层
val id = canvas.saveLayer(0f,0f,width.toFloat(),height.toFloat(),null,Canvas.ALL_SAVE_FLAG)
canvas.rotate(90f,width /2f,height / 2f)
val paint = Paint()
paint.shader = SweepGradient(width/ 2f,height / 2f,0xffffffff.toInt(),0xff000000.toInt())
paint.strokeWidth = 40f
paint.style = Paint.Style.STROKE
val rectF =RectF(20f,20f,width - 20f,height - 20f)
canvas.drawArc(rectF,30f,300f,false,paint)
//释放图层
canvas.restoreToCount(id)

val textPaint = TextPaint()
textPaint.textSize = 40f
textPaint.color = 0xff000000.toInt()
canvas.drawText("啊咧",width / 2f,height / 2f,textPaint)

    还有一个问题,如果不对渐变的开始角度和结束角度处理的话,渐变的开始角度会从0开始,这样的显示效果就不是很好

    所以这个时候要是SweepGradient的另一个构造方法SweepGradient(float cx,float cy,int colors[],float positions[])

    colors参数用于设置渐变的颜色,positions用于设置渐变的位置,取值:0-1.最终设置paint的shader的代码为:

val paint = Paint()
val intArray = IntArray(2)
intArray[0] = 0xffffffff.toInt()
intArray[1] = 0xff000000.toInt()
val floatArray = FloatArray(2)
floatArray[0] = 30f / 360f
floatArray[1] = 330f / 360f
paint.shader = SweepGradient(width/ 2f,height / 2f,intArray,floatArray)

隔离进度条和中间圆盘的部分

 

    先定义每一块旋转的角度,再根据外边框旋转角度计算总共有多少块,再计算块于块之间的角度 

 

//获取最大角度/每个角度的个数,并除以2,用于显示的个数
dividerCount = (maxRotateAngle / dividerAngle).toInt() / 2
//总间隔=最大角度-每个角度*显示个数
val allMarginLeft = maxRotateAngle - dividerAngle * dividerCount
//每个的间隔=总间隔/(显示的个数-1)
angleMarginLeft = allMarginLeft / (dividerCount - 1)

    最后再画出来

for (i in 0 until dividerCount) {
    canvas.drawArc(dividerRectF, startAngle + (dividerAngle + angleMarginLeft) * i, dividerAngle, false, dividerPaint)
}

如果想取消渐变效果,只需将Paint的shader为null

dividerPaint.shader = null

计算"%"的x坐标

    如果没有动态计算"%"的x坐标,会显得很难看

    

    要计算"%"的坐标,需要先计算百分比文本的每个文本的宽度

percentPaint = TextPaint()
...
percentWidth = percentPaint.measureText("9")

    然后每次在画"%"之前先计算x坐标

percentTextLeftLine = width / 2f + percentWidth * 0.5f * showProgress.toString().length

整个view的主要代码

/**
* 画百分比
*/
private fun drawProgress(canvas: Canvas) {
    //必须重新使用一个图层,否则当要画字的时候会变得非常麻烦
    val id = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
    //要先旋转90°,否则渐变的开始角度在3点钟方向,这样渐变的效果看起来就有问题
    canvas.rotate(90f, centerX, centerY)

    //计算当前进度占总进度的百分比
    val showProgress = (currentProgress.toFloat() / maxProgress.toFloat() * 100).toInt()

    //不是很清楚为什么同样的角度,画外边圆弧总会比进度条多一点,所以剪掉吧
    canvas.drawArc(outStrokeRectF, startAngle + 1, maxRotateAngle - 2, false, outStrokePaint)
    canvas.drawArc(outRectF, startAngle, maxRotateAngle * showProgress / 100f, false, outPaint)
    for (i in 0 until dividerCount) {
        canvas.drawArc(dividerRectF, startAngle + (dividerAngle + angleMarginLeft) * i, dividerAngle, false, dividerPaint)
    }

    //释放图层
    canvas.restoreToCount(id)

    //画百分比的文本
    canvas.drawText(showProgress.toString(), centerX, percentBaseLine, percentPaint)
    //计算%的开始坐标
    percentTextLeftLine = width / 2f + percentWidth * 0.5f * showProgress.toString().length
}

/**
* 画%
*/
private fun drawPercentText(canvas: Canvas) {
    canvas.drawText("%", percentTextLeftLine, percentTextBaseLine, percentTextPaint)
}

下载地址

全部代码

CircleProgressView.kt

功能代码并不多,多的是自己整整定义了22个属性,我也不知道怎么就写出了22个尴尬,所以多了一大堆对属性处理的代码

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.SweepGradient
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.View
import com.tongcheng.searchspinnertest.R
import com.tongcheng.searchspinnertest.util.calcTextSize
import com.tongcheng.searchspinnertest.util.isNotEmpty

class CircleProgressView : View {
    private var innerRadius = 0f
    private var outRoundStrokeWidth = 0f
    private val outRectF = RectF(0f, 0f, 0f, 0f)
    private val outStrokeRectF = RectF(0f, 0f, 0f, 0f)
    private val dividerRectF = RectF(0f, 0f, 0f, 0f)

    private val DEFAULT_START_ANGLE = 30f
    private var startAngle = DEFAULT_START_ANGLE
    private var maxRotateAngle = 360 - startAngle * 2
    private val DEFAULT_OUT_START_COLOR = 0xffffffff.toInt()
    private var outStartColor = DEFAULT_OUT_START_COLOR
    private val DEFAULT_OUT_END_COLOR = 0xff000000.toInt()
    private var outEndColor = DEFAULT_OUT_END_COLOR
    private val DEFAULT_DIVIDER_START_COLOR = 0xffffffff.toInt()
    private var dividerStartColor = DEFAULT_DIVIDER_START_COLOR
    private val DEFAULT_DIVIDER_END_COLOR = 0xff000000.toInt()
    private var dividerEndColor = DEFAULT_DIVIDER_END_COLOR
    private val DEFAULT_CURRENT_PROGRESS = 0
    private var currentProgress = DEFAULT_CURRENT_PROGRESS
    private val DEFAULT_MAX_PROGRESS = 100
    private var maxProgress = DEFAULT_MAX_PROGRESS
    private val DEFAULT_OUT_STROKE_WIDTH = 4F
    private var outStrokeWidth = DEFAULT_OUT_STROKE_WIDTH
    private val DEFAULT_DIVIDER_ANGLE = 2f
    private var dividerAngle = DEFAULT_DIVIDER_ANGLE
    private var tipText = ""

    private val innerPaint: Paint
    private val outPaint: Paint
    private val outStrokePaint: Paint
    private val dividerPaint: Paint
    private val percentPaint: TextPaint
    private val percentTextPaint: TextPaint
    private val tipPaint: TextPaint
    private val backgroundPaint: Paint

    private var centerX = 0f
    private var centerY = 0f
    private var dividerStrokeWidth = 0f

    private var percentBaseLine = 0f
    private var percentTextBaseLine = 0f
    private var tipBaseLine = 0f
    //这条线需要动态计算,否则会看起来很难看
    private var percentTextLeftLine = 0f
    private var percentWidth = 0f

    private var dividerCount = 0
    private var angleMarginLeft = 0f

    private val DEFAULT_DIVIDER_IS_GRADUAL = true
    private var isDividerGradual = DEFAULT_DIVIDER_IS_GRADUAL

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        val DEFAULT_OUT_STROKE_COLOR = 0xff000000.toInt()
        var outStrokeColor = DEFAULT_OUT_STROKE_COLOR
        val DEFAULT_DIVIDER_COLOR = 0xff000000.toInt()
        var dividerColor = DEFAULT_DIVIDER_COLOR
        val DEFAULT_CENTER_COLOR = 0xffffffff.toInt()
        var centerColor = DEFAULT_CENTER_COLOR
        val DEFAULT_PERCENT_TEXT_SIZE = -1f
        val DEFAULT_PERCENT_ACTUAL_TEXT_SIZE = 20f
        var percentTextSize = DEFAULT_PERCENT_TEXT_SIZE
        val DEFAULT_PERCENT_TEXT_COLOR = 0xff2282F0.toInt()
        var percentTextColor = DEFAULT_PERCENT_TEXT_COLOR
        var percentTextTextColor = DEFAULT_PERCENT_TEXT_COLOR
        val DEFAULT_TIP_TEXT_SIZE = -1f
        var tipTextSize = DEFAULT_TIP_TEXT_SIZE
        val DEFAULT_TIP_TEXT_COLOR = 0xff68AC68.toInt()
        var tipTextColor = DEFAULT_TIP_TEXT_COLOR
        val DEFAULT_BACKROUND_COLOR = 0xffffffff.toInt()
        var backgroundColor = DEFAULT_BACKROUND_COLOR
        if (attrs != null) {
            val array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView)
            //渐变开始的颜色
            outStartColor = array.getColor(R.styleable.CircleProgressView_circleP_outStartColor, DEFAULT_OUT_START_COLOR)
            //渐变结束的颜色
            outEndColor = array.getColor(R.styleable.CircleProgressView_circleP_outEndColor, DEFAULT_OUT_END_COLOR)
            //开始的角度
            startAngle = array.getInt(R.styleable.CircleProgressView_circleP_startAngle, DEFAULT_START_ANGLE.toInt()).toFloat()
            //当前的进度
            currentProgress = array.getInt(R.styleable.CircleProgressView_circleP_currentProgress, DEFAULT_CURRENT_PROGRESS)
            //最大的进度
            maxProgress = array.getInt(R.styleable.CircleProgressView_circleP_maxProgress, DEFAULT_MAX_PROGRESS)
            //外边圆弧框的颜色
            outStrokeColor = array.getColor(R.styleable.CircleProgressView_circleP_outStrokeColor, DEFAULT_OUT_STROKE_COLOR)
            //外边圆弧框的大小
            outStrokeWidth = array.getDimension(R.styleable.CircleProgressView_circleP_outStrokeWidth, DEFAULT_OUT_STROKE_WIDTH)
            //分割线的角度
            dividerAngle = array.getInt(R.styleable.CircleProgressView_circleP_dividerAngle, DEFAULT_DIVIDER_ANGLE.toInt()).toFloat()
            //分割线的颜色
            dividerColor = array.getColor(R.styleable.CircleProgressView_circleP_dividerColor, DEFAULT_DIVIDER_COLOR)
            //中间圆圈的颜色
            centerColor = array.getColor(R.styleable.CircleProgressView_circleP_centerColor, DEFAULT_CENTER_COLOR)
            //百分比的字体大小
            percentTextSize = array.getDimension(R.styleable.CircleProgressView_circleP_percentTextSize, DEFAULT_PERCENT_TEXT_SIZE)
            val percentTextSizePercentStr = array.getString(R.styleable.CircleProgressView_circleP_percentTextSizePercent)
            val percentTextSizePercent = context.calcTextSize(percentTextSizePercentStr)
            if (percentTextSizePercent != -1f) {
                percentTextSize = percentTextSizePercent
            }
            //百分比的字体颜色
            percentTextColor = array.getColor(R.styleable.CircleProgressView_circleP_percentTextColor, DEFAULT_PERCENT_TEXT_COLOR)
            //%的字体颜色
            percentTextTextColor = array.getColor(R.styleable.CircleProgressView_circleP_percentTextTextColor, DEFAULT_PERCENT_TEXT_COLOR)
            //提示的文本字体大小
            tipTextSize = array.getDimension(R.styleable.CircleProgressView_circleP_tipTextSize, DEFAULT_TIP_TEXT_SIZE)
            val tipTextSizePercentStr = array.getString(R.styleable.CircleProgressView_circleP_tipTextSizePercent)
            val tipTextSizePercent = context.calcTextSize(tipTextSizePercentStr)
            if (tipTextSizePercent != -1f) {
                tipTextSize = tipTextSizePercent
            }
            //提示的文本颜色
            tipTextColor = array.getColor(R.styleable.CircleProgressView_circleP_tipTextColor, DEFAULT_TIP_TEXT_COLOR)
            //提示的文本
            val tipText = array.getString(R.styleable.CircleProgressView_circleP_tipText)
            if (tipText.isNotEmpty()) {
                this.tipText = tipText
            }
            //背景
            backgroundColor = array.getColor(R.styleable.CircleProgressView_circleP_backgroundColor, DEFAULT_BACKROUND_COLOR)
            dividerStartColor = array.getColor(R.styleable.CircleProgressView_circleP_dividerStartColor, DEFAULT_DIVIDER_START_COLOR)
            dividerEndColor = array.getColor(R.styleable.CircleProgressView_circleP_dividerEndColor, DEFAULT_DIVIDER_END_COLOR)
            isDividerGradual = array.getBoolean(R.styleable.CircleProgressView_circleP_dividerIsGradual, DEFAULT_DIVIDER_IS_GRADUAL)

            array.recycle()
        }
        //计算最大转角角度
        maxRotateAngle = 360 - startAngle * 2f
        calcAngleCountAndMarginLeft()

        innerPaint = Paint()
        innerPaint.isAntiAlias = true
        innerPaint.color = centerColor
        innerPaint.style = Paint.Style.FILL

        outPaint = Paint()
        outPaint.isAntiAlias = true
        outPaint.style = Paint.Style.STROKE

        outStrokePaint = Paint()
        outStrokePaint.isAntiAlias = true
        outStrokePaint.style = Paint.Style.STROKE
        outStrokePaint.color = outStrokeColor
        outStrokePaint.strokeWidth = outStrokeWidth

        dividerPaint = Paint()
        dividerPaint.isAntiAlias = true
        dividerPaint.style = Paint.Style.STROKE
        dividerPaint.color = dividerColor
        dividerPaint.strokeWidth = dividerStrokeWidth

        percentPaint = TextPaint()
        percentPaint.textAlign = Paint.Align.CENTER
        percentPaint.isAntiAlias = true
        percentPaint.style = Paint.Style.FILL
        percentPaint.color = percentTextColor
        //没有设置百分比的textSiz的时候,设置百分比的textSize为20sp
        if (percentTextSize == DEFAULT_PERCENT_TEXT_SIZE) {
            percentPaint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_PERCENT_ACTUAL_TEXT_SIZE, context.resources.displayMetrics)
        } else {
            percentPaint.textSize = percentTextSize
        }

        percentTextPaint = TextPaint()
        percentTextPaint.textAlign = Paint.Align.LEFT
        percentTextPaint.isAntiAlias = true
        percentTextPaint.style = Paint.Style.FILL
        percentTextPaint.color = percentTextTextColor
        percentTextPaint.textSize = 25f
        //没有设置百分比的textSiz的时候,设置%的textSize为10sp
        if (percentTextSize == DEFAULT_PERCENT_TEXT_SIZE) {
            percentTextPaint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_PERCENT_ACTUAL_TEXT_SIZE / 2f, context.resources.displayMetrics)
        } else {
            percentTextPaint.textSize = percentTextSize / 2f
        }

        tipPaint = TextPaint()
        tipPaint.textAlign = Paint.Align.CENTER
        tipPaint.isAntiAlias = true
        tipPaint.style = Paint.Style.FILL
        tipPaint.color = tipTextColor
        if (tipTextSize == -1f) {
            tipPaint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, context.resources.displayMetrics)
        } else {
            tipPaint.textSize = tipTextSize
        }

        backgroundPaint = Paint()
        backgroundPaint.isAntiAlias = true
        backgroundPaint.color = backgroundColor
        backgroundPaint.style = Paint.Style.FILL
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        var w = widthSize
        var h = heightSize
        //都是wrap_content的时候给个300px
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            w = 300
            h = 300
        }
        //一个是wrap_content一个不是,把不是值给是的
        if (widthMode != MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            h = widthSize
        }
        if (widthMode == MeasureSpec.AT_MOST && heightMode != MeasureSpec.AT_MOST) {
            w = heightSize
        }
        //都不是wrap_content,哪个大用哪个
        if (widthSize < heightSize) {
            w = h
        }
        if (heightSize < widthSize) {
            h = w
        }
        setMeasuredDimension(w, h)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        //一般情况下,都会在onMeasure处理下避免这个问题,但不清楚是否真的不会发生,所以写着,或许用得上
        if (w != h) {
            throw IllegalArgumentException("width must equals height")
        }
        centerX = w / 2f
        centerY = h / 2f

        //内边圆的半径为半个控件大小的65%
        innerRadius = w * 0.65f / 2
        //外边圆弧的宽为半个控件大小的27%
        outRoundStrokeWidth = w * 0.27f / 2
        //剩下的画中间的间隔物
        dividerStrokeWidth = w / 2 - innerRadius - outRoundStrokeWidth

        //不清楚为什么要在变宽的1/2画出来的圆弧来没问题
        outRectF.left = outRoundStrokeWidth / 2
        outRectF.top = outRoundStrokeWidth / 2
        outRectF.right = w.toFloat() - outRoundStrokeWidth / 2
        outRectF.bottom = h.toFloat() - outRoundStrokeWidth / 2
        outPaint.strokeWidth = outRoundStrokeWidth

        setOutSweepGradient()
        setDividerColor()

        dividerPaint.strokeWidth = dividerStrokeWidth
        dividerRectF.left = outRoundStrokeWidth + dividerStrokeWidth / 2
        dividerRectF.top = outRoundStrokeWidth + dividerStrokeWidth / 2
        dividerRectF.right = w - outRoundStrokeWidth - dividerStrokeWidth / 2
        dividerRectF.bottom = h - outRoundStrokeWidth - dividerStrokeWidth / 2

        setOutStrokeRectF()

        calcBaseLine()
    }

    override fun onDraw(canvas: Canvas) {
        //画背景
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), backgroundPaint)
        drawCircle(canvas)
        drawProgress(canvas)
        drawPercentText(canvas)
        drawTip(canvas)
    }

    /**
     * 画中间的圆
     */
    private fun drawCircle(canvas: Canvas) {
        canvas.drawCircle(centerX, centerY, innerRadius, innerPaint)
    }

    /**
     * 画百分比
     */
    private fun drawProgress(canvas: Canvas) {
        //必须重新使用一个图层,否则当要画字的时候会变得非常麻烦
        val id = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        //要先旋转90°,否则渐变的开始角度在3点钟方向,这样渐变的效果看起来就有问题
        canvas.rotate(90f, centerX, centerY)

        //计算当前进度占总进度的百分比
        val showProgress = (currentProgress.toFloat() / maxProgress.toFloat() * 100).toInt()

        //不是很清楚为什么同样的角度,画外边圆弧总会比进度条多一点,所以剪掉吧
        canvas.drawArc(outStrokeRectF, startAngle + 1, maxRotateAngle - 2, false, outStrokePaint)
        canvas.drawArc(outRectF, startAngle, maxRotateAngle * showProgress / 100f, false, outPaint)
        for (i in 0 until dividerCount) {
            canvas.drawArc(dividerRectF, startAngle + (dividerAngle + angleMarginLeft) * i, dividerAngle, false, dividerPaint)
        }

        //释放图层
        canvas.restoreToCount(id)

        //画百分比的文本
        canvas.drawText(showProgress.toString(), centerX, percentBaseLine, percentPaint)
        //计算%的开始坐标
        percentTextLeftLine = width / 2f + percentWidth * 0.5f * showProgress.toString().length
    }

    /**
     * 画%
     */
    private fun drawPercentText(canvas: Canvas) {
        canvas.drawText("%", percentTextLeftLine, percentTextBaseLine, percentTextPaint)
    }

    /**
     * 画提示
     */
    private fun drawTip(canvas: Canvas) {
        canvas.drawText(tipText, centerX, tipBaseLine, tipPaint)
    }

    /**
     * 计算间隔物的个数和每次的marginLeft
     */
    private fun calcAngleCountAndMarginLeft() {
        //获取最大角度/每个角度的个数,并除以2,用于显示的个数
        dividerCount = (maxRotateAngle / dividerAngle).toInt() / 2
        //总间隔=最大角度-每个角度*显示个数
        val allMarginLeft = maxRotateAngle - dividerAngle * dividerCount
        //每个的间隔=总间隔/(显示的个数-1)
        angleMarginLeft = allMarginLeft / (dividerCount - 1)
    }

    /**
     * 计算基线
     */
    private fun calcBaseLine() {
        val percentY = height / 2f - height * 0.1f
        val percentMetrics = percentPaint.fontMetrics
        percentBaseLine = percentY + (percentMetrics.bottom - percentMetrics.top) / 2 - percentMetrics.descent
        percentWidth = percentPaint.measureText("9")
        val percentTextMetrics = percentTextPaint.fontMetrics
        percentTextBaseLine = percentY - percentWidth / 4 + (percentTextMetrics.bottom - percentTextMetrics.top) / 2 - percentTextMetrics.descent
        val tipMetrics = tipPaint.fontMetrics
        tipBaseLine = percentY + percentPaint.textSize / 2f + tipPaint.textSize / 2f + (tipMetrics.bottom - tipMetrics.top) / 2 - tipMetrics.descent
    }

    /**
     * 设置圆弧渐变
     */
    private fun setOutSweepGradient() {
        val intArray = IntArray(2)
        //开始的颜色的结束的颜色
        intArray[0] = outStartColor
        intArray[1] = outEndColor
        //如果不设置角度,会从0开始,360结束.这时如果圆弧有缺口,看到圆弧开始的颜色就不是渐变的开始颜色
        //所以需要计算开始的角度占360°的百分比,并设置为开始的渐变的角度,结束角度同理
        val floatArray = FloatArray(2)
        floatArray[0] = startAngle / 360
        floatArray[1] = (startAngle + maxRotateAngle) / 360
        outPaint.shader = SweepGradient(centerX, centerY, intArray, floatArray)
    }

    /**
     * 设置间隔物的渐变
     */
    private fun setDividerColor() {
        if (isDividerGradual) {
            val intArray = IntArray(2)
            intArray[0] = dividerStartColor
            intArray[1] = dividerEndColor
            val floatArray = FloatArray(2)
            floatArray[0] = startAngle / 360
            floatArray[1] = (startAngle + maxRotateAngle) / 360
            dividerPaint.shader = SweepGradient(centerX, centerY, intArray, floatArray)
        } else {
            dividerPaint.shader = null
        }
    }

    /**
     * 设置外边框的draw面积
     */
    private fun setOutStrokeRectF() {
        //不+1能看到一点点外边框
        outStrokeRectF.left = outStrokeWidth / 2 + 1
        outStrokeRectF.top = outStrokeWidth / 2 + 1
        outStrokeRectF.right = width - outStrokeWidth / 2 - 1
        outStrokeRectF.bottom = height - outStrokeWidth / 2 - 1
    }

    fun setOutGradualColor(startColor: Int, endColor: Int) {
        this.outStartColor = startColor
        this.outEndColor = endColor
        outPaint.shader = SweepGradient(centerX, centerY, this.outStartColor, this.outEndColor)
        invalidate()
    }

    fun setOutStartColor(startColor: Int) {
        this.outStartColor = startColor
        outPaint.shader = SweepGradient(centerX, centerY, this.outStartColor, this.outEndColor)
        invalidate()
    }

    fun setOutEndColor(endColor: Int) {
        this.outEndColor = endColor
        outPaint.shader = SweepGradient(centerX, centerY, this.outStartColor, this.outEndColor)
        invalidate()
    }

    fun setStartAngle(startAngle: Float) {
        this.startAngle = startAngle
        maxRotateAngle = 360 - startAngle * 2
        setOutSweepGradient()
        setDividerColor()
        calcAngleCountAndMarginLeft()
        invalidate()
    }

    private val TAG = "CircleProgressViewMsg"
    fun setCurrentProgress(currentProgress: Int) {
        Log.v(TAG,"currentProgress:$currentProgress")
        if (currentProgress < 0 || currentProgress > maxProgress) {
            return
        }
        this.currentProgress = currentProgress
        invalidate()
    }

    fun getCurrentProgress() = currentProgress

    fun setManProgress(maxProgress: Int) {
        if (maxProgress < currentProgress) {
            return
        }
        this.maxProgress = maxProgress
        invalidate()
    }

    fun getMaxProgress() = maxProgress

    fun setOutStrokeColor(outStrokeColor: Int) {
        outStrokePaint.color = outStrokeColor
        invalidate()
    }

    fun setOutStrokeWidth(outStrokeWidth: Float) {
        this.outStrokeWidth = outStrokeWidth
        outStrokePaint.strokeWidth = outStrokeWidth
        setOutStrokeRectF()
        invalidate()
    }

    fun setDividerAngle(dividerAngle: Float) {
        this.dividerAngle = dividerAngle
        calcAngleCountAndMarginLeft()
        invalidate()
    }

    fun setDividerColor(dividerColor: Int) {
        dividerPaint.color = dividerColor
        invalidate()
    }

    fun setPercentTextSize(sp: Float) {
        setPercentTextSize(sp, TypedValue.COMPLEX_UNIT_SP)
    }

    fun setPercentTextSize(textSize: Float, unit: Int) {
        val actualTextSize = TypedValue.applyDimension(unit, textSize, context.resources.displayMetrics)
        percentPaint.textSize = actualTextSize
        percentTextPaint.textSize = actualTextSize / 2f
        calcBaseLine()
        invalidate()
    }

    fun setPercentTextSizePercent(textSizePercent: String) {
        val textSize = context.calcTextSize(textSizePercent)
        if (textSize != -1f) {
            setPercentTextSize(textSize, TypedValue.COMPLEX_UNIT_PX)
        }
    }

    fun setPercentTextColor(textColor: Int) {
        percentPaint.color = textColor
        invalidate()
    }

    fun setPercentTextTextColor(textColor: Int) {
        percentTextPaint.color = textColor
        invalidate()
    }

    fun setTipTextSize(sp: Float) {
        setTipTextSize(sp, TypedValue.COMPLEX_UNIT_SP)
    }

    fun setTipTextSize(textSize: Float, unit: Int) {
        val actualTextSize = TypedValue.applyDimension(unit, textSize, context.resources.displayMetrics)
        tipPaint.textSize = actualTextSize
        calcBaseLine()
        invalidate()
    }

    fun setTipTextSizePercent(textSizePercent: String) {
        val textSize = context.calcTextSize(textSizePercent)
        if (textSize != -1f) {
            setTipTextSize(textSize, TypedValue.COMPLEX_UNIT_PX)
        }
    }

    fun setTipTextColor(textColor: Int) {
        tipPaint.color = textColor
    }

    fun setTipText(text: String?) {
        if (text.isNotEmpty()) {
            tipText = text!!
            invalidate()
        }
    }

    fun setBgColor(bgColor: Int) {
        backgroundPaint.color = bgColor
        invalidate()
    }

    fun setDividerGradualColor(startColor: Int, endColor: Int) {
        this.dividerStartColor = startColor
        this.dividerEndColor = endColor
        setDividerColor()
        invalidate()
    }

    fun setDividerStartColor(startColor: Int) {
        this.dividerStartColor = startColor
        setDividerColor()
        invalidate()
    }

    fun setDividerEndColor(endColor: Int) {
        this.dividerEndColor = endColor
        setDividerColor()
        invalidate()
    }

    fun isDividerGradual(isDividerGradual: Boolean) {
        this.isDividerGradual = isDividerGradual
        setDividerColor()
        invalidate()
    }
}

Util,kt

import android.content.Context
import org.jetbrains.anko.displayMetrics

fun Context.calcTextSize(textSizePercent: String?): Float {
    if (textSizePercent != null) {
        val regex = "^\\d{1,3}%$"
        if (textSizePercent.matches(regex.toRegex())) {
            val percent = textSizePercent.substring(0, textSizePercent.length - 1)
            return percent.toFloat() * displayMetrics.widthPixels.toFloat() / 100f
        }
    }
    return -1f
}

fun String?.isEmpty(): Boolean {
    if (this == null) {
        return true
    }
    return trim().length == 0
}

fun String?.isNotEmpty(): Boolean = !isEmpty()

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleProgressView">
        <attr name="circleP_outStartColor" format="color"/>
        <attr name="circleP_outEndColor" format="color"/>
        <attr name="circleP_startAngle" format="integer"/>
        <attr name="circleP_currentProgress" format="integer"/>
        <attr name="circleP_maxProgress" format="integer"/>
        <attr name="circleP_outStrokeColor" format="color"/>
        <attr name="circleP_outStrokeWidth" format="dimension"/>
        <attr name="circleP_dividerAngle" format="integer"/>
        <attr name="circleP_dividerColor" format="color"/>
        <attr name="circleP_centerColor" format="color"/>
        <attr name="circleP_percentTextSize" format="dimension"/>
        <attr name="circleP_percentTextSizePercent" format="string"/>
        <attr name="circleP_percentTextColor" format="color"/>
        <attr name="circleP_percentTextTextColor" format="color"/>
        <attr name="circleP_tipTextSize" format="dimension"/>
        <attr name="circleP_tipTextSizePercent" format="string"/>
        <attr name="circleP_tipTextColor" format="color"/>
        <attr name="circleP_tipText" format="string"/>
        <attr name="circleP_backgroundColor" format="color"/>
        <attr name="circleP_dividerStartColor" format="color"/>
        <attr name="circleP_dividerEndColor" format="color"/>
        <attr name="circleP_dividerIsGradual" format="boolean"/>
    </declare-styleable>
</resources>

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值