买菜计算器小应用 - HarmonyOS ArkUI 开发实战-PC版本

一、应用概述

买菜计算器小应用是一款基于HarmonyOS ArkUI框架开发的基础四则运算计算器工具。在日常生活中,无论是买菜算账、计算折扣、还是简单的加减乘除运算,都需要一个便捷的计算工具。虽然手机自带计算器功能强大,但往往界面复杂、操作繁琐。本应用旨在提供一个简洁、直观、易用的计算器解决方案,让用户能够快速完成日常计算需求。

1.1 应用定位

买菜计算器定位于"实用主义"工具应用,核心设计理念是"简单高效"。应用专注于最基础的四则运算功能,摒弃了复杂的科学计算、历史记录等功能,让用户可以在最短时间内完成计算操作。这种设计理念使得应用界面清晰、操作直观,特别适合老年人群体和追求效率的用户。

1.2 目标用户群体

  • 家庭主妇:买菜时快速计算总价、比较价格
  • 老年人群体:大字体显示、简单操作、无需学习成本
  • 学生群体:快速验证简单计算结果
  • 上班族:日常工作中快速计算数据

1.3 核心功能特性

功能模块 功能描述 技术实现
数字显示 大屏幕实时显示输入和结果 Text组件 + @State状态
数字输入 支持0-9和小数点输入 Grid + Button组件
四则运算 加减乘除四种基本运算 switch语句处理
清除功能 一键清除所有输入 状态重置方法
等号计算 执行运算并显示结果 calculate方法

1.4 应用界面预览

应用界面采用经典的计算器布局结构:

  • 顶部导航栏:包含返回按钮和应用标题
  • 显示区域:大字体显示当前输入和计算结果
  • 按钮区域:4x5网格布局的数字和运算符按钮

二、技术架构设计

2.1 整体架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    CalculatorApp 组件                        │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │              状态管理层 (@State)                      │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌────────────┐ │   │
│  │  │  display_1   │  │  firstNum_1  │  │ operator_1 │ │   │
│  │  │  (显示内容)   │  │  (第一操作数) │  │  (运算符)  │ │   │
│  │  └──────────────┘  └──────────────┘  └────────────┘ │   │
│  │  ┌────────────────────────────────────────────────┐ │   │
│  │  │          waitingForSecond_1 (等待第二操作数)    │ │   │
│  │  └────────────────────────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────────┘   │
│                            ↓                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              业务逻辑层 (方法)                        │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌────────────┐ │   │
│  │  │ appendNumber │  │ setOperator  │  │ calculate  │ │   │
│  │  │  (追加数字)   │  │  (设置运算符) │  │  (计算)    │ │   │
│  │  └──────────────┘  └──────────────┘  └────────────┘ │   │
│  │  ┌────────────────────────────────────────────────┐ │   │
│  │  │              clear_1 (清除所有状态)             │ │   │
│  │  └────────────────────────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────────┘   │
│                            ↓                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              视图层 (build方法)                       │   │
│  │  ┌───────────────────────────────────────────────┐  │   │
│  │  │  Row (导航栏)                                  │  │   │
│  │  │  ├── Button (返回)                            │  │   │
│  │  │  └── Text (标题)                              │  │   │
│  │  └───────────────────────────────────────────────┘  │   │
│  │  ┌───────────────────────────────────────────────┐  │   │
│  │  │  Column (内容区)                               │  │   │
│  │  │  ├── Text (显示屏)                             │  │   │
│  │  │  └── Grid (按钮网格)                           │  │   │
│  │  │      └── ForEach → GridItem → Button           │  │   │
│  │  └───────────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

2.2 数据流设计

本应用采用状态驱动的数据流架构,用户操作触发状态更新,进而驱动视图刷新:

复制代码
点击数字按钮 → appendNumber方法 → 更新display_1状态 → 显示屏刷新
                                              ↓
点击运算符按钮 → setOperator方法 → 保存firstNum_1和operator_1 → 设置等待标志
                                              ↓
点击等号按钮 → calculate方法 → 执行运算 → 更新display_1 → 显示结果
                                              ↓
点击清除按钮 → clear方法 → 重置所有状态 → 显示屏显示0

2.3 计算器状态机

计算器的核心逻辑可以抽象为一个状态机,包含以下状态:

状态 说明 触发条件
输入第一操作数 用户正在输入第一个数字 初始状态或清除后
等待运算符 第一操作数已输入,等待选择运算符 输入数字后
输入第二操作数 用户正在输入第二个数字 选择运算符后
显示结果 计算完成,显示运算结果 点击等号后

2.4 组件层级关系

typescript 复制代码
Column (根容器)
├── Row (导航栏容器)
│   ├── Button (返回按钮)
│   └── Text (应用标题)
└── Column (内容容器)
    ├── Text (显示屏组件)
    └── Grid (按钮网格容器)
        └── ForEach (循环渲染)
            └── GridItem (网格项容器)
                └── Button (按钮组件)

2.5 技术选型说明

