基于鸿蒙next实现一个可以进行加减乘除的简单计算器。

环境配置:DevEco Studio NEXT 5.0.2   Api14

功能实现:

支持基本运算:加(+)、减(-)、乘(×)、除(÷)

支持连续运算(如3 + 5 - 2)

支持小数点输入

支持清除功能(C)

支持结果显示(=)

支持负号输入(如-6+3= -3)

案例效果:
321aefc7000c019949252228313d1ab.png =50x
一、自定义构建组件实现计算器输入按钮绘制和界面的绘制

//静态界面界面绘制
build() {
  Column({ space: 8 }) {
    // 显示区域
    Column({ space: 4 }) {
      Text(this.expression)
        .fontSize(24)
        .textAlign(TextAlign.End)
        .width('90%')
        .fontColor('#666666')
        .height('3%')

      Text(this.displayText)
        .fontSize(48)
        .textAlign(TextAlign.End)
        .width('90%')
        .height('18%')
    }
    .margin({ bottom: 20 })

    Blank()

    // 按钮区域
    Grid() {
      GridItem() { this.createButton('C', '#ff9999') }
      GridItem() { this.createButton('±', '#cccccc') }
      GridItem() { this.createButton('%', '#cccccc') }
      GridItem() { this.createButton('÷', '#ff9933') }

      GridItem() { this.createButton('7') }
      GridItem() { this.createButton('8') }
      GridItem() { this.createButton('9') }
      GridItem() { this.createButton('×', '#ff9933') }

      GridItem() { this.createButton('4') }
      GridItem() { this.createButton('5') }
      GridItem() { this.createButton('6') }
      GridItem() { this.createButton('-', '#ff9933') }

      GridItem() { this.createButton('1') }
      GridItem() { this.createButton('2') }
      GridItem() { this.createButton('3') }
      GridItem() { this.createButton('+', '#ff9933') }

      GridItem() { this.createButton('0','#ff1463db') }.columnStart(0).columnEnd(1)
      GridItem() { this.createButton('.') }
      GridItem() { this.createButton('=', '#ff9933') }
    }
    .columnsTemplate('1fr 1fr 1fr 1fr')
    .rowsGap(8)
    .columnsGap(8)
    .width('90%')
    .height('100%')
  }
  .width('100%')
  .height('100%')
  .padding(10)
  .backgroundColor('#f0f0f0')
}
//自定义按钮按钮
@Builder
createButton(text: string, bgColor: string = '#e6e6e6', colspan: number = 1) {
  Button(text) {
    Text(text)
      .fontSize(32)
      .fontColor(text === 'C' ? '#ff4444' : '#333333')
  }
  .onClick(() => this.handleButtonClick(text))
  .backgroundColor(bgColor)
  .width(colspan === 2 ? '200%' : '100%')
  .height(80)
}

二、处理按钮的点击跳转至不同函数

handleButtonClick(buttonValue: string) {
  if (this.isNumber(buttonValue)) {
    this.handleNumber(buttonValue)
  } else if (buttonValue === '.') {
    this.handleDecimal()
  } else if (this.isOperator(buttonValue)) {
    this.handleOperator(buttonValue)
  } else if (buttonValue === '=') {
    this.calculate()
  } else if (buttonValue === 'C') {
    this.clear()
  } else if (buttonValue === '±') {
    this.handleSignChange() 
  }
}
//判断输入是否未数字或则运算符
private isNumber(value: string): boolean {
  return /^-?\d+\.?\d*$/.test(value) // 支持负数
}

private isOperator(value: string): boolean {
  return ['+', '-', '×', '÷'].includes(value)
}

三、处理数字、小数点 、等于号、清除号、负号和操作符按钮的点击

private handleNumber(num: string) {
  if (this.currentNumber === '0') {
    this.currentNumber = num
  } else {
    this.currentNumber += num
  }
  this.displayText = this.currentNumber
}

private handleDecimal() {
  if (!this.currentNumber.includes('.')) {
    this.currentNumber += this.currentNumber === '' ? '0.' : '.'
    this.displayText = this.currentNumber
  }
}

private handleOperator(op: string) {
  if (this.currentNumber !== '') {
    this.expression += ` ${this.currentNumber} ${op}`
    this.currentNumber = ''
    this.displayText = '0'
  }
}

