渐变圆环效果图:
这个图有点模糊,我是截取的 android studio 中的预览图
开发中遇到的问题:
当 SweepGradient 遇上 Paint.strokeCap = Paint.Cap.ROUND 时,在加上渐变会出现奇怪的现象
Paint.Cap.BUTT: 是默认的,不会多出任何东西
Paint.Cap.ROUND: 圆头,多出半个圆
Paint.Cap.SQUARE: 平头,多出半个方形
因为我们需要Paint.strokeCap = Paint.Cap.ROUND,保证进度条是圆头的:
不是渐变的圆环,那还好可以正常显示;
是渐变的圆环,会出现首尾重叠,并且尾部圆头会覆盖在首部,及其丑陋。
如果解决该问题呢?
那就是计算首尾两个半圆头所占的弧度,减去即可
偏移角度算法如下图:
源码如下
class GradientAnnulusProgress @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
/**
* 代表当前进度
*/
@FloatRange(from = 0.0, to = 1.0)
private var currentProgress = 0.75f
set(value) {
field = when {
value > maxProgress -> maxProgress
value < 0 -> 0f
else -> value
}
postInvalidate()
}
/**
* 最大的进度,默认为 1f 如果设置为 0.5f,那么圆环进度最多只会走整体的一半
*/
@FloatRange(from = 0.0, to = 1.0)
private var maxProgress = 1f
/**
* 开始的角度, 默认为 0 比如说 圆环的起点为 135,那么圆环的起点就会从x轴正方向顺时针旋转135
*/
@FloatRange(from = 0.0, to = 360.0)
private var startAngle = 0f
/**
* SweepGradient 的需要的颜色值
*/
@ColorInt
var colorsArray: IntArray = intArrayOf(0xFFBB86FC.toInt(), 0xFF03DAC5.toInt())
@Nullable
var positionsArray: FloatArray? = floatArrayOf(0f, maxProgress)
private var sweepGradient: SweepGradient? = null
/**
* view 的中心的 位置,在 onSizeChanged 中赋值
*/
private var center = 0f
/**
* 圆环的宽度
*/
@Px
private var progressWidth = 100
/**
* 圆环的边界
*/
private val progressBounds = RectF()
/**
* 绘制圆环进度的画笔
*/
private val progressPaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth = progressWidth.toFloat()
strokeCap = Paint.Cap.ROUND
}
/**
* 圆环的背景
*/
@ColorInt
var bgColor: Int = Color.LTGRAY
private val bgPaint = Paint().apply {
color = bgColor
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth = progressWidth.toFloat()
strokeCap = Paint.Cap.ROUND
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
sweepGradient = SweepGradient(
0f, 0f, colorsArray, positionsArray
)
progressPaint.shader = sweepGradient
center = width / 2f
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSize = max(MeasureSpec.getSize(widthMeasureSpec), (progressWidth * 5))
val heightSize = max(MeasureSpec.getSize(heightMeasureSpec), (progressWidth * 5))
val size = min(widthSize, heightSize)
setMeasuredDimension(
resolveSize(size, widthMeasureSpec), resolveSize(size, widthMeasureSpec)
)
}
override fun onDraw(canvas: Canvas) {
// 将画布移动的 view 中心点
canvas.translate(center, center)
// 圆环的半径 view 的 一半 减去 画笔的一半
val progressRadius = center - progressWidth / 2f
// Paint.Cap.ROUND 这种模式会导致起点和终点多出个圆头(起点圆头的颜色是终点的颜色值,所以显示很奇怪),
// 那么就要计算这个圆头所占的弧度,在绘制扇形的时候,起始点设置为该值
val fixStartAngle =
Math.toDegrees(asin(progressWidth / 2f / (progressRadius)).toDouble()).toFloat()
// 根据进度计算当前的弧度, 经过观察,终点圆头占据的弧度是 fixStartAngle 的两倍,所以要减去这个值
// 那么随之带来的问题就是: currentProgress * 360 - fixStartAngle * 2 有可能小于 0,
// 必须要保证 currentAngle 大于等于 0,就需要 max 一下
val currentAngle = max(currentProgress * 360 - fixStartAngle * 2, 0f)
canvas.rotate(startAngle)
progressBounds.set(
-progressRadius, -progressRadius, progressRadius, progressRadius
)
canvas.drawArc(
progressBounds, fixStartAngle, 360 - fixStartAngle * 2, false, bgPaint
)
canvas.drawArc(
progressBounds, fixStartAngle, currentAngle, false, progressPaint
)
}
}