ToB企服应用市场:ToB评测及商务社交产业平台

标题: HarmonyOS开发案例:【盘算器】 [打印本页]

作者: 科技颠覆者    时间: 2024-6-21 13:15
标题: HarmonyOS开发案例:【盘算器】
介绍

基于底子组件、容器组件,实现一个支持加减乘除混合运算的盘算器。

   
说明: 由于数字都是双精度浮点数,在盘算机中是二进制存储数据的,因此小数和非安全整数(高出整数的安全范围[-Math.pow(2, 53),Math.pow(2, 53)]的数据)在盘算过程中会存在精度丢失的情况。
  1、小数运算时:“0.2 + 2.22 = 2.4200000000000004”,当前示例的解决方法是将小数扩展到整数进行盘算,盘算完成之后再将结果缩小,盘算过程为“(0.2 * 100 + 2.22 * 100) / 100 = 2.42”。
  2、非安全整数运算时:“9007199254740992 + 1 = 9.007199254740992”,当前示例中将长度高出15位的数字转换成科学计数法,盘算结果为“9007199254740992 + 1 = 9.007199254740993e15”。
  相关概念


情况搭建

软件要求


硬件要求


情况搭建

完成本篇Codelab我们首先要完成开发情况的搭建,本示例以RK3568开发板为例,参照以下步调进行:
代码布局解读

本篇Codelab只对核心代码进行讲解,对于完备代码,我们会在gitee中提供。
  1. ├──entry/src/main/ets                           // 代码区
  2. │  ├──common
  3. │  │  ├──constants
  4. │  │  │  └──CommonConstants.ets            // 公共常量类
  5. │  │  └──util
  6. │  │     ├──CalculateUtil.ets              // 计算工具类
  7. │  │     ├──CheckEmptyUtil.ets             // 非空判断工具类
  8. │  │     └──Logger.ets                     // 日志管理工具类
  9. │  ├──entryability
  10. │  │  └──EntryAbility.ts                       // 程序入口类
  11. │  ├──model
  12. │  │  └──CalculateModel.ets                // 计算器页面数据处理类
  13. │  ├──pages
  14. │  │  └──HomePage.ets                      // 计算器页面
  15. │  └──viewmodel   
  16. │     ├──PressKeysItem.ets                 // 按键信息类
  17. │     └──PresskeysViewModel.ets            // 计算器页面键盘数据
  18. └──entry/src/main/resource                 // 应用静态资源目录
  19. `HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿`
复制代码

页面设计

页面由表达式输入框、结果输出框、键盘输入区域三部门组成,效果图如图:

表达式输入框位于页面最上方,利用TextInput组件实时显示键盘输入的数据,默认字体大小为“64fp”,当表达式输入框中数据长度大于9时,字体大小为“32fp”。
  1. // HomePage.ets
  2. Column() {
  3.   TextInput({ text: this.model.resultFormat(this.inputValue) })
  4.     .height(CommonConstants.FULL_PERCENT)
  5.     .fontSize(
  6.       (this.inputValue.length > CommonConstants.INPUT_LENGTH_MAX ?
  7.         $r('app.float.font_size_text')) : $r('app.float.font_size_input')
  8.     )
  9.     .enabled(false)
  10.     .fontColor(Color.Black)
  11.     .textAlign(TextAlign.End)
  12.     .backgroundColor($r('app.color.input_back_color'))
  13. }
  14. ....
  15. .margin({
  16.   right: $r('app.float.input_margin_right'),
  17.   top: $r('app.float.input_margin_top')
  18. })
复制代码
结果输出框位于表达式输入框下方,利用Text组件实时显示盘算结果和“错误”提示,当表达式输入框最后一位为运算符时结果输出框中值稳固。
  1. // HomePage.ets
  2. Column() {
  3.   Text(this.model.resultFormat(this.calValue))
  4.     .fontSize($r('app.float.font_size_text'))
  5.     .fontColor($r('app.color.text_color'))
  6. }
  7. .width(CommonConstants.FULL_PERCENT)
  8. .height($r('app.float.text_height'))
  9. .alignItems(HorizontalAlign.End)
  10. .margin({
  11.   right: $r('app.float.text_margin_right'),
  12.   bottom: $r('app.float.text_margin_bottom')})
