效果图
左边的黑色边框有一部分看不到,这是三星自带的录制屏幕软件的问题,请不要在意
这个控件主要分为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>