HarmonyOS应用开发实战:猫猫大作战-ForEach 遍历猫咪数组、Emoji 字符到等级映射、圆形背景色、绝对定位摆放

📅 2026/7/28 2:52:56 👁️ 阅读次数 📝 编程学习
HarmonyOS应用开发实战:猫猫大作战-ForEach 遍历猫咪数组、Emoji 字符到等级映射、圆形背景色、绝对定位摆放

前言

棋盘背景网格画完,接下来要渲猫咪棋子——这是游戏页的主角。在 ArkUI 里,棋子是「Emoji Text + 圆形背景色块 + 绝对定位」三层组合:Emoji 显字形、Circle 背景显等级色、position 摆坐标。这三种技术在前几篇都拆过,本篇要把它们合成一个完整棋子渲染流程

本篇以「猫猫大作战」游戏页猫咪层为锚点,把 ForEach 遍历猫咪数组、Emoji 字符到等级映射、圆形背景色、绝对定位摆放四大要点讲透。读完本篇你将能独立写出:Emoji 棋子层等级色圆背景绝对定位棋盘三种实战套路。

提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–14 篇。

一、场景拆解:猫咪层渲染

打开entry/src/main/ets/pages/Index.etsGameView第 2 层:

// 来源:entry/src/main/ets/pages/Index.ets GameView() 第 2 层 Column() { ForEach(this.cats, (cat: Cat) => { Stack() { Circle({ width: 30, height: 30 }).fillColor(this.getCatColor(cat.level)) Text(this.getCatEmoji(cat.level)).fontSize(20) } .alignContent(Alignment.Center) .position({ x: `${cat.x * 100 / COLS}%`, y: `${cat.y * 100 / ROWS}%` }) }, (cat: Cat) => `cat-${cat.id}`) } .width('100%').height('100%').zIndex(1)

猫咪层规格拆解:

元素类型作用
ForEach循环渲染遍历this.cats数组
Stack叠层容器圆背景 + Emoji 叠
Circle圆形矢量等级色背景(30vp)
TextEmoji 字符猫咪字形(20vp)
position绝对定位摆到棋盘坐标
密钥cat-${cat.id}唯一追踪

关键经验:圆 30 比 Emoji 20 大——圆作背景框,Emoji 不顶满留点边气。

二、Cat 数据结构

打开entry/src/main/ets/components/GameTypes.ets

export interface Cat { id: number // 唯一标识 x: number // 棋盘列坐标 (0-COLS) y: number // 棋盘行坐标 (0-ROWS) level: CatLevel // 猫咪等级 (1-6) }

2.1 interface 类型契约

ArkTS 严格模式禁any,禁对象字面量动态加属性——必须用interfaceclass显式声明字段:

// ✅ 正确:interface 显式契约 export interface Cat { id: number x: number y: number level: CatLevel } // ❌ 错误:ArkTS 禁动态对象 const cat: any = { id: 1, x: 0, y: 0 } cat.level = CatLevel.LEVEL_1 // 动态加属性,编译报错!

2.2 CatLevel enum

export enum CatLevel { LEVEL_1, // 0 LEVEL_2, // 1 LEVEL_3, // 2 LEVEL_4, // 3 LEVEL_5, // 4 LEVEL_6 // 5 }

枚举值默认从 0 递增——LEVEL_1= 0、LEVEL_6= 5。本系列第 114 篇会专讲 enum。

提示:枚举名LEVEL_1实际值是 0,不是 1。如果想值从 1 开始,写LEVEL_1 = 1, LEVEL_2 = 2, ...。本项目用默认 0 起,所以渲染时要cat.level + 1才显示「1 级」。

三、Emoji 字符到等级映射

// 来源:entry/src/main/ets/pages/Index.ets getCatEmoji(level: CatLevel): string { switch (level) { case CatLevel.LEVEL_1: return '🐱' case CatLevel.LEVEL_2: return '😺' case CatLevel.LEVEL_3: return '😸' case CatLevel.LEVEL_4: return '😻' case CatLevel.LEVEL_5: return '🐼' case CatLevel.LEVEL_6: return '🐯' default: return '🐱' } }

3.1 Emoji 映射表的两种写法

// 写法 1:switch(本项目) getCatEmoji(level: CatLevel): string { switch (level) { case CatLevel.LEVEL_1: return '🐱' case CatLevel.LEVEL_2: return '😺' // ... default: return '🐱' } } // 写法 2:Record 映射表 const CAT_EMOJI: Record<number, string> = { [CatLevel.LEVEL_1]: '🐱', [CatLevel.LEVEL_2]: '😺', [CatLevel.LEVEL_3]: '😸', [CatLevel.LEVEL_4]: '😻', [CatLevel.LEVEL_5]: '🐼', [CatLevel.LEVEL_6]: '🐯' } getCatEmoji(level: CatLevel): string { return CAT_EMOJI[level] ?? '🐱' }

实战经验Record 映射表 O(1) 查询比 switch O(n) 快,6 级以上必用 Record。本系列第 115 篇会专讲 Record。

3.2 Emoji 渲染踩坑

