1# 时钟开发 2## 场景介绍 3常见的时钟呈现方式有两种,一种是表盘方式,一种是数字方式。用户可根据个人喜好在两种形式间进行切换。本例即为大家讲解如何开发上述两种钟表样式,以供参考。 4## 效果呈现 5本例最终效果如下: 6 7 8 9## 运行环境 10本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发: 11- IDE: DevEco Studio 3.1 Beta2 12- SDK: Ohos_sdk_public 3.2.11.9 (API Version 9 Release) 13## 实现思路 14- 表盘方式的展示:通过Canvas组件提供画布;在画布上,通过CanvasRenderingContext2D对象使用RenderingContext在Canvas组件上进行绘制,绘制表盘上的数字、时针、分针、秒针。表盘上数字的分布使用fillText绘制填充类文本并确定其在画布上位置;表盘上时针的运动通过theta的角度决定时针的移动;分针和秒针同上。 15- 数字时间方式的展示:使用TextClock组件通过文本将系统时间显示在设备上。 16## 开发步骤 17根据上述思路,具体实现步骤如下: 181. 表盘方式:通过CanvasRenderingContext2D对象使用RenderingContext在Canvas组件上进行绘制,绘制表盘上的数字、时针、分针、秒针。 19 首先,创建画布,具体代码如下: 20 ```ts 21 // clock ets 22 clear() { // clear canvas function 23 this.ctx.clearRect(0, 0, 360, 500); 24 } 25 drawScene() { // main drawScene function 绘制场景 26 this.clear(); // clear canvas 27 ... 28 build() { 29 Column({ space: 5 }) { 30 Canvas(this.ctx) 31 .width(360) 32 .height(500) 33 .border({ width: 1, color: '#ffff00'}) 34 .onReady(() => { 35 setInterval(() => { 36 this.drawScene() 37 }, 1000) 38 }) 39 ... 40 } 41 } 42 ``` 43 声明相关变量,具体代码如下: 44 ```ts 45 // clock ets 46 let date = new Date(); 47 let hours = date.getHours(); 48 let minutes = date.getMinutes(); 49 let seconds = date.getSeconds(); 50 hours = hours > 12 ? hours - 12 : hours; 51 let hour = hours + minutes / 60; 52 let minute = minutes + seconds / 60; 53 ``` 54 使用fillText方法绘制表盘数字并确定其位置 55 ```ts 56 // clock ets 57 ... 58 // draw numbers 59 this.ctx.font = '36px Arial'; //文本尺寸 60 this.ctx.fillStyle = '#000'; //指定绘制的填充色 61 this.ctx.textAlign = 'center'; // 文本对齐 62 this.ctx.textBaseline = 'middle'; //文本基线 63 for (let n = 0; n < 12; n++) { 64 let theta = (n - 2) * (Math.PI * 2) / 12; 65 let x = clockRadius * 0.7 * Math.cos(theta); 66 let y = clockRadius * 0.7 * Math.sin(theta); 67 this.ctx.fillText(`${n + 1}`, x, y); // 表盘数字所在的位置 68 ... 69 ``` 70 时针的移动路径,具体代码如下: 71 ```ts 72 // clock ets 73 ... 74 // draw hour 75 this.ctx.save(); //将当前状态放入栈中,保存canvas的全部状态,通常在需要保存绘制状态时调用 76 let theta = (hour - 3) * 2 * Math.PI / 12; 77 this.ctx.rotate(theta); //顺时针旋转 78 this.ctx.beginPath(); //创建一个新的绘制路径 79 this.ctx.moveTo(-15, -5); //绘制时针组件 起始点 80 this.ctx.lineTo(-15, 5); 81 this.ctx.lineTo(clockRadius * 0.3, 1); 82 this.ctx.lineTo(clockRadius * 0.3, -1); //绘制时针组件 终点 83 this.ctx.fillStyle = 'green'; 84 this.ctx.fill(); 85 this.ctx.restore(); //对保存的绘图上下文进行恢复 86 ... 87 ``` 88 分针的移动路径,具体代码如下: 89 ```ts 90 // clock ets 91 ... 92 // draw minute 93 this.ctx.save(); 94 theta = (minute - 15) * 2 * Math.PI / 60; 95 this.ctx.rotate(theta); //顺时针旋转 96 this.ctx.beginPath(); //创建一个新的绘制路径 97 this.ctx.moveTo(-15, -4);//绘制分针组件 起始点 98 this.ctx.lineTo(-15, 4); 99 this.ctx.lineTo(clockRadius * 0.45, 1); 100 this.ctx.lineTo(clockRadius * 0.45, -1);//绘制分针组件 终点 101 this.ctx.fillStyle = 'red'; 102 this.ctx.fill(); 103 this.ctx.restore(); //对保存的绘图上下文进行恢复 104 ... 105 ``` 106 秒针的移动路径,具体代码如下: 107 ```ts 108 // clock ets 109 ... 110 // draw second 111 this.ctx.save(); 112 theta = (seconds - 15) * 2 * Math.PI / 60; 113 this.ctx.rotate(theta); //顺时针旋转 114 this.ctx.beginPath(); //创建一个新的绘制路径 115 this.ctx.moveTo(-15, -3);//绘制秒针组件 起始点 116 this.ctx.lineTo(-15, 3); 117 this.ctx.lineTo(clockRadius * 0.6, 1); 118 this.ctx.lineTo(clockRadius * 0.6, -1);//绘制秒针组件 终点 119 this.ctx.fillStyle = 'black'; 120 this.ctx.fill(); 121 this.ctx.restore(); //对保存的绘图上下文进行恢复 122 ... 123 ``` 1242. 时钟方式的转换:通过Button组件中的onClick事件进行切换页面。 125 从表盘方式往数字方式转换,具体代码如下: 126 ```ts 127 // clock.ets 128 ... 129 Button(){ 130 Text("切换") 131 .fontSize(30) 132 .fontWeight(FontWeight.Regular) 133 } 134 .type(ButtonType.Capsule) 135 .margin({top:20 136 }) 137 .backgroundColor("red") 138 .width('40%') 139 .height('5%') 140 .onClick(()=>{ 141 router.pushUrl({url:'pages/Index1'}) 142 }) 143 ... 144 ``` 145 从数字时间方式往表盘方式转换,具体代码如下: 146 ```ts 147 // TextClock.ets 148 ... 149 Button() { 150 Text("切换") 151 .fontSize(30) 152 .fontWeight(FontWeight.Regular) 153 } 154 .type(ButtonType.Capsule) 155 .margin({ top: 20 156 }) 157 .backgroundColor("red") 158 .width('40%') 159 .height('5%') 160 .onClick(() => { 161 router.back() 162 }) 163 ... 164 ``` 1653. 数字时间方式:使用TextClock组件通过文本将当前系统时间显示在设备上。 166 具体代码如下: 167 ```ts 168 // TextClock.ets 169 import router from '@ohos.router' 170 @Entry 171 @Component 172 struct Second { 173 @State accumulateTime: number = 0 174 // 导入对象 175 controller: TextClockController = new TextClockController() 176 177 build() { 178 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 179 TextClock({ timeZoneOffset: -8, controller: this.controller }) 180 .format('hms') //数字时间格式 181 .onDateChange((value: number) => { 182 this.accumulateTime = value 183 }) 184 .margin(20) 185 .fontSize(30) 186 ... 187 } 188 } 189 } 190 ``` 191## 完整代码 192完整示例代码如下: 193表盘时钟代码页 194```ts 195// clock.ets 196import router from '@ohos.router'; 197const clockRadius = 180; 198 199@Entry 200@Component 201struct Test10 { 202 private settings: RenderingContextSettings = new RenderingContextSettings(true); 203 private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); 204 205 // 绘制函数 206 clear() { 207 this.ctx.clearRect(0, 0, 360, 500); 208 } 209 drawScene() { // 绘制场景 210 this.clear(); // 清空画布 211 // 获取当前时间 212 let date = new Date(); 213 let hours = date.getHours(); 214 let minutes = date.getMinutes(); 215 let seconds = date.getSeconds(); 216 hours = hours > 12 ? hours - 12 : hours; 217 let hour = hours + minutes / 60; 218 let minute = minutes + seconds / 60; 219 this.ctx.save(); 220 this.ctx.translate(360 / 2, 500 / 2); 221 this.ctx.beginPath(); //创建一个新的绘制路径 222 223 // 绘制表盘数字 224 this.ctx.font = '45px Arial'; //文本尺寸 225 this.ctx.fillStyle = '#000'; //指定绘制的填充色 226 this.ctx.textAlign = 'center'; // 文本对齐 227 this.ctx.textBaseline = 'middle'; //文本基线 228 for (let n = 0; n < 12; n++) { 229 let theta = (n - 2) * (Math.PI * 2) / 12; 230 let x = clockRadius * 0.7 * Math.cos(theta); 231 let y = clockRadius * 0.7 * Math.sin(theta); 232 this.ctx.fillText(`${n + 1}`, x, y); // 表盘数字所在的位置 233 } 234 235 // 绘制时针 236 this.ctx.save(); //将当前状态放入栈中,保存canvas的全部状态,通常在需要保存绘制状态时调用 237 let theta = (hour - 3) * 2 * Math.PI / 12; 238 this.ctx.rotate(theta); //顺时针旋转 239 this.ctx.beginPath(); //创建一个新的绘制路径 240 this.ctx.moveTo(-15, -5); //绘制时针组件 起始点 241 this.ctx.lineTo(-15, 5); 242 this.ctx.lineTo(clockRadius * 0.3, 1); 243 this.ctx.lineTo(clockRadius * 0.3, -1); 244 this.ctx.fillStyle = 'green'; 245 this.ctx.fill(); 246 this.ctx.restore(); //对保存的绘图上下文进行恢复 247 248 // 绘制分针 249 this.ctx.save(); 250 theta = (minute - 15) * 2 * Math.PI / 60; 251 this.ctx.rotate(theta); //顺时针旋转 252 this.ctx.beginPath(); //创建一个新的绘制路径 253 this.ctx.moveTo(-15, -4); //绘制分针组件 起始点 254 this.ctx.lineTo(-15, 4); 255 this.ctx.lineTo(clockRadius * 0.45, 1); 256 this.ctx.lineTo(clockRadius * 0.45, -1); 257 this.ctx.fillStyle = 'red'; 258 this.ctx.fill(); 259 this.ctx.restore(); //对保存的绘图上下文进行恢复 260 261 // 绘制秒针 262 this.ctx.save(); 263 theta = (seconds - 15) * 2 * Math.PI / 60; 264 this.ctx.rotate(theta); //顺时针旋转 265 this.ctx.beginPath(); //创建一个新的绘制路径 266 this.ctx.moveTo(-15, -3); //绘制秒针组件 起始点 267 this.ctx.lineTo(-15, 3); 268 this.ctx.lineTo(clockRadius * 0.6, 1); 269 this.ctx.lineTo(clockRadius * 0.6, -1); 270 this.ctx.fillStyle = 'black'; 271 this.ctx.fill(); 272 this.ctx.restore(); //对保存的绘图上下文进行恢复 273 274 this.ctx.restore(); //对保存的绘图上下文进行恢复 275 } 276 277 build() { 278 Column({ space: 5 }) { 279 Canvas(this.ctx) 280 .width(360) 281 .height(500) 282 .onReady(() => { 283 setInterval(() => { 284 this.drawScene() 285 }, 1000) 286 }) 287 Button(){ 288 Text("切换") 289 .fontSize(30) 290 .fontWeight(FontWeight.Regular) 291 } 292 .type(ButtonType.Capsule) 293 .margin({top:20 294 }) 295 .backgroundColor('#E8A027') 296 .width('40%') 297 .height('5%') 298 .onClick(()=>{ 299 router.pushUrl({url:'pages/TextClock'}) 300 }) 301 }.width('100%') 302 .height('100%') 303 .backgroundColor('#A4AE75') 304 } 305} 306``` 307数字时间代码页: 308```ts 309//TextClock.ets 310import router from '@ohos.router' 311@Entry 312@Component 313struct Second { 314 @State accumulateTime: number = 0 315 // 导入对象 316 controller: TextClockController = new TextClockController() 317 318 build() { 319 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 320 321 TextClock({ timeZoneOffset: -8, controller: this.controller }) //timeZoneOffset 时区偏移ian 322 .format('hms') 323 .onDateChange((value: number) => { 324 this.accumulateTime = value 325 }) 326 .margin(20) 327 .fontSize(30) 328 Button() { 329 Text("切换") 330 .fontSize(30) 331 .fontWeight(FontWeight.Regular) 332 } 333 .type(ButtonType.Capsule) 334 .margin({ top: 20 335 }) 336 .backgroundColor('#E8A027') 337 .width('40%') 338 .height('5%') 339 .onClick(() => { 340 router.back() 341 }) 342 } 343 .width('100%') 344 .height('100%') 345 .backgroundColor('#D4C3B3') 346 } 347} 348``` 349## 参考 350[Canvas](../application-dev/reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md) 351 352[CanvasRenderingContext2D对象](../application-dev/reference/apis-arkui/arkui-ts/ts-canvasrenderingcontext2d.md) 353 354[TextClock](../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md)