技术点 选择方案 选择理由
开发语言 ArkTS HarmonyOS官方推荐,强类型支持,编译时检查
UI框架 ArkUI声明式 代码简洁,状态驱动,易于维护
状态管理 @State装饰器 轻量级,适合简单应用,自动触发UI刷新
按钮布局 Grid组件 规则网格排列,适合计算器按钮布局
循环渲染 ForEach组件 简洁高效,自动遍历数组生成组件

三、核心组件详解

3.1 Grid组件深度解析

Grid组件是本应用的核心布局组件,用于实现计算器按钮的规则网格排列。

3.1.1 Grid基础用法
typescript 复制代码
Grid() {
  ForEach(['C', '/', '*', '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '=', '0', '.'], (btn_1: string) => {
    GridItem() {
      Button(btn_1)
        .width('100%')
        .height(60)
        .fontSize(24)
        .backgroundColor(btn_1 === '=' ? '#0A59F7' : btn_1 === 'C' ? '#FF3B30' : '#F1F3F5')
        .fontColor(btn_1 === '=' || btn_1 === 'C' ? '#FFFFFF' : '#333333')
        .onClick(() => {
          // 点击事件处理
        })
    }
  })
}
.columnsTemplate('1fr 1fr 1fr 1fr')  // 4列等宽布局
.columnsGap(8)                        // 列间距8vp
.rowsGap(8)                           // 行间距8vp
.width('90%')
.margin({ top: 20 })
3.1.2 Grid属性详解表
属性名 类型 默认值 说明
columnsTemplate string - 列布局模板,如'1fr 1fr 1fr 1fr'表示4列等宽
rowsTemplate string - 行布局模板,如'1fr 1fr 1fr'表示3行等高
columnsGap Length 0 列间距
rowsGap Length 0 行间距
width Length - Grid宽度
height Length - Grid高度
scrollBar BarState Off 滚动条状态
layoutDirection GridDirection Row 布局方向
3.1.3 Grid布局模板详解

Grid的columnsTemplate和rowsTemplate属性使用类似CSS Grid的模板语法:

typescript 复制代码
// 4列等宽布局
.columnsTemplate('1fr 1fr 1fr 1fr')

// 3行等高布局
.rowsTemplate('1fr 1fr 1fr')

// 不等宽布局:第一列占50%,其余各占25%
.columnsTemplate('2fr 1fr 1fr 1fr')

// 固定宽度列:第一列固定100vp,其余自适应
.columnsTemplate('100vp 1fr 1fr 1fr')

// 混合布局:第一列固定,第二列自适应,第三列占2份
.columnsTemplate('100vp auto 2fr')
3.1.4 GridItem组件

GridItem是Grid的子组件,用于包裹每个网格项的内容:

typescript 复制代码
GridItem() {
  // 网格项内容
  Button('按钮文本')
    .width('100%')
    .height(60)
}
// GridItem可选属性
.rowStart(0)    // 起始行索引
.rowEnd(1)      // 结束行索引
.columnStart(0) // 起始列索引
.columnEnd(1)   // 结束列索引

3.2 Button组件深度解析

Button组件是计算器按钮的核心交互组件,每个按钮承担不同的功能。

3.2.1 按钮样式动态配置
typescript 复制代码
Button(btn_1)
  .width('100%')
  .height(60)
  .fontSize(24)
  // 根据按钮类型动态设置背景色
  .backgroundColor(btn_1 === '=' ? '#0A59F7' : btn_1 === 'C' ? '#FF3B30' : '#F1F3F5')
  // 根据按钮类型动态设置文字颜色
  .fontColor(btn_1 === '=' || btn_1 === 'C' ? '#FFFFFF' : '#333333')
  .onClick(() => {
    // 根据按钮类型执行不同操作
    if (btn_1 === 'C') {
      this.clear_1();
    } else if (btn_1 === '=') {
      this.calculate_1();
    } else if (['+', '-', '*', '/'].includes(btn_1)) {
      this.setOperator_1(btn_1);
    } else {
      this.appendNumber_1(btn_1);
    }
  })
3.2.2 Button属性详解表
属性名 类型 默认值 说明
label string - 按钮显示文本
width Length - 按钮宽度
height Length - 按钮高度
backgroundColor ResourceColor - 背景颜色
fontColor ResourceColor - 文字颜色
fontSize Length - 字体大小
fontWeight FontWeight Normal 字体粗细
borderRadius Length - 圆角半径
type ButtonType Capsule 按钮类型
stateEffect boolean true 是否开启按压效果
3.2.3 按钮类型与功能映射
按钮文本 按钮类型 背景色 功能说明
C 清除按钮 #FF3B30 清除所有输入,重置计算器
= 等号按钮 #0A59F7 执行运算,显示结果
+ 加法运算符 #F1F3F5 设置加法运算
- 减法运算符 #F1F3F5 设置减法运算
* 乘法运算符 #F1F3F5 设置乘法运算
/ 除法运算符 #F1F3F5 设置除法运算
0-9 数字按钮 #F1F3F5 输入数字
. 小数点按钮 #F1F3F5 输入小数点

3.3 Text组件深度解析

Text组件用于显示计算器的显示屏,实时显示用户输入和计算结果。

