还在为组件状态混乱、页面跳转丢参数而头疼?
这篇博客将揭秘如何用鸿蒙ArkTS打造一个漂亮美观的智能计算器:
✅ 输入完整表达式,秒出结果——字符串切割简单计算
✅ 状态管理黑科技——@Provide/@Consume 实现跨组件实时响应
✅ 路由传参实战——历史记录页面跳转不丢数据,跨页面传参
✅ 代码即设计稿——ArkTS声明式UI开发,代码比PPT更直观
快捷跳转到想看的地方
- 一、项目展示
- 二、技术栈讲解
- 三、项目结构说明
- 四、核心代码实现
- 1. `Index.ets`父组件总布局查看
- 2. `navbar.ets`实现
- 3. `listView.ets` 展示区的实现
- 4. `buttonView.ets` 按钮区的代码说明
- 五、点赞收藏支持一下吧,代码源码私信我
一、项目展示
这里展示了计算器的加减乘除取余等基本操作,展示了历史记录功能,展示了帮助弹窗
二、技术栈讲解
- 使用arkUI通用组件开发
- 自定义弹窗的实现
- 使用@State,@Link, @Provide, @Consume进行状态管理
- 使用router实现路由跳转以及参数携带
- 使用分模块化结构高效开发
三、项目结构说明
src/ //存放所有与项目逻辑相关的代码文件
├── main/ //包含应用程序的核心模块和组件。
│ ├── ets/ //用于存放与 ArkTS(或类似框架)相关的代码。
│ │ ├── common/ //存放可复用的工具类、辅助函数等。
│ │ ├── data/ //存放数据,这里的data层是不必要的,由于组件数据较多。写在文件里有点乱,这里分了一层
│ │ ├── entryability/ //存放与应用入口相关的逻辑,例如启动时的初始化逻辑。
│ │ ├── entrybackupability/ //存放备用入口逻辑,可能用于异常情况下的备份入口。
│ │ ├── views/ //存放页面中的组件
│ │ ├── pages/ //存放各个页面的逻辑、UI 组件等。
│ │ └── util/ //存放通用工具函数、辅助方法等。
│ └── module.json5 //项目配置文件
└── resources/ //存放静态资源文件,如图片、字体、样式文件等。
四、核心代码实现
1. Index.ets
父组件总布局查看
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
实现
// 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(() => {//触发弹窗2this.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
展示区的实现
/*** 计算器结果视图组件* * @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
按钮区的代码说明
/*** 计算器按钮视图组件* @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.resthis.calculatorResultText = this.res.toString()this.list.push(exe) // 添加历史记录}
}