鸿蒙开发实战——使用Canvas绘制环形文字

342 阅读3分钟

1、背 景

我们在之前的文章鸿蒙UI开发——实现环形文字中有讨论过如何通过 RichText组件,使用SVG方案来绘制环形文字。该例子实现的效果如下:

image.png

又有朋友提出疑问,如果我们使用Canvas去绘制一个环形文字,需要怎么实现呢?

使用Canvas实现一个环形文字其实也并不复杂。我们下面来讨论如何实现。

Canvas的基本使用以及相关的实战我们可以参考之前发表的文章:

2、使用Canvas实现环形文字

👉🏻 实现思路

由于我们需要绘制的文字是按照圆的周长进行均匀分布,因此,我们第一步就是需要确定每个文字占用的“扇形”区域弧度是多少。

另外,由于文字是围绕一个圆形做分布,因此,我们在绘制每个文字时,都先将文字按照分配的弧度进行旋转,然后进行文字绘制。

由于旋转的中心点默认是(0,0),也就是最左上角,为了保证旋转是在画布的中央,我们只需要将画布做一个平移即可,将画布移动到圆的中心点。

有了思路后,我们开始实现(文末有完整代码)。

👉🏻 step1:定义Canvas并且初始化对应的Context。

Canvas有一个onReady()事件,onReady设置的callback将会在Canvas准备完毕后调用。

代码如下(这是一个基本的Canvas开发模板):

@Entry
@Component
@Preview
export struct Index {
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private contextCanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  
  private drawCircleText(text: string) {
    // todo 绘制文本逻辑
  }

  build() {
    Row() {
      Column() {
        Canvas(this.context)
          .width(300)
          .height(300)
          .borderColor('green')
          .borderWidth(1)
          .onReady(() => this.drawCircleText('欢迎加入Harmony自习室,微信公众号【Harmony自习室】'))
      }
      .width('100%')
    }
    .height('100%');
  }
}

效果如下(由于我们还未在画布上绘制内容,因此显示是空白):

image.png

👉🏻 step2:确定一些绘制参数

我们需要确定圆心位置、圆心半径、每个文字所占用的度数。代码如下:

private drawCircleText(text: string) {
    // 获取画布的尺寸信息
    const width = this.context.width, height = this.context.height;
    const fontSize = 18// 字体大小
    const r = width / 2 - fontSize; // 半径
    const charRadius = 360 / text.length; // 计算每个文字占用的度数
    // 圆形位置: (width/2, height/2)
}

👉🏻 step3:偏移画布到圆心并且开始绘制每个文字

代码如下:我们在第10行代码将画布偏移到圆心,并且接着就是一个循环,分别绘制每个文字,在每个文字绘制完毕后,对画布进行旋转。

 private drawCircleText(text: string) {
    const width = this.context.width, height = this.context.height;
    const fontSize = 18;
    const r = width / 2 - fontSize;
    this.context.clearRect(0, 0, width, height); // 清空画布
    this.context.save();
    this.context.font = `${fontSize}vp`
    this.context.textBaseline = 'top';
    const charRadius = 360 / text.length; // 一个字符所占的弧度
    this.context.translate(width / 2, height / 2);
    for (let i = 0; i < text.length; i++) {
      this.context.fillText(text.charAt(i), 0, -r);
      this.context.rotate(this.getAngle(charRadius));
    }

    this.context.restore();
  }

一个完整的Demo如下:

@Entry
@Component
@Preview
export struct Index {
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);

  // 转换为弧度值
  private getAngle(n: number) {
    return Math.PI / 180 * n;
  }

  private drawCircleText(text: string) {
    const width = this.context.width, height = this.context.height;
    const fontSize = 18;
    const r = width / 2 - fontSize;
    this.context.clearRect(0, 0, width, height); // 清空画布
    this.context.save();
    this.context.font = `${fontSize}vp`
    this.context.textBaseline = 'top';
    const charRadius = 360 / text.length; // 一个字符所占的弧度
    this.context.translate(width / 2, height / 2);
    for (let i = 0; i < text.length; i++) {
      this.context.fillText(text.charAt(i), 0, -r);
      this.context.rotate(this.getAngle(charRadius));
    }
    this.context.restore();
  }

  build() {
    Row() {
      Column() {
        Canvas(this.context)
          .width(300)
          .height(300)
          .borderColor('green')
          .borderWidth(1)
          .onReady(() => this.drawCircleText('欢迎加入Harmony自习室,微信公众号【Harmony自习室】'))
      }
      .width('100%')
    }
    .height('100%');
  }
}

效果如下:

image.png

3、one more thing..

另外,为了方便查看,我将两个实现方案都放置到一起,效果如下:

image.png

image.png

代码地址如下:

https://gitee.com/lantingshuxu/harmony-class-room-demos/tree/feat%2FcircleText/