鸿蒙ArkTS科学计算器开发实战:从单文件玩具到模块化专业级应用的五轮迭代

中国海洋大学 2026 夏季小学期《移动软件开发》实验5
开发环境:DevEco Studio 26.0.0 + HarmonyOS 7.0 (API 26)
项目架构:ArkTS 声明式UI + 模块化分层设计


前言

这学期的移动软件开发课,前面几个实验都是微信小程序开发,终于到了鸿蒙(HarmonyOS)环节。说实话,一开始听到"鸿蒙开发"还有点发怵——新系统、新语言(ArkTS)、新IDE(DevEco Studio),一切都要从零开始。

但真正动手做下来才发现,鸿蒙的声明式UI开发范式其实非常优雅。而且这次实验我没有满足于"能算加减乘除就行",而是前前后后迭代了五轮,最终做出了一个支持完整表达式求值、4套主题、历史记录持久化、常量库、自定义按键动画的专业级科学计算器,代码也从最初的单文件800行重构为9个模块文件的分层架构

这篇博客就来记录整个开发过程中的思考、踩坑和收获,希望能帮到同样在学鸿蒙开发的同学。


一、环境搭建:第一个拦路虎是Hyper-V

1.1 安装DevEco Studio

从华为开发者联盟官网下载 DevEco Studio 26.0.0 Release,安装过程和 Android Studio 差不多(毕竟都是基于 IntelliJ 平台),一路下一步就行。

1.2 创建模拟器,踩坑Hyper-V

创建项目之前得先有个模拟器。Tools → Device Manager → New Emulator,选 Huawei_Phone 模板,下载 HarmonyOS 7.0 系统镜像。但点启动的瞬间,报错了:

ErrorCode: 00801001
未开启Hyper-V。
运行模拟器需要开启Hyper-V虚拟化支持。

行吧,鸿蒙模拟器吃 Hyper-V。我以管理员身份打开 PowerShell,执行:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

结果又报错:功能名称 Microsoft-Hyper-V 未知

查了一下才知道,Windows 家庭版默认不带完整的 Hyper-V 组件。但好消息是,鸿蒙模拟器不一定需要完整 Hyper-V,只要启用 Windows Hypervisor Platform虚拟机平台 就够了:

Enable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform -All
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All

执行完重启电脑,模拟器终于能正常启动了,看到 HarmonyOS 的开机画面那一刻还是有点小激动的。

💡 踩坑总结:Windows 家庭版用户不用慌,不需要升级到专业版,启用 HypervisorPlatform + VirtualMachinePlatform 就能跑鸿蒙模拟器。

1.3 Hello World验证环境

环境通了之后,创建一个 Empty Ability 模板的工程,点运行,模拟器上出现 Hello World——环境搭建完成,正式开始写计算器。


二、第一轮:基础计算器 + ArkTS语法踩坑

2.1 ArkTS初体验

ArkTS 是 TypeScript 的严格超集,采用声明式 UI 开发。一个页面的基本结构长这样:

@Entry
@Component
struct Index {
  @State display: string = '0'

  build() {
    Column() {
      Text(this.display)
      Button('点击我')
        .onClick((): void => {
          this.display = 'Hello'
        })
    }
  }
}

几个核心概念:

  • @Entry:页面入口
  • @Component:自定义组件
  • @State:状态变量,值变了自动刷新UI
  • build():UI描述方法,返回组件树
  • Column/Row:纵向/横向布局容器

这种"数据驱动UI"的范式和 Flutter、React 很像,但 ArkTS 的类型检查更严格。

2.2 第一次编译,22个错误

代码写完点运行,直接报了 22 个编译错误,主要是三类:

错误1:arkts-no-implicit-return-types
所有 onClick(() => this.clear()) 形式的箭头函数全部报错。ArkTS 要求箭头函数必须显式声明返回类型,改成 (): void => { this.clear() } 就好了。

错误2:FontWeight.Light 不存在
ArkTS 的 FontWeight 枚举里没有 Light,只有 Lighter、Normal、Medium、Bold 等,改成 FontWeight.Lighter

错误3:Button 没有 textAlign 属性
只有 Text 组件支持 textAlign,Button 不支持,直接删掉。

