android自定義view實現(xiàn)鐘表效果
本文實例為大家分享了android view實現(xiàn)鐘表的具體代碼,供大家參考,具體內(nèi)容如下
先看效果圖:
自定義view大家肯定已經(jīng)不陌生了,所以直接今天直接步入正題:如何利用canvas去繪制出一個鐘表
當然繪制之前我們必須進行測量(重寫onMeasure),根據(jù)自己的規(guī)則去測量,這暫時是將控件限制為一個正方形。
首先我們先把鐘表分解,看它由哪幾部分組成。如上圖:鐘表包括表盤(刻度)和表針還有文字構(gòu)成。
分清結(jié)構(gòu)之后我們再明確canvas需要畫什么,表盤的構(gòu)成其實就是外層一個圓,然后上面是有規(guī)律的線段,表針就是三個長短不一的線段,再加上12個鐘點文字。這樣一分析是不是發(fā)現(xiàn)調(diào)用canvas的drawCircle、drawLine和drawText就可以完成鐘表的繪制了。
既然明確了我們繪制所需要的方法,那么就開始重頭戲了,告訴canvas在哪繪制這些零件。
最外層的圓是最簡單的,我們只需要以控件的中心為圓心,控件的寬度一半為半徑畫一個圓就可以了。
接下來就是難點一了,這些刻度怎么辦呢,其實我們不難發(fā)現(xiàn)其中的規(guī)律,每個刻度之間的弧度是一樣的,那這樣我們是不是可以通過旋轉(zhuǎn)畫布就可以實現(xiàn)這些刻度的繪制呢,答案是肯定的。
難點二,文字又該如何繪制,難道也通過旋轉(zhuǎn)畫布嗎,但是你想一下,假如通過旋轉(zhuǎn)畫布去繪制文字,那有些文字可是會顛倒的,這并不是我們想要的結(jié)果,那該怎么辦,這時候我們只能通過數(shù)學計算老老實實的計算每個文字的起始坐標,這些坐標并沒有想象中的復雜,我們可以根據(jù)中心點的位置和偏移角度(當然還需要考慮文字的寬度)算出。
難點三,繪制表針,其實文字繪制出來,那么同樣可以根據(jù)中心點和偏移角度算出表針的起始坐標和結(jié)束坐標
表心就是一個實體的圓,這個就簡單了。
好像還沒說時分秒是怎么確定的,這當然是通過系統(tǒng)時間獲取的了。說到這里似乎一個靜態(tài)鐘表已經(jīng)繪制出來了,接下來讓它動起來就可以了。在這我們啟動一個線程,讓它隔一秒鐘進行一次重繪即可。
下面我直接貼一下代碼把,代碼是用kotlin實現(xiàn)(這不是重點)的
package com.example.commonui.widget import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.os.Handler import android.os.Message import android.util.AttributeSet import android.view.View import java.util.* /** * Created by zhang on 2017/12/20. */ class ClockView(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { companion object { private const val DEFAULT_WIDTH = 200 //默認寬度 } private lateinit var mBlackPaint: Paint//黑色畫筆 private lateinit var mRedPaint: Paint //紅色畫筆 private lateinit var mBlackPaint2: Paint//黑色畫筆 private lateinit var mTextPaint: Paint private var hour: Int? = null private var minute: Int? = null private var second: Int? = null private val textArray = arrayOf("12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11") private var refreshThread: Thread? = null private var mHandler = @SuppressLint("HandlerLeak") object : Handler() { override fun handleMessage(msg: Message?) { super.handleMessage(msg) when (msg?.what) { 0 -> { invalidate() } } } } init { initPaints() } /** * 初始化畫筆 */ private fun initPaints() { mBlackPaint = Paint() with(mBlackPaint) { color = Color.BLACK strokeWidth = 5f isAntiAlias = true style = Paint.Style.STROKE } //用于畫表心 mBlackPaint2 = Paint() with(mBlackPaint2) { color = Color.BLACK isAntiAlias = true style = Paint.Style.FILL } mRedPaint = Paint() with(mRedPaint) { color = Color.RED strokeWidth = 5f isAntiAlias = true } mTextPaint = Paint() with(mTextPaint) { color = Color.BLACK textSize = 30f isAntiAlias = true } } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) //獲取當前時間 getCurrentTime() //先畫最外層的圓圈 drawOuterCircle(canvas) //畫刻度 drawScale(canvas) //繪制文字 drawTimeText(canvas) //繪制表針 drawHand(canvas) //繪制表心 drawCenter(canvas) } private fun getCurrentTime() { val calendar = Calendar.getInstance() hour = calendar.get(Calendar.HOUR) minute = calendar.get(Calendar.MINUTE) second = calendar.get(Calendar.SECOND) } private fun drawOuterCircle(canvas: Canvas?) { mBlackPaint.strokeWidth = 5f canvas?.drawCircle(measuredWidth / 2.toFloat(), measuredHeight / 2.toFloat(), (measuredWidth / 2 - 5).toFloat(), mBlackPaint) } private fun drawCenter(canvas: Canvas?) { canvas?.drawCircle(measuredWidth / 2.toFloat(), measuredHeight / 2.toFloat(), 20f, mBlackPaint2) } private fun drawHand(canvas: Canvas?) { drawSecond(canvas, mRedPaint) mBlackPaint.strokeWidth = 10f drawMinute(canvas, mBlackPaint) mBlackPaint.strokeWidth = 15f drawHour(canvas, mBlackPaint) } private fun drawTimeText(canvas: Canvas?) { val textR = (measuredWidth / 2 - 50).toFloat()//文字構(gòu)成的圓的半徑 for (i in 0..11) { //繪制文字的起始坐標 val startX = (measuredWidth / 2 + textR * Math.sin(Math.PI / 6 * i) - mTextPaint.measureText(textArray[i]) / 2).toFloat() val startY = (measuredHeight / 2 - textR * Math.cos(Math.PI / 6 * i) + mTextPaint.measureText(textArray[i]) / 2).toFloat() canvas?.drawText(textArray[i], startX, startY, mTextPaint) } } private fun drawScale(canvas: Canvas?) { var scaleLength: Float? canvas?.save() //0..59代表[0,59] for (i in 0..59) { if (i % 5 == 0) { //大刻度 mBlackPaint.strokeWidth = 5f scaleLength = 20f } else { //小刻度 mBlackPaint.strokeWidth = 3f scaleLength = 10f } canvas?.drawLine(measuredWidth / 2.toFloat(), 5f, measuredWidth / 2.toFloat(), (5 + scaleLength), mBlackPaint) canvas?.rotate(360 / 60.toFloat(), measuredWidth / 2.toFloat(), measuredHeight / 2.toFloat()) } //恢復原來狀態(tài) canvas?.restore() } /** * 繪制秒針 */ private fun drawSecond(canvas: Canvas?, paint: Paint?) { //秒針長半徑 (表針會穿過表心 所以需要根據(jù)兩個半徑計算起始和結(jié)束半徑) val longR = measuredWidth / 2 - 60 val shortR = 60 val startX = (measuredWidth / 2 - shortR * Math.sin(second!!.times(Math.PI / 30))).toFloat() val startY = (measuredWidth / 2 + shortR * Math.cos(second!!.times(Math.PI / 30))).toFloat() val endX = (measuredWidth / 2 + longR * Math.sin(second!!.times(Math.PI / 30))).toFloat() val endY = (measuredWidth / 2 - longR * Math.cos(second!!.times(Math.PI / 30))).toFloat() canvas?.drawLine(startX, startY, endX, endY, paint) } /** * 繪制分針 */ private fun drawMinute(canvas: Canvas?, paint: Paint?) { //半徑比秒針小一點 val longR = measuredWidth / 2 - 90 val shortR = 50 val startX = (measuredWidth / 2 - shortR * Math.sin(minute!!.times(Math.PI / 30))).toFloat() val startY = (measuredWidth / 2 + shortR * Math.cos(minute!!.times(Math.PI / 30))).toFloat() val endX = (measuredWidth / 2 + longR * Math.sin(minute!!.times(Math.PI / 30))).toFloat() val endY = (measuredWidth / 2 - longR * Math.cos(minute!!.times(Math.PI / 30))).toFloat() canvas?.drawLine(startX, startY, endX, endY, paint) } /** * 繪制時針 */ private fun drawHour(canvas: Canvas?, paint: Paint?) { //半徑比秒針小一點 val longR = measuredWidth / 2 - 120 val shortR = 40 val startX = (measuredWidth / 2 - shortR * Math.sin(hour!!.times(Math.PI / 6))).toFloat() val startY = (measuredWidth / 2 + shortR * Math.cos(hour!!.times(Math.PI / 6))).toFloat() val endX = (measuredWidth / 2 + longR * Math.sin(hour!!.times(Math.PI / 6))).toFloat() val endY = (measuredWidth / 2 - longR * Math.cos(hour!!.times(Math.PI / 6))).toFloat() canvas?.drawLine(startX, startY, endX, endY, paint) } /** * 進行測量 */ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) val result = if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { DEFAULT_WIDTH } else { Math.min(widthSpecSize, heightSpecSize) } setMeasuredDimension(result, result) } override fun onAttachedToWindow() { super.onAttachedToWindow() //啟動線程 刷新界面 refreshThread = Thread(Runnable { while (true) { try { Thread.sleep(1000) mHandler.sendEmptyMessage(0) } catch (e: InterruptedException) { break } } }) refreshThread?.start() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() mHandler.removeCallbacksAndMessages(null) //中斷線程 refreshThread?.interrupt() } }
在這送上幾點建議,1.盡量不要再ondraw里面創(chuàng)建對象,因為view可能會多次重繪,每次都創(chuàng)建新的對象會造成不必要的內(nèi)存浪費
2.onmeasure方法會調(diào)用多次,請保證你的邏輯覆蓋性,否則可能會出現(xiàn)沒有按照你的預期得到寬高
3.線程的謹慎使用
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持我們。
上一篇:AndroidQ(10)分區(qū)存儲完美適配方法
欄 目:Android
本文標題:android自定義view實現(xiàn)鐘表效果
本文地址:http://mengdiqiu.com.cn/a1/Android/8984.html
您可能感興趣的文章
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實現(xiàn)雙擊返回鍵退出應用實現(xiàn)方法詳解
- 01-10android實現(xiàn)記住用戶名和密碼以及自動登錄
- 01-10android實現(xiàn)簡單計算器功能
- 01-10Android 友盟第三方登錄與分享的實現(xiàn)代碼
- 01-10C++自定義API函數(shù)實現(xiàn)大數(shù)相乘算法
- 01-10android實現(xiàn)指紋識別功能
- 01-10Emoji表情在Android JNI中的兼容性問題詳解
- 01-10Android實現(xiàn)圓形漸變加載進度條
- 01-10android開發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解


閱讀排行
本欄相關
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實現(xiàn)雙擊返回鍵退出應用實現(xiàn)方
- 01-10android實現(xiàn)簡單計算器功能
- 01-10android實現(xiàn)記住用戶名和密碼以及自動
- 01-10C++自定義API函數(shù)實現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實現(xiàn)代
- 01-10android實現(xiàn)指紋識別功能
- 01-10如何給Flutter界面切換實現(xiàn)點特效
- 01-10Android實現(xiàn)圓形漸變加載進度條
- 01-10Emoji表情在Android JNI中的兼容性問題詳
隨機閱讀
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10C#中split用法實例總結(jié)
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文