一、前言

在前一篇文章中,我们详细介绍了 HarmonyOS 原生应用开发环境 的搭建过程,包括 DevEco Studio 安装、SDK 配置、模拟器创建和真机调试的全流程。不少读者反馈说虽然环境搭起来了,但不知道下一步该做什么,缺一个真正能上手练的项目。所以今天我们就在已经搭建好的环境基础上,从零开始开发一个完整的鸿蒙计算器应用

为什么选择计算器?因为计算器是移动端最经典、最适合入门的练手项目。它麻雀虽小五脏俱全:

  • 页面布局涉及 ArkUI 的 Column、Row、Text、Button 等多种组件
  • 交互逻辑涵盖按钮点击、状态管理、事件分发
  • 运算逻辑涉及数字拼接、小数点处理、运算符优先级
  • 边缘情况处理包括除零错误、数值溢出、连续运算

一个计算器做下来,ArkUI 开发的核心知识就基本都过了一遍。本文将从项目创建开始,带你一步步实现一个功能完善、界面精美的深色主题计算器,所有代码都会详细拆解,每一行都能看懂。


二、项目准备

2.1 开发环境要求

在开始之前,请确保你的环境满足以下条件:

  • IDE:DevEco Studio(推荐最新版本,可从华为开发者官网下载)
  • SDK:API 23 或以上在 SDK Manager 中安装
  • 模拟器或真机:建议先用模拟器调试,再上真机
  • 语言框架:ArkTS + ArkUI(HarmonyOS 原生声明式开发栈)

如果还没有搭建环境,可以参考上一篇文章《HarmonyOS 原生应用开发环境搭建指南》完成配置。

2.2 新建项目

打开 DevEco Studio,按以下步骤创建项目:

  1. 点击 File → New → Create Project
  2. 选择 Empty Ability 模板(最基础的模板,没有任何示例代码干扰)
  3. 在弹出的配置页面填写项目信息:
配置项 推荐值 说明
项目名称 Calculator 全部英文,不要出现中文
包名 com.example.calculator 应用唯一标识,反向域名格式
保存路径 英文路径 不要放在有中文或空格的目录下
Compile SDK API 23 或以上 根据你安装的 SDK 版本选择
模块名称 entry 默认即可,这是应用入口模块
  1. 点击 Finish 完成创建

创建完成后,DevEco Studio 会开始自动同步项目配置,第一次同步可能需要等待几分钟,取决于网络速度和电脑配置。同步完成后可以看到完整的项目目录结构。


三、项目结构分析

新建的项目默认生成了完整的工程骨架,这是 HarmonyOS 应用的标准项目结构:

Calculator/
├── AppScope/
│   └── app.json5                ← 应用级配置(包名、版本号、API 等级)
├── entry/
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/    ← Ability 生命周期管理
│   │   │   │   └── EntryAbility.ts
│   │   │   └── pages/           ← 页面文件(⭐ 我们主要修改这里)
│   │   │       └── Index.ets    ← 计算器主页面
│   │   ├── module.json5         ← 模块配置(注册 Ability、页面路由)
│   │   └── resources/           ← 资源文件(字符串、颜色、图标等)
│   └── oh-package.json5         ← 模块依赖配置
├── build-profile.json5          ← 构建配置
├── hvigorfile.ts                ← Hvigor 构建脚本
└── oh-package.json5             ← 项目依赖配置

我们的计算器只需要修改一个文件—— entry/src/main/ets/pages/Index.ets。其他所有配置文件都由 IDE 自动管理,不需要手动改动,这也是使用 IDE 向导创建项目的最大好处:环境配置零成本,专注写代码


四、计算器 UI 设计

4.1 整体布局

计算器的 UI 采用经典的上下结构,上方是结果显示区域,下方是按钮网格:

