鸿蒙ArkTS实战:从零开发计算器
前言
计算器是鸿蒙 ArkTS 开发入门最经典的实战案例,能够帮助零基础开发者快速掌握ArkUI 基础组件、响应式状态管理、Grid 网格布局、按钮点击事件、条件判断与基础运算逻辑。
本文基于可正常编译运行的完整源码进行讲解,全程适配鸿蒙基础开发语法,无复杂高阶语法、无第三方依赖,步骤清晰、注释详尽。大家只需将代码复制到 DevEco Studio 的 Index.ets 文件中,即可一键运行。同时文中预留截图位置,适合发布技术博客、课堂作业、学习笔记使用。
技术栈:HarmonyOS NEXT、ArkTS、ArkUI 声明式UI、DevEco Studio
一、项目整体介绍
1.1 实现功能
本款简易计算器主打基础运算能力,功能简洁实用,完全贴合入门学习需求:
- 支持 0-9 数字连续输入;
- 支持小数点输入,自动禁止重复添加小数点;
- 实现加、减、乘、除四则基础运算;
- C 清空键:一键重置所有数据与界面;
- = 等于键:计算并展示最终运算结果;
- 容错处理:拦截除数为 0 异常,避免程序崩溃;
- 浮点精度优化:限制小数位数,解决基础浮点显示问题;
- 支持运算结果连续二次计算。
1.2 界面布局说明
整体页面采用纵向 Column 布局,分为两大模块:
- 顶部显示区:使用 Text 组件作为计算器显示屏,展示输入内容与计算结果;
- 底部按键区:使用 Grid + GridItem 网格布局,分为 4 列排列所有功能按钮,区分运算符、功能键、数字键样式。


二、开发环境与工程创建
2.1 环境配置
- 开发工具:DevEco Studio 正式版
- 运行设备:鸿蒙模拟器 / 鸿蒙真机
- 开发语言:ArkTS
- 布局框架:ArkUI 原生声明式布局

