效果图:
核心代码
package com.jw.gradualprogressdemo
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
/**
* author : created by JW on 2020/4/28 15:34
* package : com.jw.gradualprogressdemo
* description :
*/
class GradualProgressView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val ringPaint: Paint
private val progressPaint: Paint
private val bitmapPaint: Paint
private val ringRectF: RectF = RectF()
private var ringWidth = 4f
private val bitmap: Bitmap
private var path: Path
private var pathMeasure: PathMeasure
private var mMatrix = Matrix()
private val defaultStartAngle = 0f
private var currentStartAngle = defaultStartAngle
private var progressSweepAngle = 0f
private var currentRatio = 0f
private var isGradual: Boolean = true
private val degreeOfOnTurn = 360f
private var animator: ValueAnimator? = null
private var ringColor: Int
private var progressColor: Int
private var gradualStartColor: Int
private var gradualEndColor: Int
init {
context.obtainStyledAttributes(attrs, R.styleable.GradualProgressView).run {
ringWidth =
getDimensionPixelSize(
R.styleable.GradualProgressView_ringWidth,
context.dp2px(4).toInt()
).toFloat()
isGradual = getBoolean(R.styleable.GradualProgressView_isGradual, false)
ringColor = getColor(
R.styleable.GradualProgressView_ring_color,
context.getColorCompat(R.color.color_1AFFFFFF)
)
progressColor =
getColor(
R.styleable.GradualProgressView_progress_color,
context.getColorCompat(R.color.color_29C4C4)
)
gradualStartColor =
getColor(
R.styleable.GradualProgressView_gradual_start_color,
context.getColorCompat(R.color.color_29C4C4)
)
gradualEndColor =
getColor(
R.styleable.GradualProgressView_gradual_end_color,
context.getColorCompat(R.color.color_29C4C4)
)
bitmap = BitmapFactory.decodeResource(
resources,
getResourceId(
R.styleable.GradualProgressView_progress_thumb,
R.drawable.common_view_progress_thumb
)
)
recycle()
}
ringPaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = ringColor
strokeWidth = ringWidth
strokeCap = Paint.Cap.ROUND
}
progressPaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = progressColor
strokeWidth = ringWidth
strokeCap = Paint.Cap.ROUND
}
bitmapPaint = Paint().apply {
isAntiAlias = true
}
path = Path()
pathMeasure = PathMeasure()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.save()
// 移动、旋转
canvas?.translate((width / 2).toFloat(), (height / 2).toFloat())
canvas?.rotate(-90f)
// 绘制圆环
canvas?.drawArc(ringRectF, 0f, degreeOfOnTurn, false, ringPaint)
canvas?.drawArc(ringRectF, currentStartAngle, progressSweepAngle, false, progressPaint)
// 球型图片
pathMeasure.also {
it.getMatrix(
(it.length * currentRatio) % it.length, mMatrix,
PathMeasure.TANGENT_MATRIX_FLAG or PathMeasure.POSITION_MATRIX_FLAG
)
mMatrix.preTranslate((-bitmap.width / 2).toFloat(), (-bitmap.height / 2).toFloat())
canvas?.drawBitmap(bitmap, mMatrix, bitmapPaint)
}
canvas?.restore()
}
/**
* 暂停动画
*/
fun pauseAnimator() {
if (animator?.isPaused == true) return
animator?.pause()
}
/**
* 继续执行
*/
fun resumeAnimator() {
if (animator?.isPaused != true) return
animator?.resume()
}
fun cancelAnimator() {
if (animator?.isStarted != true) return
animator?.cancel()
}
/**
* 开始动画
*/
fun startAnimator(animatorDuration: Long = 5) {
animator = ObjectAnimator.ofFloat(0f, degreeOfOnTurn).apply {
duration = animatorDuration * 1000
interpolator = LinearInterpolator()
if (isGradual) {
repeatCount = Animation.INFINITE
repeatMode = ValueAnimator.RESTART
}
}
animator?.addUpdateListener {
val value = it.animatedValue as Float
if (isGradual) { // 有渐变
progressSweepAngle = progressSweepAngle.coerceAtLeast(value.coerceAtMost(90f))
currentRatio = (value % degreeOfOnTurn) / degreeOfOnTurn // 当前角度/360的比值
currentStartAngle = if (progressSweepAngle < 90) { // 起始角度
defaultStartAngle
} else {
(degreeOfOnTurn + value - 90f) % degreeOfOnTurn
}
// 渐变处理
val sweepGradient = SweepGradient(
ringRectF.centerX(),
ringRectF.centerY(),
intArrayOf(
gradualStartColor,
gradualEndColor
),
floatArrayOf(0.75f, 1.0f) // 设置 0f,0.25f,起始点看起来被截断一样,所以用0.75f,1.0f,并再下方使用旋转处理
)
// 旋转
sweepGradient.setLocalMatrix(Matrix().apply {
setRotate(
if (value > 90f || progressSweepAngle >= 90) {
value
} else {
90f
},
ringRectF.centerX(),
ringRectF.centerY()
)
})
progressPaint.shader = sweepGradient
} else {
progressSweepAngle = it.animatedValue as Float
currentRatio = value / degreeOfOnTurn
}
invalidate()
}
animator?.start()
}
fun setIsGradual(isGradual: Boolean) {
this.isGradual = isGradual
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
val result = width.coerceAtMost(height)
setMeasuredDimension(result, result)
val space = bitmap.width
val radius = (result.toFloat() - space) / 2
ringRectF.set(-radius, -radius, radius, radius)
path.addCircle(0f, 0f, radius, Path.Direction.CW)
pathMeasure.setPath(path, false)
}
}
PathMeasure
相关参考了该链接:https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B08%5DPath_Play.md
自定义属性
在res/values/attrs.xml中添加自定义属性
<declare-styleable name="GradualProgressView">
<!--圆环宽度-->
<attr name="ringWidth" format="dimension|reference" />
<!--是否渐变-->
<attr name="isGradual" format="boolean" />
<!--圆环颜色-->
<attr name="ring_color" format="color|reference" />
<!--进度颜色-->
<attr name="progress_color" format="color|reference" />
<!--渐变起始颜色-->
<attr name="gradual_start_color" format="color|reference" />
<!--渐变结束颜色-->
<attr name="gradual_end_color" format="color|reference" />
<!--进度前图片-->
<attr name="progress_thumb" format="reference" />
</declare-styleable>
使用
<com.jw.gradualprogressdemo.GradualProgressView
android:id="@+id/gpProgress"
android:layout_width="300dp"
android:layout_height="300dp"
app:gradual_end_color="@color/color_29c4c4"
app:gradual_start_color="@color/color_1AFFFFFF"
app:isGradual="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:ringWidth="4dp" />