kotlin-實現(xiàn)儀表盤view

效果:

思路:

  • 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()
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內(nèi)容