HarmonyOS游戏开发入门:用ArkTS打造经典贪吃蛇

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

引言

贪吃蛇,这款承载了无数人童年回忆的经典游戏,不仅是编程入门的绝佳练手项目,也能很好地帮助开发者熟悉一个新的开发环境和其 UI 框架。本文将带你从零开始,一步步使用 HarmonyOS 的 ArkUI 框架(基于 TypeScript)来构建一个功能完整、逻辑清晰的贪吃蛇游戏。我们将从最基础的地图绘制,到最终的面向对象封装,深入探索游戏开发的乐趣和 ArkUI 的强大之处。

仓库地址(贪吃蛇目录)https://gitee.com/harmonyos-projects/codelabs

目录

  1. 开发准备与环境搭建
  2. 第一步:绘制游戏地图
  3. 第二步:随机生成食物
  4. 第三步:创建并移动蛇
  5. 第四步:键盘控制与游戏逻辑
  6. 第五步:实现吃食物与计分
  7. 第六步:边界碰撞与游戏结束
  8. 第七步:游戏重置功能
  9. 进阶:面向对象封装
  10. 总结与展望

1. 开发准备与环境搭建

  • 工具 :安装最新版的 DevEco Studio
  • 语言 :本项目使用 ArkTS
  • 基础知识 :了解 HarmonyOS 应用开发的基本概念,如 @Entry@Componentbuild() 函数以及 State 装饰器等。

2. 第一步:绘制游戏地图

游戏的舞台是地图。我们将创建一个 20x20 的网格来作为我们的游戏区域。

核心思路 :使用 ForEach 循环嵌套,动态生成一个由小方块组成的二维网格。

scss 复制代码
// Index.ets
@Entry
@Component
struct SnakeGame {
  // 创建一个 20x20 的二维数组,用于表示地图网格
  private map: number[][] = Array(20).fill(null).map(() => Array(20).fill(0));

  build() {
    Column() {
      Text('贪吃蛇').fontSize(30).fontWeight(FontWeight.Bold).margin({ bottom: 10 });

      // 使用 Stack 来叠加地图、蛇和食物
      Stack() {
        // 绘制地图背景
        Column() {
          ForEach(this.map, (row: number[]) => {
            Row() {
              ForEach(row, () => {
                // 每个小方块代表一个单元格
                Column() {}.width(15).height(15)
                  .backgroundColor('#f0f0f0') // 浅灰色背景
                  .border({ width: 0.5, color: '#dddddd' }); // 加上边框,看起来更像网格
              })
            }
          })
        }
      }
      .margin({ top: 20 })
      .backgroundColor('#ffffff') // 地图容器背景
      .padding(5)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .alignItems(ItemAlign.Center)
    .backgroundColor('#e8e8e8');
  }
}

代码解析

  • 我们定义了一个 map 数组,它的唯一目的是提供一个数据源,让 ForEach 能够循环指定的次数(20 行,每行 20 列)。
  • Stack 组件是实现游戏层叠效果的关键,后续的蛇和食物都将作为子组件放在这个 Stack 中。
  • 每个单元格是一个 Column,通过设置固定的 widthheight 来控制其大小。

3. 第二步:随机生成食物

食物是蛇的目标。我们需要在地图上随机位置生成一个红色的方块。

核心思路 :在组件初始化时,随机生成食物的 X 和 Y 坐标,并使用 @State 装饰器使其成为响应式数据,以便在位置改变时 UI 能够自动更新。

typescript 复制代码
// Index.ets
// ... (省略之前的代码)
struct SnakeGame {
  private map: number[][] = Array(20).fill(null).map(() => Array(20).fill(0));
  // 使用 @State 装饰器,让食物位置变化时能刷新UI
  @State food: number[] = []; // [y, x]

  // 封装一个生成食物的函数
  private generateFood() {
    this.food = [
      Math.floor(Math.random() * 20), // 随机 Y 坐标 (0-19)
      Math.floor(Math.random() * 20)  // 随机 X 坐标 (0-19)
    ];
  }

  // 组件即将出现时调用
  aboutToAppear() {
    this.generateFood();
  }

  build() {
    Column() {
      // ... (省略标题)
      Stack() {
        // ... (省略地图绘制)

        // 绘制食物
        if (this.food.length > 0) {
          Text()
            .width(15)
            .height(15)
            .backgroundColor(Color.Red)
            .position({
              top: this.food[0] * 15,  // Y坐标 * 单元格高度
              left: this.food[1] * 15  // X坐标 * 单元格宽度
            });
        }
      }
      // ... (省略其他样式)
    }
    // ... (省略外层样式)
  }
}

代码解析

