【时间盒子】-【5.绘制闹钟】动态绘制钟表和数字时间

Tips:

  • @Preview装饰器,支持组件可预览;

  • @Component装饰器,自定义组件;

  • Canvas组件的使用;

  • 使用RenderingContext在Canvas组件上绘制图形,请参考官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-canvasrenderingcontext2d-V5

一、自定义闹钟组件

  1. 新建component目录用来存放自定义组件,在其下再新建ArkTS文件,命名为ClockArea.ets。

2.页面主要包括画布组件Canvas,点击画布切换显示时钟表盘或数字时间样式,对其属性设置及布局如下。

@State showClock: boolean = true; // 是否显示时钟
private renderContextSettings: RenderingContextSettings = new RenderingContextSettings(true);
private renderContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.renderContextSettings);
// 画布尺寸
private canvasSize: number = 252;
// 绘制间隔时间
private drawInterval: number = -1;
private clockRadius: number = this.canvasSize / 2 - 2;

build() {
  Column() {
    Canvas(this.renderContext)
      .width(this.canvasSize)
      .aspectRatio(1)
      .onClick(() => {
        this.showClock = !this.showClock;
      })
      .onReady(() => {
        if (this.drawInterval === -1) {
          // 开始绘制
          this.startDrawTask();
        }
      })
  }
}

 3.启动定时任务,每秒绘制一次闹钟,与时间同步秒针动态在走的效果。

/**
 * 启动定时绘制任务
 */
private startDrawTask(): void {
  // console.log("开始绘制");
  this.renderContext.translate(this.canvasSize / 2, this.canvasSize / 2);
  this.drawClockArea()
  this.drawInterval = setInterval(() => {
    this.drawClockArea()
  }, 1000);
}

4.定义绘制闹钟区域的方法drawClockArea

/**
 * 绘制闹钟区域
 */
private drawClockArea(): void {
  // console.log("绘制时区");
  let date = new Date();
  let hour = date.getHours();
  let minute = date.getMinutes();
  let second = date.getSeconds();
  this.renderContext.clearRect(
    -this.canvasSize,
    -this.canvasSize / 2,
    this.canvasSize * 2,
    this.canvasSize
  );
  if (this.showClock) {
    this.drawClockPan();
    this.drawClockHands(
      30 * (hour > 12 ? hour - 12 : hour)
      + minute / 12 * 6,
      BaseConstant.HOUR_HAND_IMAGE_URL);
    this.drawClockHands(minute * 6, BaseConstant.MINUTE_HAND_IMAGE_URL);
    this.drawClockHands(second * 6, BaseConstant.SECOND_HAND_IMAGE_URL);
  } else {
    // 回显数字时间
    this.drawTimeHHMMSS(hour, minute, second);
  }
}

5.定义绘制闹钟的表盘的方法drawClockPan,其实就是一张表盘时刻的背景图。

/**
 * 绘制表盘
 */
private drawClockPan(): void {
  let imgWidth = this.clockRadius * 2;
  let secondImg = new ImageBitmap(BaseConstant.CLOCK_PAN_IMAGE_URL);
  this.renderContext.beginPath();
  this.renderContext.drawImage(
    secondImg,
    -this.clockRadius,
    -this.clockRadius,
    imgWidth, imgWidth);
  this.renderContext.restore();
}

6.定义绘制表针的方法drawClockHands,包括时针、分针和秒针,每个针对应一个图片。

/**
 * 绘制表针:时针、分针、秒针
 */
private drawClockHands(degree: number, handImgRes: string): void {
  let imgWidth = 10;
  let handImg = new ImageBitmap(handImgRes);
  let theta = (degree + 180) * Math.PI / 180;
  this.renderContext.save();
  this.renderContext.rotate(theta);
  this.renderContext.beginPath();
  this.renderContext.drawImage(
    handImg,
    -imgWidth / 2,
    -this.clockRadius,
    imgWidth,
    this.clockRadius * 2);
  this.renderContext.restore();
}

7.定义绘制数字时间的方法,时间的显示格式为24小时制HH:MM:SS。

/**
 * 绘制数字时间HH:MM:SS
 */
private drawTimeHHMMSS(hour: number, minute: number, second: number): void {
  let hh = hour > 9 ? hour.toString() : "0" + hour;
  let mm = minute > 9 ? minute.toString() : "0" + minute;
  let ss = second > 9 ? second.toString() : "0" + second;
  let time = `${hh}:${mm}:${ss}`;
  this.renderContext.save();
  this.renderContext.font = SizeUtil.getPx($r("app.float.clock_time_font_size")) + "px";
  this.renderContext.beginPath();
  this.renderContext.textAlign = "center";
  this.renderContext.fillText(time, 0, 0);
  this.renderContext.restore();
}