┌─────────────────────────┐
│                         │
│         123.45          │  ← 显示区域(深色面板,右对齐)
│                         │
├─────────────────────────┤
│  C      ±     %    ÷    │
│  7      8     9    ×    │
│  4      5     6    -    │  ← 按钮网格(4列5行,圆角按钮)
│  1      2     3    +    │
│  0      .         =     │
└─────────────────────────┘

布局采用 Column 纵向排列,显示区域占固定高度 160 像素,按钮区域使用 layoutWeight(1) 占满剩余空间,保证在不同屏幕尺寸下按钮区域都能自适应填充。

4.2 配色方案

为了更接近 iOS 计算器的视觉风格,采用了深色主题配色:

元素 颜色 色值 用途
整体背景 纯黑 #000000 沉浸式深色体验
显示面板 深灰 #1C1C1E 与黑色背景形成层次感
数字键 深灰底白字 #333333 / #FFFFFF 主色调,清晰醒目
运算符键 橙色底白字 #FF9F0A / #FFFFFF 突出显示,快速定位
功能键 浅灰底黑字 #A5A5A5 / #000000 与数字键区分开
0 键 宽按钮 160px(普通键2倍) 符合真实计算器使用习惯

按钮统一采用 37.5 像素圆角(75 像素高度的一半),形成完美圆形按钮效果。

4.3 按钮数据模型

按钮文本使用二维数组定义,清晰直观,便于后期增删改:

private readonly buttons: string[][] = [
  ['C', '±', '%', '÷'],    // 第一行:功能键 + 除号
  ['7', '8', '9', '×'],    // 第二行:数字键 + 乘号
  ['4', '5', '6', '-'],    // 第三行:数字键 + 减号
  ['1', '2', '3', '+'],    // 第四行:数字键 + 加号
  ['0', '.', '', '=']      // 第五行:宽0键 + 小数点 + 占位 + 等号
]

第五行第三列的空字符串 '' 用作占位符,在渲染时生成一个透明不可见的 Button 组件,目的是保持网格对齐——因为左侧 0 键占了两个按钮的宽度(160px),右侧等号占了第 4 列,第 3 列需要空出来才能让布局不乱。


五、核心代码实现

全部代码写在一个 Index.ets 文件中。下面按模块拆解讲解,你可以边看边对照手中的代码。

5.1 状态变量定义

@State displayText: string = '0'
private currentInput: string = ''
private previousInput: string = ''
private currentOperator: string = ''
private isNewInput: boolean = true
private hasError: boolean = false
private readonly MAX_DIGITS: number = 15

这里有一个重要的 ArkUI 知识点:@State 装饰器标记的变量是响应式的,只要它的值发生变化,UI 就会自动重新渲染。计算器中只有 displayText 需要直接驱动 UI,所以只有它被标记为 @State

其余变量(currentInputpreviousInput 等)是纯粹的内部逻辑状态,它们变化时不需要触发 UI 刷新——我们会在逻辑完成时通过更新 displayText 来间接触发渲染。这样的设计让状态管理更清晰,也避免了不必要的渲染开销。

5.2 事件分发机制

private onButtonClick(label: string): void {
  if (this.hasError && label !== 'C') return  // 错误状态锁
  switch (label) {
    case 'C': this.clearAll(); break
    case '±': this.toggleSign(); break
    case '%': this.percent(); break
    case '÷': case '×': case '-': case '+': this.handleOperator(label); break
    case '=': this.calculate(); break
    case '.': this.appendDot(); break
    default: this.appendNumber(label)
  }
}

所有按钮共用同一个点击处理器,通过 switch 分发到不同的处理方法。这个设计的优点是:

  • 代码简洁:不需要给每个按钮写单独的回调
  • 扩展性好:新增按钮类型只需要加一个 case
  • 统一拦截:错误状态下在最前面做拦截,所有非 C 操作都被阻止

5.3 数字输入与格式化

数字输入的核心是字符串拼接,但需要处理几个边界情况:

private appendNumber(num: string): void {
  if (this.isNewInput) {
    this.currentInput = num       // 新输入:替换当前值
    this.isNewInput = false
  } else {
    if (this.currentInput.length >= this.MAX_DIGITS) return  // 超长截断
    this.currentInput += num      // 连续输入:拼接数字
  }
  this.displayText = this.currentInput
}

关键处理逻辑:

  • 新输入替换:当用户按完运算符或等号后,isNewInputtrue,此时按数字会替换掉当前显示内容,而不是拼接
  • 长度限制MAX_DIGITS = 15,防止无限输入撑爆显示区域
  • 小数点防重appendDot() 方法中通过 includes('.') 检查当前数字是否已包含小数点
  • 自动补零:小数点在新输入状态下按 .,会自动变为 0.

5.4 运算核心算法

计算器的运算逻辑采用经典的双操作数模式:

private handleOperator(op: string): void {
  if (this.currentOperator !== '' && !this.isNewInput) {
    this.calculate()  // 链式运算:先完成前一步再进入下一步
  }
  this.previousInput = this.currentInput || '0'
  this.currentOperator = op
  this.isNewInput = true
}

private calculate(): void {
  if (this.currentOperator === '' || this.isNewInput) return
  const prev = parseFloat(this.previousInput)
  const curr = parseFloat(this.currentInput)
  let result = 0
  switch (this.currentOperator) {
    case '+': result = prev + curr; break
    case '-': result = prev - curr; break
    case '×': result = prev * curr; break
    case '÷':
      if (curr === 0) { this.showError('Error'); return }
      result = prev / curr; break
  }
  if (!isFinite(result)) { this.showError('Error'); return }
  this.currentInput = this.formatNumber(result)
  this.displayText = this.currentInput
  this.currentOperator = ''
  this.isNewInput = true
}

这个看似简单的逻辑有几个值得注意的设计点:

链式运算支持:当用户按下 5 + 3 + 2 = 时,流程是这样的——按 5 输入,按 + 存储操作数 5 和运算符 +,按 3 替换输入为 3,按 + 时检测到已有运算符,先计算 5+3=8,再存储 8 和新运算符 +,按 2 替换输入为 2,按 = 计算 8+2=10。这就是链式运算的实现原理。

除零保护:在数学中,任何数除以 0 都是未定义的。ArkTS 的浮点运算中 5 / 0 会返回 Infinity,这显然不是用户想要的结果。所以我们提前用 curr === 0 判断,显示友好的 Error 信息。

数值格式化formatNumber() 方法处理了两种特殊情况——极小数(如 0.000000001)自动转为科学计数法,超长数字截断到 15 位。同时也通过 parseFloat(toPrecision(12)) 去掉了浮点运算带来的多余小数位(例如 0.1 + 0.2 在浮点运算中的结果是 0.30000000000000004,会被格式化为 0.3)。

5.5 UI 构建详解

ArkUI 的 UI 构建采用声明式语法,在 build() 方法中描述界面结构:

