android自定义view实现钟表效果
本文实例为大家分享了androidview实现钟表的具体代码,供大家参考,具体内容如下
先看效果图:
自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表
当然绘制之前我们必须进行测量(重写onMeasure),根据自己的规则去测量,这暂时是将控件限制为一个正方形。
首先我们先把钟表分解,看它由哪几部分组成。如上图:钟表包括表盘(刻度)和表针还有文字构成。
分清结构之后我们再明确canvas需要画什么,表盘的构成其实就是外层一个圆,然后上面是有规律的线段,表针就是三个长短不一的线段,再加上12个钟点文字。这样一分析是不是发现调用canvas的drawCircle、drawLine和drawText就可以完成钟表的绘制了。
既然明确了我们绘制所需要的方法,那么就开始重头戏了,告诉canvas在哪绘制这些零件。
最外层的圆是最简单的,我们只需要以控件的中心为圆心,控件的宽度一半为半径画一个圆就可以了。
接下来就是难点一了,这些刻度怎么办呢,其实我们不难发现其中的规律,每个刻度之间的弧度是一样的,那这样我们是不是可以通过旋转画布就可以实现这些刻度的绘制呢,答案是肯定的。
难点二,文字又该如何绘制,难道也通过旋转画布吗,但是你想一下,假如通过旋转画布去绘制文字,那有些文字可是会颠倒的,这并不是我们想要的结果,那该怎么办,这时候我们只能通过数学计算老老实实的计算每个文字的起始坐标,这些坐标并没有想象中的复杂,我们可以根据中心点的位置和偏移角度(当然还需要考虑文字的宽度)算出。
难点三,绘制表针,其实文字绘制出来,那么同样可以根据中心点和偏移角度算出表针的起始坐标和结束坐标
表心就是一个实体的圆,这个就简单了。
好像还没说时分秒是怎么确定的,这当然是通过系统时间获取的了。说到这里似乎一个静态钟表已经绘制出来了,接下来让它动起来就可以了。在这我们启动一个线程,让它隔一秒钟进行一次重绘即可。
下面我直接贴一下代码把,代码是用kotlin实现(这不是重点)的
packagecom.example.commonui.widget importandroid.annotation.SuppressLint importandroid.content.Context importandroid.graphics.Canvas importandroid.graphics.Color importandroid.graphics.Paint importandroid.os.Handler importandroid.os.Message importandroid.util.AttributeSet importandroid.view.View importjava.util.* /** *Createdbyzhangon2017/12/20. */ classClockView(context:Context?,attrs:AttributeSet?=null,defStyleAttr:Int=0):View(context,attrs,defStyleAttr){ companionobject{ privateconstvalDEFAULT_WIDTH=200//默认宽度 } privatelateinitvarmBlackPaint:Paint//黑色画笔 privatelateinitvarmRedPaint:Paint//红色画笔 privatelateinitvarmBlackPaint2:Paint//黑色画笔 privatelateinitvarmTextPaint:Paint privatevarhour:Int?=null privatevarminute:Int?=null privatevarsecond:Int?=null privatevaltextArray=arrayOf("12","1","2","3","4","5","6","7","8","9","10","11") privatevarrefreshThread:Thread?=null privatevarmHandler=@SuppressLint("HandlerLeak") object:Handler(){ overridefunhandleMessage(msg:Message?){ super.handleMessage(msg) when(msg?.what){ 0->{ invalidate() } } } } init{ initPaints() } /** *初始化画笔 */ privatefuninitPaints(){ 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 } } overridefunonDraw(canvas:Canvas?){ super.onDraw(canvas) //获取当前时间 getCurrentTime() //先画最外层的圆圈 drawOuterCircle(canvas) //画刻度 drawScale(canvas) //绘制文字 drawTimeText(canvas) //绘制表针 drawHand(canvas) //绘制表心 drawCenter(canvas) } privatefungetCurrentTime(){ valcalendar=Calendar.getInstance() hour=calendar.get(Calendar.HOUR) minute=calendar.get(Calendar.MINUTE) second=calendar.get(Calendar.SECOND) } privatefundrawOuterCircle(canvas:Canvas?){ mBlackPaint.strokeWidth=5f canvas?.drawCircle(measuredWidth/2.toFloat(),measuredHeight/2.toFloat(),(measuredWidth/2-5).toFloat(),mBlackPaint) } privatefundrawCenter(canvas:Canvas?){ canvas?.drawCircle(measuredWidth/2.toFloat(),measuredHeight/2.toFloat(),20f,mBlackPaint2) } privatefundrawHand(canvas:Canvas?){ drawSecond(canvas,mRedPaint) mBlackPaint.strokeWidth=10f drawMinute(canvas,mBlackPaint) mBlackPaint.strokeWidth=15f drawHour(canvas,mBlackPaint) } privatefundrawTimeText(canvas:Canvas?){ valtextR=(measuredWidth/2-50).toFloat()//文字构成的圆的半径 for(iin0..11){ //绘制文字的起始坐标 valstartX=(measuredWidth/2+textR*Math.sin(Math.PI/6*i)-mTextPaint.measureText(textArray[i])/2).toFloat() valstartY=(measuredHeight/2-textR*Math.cos(Math.PI/6*i)+mTextPaint.measureText(textArray[i])/2).toFloat() canvas?.drawText(textArray[i],startX,startY,mTextPaint) } } privatefundrawScale(canvas:Canvas?){ varscaleLength:Float? canvas?.save() //0..59代表[0,59] for(iin0..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() } /** *绘制秒针 */ privatefundrawSecond(canvas:Canvas?,paint:Paint?){ //秒针长半径(表针会穿过表心所以需要根据两个半径计算起始和结束半径) vallongR=measuredWidth/2-60 valshortR=60 valstartX=(measuredWidth/2-shortR*Math.sin(second!!.times(Math.PI/30))).toFloat() valstartY=(measuredWidth/2+shortR*Math.cos(second!!.times(Math.PI/30))).toFloat() valendX=(measuredWidth/2+longR*Math.sin(second!!.times(Math.PI/30))).toFloat() valendY=(measuredWidth/2-longR*Math.cos(second!!.times(Math.PI/30))).toFloat() canvas?.drawLine(startX,startY,endX,endY,paint) } /** *绘制分针 */ privatefundrawMinute(canvas:Canvas?,paint:Paint?){ //半径比秒针小一点 vallongR=measuredWidth/2-90 valshortR=50 valstartX=(measuredWidth/2-shortR*Math.sin(minute!!.times(Math.PI/30))).toFloat() valstartY=(measuredWidth/2+shortR*Math.cos(minute!!.times(Math.PI/30))).toFloat() valendX=(measuredWidth/2+longR*Math.sin(minute!!.times(Math.PI/30))).toFloat() valendY=(measuredWidth/2-longR*Math.cos(minute!!.times(Math.PI/30))).toFloat() canvas?.drawLine(startX,startY,endX,endY,paint) } /** *绘制时针 */ privatefundrawHour(canvas:Canvas?,paint:Paint?){ //半径比秒针小一点 vallongR=measuredWidth/2-120 valshortR=40 valstartX=(measuredWidth/2-shortR*Math.sin(hour!!.times(Math.PI/6))).toFloat() valstartY=(measuredWidth/2+shortR*Math.cos(hour!!.times(Math.PI/6))).toFloat() valendX=(measuredWidth/2+longR*Math.sin(hour!!.times(Math.PI/6))).toFloat() valendY=(measuredWidth/2-longR*Math.cos(hour!!.times(Math.PI/6))).toFloat() canvas?.drawLine(startX,startY,endX,endY,paint) } /** *进行测量 */ overridefunonMeasure(widthMeasureSpec:Int,heightMeasureSpec:Int){ super.onMeasure(widthMeasureSpec,heightMeasureSpec) valwidthSpecMode=MeasureSpec.getMode(widthMeasureSpec) valwidthSpecSize=MeasureSpec.getSize(widthMeasureSpec) valheightSpecMode=MeasureSpec.getMode(heightMeasureSpec) valheightSpecSize=MeasureSpec.getSize(heightMeasureSpec) valresult=if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){ DEFAULT_WIDTH }else{ Math.min(widthSpecSize,heightSpecSize) } setMeasuredDimension(result,result) } overridefunonAttachedToWindow(){ super.onAttachedToWindow() //启动线程刷新界面 refreshThread=Thread(Runnable{ while(true){ try{ Thread.sleep(1000) mHandler.sendEmptyMessage(0) }catch(e:InterruptedException){ break } } }) refreshThread?.start() } overridefunonDetachedFromWindow(){ super.onDetachedFromWindow() mHandler.removeCallbacksAndMessages(null) //中断线程 refreshThread?.interrupt() } }
在这送上几点建议,1.尽量不要再ondraw里面创建对象,因为view可能会多次重绘,每次都创建新的对象会造成不必要的内存浪费
2.onmeasure方法会调用多次,请保证你的逻辑覆盖性,否则可能会出现没有按照你的预期得到宽高
3.线程的谨慎使用
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。