2.2 新建项目步骤
- 打开 DevEco Studio,点击 Create Project 新建工程;
- 模板选择 Empty Ability 空白模板,点击下一步;
- 自定义项目名称(例:SimpleCalculator),设备类型选择手机(Phone),语言选择 ArkTS;
- 选择文件保存路径,完成创建,等待项目依赖自动同步。
项目创建完成后,核心编辑文件路径:
entry/src/main/ets/pages/Index.ets
三、核心变量与逻辑思路讲解
在正式拆分代码前,先理解本项目用到的核心变量与整体执行逻辑,新手更容易读懂代码。
3.1 核心状态变量
// 显示屏显示文本,@State 实现数据驱动UI自动刷新
@State showText: string = "0"
// 存储第一个参与运算的数字
private firstNum: number = 0
// 存储用户选中的运算符:+、-、*、/
private operator: string = ""
// 标记是否为全新输入,区分连续输入与二次输入
private isNewInput: boolean = true
- @State:ArkUI 响应式关键字,变量改变时页面自动刷新,是动态界面的核心;
- firstNum:缓存第一个运算数,用于后续四则运算;
- operator:记录运算符,判断执行哪一种计算逻辑;
- isNewInput:核心标记,用来区分“继续输入数字”和“重新开始输入数字”两种场景。
3.2 整体执行流程
- 点击数字/小数点 → 判断输入状态,拼接内容并刷新显示屏;
- 点击运算符 → 保存当前数字为第一个运算数,记录运算符,标记为等待新输入;
- 继续输入第二个数字 → 正常拼接内容;
- 点击等于号 → 读取两个数字与运算符,执行四则运算,处理异常并展示结果;
- 点击 C 键 → 所有变量重置,计算器恢复初始状态。
四、完整可运行源码
直接将下方全部代码复制粘贴到 Index.ets 中,即可编译运行:
@Entry
@Component
struct Index {
// 显示屏显示文本(响应式刷新)
@State showText: string = "0"
// 运算拆分变量
private firstNum: number = 0
private operator: string = ""
private isNewInput: boolean = true
// 按钮统一点击入口
onBtnClick(value: string): void {
// 1. 清空重置按钮 C
if (value === "C") {
this.showText = "0"
this.firstNum = 0
this.operator = ""
this.isNewInput = true
return
}
// 2. 等于号,执行计算逻辑
if (value === "=") {
this.calcResult()
return
}
// 3. 点击运算符,记录第一个数字与运算符
if (["+", "-", "*", "/"].includes(value)) {
this.firstNum = parseFloat(this.showText)
this.operator = value
this.isNewInput = true
return
}
// 4. 数字、小数点输入逻辑
if (this.isNewInput) {
this.showText = value === "." ? "0." : value
this.isNewInput = false
} else {
// 禁止重复输入小数点
if (value === "." && this.showText.includes(".")) {
return
}
this.showText += value
}
}
// 四则运算核心方法
calcResult(): void {
// 未选择运算符,直接终止计算
if (this.operator === "") {
return
}
const secondNum: number = parseFloat(this.showText)
let result: number = 0
// 根据运算符执行对应计算
switch (this.operator) {
case "+":
result = this.firstNum + secondNum
break
case "-":
result = this.firstNum - secondNum
break
case "*":
result = this.firstNum * secondNum
break
case "/":
// 容错:拦截除数为0
if (secondNum === 0) {
this.showText = "计算错误"
this.firstNum = 0
this.operator = ""
this.isNewInput = true
return
}
result = this.firstNum / secondNum
break
}
// 修正浮点数精度,保留6位小数
result = parseFloat(result.toFixed(6))
// 更新显示,支持连续运算
this.showText = result.toString()
this.firstNum = result
this.operator = ""
this.isNewInput = true
}
// 页面UI布局
build() {
Column() {
// 计算器显示屏
Text(this.showText)
.width("90%")
.height(120)
.fontSize(48)
.textAlign(TextAlign.End)
.padding({ right: 15 })
.backgroundColor(0xF5F5F5)
.borderRadius(12)
.margin({ top: 30, bottom: 20 })
// 计算器4列网格按钮区域
Grid() {
GridItem() { Button("C").fontSize(28).backgroundColor(0xEEEEEE) }
.onClick(() => this.onBtnClick("C"))
GridItem() { Button("/").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("/"))
GridItem() { Button("*").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("*"))
GridItem() { Button("-").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("-"))
GridItem() { Button("7").fontSize(28) }
.onClick(() => this.onBtnClick("7"))
GridItem() { Button("8").fontSize(28) }
.onClick(() => this.onBtnClick("8"))
GridItem() { Button("9").fontSize(28) }
.onClick(() => this.onBtnClick("9"))
GridItem() { Button("+").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("+"))
GridItem() { Button("4").fontSize(28) }
.onClick(() => this.onBtnClick("4"))
GridItem() { Button("5").fontSize(28) }
.onClick(() => this.onBtnClick("5"))
GridItem() { Button("6").fontSize(28) }
.onClick(() => this.onBtnClick("6"))
GridItem() { Button("=").fontSize(28).backgroundColor(0x42A5F5).fontColor(Color.White) }
.onClick(() => this.onBtnClick("="))
GridItem() { Button("1").fontSize(28) }
.onClick(() => this.onBtnClick("1"))
GridItem() { Button("2").fontSize(28) }
.onClick(() => this.onBtnClick("2"))
GridItem() { Button("3").fontSize(28) }
.onClick(() => this.onBtnClick("3"))
GridItem() { Button(".").fontSize(28) }
.onClick(() => this.onBtnClick("."))
GridItem() { Button("0").fontSize(28).width("100%") }
.onClick(() => this.onBtnClick("0"))
}
.columnsTemplate("1fr 1fr 1fr 1fr")
.rowsGap(12)
.columnsGap(12)
.width("90%")
.height(420)
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Start)
}
}
五、代码模块拆分详解
5.1 状态变量模块
这一部分定义程序运行所需的所有数据,是整个计算器的数据基础:
@State showText: string = "0"
private firstNum: number = 0
private operator: string = ""
private isNewInput: boolean = true
- showText:页面展示的内容,初始默认显示数字 0;
- firstNum:存储第一个运算数字,初始值为 0;
- operator:运算符字符串,初始为空;
- isNewInput:布尔标记,默认开启全新输入状态。
5.2 按钮统一点击方法 onBtnClick
所有按钮的点击事件都会进入这个方法,通过判断按钮内容分流执行不同逻辑,结构清晰易维护:
- 判断清空键 C:重置所有变量,页面回到初始状态;
- 判断等于键 =:调用计算方法,执行运算;
- 判断运算符:保存当前数字和运算符,切换为新输入状态;
- 判断数字/小数点:结合 isNewInput 标记,区分“覆盖输入”和“拼接输入”,同时限制重复小数点。
5.3 运算核心方法 calcResult
该方法专门实现四则运算与异常处理,是计算器的核心逻辑:
- 先判断是否选择运算符,无运算符则不执行计算;
- 将显示屏字符串转为数字,得到第二个运算数;
- 通过 switch 匹配运算符,分别执行加、减、乘、除;
- 异常处理:除法时判断除数是否为 0,若为 0 则提示“计算错误”并重置状态;
- 精度处理:使用 toFixed(6) 限制小数位数,优化浮点计算显示问题;
- 运算完成后更新界面,同时把结果赋值给 firstNum,支持连续运算。
5.4 UI 布局模块 build
build 函数负责页面渲染,分为显示屏与按钮网格两部分:
- Text 显示屏:设置宽高、字体大小、背景色、圆角、右对齐样式,模拟真实计算器屏幕;
- Grid 网格布局:设置为 4 列布局,通过 rowsGap、columnsGap 设置按钮间距;
- GridItem + Button:逐个定义按钮,为不同功能按钮设置区分色,每个按钮绑定点击事件;
- 最外层 Column 纵向布局,让显示屏在上、按键区在下,适配手机屏幕。
5.5计算器核心逻辑类
@Entry
@Component
struct Index {
@State showText: string = "0"
private firstNum: number = 0
private operator: string = ""
private isNewInput: boolean = true
onBtnClick(value: string): void {
if (value === "C") {
this.showText = "0"
this.firstNum = 0
this.operator = ""
this.isNewInput = true
return
}
if (value === "=") {
this.calcResult()
return
}
if (["+", "-", "*", "/"].includes(value)) {
this.firstNum = parseFloat(this.showText)
this.operator = value
this.isNewInput = true
return
}
if (this.isNewInput) {
this.showText = value === "." ? "0." : value
this.isNewInput = false
} else {
if (value === "." && this.showText.includes(".")) return
this.showText += value
}
}
calcResult(): void {
if (this.operator === "") return
const secondNum: number = parseFloat(this.showText)
let result: number = 0
switch (this.operator) {
case "+": result = this.firstNum + secondNum; break
case "-": result = this.firstNum - secondNum; break
case "*": result = this.firstNum * secondNum; break
case "/":
if (secondNum === 0) {
this.showText = "计算错误"
this.firstNum = 0
this.operator = ""
this.isNewInput = true
return
}
result = this.firstNum / secondNum
break
}
result = parseFloat(result.toFixed(6))
this.showText = result.toString()
this.firstNum = result
this.operator = ""
this.isNewInput = true
}
}
5.6UI布局组件
build() {
Column() {
Text(this.showText)
.width("90%")
.height(120)
.fontSize(48)
.textAlign(TextAlign.End)
.padding({ right: 15 })
.backgroundColor(0xF5F5F5)
.borderRadius(12)
.margin({ top: 30, bottom: 20 })
Grid() {
GridItem() { Button("C").fontSize(28).backgroundColor(0xEEEEEE) }
.onClick(() => this.onBtnClick("C"))
GridItem() { Button("/").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("/"))
GridItem() { Button("*").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("*"))
GridItem() { Button("-").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("-"))
GridItem() { Button("7").fontSize(28) }
.onClick(() => this.onBtnClick("7"))
GridItem() { Button("8").fontSize(28) }
.onClick(() => this.onBtnClick("8"))
GridItem() { Button("9").fontSize(28) }
.onClick(() => this.onBtnClick("9"))
GridItem() { Button("+").fontSize(28).backgroundColor(0xFFB74D) }
.onClick(() => this.onBtnClick("+"))
GridItem() { Button("4").fontSize(28) }
.onClick(() => this.onBtnClick("4"))
GridItem() { Button("5").fontSize(28) }
.onClick(() => this.onBtnClick("5"))
GridItem() { Button("6").fontSize(28) }
.onClick(() => this.onBtnClick("6"))
GridItem() { Button("=").fontSize(28).backgroundColor(0x42A5F5).fontColor(Color.White) }
.onClick(() => this.onBtnClick("="))
GridItem() { Button("1").fontSize(28) }
.onClick(() => this.onBtnClick("1"))
GridItem() { Button("2").fontSize(28) }
.onClick(() => this.onBtnClick("2"))
GridItem() { Button("3").fontSize(28) }
.onClick(() => this.onBtnClick("3"))
GridItem() { Button(".").fontSize(28) }
.onClick(() => this.onBtnClick("."))
GridItem() { Button("0").fontSize(28).width("100%") }
.onClick(() => this.onBtnClick("0"))
}
.columnsTemplate("1fr 1fr 1fr 1fr")
.rowsGap(12)
.columnsGap(12)
.width("90%")
.height(420)
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Start)
}
每一步都要认真看,避免出错,慢慢来,不要着急
六、项目运行与功能测试
6.1 编译运行
代码粘贴完成后,点击 DevEco Studio 顶部运行按钮,选择模拟器或已连接的鸿蒙真机,等待编译安装,应用即可正常启动。
6.2 逐项功能测试
- 数字输入
点击 0-9 任意数字,屏幕正常显示,支持多位数连续输入。