private calculate() {
  if (this.currentNumber === '') return

  const fullExpression = `${this.expression} ${this.currentNumber}`
  try {
    const result = this.evaluateExpression(fullExpression)
    this.displayText = result.toString()
    this.expression = `${fullExpression} =`
    this.currentNumber = result.toString()
  } catch (error) {
    this.displayText = 'Error'
    this.expression = ''
    this.currentNumber = '0'
  }
}

private clear() {
  this.currentNumber = '0'
  this.expression = ''
  this.displayText = '0'
}

private handleSignChange() {
  if (this.currentNumber !== '') {
    if (this.currentNumber.startsWith('-')) {
      this.currentNumber = this.currentNumber.substring(1)
    } else {
      this.currentNumber = `-${this.currentNumber}`
    }
    this.displayText = this.currentNumber 
  } else if (this.displayText !== '0') {
    if (this.displayText.startsWith('-')) {
      this.displayText = this.displayText.substring(1)
    } else {
      this.displayText = `-${this.displayText}`
    }
    this.currentNumber = this.displayText 
  }
}

四、核心计算逻辑(支持运算符优先级)

tokenize 函数的核心功能是将一个数学表达式字符串拆分为 token 数组,支持处理数字、运算符、负数和空格。

private tokenize(expr: string): string[] {
  const tokens: string[] = []
  let currentToken = ''

  for (let i = 0; i < expr.length; i++) {
    const char = expr[i]

    if (this.isOperator(char) || char === ' ') {
      if (currentToken !== '') {
        tokens.push(currentToken)
        currentToken = ''
      }
      if (char !== ' ') {
        // 处理负数:如果当前字符是'-',且前一个字符是操作符或空格,或者当前是第一个字符
        if (char === '-' && (i === 0 || this.isOperator(expr[i - 1]) || expr[i - 1] === ' ')) {
          currentToken += char // 将'-'作为负数的一部分
        } else {
          tokens.push(char)
        }
      }
    } else {
      currentToken += char
    }
  }

  if (currentToken !== '') {
    tokens.push(currentToken)
  }

  return tokens
}

//解析并计算一个数学表达式的值。它使用了经典的 Shunting-yard 算法 将中缀表达式
//(例如 3 + 5 × 2)转换为后缀表达式(例如 3 5 2 × +),然后通过栈来计算后缀表达式的值。

private evaluateExpression(expr: string): number {
  const tokens = this.tokenize(expr)
  const outputQueue: string[] = []
  const operatorStack: string[] = []

  // 定义运算符优先级
  const precedence: Record<string, number> = {
    '+': 1,
    '-': 1,
    '×': 2,
    '÷': 2
  }

  // 中缀转后缀(Shunting-yard算法)
  tokens.forEach(token => {
    if (this.isNumber(token) || (token.startsWith('-') && token.length > 1)) {
      // 如果是数字或负数,直接加入输出队列
      outputQueue.push(token)
    } else if (this.isOperator(token)) {
      while (operatorStack.length > 0 &&
        precedence[operatorStack[operatorStack.length - 1]] >= precedence[token]) {
        outputQueue.push(operatorStack.pop()!)
      }
      operatorStack.push(token)
    }
  })

  while (operatorStack.length > 0) {
    outputQueue.push(operatorStack.pop()!)
  }

  // 计算后缀表达式
  const stack: number[] = []
  outputQueue.forEach(token => {
    if (this.isNumber(token) || (token.startsWith('-') && token.length > 1)) {
      // 如果是数字或负数,解析为数值并压入栈
      stack.push(parseFloat(token))
    } else {
      const b = stack.pop()!
      const a = stack.pop()!
      switch (token) {
        case '+': stack.push(a + b); break
        case '-': stack.push(a - b); break
        case '×': stack.push(a * b); break
        case '÷':
          if (b === 0) throw new Error('Division by zero')
          stack.push(a / b)
          break
      }
    }
  })
  return stack[0]
}

这个函数是计算器的核心逻辑,能够正确处理运算符优先级、负数和错误情况(如除零)。如果需要扩展功能(例如支持更多运算符或函数),可以在此基础上进行修改。

evaluateExpression 函数通过以下步骤实现表达式的计算:

将中缀表达式转换为后缀表达式(Shunting-yard 算法)。

使用栈计算后缀表达式的值。

返回最终的计算结果。


旋转门123
1 声望0 粉丝

一个正在学习鸿蒙的开发者。