Android Studio实现简单的自定义钟表

2024-05-03 15:04

本文主要是介绍Android Studio实现简单的自定义钟表,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目目录

  • 一、项目概述
  • 二、开发环境
  • 三、详细设计
    • 3.1、尺寸设置
    • 3.2、绘制表盘和指针
    • 3.3、动态效果
  • 四、运行演示
  • 五、总结展望
  • 六、源码获取

一、项目概述

在安卓开发中,当系统自带的View已经无法满足项目需求时,就要自定义View。在Android中是没有与钟表有关的View,因此我们制作一个简单的钟表View,这样就可以在其他项目中进行使用。

自定义钟表具有表盘,表盘上有12个刻度,有时针、分针、秒针,和家里面的石英表样式相同,用于显示时间会比数码表更加有内涵。

二、开发环境

只要是21年之后从Android Studio官网下载的AS,都可以运行该App。因为高版本IDE向下兼容,只需要修改Java环境。

在这里插入图片描述

三、详细设计

3.1、尺寸设置

onMeasure方法被重写用于决定自定义View的最终大小。这个过程考虑了父布局传递过来的宽度和高度的具体规格(spec)。MeasureSpec类提供了一种方式来理解这些规格,包括它们的模式和大小。

模式有三种:

  • UNSPECIFIED:父布局没有限制子View的大小,子View可以选择任何大小。
  • EXACTLY:父布局指定了一个确切的大小,子View应该尽可能地匹配这个大小。
  • AT_MOST:父Layout设定了一个最大值,子View的大小不能超过这个值。

在代码中,首先检查了宽度和高度的规格模式。

  • 如果宽度和高度都是EXACTLY,则取两者中的较小值作为View的大小。
  • 如果只有高度是EXACTLY,则取高度的值作为View的大小。
  • 如果只有宽度是EXACTLY,则取宽度的值作为View的大小。
  • 如果两者都不是EXACTLY,则取一个默认的值400作为View的大小。

最后,调用setMeasuredDimension(int, int)方法来设置View的大小。这个方法接受两个参数:第一个是View的宽度,第二个是View的高度。由于在本例中,View是一个圆形,所以不管宽度还是高度,最终的大小都会被设置为相同的值,从而保证View是完美圆形的。

这种方法确保了View在不同设备和屏幕方向上具有一致的外观和大小,前提是父布局至少为View指定了一个方向上的确切大小。如果宽度和高度都没有具体的规格,那么View将会有一个默认的400px大小。这可能会导致View在布局中超出预期的范围,因此在实际应用中,可能需要对这种情况进行额外的处理。

  //显示的尺寸,和使用时传入的宽高相关,因为整体为圆形@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//获取传入宽高的模式int wmode = MeasureSpec.getMode(widthMeasureSpec);int hmode = MeasureSpec.getMode(heightMeasureSpec);int wsize = MeasureSpec.getSize(widthMeasureSpec);int hsize = MeasureSpec.getSize(heightMeasureSpec);//判断模式,获取最终显示的尺寸int size = 400;if (wmode == MeasureSpec.EXACTLY) {if (hmode == MeasureSpec.EXACTLY) {size = Math.min(wsize, hsize);} else {size = wsize;}} else {if (hmode == MeasureSpec.EXACTLY) {size = hsize;} else {size = 400;}}//将测量好的值设置给宽高setMeasuredDimension(size, size);}

3.2、绘制表盘和指针

重写onDraw方法来绘制时钟的表盘和指针。在绘制之前,先创建一个Paint对象并根据需要设置其样式、颜色、宽度等属性。设置Paint对象的抗锯齿模式为true,这样可以让时钟的数字和指针看起来更加平滑。

     Paint paint = new Paint();//设置抗锯齿paint.setAntiAlias(true);//获取在布局当中设置的自定义属性,设置给viewTypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColokView);int color = typedArray.getColor(R.styleable.ColokView_clockColor, Color.BLACK);//设置画笔的颜色paint.setColor(color);