3.3.1 显示屏实现
typescript 复制代码
Text(this.display_1)
  .fontSize(40)                    // 大字体便于阅读
  .fontWeight(FontWeight.Bold)     // 粗体显示
  .width('90%')                    // 占据90%宽度
  .textAlign(TextAlign.RIGHT)      // 右对齐显示
  .margin({ top: 20 })             // 顶部间距
3.3.2 Text属性详解表
属性名 类型 默认值 说明
content string - 显示文本内容
fontSize Length - 字体大小
fontWeight FontWeight Normal 字体粗细
fontColor ResourceColor - 文字颜色
textAlign TextAlign Start 文本对齐方式
maxLines number - 最大行数限制
textOverflow TextOverflow Clip 文本溢出处理
3.3.3 TextAlign对齐方式
typescript 复制代码
// 左对齐
Text('内容')
  .textAlign(TextAlign.Start)

// 居中对齐
Text('内容')
  .textAlign(TextAlign.Center)

// 右对齐(计算器常用)
Text('内容')
  .textAlign(TextAlign.End)

3.4 ForEach组件深度解析

ForEach用于循环渲染Grid中的按钮组件,简化代码结构。

3.4.1 ForEach基础语法
typescript 复制代码
ForEach(
  ['C', '/', '*', '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '=', '0', '.'],  // 数据源数组
  (btn_1: string) => {                    // item生成函数
    GridItem() {
      Button(btn_1)
        // 按钮样式和事件
    }
  },
  (btn_1: string, index_1: number) => {   // key生成函数(可选)
    return 'btn_' + index_1;
  }
)
3.4.2 ForEach参数说明
参数 类型 必填 说明
arr Array 数据源数组
itemGenerator (item: any, index?: number) => void 组件生成函数
keyGenerator (item: any, index?: number) => string 键值生成函数

四、状态管理详解

4.1 @State装饰器原理

本应用使用四个@State装饰的状态变量来管理计算器的核心状态:

typescript 复制代码
@State display_1: string = '0';           // 显示屏内容
@State firstNum_1: number = 0;            // 第一个操作数
@State operator_1: string = '';           // 当前运算符
@State waitingForSecond_1: boolean = false; // 是否等待输入第二操作数

4.2 状态变量详解

4.2.1 display_1状态
typescript 复制代码
// display_1存储当前显示屏的内容
// 初始值为'0',表示显示屏初始显示0
@State display_1: string = '0';

// 状态更新示例
this.display_1 = '123';      // 显示数字123
this.display_1 = '45.67';    // 显示小数45.67
this.display_1 = '0';        // 重置显示
4.2.2 firstNum_1状态
typescript 复制代码
// firstNum_1存储第一个操作数(运算前的数字)
// 用于在点击运算符后保存当前显示的数字
@State firstNum_1: number = 0;

// 状态更新示例
this.firstNum_1 = parseFloat(this.display_1);  // 将显示内容转为数字保存
4.2.3 operator_1状态
typescript 复制代码
// operator_1存储当前选择的运算符
// 支持'+', '-', '*', '/'四种运算符
@State operator_1: string = '';

// 状态更新示例
this.operator_1 = '+';  // 加法运算
this.operator_1 = '-';  // 减法运算
this.operator_1 = '*';  // 乘法运算
this.operator_1 = '/';  // 除法运算
this.operator_1 = '';   // 清除运算符
4.2.4 waitingForSecond_1状态
typescript 复制代码
// waitingForSecond_1标记是否正在等待输入第二个操作数
// true表示已输入运算符,等待输入第二个数字
// false表示正在输入第一个数字或已完成计算
@State waitingForSecond_1: boolean = false;

// 状态更新示例
this.waitingForSecond_1 = true;   // 设置等待状态
this.waitingForSecond_1 = false;  // 清除等待状态

4.3 状态管理流程图

复制代码
┌───────────────────────────────────────────────────────────────┐
│                      计算器状态流程                             │
├───────────────────────────────────────────────────────────────┤
│                                                                │
│  初始状态                                                       │
│  display_1 = '0'                                               │
│  firstNum_1 = 0                                                │
│  operator_1 = ''                                               │
│  waitingForSecond_1 = false                                    │
│                                                                │
│              ↓                                                 │
│                                                                │
│  输入数字(appendNumber)                                        │
│  display_1 = '123'                                             │
│  waitingForSecond_1 = false                                    │
│                                                                │
│              ↓                                                 │
│                                                                │
│  选择运算符(setOperator)                                        │
│  firstNum_1 = 123                                              │
│  operator_1 = '+'                                              │
│  waitingForSecond_1 = true                                     │
│                                                                │
│              ↓                                                 │
│                                                                │
│  输入第二数字(appendNumber)                                     │
│  display_1 = '456'                                             │
│  waitingForSecond_1 = false                                    │
│                                                                │
│              ↓                                                 │
│                                                                │
│  点击等号(calculate)                                           │
│  result = firstNum_1 + parseFloat(display_1)                   │
│  display_1 = '579'                                             │
│  operator_1 = ''                                               │
│                                                                │
└───────────────────────────────────────────────────────────────┘

4.4 状态更新最佳实践

typescript 复制代码
// ✅ 推荐:使用方法封装状态更新逻辑
appendNumber_1(num_1: string) {
  if (this.waitingForSecond_1) {
    this.display_1 = num_1;
    this.waitingForSecond_1 = false;
  } else {
    this.display_1 = this.display_1 === '0' ? num_1 : this.display_1 + num_1;
  }
}

// ✅ 推荐:状态更新后立即清除相关标志
calculate_1() {
  // ...计算逻辑
  this.display_1 = String(result_1);
  this.operator_1 = '';  // 清除运算符
}

// ✅ 推荐:重置方法统一处理所有状态
clear_1() {
  this.display_1 = '0';
  this.firstNum_1 = 0;
  this.operator_1 = '';
  this.waitingForSecond_1 = false;
}

五、业务逻辑实现详解

5.1 数字追加方法

appendNumber_1方法是处理数字输入的核心方法,负责将用户点击的数字追加到显示屏。

typescript 复制代码
appendNumber_1(num_1: string) {
  if (this.waitingForSecond_1) {
    // 如果正在等待第二操作数,直接替换显示内容
    this.display_1 = num_1;
    this.waitingForSecond_1 = false;
  } else {
    // 如果正在输入第一操作数或继续输入第二操作数
    if (this.display_1 === '0') {
      // 如果当前显示0,直接替换
      this.display_1 = num_1;
    } else {
      // 否则追加数字
      this.display_1 = this.display_1 + num_1;
    }
  }
}
5.1.1 方法逻辑流程图
复制代码
用户点击数字按钮
        ↓
判断waitingForSecond_1状态
        ↓
┌───────┴───────┐
│               │
│ true          │ false
│               │
↓               ↓
直接替换显示    判断display_1是否为'0'
                ↓
        ┌───────┴───────┐
        │               │
        │ 是'0'         │ 不是'0'
        │               │
        ↓               ↓
        直接替换        追加数字
        │               │
        └───────┬───────┘
                ↓
        更新display_1状态
5.1.2 方法调用示例
typescript 复制代码
// 初始状态:display_1 = '0'
this.appendNumber_1('7');  // display_1 = '7'
this.appendNumber_1('8');  // display_1 = '78'
this.appendNumber_1('9');  // display_1 = '789'

// 选择运算符后:waitingForSecond_1 = true
this.appendNumber_1('4');  // display_1 = '4'(替换)
this.appendNumber_1('5');  // display_1 = '45'(追加)

5.2 运算符设置方法

setOperator_1方法负责保存第一个操作数和运算符,并设置等待第二操作数的标志。

typescript 复制代码
setOperator_1(op_1: string) {
  // 将当前显示内容转换为数字并保存为第一操作数
  this.firstNum_1 = parseFloat(this.display_1);
  
  // 保存运算符
  this.operator_1 = op_1;
  
  // 设置等待第二操作数标志
  this.waitingForSecond_1 = true;
}
5.2.1 方法逻辑说明
步骤 操作 说明
1 parseFloat转换 将字符串显示内容转为数字
2 保存firstNum_1 存储第一操作数供后续计算使用
3 保存operator_1 存储运算符供后续计算使用
4 设置等待标志 标记等待输入第二操作数
5.2.2 方法调用示例
typescript 复制代码
// 输入第一个数字
this.appendNumber_1('1');
this.appendNumber_1('2');
this.appendNumber_1('3');  // display_1 = '123'

// 选择加法运算符
this.setOperator_1('+');
// firstNum_1 = 123, operator_1 = '+', waitingForSecond_1 = true

5.3 计算方法

calculate_1方法是执行运算的核心方法,根据运算符执行相应的计算并显示结果。

typescript 复制代码
calculate_1() {
  // 获取第二操作数
  let secondNum_1: number = parseFloat(this.display_1);
  
  // 初始化结果变量
  let result_1: number = 0;
  
  // 根据运算符执行相应计算
  switch (this.operator_1) {
    case '+':
      result_1 = this.firstNum_1 + secondNum_1;
      break;
    case '-':
      result_1 = this.firstNum_1 - secondNum_1;
      break;
    case '*':
      result_1 = this.firstNum_1 * secondNum_1;
      break;
    case '/':
      result_1 = this.firstNum_1 / secondNum_1;
      break;
  }
  
  // 将结果转换为字符串并显示
  this.display_1 = String(result_1);
  
  // 清除运算符
  this.operator_1 = '';
}
5.3.1 switch语句详解
typescript 复制代码
// switch语句用于多分支条件判断
switch (this.operator_1) {
  case '+':
    // 加法运算
    result_1 = this.firstNum_1 + secondNum_1;
    break;  // 必须使用break跳出switch
  
  case '-':
    // 减法运算
    result_1 = this.firstNum_1 - secondNum_1;
    break;
  
  case '*':
    // 乘法运算
    result_1 = this.firstNum_1 * secondNum_1;
    break;
  
  case '/':
    // 除法运算
    result_1 = this.firstNum_1 / secondNum_1;
    break;
  
  // default分支可选,处理未匹配的情况
  default:
    result_1 = 0;
    break;
}
5.3.2 计算示例
typescript 复制代码
// 示例:计算 123 + 456
// firstNum_1 = 123, operator_1 = '+', display_1 = '456'
this.calculate_1();
// secondNum_1 = 456
// result_1 = 123 + 456 = 579
// display_1 = '579', operator_1 = ''

// 示例:计算 100 - 30
// firstNum_1 = 100, operator_1 = '-', display_1 = '30'
this.calculate_1();
// result_1 = 100 - 30 = 70
// display_1 = '70'

// 示例:计算 12 * 5
// firstNum_1 = 12, operator_1 = '*', display_1 = '5'
this.calculate_1();
// result_1 = 12 * 5 = 60
// display_1 = '60'

// 示例:计算 100 / 4
// firstNum_1 = 100, operator_1 = '/', display_1 = '4'
this.calculate_1();
// result_1 = 100 / 4 = 25
// display_1 = '25'

5.4 清除方法

clear_1方法负责重置所有状态变量,将计算器恢复到初始状态。

typescript 复制代码
clear_1() {
  // 重置显示屏显示为0
  this.display_1 = '0';
  
  // 重置第一操作数为0
  this.firstNum_1 = 0;
  
  // 清除运算符
  this.operator_1 = '';
  
  // 清除等待标志
  this.waitingForSecond_1 = false;
}
5.4.1 清除操作流程
复制代码
点击清除按钮(C)
        ↓
调用clear_1方法
        ↓
┌───────┴───────┐
│               │
│ 重置display_1 │ → '0'
│               │
│ 重置firstNum_1 │ → 0
│               │
│ 清除operator_1 │ → ''
│               │
│ 清除等待标志  │ → false
│               │
└───────┬───────┘
        ↓
计算器恢复初始状态

六、UI布局实现详解

6.1 整体布局结构

typescript 复制代码
Column() {
  // 导航栏
  Row() {
    Button('返回')
      .onClick(() => router.back())
    Text('买菜计算器小应用')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .layoutWeight(1)
      .textAlign(TextAlign.Center)
  }
  .width('100%')
  .padding(12)
  .backgroundColor('#F1F3F5')

  // 内容区域
  Column() {
    // 显示屏
    Text(this.display_1)
      .fontSize(40)
      .fontWeight(FontWeight.Bold)
      .width('90%')
      .textAlign(TextAlign.RIGHT)
      .margin({ top: 20 })

    // 按钮网格
    Grid() {
      ForEach(['C', '/', '*', '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '=', '0', '.'], (btn_1: string) => {
        GridItem() {
          Button(btn_1)
            .width('100%')
            .height(60)
            .fontSize(24)
            .backgroundColor(btn_1 === '=' ? '#0A59F7' : btn_1 === 'C' ? '#FF3B30' : '#F1F3F5')
            .fontColor(btn_1 === '=' || btn_1 === 'C' ? '#FFFFFF' : '#333333')
            .onClick(() => {
              if (btn_1 === 'C') {
                this.clear_1();
              } else if (btn_1 === '=') {
                this.calculate_1();
              } else if (['+', '-', '*', '/'].includes(btn_1)) {
                this.setOperator_1(btn_1);
              } else {
                this.appendNumber_1(btn_1);
              }
            })
        }
      })
    }
    .columnsTemplate('1fr 1fr 1fr 1fr')
    .columnsGap(8)
    .rowsGap(8)
    .width('90%')
    .margin({ top: 20 })
  }
  .width('100%')
  .layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')

6.2 按钮网格布局详解

6.2.1 按钮排列顺序

计算器按钮按照标准计算器布局排列:

复制代码
┌─────┬─────┬─────┬─────┐
│  C  │  /  │  *  │     │  第1行:清除、除法、乘法
├─────┼─────┼─────┼─────┤
│  7  │  8  │  9  │  -  │  第2行:7、8、9、减法
├─────┼─────┼─────┼─────┤
│  4  │  5  │  6  │  +  │  第3行:4、5、6、加法
├─────┼─────┼─────┼─────┤
│  1  │  2  │  3  │  =  │  第4行:1、2、3、等号
├─────┼─────┼─────┼─────┤
│  0  │  .  │     │     │  第5行:0、小数点
└─────┴─────┴─────┴─────┘
6.2.2 按钮数组定义
typescript 复制代码
const buttons: string[] = [
  'C', '/', '*',      // 第1行(缺少第4个按钮)
  '7', '8', '9', '-', // 第2行
  '4', '5', '6', '+', // 第3行
  '1', '2', '3', '=', // 第4行
  '0', '.'            // 第5行(缺少第3、4个按钮)
];
6.2.3 Grid布局参数
typescript 复制代码
Grid() {
  // ...
}
.columnsTemplate('1fr 1fr 1fr 1fr')  // 4列等宽
.columnsGap(8)                        // 列间距8vp
.rowsGap(8)                           // 行间距8vp

6.3 布局属性详解

6.3.1 尺寸属性
属性 类型 说明 示例
width Length 组件宽度 .width('90%')
height Length 组件高度 .height(60)
6.3.2 间距属性
属性 类型 说明 示例
margin Margin 外边距 .margin({ top: 20 })
padding Padding 内边距 .padding(12)
columnsGap Length Grid列间距 .columnsGap(8)
rowsGap Length Grid行间距 .rowsGap(8)
6.3.3 权重属性
typescript 复制代码
// layoutWeight用于分配剩余空间
Row() {
  Button('返回')
    // 固定宽度
  Text('标题')
    .layoutWeight(1)  // 占据剩余空间
}

七、样式定制详解

7.1 颜色规范

用途 颜色值 说明
主色调 #0A59F7 HarmonyOS主题蓝,用于等号按钮
危险操作 #FF3B30 红色,用于清除按钮
普通按钮 #F1F3F5 浅灰色,用于数字和运算符按钮
背景色 #FFFFFF 白色,主内容区背景
导航栏背景 #F1F3F5 浅灰色,区分导航区域
文字主色 #333333 深灰色,普通按钮文字
文字辅色 #FFFFFF 白色,特殊按钮文字

7.2 字体规范

typescript 复制代码
// 显示屏字体
Text(this.display_1)
  .fontSize(40)                    // 大字体便于阅读
  .fontWeight(FontWeight.Bold)     // 粗体显示

// 按钮字体
Button(btn_1)
  .fontSize(24)                    // 中等字体

7.3 按钮样式动态配置

typescript 复制代码
Button(btn_1)
  .backgroundColor(
    btn_1 === '=' ? '#0A59F7' :    // 等号按钮蓝色
    btn_1 === 'C' ? '#FF3B30' :    // 清除按钮红色
    '#F1F3F5'                      // 其他按钮灰色
  )
  .fontColor(
    btn_1 === '=' || btn_1 === 'C' ? '#FFFFFF' : '#333333'
  )

7.4 间距规范

元素 margin padding 说明
导航栏 - 12 导航栏内边距
显示屏 top: 20 - 显示屏顶部间距
按钮网格 top: 20 - 网格顶部间距
按钮间距 columnsGap: 8, rowsGap: 8 - 按钮行列间距

八、完整代码示例

8.1 完整源代码

typescript 复制代码
import { router } from '@kit.ArkUI';

@Entry
@Component
struct CalculatorApp {
  // 状态变量:显示屏内容
  @State display_1: string = '0';
  
  // 状态变量:第一个操作数
  @State firstNum_1: number = 0;
  
  // 状态变量:运算符
  @State operator_1: string = '';
  
  // 状态变量:是否等待第二操作数
  @State waitingForSecond_1: boolean = false;

  // 追加数字方法
  appendNumber_1(num_1: string) {
    if (this.waitingForSecond_1) {
      this.display_1 = num_1;
      this.waitingForSecond_1 = false;
    } else {
      this.display_1 = this.display_1 === '0' ? num_1 : this.display_1 + num_1;
    }
  }

  // 设置运算符方法
  setOperator_1(op_1: string) {
    this.firstNum_1 = parseFloat(this.display_1);
    this.operator_1 = op_1;
    this.waitingForSecond_1 = true;
  }

  // 计算方法
  calculate_1() {
    let secondNum_1: number = parseFloat(this.display_1);
    let result_1: number = 0;
    switch (this.operator_1) {
      case '+':
        result_1 = this.firstNum_1 + secondNum_1;
        break;
      case '-':
        result_1 = this.firstNum_1 - secondNum_1;
        break;
      case '*':
        result_1 = this.firstNum_1 * secondNum_1;
        break;
      case '/':
        result_1 = this.firstNum_1 / secondNum_1;
        break;
    }
    this.display_1 = String(result_1);
    this.operator_1 = '';
  }

  // 清除方法
  clear_1() {
    this.display_1 = '0';
    this.firstNum_1 = 0;
    this.operator_1 = '';
    this.waitingForSecond_1 = false;
  }

  build() {
    Column() {
      // 导航栏
      Row() {
        Button('返回')
          .onClick(() => router.back())
        Text('买菜计算器小应用')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
      }
      .width('100%')
      .padding(12)
      .backgroundColor('#F1F3F5')

      // 内容区域
      Column() {
        // 显示屏
        Text(this.display_1)
          .fontSize(40)
          .fontWeight(FontWeight.Bold)
          .width('90%')
          .textAlign(TextAlign.RIGHT)
          .margin({ top: 20 })

        // 按钮网格
        Grid() {
          ForEach(['C', '/', '*', '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '=', '0', '.'], (btn_1: string) => {
            GridItem() {
              Button(btn_1)
                .width('100%')
                .height(60)
                .fontSize(24)
                .backgroundColor(btn_1 === '=' ? '#0A59F7' : btn_1 === 'C' ? '#FF3B30' : '#F1F3F5')
                .fontColor(btn_1 === '=' || btn_1 === 'C' ? '#FFFFFF' : '#333333')
                .onClick(() => {
                  if (btn_1 === 'C') {
                    this.clear_1();
                  } else if (btn_1 === '=') {
                    this.calculate_1();
                  } else if (['+', '-', '*', '/'].includes(btn_1)) {
                    this.setOperator_1(btn_1);
                  } else {
                    this.appendNumber_1(btn_1);
                  }
                })
            }
          })
        }
        .columnsTemplate('1fr 1fr 1fr 1fr')
        .columnsGap(8)
        .rowsGap(8)
        .width('90%')
        .margin({ top: 20 })
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

8.2 代码结构说明

代码区域 行数 功能说明
导入语句 1 导入router模块
组件声明 3-5 定义CalculatorApp组件
状态变量 6-9 定义显示屏、操作数、运算符等状态
appendNumber方法 11-18 处理数字输入
setOperator方法 20-24 处理运算符设置
calculate方法 26-45 执行计算运算
clear方法 47-52 清除所有状态
导航栏 56-67 返回按钮和标题
显示屏 70-76 显示计算结果
按钮网格 78-105 数字和运算符按钮

九、组件对比分析

9.1 Grid与其他布局组件对比

组件 用途 特点 适用场景
Grid 网格布局 规则网格排列,行列模板配置 计算器按钮、图片墙、应用列表
Column 垂直布局 简单垂直排列 页面主体、垂直列表
Row 水平布局 简单水平排列 导航栏、工具栏
Flex 弹性布局 支持换行、对齐方式 复杂布局、响应式布局
List 列表布局 高性能滚动、自动回收 长列表、数据展示

9.2 Button与其他交互组件对比

组件 用途 特点 适用场景
Button 通用按钮 样式丰富,事件简单 提交、确认、计算器按钮
Toggle 开关切换 二态切换,状态明确 设置开关、功能启用
Checkbox 多选框 支持多选,状态独立 多选列表、选项组
Radio 单选按钮 互斥选择,状态关联 单选列表、选项组
Slider 滑动条 连续值选择 音量、亮度调节

十、最佳实践

10.1 状态管理最佳实践

typescript 复制代码
// ✅ 推荐:使用方法封装业务逻辑
appendNumber_1(num_1: string) {
  // 清晰的逻辑判断
  if (this.waitingForSecond_1) {
    this.display_1 = num_1;
    this.waitingForSecond_1 = false;
  } else {
    this.display_1 = this.display_1 === '0' ? num_1 : this.display_1 + num_1;
  }
}

// ✅ 推荐:使用switch处理多分支逻辑
switch (this.operator_1) {
  case '+':
    result_1 = this.firstNum_1 + secondNum_1;
    break;
  // ...
}

// ✅ 推荐:统一重置方法
clear_1() {
  this.display_1 = '0';
  this.firstNum_1 = 0;
  this.operator_1 = '';
  this.waitingForSecond_1 = false;
}

10.2 代码组织最佳实践

typescript 复制代码
// ✅ 推荐:将按钮数组提取为常量
const BUTTONS: string[] = [
  'C', '/', '*', '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '=', '0', '.'
];

// ✅ 推荐:将样式提取为常量
const BUTTON_COLORS = {
  equal: '#0A59F7',
  clear: '#FF3B30',
  normal: '#F1F3F5'
};

// ✅ 推荐:使用方法判断按钮类型
isOperatorButton(btn: string): boolean {
  return ['+', '-', '*', '/'].includes(btn);
}

isSpecialButton(btn: string): boolean {
  return btn === 'C' || btn === '=';
}

10.3 性能优化最佳实践

typescript 复制代码
// ✅ 推荐:为ForEach提供key生成函数
ForEach(
  BUTTONS,
  (btn_1: string, index_1: number) => {
    GridItem() {
      Button(btn_1)
      // ...
    }
  },
  (btn_1: string, index_1: number) => {
    return 'btn_' + index_1 + '_' + btn_1;
  }
)

十一、扩展功能设计

11.1 百分比计算功能

typescript 复制代码
// 添加百分比按钮
const BUTTONS_WITH_PERCENT: string[] = [
  'C', '/', '*', '%',
  '7', '8', '9', '-',
  '4', '5', '6', '+',
  '1', '2', '3', '=',
  '0', '.', '±', ''
];

// 百分比计算方法
calculatePercent_1() {
  let currentNum: number = parseFloat(this.display_1);
  this.display_1 = String(currentNum / 100);
}

11.2 正负切换功能

typescript 复制代码
// 正负切换方法
toggleSign_1() {
  let currentNum: number = parseFloat(this.display_1);
  this.display_1 = String(-currentNum);
}

// 在onClick中添加处理
if (btn_1 === '±') {
  this.toggleSign_1();
}

11.3 历史记录功能

typescript 复制代码
// 定义历史记录数据模型
class CalculationHistory {
  expression: string = '';
  result: string = '';
  timestamp: number = 0;
}

// 添加历史记录状态
@State history_1: CalculationHistory[] = [];

// 保存历史记录方法
saveHistory_1() {
  let historyItem: CalculationHistory = {
    expression: `${this.firstNum_1} ${this.operator_1} ${this.display_1}`,
    result: this.display_1,
    timestamp: Date.now()
  };
  this.history_1 = [...this.history_1, historyItem];
}

11.4 连续运算功能

typescript 复制代码
// 支持连续运算(如 1+2+3+4)
@State lastResult_1: number = 0;

// 改进calculate方法支持连续运算
calculate_1() {
  let secondNum_1: number = parseFloat(this.display_1);
  let result_1: number = 0;
  
  switch (this.operator_1) {
    case '+':
      result_1 = this.firstNum_1 + secondNum_1;
      break;
    // ...
  }
  
  this.display_1 = String(result_1);
  this.lastResult_1 = result_1;
  this.firstNum_1 = result_1;  // 将结果作为下一次运算的第一操作数
  this.operator_1 = '';
}

11.5 错误处理功能

typescript 复制代码
// 添加错误处理
calculate_1() {
  let secondNum_1: number = parseFloat(this.display_1);
  
  // 除零错误处理
  if (this.operator_1 === '/' && secondNum_1 === 0) {
    this.display_1 = 'Error';
    return;
  }
  
  let result_1: number = 0;
  switch (this.operator_1) {
    case '+':
      result_1 = this.firstNum_1 + secondNum_1;
      break;
    // ...
  }
  
  // 数字溢出处理
  if (result_1 > Number.MAX_SAFE_INTEGER || result_1 < Number.MIN_SAFE_INTEGER) {
    this.display_1 = 'Overflow';
    return;
  }
  
  this.display_1 = String(result_1);
  this.operator_1 = '';
}

十二、性能优化策略

12.1 Grid渲染优化

typescript 复制代码
// ✅ 推荐:固定Grid高度避免动态计算
Grid() {
  // ...
}
.height(300)  // 固定高度
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')  // 固定行模板

// ❌ 不推荐:动态高度可能导致性能问题
Grid() {
  // ...
}
.height('auto')  // 动态高度

12.2 状态更新优化

typescript 复制代码
// ✅ 推荐:批量状态更新
clear_1() {
  // 同时更新多个状态
  this.display_1 = '0';
  this.firstNum_1 = 0;
  this.operator_1 = '';
  this.waitingForSecond_1 = false;
}

// ✅ 推荐:避免不必要的状态更新
appendNumber_1(num_1: string) {
  // 只在必要时更新状态
  if (this.waitingForSecond_1) {
    this.display_1 = num_1;
    this.waitingForSecond_1 = false;
  } else if (this.display_1 !== '0' || num_1 !== '0') {
    this.display_1 = this.display_1 === '0' ? num_1 : this.display_1 + num_1;
  }
}

12.3 按钮点击优化

typescript 复制代码
// ✅ 推荐:使用方法封装点击逻辑
handleButtonClick_1(btn_1: string) {
  if (btn_1 === 'C') {
    this.clear_1();
  } else if (btn_1 === '=') {
    this.calculate_1();
  } else if (this.isOperatorButton(btn_1)) {
    this.setOperator_1(btn_1);
  } else {
    this.appendNumber_1(btn_1);
  }
}

Button(btn_1)
  .onClick(() => {
    this.handleButtonClick_1(btn_1);
  })

十三、总结

买菜计算器小应用是一个典型的HarmonyOS ArkUI入门项目,通过这个项目我们学习了以下核心知识点:

13.1 技术要点总结

技术点 学习内容 实践应用
状态管理 @State装饰器的使用 管理显示屏、操作数、运算符等状态
组件使用 Grid、Button、Text、ForEach 构建计算器界面
布局技术 Grid网格布局、columnsTemplate 实现规则按钮排列
业务逻辑 switch语句、方法封装 实现四则运算逻辑
事件处理 onClick事件分发 处理不同按钮点击

13.2 开发流程总结

复制代码
需求分析 → 状态设计 → 业务逻辑实现 → 界面布局 → 样式定制 → 测试优化

13.3 最佳实践总结

  1. 状态管理:使用多个@State变量管理复杂状态,用方法封装状态更新逻辑
  2. 布局设计:Grid组件适合规则网格布局,使用columnsTemplate配置列宽
  3. 代码组织:将按钮数组、样式常量提取为独立定义,提高代码可维护性
  4. 业务逻辑:使用switch处理多分支逻辑,统一清除方法重置所有状态

13.4 扩展方向

  • 百分比计算:添加百分号按钮和计算逻辑
  • 正负切换:添加正负号切换功能
  • 历史记录:保存和显示计算历史
  • 连续运算:支持连续多次运算
  • 错误处理:处理除零、溢出等错误情况
  • 科学计算:添加三角函数、对数等高级功能

通过这个项目的学习,开发者可以掌握HarmonyOS ArkUI的基础开发技能,特别是Grid布局、状态管理和业务逻辑封装等核心知识点,为开发更复杂的应用打下坚实基础。

相关推荐
阿捏利2 小时前
系列总览-鸿蒙科普系列完全指南
华为·harmonyos
小雨下雨的雨2 小时前
HarmonyOS ArkUI训练营入门-组件掌握系列-Animation 动画效果实现-PC版本
学习·华为·harmonyos·鸿蒙
yuegu7772 小时前
HarmonyOS应用<节气通>开发第32篇:ArkTS语法快速入门——从TypeScript到声明式UI的完整指南
harmonyos
cqbzcsq3 小时前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
2601_962072554 小时前
李梦娇常识4600问|题库|打印版
sql·华为od·华为·c#·华为云·.net·harmonyos
伶俜664 小时前
鸿蒙原生应用实战(十九)ArkUI 喝水提醒 App:定时通知 + 每日记录 + 统计图表
华为·harmonyos
YangYang9YangYan4 小时前
2026初入职场学习数据分析的价值
学习·数据挖掘·数据分析
guslegend4 小时前
理论学习:什么是 Coding Agent?
学习
自传.5 小时前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding