鸿蒙 ArkTS 实战|波点变动动画实现:setInterval 帧驱动 + 坐标映射 + 象限判定
·


一、设计思路
"波点变动"是本章技术核心:一个波点沿直线 y = kx + b 匀速往返,让抽象的线性关系"动"起来。实现要点有四:帧驱动(setInterval 定时步进)、状态机(movingRight 控制往返)、坐标映射(数学系 ↔ 像素系)、象限判定(坐标符号 → 象限文本)。本篇拆解这四块代码。
二、帧驱动:setInterval 每 30ms 步进一步
@Entry
@Component
struct LinearEquation {
private timer: number = 0; // 定时器句柄(销毁时清理)
aboutToAppear(): void {
this.solve();
this.draw();
// 波点变动:每 30ms 前进 0.2,≈33 帧/秒,视觉平滑
this.timer = setInterval(() => { this.tick(); }, 30);
}
aboutToDisappear(): void {
clearInterval(this.timer); // 离开页面必须清理,否则后台空转
}
// 每一帧:x 步进 → y 重算 → 象限刷新 → 重绘
private tick(): void {
let nx = this.dotX + (this.movingRight ? 0.2 : -0.2);
if (nx >= this.xRange) { // 到达右边界
nx = this.xRange;
this.movingRight = false; // 掉头向左
}
if (nx <= -this.xRange) { // 到达左边界
nx = -this.xRange;
this.movingRight = true; // 掉头向右
}
this.dotX = nx;
this.dotY = this.yOf(nx); // 波点永远"贴"在直线上
this.dotQuadrant = this.quadrantOf(nx, this.dotY);
this.draw();
}
}
要点:
setInterval返回number型句柄,必须保存,aboutToDisappear中clearInterval,这是 HarmonyOS 页面生命周期的最佳实践。- 步长 0.2、间隔 30ms → 速度约 6.7 单位/秒,x 全范围 20 单位约 3 秒一趟往返,节奏适中。
tick()是"纯步进":只修改 @State,不做计算外的事情,职责单一。
三、坐标映射:数学系 ↔ 像素系
// 世界坐标 → 画布像素(画布中心 = 原点,y 轴向上为正)
private toPixel(x: number, y: number, w: number, h: number): Pixel {
return {
px: w / 2 + (x / this.xRange) * (w / 2),
py: h / 2 - (y / this.yRange) * (h / 2)
};
}
| 坐标系 | 原点 | y 方向 | x 范围 | y 范围 |
|---|---|---|---|---|
| 数学系 | 画布中心 | 向上为正 | ±10 | ±6 |
| 像素系 | 左上角 | 向下为正 | 0~w | 0~h |
映射公式拆解:
px = w/2 + (x/10) * (w/2):x = 0 时在中心;x = +10 时到右边缘;x = −10 到左边缘。py = h/2 − (y/6) * (h/2):唯一差别是 y 前是负号——把数学的"向上"翻转为像素的"向下"。- 所有绘制(网格、轴线、直线、波点)都经过这一个函数,保证全图坐标一致。
四、象限判定:坐标符号 → 象限文本
private quadrantOf(x: number, y: number): string {
if (x >= 0 && y >= 0) return '第一象限';
if (x <= 0 && y >= 0) return '第二象限';
if (x <= 0 && y <= 0) return '第三象限';
return '第四象限';
}
象限判定就是符号表的代码化:
| 象限 | x 符号 | y 符号 | 口诀 |
|---|---|---|---|
| Ⅰ | + | + | 同正 |
| Ⅱ | − | + | 左正上 |
| Ⅲ | − | − | 同负 |
| Ⅳ | + | − | 右下 |
边界(坐标轴上)归入非负区:x = 0 且 y > 0 判为Ⅱ象限,保证任何时刻都有确定结果。
五、波点绘制:光晕 + 实心 + 描边
const dp = this.toPixel(this.dotX, this.dotY, w, h);
// 外圈光晕:半径 10 的半透明绿
ctx.beginPath(); ctx.arc(dp.px, dp.py, 10, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(16,185,129,0.25)';
ctx.fill();
// 实心核心:半径 5 的亮绿
ctx.beginPath(); ctx.arc(dp.px, dp.py, 5, 0, Math.PI * 2);
ctx.fillStyle = '#10B981';
ctx.fill();
// 白色描边:增强对比,深色背景也清晰
ctx.strokeStyle = Color.White;
ctx.lineWidth = 2;
ctx.stroke();
三层绘制的意义:光晕制造"发光感",让运动中的点更醒目;白描边保证任何背景色下都可见。Color.White 是 ArkUI 内置枚举,Canvas 可直接用作 strokeStyle。
六、动画与 UI 双刷新的配合
// 数据面板:@State 自动刷新 —— 不需要任何手动操作
Text(this.dotX.toFixed(1)) // 每次 tick 改 dotX 后自动重渲染
// Canvas 图:手动重绘 —— 需要每帧调用 draw()
// 因此 tick() 末尾固定调 this.draw()
// 波点数据面板(UI 层,@State 驱动,自动刷新)
Row({ space: 10 }) {
Column({ space: 4 }) {
Text('横坐标 x').fontSize(12).fontColor('#9CA3AF')
Text(this.dotX.toFixed(1))
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#10B981')
}.layoutWeight(1)
Column({ space: 4 }) {
Text('纵坐标 y').fontSize(12).fontColor('#9CA3AF')
Text(this.dotY.toFixed(1))
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#10B981')
}.layoutWeight(1)
Column({ space: 4 }) {
Text('所在象限').fontSize(12).fontColor('#9CA3AF')
Text(this.dotQuadrant)
.fontSize(14).fontWeight(FontWeight.Bold).fontColor('#4C7DFF')
}.layoutWeight(1.4)
}.width('100%').margin({ top: 4 })
性能注意:30ms 一帧、一帧只改 3 个 @State + 一次 Canvas 重绘,开销极小;不要在 tick 里做字符串拼接、数组操作等重活。
七、调试与扩展建议
| 需求 | 改哪里 |
|---|---|
| 加快/减慢波点 | 调 setInterval 间隔或步长 0.2 |
| 换运动轨迹 | 改 tick() 中 x 的更新规则(如正弦:x = sin(t)) |
| 显示轨迹残影 | 不 clearRect,改用半透明底色填充即可 |
| 显示坐标网格刻度 | 在网格线上加 fillText 标注数字 |
| 暂停/继续 | 用 @State 布尔值包住 tick() 主体 |
八、波点变动与数学本质
波点变动不只是动画——它演示了函数的本质:给定 x,函数唯一确定 y。波点每走一步,y = kx + b 都被验证一次;当它扫过交点(方程 ax + b = 0 的解在 x 轴上对应的点)时,可以引导观察"y = 0 时 x 的值正是方程的解"。代码与数学在这个页面上互为注解。
更多推荐



所有评论(0)