鸿蒙-canvas-画时钟
文章目录
- 前言
- 准备
- 分析
- 组成部分
- 数值计算
- 过程
- 开始
- 第一步 画圆环
- 第二步 画格子
- 第三步 画数字
- 第四、五步 画指针&定时更新
- 最后一步
前言
你在 Android 上能画出来的东西,在鸿蒙上画不出来?
画个时钟嘛,有啥难的?
你行你上!
给钱就上!
给钱?早说嘛,来来来,现在就画
准备
画时钟需要画哪些元素?
圆圈、直线,没了,就这些,临时看一下canvas 相关的 api,这不都有么?直接画。
看看需要用的方法
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void
看下参数含义
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
| x | number | 是 | 0 | 弧线圆心的x坐标值。 |
| y | number | 是 | 0 | 弧线圆心的y坐标值。 |
| radius | number | 是 | 0 | 弧线的圆半径。 |
| startAngle | number | 是 | 0 | 弧线的起始弧度。 |
| endAngle | number | 是 | 0 | 弧线的终止弧度。 |
| counterclockwise | boolean | 否 | false | 是否逆时针绘制圆弧。 |
弧度制,一圈是 2π,这个需要注意一下,还有endAngle,是终止弧度,而不是需要画多少弧度,浅浅的尝试。
struct ClockViewTest {private settings: RenderingContextSettings = new RenderingContextSettings(true)private canvasRendering: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)build() {Canvas(this.canvasRendering).width("100%").height("100%").onReady(() => {let width = this.canvasRendering.widthlet height = this.canvasRendering.heightlet centerX = width / 2let centerY = height / 2//取长宽中小的一个做直径let maxRadius = Math.min(width, height) / 2//设置线的粗细this.canvasRendering.lineWidth = 4this.canvasRendering.arc(centerX, centerY, maxRadius-50, 0, 1, false)//设置线的颜色this.canvasRendering.strokeStyle = "#ff0000"this.canvasRendering.stroke()this.canvasRendering.beginPath()this.canvasRendering.arc(centerX, centerY, maxRadius - 30, 0, 1, true)//设置线的颜色this.canvasRendering.strokeStyle = "#00ff00"this.canvasRendering.stroke()})}
}
效果是这样的:

画直线就不用多说了,开干~~
分析
组成部分
指针是需要根据时间变化来转动的,表盘画好一次就不需要重绘了,偷个懒,搞两个 canvas 摞起来,底层画表盘,上层画指针,时间变了只重画上层指针就行了。
数值计算
简单的三角函数,但要注意是弧度制,数值别搞错了。
另外需要注意的是画布左上角坐标是(0,0),右下角坐标为(width,height)。
过程
- 先画一大一小两个圆圈组成一个圆环。
- 再划线把圆环均分 60 份,每 5 条线加粗一下。
- 再把圆周分成 12 份,对应位置画上1~12 数字。
- 获取当前时间,计算出指针位置,划线。
- 定时更新指针位置。
- 结束。
开始
第一步 画圆环
Canvas(this.canvasRendering).width("100%").height("100%").onReady(() => {let width = this.canvasRendering.widthlet height = this.canvasRendering.heightlet centerX = width / 2let centerY = height / 2//取长宽中小的一个做直径let maxRadius = Math.min(centerX, centerY)//留一些外边距let outerCircleRadius = maxRadius - 20this.canvasRendering.strokeStyle = "#1b91e0"this.canvasRendering.lineWidth = 2//最中间的小圈圈this.canvasRendering.arc(centerX, centerY, 10, 0, Math.PI * 2, false)this.canvasRendering.stroke()//画内圈this.canvasRendering.beginPath()let innerCircleRadius = outerCircleRadius -20this.canvasRendering.arc(centerX, centerY, innerCircleRadius, 0, Math.PI * 2, false)this.canvasRendering.stroke()//画外圈this.canvasRendering.beginPath()this.canvasRendering.arc(centerX, centerY, outerCircleRadius, 0, Math.PI * 2, false);this.canvasRendering.stroke()})
效果图

看着还行,颜色和粗细大家自己调。
第二步 画格子
//画 60 个格子,5 的倍数则线条粗一些
let perMinuteDegree = Math.PI * 2 / 60
for (let i = 1;i <= 60; i++) {//结束坐标,也就是在外圆上的点let endX = centerX + Math.sin(i * perMinuteDegree) * outerCircleRadiuslet endY = centerY + Math.cos(i * perMinuteDegree + Math.PI) * outerCircleRadius//起始坐标,也就是在内圆上的点let startX = centerX + Math.sin(i * perMinuteDegree) * innerCircleRadiuslet startY = centerY + Math.cos(i * perMinuteDegree + Math.PI) * innerCircleRadiusthis.canvasRendering.strokeStyle = "#000000"let path2D = new Path2D()path2D.moveTo(startX, startY)path2D.lineTo(endX, endY)if (i % 5 == 0) {this.canvasRendering.lineWidth = 6} else {this.canvasRendering.lineWidth = 2}this.canvasRendering.stroke(path2D)
}
效果图

马马虎虎,不太好看。
这里需要注意一下,画布是以垂直向下为 Y 轴的正方向,计算时加了 Math.PI 弧度纠正一下
第三步 画数字
//画 1~12 数字圆形分布
this.canvasRendering.font = "40px"
let perNumberDegree = Math.PI * 2 / 12
let numberRadius = outerCircleRadius - 40for (let i = 1;i <= 12; i++) {let x = centerX + Math.sin(i * perNumberDegree) * numberRadiuslet y = centerY + Math.cos(i * perNumberDegree + Math.PI) * numberRadiuslet text: string = i + ""this.canvasRendering.fillStyle = "#000000"let textMetrics: TextMetrics = this.canvasRendering.measureText(text)//填充文字时,传入的坐标是文字的左下角坐标this.canvasRendering.fillText(text, x-textMetrics.width/2 , y+textMetrics.height/2)//把下面这两行注释掉就没有小方块了this.canvasRendering.fillStyle = "#aaff6134"this.canvasRendering.fillRect(x,y,textMetrics.width,textMetrics.height)}
效果图

图上的方块是为了对比画文字和画方块的坐标区别展示出来的:填充文字时传入的坐标是文字左下角的坐标,而画方块时是传入的方块左上角坐标,这里注意一下就好了,代码中测量了一下文字宽高,粗暴的做了一下纠偏。
第四、五步 画指针&定时更新
上面也说要把指针画在另外一个 canvas 上,减少一下绘制时的内容,没做对比,也不知道有没有作用。
准备另外个画布,把两个画布用 Stack 包一下。
Canvas(this.canvasRenderingClock).width("100%").height("100%").onReady(() => {this.timer = setInterval(function(){let date: Date = new Date()this.minute = date.getMinutes()this.hour = date.getHours()this.second = date.getSeconds()this.draw()}.bind(this), 500)
})
这里需要把第一块代码中的 innerCircleRadius 变量提到外部,作为类成员两个画布共用一下,主要是计算指针终点坐标用的。centerX 和 centerY 无所谓,只要两个画布对齐了,用哪个都行,这里还是提到了外部,用的第一块画布的。
private draw() {清空一下画布this.canvasRenderingClock.clearRect(0, 0, this.centerX * 2, this.centerY * 2)//画秒针//计算秒针的角度let secondDegree = Math.PI * 2 / 60 * this.secondlet secondStartX = this.centerXlet secondStartY = this.centerYlet secondEndX = this.centerX + Math.sin(secondDegree) * this.innerCircleRadiuslet secondEndY = this.centerY + Math.cos(secondDegree + Math.PI) * this.innerCircleRadiuslet secondPath = new Path2D()secondPath.moveTo(secondStartX, secondStartY)secondPath.lineTo(secondEndX, secondEndY)this.canvasRenderingClock.lineWidth = 2this.canvasRenderingClock.stroke(secondPath)//画分针 颜色弄点透明度,要不然重合的时候看不清楚//秒针走一圈,分针走一格,其实可以忽略不计let minuteDegree = Math.PI * 2 / 60 * this.minutelet minuteStartX = this.centerXlet minuteStartY = this.centerYlet minuteEndX = this.centerX + Math.sin(minuteDegree) * (this.innerCircleRadius / 5 * 4)let minuteEndY = this.centerY + Math.cos(minuteDegree + Math.PI) * (this.innerCircleRadius / 5 * 4)let minutePath = new Path2D()minutePath.moveTo(minuteStartX, minuteStartY)minutePath.lineTo(minuteEndX, minuteEndY)this.canvasRenderingClock.strokeStyle = "#aa1b91e0"this.canvasRenderingClock.lineWidth = 4this.canvasRenderingClock.stroke(minutePath)//画时针//分针走一圈,时针走 5 小格let hourDegree = Math.PI * 2 / 12 * this.hour + this.minute / 60 * Math.PI * 2 / 12let hourStartX = this.centerXlet hourStartY = this.centerYlet hourEndX = this.centerX + Math.sin(hourDegree) * (this.innerCircleRadius / 4 * 3)let hourEndY = this.centerY + Math.cos(hourDegree + Math.PI) * (this.innerCircleRadius / 4 * 3)let hourPath = new Path2D()hourPath.moveTo(hourStartX, hourStartY)hourPath.lineTo(hourEndX, hourEndY)this.canvasRenderingClock.lineWidth = 6this.canvasRenderingClock.strokeStyle = "#aa39d167"this.canvasRenderingClock.stroke(hourPath)
}
计算指针角度的时候也偷懒了,时针只考虑了当前分钟数,没有考虑秒数,实际差不多,先这样吧。
最后一步
效果图

就先这样吧,勉勉强强,可以自己调调颜色,调调样式,或者搞一些图片来代替这些元素也行。
源码在这里 https://github.com/huangyuanlove/HelloArkUI/blob/main/entry/src/main/ets/pages/playground/AlarmClockPage.ets ,
https://gitee.com/huangyuan/HelloArkUI/blob/main/entry/src/main/ets/pages/playground/AlarmClockPage.ets
仓库地址:https://github.com/huangyuanlove/HelloArkUI
https://gitee.com/huangyuan/HelloArkUI
以上
相关文章:
鸿蒙-canvas-画时钟
文章目录 前言准备分析组成部分数值计算过程 开始第一步 画圆环第二步 画格子第三步 画数字第四、五步 画指针&定时更新最后一步 前言 你在 Android 上能画出来的东西,在鸿蒙上画不出来? 画个时钟嘛,有啥难的? 你行你上&…...
【AI实践】阿里百炼文本对话Agent安卓版搭建
环境:安卓手机运行环境;WinsurfAI编程工具;阿里百炼提前创建Agent应用; 耗时:2小时; 1,新建安卓项目 完成文本输入,并将输入的文字显示出来。 2,安装SDK 参考文档 安…...
算法很美笔记(Java)——动态规划
解重叠子问题(当前解用到了以前求过的解) 形式:记忆型递归或递推(dp) 动态规划本质是递推,核心是找到状态转移的方式,也就是填excel表时的逻辑(填的方式),而…...
Jest单元测试
由于格式和图片解析问题,可前往 阅读原文 前端自动化测试在提高代码质量、减少错误、提高团队协作和加速交付流程方面发挥着重要作用。它是现代软件开发中不可或缺的一部分,可以帮助开发团队构建可靠、高质量的应用程序 单元测试(Unit Testi…...
《Stable Diffusion绘画完全指南:从入门到精通的Prompt设计艺术》-配套代码示例
第一章:模型加载与基础生成 1.1 基础模型加载 from diffusers import StableDiffusionPipeline import torch# 加载SD 1.5基础模型(FP32精度) pipe StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5",…...
OnlyOffice:前端编辑器与后端API实现高效办公
OnlyOffice:前端编辑器与后端API实现高效办公 一、OnlyOffice概述二、前端编辑器:高效、灵活且易用1. 完善的编辑功能2. 实时协作支持3. 自动保存与版本管理4. 高度自定义的界面 三、后端API:管理文档、用户与权限1. 轻松集成与定制2. 实时协…...
springboot多实例部署时,@Scheduled注释的方法重复执行
问题:springboot多实例部署时,Scheduled注释的方法重复执行 在 Spring Boot 中要实现 Redis 的SET NX EX命令,可以借助 Spring Data Redis 来完成。SET NX EX命令用于在键不存在时设置键值对,并同时设置过期时间。 <dependen…...
coco格式
COCO(Common Objects in Context)格式是一种广泛用于图像识别和分割任务的数据格式,尤其是在目标检测、语义分割等任务中。COCO格式的核心包括以下几个部分: images: 包含图像的基本信息(如文件名、大小、ID等&#x…...
骶骨神经
骶骨肿瘤手术后遗症是什么_39健康网_癌症 [健康之路]匠心仁术(七) 勇闯禁区 骶骨肿瘤切除术...
Nacos学习(二)——继承Feign与Config中心
目录 一、集成Feign (一)基础用法 1.添加openfeign依赖 2. 开启openFeign注解扫描 3.创建ProviderService接口 4.修改ConsumerController (二)OpenFeign日志配置 (三)参数传递 1.参数传递的问题 2.参数传递的方式 2.1URL路径传参 2.2URL上拼接参数 2.3body传参 …...
计算机网络安全之一:网络安全概述
1.1 网络安全的内涵 随着计算机和网络技术的迅猛发展和广泛普及,越来越多的企业将经营的各种业务建立在Internet/Intranet环境中。于是,支持E-mail、文件共享、即时消息传送的消息和协作服务器成为当今商业社会中的极重要的IT基础设施。然而࿰…...
未来SLAM的研究方向和热点
SLAM(Simultaneous Localization and Mapping)是同时定位与地图构建的缩写,指的是机器人或设备在一个未知环境中一边进行自我定位,一边构建出环境的地图。SLAM广泛应用于机器人、自动驾驶、无人机等领域,涉及多个研究方…...
DuodooBMS源码解读之 purchase_change 模块
采购变更模块用户使用手册 一、模块概述 本扩展模块主要用于处理采购变更相关业务,包括采购变更单的创建、展示以及将采购变更信息导出为 Excel 文件等功能。以下将详细介绍该模块的具体使用方法。 二、模块功能及使用方法 (一)采购变更单…...
uniapp中引入Vant Weapp的保姆级教学(包含错误处理)
废话不多说,直接上方法,网上的教学好多都是错误的 1.安装vant weapp 在Hbuilder的终端,输入以下代码 npm install vant/weapp -S --production 2.新建wxcomponents文件夹 在项目的跟目录新建一个“wxcomponents’文件夹,与app.…...
Effective C++ 读书笔记(十二)
条款三十四:区分接口继承和实现继承 public继承由两部分组成:函数接口继承和函数实现继承。这两者的差异很像函数声明和函数定义之间的差异。 作为类的设计者,我们有时希望派生类只继承成员函数的接口(也就是函数声明࿰…...
【卡梅德生物】构建噬菌体文库与噬菌体展示文库构建服务新探索
在生命科学与生物技术快速发展的当下,抗体文库构建、构建噬菌体文库以及噬菌体展示文库构建服务在生物医药研发领域中占据着举足轻重的地位。它们不仅是基础研究的重要工具,更是推动抗体药物开发、疾病诊断技术进步的关键力量。 构建噬菌体文库是整个技…...
【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter19-表单脚本
十九、表单脚本 表单脚本 JavaScript 较早的一个用途是承担一部分服务器端表单处理的责任。虽然 Web 和 JavaScript 都已经发展了很多年,但 Web 表单的变化不是很大。由于不能直接使用表单解决问题,因此开发者不得不使用JavaScript 既做表单验证…...
C++STL容器之map
1.介绍 map是 C 标准模板库(STL)中的一个关联容器,用于存储键值对(key-value pairs)。map中的元素是按照键(key)进行排序的,并且每个键在容器中是唯一的。map通常基于红黑树…...
基于Nanopi duo2的WiFi智能摄像头
1.固件包烧录 https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo2/zh#.E8.BF.9E.E6.8E.A5WiFi 固件包链接以及烧录工具都在上面链接中 烧录过程 使用读卡器将SD卡插入到电脑,然后打开烧录工具 2.通过串口工具连接板子使其连接WiFi 对应的串口工具,就是这个HyperT…...
Java 内存区域详解
1 常见面试题 1.1 基本问题 介绍下Java内存区域(运行时数据区)Java对象的创建过程(五步,建议能够默写出来并且要知道每一步虚拟机做了什么)对象的访问定位的两种方式(句柄和直接指针两种方式)…...
MyBatis框架详解与核心配置解读
目录 前言 一、MyBatis框架概述 1.1 什么是MyBatis 1.2 MyBatis的优点 二、MyBatis的使用入门与案例 2.1 MyBatis核心配置文件(mybatis-config.xml) 2.2 XML映射文件(UserMapper.xml) 三、MyBatis的常用注解及其用法 3.1…...
Windows 快速搭建C++开发环境,安装C++、CMake、QT、Visual Studio、Setup Factory
安装C 简介 Windows 版的 GCC 有三个选择: CygwinMinGWmingw-w64 Cygwin、MinGW 和 mingw-w64 都是在 Windows 操作系统上运行的工具集,用于在 Windows 环境下进行开发和编译。 Cygwin 是一个在 Windows 上运行的开源项目,旨在提供类Uni…...
GO系列-IO 文件操作
os io 判断文件是否存在 func fileExist(filePath string) (bool, error) {_, err : os.Stat(filePath)if err nil {return true, nil}if os.IsNotExist(err) {return false, nil}return false, &CheckFileExistError{filePath} } 读取文件内容 func readFileContext(…...
Unity Excel导表工具转Lua文件
思路介绍 借助EPPlus读取Excel文件中的配置数据,根据指定的不同类型的数据配置规则来解析成对应的代码文本,将解析出的字符串内容写入到XXX.lua.txt文件中即可 EPPlus常用API //命名空间 using OfficeOpenXml;//Excel文件路径 var fileExcel new File…...
Helix——Figure 02发布通用人形机器人控制的VLA:一组神经网络权重下的快与慢双系统,让两个机器人协作干活
前言 过去一周,我花了很大的心思、力气,把deepseek的GRPO、MLA算法的代码解析通透,比如GRPO与PPO的详细对比,再比如MLA中,图片 公式 代码的一一对应 2.20日晚,无意中刷到figure 02发布Helix的一个演示视频…...
汽车自动驾驶辅助L2++是什么?
自动驾驶辅助级别有哪些? 依照SAE(SAE International,Society of Automotive Engineers国际自动机工程师学会)的标准,大致划分为6级(L0-L5): L0人工驾驶:即没有驾驶辅助…...
进程的介绍--进程状态/切换
1.冯 • 诺依曼体系结构 1.1 体系结构 冯•诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。数学家冯•诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成&#x…...
goby(蓝队红队版)扫描工具扫描使用时候报错解决方法
1.Goby 是一款开源的网络安全扫描工具,主要用于漏洞扫描、资产发现和信息收集。它旨在帮助安全研究人员、渗透测试人员和红队成员自动化和简化网络漏洞扫描过程。Goby 提供了多种功能,能够在大量的目标中高效地识别出潜在的安全漏洞。 2.今天在官网下载…...
Word文档中插入的图片不能完整显示
在在Word文档中插入图片,只显示图片最下面的一小部分。 将“固定值”更改为“单倍行距”...
模电知识点总结(6)
1.选取频率高于1000Hz的信号时,可选用高通滤波器;抑制50Hz的交流干扰时,可选用带阻滤波器如果希望抑制500Hz以下的信号,可选用高通滤波器。 2.有用信号频率高于1000Hz,可选用高通滤波器;希望抑制50Hz的交流…...