build() {
  Column() {
    // ── 显示区域 ──
    Column() {
      Text(this.displayText)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
        .textAlign(TextAlign.End)
        .width('100%')
        .padding({ right: 20 })
        .maxLines(1)
    }
    .width('100%')
    .height(160)
    .backgroundColor('#1C1C1E')
    .justifyContent(FlexAlign.End)
    .padding({ bottom: 20 })

    // ── 按钮区域 ──
    Column() {
      ForEach(this.buttons, (row: string[]) => {
        Row() {
          ForEach(row, (label: string) => {
            if (label === '') {
              Button().width(75).height(75).opacity(0)
            } else {
              Button(label)
                .width(label === '0' ? 160 : 75)
                .height(75)
                .borderRadius(37.5)
                .fontSize(this.getButtonFontSize(label))
                .fontWeight(FontWeight.Bold)
                .backgroundColor(this.getButtonColor(label))
                .fontColor(this.getButtonTextColor(label))
                .margin(5)
                .onClick(() => { this.onButtonClick(label) })
            }
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)
        .padding({ left: 10, right: 10 })
      })
    }
    .layoutWeight(1)
    .width('100%')
    .backgroundColor('#000000')
    .padding({ bottom: 20 })
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#000000')
}

这里用到了 ArkUI 的 ForEach 来做列表渲染——外层 ForEach 遍历行,内层 ForEach 遍历每行的按钮。按钮的属性(宽度、颜色、字号)由辅助方法根据按钮文本动态计算,避免了大量的条件判断代码。


六、运行与调试

6.1 替换代码

打开 entry/src/main/ets/pages/Index.ets,将文件内容全选替换为本文提供的完整代码。DevEco Studio 会自动保存并触发增量编译,通常几秒钟就能完成。

6.2 启动模拟器

如果还没有创建模拟器,点击 DevEco Studio 右侧的 Device Manager,选择手机类型,下载推荐的系统镜像,创建并启动模拟器。首次启动模拟器可能需要 2-5 分钟等待开机。

6.3 运行应用

在 IDE 顶部的设备列表中选择已启动的模拟器,点击绿色 Run(▶) 按钮。DevEco Studio 会自动编译、打包、安装并在模拟器中启动应用。


七、运行效果

📸在这里插入图片描述

功能测试用例

替换代码完成后,用以下几个测试用例验证计算器功能是否正常:

测试操作 预期结果 说明
12 + 34 = 显示 46 基础加法
100 - 23 = 显示 77 基础减法
7 × 8 = 显示 56 基础乘法
100 ÷ 4 = 显示 25 基础除法
5 ÷ 0 = 显示 Error 除零保护
C 显示 0 清空功能
100 ± 显示 -100 正负切换
200 % 显示 2 百分比计算
5 + 3 + 2 = 显示 10 链式运算
0.1 + 0.2 = 显示 0.3 浮点数精度处理
9 连续按 20 次 最多显示 15 位 长度限制

八、扩展思路

一个基础版计算器做完后,如果你还想继续挑战,可以考虑以下扩展方向:

8.1 科学计算器

在现有布局基础上增加第二页功能按钮,包括三角函数(sin、cos、tan)、对数(log、ln)、平方根、幂运算等。横屏时自动切换到科学模式。

8.2 历史记录

使用 @StorageLink 或轻量级数据库,保存每次计算的历史记录,支持上滑查看和回点复用。

8.3 键盘支持

通过监听 onKeyEvent 事件,支持外接键盘或蓝牙键盘的数字和运算符输入,甚至可以用电脑键盘操作模拟器中的应用。

8.4 主题切换

增加浅色主题和彩色主题设置,使用 @State 控制颜色变量的切换,实现一键换肤。


九、总结

本文通过一个完整的计算器案例,带你走过了 HarmonyOS 原生应用开发 从创建项目到运行调试的全流程:

  1. ✅ 使用 DevEco Studio 向导创建项目,省去手动配置的麻烦
  2. ✅ 理解 ArkUI 的项目结构,知道哪些文件需要修改
  3. ✅ 学习 ArkTS 的状态管理,掌握 @State 的使用时机
  4. ✅ 实现完整的按钮布局和动态样式
  5. ✅ 编写严谨的运算逻辑,涵盖除零、溢出、链式运算等边缘情况
  6. ✅ 在模拟器中运行并测试

计算器虽小,却涵盖了 ArkUI 开发最核心的知识点:声明式 UI、状态管理、事件处理、列表渲染、条件样式。掌握了这些,你就具备了开发更复杂鸿蒙应用的基础能力。

希望这篇文章对正在学习鸿蒙开发的你有所帮助。欢迎在评论区交流你的学习心得或遇到的问题,也期待看到你在此基础上创作出更有趣的应用!


项目完整代码可私信获取。如果你觉得这篇文章有用,请点赞收藏,这是对我最大的鼓励!

Logo

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

更多推荐