  1. 预览器可能缺字形:DevEco 预览器某些 Emoji 不显示,真机才完整。
  2. fontColor 对彩色 Emoji 无效:Emoji 自带色表,.fontColor(Color.Red)只影响单色 Emoji(如)。
  3. 字号比汉字大 10–20%:同字号 Emoji 视觉比汉字小一圈,要调大才平衡。本项目 Emoji 20 配 Circle 30,视觉才稳。

四、圆形背景色

getCatColor(level: CatLevel): string { switch (level) { case CatLevel.LEVEL_1: return '#FFC107' // 黄 case CatLevel.LEVEL_2: return '#FF9800' // 橙 case CatLevel.LEVEL_3: return '#E74C3C' // 红 case CatLevel.LEVEL_4: return '#9B59B6' // 紫 case CatLevel.LEVEL_5: return '#2ECC71' // 绿 case CatLevel.LEVEL_6: return '#3498DB' // 蓝 default: return '#FFC107' } }

4.1 圆背景 + Emoji 叠层结构

Stack() { Circle({ width: 30, height: 30 }).fillColor(this.getCatColor(cat.level)) Text(this.getCatEmoji(cat.level)).fontSize(20) } .alignContent(Alignment.Center)

三层叠

元素尺寸作用
Circle30vp等级色背景圆
TextEmoji20vp猫咪字形
容器Stackauto叠层居中

StackalignContent(Alignment.Center)让 Emoji 居中叠在 Circle 上。

4.2 圆尺寸 vs Emoji 尺寸比例

比例视觉适用
圆 30 + Emoji 20(1.5:1)圆作背景框,Emoji 不顶满本项目
圆 24 + Emoji 24(1:1)Emoji 顶满圆,无背景感纯 Emoji 棋子
圆 40 + Emoji 24(1.67:1)圆大留白多,Emoji 小低视觉权重

实战经验1.5:1 是甜区——圆显色,Emoji 显形,两者各司其职。

五、ForEach 遍历猫咪数组

ForEach(this.cats, (cat: Cat) => { Stack() { /* ... */ } .position({ x: `${cat.x * 100 / COLS}%`, y: `${cat.y * 100 / ROWS}%` }) }, (cat: Cat) => `cat-${cat.id}`)

5.1 keyGenerator 用 cat.id

(cat: Cat) => `cat-${cat.id}` // 密钥 'cat-1'/'cat-2'/...

关键密钥用cat.id而非cat.xcat.y——因为猫咪会移动(下落、合并),x/y会变,但id不变。用 id 做密钥才能追踪同一只猫的移动,而非每次坐标变都重建。

5.2 数组增删触发 ForEach 重渲

this.cats.push(newCat) // 添加 → ForEach 新增一项渲染 this.cats.splice(idx, 1) // 删除 → ForEach 移除一项渲染 this.cats = [...this.cats] // 整体替换 → ForEach 全 diff

ArkUI 监视机制@State cats: Cat[]被重新赋值或增删时,ForEach 用密钥做 diff——新增 id 渲染新项,删除 id 销毁旧项,其余复用。

5.3 数组项内部属性变化

this.cats[0].y += 1 // 移动第 0 只猫

注意直接改cats[0].y不会触发 ForEach 重渲——因为@State浅观察,只监听数组引用变化和增删,不监听项内部属性。要触发重渲有两种方式:

// 方式 1:重新赋值整个数组(强制 diff) this.cats = [...this.cats] // 方式 2:用 @Observed + @ObjectLink 深观察(本系列第 43 篇会讲)

本项目用方式 1——主循环里this.cats = [...this.cats]强制 diff,ForEach 用密钥追踪移动。

六、绝对定位摆到棋盘坐标

.position({ x: `${cat.x * 100 / COLS}%`, y: `${cat.y * 100 / ROWS}%` })

6.1 百分比坐标响应式

`${cat.x * 100 / COLS}%` // cat.x=2, COLS=8 → '25%'

猫咪在第 2 列,定位到棋盘 25% 处——无论屏宽多少,都落在第 3 列。百分比坐标是响应式棋盘的甜区

6.2 position 基准是父容器左上角

Column() { ForEach(this.cats, (cat: Cat) => { Stack() { /* ... */ } .position({ x: '...', y: '...' }) }, ...) } .width('100%').height('100%') // 父容器齔满棋盘

关键position的基准是父容器左上角。父Column必须width('100%').height('100%')齔满棋盘,猫咪的百分比定位才正确。

6.3 vs 固定 vp 坐标

// 方式 1:百分比(响应式,本项目) .position({ x: `${cat.x * 100 / COLS}%`, y: `${cat.y * 100 / ROWS}%` }) // 方式 2:固定 vp(不响应屏宽) const CELL_SIZE = 48 // 每格 48vp .position({ x: cat.x * CELL_SIZE, y: cat.y * CELL_SIZE })

百分比优势:屏宽变化棋盘自动缩放,猫咪跟随。固定 vp 优势:格子大小一致,棋盘居中不齔屏时用。

七、完整代码:猫咪层渲染

@Entry @Component struct Index { @State cats: Cat[] = [] // ... 其他 state getCatEmoji(level: CatLevel): string { switch (level) { case CatLevel.LEVEL_1: return '🐱' case CatLevel.LEVEL_2: return '😺' case CatLevel.LEVEL_3: return '😸' case CatLevel.LEVEL_4: return '😻' case CatLevel.LEVEL_5: return '🐼' case CatLevel.LEVEL_6: return '🐯' default: return '🐱' } } getCatColor(level: CatLevel): string { switch (level) { case CatLevel.LEVEL_1: return '#FFC107' case CatLevel.LEVEL_2: return '#FF9800' case CatLevel.LEVEL_3: return '#E74C3C' case CatLevel.LEVEL_4: return '#9B59B6' case CatLevel.LEVEL_5: return '#2ECC71' case CatLevel.LEVEL_6: return '#3498DB' default: return '#FFC107' } } @Builder GameView() { Stack() { // 第 1 层:棋盘背景(第 13 篇讲) Column() { /* ... */ }.width('100%').height('100%').zIndex(0) // 第 2 层:猫咪层 Column() { ForEach(this.cats, (cat: Cat) => { Stack() { Circle({ width: 30, height: 30 }).fillColor(this.getCatColor(cat.level)) Text(this.getCatEmoji(cat.level)).fontSize(20) } .alignContent(Alignment.Center) .position({ x: `${cat.x * 100 / COLS}%`, y: `${cat.y * 100 / ROWS}%` }) }, (cat: Cat) => `cat-${cat.id}`) } .width('100%').height('100%').zIndex(1) // 第 3 层:顶部 HUD(第 11 篇讲) GameHUD().zIndex(2) } .width('100%').height('100%') .alignContent(Alignment.Top) } }

八、调试技巧:猫咪渲染怎么调

  1. console.info打坐标:Builder 里加console.info('cat', cat.id, cat.x, cat.y),追每只猫定位。
  2. 密钥冲突排查:猫少了?查cat.id是否唯一,密钥cat-${id}是否冲突。
  3. 临时加 border:给 Stack 加.border({ width: 1, color: Color.Red }),看清猫占框。
  4. 真机看 Emoji:预览器某些 Emoji 缺字形,以真机为准

九、性能与最佳实践

  1. 密钥用 id 非坐标:坐标会变,用坐标做密钥每次移动都重建。id 不变,追踪移动复用。
  2. 圆背景 + Emoji 叠层:比纯 Emoji 棋子多一层视觉信息(等级色),且 Circle 矢量省资源。
  3. 百分比定位配百分比格子:棋盘格和猫都用百分比,屏宽变化整体缩放一致。
  4. 数组项内部改要重新赋值this.cats[0].y += 1不触发重渲,要this.cats = [...this.cats]强制 diff。

总结

本篇我们从猫咪层渲染切入,掌握Catinterface 数据契约、getCatEmoji/getCatColor等级映射函数、Stack叠圆背景 + Emoji、ForEach用 id 做密钥追踪移动、position百分比绝对定位五大要点,并给出了猫咪层完整代码。核心要点:interface 显式契约禁 any,密钥用 id 非坐标追踪移动,圆 30 配 Emoji 20 是 1.5:1 甜区,百分比定位响应式

下一篇我们将继续游戏页,拆解绝对定位的 position 坐标摆放细节。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

  • 「猫猫大作战」项目源码:本仓库entry/src/main/ets/pages/Index.etsentry/src/main/ets/components/GameTypes.ets
  • ForEach 循环渲染官方指南
  • Shape 容器官方指南
  • Stack API 参考
  • 状态管理概述 — HarmonyOS 官方文档
  • 开源鸿蒙跨平台社区
  • HarmonyOS 开发者官方文档首页
  • 系列索引:本仓库articles/INDEX.md