- 小数点限制
多次点击小数点按钮,页面只会保留一个小数点,不会重复添加。
- 四则运算测试
输入 12 + 36 点击等于,正常输出结果;输入 88 - 22、6 * 7 均可正常计算。

- 除零异常测试
输入任意数字 ÷ 0,点击等于,屏幕显示计算错误,程序不会闪退崩溃。

- 清空功能
点击 C 键,页面恢复初始 0,所有运算数据全部重置。
- 连续运算
一次计算完成后,直接点击运算符和新数字,可基于上一次结果继续运算。
七、开发常见问题与解决办法
1. 修改代码后页面不刷新
原因:展示文本变量未使用 @State 修饰。
解决:动态展示的内容必须添加 @State 关键字,开启响应式更新。
2. 可以重复输入多个小数点
原因:未做字符判断。
解决:使用 showText.includes(".") 判断当前内容是否已存在小数点,存在则禁止输入。
3. 除数为0时程序报错/闪退
原因:缺少异常判断。
解决:在除法逻辑中增加 secondNum === 0 判断,主动拦截非法运算并提示错误。
4. 运算出现长串小数(浮点精度问题)
原因:编程语言原生浮点运算特性。
解决:使用 toFixed(位数) 限制小数展示位数,再转回数字格式。
八、项目总结
本项目基于标准 ArkTS 语法与 ArkUI 基础组件开发,代码简洁、逻辑清晰、可直接运行,非常适合鸿蒙入门学习者练习。
通过本次实战,我们掌握了以下基础知识点:
- DevEco Studio 项目创建、代码编写与运行流程;
- @State 响应式状态管理的使用场景;
- Column、Grid、GridItem、Text、Button 等常用基础组件;
- 按钮点击事件、分支判断、字符串与数字转换;
- 程序基础容错、异常处理的编写思路。
大家可以在现有代码基础上拓展功能,例如增加正负号、退格键、更多运算方式等,进一步巩固鸿蒙开发基础。
更多推荐


所有评论(0)