  • @State food: number[]food 数组的第一个元素是 Y 轴坐标,第二个是 X 轴坐标。@State 确保了一旦 food 的值改变,使用它的 UI 组件(这里是食物的 position)会重新渲染。
  • aboutToAppear():这是一个生命周期回调,在组件即将在界面上显示时执行。我们在这里调用 generateFood(),确保游戏一开始就有食物。
  • position({ top: ..., left: ... }):通过绝对定位将食物放置在计算好的位置上。

4. 第三步:创建并移动蛇

蛇是游戏的主角。它由多个方块组成,需要根据规则移动。

核心思路 :用一个二维数组 snake 来存储蛇身体每个部分的坐标。移动时,从蛇尾开始,每个身体部分都移动到前一个部分的位置,最后再根据当前方向移动蛇头。

kotlin 复制代码
// Index.ets
// ... (省略之前的代码)
struct SnakeGame {
  // ... (省略 map, food)
  @State snake: number[][] = [
    [10, 6], // 蛇头 [y, x]
    [10, 5],
    [10, 4],
    [10, 3],
    [10, 2]  // 蛇尾
  ];
  private direction: 'top' | 'left' | 'bottom' | 'right' = 'right'; // 初始方向向右
  private timer: number = 0; // 定时器ID

  // ... (省略 generateFood, aboutToAppear)

  // 蛇移动的核心逻辑
  private moveSnake() {
    // 从蛇尾开始,依次向前移动
    for (let i = this.snake.length - 1; i > 0; i--) {
      this.snake[i] = [...this.snake[i - 1]]; // 复制前一个节点的坐标
    }

    // 根据方向移动蛇头
    switch (this.direction) {
      case 'top':
        this.snake[0][0]--;
        break;
      case 'bottom':
        this.snake[0][0]++;
        break;
      case 'left':
        this.snake[0][1]--;
        break;
      case 'right':
        this.snake[0][1]++;
        break;
    }
    
    // 在 HarmonyOS 中,直接修改数组元素可能无法触发UI更新,
    // 因此我们创建一个新数组来触发重新渲染
    this.snake = [...this.snake];
  }

  build() {
    Column() {
      // ... (省略标题)
      Stack() {
        // ... (省略地图绘制)
        // ... (省略食物绘制)

        // 绘制蛇
        ForEach(this.snake, (segment: number[], index: number) => {
          Text()
            .width(15)
            .height(15)
            .backgroundColor(index === 0 ? Color.Pink : Color.Black) // 蛇头用粉色,身体用黑色
            .position({
              top: segment[0] * 15,
              left: segment[1] * 15
            });
        })
      }
      // ... (省略其他样式)
    }
    // ... (省略外层样式)
  }
}

代码解析

  • @State snake: number[][]snake 数组中的每个元素都是一个 [y, x] 坐标对,代表蛇身体的一个部分。数组的第一个元素是蛇头。
  • moveSnake():这是蛇移动的核心。通过循环,我们让蛇的每一节身体都 "继承" 前一节的位置,从而实现整体移动的效果。最后再单独处理蛇头的位置。
  • this.snake = [...this.snake];:这是一个在 ArkUI 中非常重要的技巧。由于 snake 是一个引用类型(数组),直接修改其内部元素(如 this.snake[0][0]--)并不会触发 UI 的重新渲染。通过创建一个新的数组(使用扩展运算符 ...)并赋值给 this.snake,我们可以强制触发 UI 更新。

5. 第四步:键盘控制与游戏逻辑

现在,我们需要让蛇动起来,并能通过按钮控制它的方向。

核心思路

  1. 添加 "开始"、"暂停"、"重置" 按钮。
  2. 使用 setInterval 定时器来周期性地调用 moveSnake 函数,让蛇自动移动。
  3. 添加方向控制按钮(上、下、左、右),并在点击时改变 direction 变量的值。
scss 复制代码
// Index.ets
// ... (省略之前的代码)
struct SnakeGame {
  // ... (省略 map, food, snake, direction, timer)
  @State score: number = 0;
  @State gameOverStr: string = '';

  // ... (省略 generateFood, aboutToAppear, moveSnake)