时钟想要显示当前时间,所以必须获取系统的准确时间,并将其分解为小时、分钟和秒。定义一个getTime方法获取系统时间。首先,创建了一个 Calendar 类的实例,通过调用静态方法 getInstance() 来初始化该实例,这样可以确保 calendar 对象包含了调用该方法时设备上的当前日期和时间。

接下来,使用 calendar 对象来获取当前的时间:

  • hours = calendar.get(Calendar.HOUR); 这行代码获取了当前的小时数,但是请注意,这是基于12小时制的,所以它返回的小时数范围是0(午夜12点)到11(中午12点)。
  • minutes = calendar.get(Calendar.MINUTE); 这行代码获取了当前的分钟数,范围是0到59。
  • seconds = calendar.get(Calendar.SECOND); 这行代码获取了当前的秒数,范围也是0到59。
    //获取当前时间的方法public void getTime() {Calendar calendar = Calendar.getInstance();hours = calendar.get(Calendar.HOUR);minutes = calendar.get(Calendar.MINUTE);seconds = calendar.get(Calendar.SECOND);}

下面讲解onDraw方法的具体实现,它负责在View上绘制表盘和指针。

  1. 首先,覆盖了onDraw方法,这个方法是View类的一部分,用于在View上进行绘制。
  2. 调用super.onDraw(canvas);确保父类的绘制逻辑得到执行。
  3. 设置画笔的风格为空心(STROKE),这样绘制的图形只有边缘有颜色。
  4. 设置View的内边距为20像素。
  5. 绘制外层大圈,设置线条宽度为8像素,以View中心为圆心,以View宽度的一半减去20像素为半径绘制一个圆。
  6. 绘制内层大圆,设置线条宽度为4像素,以View中心为圆心,以View宽度的一半减去30像素为半径绘制一个圆。
  7. 绘制时钟的中心点小圆,设置填充样式为FILL,以View中心为圆心,以10像素为半径绘制一个圆。
  8. 循环12次,绘制时钟的12个刻度。每次循环中:
    • 保存当前的Canvas状态。
    • 使用canvas.rotate()方法根据角度绘制刻度,这里有一个问题,因为每次旋转后都应该绘制新的刻度,但代码中却重复绘制了相同的刻度,这可能是一个错误。
    • 发送一个空消息延迟1秒(通过handler.sendEmptyMessageDelayed(1, 1000);),这部分代码的意图可能是让时钟每秒移动一次,但它被放置在了绘制刻度的循环中,这也是一个逻辑错误。
  9. 绘制时针:
    • 设置画笔宽度为8像素。
    • 保存Canvas状态。
    • 根据当前小时数和分钟数计算出的角度旋转Canvas
    • 绘制时针,从View中心向上40像素处开始到60像素处结束。
    • 恢复Canvas状态。
  10. 绘制分针:
    • 设置画笔宽度为5像素。
    • 保存Canvas状态。
    • 根据当前分钟数计算出的角度旋转Canvas
    • 绘制分针,从View中心向上2/3的高度处开始向下2/3的高度处结束。
    • 恢复Canvas状态。
  11. 绘制秒针与上述分针的逻辑基本相同,只是画笔宽度改为3像素,角度计算改为每秒钟6度。
	//显示的内容就在onDraw方法中进行绘制@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//设置空心paint.setStyle(Paint.Style.STROKE);//设置内边距setPadding(20, 20, 20, 20);//绘制外层大圈paint.setStrokeWidth(8);//设置线条宽度canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - 20, paint);//绘制内层大圆paint.setStrokeWidth(4);canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - 30, paint);//绘制表中间轴心paint.setStyle(Paint.Style.FILL);canvas.drawCircle(getWidth() / 2, getHeight() / 2, 10, paint);//绘制表的刻度12个,通过旋转画布实现for (int i = 1; i <= 12; i++) {//保存画布的状态canvas.save();//旋转到指定的角度canvas.rotate(30 * i, getWidth() / 2, getHeight() / 2);canvas.drawLine(getWidth() / 2, 40, getWidth() / 2, 60, paint);//恢复旋转之前的状态canvas.restore();handler.sendEmptyMessageDelayed(1, 1000);}//绘制时针,1h=30°,1m=0.5°paint.setStrokeWidth(8);canvas.save();//旋转画布,旋转的度数由当前时间决定canvas.rotate(30 * hours + 0.5f * minutes, getWidth() / 2, getHeight() / 2);canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - getHeight() / 5, paint);canvas.restore();//绘制分针,1min=6°paint.setStrokeWidth(5);canvas.save();canvas.rotate(6 * minutes, getWidth() / 2, getHeight() / 2);canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - getHeight() / 4, paint);canvas.restore();//绘制秒针,1s=6°paint.setStrokeWidth(3);canvas.save();canvas.rotate(6 * seconds, getWidth() / 2, getHeight() / 2);canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - getHeight() / 3, paint);canvas.restore();}