💡 踩坑总结:ArkTS 的 API 和其他框架有差异,不能凭经验想当然,遇到不确定的属性查官方文档最靠谱。

修完这 22 个错误,基础计算器终于跑起来了,能算加减乘除了。


三、第二轮:布局优化与界面美化

基础功能有了,但界面实在是……朴素。于是开始第二轮迭代,重点解决布局问题和美化界面。

3.1 布局踩坑:按钮变成了横向长条

一开始给按钮设置 width('80%'),结果四个按钮在一行里每个都占父容器 80% 的宽度,直接溢出屏幕,按钮被拉成了横向长条,右边的按钮还被截断了。

解决方法:改用 layoutWeight(1),让按钮在 Row 里平均分配剩余宽度。0 按钮要占两格就设 layoutWeight(2),完美解决。

// ❌ 错误写法
Button('7').width('80%')

// ✅ 正确写法
Button('7').layoutWeight(1).height(55)

3.2 界面美化:深色渐变 + 玻璃拟态

布局修好之后开始美化,最终定了一套深色科技风:

  • 背景:深紫蓝三色渐变
  • 显示屏:玻璃拟态效果——半透明白色背景 + 细边框 + 大阴影 + 结果文字青色辉光
  • 按钮:145° 渐变 + 圆角 + 投影
// 玻璃拟态显示屏
Column() {
  Text(this.history)
  Text(this.display)
    .shadow({
      radius: 10,
      color: 'rgba(0, 229, 255, 0.2)',
      offsetX: 0,
      offsetY: 0
    })
}
.width('88%')
.backgroundColor('rgba(255, 255, 255, 0.06)')
.borderRadius(18)
.border({ width: 1, color: 'rgba(255, 255, 255, 0.1)' })

3.3 又踩一坑:stateStyles按下不恢复

想给按钮加按下变暗的效果,用了 .stateStyles({ pressed: { .opacity(0.65) } }),结果点击后按钮一直保持半透明,不恢复了。查了一下是 ArkTS 的 stateStyles 在 @Builder 自定义组件里有 bug。

这个问题后来在第四轮迭代中通过封装自定义按键组件彻底解决了,按下效果反而做得更丰富了。


四、第三轮:升级为科学计算器

基础计算器 + 美化完成后,我觉得还不够——既然叫"计算器",不如直接做成专业级的科学计算器。于是第三轮迭代,加入了一大堆专业功能。

4.1 2nd第二功能键:一键双功能的经典设计

专业科学计算器(卡西欧、TI 系列)都有一个 2nd 键,按下后所有函数切换为第二功能(sin→sin⁻¹、ln→eˣ、√→∛ 等)。我也实现了这个设计:

@State is2nd: boolean = false

// 按钮文字根据 is2nd 动态切换
this.CalcButton(
  this.is2nd ? 'sin⁻¹' : 'sin',
  ...
  (): void => { this.applyUnary(this.is2nd ? 'asin' : 'sin') }
)

2nd 键按下后高亮(青色背景 + 黑色文字),而且是一次性开关——按下任意功能键后自动复位(2nd 键自身除外),符合专业计算器的交互习惯。

完整的 2nd 功能映射:

默认功能2nd模式
sinsin⁻¹ (反正弦)
coscos⁻¹ (反余弦)
tantan⁻¹ (反正切)
lneˣ (e的x次方)
log10ˣ (10的x次方)
∛ (立方根)
x³ (立方)
1/x2ˣ (2的x次方)
M+M− (内存减)
n!Rand (随机数)

一下子多了 10 个功能,但按钮数量没变——这就是 2nd 键的魅力。

4.2 内存操作:MC/MR/M+/M−

专业计算器标配的内存功能也安排上了:

  • MC(Memory Clear):清除内存
  • MR(Memory Recall):读取内存值
  • M+(Memory Add):当前值加到内存
  • M−(Memory Subtract):从内存减去当前值(2nd切换)

4.3 角度三态切换:RAD/DEG/GRAD

三角函数的结果取决于角度单位,我实现了三态切换:

  • RAD:弧度(默认)
  • DEG:角度
  • GRAD:梯度

