【鸿蒙小白】简单计算器案例代码

项目结构

项目结构

代码

Index

  • 处理显示格式
  • 输入后判断合法性,存入expressions(expressions存表达式拆分数字和符号后的元素)
  • 显示输入输出框、按键
import { CalSymbol, CommonConstants, Symbol, SymbolIcon } from '../common/constants/CommonConstants'
import CalculateUtil from '../common/util/CalculateUtil'
import CheckEmptyUtil from '../common/util/CheckEmptyUtil'
import { PressKeyItem } from '../ViewModel/PresskeyItem'
import keysModel from '../ViewModel/PresskeysViewModel'

@Entry
@Component
struct Index {
  @State calVar:string=''
  @State inputVar:string=''
  @State expressions:string[]=[]

  build() {
    Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.End}) {
      Input({inputVar:this.inputVar,calVar:this.calVar});
      Keys({inputVar:$inputVar,calVar:$calVar,expressions:$expressions})
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct Input {
  @Prop inputVar:string//输入框结果
  @Prop calVar:string//输出框结果


  build() {
    Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.End}) {
      TextInput({
        text: this.inputVar
      })
        .fontColor('#ff515151')
        .textAlign(TextAlign.End)
        .fontSize((this.inputVar.length > CommonConstants.INPUT_LENGTH_LIM) ? $r('app.float.font_size_small') :
        $r('app.float.font_size_big'))
        .placeholderFont({ size: $r('app.float.font_size_big') })
        .backgroundColor('#ffff')
        .width('100%')
        .height('40%')
        .enabled(false)
      Text(this.calVar)
        .textAlign(TextAlign.End)
        .width('100%')
        .fontSize($r('app.float.font_size_big'))
        .width('100%')
        .height('60%')
    }
    .width('100%')
    .height('30%')
  }
}



@Component
struct Keys {
  Number:PressKeyItem[]=keysModel.getNumberKeys()
  Operation:PressKeyItem[]=keysModel.getOperationKey()
  @Link calVar:string
  @Link inputVar:string
  @Link expressions:string[]


  @Builder KeyItem(key:PressKeyItem,fontsize:Resource,fontcolor:Resource,bgcolor:Resource){
    Text(key.icon)
      .fontWeight(FontWeight.Bold)
      .fontSize(fontsize)
      .fontColor(fontcolor)
      .borderRadius(10)
      .backgroundColor(bgcolor)
      .height('100%')
      .width('100%')
      .textAlign(TextAlign.Center)
      .onClick(()=>{
        if(key.flag===0){
          this.inputSymbol(key.value)
        } else {
          this.inputNumber(key.value)
        }
      })
   }

