HarmonyOS NEXT 教程:ArkTS 实现简易计算器 App 实例

计算器项目详细设计

以下是一个基于 HarmonyOS NEXT 的简易计算器 App 实例,涵盖 UI 布局、事件处理和基础计算逻辑,帮助开发者快速上手鸿蒙应用开发:

一、项目创建与结构

  1. 新建项目(SimpleCalculator) 在 DevEco Studio 中选择 “Create Project”,模板选 “Empty Ability”,Language 选择 “ArkTS”,项目名为 SimpleCalculator

  2. 项目结构说明

    plaintext 复制
    SimpleCalculator/
    ├── entry/                      # 主模块
    │   ├── src/
    │   │   ├── main/
    │   │   │   ├── ets/            # ArkTS源码目录
    │   │   │   │   ├── pages/      # 页面文件夹
    │   │   │   │   │   └── index.ets  # 主页面
    │   │   │   ├── resources/      # 资源文件
    │   │   └── module.json5        # 模块配置
    └── build-profile.json5          # 构建配置
    

二、UI 布局与逻辑实现

1. index.ets 页面代码

typescript 复制
// entry/src/main/ets/pages/index.ets
import promptAction from '@ohos.promptAction';

@Entry
@Component
struct Index {
  // 状态管理:存储计算表达式和结果
  @State expression: string = '0'
  @State result: string = '0'
  @State isNewInput: boolean = true  // 标记是否开始新输入

  // 数字按钮点击处理
  handleNumberClick(num: string) {
    if (this.isNewInput) {
      // 新输入时覆盖当前显示
      this.expression = num
      this.isNewInput = false
    } else {
      // 追加数字
      this.expression = this.expression === '0' ? num : this.expression + num
    }
  }

  // 运算符按钮点击处理
  handleOperatorClick(operator: string) {
    if (this.isNewInput) return;
    
    // 避免重复点击运算符
    if (['+', '-', '*', '/'].includes(this.result)) {
      this.result = operator
      return
    }

    // 计算当前表达式并更新结果
    this.result = this.calculate(this.expression, this.result)
    this.expression = this.result
    this.result = operator
    this.isNewInput = true
  }

  // 等号按钮处理(计算最终结果)
  handleEqualClick() {
    if (this.isNewInput || !this.result) return;
    
    this.result = this.calculate(this.expression, this.result)
    this.expression = this.result
    this.isNewInput = true
    this.result = ''
  }

  // 清除按钮处理
  handleClearClick() {
    this.expression = '0'
    this.result = '0'
    this.isNewInput = true
  }

  // 计算函数
  calculate(num1: string, operator: string): string {
    const num = parseFloat(num1)
    switch (operator) {
      case '+': return (parseFloat(this.result) + num).toString()
      case '-': return (parseFloat(this.result) - num).toString()
      case '*': return (parseFloat(this.result) * num).toString()
      case '/': 
        if (num === 0) {
          promptAction.showToast({ message: '除数不能为0' })
          return this.result
        }
        return (parseFloat(this.result) / num).toString()
      default: return num.toString()
    }
  }

  build() {
    Column() {
      // 显示区域
      Stack() {
        Text(this.expression)
          .fontSize(48)
          .fontWeight(FontWeight.Bold)
          .textAlign(TextAlign.End)
          .width('100%')
          .margin(16)
          .foregroundColor('#000000')
      }
      .height(120)
      .width('100%')
      .backgroundColor('#F5F5F5')
      .marginBottom(16)

      // 按钮区域 - 4x4网格
      Grid() {
        // 第一行:C(清除)、±(正负)、%(百分比)、÷(除法)
        GridItem() {
          Button('C')
            .width(80)
            .height(80)
            .backgroundColor('#E0E0E0')
            .onClick(() => this.handleClearClick())
        }
        GridItem() {
          Button('±')
            .width(80)
            .height(80)
            .backgroundColor('#E0E0E0')
        }
        GridItem() {
          Button('%')
            .width(80)
            .height(80)
            .backgroundColor('#E0E0E0')
        }
        GridItem() {
          Button('÷')
            .width(80)
            .height(80)
            .backgroundColor('#FF9500')
            .onClick(() => this.handleOperatorClick('/'))
        }

        // 第二行:7、8、9、×(乘法)
        GridItem() {
          Button('7')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('7'))
        }
        GridItem() {
          Button('8')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('8'))
        }
        GridItem() {
          Button('9')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('9'))
        }
        GridItem() {
          Button('×')
            .width(80)
            .height(80)
            .backgroundColor('#FF9500')
            .onClick(() => this.handleOperatorClick('*'))
        }

