android自定義view實(shí)現(xiàn)鐘表效果
本文實(shí)例為大家分享了android view實(shí)現(xiàn)鐘表的具體代碼,供大家參考,具體內(nèi)容如下
先看效果圖:
自定義view大家肯定已經(jīng)不陌生了,所以直接今天直接步入正題:如何利用canvas去繪制出一個(gè)鐘表
當(dāng)然繪制之前我們必須進(jìn)行測(cè)量(重寫onMeasure),根據(jù)自己的規(guī)則去測(cè)量,這暫時(shí)是將控件限制為一個(gè)正方形。
首先我們先把鐘表分解,看它由哪幾部分組成。如上圖:鐘表包括表盤(刻度)和表針還有文字構(gòu)成。
分清結(jié)構(gòu)之后我們?cè)倜鞔_canvas需要畫什么,表盤的構(gòu)成其實(shí)就是外層一個(gè)圓,然后上面是有規(guī)律的線段,表針就是三個(gè)長(zhǎng)短不一的線段,再加上12個(gè)鐘點(diǎn)文字。這樣一分析是不是發(fā)現(xiàn)調(diào)用canvas的drawCircle、drawLine和drawText就可以完成鐘表的繪制了。
既然明確了我們繪制所需要的方法,那么就開始重頭戲了,告訴canvas在哪繪制這些零件。
最外層的圓是最簡(jiǎn)單的,我們只需要以控件的中心為圓心,控件的寬度一半為半徑畫一個(gè)圓就可以了。
接下來就是難點(diǎn)一了,這些刻度怎么辦呢,其實(shí)我們不難發(fā)現(xiàn)其中的規(guī)律,每個(gè)刻度之間的弧度是一樣的,那這樣我們是不是可以通過旋轉(zhuǎn)畫布就可以實(shí)現(xiàn)這些刻度的繪制呢,答案是肯定的。
難點(diǎn)二,文字又該如何繪制,難道也通過旋轉(zhuǎn)畫布嗎,但是你想一下,假如通過旋轉(zhuǎn)畫布去繪制文字,那有些文字可是會(huì)顛倒的,這并不是我們想要的結(jié)果,那該怎么辦,這時(shí)候我們只能通過數(shù)學(xué)計(jì)算老老實(shí)實(shí)的計(jì)算每個(gè)文字的起始坐標(biāo),這些坐標(biāo)并沒有想象中的復(fù)雜,我們可以根據(jù)中心點(diǎn)的位置和偏移角度(當(dāng)然還需要考慮文字的寬度)算出。
難點(diǎn)三,繪制表針,其實(shí)文字繪制出來,那么同樣可以根據(jù)中心點(diǎn)和偏移角度算出表針的起始坐標(biāo)和結(jié)束坐標(biāo)
表心就是一個(gè)實(shí)體的圓,這個(gè)就簡(jiǎn)單了。
好像還沒說時(shí)分秒是怎么確定的,這當(dāng)然是通過系統(tǒng)時(shí)間獲取的了。說到這里似乎一個(gè)靜態(tài)鐘表已經(jīng)繪制出來了,接下來讓它動(dòng)起來就可以了。在這我們啟動(dòng)一個(gè)線程,讓它隔一秒鐘進(jìn)行一次重繪即可。
下面我直接貼一下代碼把,代碼是用kotlin實(shí)現(xiàn)(這不是重點(diǎ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 //默認(rèn)寬度
}
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)
//獲取當(dāng)前時(shí)間
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) {
//繪制文字的起始坐標(biāo)
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())
}
//恢復(fù)原來狀態(tài)
canvas?.restore()
}
/**
* 繪制秒針
*/
private fun drawSecond(canvas: Canvas?, paint: Paint?) {
//秒針長(zhǎng)半徑 (表針會(huì)穿過表心 所以需要根據(jù)兩個(gè)半徑計(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?) {
//半徑比秒針小一點(diǎn)
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)
}
/**
* 繪制時(shí)針
*/
private fun drawHour(canvas: Canvas?, paint: Paint?) {
//半徑比秒針小一點(diǎn)
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)
}
/**
* 進(jìn)行測(cè)量
*/
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()
//啟動(dòng)線程 刷新界面
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()
}
}
在這送上幾點(diǎn)建議,1.盡量不要再ondraw里面創(chuàng)建對(duì)象,因?yàn)関iew可能會(huì)多次重繪,每次都創(chuàng)建新的對(duì)象會(huì)造成不必要的內(nèi)存浪費(fèi)
2.onmeasure方法會(huì)調(diào)用多次,請(qǐng)保證你的邏輯覆蓋性,否則可能會(huì)出現(xiàn)沒有按照你的預(yù)期得到寬高
3.線程的謹(jǐn)慎使用
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:AndroidQ(10)分區(qū)存儲(chǔ)完美適配方法
欄 目:Android
本文標(biāo)題:android自定義view實(shí)現(xiàn)鐘表效果
本文地址:http://www.jygsgssxh.com/a1/Android/8984.html
您可能感興趣的文章
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方法詳解
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)登錄
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代碼
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10Emoji表情在Android JNI中的兼容性問題詳解
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10android開發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解


閱讀排行
- 1C語言 while語句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問題詳
隨機(jī)閱讀
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文