点击右上角胶囊循环切换,三角函数计算时自动转换:

toRadian(value: number): number {
  if (this.angleMode === 1) return value * Math.PI / 180  // DEG
  if (this.angleMode === 2) return value * Math.PI / 200  // GRAD
  return value
}

反三角函数的结果也要反向转换回来,细节拉满。

4.4 其他细节

  • Ans:调用上一次计算结果,方便链式计算
  • Rand:生成 0-1 随机数
  • ±:正负号切换
  • n!:阶乘(支持 0-170 整数范围,超出返回 Error)
  • :任意次幂(如 2^10=1024)
  • 错误处理:除零、负数开平方、负数对数都返回 Error

五、第四轮:架构重构——从单文件玩具到模块化专业应用

科学功能加完之后,新问题来了——Index.ets 已经膨胀到 1000 多行,计算逻辑、UI布局、格式化、按键定义全混在一起,修改一处容易影响其他部分。

于是我做了一个重要决定:全面重构,模块化分层

5.1 模块化架构设计

按职责将代码拆分为 5 个目录、9 个文件:

entry/src/main/ets/
├── pages/
│   └── Index.ets              // 主页面(状态管理 + 按键分发)
├── engine/
│   ├── Evaluator.ets          // 表达式求值引擎(双栈算法,纯函数)
│   └── Formatter.ets          // 数字格式化(千分位、科学计数法上标、自适应字号)
├── components/
│   └── CalcKeyButton.ets      // 自定义按键组件(按下动画、长按回调)
├── common/
│   ├── AppTheme.ets           // 主题系统(4套主题定义)
│   └── KeyModel.ets           // 按键模型(数据驱动布局,2nd第二功能)
├── model/
│   ├── HistoryRecord.ets      // 历史记录模型
│   └── ConstItem.ets          // 常量库模型(数学常数+物理常数)
└── utils/
    ├── Prefs.ets              // 偏好设置持久化
    └── Haptic.ets             // 震动反馈(20ms节流)

这种分层架构遵循"单一职责"原则:计算逻辑(engine)、UI组件(components)、数据模型(model)、工具函数(utils)、公共配置(common)各司其职,主页面(pages)只负责状态管理和按键分发。

5.2 核心亮点1:双栈表达式求值引擎

初版采用的是"输入一个数→点运算符→保存当前值→输入下一个数→点等号"的即时计算模式,无法支持括号和运算符优先级。重构时我实现了一个完整的双栈表达式求值引擎。

算法采用经典的"双栈 + 期望操作数"状态机:

  • 一个值栈(values)存放操作数
  • 一个运算符栈(ops)存放待运算的运算符
  • expectOperand 状态区分一元负号和二元减号
  • 通过比较运算符优先级决定入栈还是弹出运算

支持的特性:

  • 运算符优先级:×÷高于±,^(幂)最高且右结合
  • 圆括号嵌套:支持任意层级括号
  • 一元负号:如 -3、-(-2),与二元减号自动区分
  • 后缀运算符:百分号(%)与阶乘(!)
  • 错误处理:语法错误、括号不匹配、除零、超定义域分别返回不同错误信息
// 运算符优先级,数值越大越先算
function precedence(op: string): number {
  switch (op) {
    case '+': case '-': return 1
    case '×': case '÷': return 2
    case 'neg': case '%': return 3
    case '^': return 4
    case '!': return 5
    default: return 0
  }
}

所有对外接口均为纯函数,不持有状态,便于单元测试和维护。这次重构让计算器从"玩具级"升级为"专业级"。

同时增加了实时预览功能:输入过程中(token 数 ≥ 3 时)实时显示当前表达式的预览结果(“= xxx”),无需按等号即可看到计算结果。

5.3 核心亮点2:自定义按键组件

放弃了有 bug 的 stateStyles 方案,封装了独立的 CalcKeyButton 组件,自带完整的交互反馈:

  • 按下态动画:按下时透明度降至0.78、缩放至0.94、阴影半径缩小且偏移减小,抬起时通过 130ms 缓动动画恢复
  • 长按支持:按住 550ms 触发长按回调,长按触发后不再触发点击回调
  • 状态隔离:按下状态放在组件内部(@State private pressed),避免父组件因按键态变化而整体重建
