Android 自定義View是高級進階不可或缺的內容,平常工做中,常常會遇到產品、UI設計出花裏胡哨的界面。當系統自帶的控件不能知足開發需求時,就只能本身動手擼一個效果。git
本文就帶自定義View初學者手動擼一個效果,經過自定義View實現鐘錶功能,每行代碼都有註釋,保證易懂,看不懂你留言打我!!!github
實現以上效果,主要分爲四個步驟:web
外層錶盤就是一個空心圓,只要獲取圓的x、y軸位置、圓的半徑,使用Canvas.drawCircle()方法便可完成。canvas
/** * 繪製錶盤 */ 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) } 複製代碼
繪製思路分析微信
看到效果圖上密密麻麻刻度線後,先不要着急上手,屢清楚繪製思路。繪製刻度線必定要結合Canvas幾何變換思路完成,千萬不要侷限於效果圖的表面(若是對Canvas相關API不熟悉的朋友,建議先了解下)。編輯器
聽完以上分析,是否以爲繪製刻度線很簡單,只要在60個刻度遍歷判斷長短,便可輕鬆出效果。ide
/** * 繪製錶盤刻度 */ 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 ) } } } 複製代碼
以上代碼就完成了繪製刻度線的效果,下面插個題外話,第一次嘗試在繪製刻度線的時候,錶盤數字一併完成,後來發現數字以下圖所示: 佈局
// 測量繪製數字
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 ) 複製代碼
熱心網友指導我繪製數字新方案,真的是高手如雲阿。post
首先將座標位置(0,0)設置到圓心位置,這步是在繪製外層圓的時候,已經設置了。這樣的好處是後期減小不少計算的步驟,新方案已經在代碼中更改!學習
canvas.translate(centerX, centerY)
複製代碼
主要是經過canvas幾何變換方式,先將圓點平移到12點鐘位置,而後逆時針旋轉數字對應的角度,而後開始繪製數字文本。這樣的話,繪製數字文本就和繪製刻度線能夠一併完成,使得代碼清晰不少。 須要注意的是,記得在使用幾何變換先後分別調用canvas.restore()和canvas.restore()方法。
其中相關座標計算方式:
一、平移 y 軸距離 = - 半徑 + 刻度線長度 + 刻度與文本間距 + 文本高度 / 2
(由於座標原點在圓心,須要平移到12點鐘位置,因此半徑爲負數)
二、旋轉角度 = - 6 * 數字大小
三、文本 x 軸距離 = 文本寬度 / 2 ;
四、文本 y 軸距離 = 文本高度 / 2 ;
附上繪製刻度線和文本的完整代碼:
/** * 繪製錶盤刻度線和數字文本 */ 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) } } } 複製代碼
指針繪製具體分如下步驟:
/** * 第四步:繪製指針 */ 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 ) } 複製代碼
以上就已經完成錶盤指針繪製工做,效果圖以下:
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初學者都能看懂,由於每行代碼都會添加註釋,若是沒看懂的留言打我!!!
- 歡迎加入微信交流羣,添加微信:jaynm888,拉你進羣