Android 自定義View篇(六)實現時鐘錶盤效果

Android 自定義View篇(六)實現時鐘錶盤效果

前言

Android 自定義 View 是高級進階不可或缺的內容,平常工做中,常常會遇到產品、UI 設計出花裏胡哨的界面。當系統自帶的控件不能知足開發需求時,就只能本身動手擼一個效果。
本文就帶自定義 View 初學者手動擼一個效果,經過自定義 View 實現鐘錶功能,每行代碼都有註釋,保證易懂,看不懂你留言打我!!!git

實現效果

一、先看效果圖

Android 自定義View篇(六)實現時鐘錶盤效果
在這裏插入圖片描述github

二、下載地址

https://github.com/jaynm888/ClockCustomizeViewcanvas

三、步驟分析

實現以上效果,主要分爲四個步驟:ide

  1. 繪製外層錶盤
  2. 繪製刻度線
  3. 繪製刻度數字
  4. 繪製指針
  5. 測量View寬高

    代碼實現

一、繪製外層錶盤
外層錶盤就是一個空心圓,只要獲取圓的 x、y 軸位置、圓的半徑,使用 Canvas.drawCircle()方法便可完成。佈局

/**
 * 繪製錶盤
 */
private fun drawClock(canvas: Canvas, centerX: Float, centerY: Float) {

    // 設置外層圓畫筆寬度
    mPaint.strokeWidth = mCircleWidth
    // 設置畫筆顏色
    mPaint.color = Color.BLACK
    // 設置畫筆空心風格
    mPaint.style = Paint.Style.STROKE
    // 繪製圓方法
    canvas.drawCircle(centerX, centerY, radius, mPaint)
}

Android 自定義View篇(六)實現時鐘錶盤效果
在這裏插入圖片描述post

二、繪製刻度線

繪製思路分析
看到效果圖上密密麻麻刻度線後,先不要着急上手,屢清楚繪製思路。繪製刻度線必定要結合 Canvas 幾何變換思路完成,千萬不要侷限於效果圖的表面(若是對 Canvas 相關 API 不熟悉的朋友,建議先了解下)。學習

  1. 假設以 12 點鐘爲例,那麼刻度線就是一條筆直的豎線,調用 Canvas.drawLine()方法完成繪製。
  2. 若是每繪製完成一個刻度,把錶盤逆時針/順時針旋轉必定角度,將下次須要繪製刻度線的位置旋轉到 12 點鐘位置,那麼每次繪製刻度線的 startX、startY、stopX、stopY 一致(一致僅表明全部長刻度一致,全部短刻度一致)。
  3. 觀察錶盤共有 60 個刻度線(12 長,48 短),那麼每次旋轉角度degrees=360/60
    聽完以上分析,是否以爲繪製刻度線很簡單,只要在 60 個刻度遍歷判斷長短,便可輕鬆出效果。
/**
 * 繪製錶盤刻度
 */
private fun drawClockScale(canvas: Canvas, centerX: Float, centerY: Float) {
    for (index in 1..60) {
        // 刻度繪製以12點鐘爲準,每次將錶盤旋轉6°,後續繪製都以12點鐘爲基準繪製
        canvas.rotate(6F, centerX, centerY)
        // 繪製長刻度線
        if (index % 5 == 0) {
            // 設置長刻度畫筆寬度
            mPaint.strokeWidth = 4.0F
            // 繪製刻度線
            canvas.drawLine(
                centerX,
                centerY - radius,
                centerX,
                centerY - radius + scaleMax,
                mPaint
            )
        }
        // 繪製短刻度線
        else {
            // 設置短刻度畫筆寬度
            mPaint.strokeWidth = 2.0F
            canvas.drawLine(
                centerX,
                centerY - radius,
                centerX,
                centerY - radius + scaleMin,
                mPaint
            )
        }
    }
}

以上代碼就完成了繪製刻度線的效果,下面插個題外話,第一次嘗試在繪製刻度線的時候,錶盤數字一併完成,後來發現數字以下圖所示:
Android 自定義View篇(六)實現時鐘錶盤效果線程

// 測量繪製數字
mPaint.strokeWidth = 1.0F
mPaint.style = Paint.Style.FILL
mPaint.getTextBounds((index / 5).toString(), 0, (index / 5).toString().length, mRect)
val width = mRect.width()
canvas.drawText(
    (index / 5).toString(),
    centerX - mRect.width() / 2,
    (centerY - radius + scaleMax + mRect.height() + 8),
    mPaint
)

三、繪製數字方案

熱心網友指導我繪製數字新方案,真的是高手如雲阿。
首先將座標位置(0,0)設置到圓心位置,這步是在繪製外層圓的時候,已經設置了。這樣的好處是後期減小不少計算的步驟,新方案已經在代碼中更改!設計

