这次做一个能直接看到效果的文字排版工坊:同一块 Canvas 里切换霓虹标题、逐字节奏和测量台。标题会在扫描光束和轨道环之间入场,汉字沿发光波形线跳动,测量台把文字边界框、宽度和 FontMetrics 画出来。
下面两段动态 GIF 和静态步骤图均来自已经完成的 Mate 60 Pro 实机操作。
先看最终效果
两段 GIF 都是从实体 Mate 60 Pro 连续抓帧后裁出 Canvas 区域制作:24 帧、8 FPS、3 秒、720×610,不是静态图重复播放。
进入实验 22 后,页面先运行"霓虹标题"。切换"逐字节奏",六个汉字逐字生成 TextBlob 并在基线附近做错峰运动;切换"测量台",可以直接看到文字的测量框、宽度、字形数量和 ascent/descent 数值。
本次要完成什么
- 用
drawing.Font设置字号、抗锯齿和粗体。 - 用
TextBlob.makeFromString()创建可绘制的文字对象。 - 用
Canvas.drawTextBlob()把文字画在 ArkGraphics 2D Canvas 上。 - 用
Font.measureText()、countText()和getMetrics()支撑排版,而不是凭经验写死位置。 - 用一个 Animator 驱动标题入场和逐字波动,暂停后验证绘制计数不再增长。
准备
- DevEco Studio 及 API 26 工程。
- 已连接的 HarmonyOS 真机;本次页面不申请相机、相册、网络或存储权限。
- 工程入口:
entry/src/main/ets/features/graphics2d/TextTypographyPage.ets。
参考:HarmonyOS ArkGraphics 2D 官方文档。
实施过程
1. 从首页进入实验 22
在首页点击"实验 22:GlyphFlow 动态文字排版工坊"。页面顶部显示 GLYPHFLOW 和 实验 22 · ArkGraphics 2D,中间大画布是实时绘制区域。
实验页截图同时记录了入口和默认的霓虹标题场景,后续切换场景都在同一页面完成。
2. 绘制霓虹标题
页面使用 RenderNode.draw() 获取 drawing.Canvas,先清理深色背景,再按顺序绘制基线、光晕层、主标题和字幕。光晕不是图片,而是对同一个 TextBlob 做偏移绘制。
ArkTS/ets
private drawText(canvas: drawing.Canvas, text: string, font: drawing.Font,
x: number, y: number, color: number): void {
const blob: drawing.TextBlob = drawing.TextBlob.makeFromString(
text, font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
const brush: drawing.Brush = new drawing.Brush();
brush.setColor(color);
brush.setAntiAlias(true);
canvas.attachBrush(brush);
canvas.drawTextBlob(blob, x, y);
canvas.detachBrush();
}
同一标题先在 (x + 4, y + 5) 用低透明度颜色画一层,再在 (x, y) 画主色,最后用青色偏移两像素,得到可观察的光晕边缘。
霓虹标题真机结果见本节前的入口截图。
3. 切到逐字节奏
"逐字节奏"不把整句当作一块文字,而是逐个字符测量、逐个创建 TextBlob。每个字符的起点由前面字符的真实宽度累加,Y 坐标用相位和字符索引计算:
ArkTS/ets
const rise: number = Math.sin(local * Math.PI * 2) * height * 0.055;
const x: number = cursor;
const y: number = centerY + rise;
this.drawText(canvas, text.charAt(index), font, x, y,
index % 2 === 0 ? 0xFF6CE8FF : 0xFF9CFFE3);
cursor += widths[index];
这样可以单独控制每个字的延迟、颜色和位移,适合做标题入场、歌词、提示语和数据播报。
4. 在测量台查看真实指标
测量台固定绘制 MEASURE,边界框来自字体指标,而不是手工估计:
ArkTS/ets
const textWidth: number = font.measureText(
text, drawing.TextEncoding.TEXT_ENCODING_UTF8);
const glyphCount: number = font.countText(text);
const metrics: drawing.FontMetrics = font.getMetrics();
const top: number = baseline + metrics.ascent - 18;
const bottom: number = baseline + metrics.descent + 18;
画布下方同步显示 WIDTH、GLYPHS 和 A / D。字号从 42、54、68 px 切换,测量框和宽度会随之变化。
5. 调整字号、字重并暂停
点击 42 PX / 54 PX / 68 PX 可以重新创建字体并立即刷新画布;点击"字重:粗"切换 Font.enableEmbolden(true/false)。点击"暂停动画"后观察画布中的 绘制次数 和 动画回调,等待几秒后再点"继续动画"。
点击"退出"后回到首页,退出路径会停止 Animator 并释放 RenderNode。
遇到的情况与处理
文字不显示
第一次验证时只创建了 TextBlob,没有给 Canvas 附加 Brush。修正为"创建 Brush → attachBrush → drawTextBlob → detachBrush"后,文字正常显示。
中文宽度不能用字符个数估算
中文和英文的字宽不同,逐字排版如果使用固定步长会出现重叠或间隙。现在每个字符都调用 Font.measureText(),用真实宽度累加 cursor。
退出后动画仍在回调
页面退出统一执行 animator.cancel() 和 controller.release()。RenderNode 的 dispose() 在退出路径中只执行一次,避免重进页面后残留上一轮动画。
真机日志
下面是本次实体真机采集的 Hilog 关键行。包名和进程号已去掉,只保留用于核对绘制、切换、暂停与释放的字段:
text
TEXT_READY modes=3 durationMs=4200 expectedFps=60 permissionRequired=false
TEXT_DRAW mode=0 drawCount=1 width=995 height=995 textWidth=336 glyphs=4
TEXT_SWITCH mode=1 name=逐字节奏 phasePermille=675
TEXT_DRAW mode=1 drawCount=1800 width=1174 height=995 textWidth=294 glyphs=6
TEXT_SWITCH mode=2 name=测量台 phasePermille=236
TEXT_DRAW mode=2 drawCount=3180 width=1174 height=995 textWidth=264 glyphs=7
TEXT_SIZE sizePx=68 mode=2
TEXT_WEIGHT embolden=false
TEXT_PAUSE mode=2 phasePermille=569 drawCount=11445 callbacks=11440
TEXT_RESUME mode=2 phasePermille=569 drawCount=11445 callbacks=11440
TEXT_RESET mode=0 sizePx=54 embolden=true
TEXT_RELEASE reason=EXIT_BUTTON drawCount=11539 callbacks=11537
暂停截图显示"测量台已暂停 / PAUSED",日志中的暂停与恢复都落在同一 phasePermille=569;复位和退出分别记录了默认字号/字重以及 EXIT_BUTTON 释放路径。
验证结果
- 三种场景均由 ArkGraphics 2D
Font + TextBlob + Canvas.drawTextBlob()实时绘制。 - 字号、字重切换后,真机测量宽度和画布边界框同步变化。
- 逐字节奏 GIF 使用 24 帧连续真机帧,画布中 6 个字的波形位置会变化。
- 页面无需任何权限;日志确认退出时执行
TEXT_RELEASE,重新进入后从新的TEXT_READY开始。
本次文件
text
entry/src/main/ets/features/graphics2d/TextTypographyPage.ets
scripts/check-text-typography.ps1
articles/第二十篇-ArkGraphics 2D文本绘制实战/
docs/qa/arkgraphics2d-20/
本次动态素材的原始帧在 docs/qa/arkgraphics2d-20/返工动态原始帧/,真机关键日志在 docs/qa/arkgraphics2d-20/真机验证日志.txt。