@Component
export struct CalcKeyButton {
  @Prop label: string = ''
  @Prop colorStart: string = '#2C2C4A'
  @Prop colorEnd: string = '#1B1B33'
  // ... 其他属性
  onTap: () => void = (): void => {}
  onLongTap: () => void = (): void => {}
  @State private pressed: boolean = false

  build() {
    Text(this.label)
      .opacity(this.pressed ? 0.78 : 1)
      .scale({ x: this.pressed ? 0.94 : 1, y: this.pressed ? 0.94 : 1 })
      .animation({ duration: 130, curve: Curve.EaseOut })
      .onTouch((event: TouchEvent) => {
        if (event.type === TouchType.Down) {
          this.pressed = true
          // 长按计时...
        } else if (event.type === TouchType.Up) {
          this.pressed = false
          if (!this.longFired) { this.onTap() }
        }
      })
  }
}

5.4 核心亮点3:4套主题系统

实现了完整的多主题系统,界面上不允许出现硬编码颜色,全部从当前主题取值。每套主题包含 20 余项配置(背景渐变、玻璃卡片、三级文字色、强调色、四类按键渐变、投影色、浮层色等)。

内置 4 套主题:

主题名风格强调色
深空霓虹暗夜里的赛博光晕青色 #22E6FF
极光森林清透的青绿薄雾青绿 #5EEAD4
暮色霞光落日熔金的暖调暖橙 #FFB07C
瓷白明亮通透的日间模式蓝紫 #3D5AFE

新增一套皮肤只需在 buildThemes() 中加一个条目,无需修改任何 UI 代码。主题选择通过底部弹窗切换,并持久化到本地存储。

5.5 核心亮点4:数字格式化引擎

负责输入串和结果的显示格式化:

  • 千分位分组:输入时自动插入千分位逗号(1234567 → 1,234,567)
  • 科学计数法上标美化:结果超范围自动转科学计数法,指数用上标字符显示(3.14×10⁸)
  • 自适应字号:根据文本长度自动挑选字号(6字以内44号,24字以上18号),保证长数字不被截断

5.6 核心亮点5:历史记录与常量库

  • 历史记录:每次按等号后保存表达式、结果、时间,持久化到本地。点击记录可复用结果,支持清空。
  • 常量库:内置 14 个常用常量,分为数学常数(π、e、φ黄金比例、√2、ln2、γ欧拉常数)和物理常数(c真空光速、g重力加速度、G引力常数、h普朗克常数、e⁻元电荷、NA阿伏伽德罗常数、R摩尔气体常数、k玻尔兹曼常数),点击直接填入。

5.7 核心亮点6:持久化存储

通过 Prefs 工具类封装 @kit.ArkData 的 preferences API,实现主题选择、角度模式、科学区状态、历史记录的持久化。所有读写包在 try/catch 中,即便存储不可用也退化为纯内存运行,不影响主流程。


六、第五轮:细节打磨——让应用"活"起来

功能和架构都到位之后,最后一轮迭代专注于交互细节的打磨,让应用从"能用"变成"好用"。

6.1 震动反馈

每次按键触发 12ms 轻震动,让点击有实体感。20ms 内重复触发会被合并,避免连点时马达过度抖动。设备不支持时静默降级。

export class Haptic {
  static tick(): void {
    try {
      const now: number = new Date().getTime()
      if (now - lastTick < 20) return  // 20ms节流
      lastTick = now
      vibrator.startVibration({ type: 'time', duration: 12 }, { id: 0, usage: 'touch' })
    } catch (e) { /* 不支持时忽略 */ }
  }
}

6.2 结果滑入动画

按等号后结果从下方轻微滑入并回弹(280ms 缓动),同时显示区缩放至 0.955 再恢复,增强计算完成的仪式感。

6.3 背景氛围光斑

界面左上角和右下角各有一个模糊光晕(blur 80),颜色随主题强调色变化,营造氛围感。

