kotlin-实现仪表盘view

效果:

kotlin-实现仪表盘view_第1张图片

思路:

  • 1、首先画一个圆弧;
  • 2、画刻度,就是一个个小矩形,但是有方向。用PathDashPathEffect来画就很简单,把小矩形当做虚线路径;
  • 3、画指针,就是一条线,起点是圆心,终点用三角函数计算
  • 4、加一个pointerProgress属性,并且设置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: 仪表盘,支持指针转动动画
 * 1、首先画一个圆弧;
 * 2、画刻度,就是一个个小矩形,但是有方向。用PathDashPathEffect来画就很简单,把小矩形当做虚线路径;
 * 3、画指针,就是一条线,起点是圆心,终点用三角函数计算
 * 4、加一个pointerProgress属性,并且设置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

//刻度间隔数
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
    )

    /**值区间:0.0 - DASH_COUNT*/
    private var pointerProgress = 0f
        set(value) {
            field = value
            invalidate()
        }

    /**值区间:0.0 - 100*/
    private var arcProgress = 0f
        set(value) {
            field = value
            invalidate()
        }

    /**值区间: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

    //指针角度 (用三角函数时需要把角度转成弧度)
    var pointerAngle = (360 - OPEN_ANGLE) / 20f * pointerProgress + 90 + OPEN_ANGLE / 2


    init {
        //因为画笔颜色没变,这里就不用填充了,不然刻度和指针都看不见
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 3f.px
        //构建刻度的小矩形
        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 就是间隔
        //计算弧线总长度
        //创建路径效果:用虚线模式  虚线就是刻度小矩形
        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
        //指针的起点是圆心,终点坐标就是三角函数的正余弦,正弦*斜边长度=纵坐标,余弦*斜边长度 = 横坐标,算好坐标再加上起始坐标即可
        //注意:三角函数需要传入的是弧度值,要先把角度转弧度 、并且不用考虑坐标的正负数,因为三角函数自带正负
        //指针的角度计算 = 圆弧的划过角度除以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()
}

你可能感兴趣的:(kotlin-实现仪表盘view)