  // 开始游戏
  private startGame() {
    this.pauseGame(); // 先停止现有定时器,防止重复启动
    this.timer = setInterval(() => {
      this.moveSnake();
      this.checkCollisions(); // 移动后检查碰撞
    }, 200); // 每200毫秒移动一次
  }

  // 暂停游戏
  private pauseGame() {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = 0;
    }
  }

  build() {
    Column() {
      Text(`分数: ${this.score}`).fontSize(20).margin({ top: 10 });

      // 游戏控制按钮
      Row() {
        Button('开始').onClick(() => this.startGame());
        Button('暂停').margin({ left: 10 }).onClick(() => this.pauseGame());
        Button('重置').margin({ left: 10 }).onClick(() => this.resetGame());
      }.margin({ bottom: 10 });

      // ... (省略 Stack 中的地图、蛇、食物)

      // 方向控制按钮
      Column() {
        Button('↑').onClick(() => this.direction = 'top');
        Row() {
          Button('←').onClick(() => this.direction = 'left');
          Button('→').margin({ left: 20 }).onClick(() => this.direction = 'right');
        }.margin({ top: 5, bottom: 5 });
        Button('↓').onClick(() => this.direction = 'bottom');
      }
      .enabled(this.gameOverStr === '') // 游戏结束时禁用按钮
      .margin({ top: 20 });
    }
    // ... (省略外层样式)
  }
}

注意checkCollisionsresetGame 函数我们将在后续步骤中实现。

6. 第五步:实现吃食物与计分

当蛇头移动到食物的位置时,蛇的身体应该变长,分数应该增加,并且食物应该在新的位置重新生成。

核心思路 :在 moveSnake 之后,检查蛇头坐标是否与食物坐标重合。如果重合,则在蛇尾添加一个新的身体部分,并重新生成食物。

kotlin 复制代码
// Index.ets
// ... (省略之前的代码)
struct SnakeGame {
  // ... (省略所有变量和其他函数)

  // 检查碰撞(包括边界、自己、食物)
  private checkCollisions() {
    const head = this.snake[0];

    // 1. 检查是否吃到食物
    if (head[0] === this.food[0] && head[1] === this.food[1]) {
      this.score += 10;
      this.snake.push([...this.snake[this.snake.length - 1]]); // 在蛇尾添加一节
      this.generateFood();
      return; // 吃到食物后,不再检查死亡
    }

    // 后续将添加边界和自撞检测...
  }

  private startGame() {
    this.pauseGame();
    this.timer = setInterval(() => {
      this.moveSnake();
      this.checkCollisions(); // 调用检查函数
    }, 200);
  }

  // ... (省略 build 方法)
}

代码解析

  • checkCollisions 函数被放在 setInterval 中,在每次蛇移动后调用。
  • 通过比较蛇头 head 和食物 food 的坐标来判断是否吃到食物。
  • this.snake.push([...this.snake[this.snake.length - 1]]):当吃到食物时,我们在蛇数组的末尾添加一个新的元素,其坐标与当前蛇尾相同。由于下一次移动时,所有身体部分都会向前移动,这个新的部分就会自然地成为新的蛇尾,从而实现蛇身变长的效果。

7. 第六步:边界碰撞与游戏结束

游戏需要有结束条件。最常见的就是蛇头撞到地图边界或者撞到自己的身体。

核心思路 :在 checkCollisions 函数中,添加对边界和自身身体的检测。

kotlin 复制代码
// Index.ets
// ... (省略之前的代码)
struct SnakeGame {
  // ... (省略所有变量和其他函数)

  // 检查碰撞(包括边界、自己、食物)
  private checkCollisions() {
    const head = this.snake[0];

    // 1. 检查是否吃到食物 (上面已实现)
    if (head[0] === this.food[0] && head[1] === this.food[1]) {
      // ... (省略吃食物的逻辑)
      return;
    }

    // 2. 检查边界碰撞
    if (head[0] < 0 || head[0] >= 20 || head[1] < 0 || head[1] >= 20) {
      this.gameOver('游戏结束!撞到墙了!');
      return;
    }

    // 3. 检查自撞
    for (let i = 1; i < this.snake.length; i++) {
      if (head[0] === this.snake[i][0] && head[1] === this.snake[i][1]) {
        this.gameOver('游戏结束!撞到自己了!');
        return;
      }
    }
  }

  // 游戏结束
  private gameOver(message: string) {
    this.pauseGame();
    this.gameOverStr = message;
  }

  build() {
    Column() {
      // ... (省略其他UI)

      Stack() {
        // ... (省略地图、蛇、食物绘制)

        // 游戏结束提示
        if (this.gameOverStr) {
          Column() {
            Text(this.gameOverStr).fontSize(30).fontColor(Color.Red).fontWeight(FontWeight.Bold);
            Text('点击重置按钮重新开始').fontSize(16).fontColor(Color.Gray).margin({ top: 10 });
          }
          .justifyContent(FlexAlign.Center)
          .alignItems(ItemAlign.Center)
          .backgroundColor('rgba(255, 255, 255, 0.8)')
          .width('100%')
          .height('100%');
        }
      }
      // ... (省略其他UI)
    }
    // ... (省略外层样式)
  }
}

8. 第七步:游戏重置功能

游戏结束后,玩家需要一个可以重新开始的按钮。

核心思路 :创建一个 resetGame 函数,将所有游戏状态(蛇的位置、方向、食物、分数、游戏结束标志等)恢复到初始值。

csharp 复制代码
// Index.ets
// ... (省略之前的代码)
struct SnakeGame {
  // ... (省略所有变量和其他函数)

  // 重置游戏
  private resetGame() {
    this.pauseGame();
    this.snake = [
      [10, 6],
      [10, 5],
      [10, 4],
      [10, 3],
      [10, 2]
    ];
    this.direction = 'right';
    this.score = 0;
    this.gameOverStr = '';
    this.generateFood();
  }

  build() {
    Column() {
      // ...
      Button('重置').margin({ left: 10 }).onClick(() => this.resetGame());
      // ...
    }
  }
}

至此,一个功能完整的贪吃蛇游戏就已经完成了!你可以将以上代码整合到一个 .ets 文件中直接运行。

9. 进阶:面向对象封装 (OOP)

对于简单的应用,将所有逻辑放在一个组件里是可行的。但为了提高代码的可读性、可维护性和可扩展性,我们应该采用面向对象的思想对代码进行重构。

核心思路:将游戏中的核心元素(如蛇、食物、游戏控制器)抽象成独立的类。

9.1 创建领域模型 (Models)

Area.ts - 地图模型

typescript 复制代码
// models/Area.ts
export class Area {
    public readonly row: number;
    public readonly col: number;

    constructor(row: number = 20, col: number = 20) {
        this.row = row;
        this.col = col;
    }

    // 创建地图网格数据
    create(): number[][] {
        return Array(this.row).fill(null).map(() => Array(this.col).fill(0));
    }
}

Food.ts - 食物模型

typescript 复制代码
// models/Food.ts
export class Food {
    public data: number[] = []; // [y, x]

    constructor(private area: Area) {
        this.generate();
    }

    // 随机生成食物位置
    generate(): void {
        this.data = [
            Math.floor(Math.random() * this.area.row),
            Math.floor(Math.random() * this.area.col)
        ];
    }
}

Snake.ts - 蛇模型

kotlin 复制代码
// models/Snake.ts
export class Snake {
    public data: number[][] = []; // [ [y, x], [y, x], ... ]
    private direction: 'top' | 'left' | 'bottom' | 'right' = 'right';

    constructor() {
        this.init();
    }

    // 初始化蛇的位置
    init(): void {
        this.data = [
            [10, 6],
            [10, 5],
            [10, 4],
            [10, 3],
            [10, 2]
        ];
        this.direction = 'right';
    }

    // 改变方向
    changeDirection(newDirection: 'top' | 'left' | 'bottom' | 'right'): void {
        // 防止蛇向相反方向移动(例如正在向右,不能直接向左)
        const oppositeDirections = {
            'top': 'bottom',
            'bottom': 'top',
            'left': 'right',
            'right': 'left'
        };
        if (newDirection !== oppositeDirections[this.direction]) {
            this.direction = newDirection;
        }
    }

    // 移动一步
    move(): void {
        // 身体跟随
        for (let i = this.data.length - 1; i > 0; i--) {
            this.data[i] = [...this.data[i - 1]];
        }
        // 移动头部
        switch (this.direction) {
            case 'top': this.data[0][0]--; break;
            case 'bottom': this.data[0][0]++; break;
            case 'left': this.data[0][1]--; break;
            case 'right': this.data[0][1]++; break;
        }
    }

    // 吃到食物后增长
    grow(): void {
        this.data.push([...this.data[this.data.length - 1]]);
    }

    // 获取蛇头
    getHead(): number[] {
        return this.data[0];
    }
}

9.2 创建游戏控制器 (Controller)

GameController.ts - 游戏核心控制器

kotlin 复制代码
// controller/GameController.ts
import { Area } from '../models/Area';
import { Food } from '../models/Food';
import { Snake } from '../models/Snake';

export class GameController {
    public area: Area;
    public food: Food;
    public snake: Snake;
    public score: number = 0;
    public isGameOver: boolean = false;
    public gameOverMessage: string = '';

    private timer: number = 0;
    private speed: number = 200; // 移动速度,毫秒

    constructor(private row: number = 20, private col: number = 20) {
        this.area = new Area(row, col);
        this.snake = new Snake();
        this.food = new Food(this.area);
    }

    // 开始游戏
    start(): void {
        this.pause();
        this.isGameOver = false;
        this.gameOverMessage = '';
        this.timer = setInterval(() => {
            this.snake.move();
            if (!this.checkCollisions()) {
                this.gameLoop();
            }
        }, this.speed);
    }

    // 暂停游戏
    pause(): void {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = 0;
        }
    }

    // 重置游戏
    reset(): void {
        this.pause();
        this.score = 0;
        this.isGameOver = false;
        this.gameOverMessage = '';
        this.snake.init();
        this.food.generate();
    }

    // 游戏主循环逻辑
    private gameLoop(): void {
        // 游戏逻辑更新后,可以在这里通知UI刷新
    }

    // 检查所有碰撞
    private checkCollisions(): boolean {
        const head = this.snake.getHead();

        // 边界碰撞
        if (head[0] < 0 || head[0] >= this.area.row || head[1] < 0 || head[1] >= this.area.col) {
            this.endGame('撞到墙了!');
            return true;
        }

        // 自撞
        for (let i = 1; i < this.snake.data.length; i++) {
            if (head[0] === this.snake.data[i][0] && head[1] === this.snake.data[i][1]) {
                this.endGame('撞到自己了!');
                return true;
            }
        }

        // 食物碰撞
        if (head[0] === this.food.data[0] && head[1] === this.food.data[1]) {
            this.score += 10;
            this.snake.grow();
            this.food.generate();
        }
        
        return false; // 没有发生导致游戏结束的碰撞
    }

    // 结束游戏
    private endGame(message: string): void {
        this.pause();
        this.isGameOver = true;
        this.gameOverMessage = message;
    }
}

9.3 重构 UI 组件

Index.ets - 最终的 UI 展示

scss 复制代码
// Index.ets
import { GameController } from '../controller/GameController';

@Entry
@Component
struct SnakeGameUI {
    // 实例化游戏控制器
    private gameController: GameController = new GameController();
    
    // UI状态
    @State score: number = 0;
    @State gameOverStr: string = '';
    @State snakeSegments: number[][] = [];
    @State foodPos: number[] = [];

    build() {
        Column() {
            Text('贪吃蛇 (OOP版)').fontSize(30).fontWeight(FontWeight.Bold);
            Text(`分数: ${this.score}`).fontSize(20).margin({ top: 5 });

            // 控制按钮
            Row() {
                Button('开始').onClick(() => {
                    this.gameController.start();
                    this.updateUI(); // 开始后立即更新一次UI
                });
                Button('暂停').margin({ left: 10 }).onClick(() => this.gameController.pause());
                Button('重置').margin({ left: 10 }).onClick(() => {
                    this.gameController.reset();
                    this.updateUI(); // 重置后立即更新UI
                });
            }.margin({ bottom: 10 });

            // 游戏区域
            Stack() {
                // 地图
                Column() {
                    ForEach(this.gameController.area.create(), (row: number[]) => {
                        Row() {
                            ForEach(row, () => {
                                Column() {}.width(15).height(15).backgroundColor('#f0f0f0').border({ width: 0.5, color: '#dddddd' });
                            })
                        }
                    })
                }

                // 食物
                Text().width(15).height(15).backgroundColor(Color.Red)
                    .position({ top: this.foodPos[0] * 15, left: this.foodPos[1] * 15 });

                // 蛇
                ForEach(this.snakeSegments, (segment: number[], index: number) => {
                    Text().width(15).height(15)
                        .backgroundColor(index === 0 ? Color.Pink : Color.Black)
                        .position({ top: segment[0] * 15, left: segment[1] * 15 });
                })

                // 游戏结束 overlay
                if (this.gameOverStr) {
                    Column() {
                        Text('游戏结束').fontSize(30).fontColor(Color.Red).fontWeight(FontWeight.Bold);
                        Text(this.gameOverStr).fontSize(20).margin({ top: 10 });
                    }
                    .justifyContent(FlexAlign.Center)
                    .alignItems(ItemAlign.Center)
                    .backgroundColor('rgba(255, 255, 255, 0.7)')
                    .width('100%')
                    .height('100%');
                }
            }
            .width(this.gameController.area.col * 15 + 10) // 加上padding
            .height(this.gameController.area.row * 15 + 10)
            .backgroundColor('#ffffff')
            .padding(5)

            // 方向键
            Column() {
                Button('↑').onClick(() => this.gameController.snake.changeDirection('top'));
                Row() {
                    Button('←').onClick(() => this.gameController.snake.changeDirection('left'));
                    Button('→').margin({ left: 20 }).onClick(() => this.gameController.snake.changeDirection('right'));
                }.margin({ top: 5, bottom: 5 });
                Button('↓').onClick(() => this.gameController.snake.changeDirection('bottom'));
            }
            .enabled(!this.gameController.isGameOver)
            .margin({ top: 20 })

        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Start)
        .alignItems(ItemAlign.Center)
        .backgroundColor('#e8e8e8')
        .onChange(() => {
            // 这是一个简化的UI刷新机制。在更复杂的应用中,你可能需要使用事件总线或状态管理库。
            // 这里假设游戏状态变化时会触发UI的重新渲染,从而调用updateUI
            this.updateUI();
        })
    }

    // 同步游戏状态到UI
    private updateUI() {
        this.score = this.gameController.score;
        this.gameOverStr = this.gameController.gameOverMessage;
        this.snakeSegments = [...this.gameController.snake.data];
        this.foodPos = [...this.gameController.food.data];
    }
}

注意 :上面的 onChange 是一种简化的模拟。在实际的 HarmonyOS 开发中,更优雅的方式是使用 EventBus 或者让控制器持有一个 UI 的回调接口,当游戏状态改变时主动通知 UI 刷新。这里的重点是展示如何将逻辑与 UI 分离。


10. 总结与展望

恭喜你!你已经成功地使用 HarmonyOS ArkUI 框架从零开始构建了一个贪吃蛇游戏,并且还学习了如何使用面向对象的思想来重构代码。

本项目涉及的关键技术点:

  • ArkUI 布局与组件(Column, Row, Stack, ForEach, Button, Text)。
  • 响应式状态管理(@State)。
  • 定时器的使用(setInterval, clearInterval)。
  • 事件处理(onClick)。
  • 游戏开发的基本模式(游戏循环、状态更新、碰撞检测)。
  • 面向对象编程(封装、继承、多态的初步实践)。

未来可以扩展的功能:

  1. 游戏难度选择 :通过调整 speed 变量来实现不同的难度。
  2. 记录最高分 :使用 Preferences 存储用户的最高得分。
  3. 更丰富的视觉效果:为蛇和食物添加图片或动画。
  4. 使用物理按键或手势控制:除了屏幕按钮,可以监听设备的物理方向键或滑动手势。
  5. 完善的状态管理 :使用 EventBusRedux 等模式,让控制器和 UI 的通信更加解耦和高效。

希望这篇详细的教程能帮助你更好地理解 HarmonyOS 应用开发,并激发你开发更多有趣应用的灵感!

鸿蒙开发者班级

✨家人们点个账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个账号关注,会持续发布大前端领域技术文章💕 🍃

^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

相关推荐
IT充电站27 分钟前
HarmonyOS游戏开发入门:用ArkTS打造经典五子棋
harmonyos·arkts
q***R3082 小时前
HarmonyOS在智能家居中的场景模式
华为·智能家居·harmonyos
可观测性用观测云2 小时前
为鸿蒙生态注入可观测动力:观测云 HarmonyOS SDK 重磅上线
harmonyos
xq95274 小时前
鸿蒙next sqlite进阶版本来了
harmonyos
c***97985 小时前
HarmonyOS在智能车载系统的集成
华为·车载系统·harmonyos
1***s6326 小时前
HarmonyOS智能电视应用开发指南
华为·harmonyos·智能电视
lqj_本人10 小时前
鸿蒙Cordova开发踩坑记录:跨域请求的“隐形墙“
harmonyos
Z***258014 小时前
HarmonyOS在物联网场景的应用
物联网·华为·harmonyos
Pocker_Spades_A16 小时前
John the Ripper 在 HarmonyOS 上的构建与适配
华为·harmonyos