效果:
思路:
- 1、首先畫一個圓弧;
- 2、畫刻度,就是一個個小矩形,但是有方向。用PathDashPathEffect來畫就很簡單,把小矩形當(dāng)做虛線路徑;
- 3、畫指針,就是一條線,起點是圓心,終點用三角函數(shù)計算
- 4、加一個pointerProgress屬性,并且設(shè)置set方法,給屬性動畫使用,這樣指針就可以做動畫了
- ps:給圓弧和刻度的繪畫過程也加了動畫,只是為了更深刻的體會繪制的過程。
代碼中各種注釋都有
自定義view代碼
package com.xc.test
import android.animation.Animator
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.xc.test.util.logE
import com.xc.test.util.px
import kotlin.math.cos
import kotlin.math.sin
/**
* @author: xuchun
* @time: 2021/4/1 - 16:20
* @desc: 儀表盤,支持指針轉(zhuǎn)動動畫
* 1、首先畫一個圓弧;
* 2、畫刻度,就是一個個小矩形,但是有方向。用PathDashPathEffect來畫就很簡單,把小矩形當(dāng)做虛線路徑;
* 3、畫指針,就是一條線,起點是圓心,終點用三角函數(shù)計算
* 4、加一個pointerProgress屬性,并且設(shè)置set方法,給屬性動畫使用,這樣指針就可以做動畫了
*
* ps:給圓弧和刻度的繪畫過程也加了動畫,只是為了更深刻的體會繪制的過程
*/
//儀表盤開口角度
const val OPEN_ANGLE = 90f
//儀表盤半徑
val RADIUS = 100f.px
//刻度,即一個小矩形的寬高
val DASH_WIDTH = 2f.px
val DASH_LENGTH = 5f.px
//指針的長度
val POINTER_LENGTH = 80f.px
//刻度間隔數(shù)
const val DASH_COUNT = 20f
class DashboardView : View {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
/**值區(qū)間:0.0 - DASH_COUNT*/
private var pointerProgress = 0f
set(value) {
field = value
invalidate()
}
/**值區(qū)間:0.0 - 100*/
private var arcProgress = 0f
set(value) {
field = value
invalidate()
}
/**值區(qū)間:0.0 - 100*/
private var dashProgress = 0f
set(value) {
field = value
invalidate()
}
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
//整體所在矩形
private lateinit var rectF: RectF
//表盤圓弧
private val tempArc = Path()
private val pathArc = Path()
//刻度的圓弧
private val pathDashArc = Path()
//刻度線小矩形,雖然叫做path,其實更多是用來畫圖形的
private val dash = Path()
//用來計算path長度
private lateinit var pathMeasure: PathMeasure
//路徑效果定制器
private var pathDashPathEffect: PathDashPathEffect? = null
//指針角度 (用三角函數(shù)時需要把角度轉(zhuǎn)成弧度)
var pointerAngle = (360 - OPEN_ANGLE) / 20f * pointerProgress + 90 + OPEN_ANGLE / 2
init {
//因為畫筆顏色沒變,這里就不用填充了,不然刻度和指針都看不見
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f.px
//構(gòu)建刻度的小矩形
dash.addRect(0f, 0f, DASH_WIDTH, DASH_LENGTH, Path.Direction.CW)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
pathArc.reset()
tempArc.reset()
rectF =
RectF(width / 2 - RADIUS, height / 2 - RADIUS, width / 2 + RADIUS, height / 2 + RADIUS)
tempArc.addArc(rectF, 90 + OPEN_ANGLE / 2, (360 - OPEN_ANGLE))
//動畫畫圓弧
val objectAnimator = ObjectAnimator.ofFloat(this@DashboardView, "arcProgress", 0f, 100f)
objectAnimator.duration = 1000
// objectAnimator.interpolator = LinearInterpolator()
objectAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
//圓弧畫完 再畫其他
drawArcFinished = true
preDrawDsh()
//開始畫刻度
val objectA = ObjectAnimator.ofFloat(this@DashboardView, "dashProgress", 0f, 100f)
objectA.duration = 2000
objectA.start()
}
})
objectAnimator.start()
}
private var drawArcFinished = false
private fun preDrawDsh() {
//假如有20個格子(21個刻度) 那就用弧線總長度除以20 就是間隔
//計算弧線總長度
//創(chuàng)建路徑效果:用虛線模式 虛線就是刻度小矩形
pathMeasure = PathMeasure(tempArc, false)
"pathMeasure.length = ${pathMeasure.length}".logE()
pathDashPathEffect = PathDashPathEffect(
dash,
(pathMeasure.length - DASH_WIDTH) / 20f,
0f,
PathDashPathEffect.Style.ROTATE
)
}
override fun onDraw(canvas: Canvas?) {
/**
* useCenter = true 可以用來做餅圖! =false就是儀表盤了 不過要想用userCenter要用canvas?.drawArc不能用path.addArc
*/
//1、畫圓弧
pathArc.addArc(rectF, 90 + OPEN_ANGLE / 2, (360 - OPEN_ANGLE) * (arcProgress / 100))
canvas?.drawPath(pathArc, paint)
//2、畫刻度效果
drawDashPath(canvas)
//3、畫指針
drawPointer(canvas)
}
private fun drawPointer(canvas: Canvas?) {
if (!drawArcFinished) return
//指針的起點是圓心,終點坐標就是三角函數(shù)的正余弦,正弦*斜邊長度=縱坐標,余弦*斜邊長度 = 橫坐標,算好坐標再加上起始坐標即可
//注意:三角函數(shù)需要傳入的是弧度值,要先把角度轉(zhuǎn)弧度 、并且不用考慮坐標的正負數(shù),因為三角函數(shù)自帶正負
//指針的角度計算 = 圓弧的劃過角度除以DASH_COUNT乘以progress可以得到指針在圓弧的角度,再加上圓弧的起始角度(90+半個開口角度),最終得到指針真正的角度
pointerAngle = (360 - OPEN_ANGLE) / DASH_COUNT * pointerProgress + 90 + OPEN_ANGLE / 2
// "pointerAngle = $pointerAngle".logE()
canvas?.drawLine(
width / 2f,
height / 2f,
(cos(Math.toRadians(pointerAngle.toDouble())) * POINTER_LENGTH + width / 2f).toFloat(),
(sin(Math.toRadians(pointerAngle.toDouble())) * POINTER_LENGTH + height / 2f).toFloat(),
paint
)
}
private fun drawDashPath(canvas: Canvas?) {
pathDashPathEffect?.let {
paint.pathEffect = pathDashPathEffect
pathDashArc.addArc(
rectF,
90 + OPEN_ANGLE / 2,
(360 - OPEN_ANGLE) * (dashProgress / 100)
)
canvas?.drawPath(pathDashArc, paint)
paint.pathEffect = null
}
}
}
工具類代碼
val Float.px
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics
)
fun String.logE() {
Log.e("xc", this)
}
activity中指針動畫代碼
startButton.setOnClickListener {
val objectAnimator = ObjectAnimator.ofFloat(dash_view, "pointerProgress", 0f, 20f)
objectAnimator.duration = 10000
objectAnimator.interpolator = LinearInterpolator()
objectAnimator.start()
}