1. 为什么用几行代码而不是Photoshop:中心对称图案的开发场景
先说我为什么会对这个题目感兴趣。前阵子做鸿蒙桌面小组件,需要一个类似万花筒的底纹图标,素材库里扒了半天,不是版权不合适就是风格对不上,后来索性直接用Canvas画了一个。画的过程中发现,中心对称图案这东西,数学上看着挺玄乎,代码实现其实非常直观——一圈旋转、重复绘制,几十行就能搞定一个看起来相当复杂的图形。这也是HarmonyOS应用实例里这类练习最有价值的地方:它不是为了画一朵花给你看,而是让你在动手过程中把状态管理、Canvas绘制、事件交互这几块基础能力全部串起来。
中心对称图案,按定义说就是图形绕某个中心点旋转一定角度后能和自身重合。最常见的像五角星旋转72度、雪花旋转60度,都属于这类。实际开发中,这种图案的用途远比你想的广:加载动画里的转圈菊花、数据大屏里的雷达图背景、游戏里的技能范围提示、甚至是自定义键盘的背景纹理,全是中心对称的变体。你只要掌握了“旋转-复制-重绘”这一个核心逻辑,这些场景基本都能复用。
我在这个项目里选型时也纠结过两种方案:一是用Canvas 2D手绘,二是用XComponent接OpenGL。对比下来,如果只是做2D图案设计、交互调节参数,Canvas完全够用,而且代码量小、调试方便;XComponent适合做大量粒子、3D变换那种重度渲染场景,杀鸡用牛刀没必要。文章里的实现默认走Canvas 2D这条路线,开发环境是DevEco Studio,语言ArkTS,API版本以HarmonyOS 4.2为基准。
适用人群方面,这个实例的难度区间比较友好:你刚学完ArkTS基础语法,想拿一个小项目练手,很合适;你已经写过几个页面,但对Canvas这块还是空白,这篇也能帮你快速补齐;甚至你打算做自定义绘图表单控件、小游戏,中心对称的绘制思路同样能给你一些启发。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 绘图核心:Canvas画布坐标系与对称绘制的底层逻辑
2.1 Canvas在ArkTS里的基本用法
HarmonyOS的Canvas不是HTML5那种<canvas>标签,而是ArkUI框架里的一个组件,配套一个CanvasRenderingContext2D对象。用法上先声明一个RenderingContextSettings,创建上下文,再把上下文绑定到Canvas组件上,之后所有绘制指令都通过这个上下文对象发出。
typescript复制@Entry
@Component
struct SymmetricPattern {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Column() {
Canvas(this.context)
.width('100%')
.height(400)
.backgroundColor('#FFFFFF')
.onReady(() => {
this.drawPattern()
})
}
}
drawPattern() {
// 绘制逻辑
}
}
onReady是Canvas组件的生命周期回调,表示画布已经准备好可以绘制了。这一步很容易被忽略,如果直接在前面的aboutToAppear里调drawPattern,大概率会拿到一个空画布,因为组件还没完成布局。我一开始就在这踩过坑:Canvas宽度高度都没确定,画布上下文拿不到有效的尺寸,画出来的东西位置全乱。
另外注意RenderingContextSettings构造函数里的true,这个参数开启抗锯齿。绘制中心对称图案时会有大量弧线、斜线重叠,不开抗锯齿边缘会像锯齿一样参差不齐,尤其在高分辨率屏幕上会更明显,所以这个参数建议默认就开。
2.2 坐标轴旋转:理解save/restore为什么是万能的
中心对称绘制的核心不是去算每个图形坐标,而是利用Canvas的坐标变换。Canvas的坐标系默认原点在左上角,x轴向右,y轴向下。当你调用rotate(角度)时,整个坐标系绕当前原点旋转,之后画的每个图形坐标都基于旋转后的坐标系。
这里有个关键点:rotate接受的是弧度而不是角度。180度 = Math.PI,90度 = Math.PI / 2。我见过不少新手直接把角度值传给rotate,结果图形偏得离谱。转换公式很简单:弧度 = 角度 * Math.PI / 180。
typescript复制// 错误示例:直接传角度值
context.rotate(45) // 实际旋转了45弧度,约2578度
// 正确示例
context.rotate(45 * Math.PI / 180)
接下来就是save和restore。这两个方法构成的“存档-读档”机制,是绘制重复性图案的基石。save()把当前坐标系状态压入栈中,restore()从栈顶弹出状态并恢复。配合循环,就能在同一个中心点重复绘制多次,而不用担心坐标系越转越乱。
2.3 从花瓣到万花筒:一个8片花瓣的完整绘制代码
中心对称图案的基本套路是这样的:先把坐标原点平移到图案中心,然后在循环里不断旋转坐标系并绘制同一个基础图形单元。基础单元可以是一段弧、一条路径、一个圆、或者一张图片,组合起来就是完整图案。
下面实现一个最经典的8片花瓣图案,每一片都是半圆弧,通过旋转8次覆盖整圆。
typescript复制drawPattern() {
const ctx = this.context
const width = this.context.width
const height = this.context.height
const centerX = width / 2
const centerY = height / 2
const peta