  /**
   * 输入符号
   */
  inputSymbol(value:string){
    if(CheckEmptyUtil.isEmpty(value))return;
    let len=this.expressions.length
    switch (value) {
      case Symbol.CLEAN:
        this.expressions=[]
        this.calVar=''
        this.inputVar=''
        break;
      case Symbol.DEL:
        this.inputDel(len)
        break;
      case Symbol.EQU:
        if(len===0)return
        this.inputVar=this.calVar
        this.calVar=''
        this.expressions=[]
        this.expressions.push(this.inputVar)
        break;
      default:
        this.inputOperations(len,value)
        break;
    }
    this.formatInputValue();
  }
  //输入回退键
  inputDel(len:number){
    /*console.log('Press Del')*/
    if(len===0){
      return
    }
    let last=this.expressions[len-1]
    let lastLen=last.length
    if(len>0){
      if(lastLen===1){
        this.expressions.pop()
      }else{
        this.expressions[len-1]=last.slice(0,lastLen-1)
      }
    }
    if(this.expressions.length===0){//删除后长度变化 不能用len
      this.inputVar=''
      this.calVar=''
      return
    }
    if(!CalculateUtil.isSymbol(this.expressions[len-1])){
      this.getResult()//实时得到结果
    }
  }
  //输入运算符
  inputOperations(len:number,value:string){

    if(CheckEmptyUtil.isEmpty(value))return;
    // if(len<=0)return
    let last = len > 0 ? this.expressions[len - 1] : undefined;
    let secondLast = len > 1 ? this.expressions[len - 2] : undefined;
    let symbol:string=this.getSymbol(value)

    if(!last) {
      if(value===Symbol.MIN){
        this.expressions.push(symbol)
      }
      return
    }
    else{
      if(!CalculateUtil.isSymbol(last))/*last是数字*/{
        this.expressions.push(this.getSymbol(value))
        return
      }
      else/*last是符号*/{
        if (secondLast) {
          if (!CalculateUtil.isSymbol(secondLast)) /*second是数字*/ {
            if (!(value === Symbol.MIN)) {
              this.expressions.pop()
              this.expressions.push(symbol)
              return
            } else {
              if (last === SymbolIcon.MUL || last === SymbolIcon.DIV) {
                this.expressions.push(symbol)
                return
              } else {
                this.expressions.pop()
                this.expressions.push(symbol)
                return
              }
            }
          } else /*second是符号*/{
            if (value === Symbol.ADD) {
              this.expressions.pop()
              return
            } else if (value === Symbol.MIN) return
            else {
              this.expressions.slice(0, len - 2)
              this.expressions.push(symbol)
            }
          }
        }
      }
    }
  }
  getSymbol(value:string):string{
    if(CheckEmptyUtil.isEmpty(value))return''
    let symbol:string=''
    switch(value){
      case Symbol.ADD:
        symbol=SymbolIcon.ADD
        break;
      case Symbol.MIN:
        symbol=SymbolIcon.MIN
        break;
      case Symbol.MUL:
        symbol=SymbolIcon.MUL
        break;
      case Symbol.DIV:
        symbol=SymbolIcon.DIV
        break;
      default :
        break;
    }
    return symbol
  }
  /**
   * 输入数字
   */
  inputNumber(value:string){
    if(CheckEmptyUtil.isEmpty(value))return;
    let len=this.expressions.length
    let last = len > 0 ? this.expressions[len - 1] : ''
    let secondLast = len > 1 ? this.expressions[len - 2] : undefined


    if(!this.validateEnter(last,value))return
    if(last===''){
      this.expressions.push(value)

    }else{
      if(!secondLast)/*没有second*/{
        this.expressions[len-1]+=value
      }else{
        if(CalculateUtil.isSymbol(secondLast))this.expressions[len-1]+=value
        else if(CalculateUtil.isSymbol(last)) this.expressions.push(value)
      }
    }

    if(this.formatInputValue) this.formatInputValue()
    if (value !== CalSymbol.DOTS) {//实时得到计算结果
      this.getResult();
    }
  }
  //判断输入是否正确
  validateEnter(last:string,value:string):boolean{
    if(value===CalSymbol.PERCENT_SIGN) {
      if (!last) return false//直接加%
      if (CalculateUtil.isSymbol(last))return false//符号后加%
      if(last.endsWith(CalSymbol.PERCENT_SIGN))return false
    }
    else if(value===CalSymbol.DOTS) {
      //TODO 结果初始显示0
      //TODO 只输入. 自动变成0.
      if(last.endsWith(CalSymbol.PERCENT_SIGN))return false
      if(last.indexOf('.')!==-1)return false//已经有小数点了
    }else{
      if(last==='0')return false
    }
    return true
  }
  /**
   * 得到结果
   */
  getResult(){
    let calResult=CalculateUtil.parseExpression(this.deepCopy())//处理表达式逻辑得到的结果
    console.log('getResult:'+calResult)
    if(calResult==='NaN'){
      this.calVar='Error'
      return false//没得到结果
    }else{
      this.calVar=calResult
      return true
    }
  }


