HarmonyOS ArkGraphics 2D 实操:把一条极光轨迹做成可交互徽章
这次不做孤立的线条示例,直接做一枚能放进真实页面的 TrailSeal 极光轨迹徽章:天空、山峰、帐篷、营地路线和移动信标全部由 ArkGraphics 2D 绘制;点击主题、拖动线宽、切换线帽/转角,画面会即时重绘;点击“生成成品贴纸”还能把实时画布冻结成一张离屏 PixelMap。
上面的 GIF 是 Mate 60 Pro 连续截取的 12 帧真机画面,信标沿路线移动,画面不是预制动画。
先看最终效果
默认状态是日落主题、8 px、圆头、圆角,徽章有深色玻璃画布、金属外圈、极光路径和右上角样式小面板:
同一套 Path 可以切换为极光和午夜两套颜色:
本篇的视觉方向先用 image2 做了“深海蓝 + 极光青 + 珊瑚橙”的设计参考,再在 Figma 中整理成可编辑稿,再在真机上重现:TrailSeal 极光轨迹徽章视觉重设计。设计参考图只用于确定色彩和构图,文章中的效果图和结论全部来自真机。
实验准备
| 项目 | 实际环境 |
|---|---|
| 设备 | HUAWEI Mate 60 Pro |
| 系统 | HarmonyOS 7.0 |
| SDK | API 26 |
| 开发方式 | ArkTS / ArkUI |
| 图形接口 | @kit.ArkGraphics2D |
| 图像接口 | @kit.ImageKit |
| 权限 | 不需要相机、相册、定位、存储或网络权限 |
打开应用后进入实验20即可看到设计器。这个实验不依赖相机或素材文件,所有图形在 Canvas 中生成。
1. 先把徽章拆成“填充”和“描边”
绘制顺序固定为:工作台背景 → 玻璃面板 → 圆形天空 → 远山和近山 → 帐篷 → 路线 → 动态信标 → 底部色卡。填充区域交给 Brush,轮廓和路线交给 Pen。
RenderNode.draw() 只负责选择实时画布或冻结后的 PixelMap:
draw(context: DrawContext): void {
const width: number = Math.max(1, Math.round(context.sizeInPixel.width));
const height: number = Math.max(1, Math.round(context.sizeInPixel.height));
if (this.previewPixelMap) {
context.canvas.clear(BADGE_THEMES[this.stateValue.themeIndex].workspace);
context.canvas.drawImage(this.previewPixelMap, 0, 0);
} else {
this.drawBadgeScene(context.canvas, width, height, this.stateValue, true);
}
}
填充时先设置 Brush,再挂载到 Canvas,画完立即解绑:
private fillPath(canvas: drawing.Canvas, path: drawing.Path,
color: number, antiAlias: boolean): void {
const brush: drawing.Brush = new drawing.Brush();
brush.setColor(color);
brush.setAntiAlias(antiAlias);
canvas.attachBrush(brush);
canvas.drawPath(path);
canvas.detachBrush();
}
2. 用 Pen 画路线和山峰边缘
路线是平滑的三段 Cubic Path,山峰边缘使用折线。Pen 的颜色、宽度、线帽、转角和抗锯齿都在 attachPen() 前设置:
private strokePath(canvas: drawing.Canvas, path: drawing.Path,
color: number, strokeWidth: number,
capStyle: drawing.CapStyle,
joinStyle: drawing.JoinStyle,
antiAlias: boolean): void {
const pen: drawing.Pen = new drawing.Pen();
pen.setColor(color);
pen.setStrokeWidth(strokeWidth);
pen.setCapStyle(capStyle);
pen.setJoinStyle(joinStyle);
pen.setAntiAlias(antiAlias);
canvas.attachPen(pen);
canvas.drawPath(path);
canvas.detachPen();
}
页面上的三个线帽和三个转角分别映射到 ArkGraphics 2D 枚举:
private capStyle(index: number): drawing.CapStyle {
if (index === 0) {
return drawing.CapStyle.FLAT_CAP;
}
if (index === 2) {
return drawing.CapStyle.SQUARE_CAP;
}
return drawing.CapStyle.ROUND_CAP;
}
private joinStyle(index: number): drawing.JoinStyle {
if (index === 0) {
return drawing.JoinStyle.MITER_JOIN;
}
if (index === 2) {
return drawing.JoinStyle.BEVEL_JOIN;
}
return drawing.JoinStyle.ROUND_JOIN;
}
把滑杆拖到18 px,再选择方头和斜角:
切换为平切和尖角,路线端点和山峰转角会立即改变:
这组截图同时证明了三件事:滑杆确实改变了 Pen.setStrokeWidth(),线帽改变了路径端点,转角改变了山峰折线的连接方式。
3. 三套主题共用同一套几何数据
主题只保存颜色,不复制 Path。点击主题按钮后更新 themeIndex,RenderNode 调用 invalidate() 重绘:
updateStyle(state: BadgeStyleState): void {
this.stateValue = {
themeIndex: state.themeIndex,
strokeWidth: state.strokeWidth,
capIndex: state.capIndex,
joinIndex: state.joinIndex,
antiAlias: state.antiAlias,
beaconProgress: state.beaconProgress
};
if (!this.previewPixelMap) {
this.invalidate();
}
}
日落使用珊瑚橙,极光使用青绿色,午夜使用紫色;山体轮廓和路线位置保持不变。这样切换主题只改颜色,不增加组件和 Path 数量。
4. 让信标沿路线移动
信标的位置由 0—1 的进度计算。进度小于0.5时沿第一段二次曲线移动,大于0.5时沿第二段曲线移动。Animator 每5秒循环一次,请求30 FPS:
this.animator = this.getUIContext().createAnimator({
duration: 5000,
easing: 'linear',
delay: 0,
fill: 'both',
direction: 'normal',
iterations: -1,
begin: 0,
end: 1
});
this.animator.setExpectedFrameRateRange({
min: 30,
max: 30,
expected: 30
});
this.animator.onFrame = (progress: number): void => {
if (this.released || this.previewMode) {
return;
}
this.progress = progress;
this.controller.setProgress(progress);
};
点击暂停后,按钮变成“继续信标”,当前进度停在35%,画布不再请求新帧:
等待2秒后,裁剪出的画布区域 SHA-256 仍然相同:
10948E0707C21A31F861DEB6099FA6F0830627758F3F6A2F5865A3295E327EA6
10948E0707C21A31F861DEB6099FA6F0830627758F3F6A2F5865A3295E327EA6
5. 生成离屏成品贴纸
点击“生成成品贴纸”后,页面先暂停 Animator,再按实时 Canvas 尺寸创建 RGBA_8888 PixelMap,复用同一个绘制函数,但关闭右上角的编辑样例:
const metrics: BadgeMetrics = this.badgeNode.getMetrics();
const pixelBuffer: ArrayBuffer = new ArrayBuffer(
metrics.width * metrics.height * 4
);
const pixelMap: image.PixelMap = await image.createPixelMap(pixelBuffer, {
editable: true,
pixelFormat: image.PixelMapFormat.RGBA_8888,
size: { width: metrics.width, height: metrics.height }
});
const offscreenCanvas: drawing.Canvas = new drawing.Canvas(pixelMap);
this.badgeNode.drawBadgeScene(
offscreenCanvas,
metrics.width,
metrics.height,
this.badgeNode.getState(),
false
);
this.badgeNode.setPreview(pixelMap);
真机页面显示 OFFSCREEN、预览编号和 1174 × 884 px:
回到画布顶部后,状态变成 PREVIEW,信标停止,编辑样例不再出现在成品中:
实操中遇到的情况
线宽滑杆拖动时触发了多次回调
真机拖动滑杆会连续产生 WIDTH 日志,这是正常的交互过程。最终状态以页面右上角的线宽值和最后一条 BADGE_STYLE source=WIDTH 为准;本次最终验证为18 px和8 px两组。
抗锯齿开关肉眼差异不大
高像素密度屏幕上,整屏观看不容易看出差异。测试时保持固定 Path,只切换 Brush 和 Pen 的 setAntiAlias(),再放大圆弧、山峰斜线观察。状态栏和日志均能确认开关已经进入绘制状态。
预览后画布不再动
这是预期结果:预览模式由 PixelMap 提供画面,Animator 被暂停,退出或返回实时编辑时再释放 PixelMap 并恢复 Animator。
本次真机关键日志
下面保留本次重设计后的关键日志,已去掉应用标识:
BADGE_READY themes=3 caps=3 joins=3 durationMs=5000 expectedFps=30 permissionRequired=false
BADGE_STYLE source=THEME theme=1 width=8 cap=ROUND join=ROUND aa=true
BADGE_STYLE source=THEME theme=2 width=8 cap=ROUND join=ROUND aa=true
BADGE_STYLE source=WIDTH theme=1 width=18 cap=FLAT join=MITER aa=true
BADGE_STYLE source=CAP theme=1 width=8 cap=ROUND join=MITER aa=true
BADGE_STYLE source=JOIN theme=1 width=8 cap=ROUND join=ROUND aa=true
BADGE_PAUSE progressPermille=345 drawCount=23573
BADGE_RESUME progressPermille=345 drawCount=23573
BADGE_PREVIEW success=true number=2 width=1174 height=884 progressPermille=599
BADGE_RELEASE action=EXIT mode=PREVIEW drawCount=23613 callbacks=23585 previews=2 progressPermille=599
最终结果
这枚徽章已经把 ArkGraphics 2D 的关键基础能力放进一个完整交互场景:Brush 负责天空、山体和帐篷填充,Pen 负责轮廓和路线,三套主题复用同一套 Path,线宽/线帽/转角/抗锯齿可即时调整,Animator 驱动信标沿路线移动,离屏 Canvas 输出冻结贴纸。
真机确认:新版深色玻璃视觉已落地;日落、极光、午夜三套主题可切换;18 px 描边、三种线帽和三种转角均有截图;暂停后画布区域哈希不变;离屏预览为 1174 × 884 px;退出页面产生资源释放日志。
官方资料:基础绘制效果(ArkTS)
更多推荐

所有评论(0)