当前位置:   article > 正文

OpenHarmony开发实战:简易计算器(ArkTS)_用arkts写一个计算器

用arkts写一个计算器

介绍

本篇Codelab基于基础组件、容器组件,实现一个支持加减乘除混合运算的计算器。

 说明: 由于数字都是双精度浮点数,在计算机中是二进制存储数据的,因此小数和非安全整数(超过整数的安全范围[-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”。

相关概念

  • ForEach组件:循环渲染组件**,**迭代数组并为每个数组项创建相应的组件。
  • TextInput组件:单行文本输入框组件。
  • Image组件:图片组件,支持本地图片和网络图片的渲染展示。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. 完成DevEco Device Tool的安装
    2. 完成RK3568开发板的烧录
  3. 搭建开发环境。

    1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用真机进行调测

代码结构解读

本篇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 // 应用静态资源目录

页面设计

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

表达式输入框位于页面最上方,使用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”组装后的表达式,用原理图讲解上面代码,原理图如图:

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

 获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

  •  HarmonOS基础技能

  • HarmonOS就业必备技能 
  •  HarmonOS多媒体技术

  • 鸿蒙NaPi组件进阶

  • HarmonOS高级技能

  • 初识HarmonOS内核 
  • 实战就业级设备开发

 有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

图片

 《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

图片

 《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

图片

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

图片

 获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/832434
推荐阅读
相关标签
  

闽ICP备14008679号