还在为组件状态混乱、页面跳转丢参数而头疼?
这篇博客将揭秘如何用鸿蒙ArkTS打造一个漂亮美观的智能计算器:
✅ 输入完整表达式,秒出结果------字符串切割简单计算
✅ 状态管理黑科技------@Provide/@Consume 实现跨组件实时响应
✅ 路由传参实战------历史记录页面跳转不丢数据,跨页面传参
✅ 代码即设计稿------ArkTS声明式UI开发,代码比PPT更直观
快捷跳转到想看的地方
- 一、项目展示
- 二、技术栈讲解
- 三、项目结构说明
- 四、核心代码实现
-
- [1. `Index.ets`父组件总布局查看](#1.
Index.ets
父组件总布局查看) - [2. `navbar.ets`实现](#2.
navbar.ets
实现) - [3. `listView.ets` 展示区的实现](#3.
listView.ets
展示区的实现) - [4. `buttonView.ets` 按钮区的代码说明](#4.
buttonView.ets
按钮区的代码说明)
- [1. `Index.ets`父组件总布局查看](#1.
- 五、点赞收藏支持一下吧,代码源码私信我
一、项目展示
这里展示了计算器的加减乘除取余等基本操作,展示了历史记录功能,展示了帮助弹窗

二、技术栈讲解
- 使用arkUI通用组件开发
- 自定义弹窗的实现
- 使用@State,@Link, @Provide, @Consume进行状态管理
- 使用router实现路由跳转以及参数携带
- 使用分模块化结构高效开发
三、项目结构说明
ts
src/ //存放所有与项目逻辑相关的代码文件
├── main/ //包含应用程序的核心模块和组件。
│ ├── ets/ //用于存放与 ArkTS(或类似框架)相关的代码。
│ │ ├── common/ //存放可复用的工具类、辅助函数等。
│ │ ├── data/ //存放数据,这里的data层是不必要的,由于组件数据较多。写在文件里有点乱,这里分了一层
│ │ ├── entryability/ //存放与应用入口相关的逻辑,例如启动时的初始化逻辑。
│ │ ├── entrybackupability/ //存放备用入口逻辑,可能用于异常情况下的备份入口。
│ │ ├── views/ //存放页面中的组件
│ │ ├── pages/ //存放各个页面的逻辑、UI 组件等。
│ │ └── util/ //存放通用工具函数、辅助方法等。
│ └── module.json5 //项目配置文件
└── resources/ //存放静态资源文件,如图片、字体、样式文件等。
四、核心代码实现
1. Index.ets
父组件总布局查看
ts
import buttonView from '../views/buttonView'
import navbar from '../views/navbar'
import resultView from '../views/resultView'
@Entry
@Component
struct Index {
@State calculatorResultText: string = "" //存储打印在结果区的最终结果
@State res: number = 0 //存储计算器内部逻辑计算的值
@Provide list: string[] = [] // 存储历史记录数组
build() {
Scroll(){
Column(){
navbar() // 导航栏
resultView({calculatorResultText: this.calculatorResultText, res: this.res}) // 结果打印区
buttonView({calculatorResultText: this.calculatorResultText, res: this.res}) // 按钮控制区
}
}
.scrollBar(BarState.Off)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
//控制模拟器展示安全区为全屏展示
.backgroundColor('#1C2220')
.width('100%')
.height('100%')
}
}
2. navbar.ets
实现
ts
// navbar.ets
import app from '@system.app';
@Component
export default struct navbar {
//定义两个弹窗控制器,来控制弹窗的行为
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample(),
})
controller: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample2({}),
})
build() {
Row(){
// 软件logo和名字
Row(){
Image($r('app.media.calculator'))
.width(20)
.margin({left: 20,right: 20})
Text('小东计算器')
.fontColor('#DEDFDF')
}
// 软件的一些系统功能
Row({space: 10}){
Text("帮助").fontColor('#DEDFDF')
.onClick(() => {
//触发弹窗
this.dialogController.open()
})
Text("设置").fontColor('#DEDFDF')
.onClick(() => {
//触发弹窗2
this.controller.open()
})
Text("退出").fontColor('#DEDFDF')
.onClick(() => {
app.terminate()
})
}
.margin({right: 20})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
}
//自定义弹窗
@CustomDialog
struct CustomDialogExample {
controller: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({}),
})
//小东计算器的帮助文档
build() {
Column() {
// 标题
Text('小东计算器使用帮助')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
.fontColor($r('app.string.calculator_text_color'))
// 帮助内容
Text('本计算器支持四则运算(+-×÷),输入数字后点击运算符继续计算,按等号(=)显示结果。\n\n'
+ '特殊功能:\n'
+ '• CE:清除当前输入\n'
+ '• C:完全重置计算器\n'
+ '• DE:删除最后一位数字\n'
+ '• 小数点:点击 . 输入小数部分')
.fontSize(16)
.lineHeight(24)
.fontColor($r('app.string.calculator_text_color'))
}
.justifyContent(FlexAlign.Center)
.padding(20)
.width('100%')
.backgroundColor($r('app.string.calculator_bk_color'))
}
}
@CustomDialog
struct CustomDialogExample2 {
controller: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample2({}),
})
//设置
build() {
Column() {
//日间模式和夜间模式切换按钮
Row() {
Text('日间模式').fontColor($r('app.string.calculator_text_color'))
Toggle({ type: ToggleType.Switch, isOn: true })
}
}.justifyContent(FlexAlign.Center)
.padding(20)
.width('100%')
.backgroundColor($r('app.string.calculator_bk_color'))
}
}
3. listView.ets
展示区的实现
ts
/**
* 计算器结果视图组件
*
* @Component 声明为可复用的自定义组件
*
* @State isUnFold - 控制模式选择下拉框的展开状态
* @State calculatorState - 当前计算器模式(标准/科学)
* @Link calculatorResultText - 双向绑定的计算结果文本
* @Link res - 双向绑定的计算结果数值
* @Consume list - 消费上下文中的历史记录列表
* @State isHover - 按钮悬停状态标识
* @State m - M系列功能按钮的标签数组
*/
@Component
export default struct resultView {
// ... 状态变量声明保持不变
build() {
Column() {
/* 顶部操作栏 - 包含模式切换按钮和历史记录入口 */
Row(){
// 左侧操作区:模式切换按钮组
Row(){
// 模式展开/收起触发器
Image($r('app.media.ic_grid_setting'))
.onClick(() => { this.isUnFold = !this.isUnFold })
// 当前模式显示文本
Text(this.calculatorState)
.margin({left: 20, right: 20})
// 辅助功能按钮
Image($r('app.media.ic_global_menu'))
}
// 右侧操作区:历史记录入口
Row(){
Image($r('app.media.ic_time'))
.onClick(() => { router.pushUrl({ url: 'pages/AfterCheck' }) })
}
}
.justifyContent(FlexAlign.SpaceBetween)
/* 结果展示区域 - 包含模式选择下拉框和计算结果显示 */
Stack(){
// 模式选择下拉框(条件渲染)
if (this.isUnFold){
Column({space: 10}) {
// 标准模式选项
Text("标准计算器").onClick(() => {
this.calculatorState = '标准'
this.isUnFold = false
})
// 科学模式选项(未实现)
Text("科学计算器").onClick(() => {
this.calculatorState = '科学'
this.isUnFold = false
})
}
}
// 计算结果显示区域
Row() {
Column() {
Text(`${this.calculatorResultText}`) // 显示计算结果文本
.textAlign(TextAlign.End) // 右对齐显示
}
}
}.alignContent(Alignment.TopStart)
/* M系列功能按钮区域 - 当前为占位实现 */
Row() {
ForEach(this.m, (item: string) =>{
Column() {
Button(item)
.hoverEffect(HoverEffect.Scale) // 悬停缩放效果
.onHover((hovered: boolean) => {
this.isHover = hovered // 更新悬停状态
})
}
})
}
}
.padding(10)
}
}
4. buttonView.ets
按钮区的代码说明
ts
/**
* 计算器按钮视图组件
* @Component 标记为可复用的UI组件
* @struct 定义组件结构
*
* @State button - 按钮标签数组,包含计算器所有操作符和数字:
* '%' 取模, 'CE' 清空输入, 'C' 清空, 'DE' 删除字符,
* 数字0-9, 运算符x-乘、-减、+加、÷除, '=' 计算结果
* @State isHover - 按钮悬停状态标识
* @Link res - 双向绑定的计算结果数值
* @Link calculatorResultText - 双向绑定的计算表达式文本
* @Consume list - 消费的历史记录列表,用于存储计算记录
*/
@Component
export default struct buttonView {
@State button: string[] = ['%','CE','C','DE', '7', '8', '9', 'x', '4', '5', '6', '-', '1', '2', '3', '+', '0', '.', '÷','=']
@State isHover:boolean = false
@Link res: number
@Link calculatorResultText: string;
@Consume list: string[]
/**
* 构建计算器界面布局
* 使用网格布局创建按钮矩阵,处理按钮交互事件
*/
build() {
Column() {
Grid(){
// 动态生成计算器按钮矩阵
ForEach(this.button, (item: string, index: number) =>{
GridItem() {
Button(item)
.fontSize(24)
.fontColor($r('app.string.calculator_text_color'))
.backgroundColor(this.isHover? Color.Gray : $r('app.string.calculator_bk_color'))
.hoverEffect(HoverEffect.Scale)
.onHover((hover: boolean) => {
this.isHover = hover; // 更新全局悬停状态
})
.onClick(() => {
// 处理不同按钮的点击逻辑
if (item === '=') {
this.calculator(this.calculatorResultText)
}
if(item === 'CE'){
this.calculatorResultText = ''
}
if(item === 'C'){
this.calculatorResultText = ""
}
if(item === 'DE'){
this.calculatorResultText = this.calculatorResultText.slice(0, -1)
}
// 处理数字和运算符输入
if(item !== 'CE' && item !== 'C' && item !== 'DE' && item !== '='){
this.calculatorResultText += item
}
})
}
.width(80)
.height(80)
})
}
.width('100%')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr') // 8行等分布局
.columnsTemplate('1fr 1fr 1fr 1fr') // 4列等分布局
}
.width('100%')
}
/**
* 计算器核心逻辑处理函数
* @param text - 需要计算的表达式字符串
* 功能:解析计算表达式,执行运算,更新结果和历史记录
*/
calculator(text: string) {
let exe: string = text;
// 解析表达式为操作数和运算符数组
let arr: string[] = []
for (let i = 0; i < text.length; i++) {
if (text[i] === '+' || text[i] === '-' || text[i] === 'x' || text[i] === '÷' || text[i] === '%') {
arr.push(text.slice(0, i))
arr.push(text[i])
text = text.slice(i + 1)
i = 0
}
}
// 根据运算符执行对应计算
if(arr[1] === '+'){
this.res = Number(arr[0]) + Number(text)
}
else if(arr[1] === '-'){
this.res = Number(arr[0]) - Number(text)
}
else if(arr[1] === 'x'){
this.res = Number(arr[0]) * Number(text)
}
else if(arr[1] === '÷'){
this.res = Number(arr[0]) / Number(text)
}
else if(arr[1] === '%'){
this.res = Number(arr[0]) % Number(text)
}
// 记录完整计算表达式并更新结果
exe += '=' + this.res
this.calculatorResultText = this.res.toString()
this.list.push(exe) // 添加历史记录
}
}