自定义view之绘制模拟时钟
之前在自定义view之写一个带删除按钮的Edittext中简单介绍了如何继承Edittext实现点击区域删除全部文字。
在自定义view之可伸缩的圆弧与扇形中介绍了如何制作带有动画效果的圆弧和扇形图。
模拟时钟实现思路
前边两篇都是入门文章,这篇算是一个基础文章,我们来制作一个模拟时钟,与手机上的时间保持同步运转。首先看一下我自己的做的效果图(很low的一个界面):
可以看到在53分钟结束到54分钟开始的时候,时针分针秒针基本保持与时间同步(实际在绘制过程中由于三角函数的double类型转float类型,以及π的位数,还是会有误差)。
时钟实现的难点在于如何绘制指针的重点坐标并时刻刷新保持与手机同步。此处我采用了取巧的方式,后边会详细介绍。
初始化工作
首先同样需要继承view类作为父类,并实现几个构造函数。
private static final float threeSqure = 1.7320508075689F;private static final float PIE = 3.1415926535898F;public MyClock(Context context) {super(context);init();}public MyClock(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public MyClock(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}复制代码
在init函数中定义了一系列的画笔等工具。
private void init() {bgPaint = new Paint();bgPaint.setStyle(Paint.Style.STROKE);bgPaint.setColor(Color.BLACK);bgPaint.setStrokeWidth(10);bgPaint.setAntiAlias(true);boldNumPaint = new Paint();boldNumPaint.setStyle(Paint.Style.STROKE);boldNumPaint.setColor(Color.BLACK);boldNumPaint.setStrokeWidth(20);boldNumPaint.setAntiAlias(true);thinNumPaint = new Paint();thinNumPaint.setStyle(Paint.Style.STROKE);thinNumPaint.setColor(Color.BLACK);thinNumPaint.setStrokeWidth(10);thinNumPaint.setAntiAlias(true);secondPaint = new Paint();secondPaint.setStyle(Paint.Style.FILL);secondPaint.setColor(Color.GREEN);secondPaint.setAntiAlias(true);secondPaint.setStrokeWidth(10);centerPaint = new Paint();centerPaint.setStyle(Paint.Style.FILL);centerPaint.setColor(Color.BLACK);centerPaint.setAntiAlias(true);innerPaint = new Paint();innerPaint.setStyle(Paint.Style.FILL);innerPaint.setColor(Color.WHITE);innerPaint.setAntiAlias(true);}复制代码
此处指明一些需要注意的地方就是setstyle一定要设置好,FILL是填充,画出来的是实心的,STROKE是描边,画出来的是空心的。其实也可以用一个画笔然后再每次绘制的时候不断重新设置也可以。
画笔中定义width等参数的时候一般是以px为单位,但是更多的时候我们需要以dp为单位,此处可以稍微注意一下,px与dp的转换。
我们知道,要想获得view的实际尺寸要在onsizechange方法中。在onsizechange方法中我们获取了一些在绘图中会用到的尺寸,实际需要的是一个正放形,所以取了区域中上边的一个方形。
protected void onSizeChanged(int w, int h, int oldw, int oldh) {Log.d(TAG, "onSizeChanged");super.onSizeChanged(w, h, oldw, oldh);this.width = Math.min(w, h);this.height = Math.min(w, h);inCircle = new RectF(55, 55, width - 55, height - 55);outCircle = new RectF(5, 5, width - 5, height - 5);radius = (float) ((width - 110) / 2);innerCircle = new RectF(100, 100, width - 100, height - 100);}复制代码
暴露接口
为了让时钟启动,我们需要自定一个外部可以访问的方法来启动时钟:startClock()。
public void startClock() {myTime = new MyTime();Log.d(TAG, myTime.toString());animatorSecond = ValueAnimator.ofFloat(setSecond(myTime), setSecond(myTime) + 2 * 60 * PIE);animatorMinute = ValueAnimator.ofFloat(setMinute(myTime), setMinute(myTime) + 2 * PIE);animatorHour = ValueAnimator.ofFloat(setHour(myTime), setHour(myTime) + 6 * PIE / 180);animatorSecond.removeAllUpdateListeners();animatorMinute.removeAllUpdateListeners();animatorHour.removeAllUpdateListeners();animatorSecond.setDuration(60 * 1000 * 60);animatorMinute.setDuration(60 * 1000 * 60);animatorHour.setDuration(60 * 1000 * 60);animatorSecond.setInterpolator(new LinearInterpolator());animatorMinute.setInterpolator(new LinearInterpolator());animatorHour.setInterpolator(new LinearInterpolator());animatorSecond.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {passSecondArc = (float) animation.getAnimatedValue();postInvalidate();}});animatorMinute.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {passMinuteArc = (float) animation.getAnimatedValue();}});animatorHour.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {passHourArc = (float) animation.getAnimatedValue();}});AnimatorSet set = new AnimatorSet();set.removeAllListeners();set.playTogether(animatorSecond, animatorMinute, animatorHour);set.start();}复制代码
这个方法中首先定义了一个内部类MyTime,用来获取当前时间的时分秒。内部类的核心方法:
public MyTime() {Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));year = calendar.get(Calendar.YEAR);month = calendar.get(Calendar.MONTH);day = calendar.get(Calendar.DAY_OF_MONTH);hour = calendar.get(Calendar.HOUR_OF_DAY);min = calendar.get(Calendar.MINUTE);sec = calendar.get(Calendar.SECOND);}复制代码
计算时间的起始位置
我们定义了三个动画时间引擎,这三个引擎分别负责时针、分针、秒针的运动。设置三个指针的起始值要根据我们获取的当前时间来定义:
private float setSecond(MyTime myTime) {float passSecond = myTime.getSec();return 6 * passSecond / 180 * PIE + PIE / 2;}复制代码
此处要复习一下三角函数的相关知识。
我们的起始位置是在屏幕的最左边高度的中点,但是这个位置并不是我们需要的12点起始位置,为了公式计算方便,我们需要的是将他顺时针旋转90度以后的位置,也就是屏幕宽度的中点高度的起点位置。
- 秒针的计算:
一周是360度,也就是2π,1分钟60s,每秒经过的角度就是6度。
首先获取当前的秒的时间,计算经过的秒数,然后换算成弧度,最后加上π的一半,就是我们要展现出来的弧度。此处使用的单位是float单精度浮点型。这就是我们设置的时间引擎的起始值。
这个demo中我设定的时间是1个小时的动画,所以一个小时秒针会经过60圈,最后的中点值就设为了起始值+60*2π。
- 分针的计算
private float setMinute(MyTime myTime) {float passMinute = myTime.getMin() * 6 + myTime.getSec() / 10;return passMinute / 180 * PIE + PIE / 2;}复制代码
一小时是60分钟,所以每经过1分钟要经过6度。为了使程序看起来更准确,我们还要计算经过的秒数,而不至于在一开始就在一个不准确的位置。60秒钟经过6度,则每秒钟经过0.1度,粗略计算出经过的分钟角度是myTime.getMin() * 6 + myTime.getSec() / 10,然后换算成弧度并加上π/2。
- 时针的计算
时针计算与分针计算相似,只是注意一小时走过的角度是30度,所以在换算的时候要注意经过的小时和经过的分钟的角度关系。
然后我们为每个引擎加上了监听方法,这个方法会将在每一个时刻的具体位置返回给我们。注意默认的插值器是低速-高度-低速这样的速度数值变化,明显不是我们要的结果,我们要用线性插值器来获得一个匀速的变化。然后启动动画引擎集合。
绘制
在onDraw方法中我们要绘制所有的一切图形。
drawBackGround(canvas);draw0369(canvas);drawHourGap(canvas);drawInnerCircle(canvas);drawM(canvas);drawS(canvas);drawH(canvas);drawCenter(canvas);复制代码
- drawBackGround(canvas)
private void drawBackGround(Canvas canvas) {bgPaint.setColor(Color.WHITE);bgPaint.setStyle(Paint.Style.FILL);canvas.drawRect(0, 0, width, height, bgPaint);bgPaint.setColor(Color.BLACK);bgPaint.setStyle(Paint.Style.STROKE);canvas.drawArc(outCircle, 0, 360, false, bgPaint);canvas.drawArc(inCircle, 0, 360, false, bgPaint);}复制代码
这个是绘制背景圆,效果是这样的
在上一篇文章中已经介绍了如何使用paint来画扇形和弧线,这里就不介绍了,只要设置起点和重点为0-360即可。
draw0369(canvas)
private void draw0369(Canvas canvas) {canvas.drawLine(width / 2, 55, width / 2, height - 55, boldNumPaint);canvas.drawLine(55, height / 2, width - 55, height / 2, boldNumPaint);}复制代码
这个是绘制3点6点9点12点的位置。我们使用line加粗实现的。
完成后效果如下drawHourGap(canvas);
private void drawHourGap(Canvas canvas) {canvas.drawLine(radius * (1 - threeSqure / 2) + 55,(height - radius) / 2,width - 55 - radius * (1 - threeSqure / 2),(height + radius) / 2, thinNumPaint);canvas.drawLine(radius * (1 - threeSqure / 2) + 55,(height + radius) / 2,width - 55 - radius * (1 - threeSqure / 2),(height - radius) / 2, thinNumPaint);canvas.drawLine(radius / 2 + 55,height / 2 - radius * threeSqure / 2,width - 55 - radius / 2,height / 2 + radius * threeSqure / 2, thinNumPaint);canvas.drawLine(radius / 2 + 55,height / 2 + radius * threeSqure / 2,width - 55 - radius / 2,height / 2 - radius * threeSqure / 2, thinNumPaint); }复制代码
这个是绘制其他小时的,用的是细的line实现。注意角度换算关系,因为要计算时间的角度,所以三角函数关系还是要把这些基本的计算掌握。效果如下:
4.drawInnerCircle(canvas)
这个和1是一样的,只是要绘制实心将中间的线挡住,所以paint要设置为FILL。
效果如下:
5.drawM(canvas) drawS(canvas) drawH(canvas);
private void drawM(Canvas canvas) {secondPaint.setColor(Color.BLUE);secondPaint.setStrokeWidth(20);canvas.drawLine(width / 2, height / 2,height / 2 - (radius - 80) * (float) Math.cos(passMinuteArc),width / 2 - (radius - 80) * (float) Math.sin(passMinuteArc),secondPaint);}复制代码
主要看一下这个计算过程,起始坐标是我们的中心点位置,而终点的x轴是中心点减去经过角度的余弦值,同样可计算得到y。
- drawCenter(canvas);
最后我们做一个改在所有指针中心上的盖子。
最终效果:
下一节我们将介绍如何绘制一个日历,并介绍为何暴露出来的方法startTime会在所有的重写方法之前执行。
自定义view之绘制模拟时钟相关推荐
- Android自定义view实现动态模拟时钟。
效果: 一,自定义一个view的大致步骤: 1.自定义View,首先定义一个MyView类继承View类. 2. 重写View的两个构造器.View是包含四个构造器的,我们必须重写MyWidgetVi ...
- HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序
这期是 HenCoder 自定义绘制的第 1-5 期:绘制顺序 之前的内容在这里: HenCoder Android 开发进阶 自定义 View 1-1 绘制基础 HenCoder Android ...
- Android之自定义View以及画一个时钟
https://www.2cto.com/kf/201509/443112.html 概述: 当Android自带的View满足不了开发者时,自定义View就发挥了很好的作用. 建立一个自定义View ...
- android canvas_Android自定义View之绘制虚线
开发中遇到需要画虚线,我们首先就会想到ShapeDrawable,在布局中加一个View,并给它添加一个虚线背景,是挺简单的. <?xml version="1.0" enc ...
- 自定义View实现米老鼠时钟
先看效果图,米老鼠的两个手分别指向时钟和分钟,然后米老鼠的脚在一秒执行一次动画操作. 分析完之后就先实现gif的播放, 如果实现gif的播放,就想着使用SurfaceView实现每格一秒循环一次贞动画 ...
- 精通Android自定义View(十一)绘制篇Canvas分析之裁剪
clipRect(int left, int top, int right, int bottom) 这个方法作用就是裁切一个矩形出来,但是图形不还是在canvas上面的,所以本质上还是裁切的can ...
- 精通Android自定义View(十)绘制篇Canvas分析之绘制Path
1 Path常用方法简析 Path在2D绘图中是一个很重要的类. Path在这里可以绘制基本的图形,也可以绘制其他复杂的图形. 2 常用API解析与示例 2.1 xxxTo方法 Path类中提供了一套 ...
- 精通Android自定义View(九)绘制篇Canvas分析之绘制图片
绘制图片分为:绘制矢量图(drawPicture)和 绘制位图(drawBitmap) 1 drawBitmap 1.1 基本的绘制图片方法 //Bitmap:图片对象,left:偏移左边的位置,to ...
- 精通Android自定义View(八)绘制篇Canvas分析之绘制文本
1 简述 绘制文字分为三种应用场景: 情况1:指定文本开始的位置 即指定文本基线位置 基线x默认在字符串左侧,基线y默认在字符串下方 情况2:指定每个文字的位置 情况3:指定路径,并根据路径绘制文字 ...
最新文章
- 独家 | 攀登数据科学家和数据工程师之间的隔墙
- 吴恩达deeplearning.ai五项课程完整笔记了解一下?
- 傻子的成长日记,编程路上	2016-04-07
- none 和 host 网络的适用场景 - 每天5分钟玩转 Docker 容器技术(31)
- 【QuotationTool】主要数据结构
- cf1108E2 线段树类似扫描线
- 使用分层网络模型的两个优点是什么_从零开始学网络|搞懂OSI参考模型和TCP/IP分层模型,看这篇文章就够了...
- python程序-调试Python程序代码的几种方法总结
- jinja2中的过滤器
- 【转】书上的字快速弄到电脑上
- 点击reset按钮失效 input 和 button元素 作为提交、重置、按钮功用的区别
- OV7670 FIFO 30W摄像头介绍(三) --- STM32F103驱动OV7670代码介绍
- 面试后说hold什么意思_为什么面试完,总是让你回去等通知?
- Java教程:如何使用Jib插件容器化SpringBoot应用?
- codeforces 722C Destroying Array
- 2022 lineCTF WEB复现WriteUp
- MyBatis高频面试题
- 计算机毕业设计ssm文学阅读平台
- 阿里云个人账户如何变更为企业用户
- 成长杂志成长杂志社成长编辑部2022年第7期目录