服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - android自定义view实现钟表效果

android自定义view实现钟表效果

2022-11-13 14:47_implements Android

这篇文章主要为大家详细介绍了android自定义view实现钟表效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了android view实现钟表的具体代码,供大家参考,具体内容如下

先看效果图:

android自定义view实现钟表效果

自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表

当然绘制之前我们必须进行测量(重写onMeasure),根据自己的规则去测量,这暂时是将控件限制为一个正方形。

首先我们先把钟表分解,看它由哪几部分组成。如上图:钟表包括表盘(刻度)和表针还有文字构成。

分清结构之后我们再明确canvas需要画什么,表盘的构成其实就是外层一个圆,然后上面是有规律的线段,表针就是三个长短不一的线段,再加上12个钟点文字。这样一分析是不是发现调用canvas的drawCircle、drawLine和drawText就可以完成钟表的绘制了。

既然明确了我们绘制所需要的方法,那么就开始重头戏了,告诉canvas在哪绘制这些零件。

最外层的圆是最简单的,我们只需要以控件的中心为圆心,控件的宽度一半为半径画一个圆就可以了。

接下来就是难点一了,这些刻度怎么办呢,其实我们不难发现其中的规律,每个刻度之间的弧度是一样的,那这样我们是不是可以通过旋转画布就可以实现这些刻度的绘制呢,答案是肯定的。

难点二,文字又该如何绘制,难道也通过旋转画布吗,但是你想一下,假如通过旋转画布去绘制文字,那有些文字可是会颠倒的,这并不是我们想要的结果,那该怎么办,这时候我们只能通过数学计算老老实实的计算每个文字的起始坐标,这些坐标并没有想象中的复杂,我们可以根据中心点的位置和偏移角度(当然还需要考虑文字的宽度)算出。

难点三,绘制表针,其实文字绘制出来,那么同样可以根据中心点和偏移角度算出表针的起始坐标和结束坐标
表心就是一个实体的圆,这个就简单了。

好像还没说时分秒是怎么确定的,这当然是通过系统时间获取的了。说到这里似乎一个静态钟表已经绘制出来了,接下来让它动起来就可以了。在这我们启动一个线程,让它隔一秒钟进行一次重绘即可。

下面我直接贴一下代码把,代码是用kotlin实现(这不是重点)的

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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()//文字构成的圆的半径
    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())
    }
    //恢复原来状态
    canvas?.restore()
  }
 
  /**
   * 绘制秒针
   */
  private fun drawSecond(canvas: Canvas?, paint: Paint?) {
    //秒针长半径 (表针会穿过表心 所以需要根据两个半径计算起始和结束半径)
    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里面创建对象,因为view可能会多次重绘,每次都创建新的对象会造成不必要的内存浪费

2.onmeasure方法会调用多次,请保证你的逻辑覆盖性,否则可能会出现没有按照你的预期得到宽高

3.线程的谨慎使用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/u013320868/article/details/78865512

延伸 · 阅读

精彩推荐