复制代码
用ForEach组件渲染键盘输入区域,此中0~9、“.”、“%”用Text组件渲染;“±×÷=”、清零、删除用Image组件渲染。
  1. // HomePage.ets
  2. ForEach(columnItem, (keyItem: PressKeysItem, keyItemIndex?: number) => {
  3.   Column() {
  4.     Column() {
  5.       if (keyItem.flag === 0) {
  6.         Image(keyItem.source !== undefined ? keyItem.source : '')
  7.           .width(keyItem.width)
  8.           .height(keyItem.height)
  9.       } else {
  10.         Text(keyItem.value)
  11.           .fontSize(
  12.             (keyItem.value === CommonConstants.DOTS) ?
  13.               $r('app.float.font_size_dot') : $r('app.float.font_size_text')
  14.           )
  15.           .width(keyItem.width)
  16.           .height(keyItem.height)
  17.       }
  18.     }
  19.     .width($r('app.float.key_width'))
  20.     .height(
  21.       ((columnItemIndex === (keysModel.getPressKeys().length - 1)) &&
  22.         (keyItemIndex === (columnItem.length - 1))) ?
  23.         $r('app.float.equals_height') : $r('app.float.key_height')
  24.     )
  25.     ...
  26.     .backgroundColor(
  27.       ((columnItemIndex === (keysModel.getPressKeys().length - 1)) &&
  28.         (keyItemIndex === (columnItem.length - 1))) ?
  29.         $r('app.color.equals_back_color') : Color.White
  30.     )
  31.     ...
  32.   }
  33.   .layoutWeight(
  34.     ((columnItemIndex === (keysModel.getPressKeys().length - 1)) &&
  35.       (keyItemIndex === (columnItem.length - 1))) ? CommonConstants.TWO : 1
  36.   )
  37.   ...
  38. }, (keyItem: PressKeysItem) => JSON.stringify(keyItem))
复制代码
组装盘算表达式

页面中数字输入和运算符输入分别调用inputNumber方法和inputSymbol方法。
  1. // HomePage.ets
  2. ForEach(columnItem, (keyItem: PressKeysItem, keyItemIndex?: number) => {
  3.   Column() {
  4.     Column() {
  5.       ...
  6.     }
  7.     ...
  8.     .onClick(() => {
  9.       if (keyItem.flag === 0) {
  10.         this.model.inputSymbol(keyItem.value);
  11.       } else {
  12.         this.model.inputNumber(keyItem.value);
  13.       }
  14.     })
  15.   }
  16.   ...
  17.   )
  18.   ...
  19. }, (keyItem: PressKeysItem) => JSON.stringify(keyItem))
复制代码
  
说明: 输入的数字和运算符生存在数组中,数组通过“±×÷”运算符将数字分开。 比方表达式为“10×8.2+40%÷2×-5-1”在数组中为[“10”, “×”, “8.2”, “+”, “40%”, “÷”, “2”, “×”, “-5”, “-”, “1”]。 表达式中“%”为百分比,比方“40%”为“0.4”。
  当为数字输入时,首先根据表达式数组中最后一个元素判断当前输入是否匹配,再判断表达式数组中最后一个元素为是否为负数。
  1. // CalculateModel.ets
  2. inputNumber(value: string) {
  3.   ...
  4.   let len = this.expressions.length;
  5.   let last = len > 0 ? this.expressions[len - 1] : '';
  6.   let secondLast = len > 1 ? this.expressions[len - CommonConstants.TWO] : undefined;
  7.   if (!this.validateEnter(last, value)) {
  8.     return;
  9.   }
  10.   if (!last) {
  11.     this.expressions.push(value);
  12.   } else if (!secondLast) {
  13.     this.expressions[len - 1] += value;
  14.   }
  15.   if (secondLast && CalculateUtil.isSymbol(secondLast)) {
  16.     this.expressions[len -1] += value;
  17.   }
  18.   if (secondLast && !CalculateUtil.isSymbol(secondLast)) {
  19.     this.expressions.push(value);
  20.   }
  21.   ...
  22. }
  23. // CalculateModel.ets
  24. validateEnter(last: string, value: string) {
  25.   if (!last && value === CommonConstants.PERCENT_SIGN) {
  26.     return false;
  27.   }
  28.   if ((last === CommonConstants.MIN) && (value === CommonConstants.PERCENT_SIGN)) {
  29.     return false;
  30.   }
  31.   if (last.endsWith(CommonConstants.PERCENT_SIGN)) {
  32.     return false;
  33.   }
  34.   if ((last.indexOf(CommonConstants.DOTS) !== -1) && (value === CommonConstants.DOTS)) {
  35.     return false;
  36.   }
  37.   if ((last === '0') && (value != CommonConstants.DOTS) &&
  38.     (value !== CommonConstants.PERCENT_SIGN)) {
  39.     return false;
  40.   }
  41.   return true;
  42. }
复制代码
当输入为“=”运算符时,将结果输入出框中的值显示到表达式输入框中,并清空结果输出框。当输入为“清零”运算符时,将页面和表达式数组清空。
  1. // CalculateModel.ets
  2. inputSymbol(value: string) {
  3.   ...
  4.   switch (value) {
  5.     case Symbol.CLEAN:
  6.       this.expressions = [];
  7.       this.context.calValue = '';
  8.       break;
  9.     ...
  10.     case Symbol.EQU:
  11.       if (len === 0) {
  12.         return;
  13.       }
  14.       this.getResult().then(result => {
  15.         if (!result) {
  16.           return;
  17.         }
  18.         this.context.inputValue = this.context.calValue;
  19.         this.context.calValue = '';
  20.         this.expressions = [];
  21.         this.expressions.push(this.context.inputValue);
  22.       })
  23.       break;
  24.     ...
  25.   }
  26.   ...
  27. }
复制代码
当输入为“删除”运算符时,若表达式数组中最后一位元素为运算符则删除,为数字则删除数字最后一位,重新盘算表达式的值(表达式数组中最后一位为运算符则不参与盘算),删除之后若表达式长度为0则清空页面。
  1. // CalculateModel.ets
  2. inputSymbol(value: string) {
  3.   ...
  4.   switch (value) {
  5.     ...
  6.     case CommonConstants.SYMBOL.DEL:
  7.       this.inputDelete(len);
  8.       break;
  9.     ...
  10.   }
  11.   ...
  12. }
  13. // CalculateModel.ets
  14. inputDelete(len: number) {
  15.   if (len === 0) {
  16.     return;
  17.   }
  18.   let last = this.expressions[len - 1];
  19.   let lastLen = last.length;
  20.   if (lastLen === 1) {
  21.     this.expressions.pop();
  22.     len = this.expressions.length;
  23.   } else {
  24.     this.expressions[len - 1] = last.slice(0, last.length - 1);
  25.   }
  26.   if (len === 0) {
  27.     this.context.inputValue = '';
  28.     this.context.calValue = '';
  29.     return;
  30.   }
  31.   if (!CalculateUtil.isSymbol(this.expressions[len - 1])) {
  32.     this.getResult();
  33.   }
  34. }
复制代码
当输入为“±×÷”四则运算符时,由于可输入负数,故优先级高的运算符“×÷”后可输入“-”,别的场景则更换原有运算符。
  1. // CalculateModel.ets
  2. inputSymbol(value: string) {
  3.   ...
  4.   switch (value) {
  5.     ...
  6.     default:
  7.       this.inputOperators(len, value);
  8.       break;
  9.   }
  10.   ...
  11. }
  12. // CalculateModel.ets
  13. inputOperators(len: number, value: string) {
  14.   let last = len > 0 ? this.expressions[len - 1] : undefined;
  15.   let secondLast = len > 1 ? this.expressions[len - CommonConstants.TWO] : undefined;
  16.   if (!last && (value === Symbol.MIN)) {
  17.     this.expressions.push(this.getSymbol(value));
  18.     return;
  19.   }
  20.   if (!last) {
  21.     return;
  22.   }
  23.   if (!CalculateUtil.isSymbol(last)) {
  24.     this.expressions.push(this.getSymbol(value));
  25.     return;
  26.   }
  27.   if ((value === Symbol.MIN) &&
  28.     (last === CommonConstants.MIN || last === CommonConstants.ADD)) {
  29.     this.expressions.pop();
  30.     this.expressions.push(this.getSymbol(value));
  31.     return;
  32.   }
  33.   if (!secondLast) {
  34.     return;
  35.   }
  36.   if (value !== Symbol.MIN) {
  37.     this.expressions.pop();
  38.   }
  39.   if (CalculateUtil.isSymbol(secondLast)) {
  40.     this.expressions.pop();
  41.   }
  42.   this.expressions.push(this.getSymbol(value));
  43. }
复制代码
解析盘算表达式

将表达式数组中带“%”的元素转换成小数,若表达式数组中最后一位为“±×÷”则删除。
  1. // CalculateUtil.ets
  2. parseExpression(expressions: Array<string>): string {
  3.   ...
  4.   let len = expressions.length;
  5.   ...
  6.   expressions.forEach((item: string, index: number) => {
  7.     // 处理表达式中的%
  8.     if (item.indexOf(CommonConstants.PERCENT_SIGN) !== -1) {
  9.       expressions[index] = (this.mulOrDiv(item.slice(0, item.length - 1),
  10.         CommonConstants.ONE_HUNDRED, CommonConstants.DIV)).toString();
  11.     }
  12.     // 最后一位是否为运算符
  13.     if ((index === len - 1) && this.isSymbol(item)) {
  14.       expressions.pop();
  15.     }
  16.   });
  17.   ...
  18. }
复制代码
先初始化队列和栈,再从表达式数组左边取出元素,进行如下操作:

  1. // CalculateUtil.ets
  2. parseExpression(expressions: Array<string>): string {
  3.   ...
  4.   while (expressions.length > 0) {
  5.     let current = expressions.shift();
  6.      if (current !== undefined) {
  7.         if (this.isSymbol(current)) {
  8.            while (outputStack.length > 0 &&
  9.            this.comparePriority(current, outputStack[outputStack.length - 1])) {
  10.               let popValue: string | undefined = outputStack.pop();
  11.               if (popValue !== undefined) {
  12.                  outputQueue.push(popValue);
  13.               }
  14.            }
  15.            outputStack.push(current);
  16.         } else {
  17.            outputQueue.push(current);
  18.         }
  19.      }
  20.   }
  21.   while (outputStack.length > 0) {
  22.     outputQueue.push(outputStack.pop());
  23.   }
  24.   ...
  25. }
复制代码
以表达式“3×5+4÷2”为例,用原理图讲解上面代码,原理图如图:

遍历队列中的元素,当为数字时将元素压入栈,当为运算符时将数字弹出栈,并团结当前运算符进行盘算,再将盘算的结果压栈,终极栈底元素为表达式结果。
  1. // CalculateUtil.ets
  2. dealQueue(queue: Array<string>) {
  3.   ...
  4.   let outputStack: string[] = [];
  5.    while (queue.length > 0) {
  6.       let current: string | undefined = queue.shift();
  7.       if (current !== undefined) {
  8.          if (!this.isSymbol(current)) {
  9.             outputStack.push(current);
  10.          } else {
  11.             let second: string | undefined = outputStack.pop();
  12.             let first: string | undefined = outputStack.pop();
  13.             if (first !== undefined && second !== undefined) {
  14.                let calResultValue: string = this.calResult(first, second, current)
  15.                outputStack.push(calResultValue);
  16.             }
  17.          }
  18.       }
  19.    }
  20.    if (outputStack.length !== 1) {
  21.       return 'NaN';
  22.    } else {
  23.       let end = outputStack[0].endsWith(CommonConstants.DOTS) ?
  24.       outputStack[0].substring(0,  outputStack[0].length - 1) : outputStack[0];
  25.       return end;
  26.    }
  27. }
复制代码
获取表达式“3×5+4÷2”组装后的表达式,用原理图讲解上面代码,原理图如图:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4