canvas.translate(centerX, centerY)

主要是經過 canvas 幾何變換方式,先將圓點平移到 12 點鐘位置,而後逆時針旋轉數字對應的角度,而後開始繪製數字文本。這樣的話,繪製數字文本就和繪製刻度線能夠一併完成,使得代碼清晰不少。須要注意的是,記得在使用幾何變換先後分別調用canvas.restore()和 canvas.restore()方法。
其中相關座標計算方式:
一、平移 y 軸距離 = - 半徑 + 刻度線長度 + 刻度與文本間距 + 文本高度 / 2
(由於座標原點在圓心,須要平移到 12 點鐘位置,因此半徑爲負數)
二、旋轉角度 = - 6 * 數字大小
三、文本 x 軸距離 = 文本寬度 / 2 ;
四、文本 y 軸距離 = 文本高度 / 2 ;
附上繪製刻度線和文本的完整代碼:3d

/**
 * 繪製錶盤刻度線和數字文本
 */
private fun drawClockScale(canvas: Canvas) {
    for (index in 1..60) {
        // 刻度繪製以12點鐘爲準,每次將錶盤旋轉6°,後續繪製都以12點鐘爲基準繪製
        canvas.rotate(6F, 0F, 0F)
        // 繪製長刻度線
        if (index % 5 == 0) {
            // 設置長刻度畫筆寬度
            mPaint.strokeWidth = 4.0F
            // 繪製刻度線
            canvas.drawLine(0F, -radius, 0F, -radius + scaleMax, mPaint)
            /** 繪製文本 **/
            canvas.save()
            // 設置畫筆寬度
            mPaint.strokeWidth = 1.0F
            // 設置畫筆實心風格
            mPaint.style = Paint.Style.FILL
            mPaint.getTextBounds(
                (index / 5).toString(),
                0,
                (index / 5).toString().length,
                mRect
            )
            canvas.translate(0F, -radius + mNumberSpace + scaleMax + (mRect.height() / 2))
            canvas.rotate((index * -6).toFloat())
            canvas.drawText(
                (index / 5).toString(), -mRect.width() / 2.toFloat(),
                mRect.height().toFloat() / 2, mPaint
            )
            canvas.restore()
        }
        // 繪製短刻度線
        else {
            // 設置短刻度畫筆寬度
            mPaint.strokeWidth = 2.0F
            canvas.drawLine(0F, -radius, 0F, -radius + scaleMin, mPaint)
        }
    }
}

四、繪製指針

指針繪製具體分如下步驟:

  1. 首先獲取當前時間
  2. 根據當前時間計算指針旋轉過的角度
  3. 利用 Canvas.rotate()旋轉畫布
  4. 使用 Canvas.drawRoundRect()繪製指針矩形
  5. 繪製圓點
/**
 * 第四步:繪製指針
 */
private fun drawPointer(canvas: Canvas, centerX: Float, centerY: Float) {
    // 獲取當前時間:時分秒
    val calendar = Calendar.getInstance()
    val hour = calendar[Calendar.HOUR]
    val minute = calendar[Calendar.MINUTE]
    val second = calendar[Calendar.SECOND]
    // 計算時分秒轉過的角度
    val angleHour = (hour + minute.toFloat() / 60) * 360 / 12
    val angleMinute = (minute + second.toFloat() / 60) * 360 / 60
    val angleSecond = second * 360 / 60

    // 繪製時針
    canvas.save()
    // 旋轉到時針的角度
    canvas.rotate(angleHour, centerX, centerY)
    val rectHour = RectF(
        centerX - mHourPointWidth / 2,
        centerY - radius / 2,
        centerX + mHourPointWidth / 2,
        centerY + radius / 6
    )
    // 設置時針畫筆屬性
    mPaint.color = Color.BLUE
    mPaint.style = Paint.Style.STROKE
    mPaint.strokeWidth = mHourPointWidth
    canvas.drawRoundRect(rectHour, mPointRange, mPointRange, mPaint)
    canvas.restore()

    // 繪製分針
    canvas.save()
    // 旋轉到分針的角度
    canvas.rotate(angleMinute, centerX, centerY)
    val rectMinute = RectF(
        centerX - mMinutePointWidth / 2,
        centerY - radius * 3.5f / 5,
        centerX + mMinutePointWidth / 2,
        centerY + radius / 6
    )
    // 設置分針畫筆屬性
    mPaint.color = Color.BLACK
    mPaint.strokeWidth = mMinutePointWidth
    canvas.drawRoundRect(rectMinute, mPointRange, mPointRange, mPaint)
    canvas.restore()

    // 繪製秒針
    canvas.save()
    // 旋轉到分針的角度
    canvas.rotate(angleSecond.toFloat(), centerX, centerY)
    val rectSecond = RectF(
        centerX - mSecondPointWidth / 2,
        centerY - radius + 10,
        centerX + mSecondPointWidth / 2,
        centerY + radius / 6
    )
    // 設置秒針畫筆屬性
    mPaint.strokeWidth = mSecondPointWidth
    mPaint.color = Color.RED
    canvas.drawRoundRect(rectSecond, mPointRange, mPointRange, mPaint)
    canvas.restore()

    // 繪製原點
    mPaint.style = Paint.Style.FILL
    canvas.drawCircle(
        (centerX).toFloat(),
        (centerY).toFloat(), mSecondPointWidth * 4, mPaint
    )
}

