前言
你在 Android 上能画出来的东西,在鸿蒙上画不出来?
画个时钟嘛,有啥难的?
你行你上!
给钱就上!
给钱?早说嘛,来来来,现在就画
准备
画时钟需要画哪些元素?
圆圈、直线,没了,就这些,临时看一下canvas 相关的 api,这不都有么?直接画。
看看需要用的方法
1
| 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,是终止弧度,而不是需要画多少弧度,浅浅的尝试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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.width let height = this.canvasRendering.height
let centerX = width / 2 let centerY = height / 2 let maxRadius = Math.min(width, height) / 2
this.canvasRendering.lineWidth = 4
this.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 数字。
- 获取当前时间,计算出指针位置,划线。
- 定时更新指针位置。
- 结束。
开始
第一步 画圆环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| Canvas(this.canvasRendering).width("100%").height("100%") .onReady(() => {
let width = this.canvasRendering.width let height = this.canvasRendering.height
let centerX = width / 2 let centerY = height / 2 let maxRadius = Math.min(centerX, centerY)
let outerCircleRadius = maxRadius - 20 this.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 -20 this.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() })
|
效果图
看着还行,颜色和粗细大家自己调。
第二步 画格子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let perMinuteDegree = Math.PI * 2 / 60 for (let i = 1;i <= 60; i++) { let endX = centerX + Math.sin(i * perMinuteDegree) * outerCircleRadius let endY = centerY + Math.cos(i * perMinuteDegree + Math.PI) * outerCircleRadius let startX = centerX + Math.sin(i * perMinuteDegree) * innerCircleRadius let startY = centerY + Math.cos(i * perMinuteDegree + Math.PI) * innerCircleRadius
this.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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| this.canvasRendering.font = "40px" let perNumberDegree = Math.PI * 2 / 12 let numberRadius = outerCircleRadius - 40
for (let i = 1;i <= 12; i++) { let x = centerX + Math.sin(i * perNumberDegree) * numberRadius let y = centerY + Math.cos(i * perNumberDegree + Math.PI) * numberRadius let 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
包一下。
1 2 3 4 5 6 7 8 9
| 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
无所谓,只要两个画布对齐了,用哪个都行,这里还是提到了外部,用的第一块画布的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| private draw() { 清空一下画布 this.canvasRenderingClock.clearRect(0, 0, this.centerX * 2, this.centerY * 2)
let secondDegree = Math.PI * 2 / 60 * this.second let secondStartX = this.centerX let secondStartY = this.centerY let secondEndX = this.centerX + Math.sin(secondDegree) * this.innerCircleRadius let secondEndY = this.centerY + Math.cos(secondDegree + Math.PI) * this.innerCircleRadius let secondPath = new Path2D() secondPath.moveTo(secondStartX, secondStartY) secondPath.lineTo(secondEndX, secondEndY) this.canvasRenderingClock.lineWidth = 2 this.canvasRenderingClock.stroke(secondPath)
let minuteDegree = Math.PI * 2 / 60 * this.minute let minuteStartX = this.centerX let minuteStartY = this.centerY let 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 = 4 this.canvasRenderingClock.stroke(minutePath)
let hourDegree = Math.PI * 2 / 12 * this.hour + this.minute / 60 * Math.PI * 2 / 12 let hourStartX = this.centerX let hourStartY = this.centerY let 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 = 6 this.canvasRenderingClock.strokeStyle = "#aa39d167" this.canvasRenderingClock.stroke(hourPath) }
|
计算指针角度的时候也偷懒了,时针只考虑了当前分钟数,没有考虑秒数,实际差不多,先这样吧。
最后一步
效果图
就先这样吧,勉勉强强,可以自己调调颜色,调调样式,或者搞一些图片来代替这些元素也行。
源码在这里 github ,
gitee
仓库地址:https://github.com/huangyuanlove/HelloArkUI
https://gitee.com/huangyuan/HelloArkUI
以上