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

news2024/11/24 0:59:38

项目目录

  • 一、项目概述
  • 二、开发环境
  • 三、详细设计
    • 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在布局中超出预期的范围,因此在实际应用中,可能需要对这种情况进行额外的处理。

  //显示的尺寸,和使用时传入的宽高相关,因为整体为圆形
    @Override
    protected 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);
     //获取在布局当中设置的自定义属性,设置给view
     TypedArray 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方法中进行绘制
    @Override
    protected 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() {
        @Override
        public 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实现五子棋小游戏

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1640082.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习之基于Matlab BP神经网络烟叶成熟度分类

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 烟叶的成熟度是评估烟叶品质的重要指标之一&#xff0c;它直接影响着烟叶的口感、香气和理化特性。传…

jupyter notebook切换conda虚拟环境

首先&#xff0c;切换到某个虚拟环境&#xff0c;本人切换到了d2l环境&#xff1a; (d2l) C:\Users\10129>pip install ipykernel然后&#xff0c;如代码所示安装ipykernel包 最后&#xff0c;按下述代码执行&#xff1a; (d2l) C:\Users\10129>python -m ipykernel i…

软件工程全过程性文档(软件全套文档整理)

软件项目相关全套精华资料包获取方式①&#xff1a;进主页。 获取方式②&#xff1a;本文末个人名片直接获取。 在软件开发的全过程中&#xff0c;文档是记录项目进展、决策、设计和测试结果的重要工具。以下是一个简要的软件全过程性文档梳理清单&#xff1a; 需求分析阶段…

Ex1-C6油气化工防爆轮式巡检机器人

Ex1系列防爆轮式巡检机器人整机采用防爆设计&#xff0c;防爆等级为Exd II CT4 Gb。机器人通过无轨3D形态导航技术&#xff0c;结合360度防爆云台和无线防爆充电桩&#xff0c;实现整套防爆标准&#xff0c;可广泛应用于石油、燃气、化工、冶金等II类爆炸环境中&#xff0c;代替…

【Qt QML】QLibrary加载共享库中的类

QLibrary是一个用于加载动态链接库&#xff08;或称为共享库&#xff09;的类。它提供了一种独立于平台的方式来访问库中的功能。 在QLibrary中&#xff0c;可以通过构造函数或setFileName()方法设置要加载的库文件名。当加载库文件时&#xff0c;QLibrary会搜索所有平台特定的…

VmWare 虚拟机没有网络解决办法

由于最近需要&#xff0c;装了个VM虚拟机&#xff0c;但是突然发现本机有网络&#xff0c;虚拟机却没有网络&#xff0c;更换了虚拟机的网络设置&#xff0c;都尝试过了 都不管用&#xff0c; 最后尝试了这种方法完美解决 还原网络默认设置 首先还原虚拟网络编辑器设置 启动V…

Re69:读论文 LaMDA: Language Models for Dialog Applications

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称&#xff1a;LaMDA: Language Models for Dialog Applications ArXiv网址&#xff1a;https://arxiv.org/abs/2201.08239 本文介绍谷歌提出的对话大模型LaMDA&#xff0c;主要关注对各项指标&#x…

使用 MediaMTX 和 FFmpeg 推拉 RTSP 流媒体

实时流传输协议 RTSP&#xff08;Real-Time Streaming Protocol&#xff09;是 TCP/IP 协议体系中的一个应用层协议&#xff0c;由哥伦比亚大学、网景和 RealNetworks 公司提交的 IETF RFC 标准。该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。RTSP 在体系…

idea常用知识点随记

idea常用知识点随记 1. 打开idea隐藏的commit窗口2. idea中拉取Git分支代码3. idea提示代码报错&#xff0c;项目编译没有报错4. idea中实体类自动生成序列号5. idea隐藏当前分支未commit代码6. idea拉取新建分支的方法 1. 打开idea隐藏的commit窗口 idea左上角File→Settings…

Linux的软件包管理器-yum

文章目录 软件包的概念yum源的配置的原因yum的使用查看软件包安装软件卸载软件 软件包的概念 软件包(SoftWare Package)是指具有特定的功能&#xff0c;用来完成特定任务的一个程序或一组程序。可分为应用软件包和系统软件包两大类 在Linux系统中&#xff0c;下载安装软件的方式…

设计模式: 模板模式

目录 一&#xff0c;模板模式 二&#xff0c;特点 三&#xff0c;组成部分 四&#xff0c;实现步骤 五&#xff0c;案例 一&#xff0c;模板模式 模板模式&#xff08;Template Pattern&#xff09;是一种行为型设计模式&#xff0c;它在超类中定义了一个算法的骨架&#…

React Native支持Tailwind CSS 语法

React Native支持Tailwind CSS 语法 一、前沿背景 回想下我们平时按照官方的规范进行书写样式是什么样&#xff1f; 是像下面这样&#xff1a; const MyComponent () > {return (<View><Text style{{ fontSize: 20 }}>开发者演示专用</Text></View…

每日一题(力扣213):打家劫舍2--dp+分治

与打家劫舍1不同的是它最后一个和第一个会相邻&#xff0c;事实上&#xff0c;从结果思考&#xff0c;最后只会有三种&#xff1a;1 第一家不被抢 最后一家被抢 2 第一家被抢 最后一家不被抢 3 第一和最后一家都不被抢 。那么&#xff0c;根据打家劫舍1中的算法 我们能算出在i…

【linux】进程间通信(匿名管道)

对于本篇文章我们采取三段论&#xff1a;是什么 为什么 怎么办。 目录 进程间为什么要通信&#xff1f;进程间如何通信&#xff1f;进程间怎么通信&#xff1f;匿名管道&#xff1a;匿名管道原理&#xff1a;代码示例&#xff1a;匿名管道的情况与特征&#xff1a; 进程间为什…

Debian 12 tomcat 9 catalina 日志信息 中文显示乱码

目录 问题现象 解决办法&#xff1a; 1、设定Debian locale 2、设定catalina.sh utf8字符集 问题现象 Debian 12 linux操作系统中&#xff0c;tomcat 9 catalina 启动日志输出 中文乱码 解决办法&#xff1a; 1、设定Debian locale 先确保系统本身就支持中文的 Debian …

【数据结构】算法的效率(时间复杂度和空间复杂度)

目录 一.算法的效率 二.时间复杂度 1.概念 2.大O的渐进表示法 3.常见时间复杂度计算举例 三.空间复杂度 四.常见复杂度对比 五. 复杂度的oj练习 1.消失的数字 2.轮转数字&#xff1a; 一.算法的效率 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空…

uniapp 异步加载级联选择器(Cascader,data-picke)

目录 Props 事件方法 inputChange事件回调参数说明&#xff1a; completeChange事件回调参数说明&#xff1a; temList 属性Object参数说明 defaultItemList 属性Object参数说明 在template中使用 由于uniapp uni-ui的data-picke 不支持异步作者自己写了一个 插件市场下…

Deep Learning Part Seven基于RNN生成文本--24.5.2

不存在什么完美的文章&#xff0c;就好像没有完美的绝望。 ——村上春树《且听风吟》 本章所学的内容 0.引子 本章主要利用LSTM实现几个有趣的应用&#xff1a; 先剧透一下&#xff1a;是AI聊天软件&#xff08;现在做的ChatGPT&#xff08;聊天神器&#xff0c;水论文高手…

Latex小技巧:将图索引、表索引加到目录;调整公式段前段后间距

LaTex将图索引、表索引加到目录中 LaTex中通过\listoffigures命令生成图索引&#xff1b;通过listoftables命令生成表索引。但是图索引和表索引默认不出现在目录中。 为了将图索引、表索引加到目录中&#xff0c;使用\addcontentsline{}{}{}命令&#xff1a; \tableofconten…

【STM32+HAL】SDIO+DMA模式读写SD卡

一、准备工作 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 二、所用工具 1、芯片&#xff1a; STM32F407ZGT6 2、IDE&#xff1a; MDK-Keil软件 3、库文件&#xff1a;STM32F4xxHAL库 三、实现功能 实现用SDIODMA读写S…