效果图

左边的黑色边框有一部分看不到,这是三星自带的录制屏幕软件的问题,请不要在意
这个控件主要分为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>
本文介绍了一个自定义的圆形进度条控件实现细节,包括如何处理旋转、渐变填充及文字布局等问题,通过Kotlin语言实现了良好的用户体验。
2913