到此,会发现以上代码缺少2个重要的类文件,分别是BaseConstant.ets和SizeUtil.ets,以及资源文件float.json的参数定义。

 

二、定义常量类文件BaseConstant.ets

新建目录constants,在其下新建ArkTS文件BaseConstant.ets。程序中的常量可以定义在这个类文件中,比如:图片路径等。

export class BaseConstant {
  static readonly CLOCK_PAN_IMAGE_URL: string = "images/icon_clock_pan.png";
  static readonly HOUR_HAND_IMAGE_URL: string = "images/icon_hour_hand.png";
  static readonly MINUTE_HAND_IMAGE_URL: string = "images/icon_minute_hand.png";
  static readonly SECOND_HAND_IMAGE_URL: string = "images/icon_second_hand.png";
}

三、定义单位转换类文件SizeUtil.ets

新建目录utils,在其下新建ArkTS文件SizeUtil.ets。为什么要封装这个单位转换公共类,可参考我的帖子:https://developer.huawei.com/consumer/cn/forum/topic/0208151714177357329?fid=0101587866109860105

import display from '@ohos.display';
import { GlobalContext } from './GlobalContext';

let context = getContext(this);
const DESIGN_WIDTH = 360; // 设计稿宽度
const DESIGN_HEIGHT = 780; // 设计稿高度

/**
 * 尺寸适配工具类
 */
export default class SizeUtil {
  /**
   * 尺寸适配
   * @param value 设计稿尺寸
   */
  static adaptSize(value: number): number {
    let deviceDisplay = GlobalContext.getContext().getObject("globalDisplay") as display.Display;
    let widthScale = deviceDisplay.width / DESIGN_WIDTH;
    let virtualHeight = DESIGN_HEIGHT * widthScale;
    let designDim = Math.sqrt(DESIGN_WIDTH * DESIGN_WIDTH + DESIGN_HEIGHT * DESIGN_HEIGHT);
    let virtualDim = Math.sqrt(deviceDisplay.width * deviceDisplay.width + virtualHeight * virtualHeight);
    return virtualDim * value / designDim; // 放缩后长度
  }

  /**
   * 获取px
   * @param value 设计稿尺寸
   */
  static getPx(value: Resource): number {
    console.log("context:", context);
    let beforeVp = context.resourceManager.getNumber(value.id);
    return SizeUtil.adaptSize(beforeVp);
  }

  /**
   * 获取vp
   * @param value 设计稿尺寸
   */
  static getVp(value: Resource): number {
    let beforeVp = context.resourceManager.getNumber(value.id);
    return px2vp(SizeUtil.adaptSize(beforeVp));
  }

  /**
   * 获取fp
   * @param value 设计稿尺寸
   */
  static getFp(value: Resource): number {
    let beforeFp = context.resourceManager.getNumber(value.id);
    return px2fp(SizeUtil.adaptSize(beforeFp));
  }
}

四、定义全局上下文类文件GlobalContext.ets

在utils目录下新建ArkTS文件GlobalContext.ets。

/**
 * 全局上下文
 */
export class GlobalContext {
  private constructor() {
  }

  private static instance: GlobalContext;
  private objects = new Map<string, Object>();

  /**
   * 获取全局上下文
   */
  public static getContext(): GlobalContext {
    if (!GlobalContext.instance) {
      GlobalContext.instance = new GlobalContext();
    }
    return GlobalContext.instance;
  }

  /**
   * 获取对象
   */
  getObject(name: string): Object | undefined {
    return this.objects.get(name);
  }

  /**
   * 设置对象
   */
  setObject(key: string, objectClass: Object): void {
    this.objects.set(key, objectClass);
  }
}

五、资源文件float.json

在文件中定义常用的数值变量,比如:显示数字时间的字体大小。

{
  "name": "clock_time_font_size",
  "value": "50"
},

请查阅官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-usage-0000001820880417

六、图片文件

新建images目录,在其下添加已设计好的表盘、时针、分针和秒针的图片。

 (图片素材见文章顶部的附件)

七、运行效果

注意:需要在真机上才可见秒针走动的效果,及点击切换显示数字时钟。

 ​​​​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与辉鸿蒙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值