  /**
   * 处理显示格式
   */
  //复制expression
  deepCopy():Array<string>{
    let copyExpressions:Array<string>=Array.from(this.expressions)
    return copyExpressions
  }
  //看华为官方给的案例代码,应该是千位后加逗号,但是在我的电脑上没实现
  resultFormat(value: string) {
    let reg = (value.indexOf('.') > -1) ? new RegExp("/(\d)(?=(\d{3})+\.)/g") : new RegExp("/(\d)(?=(?:\d{3})+$)/g");//正则表达式
    return value.replace(reg, '$1,');//replace(patten,replacement) 字符串中patten换成后者
  }
  //输出处理后的表达式
  formatInputValue() {
    let deepExpressions: Array<string> = []
    this.deepCopy().forEach((item:string,index:number)=>{
      deepExpressions[index]=this.resultFormat(item)
    })
    this.inputVar=deepExpressions.join('')
  }


  build() {
    Flex({ direction: FlexDirection.Row }) {
      Grid() {
        ForEach(
          this.Number,
          (key: PressKeyItem) => {
            GridItem() {
              if(key.icon&&key.flag==0) {//是操作符
                this.KeyItem(key, $r('app.float.op_font_size'), $r('app.color.theme_color'), $r('app.color.key'))
              }else{
                this.KeyItem(key, $r('app.float.number_font_size'), $r('app.color.font_color'), $r('app.color.key'))
              }
            }

          }, (key:PressKeyItem) => key.value)
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
      .columnsGap(1)
      .rowsGap(1)
      .width('75%')
      .height('100%')

      Grid() {
        ForEach(
          this.Operation,
          (key: PressKeyItem) => {
            GridItem() {
              if(key.icon){
                if (key.icon === '=') {
                  this.KeyItem(key, $r('app.float.op_font_size'), $r('app.color.font_color'),
                    $r('app.color.theme_color'))
                } else {
                  this.KeyItem(key, $r('app.float.op_font_size'), $r('app.color.theme_color'),
                    $r('app.color.key'))
                }
              }
            }
          }, (key:PressKeyItem) => key.value)
      }
      .columnsTemplate('1fr')
      .rowsTemplate('1fr 1fr 1fr 2fr')
      .columnsGap(1)
      .rowsGap(1)
      .width('25%')
      .height('100%')
      .padding({ left: 1 })
    }
    .width('100%')
    .height('57%')
    .backgroundColor('#000')
  }
}

CalculateUtil

  • 处理expressions中的数字
  • 完成计算
    • 中序表达式变后序:
      • 数字放队列前后顺序不变
      • 符号放栈,和上一个符号(栈顶元素)比优先级,同级先计算/优先级高的放队列
      • 最后都放到队列中
    • 在队列中按顺序计算(数 数 符号)
import { CalSymbol, CommonConstants, OperationPriority, SymbolIcon } from '../constants/CommonConstants'
import CheckEmptyUtil from './CheckEmptyUtil'

class CalculateUtil{
  /**
   * 判断是否为字符 是返回true
   */
  isSymbol(value:string):boolean{
    return (CommonConstants.OPERATION.indexOf(value)!==-1)
  }