6.4 其他细节

  • Toast 提示:操作反馈(如"内存已清除")通过底部 Toast 显示,1.6秒后自动消失
  • 待命运算符高亮:当前表达式末尾落在运算符上时,对应运算符按键高亮显示
  • 未闭合括号计数:实时统计未闭合的括号数量,显示在界面上
  • 科学区滚动:科学模式展开后内容超出屏幕,最外层 Scroll 包裹,隐藏滚动条 + 弹性回弹

七、踩坑总结

整个开发过程踩了不少坑,在这里集中总结一下:

解决方案
Windows家庭版没有Hyper-V启用 HypervisorPlatform + VirtualMachinePlatform 即可
箭头函数编译报错显式声明返回类型 (): void => { ... }
FontWeight.Light不存在改用 FontWeight.Lighter
Button没有textAlign属性删掉该属性,只有Text支持
width(‘80%’)导致布局错乱改用 layoutWeight(1) 平均分配宽度
stateStyles按下不恢复封装自定义按键组件,内部管理pressed状态
科学模式展开后按钮被遮挡最外层Scroll包裹,隐藏滚动条+弹性回弹
即时计算不支持括号重构为双栈表达式求值引擎
单文件代码膨胀难维护模块化分层重构,拆分为9个文件

八、收获与思考

这次鸿蒙计算器开发,是我第一次接触国产操作系统的应用开发,收获远超预期:

1. 声明式UI开发范式的魅力
ArkTS 的 @State + 声明式布局,让"数据变了UI自动刷新"成为自然,不需要手动操作控件引用。写惯了命令式UI再写声明式,会有种"原来可以这么简洁"的感觉。

2. 模块化架构不是银弹,但没有银弹不行
初版单文件 1000 多行,修改计算逻辑时不小心影响了 UI 布局,排查困难。重构为分层架构后,各模块职责单一、接口清晰,新增功能只需修改对应模块。尤其是将表达式求值引擎设计为纯函数模块,不持有任何状态,不仅便于测试和维护,也让主页面的状态管理变得简洁。这次实践让我真正理解了"高内聚、低耦合"不是书本上的空话。

3. 好的算法是功能的基础
从"即时计算"升级为"双栈表达式求值",让计算器从"玩具级"变成了"专业级"。亲手实现一遍双栈算法,比看书理解深刻得多——尤其是用"期望操作数"状态区分一元负号和二元减号这个技巧,非常巧妙。

4. 迭代开发比一步到位更高效
计算器从基础版到专业科学计算器,经过了五轮迭代。每一轮都聚焦解决当前最突出的问题,功能和体验螺旋上升。如果一开始就想"一步到位做个完美的科学计算器",大概率会因为复杂度太高而卡住。

5. 细节决定体验
震动反馈、结果滑入动画、背景光晕、实时预览、待命运算符高亮、千分位分组、科学计数法上标……这些细节单个看都不起眼,但组合起来让应用的体验提升了一个档次。做移动端开发,细节真的很重要。

6. 鸿蒙开发生态已经相当成熟
DevEco Studio 的开发体验、ArkTS 的语言设计、模拟器的运行速度,都超出了我的预期。声明式UI + 严格类型检查 + 丰富的组件库,开发效率很高。作为国产操作系统,鸿蒙的应用开发生态已经具备了和主流平台掰手腕的实力,未来可期。


写在最后

鸿蒙开发比我想象中更有趣,ArkTS 写起来也很顺手。期待后续能学到更多鸿蒙的高级特性,比如分布式能力、原子服务、自定义组件封装等等。

如果这篇博客对你有帮助,欢迎点赞收藏~有问题也可以在评论区交流!


项目信息:

  • 课程:中国海洋大学 2026 夏季小学期《移动软件开发》
  • 实验:实验5 鸿蒙开发入门及计算器开发
  • 开发环境:DevEco Studio 26.0.0 + HarmonyOS 7.0 (API 26)
  • 开发语言:ArkTS
  • 项目架构:模块化分层设计(engine/components/common/model/utils)
  • 核心功能:双栈表达式求值、2nd第二功能、内存操作、4套主题、历史记录持久化、常量库、自定义按键动画、震动反馈
Logo

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

更多推荐