以上就已經完成錶盤指針繪製工做,效果圖以下:
Android 自定義View篇(六)實現時鐘錶盤效果
在這裏插入圖片描述

五、onMeasure 測量 View 寬高

MeasureSpecMode 有三個屬性:EXACTLY、AT_MOST、UNSPECIFIED
根據 View 在 xml 設置寬高類型不一樣會觸發相應的方法,這裏對 onMeasure()測量不作具體講解,若是對自定義 View 測量寬高 onMeasure()方法不熟悉的,請本身補習。onMeasure()方法也是自定義 View 學習過程當中很是重要的一個環節!

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    // 根據錶盤半徑 + 錶盤圓環寬度計算View寬度和高度
    mWidth = onMeasuredSpec(widthMeasureSpec) + (mCircleWidth * 2).toInt()
    mHeight = onMeasuredSpec(heightMeasureSpec) + (mCircleWidth * 2).toInt()
    // 計算最終View錶盤的半徑
    radius = (mWidth - mCircleWidth * 2) / 2
    // 設置View最終寬高
    setMeasuredDimension(mWidth, mHeight)
}
private fun onMeasuredSpec(measureSpec: Int): Int {
    // 臨時值,用於計算後返回
    var specViewSize = 0
    val specMode = MeasureSpec.getMode(measureSpec)
    val specSize = MeasureSpec.getSize(measureSpec)

    when (specMode) {
        MeasureSpec.EXACTLY -> {
            // 通常爲固定尺寸或者match_parent
            specViewSize = specSize
        }
        MeasureSpec.AT_MOST -> {
            // 計算半徑以寬高最小值爲準
            specViewSize = min((radius * 2).toInt(), specSize)
        }
    }
    return specViewSize
}

到此爲止,若是想讓錶盤和指針動起來,還須要在 onDraw()方法裏調用postInvalidateDelayed(1000)方法,或者啓動一個線程,使每秒鐘刷新重繪一次佈局,這樣就可讓指針實時更新時間。最後貼上 onDraw()方法裏面的代碼:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // 設置圓心X軸位置
    val centerX: Float = (mWidth / 2).toFloat()
    // 設置圓心Y軸位置
    val centerY: Float = (mHeight / 2).toFloat()

    /** 第一步:繪製最外層的圓 **/
    drawClock(canvas, centerX, centerY)
    /** 第二步:錶盤一共60個刻度,1到12點整數屬於長刻度,其他屬於短刻度 **/
    drawClockScale(canvas, centerX, centerY)
    /** 第三步:繪製錶盤數字 **/
    drawClockNumber(canvas, centerX, centerY)
    /** 第四步:繪製指針 **/
    drawPointer(canvas, centerX, centerY)
    // 刷新錶盤指針
    postInvalidateDelayed(1000)
}

總結

自定 view 在 Android 開發過程當中應用極其普遍,爲了更好的掌握,建議從自定義 View 繪製流程、Canvas、Paint、Path、onLayout()、onMeasure()、onDraw() 系統化學習,而後上手多作練習,這樣勢必會對自定義 View 有很好的提高!
看完文章,是否是以爲這個效果其實也很簡單,案例中相關屬性可使用自定義屬性,由於練習案例,因此這裏在 View 中直接寫死了,感興趣的朋友可使用自定義屬性嘗試實現。這個案例基本上將自定義 View 中 Canvas、Paint 常見的 API 方法以及 onMeasure()測量方法都應用到了,算是一個上手練習自定義 View 的好案例,但願看完文章對你學習有所幫助!
前文說過,保證每一個自定義 View 初學者都能看懂,由於每行代碼都會添加註釋,若是沒看懂的留言打我!!!
Android 自定義View篇(六)實現時鐘錶盤效果
Android 自定義View篇(一)View繪製流程
Android 自定義View篇(二)Canvas詳解
Android 自定義View篇(三)Paint詳解
Android 自定義View篇(四)自定義屬性詳解
Android 自定義View篇(五)事件分發機制
Android 自定義View篇(六)實現時鐘錶盤效果

相關文章
相關標籤/搜索