1# 时钟开发
2## 场景介绍
3常见的时钟呈现方式有两种,一种是表盘方式,一种是数字方式。用户可根据个人喜好在两种形式间进行切换。本例即为大家讲解如何开发上述两种钟表样式,以供参考。
4## 效果呈现
5本例最终效果如下:
6
7![](figures/clock.gif)
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)