  /**
   * 处理表达式
   */
  parseExpression(expressions:string[]):string{
    if(CheckEmptyUtil.isEmpty(expressions)){
      return 'NaN'
    }
    let len=expressions.length
    let outputStack:string[]=[]
    let outputQueue:string[]=[]

    //处理每一个元素
    expressions.forEach((item:string,index:number)=>{
      //处理百分号
      if(item.indexOf(CalSymbol.PERCENT_SIGN)!==-1){
        expressions[index]=this.mulOrDiv(item.replace('%',''),'100',SymbolIcon.DIV).toString()
      }
      //最后是运算符
      if((index==len-1)&& this.isSymbol(item))expressions.pop()
    })

    while (expressions.length>0) {
      let current:string|undefined=expressions.shift()
      if(current!==undefined){
        if(this.isSymbol(current)){//运算符
          while(outputStack.length>0&&this.comparePriority(current,outputStack[outputStack.length-1])){
            let popVar=outputStack.pop()
            if(popVar!==undefined) outputQueue.push(popVar)
          }
          outputStack.push(current)
        }else outputQueue.push(current)
      }
    }
    while (outputStack.length>0){
      let popVar=outputStack.pop()
      if(popVar!==undefined) outputQueue.push(popVar)
    }
    return this.dealQueue(outputQueue)
  }
  dealQueue(queue:string[]):string{
    if (CheckEmptyUtil.isEmpty(queue)) {
      return 'NaN';
    }
    let outputStack:string[]=[]
    while(queue.length>0){
      let current:string|undefined=queue.shift();//一开始写成pop 注意shift和pop区别
      if(current) {
        if (!this.isSymbol(current)) {
          outputStack.push(current)
        }else{
          let sec:string|undefined=outputStack.pop()
          let fst:string|undefined=outputStack.pop()
          if(sec!==undefined&&fst!==undefined){
            let calResultVar=this.calResult(fst,sec,current)
            outputStack.push(calResultVar)
          }
        }
      }
    }
    if(outputStack.length!==1){
      console.log('StopHere')
      return 'NaN'
    }
    else{
      let finResult:string=outputStack[0]?.endsWith(CalSymbol.DOTS)?
      outputStack[0].substring(0, outputStack[0].length - 1) : outputStack[0];
      return finResult
    }
  }
  calResult(fst:string,sec:string,op:string):string{
    if(CheckEmptyUtil.isEmpty(fst)||CheckEmptyUtil.isEmpty(sec)||CheckEmptyUtil.isEmpty(op)){
      return 'NaN'
    }
    let result=0
    switch (op) {
      case SymbolIcon.ADD:case SymbolIcon.MIN:
        result=this.addOrMin(fst,sec,op)
        break;
      case SymbolIcon.MUL:case SymbolIcon.DIV:
        result=this.mulOrDiv(fst,sec,op)
        break;
    }
    return this.numberToScientificNotation(result)
  }
  /**
   * op1<=op2返回true,反之返回false
   *
   * @param op1,op2
   */
  comparePriority(op1:string,op2:string):boolean{
    if (CheckEmptyUtil.isEmpty(op1) || CheckEmptyUtil.isEmpty(op2)) {
      return false;
    }
    return (OperationPriority[op1]<=OperationPriority[op2])
  }
  addOrMin(fst:string,sec:string,symbol:string){
    let addFlag=(symbol===SymbolIcon.ADD)
    if(this.containScientificNotation(fst)||this.containScientificNotation(sec)){
      if(addFlag)return Number(fst)+Number(sec)
      else return Number(fst)-Number(sec)
    }
    let leftLen:number=fst.split(CalSymbol.DOTS)[1]?fst.split(CalSymbol.DOTS)[1].length:0
    let rightLen:number=sec.split(CalSymbol.DOTS)[1]?sec.split(CalSymbol.DOTS)[1].length:0
    let maxLen:number=Math.max(leftLen,rightLen)
    let multiple=Math.pow(10,maxLen)
    if(addFlag){
      return Number(((Number(fst)*multiple+Number(sec)*multiple)/multiple).toFixed(maxLen))
    }else{
      return Number(((Number(fst)*multiple-Number(sec)*multiple)/multiple).toFixed(maxLen))
    }
  }
  /**
   * 乘除
   */
  mulOrDiv(fst:string,sec:string,symbol:string):number{
    let mulFlag=(symbol===SymbolIcon.MUL)
    if(this.containScientificNotation(fst)||this.containScientificNotation(sec)){
      if(mulFlag)return Number(fst)*Number(sec)
      else return Number(fst)/Number(sec)
    }
    let leftLen:number=fst.split(CalSymbol.DOTS)[1]?fst.split(CalSymbol.DOTS)[1].length:0
    let rightLen:number=sec.split(CalSymbol.DOTS)[1]?sec.split(CalSymbol.DOTS)[1].length:0
    if(mulFlag){
      return Number(fst.replace(CalSymbol.DOTS,''))*Number(sec.replace(CalSymbol.DOTS,''))/Math.pow(10,leftLen+rightLen)
    }else{
      return (Number(fst.replace(CalSymbol.DOTS,''))/Number(sec.replace(CalSymbol.DOTS,'')))*Math.pow(10,rightLen-leftLen)
    }
  }

  containScientificNotation(arg:string){
    return(arg.indexOf('e')!==-1)
  }
  numberToScientificNotation(result: number):string{
    if(result===Number.NEGATIVE_INFINITY||result===Number.POSITIVE_INFINITY)return 'NaN'
    let resultStr:string=JSON.stringify(result)
    if(this.containScientificNotation(resultStr))return resultStr
    if(resultStr.replace(CalSymbol.DOTS,'').replace(SymbolIcon.MIN,'').length<=CommonConstants.NUM_MAX_LEN)return resultStr
    let prefixNum=result>0?1:-1
    result*=prefixNum;
    let suffix=Math.floor(Math.log10(result))
    let prefix=prefixNum*(result*Math.pow(10,-suffix))
    return prefix.toFixed(2).toString()+'e'+suffix.toString()
  }
}

export default new CalculateUtil()

CheckEmptyUtil

(我为什么要另开一个文件放判断置空的方法

class CheckEmptyUtil{
  /**
   * 空返回true
   */
  isEmpty(obj:object|string):boolean{
    return (obj===undefined||obj===null||obj==='')
  }
}
export default new CheckEmptyUtil();

PresskeyItem

按键类:类型flag,代表值:value,显示的符号:icon

export class PressKeyItem{
  flag:number
  value:string
  icon:string

  constructor(flag:number,value:string,icon?:string) {
    this.flag=flag
    this.value=value
    if(flag==0&&icon){
      this.icon=icon
    }else{
      this.icon=value
    }
  }
}

PresskeysViewModel

创建按键数组

import { PressKeyItem } from './PresskeyItem';

export class  PressKeyViewModel{
  getNumberKeys():Array<PressKeyItem>{
    return[
      new PressKeyItem(0,'clean','C'),
      new PressKeyItem(0,'div','÷'),
      new PressKeyItem(0,'mul','×'),
      new PressKeyItem(1,'7'),
      new PressKeyItem(1,'8'),
      new PressKeyItem(1,'9'),
      new PressKeyItem(1,'4'),
      new PressKeyItem(1,'5'),
      new PressKeyItem(1,'6'),
      new PressKeyItem(1,'1'),
      new PressKeyItem(1,'2'),
      new PressKeyItem(1,'3'),
      new PressKeyItem(1,'%'),
      new PressKeyItem(1,'0'),
      new PressKeyItem(1,'.'),
    ]
  }
  getOperationKey():Array<PressKeyItem>{
    return [
      new PressKeyItem(0,'del','◀'),
      new PressKeyItem(0,'min','-'),
      new PressKeyItem(0,'add','+'),
      new PressKeyItem(0,'equ','='),
    ]
  }
}

let keysModel:PressKeyViewModel = new PressKeyViewModel();
export default keysModel as PressKeyViewModel;

CommonConstants

常量

export class CommonConstants {
  static readonly INPUT_LENGTH_LIM:number=9
  static readonly OPERATION:string='+-×÷'
  static readonly E:string='e'
  static readonly NUM_MAX_LEN:number=16
}
export enum Symbol {
  ADD = 'add',
  MIN = 'min',
  MUL = 'mul',
  DIV = 'div',
  CLEAN = 'clean',
  DEL = 'del',
  EQU = 'equ'
}
export enum SymbolIcon{
  ADD = '+',
  MIN = '-',
  MUL = '×',
  DIV = '÷',
}
export enum CalSymbol{
  PERCENT_SIGN='%',
  DOTS='.'
}
type Priority=Record<string,number>
export const OperationPriority:Priority={
  /*SymbolIcon.ADD=1,*/
  '-':1,
  '+':1,
  '×':2,
  '÷':2
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值