        // 第三行:4、5、6、-(减法)
        GridItem() {
          Button('4')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('4'))
        }
        GridItem() {
          Button('5')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('5'))
        }
        GridItem() {
          Button('6')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('6'))
        }
        GridItem() {
          Button('-')
            .width(80)
            .height(80)
            .backgroundColor('#FF9500')
            .onClick(() => this.handleOperatorClick('-'))
        }

        // 第四行:1、2、3、+(加法)
        GridItem() {
          Button('1')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('1'))
        }
        GridItem() {
          Button('2')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('2'))
        }
        GridItem() {
          Button('3')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('3'))
        }
        GridItem() {
          Button('+')
            .width(80)
            .height(80)
            .backgroundColor('#FF9500')
            .onClick(() => this.handleOperatorClick('+'))
        }

        // 第五行:0、.(小数点)、=(等号)
        GridItem() {
          Button('0')
            .width(180)
            .height(80)
            .backgroundColor('#F0F0F0')
            .onClick(() => this.handleNumberClick('0'))
        }
        GridItem() {
          Button('.')
            .width(80)
            .height(80)
            .backgroundColor('#F0F0F0')
        }
        GridItem() {
          Button('=')
            .width(80)
            .height(80)
            .backgroundColor('#FF9500')
            .onClick(() => this.handleEqualClick())
        }
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
  }
}

三、功能说明

1. 状态管理

  • @State expression:显示当前输入的数字或表达式(如 “123+45”)。
  • @State result:存储运算符和中间计算结果(如 “+”“123”)。
  • @State isNewInput:标记是否开始新的数字输入(避免连续输入时覆盖前导零)。

2. 核心逻辑

  • 数字输入:点击数字按钮时,根据isNewInput状态决定是覆盖还是追加数字。
  • 运算符处理:点击运算符(+/-/*//)时,触发当前表达式计算,并更新结果状态。
  • 等号计算:调用calculate函数完成最终运算,处理除数为 0 等异常情况。
  • 清除功能:重置所有状态,回到初始界面。

3. UI 布局

  • 采用Column垂直布局,上部分为表达式显示区(Stack容器),下部分为按钮区(Grid网格布局)。
  • 按钮区按 4x5 网格排列,运算符按钮使用橙色背景(#FF9500)区分,数字按钮使用浅灰色(#F0F0F0)。

四、运行与测试

  1. 启动模拟器 点击 DevEco Studio 右上角的 “Device Manager”,选择 HarmonyOS 模拟器(如 Phone API 9+),点击 “Run” 按钮启动应用。
  2. 功能测试
    • 输入数字 “123”,点击 “+”,再输入 “45”,点击 “=”,应显示结果 “168”。
    • 点击 “C” 按钮,确认表达式和结果重置为 “0”。
    • 尝试输入 “0÷0”,确认提示 “除数不能为 0”。

五、扩展方向

  1. 分布式能力:在handleEqualClick中添加跨设备计算逻辑,例如:

    typescript 复制
    // 调用远程设备(如平板)进行复杂计算
    import distributedData from '@ohos.distributedData';
    // ...
    distributedData.callRemoteService({
      deviceId: 'targetDeviceId',
      serviceName: 'calculatorService',
      method: 'complexCalculate',
      args: [this.expression, this.result]
    }).then((res) => {
      this.expression = res.result
    });
    
  2. UI 优化:添加按钮点击动画效果:

    typescript 复制
    Button('7')
      .width(80)
      .height(80)
      .backgroundColor('#F0F0F0')
      .onClick(() => {
        this.handleNumberClick('7')
      })
      .stateEffect(StateEffect.Scale(1.1, 1.1, 300))  // 点击缩放动画
    

通过这个实例,开发者可快速掌握 HarmonyOS NEXT 的状态管理、UI 布局和事件处理机制,进一步可结合分布式能力实现跨设备协同功能。