android中怎么通过自定义view实现钟表效果
今天就跟大家聊聊有关android中怎么通过自定义view实现钟表效果,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
成都创新互联公司客户idc服务中心,提供四川雅安服务器托管、成都服务器、成都主机托管、成都双线服务器等业务的一站式服务。通过各地的服务中心,我们向成都用户提供优质廉价的产品以及开放、透明、稳定、高性价比的服务,资深网络工程师在机房提供7*24小时标准级技术保障。
先看效果图:
自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表
当然绘制之前我们必须进行测量(重写onMeasure),根据自己的规则去测量,这暂时是将控件限制为一个正方形。
首先我们先把钟表分解,看它由哪几部分组成。如上图:钟表包括表盘(刻度)和表针还有文字构成。
分清结构之后我们再明确canvas需要画什么,表盘的构成其实就是外层一个圆,然后上面是有规律的线段,表针就是三个长短不一的线段,再加上12个钟点文字。这样一分析是不是发现调用canvas的drawCircle、drawLine和drawText就可以完成钟表的绘制了。
既然明确了我们绘制所需要的方法,那么就开始重头戏了,告诉canvas在哪绘制这些零件。
最外层的圆是最简单的,我们只需要以控件的中心为圆心,控件的宽度一半为半径画一个圆就可以了。
接下来就是难点一了,这些刻度怎么办呢,其实我们不难发现其中的规律,每个刻度之间的弧度是一样的,那这样我们是不是可以通过旋转画布就可以实现这些刻度的绘制呢,答案是肯定的。
难点二,文字又该如何绘制,难道也通过旋转画布吗,但是你想一下,假如通过旋转画布去绘制文字,那有些文字可是会颠倒的,这并不是我们想要的结果,那该怎么办,这时候我们只能通过数学计算老老实实的计算每个文字的起始坐标,这些坐标并没有想象中的复杂,我们可以根据中心点的位置和偏移角度(当然还需要考虑文字的宽度)算出。
难点三,绘制表针,其实文字绘制出来,那么同样可以根据中心点和偏移角度算出表针的起始坐标和结束坐标表心就是一个实体的圆,这个就简单了。
好像还没说时分秒是怎么确定的,这当然是通过系统时间获取的了。说到这里似乎一个静态钟表已经绘制出来了,接下来让它动起来就可以了。在这我们启动一个线程,让它隔一秒钟进行一次重绘即可。
下面我直接贴一下代码把,代码是用kotlin实现(这不是重点)的
package com.example.commonui.widgetimport android.annotation.SuppressLintimport android.content.Contextimport android.graphics.Canvasimport android.graphics.Colorimport android.graphics.Paintimport android.os.Handlerimport android.os.Messageimport android.util.AttributeSetimport android.view.Viewimport 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() }}
看完上述内容,你们对android中怎么通过自定义view实现钟表效果有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。
文章题目:android中怎么通过自定义view实现钟表效果
本文地址:http://scyanting.com/article/ieoesg.html