3.3、动态效果

为了让指针走起来,我们定义了一个Handler的匿名子类,并覆写了其handleMessage方法。Handler通常用于处理线程间通信,特别是在Android开发中,它常用来处理UI线程(主线程)和后台线程之间的消息传递。

handleMessage方法中,首先检查传入的Message对象的what字段是否等于1。如果是,表示该消息需要被处理:

  1. 调用getTime()方法来获取当前的时间。

  2. 调用invalidate()方法强制重新绘制View。invalidate()方法会告诉系统该View的部分或全部区域已经变得不再有效,需要重绘。调用此方法后,系统将安排onDraw方法在适当的时候被再次调用。

  3. 使用handler.sendEmptyMessageDelayed(1, 1000);来设定一个定时器。这行代码的意思是,它会让当前的消息处理器(handler)在1000毫秒(即1秒)后,再次向自己发送一个what值为1的空Message对象。这样就创建了一个每秒重复执行一次的循环,用于不断更新时钟时间并刷新界面。

通过Handler在Android的主线程上每秒更新并重绘时钟界面,从而模拟了一个动态的时钟效果。

    Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (msg.what == 1) {//重新获取时间getTime();//重新绘制界面invalidate();handler.sendEmptyMessageDelayed(1, 1000);}}};

四、运行演示

在这里插入图片描述

五、总结展望

总的来说,本次自定义钟表项目可以学习到编程技巧,并且加深对Canvas API的理解。大家可以考虑添加一些定制选项,如不同的表盘样式、颜色或字体,以便用户可以根据自己的喜好来个性化他们的时钟;也可以考虑添加一些高级功能,如秒表、闹钟或世界时钟,以扩展应用程序的实用性。简单的项目掌握透了就不简单,期待大家在此基础上制作更多的创新钟表!

六、源码获取

♻️下面两种方式都可以获取源代码
1️⃣ 点击直接下载 Android Studio 自定义钟表
2️⃣关注公众号《 萌新加油站 》,后台回复: 钟表

🚀这有你错过的精彩内容
Android Studio实现文艺阅读App
Android Studio实现志愿者系统
Android Studio实现多功能日记本
Android Studio实现推箱子小游戏
Android Studio实现五子棋小游戏

普劝青年烈士,黄卷名流,发觉悟之心,破色魔之障。芙蓉白面,须知带肉骷髅。美貌红妆,不过蒙衣漏厕。纵对如玉如花之貌,皆存若姊若母之心。未犯淫邪者,宜防失足。曾行恶事者,务劝回头。更祈展转流通,迭相化导。必使在在齐归觉路,人人共出迷津。——《欲海回狂》

这篇关于Android Studio实现简单的自定义钟表的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/957022

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount