HarmonyOS NEXT 实战:从零打造一个「能用的」计算器 App

很多开发者第一次接触 HarmonyOS NEXT,往往不知道从何下手。官方文档看了不少,但真到动手写项目的时候,还是一脸懵——ArkTS 的语法跟 TypeScript 哪里不一样?ArkUI 的声明式写法怎么组织代码?业务逻辑和 UI 怎么分离?

这篇文章不讲大道理,只带你从零写一个真正能用的计算器。四则运算、百分数、正负号、连续运算、除零保护,全都安排上。我会把每一步的思考过程、踩过的坑、最终方案完整记录下来,你可以直接照着做。


一、项目概览:我们要做什么

先看最终效果——一个界面简洁、功能完整的计算器:

  • 顶部显示区域:表达式预览 + 当前数值
  • 底部按钮区域:数字键、运算符键、功能键(AC / +/- / %)
  • 支持连续运算(如 3 + 5 × 2 =
  • 除零保护,不会崩溃
  • 数字过长时自动截断,防止溢出

技术栈:

  • HarmonyOS NEXT API 23(SDK 6.1.0)
  • ArkTS + ArkUI 声明式开发
  • 无第三方依赖,纯原生实现

项目结构:

MyApplication/
├── AppScope/
│   ├── app.json5                          # 应用全局配置
│   └── resources/base/element/string.json # 应用级字符串
├── entry/
│   ├── src/main/
│   │   ├── module.json5                   # 模块配置
│   │   ├── ets/
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets       # Ability 生命周期
│   │   │   ├── entrybackupability/
│   │   │   │   └── EntryBackupAbility.ets # 备份扩展
│   │   │   ├── model/
│   │   │   │   └── CalcEngine.ets         # 计算器引擎(核心逻辑)
│   │   │   └── pages/
│   │   │       └── Index.ets              # 计算器主界面
│   │   └── resources/
│   │       ├── base/element/
│   │       │   ├── string.json            # 模块字符串资源
│   │       │   ├── color.json             # 颜色资源
│   │       │   └── float.json             # 尺寸资源
│   │       ├── base/profile/
│   │       │   └── main_pages.json        # 页面路由配置
│   │       └── base/media/                # 图标资源
│   └── build-profile.json5
├── build-profile.json5                    # 工程构建配置
└── hvigorfile.ts                          # 构建脚本

核心代码只有两个文件:CalcEngine.ets(业务逻辑)和 Index.ets(UI 界面)。这就是 HarmonyOS NEXT 的 MVC 分层思路——Model 负责数据与计算,View 负责渲染与交互,干净利落。


二、搭建项目:DevEco Studio 里的那些事

2.1 创建项目

打开 DevEco Studio,选择 File → New → Create Project,选择 Empty Ability 模板。

关键配置:

配置项 说明
Project Name MyApplication 项目名
Bundle Name com.calculator.app 应用包名
Compatible SDK 6.1.0(23) API 23
Module Type Entry 主模块
Device Type Phone 手机

创建完成后,DevEco Studio 会自动生成项目骨架。我们要改的主要是 ets/ 下的代码。

2.2 app.json5 配置

AppScope/app.json5 是应用的全局配置,我们修改了 bundleName 和版本信息:

{
  "app": {
    "bundleName": "com.calculator.app",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}

2.3 module.json5 配置

entry/src/main/module.json5 定义了模块能力,关键部分:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "deviceTypes": ["phone"],
    "pages": "$profile:main_pages",
    "abilities": [{
      "name": "EntryAbility",
      "srcEntry": "./ets/entryability/EntryAbility.ets",
      "exported": true,
      "skills": [{
        "entities": ["entity.system.home"],
        "actions": ["ohos.want.action.home"]
      }]
    }]
  }
}

skills 里配置了 ohos.want.action.home,这样我们的应用就能出现在桌面上,用户点击图标即可启动。

2.4 main_pages.json 页面路由

{
  "src": ["pages/Index"]
}

目前只有一个页面,后续如果想加历史记录页、设置页,在这里注册路由即可。


三、核心逻辑:CalcEngine 计算引擎

这是整个项目最核心的部分。计算器看似简单,但里面的状态管理比想象中复杂——你得处理"用户按完运算符后按数字是追加还是替换"“连续按运算符怎么处理”"按完等号再按数字如何重置"等一系列边界情况。

3.1 设计思路

我把计算器引擎设计为一个纯逻辑类,不依赖任何 ArkUI 组件。好处显而易见:

  1. 可独立测试:不需要启动模拟器就能验证计算逻辑
  2. 关注点分离:UI 只负责展示和转发事件,引擎只负责计算
  3. 可复用:如果以后做科学计算器,引擎可以扩展而不影响 UI

引擎需要维护以下状态:

private _display: string = '0';             // 当前显示文本
private _previousOperand: number = 0;       // 上一个操作数
private _currentOperand: number = 0;        // 当前操作数
private _pendingOperator: CalcOperator = CalcOperator.NONE;  // 待执行运算符
private _isNewEntry: boolean = false;       // 是否正在输入新数字
private _justEvaluated: boolean = false;    // 是否刚按了等号
private _expressionPreview: string = '';    // 表达式预览
private _hasError: boolean = false;         // 是否发生错误

3.2 运算符枚举

export enum CalcOperator {
  NONE = '',
  ADD = '+',
  SUBTRACT = '−',
  MULTIPLY = '×',
  DIVIDE = '÷'
}

这里用 Unicode 字符 (U+2212)和 ×(U+00D7)而不是 ASCII 的 -*,这样在界面上显示更美观,也跟 iOS 计算器保持一致。

3.3 输入数字:inputDigit

这是最基本也是最需要仔细处理的逻辑:

inputDigit(digit: string): void {
  if (this._hasError) return;

  // 刚算完结果,按数字开始新计算
  if (this._justEvaluated) {
    this.clearAll();
    this._justEvaluated = false;
  }

  if (this._isNewEntry) {
    // 刚按了运算符,显示区替换为新数字
    this._display = digit;
    this._isNewEntry = false;
  } else {
    if (digit === '.') {
      // 小数点:已有则忽略
      if (this._display.includes('.')) return;
      this._display += '.';
    } else {
      if (this._display === '0') {
        // 前导零替换
        this._display = digit;
      } else {
        // 限制输入长度,防止溢出
        if (this._display.replace('-', '').replace('.', '').length >= 15) return;
        this._display += digit;
      }
    }
  }
  this._currentOperand = parseFloat(this._display);
}

几个关键点:

  1. 错误状态下禁止输入:如果除零报错了,必须先按 AC 才能继续操作
  2. _justEvaluated 机制:按完 = 得到结果后,再按数字应该开始全新计算,而不是在结果后面追加
  3. 前导零处理:显示 0 时按 5,应该变成 5 而不是 05
  4. 小数点去重:已经有小数点就不能再按
  5. 长度限制:15 位有效数字,防止浮点数精度问题

3.4 输入运算符:inputOperator

运算符的逻辑最复杂,因为要处理三种场景:

inputOperator(op: CalcOperator): void {
  if (this._hasError) return;

  if (this._pendingOperator !== CalcOperator.NONE && !this._isNewEntry) {
    // 场景1:连续运算 —— 3 + 5 × → 先算 3+5=8,然后等待 × 的下一个数
    this.evaluate();
  } else if (this._isNewEntry && this._pendingOperator !== CalcOperator.NONE) {
    // 场景2:替换运算符 —— 3 + 然后按 × → 变成 3 ×
    this._pendingOperator = op;
    this._expressionPreview = this.formatOperand(this._previousOperand) + ' ' + op + ' ';
    return;
  }

  // 场景3:首次输入运算符,保存当前数
  this._previousOperand = parseFloat(this._display);
  this._pendingOperator = op;
  this._isNewEntry = true;
  this._justEvaluated = false;

  this._expressionPreview = this.formatOperand(this._previousOperand) + ' ' + op + ' ';
}

三种场景的通俗解释:

场景 用户操作 引擎行为
连续运算 3 + 5 × 先算 3+5=8,把 8 存为上一操作数,等待输入 × 后面的数
替换运算符 3 + × 用户改主意了,把 + 换成 ×,不执行计算
首次运算 3 + 保存 3,等待下一个操作数

3.5 执行计算:evaluate

evaluate(): string {
  if (this._hasError || this._pendingOperator === CalcOperator.NONE) {
    return this._display;
  }

  this._currentOperand = parseFloat(this._display);
  const prev = this._previousOperand;
  const curr = this._currentOperand;
  let result: number;

  switch (this._pendingOperator) {
    case CalcOperator.ADD:
      result = prev + curr;
      break;
    case CalcOperator.SUBTRACT:
      result = prev - curr;
      break;
    case CalcOperator.MULTIPLY:
      result = prev * curr;
      break;
    case CalcOperator.DIVIDE:
      if (curr === 0) {
        // 除零保护
        this._hasError = true;
        this._display = '错误';
        this._expressionPreview = '';
        this._pendingOperator = CalcOperator.NONE;
        return '错误';
      }
      result = prev / curr;
      break;
    default:
      return this._display;
  }

  // 构建完整表达式预览:5 + 3 =
  this._expressionPreview =
    this.formatOperand(prev) + ' ' + this._pendingOperator + ' ' +
    this.formatOperand(curr) + ' =';

  this._display = this.formatResult(result);
  this._previousOperand = result;
  this._pendingOperator = CalcOperator.NONE;
  this._isNewEntry = true;
  this._justEvaluated = true;

  return this._display;
}

除零保护是最关键的安全措施。如果不处理,JavaScript 的 InfinityNaN 会在 UI 上显示一堆奇怪的东西,甚至导致后续逻辑崩溃。这里直接设 _hasError = true,之后所有操作都被阻断,直到用户按 AC。

3.6 辅助功能

引擎还提供了几个辅助功能:

/** 清除所有 */
clearAll(): void {
  this._display = '0';
  this._previousOperand = 0;
  this._currentOperand = 0;
  this._pendingOperator = CalcOperator.NONE;
  this._isNewEntry = false;
  this._justEvaluated = false;
  this._expressionPreview = '';
  this._hasError = false;
}

/** 正负号切换 */
toggleSign(): void {
  if (this._hasError || this._display === '0') return;
  this._display = this._display.startsWith('-')
    ? this._display.substring(1)
    : '-' + this._display;
  this._currentOperand = parseFloat(this._display);
}

/** 百分数 */
percentage(): void {
  if (this._hasError) return;
  const val = parseFloat(this._display) / 100;
  this._display = this.formatResult(val);
  this._currentOperand = parseFloat(this._display);
}

3.7 数字格式化

计算结果需要合理格式化,避免 0.1 + 0.2 = 0.30000000000000004 这种经典浮点问题:

private formatResult(num: number): string {
  if (!isFinite(num)) return '错误';
  if (Math.abs(num) < 1e-10) return '0';
  if (Number.isInteger(num) && Math.abs(num) < 1e15) return num.toString();
  if (Math.abs(num) > 1e15) return num.toExponential(6);
  return parseFloat(num.toFixed(10)).toString();
}

处理策略:

  • 非有限数(InfinityNaN)→ 显示"错误"
  • 极小值(< 1e-10)→ 显示 0
  • 整数 → 直接显示
  • 超大数 → 科学计数法
  • 普通小数 → 保留最多 10 位有效小数

四、UI 界面:ArkUI 声明式布局

有了引擎,接下来是界面。ArkUI 的声明式语法跟 SwiftUI 很像——你描述"界面应该长什么样",框架负责把它渲染出来。

4.1 整体布局结构

@Entry
@Component
struct Index {
  @State display: string = '0';
  @State expression: string = '';
  @State hasError: boolean = false;

  private engine: CalcEngine = new CalcEngine();

  build() {
    Column() {
      // 显示区域
      Column() { ... }
        .layoutWeight(1)  // 占据剩余空间

      // 分隔线
      Divider()

      // 按钮区域
      Column({ space: 12 }) { ... }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA')
  }
}

整体是一个纵向 Column:上面显示区域用 layoutWeight(1) 撑满剩余空间,下面按钮区域按内容高度排列。这样无论屏幕多大,显示区域都会自动伸缩。

4.2 显示区域

Column() {
  // 表达式预览(小字,灰色)
  Text(this.expression)
    .fontSize(16)
    .fontColor('#999')
    .width('100%')
    .textAlign(TextAlign.End)
    .margin({ top: 20, bottom: 4 })
    .maxLines(1)
    .textOverflow({ overflow: TextOverflow.Ellipsis })

  // 主显示(大字,加粗)
  Text(this.display)
    .fontSize(this.hasError ? 32 : 48)
    .fontWeight(FontWeight.Bold)
    .fontColor(this.hasError ? '#E74C3C' : '#1a1a2e')
    .width('100%')
    .textAlign(TextAlign.End)
    .maxLines(1)
    .textOverflow({ overflow: TextOverflow.Ellipsis })
}
.width('100%')
.padding({ left: 24, right: 24, top: 40, bottom: 20 })
.layoutWeight(1)
.justifyContent(FlexAlign.End)

两个 Text 组件纵向排列:

  • 上面的小字显示表达式预览(如 5 + 3 =),灰色、右对齐、超长省略
  • 下面的大字显示当前数值,右对齐、加粗

错误时字体变小(32 → 48)、颜色变红(#E74C3C),给用户明确的视觉反馈。

4.3 按钮区域——@Builder 复用

5 行 4 列的按钮如果每个都手写,代码会非常冗长。我用 @Builder 做了复用:

@Builder
ButtonRow(labels: string[]) {
  Row() {
    ForEach(labels, (label: string) => {
      this.CalcButton(label)
    }, (label: string) => label)
  }
  .width('100%')
  .height(64)
  .padding({ left: 16, right: 16 })
}

@Builder
CalcButton(label: string) {
  Text(label)
    .fontSize(26)
    .fontColor(this.getBtnTextColor(label))
    .fontWeight(this.getBtnFontWeight(label))
    .width('calc((100% - 12px * 4) / 4)')
    .height(64)
    .textAlign(TextAlign.Center)
    .backgroundColor(this.getBtnBg(label))
    .borderRadius(32)
    .margin({ left: 6, right: 6 })
    .onClick(() => this.onButtonClick(label))
}

按钮宽度用了 calc() 表达式:(100% - 12px × 4) / 4,即总宽度减去 4 个间隔后均分。ArkUI 的 calc() 支持跟 CSS 一样的计算语法,非常方便。

按钮配色方案:

按钮类型 背景色 文字色 示例
功能键(AC / +/- / %) #E8E8E8 浅灰 #1a1a2e 深色 AC
数字键 #3a3a4a 深灰 #fff 白色 7
运算符键 #FF6B35 橙色 #fff 白色 +
等号键 #1a1a2e 深色 #fff 白色 =
private getBtnBg(label: string): ResourceColor {
  if (label === 'AC' || label === '+/-' || label === '%') return '#E8E8E8';
  if (label === '÷' || label === '×' || label === '−' || label === '+') return '#FF6B35';
  if (label === '=') return '#1a1a2e';
  return '#3a3a4a';
}

4.4 第五行按钮的特殊处理

最后一行(0 . =)的 0 键比较宽,需要特殊处理:

Row() {
  Text('0')
    .fontSize(26).fontColor('#fff')
    .width(136).height(64).textAlign(TextAlign.Center)
    .backgroundColor('#3a3a4a').borderRadius(32)
    .margin({ left: 6, right: 6 })
    .onClick(() => this.onButtonClick('0'))

  Text('.')
    .fontSize(26).fontColor('#fff')
    .width(64).height(64).textAlign(TextAlign.Center)
    .backgroundColor('#3a3a4a').borderRadius(32)
    .margin({ left: 6, right: 6 })
    .onClick(() => this.onButtonClick('.'))

  Text('=')
    .fontSize(26).fontColor('#fff').fontWeight(FontWeight.Bold)
    .width(64).height(64).textAlign(TextAlign.Center)
    .backgroundColor('#1a1a2e').borderRadius(32)
    .margin({ left: 6, right: 6 })
    .onClick(() => this.onButtonClick('='))
}
.width('100%')
.height(64)
.padding({ left: 16, right: 16 })

0 键宽度 136px,是普通按钮的两倍左右,模拟真实计算器的宽零键效果。

4.5 事件处理:onButtonClick

所有按钮点击最终汇聚到 onButtonClick

onButtonClick(label: string): void {
  switch (label) {
    case 'AC':    this.engine.clearAll(); break;
    case '+/-':   this.engine.toggleSign(); break;
    case '%':     this.engine.percentage(); break;
    case '÷':     this.engine.inputOperator(CalcOperator.DIVIDE); break;
    case '×':     this.engine.inputOperator(CalcOperator.MULTIPLY); break;
    case '−':     this.engine.inputOperator(CalcOperator.SUBTRACT); break;
    case '+':     this.engine.inputOperator(CalcOperator.ADD); break;
    case '=':     this.engine.evaluate(); break;
    case '.':     this.engine.inputDigit('.'); break;
    default:      this.engine.inputDigit(label); break;
  }
  this.refreshDisplay();
}

每次操作后调用 refreshDisplay() 把引擎状态同步到 UI:

private refreshDisplay(): void {
  this.display = this.engine.display;
  this.expression = this.engine.expressionPreview;
  this.hasError = this.engine.hasError;
}

这里体现了 ArkUI 响应式更新的核心@State 变量一旦被赋新值,UI 自动刷新,不需要手动操作 DOM。


五、Ability 生命周期:EntryAbility

虽然计算器不需要复杂的生命周期管理,但 EntryAbility 仍然是应用的入口,理解它的执行流程很重要:

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 设置颜色模式跟随系统
    this.context.getApplicationContext()
      .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(DOMAIN, 'testTag', 'Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 加载主页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }
}

生命周期顺序:onCreateonWindowStageCreate → 页面渲染 → onForeground → 用户交互 → onBackgroundonWindowStageDestroyonDestroy

onWindowStageCreate 里的 loadContent('pages/Index') 就是加载我们写的 Index.ets 页面。


六、踩坑记录

坑 1:按钮宽度 calc() 表达式的写法

最初我写的是 width('25%'),4 个按钮各 25%,但加上 margin 后总宽度超出屏幕,最后一列被挤出去了。

解决方案:用 calc((100% - 12px * 4) / 4) 精确计算。4 个按钮之间有 4 个 margin 间隔(每个 6px × 2 侧 = 12px),减去后均分。

ArkUI 的 calc() 支持加减乘除和混合单位运算,语法基本兼容 CSS,这是做精确布局的利器。

坑 2:连续运算的顺序问题

计算器不是简单地把两个数算一下就完事了。用户可能这样操作:3 + 5 × 2 =

在真实的计算器中,这不是先乘后加(那是数学规则),而是从左到右依次计算——这是所有手机计算器的通用行为。所以 3 + 5 × 2 的计算过程是:

  1. 3 + 5 → 8
  2. 8 × 2 → 16

而不是数学上的 5 × 2 = 10, 3 + 10 = 13

inputOperator 里,当检测到已有待执行运算符且用户又按了新运算符时,先执行前一个运算,再把结果存为 _previousOperand,等待下一个操作数。

坑 3:0.1 + 0.2 的浮点精度问题

JavaScript/ArkTS 的浮点数遵循 IEEE 754 标准,所以 0.1 + 0.2 = 0.30000000000000004

formatResult 方法中,我用 parseFloat(num.toFixed(10)) 来截断多余的小数位。toFixed(10) 保留 10 位小数,parseFloat 再去掉尾部的零。这样 0.30000000000000004 就变成了 0.3

坑 4:按完等号再按数字的预期行为

这个场景很容易被忽略:用户按了 3 + 5 = 得到 8,然后按 7,预期是什么?

  • 错误行为:显示变成 87(在结果后面追加)
  • 正确行为:显示变成 7(开始全新计算)

通过 _justEvaluated 标志实现:按 = 后设为 true,下次 inputDigit 检测到它就 clearAll() 重新开始。

坑 5:Text 组件溢出处理

计算结果可能很长(比如 1.2345678901234),不做处理会撑爆布局。

Text(this.display)
  .maxLines(1)
  .textOverflow({ overflow: TextOverflow.Ellipsis })

maxLines(1) 限制单行,textOverflow 让超长文本显示省略号。同时引擎内部做了 15 位有效数字的截断,从源头控制长度。


七、构建与运行

7.1 构建配置

build-profile.json5 中的关键配置:

{
  "app": {
    "products": [{
      "name": "default",
      "targetSdkVersion": "6.1.0(23)",
      "compatibleSdkVersion": "6.1.0(23)",
      "runtimeOS": "HarmonyOS"
    }]
  }
}

targetSdkVersioncompatibleSdkVersion 都设为 API 23,确保使用最新的 HarmonyOS NEXT 能力。

7.2 运行

  1. 连接真机或启动模拟器
  2. 点击 DevEco Studio 的运行按钮(▶️)
  3. 等待编译部署完成

截图占位:
在这里插入图片描述


八、架构思考:为什么要把引擎分离出来

你可能觉得一个计算器没必要搞 MVC,全写在 Index.ets 里也能跑。但我想说几个实际的好处:

8.1 可测试性

引擎类不依赖任何 ArkUI API,你可以直接在 Node.js 里跑单元测试:

const engine = new CalcEngine();
engine.inputDigit('3');
engine.inputOperator(CalcOperator.ADD);
engine.inputDigit('5');
engine.evaluate();
console.log(engine.display); // "8"

如果逻辑混在 UI 里,你得启动模拟器才能验证,效率天差地别。

8.2 可维护性

想象一下,产品经理说要加"历史记录"功能。如果逻辑和 UI 耦合,你得在按钮点击回调里同时管理 UI 状态和历史记录逻辑,代码很快就会变成面条。

分离后,CalcEngine 暴露一个 history 数组就行,UI 只负责展示。

8.3 可复用性

引擎是纯逻辑,没有 UI 依赖。如果以后要:

  • 做一个带侧边栏的平板计算器
  • 做一个语音计算器
  • 在 Widget 卡片里显示计算结果

引擎代码零改动,只换 UI 层就行。


九、可能的扩展方向

这个计算器是 MVP 版本,还有很多可以加的功能:

  1. 历史记录:用 @ohos.data.preferences 或轻量数据库存储运算历史
  2. 科学计算:加 sincoslog 等函数,扩展 CalcEngine 即可
  3. 键盘输入:监听物理键盘事件,在 onKeyEvent 中处理
  4. 深色模式:监听系统颜色模式切换,动态修改配色方案
  5. 横屏布局:检测屏幕方向,横屏时展示科学计算器模式
  6. 手势操作:左滑退格、长按复制结果等交互优化
  7. 动画效果:按钮按下缩放、数字切换过渡动画

十、总结

这篇文章从零开始,完整实现了一个 HarmonyOS NEXT 计算器应用。核心要点:

  1. 项目结构model/CalcEngine.ets + pages/Index.ets,清晰的 MVC 分层
  2. 计算引擎:独立于 UI 的纯逻辑类,处理输入、运算、状态管理、错误保护
  3. ArkUI 界面@Builder 复用按钮组件,@State 响应式更新,calc() 精确布局
  4. 边界处理:连续运算、运算符替换、除零保护、浮点精度、等号后重置
  5. 踩坑经验:5 个实战踩坑记录,每个都有原因分析和解决方案

HarmonyOS NEXT 的开发体验说实话还不错——ArkTS 本质上就是带类型系统的 TypeScript,ArkUI 的声明式写法也很直观。主要的学习曲线在两个地方:一是项目结构和配置体系(module.json5build-profile.json5、各种 json5),二是 ArkUI 的组件 API(有哪些属性可以设、怎么设)。

这两个问题没有捷径,只能多看官方文档、多写代码。但好消息是,一旦上手,开发效率是很高的——尤其是习惯了声明式 UI 之后,再回去写命令式 UI 反而觉得别扭。


作者注:本文基于 HarmonyOS NEXT SDK 6.1.0(23) 编写,API 可能随版本迭代发生变化,请以最新官方文档为准。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