1. 介绍 本文档将为大家介绍如何使用HarmonyOS实现一个简单的计算器应用。该应用支持简单的加、减、乘、除以及取余运算。
图1-1 计算器交互界面
2. 搭建HarmonyOS环境
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
您可以利用如下设备完成Codelab:- 开启了开发者模式的HarmonyOS真机或DevEco Studio自带模拟器。
本篇文章使用的DevEco Studio版本为DevEco Studio 2.1 Beta4,使用的SDK版本为API Version 5。
3. 代码结构解读 本教程会对计算器应用的核心代码进行解读,首先为大家介绍整个工程的代码结构。
- slice
- MainAbilitySlice:计算器交互界面,用于完成界面元素渲染、按钮事件绑定动作。
- utils
- MathUtil:用于计算表达式结果的工具类,封装有运算符权重获取、表达式计算等方法。
- resources
- resourcesbaselayout:存放xml布局文件。
- resourcesbasemedia:存放图片资源文件。
- resourcesbasegraphic:存放xml样式文件。
- MainAbility:HAP的入口ability,由DevEco Studio自动生成。
- MyApplication:表示HAP的类,由DevEco Studio自动生成。
- config.json:应用的配置文件,由DevEco Studio自动生成。
4. 应用初始化
计算器应用使用的是自定义的按键,为避免用户在输入数学表达式时,系统自动弹出软键盘,本应用在初始化时,会在入口类MainAbility禁止应用弹出软键盘。
- // 禁止软键盘弹出
- getWindow().setLayoutFlags(WindowManager.LayoutConfig.MARK_ALT_FOCUSABLE_IM,
- WindowManager.LayoutConfig.MARK_ALT_FOCUSABLE_IM);
复制代码随后会在计算器交互界面类MainAbilitySlice中,完成界面渲染和按键点击事件绑定。
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_main);
-
- initView();
- initListener();
- }
复制代码本示例的完整代码会在本教程的"完整示例"章节中给出,其他章节仅对核心代码进行讲解说明。
5. 算法表达式计算
用户通过点击按键,输入要进行计算的数学表达式。用户每次输入新的字符,应用都会获取当前的表达式字符串,并判断是否需要进行计算。
- private void calculateResult(String exp, Boolean isAutoCalculate) {
- if (exp.isEmpty()) {
- return;
- }
- // 只有数字 不计算
- String pattern = "(\d*\.?\d*)|(0\.\d*[1-9])";
- boolean isMatch = Pattern.matches(pattern, exp);
- if (isMatch) {
- return;
- }
- // 末位是运算符 不计算
- if (MathUtil.isOperator(exp.substring(exp.length() - 1))) {
- return;
- }
-
- String resultValue;
- try {
- resultValue = MathUtil.getResultString(exp);
- } catch (NumberFormatException | ArithmeticException | EmptyStackException e) {
- preResult.setText("错误");
- return;
- }
-
- if (isAutoCalculate) {
- preResult.setText(resultValue);
- return;
- }
- preResult.setText("");
- inputText.setText(resultValue);
- }
复制代码若数学表达式字符串经过检查后,需要进行计算。程序则会调用MathUtil中的getResultString方法,对表达式进行计算。
步骤 1 - 把表达式拆分为数字和运算符,分别存放在两个栈中。
- Stack<String> numStack = new Stack<>();
- numStack.push(separateStr); // 数字用@分开,数字栈中@号 为了便于区分小数
- Stack<String> oprStack = new Stack<>();
复制代码
步骤 2 - 数字直接入数字栈。
- for (String singleStr : strings) {
- if (isOperator(singleStr)) {
- spiltExp(numStack, oprStack, singleStr);
- } else {
- numStack.push(singleStr);
- }
- }
复制代码
步骤 3 - 如果是运算符,且当前运算符优先级小于等于栈中前一个运算符,则数字栈弹出两个数值,符号栈弹出一个运算符进行计算,计算结果入数字栈,当前运算符入符号栈;若当前运算符优先级大于栈中前一个运算符,则当前符号直接入栈。
- private static void spiltExp(Stack<String> numStack, Stack<String> oprStack, String singleStr) {
- // 运算符间的字符拼接成一个数字
- combineString(numStack);
- if (!oprStack.isEmpty()) {
- // 先处理优先级高的运算符
- while (!oprStack.isEmpty() && priority(singleStr) <= priority(oprStack.peek())) {
- combineString(numStack);
- compute(numStack, oprStack);
- }
- }
- oprStack.push(singleStr);
- }
复制代码
步骤 4 - 依次取出数字栈两个数值和符号栈的一个运算符,依次运算,结果入数字栈。
- private static void compute(Stack<String> numStack, Stack<String> oprStack) {
- BigDecimal result = null;
-
- numStack.pop();
- BigDecimal rightNumber = new BigDecimal(numStack.pop());
-
- numStack.pop();
- BigDecimal leftNumber = new BigDecimal(numStack.pop());
-
- String operator = oprStack.pop();
- switch (operator) {
- case "-":
- result = leftNumber.subtract(rightNumber);
- break;
- case "+":
- result = leftNumber.add(rightNumber);
- break;
- case "%":
- result = leftNumber.divideAndRemainder(rightNumber)[1];
- break;
- case "×":
- result = leftNumber.multiply(rightNumber);
- break;
- case "÷":
- result = leftNumber.divide(rightNumber, DECIMAL_DIGIT, BigDecimal.ROUND_HALF_UP);
- break;
- default:
- break;
- }
- numStack.push(result.stripTrailingZeros().toPlainString());
- numStack.push(separateStr);
- }
复制代码
步骤 5 - 最后符号栈为空,数字栈只有一个数字,即为最终计算结果。
- while (!oprStack.isEmpty()) {
- combineString(numStack);
- compute(numStack, oprStack);
- }
- numStack.pop();
- String resultValue = numStack.peek();
复制代码
6. 完整示例 MainAbility
- package com.huawei.cookbook;
-
- import com.huawei.cookbook.slice.MainAbilitySlice;
-
- import ohos.aafwk.ability.Ability;
- import ohos.aafwk.content.Intent;
- import ohos.agp.window.service.WindowManager;
-
- /**
- * MainAbility
- *
- * @since 2021-04-29
- */
- public class MainAbility extends Ability {
- @Override
- public void onStart(Intent intent) {
- // 禁止软键盘弹出
- getWindow().setLayoutFlags(WindowManager.LayoutConfig.MARK_ALT_FOCUSABLE_IM,
- WindowManager.LayoutConfig.MARK_ALT_FOCUSABLE_IM);
- super.onStart(intent);
- super.setMainRoute(MainAbilitySlice.class.getName());
- }
- }
复制代码 MainAbilitySlice
- package com.huawei.cookbook.slice;
-
- import com.huawei.cookbook.ResourceTable;
- import com.huawei.cookbook.utils.MathUtil;
-
- import ohos.aafwk.ability.AbilitySlice;
- import ohos.aafwk.content.Intent;
- import ohos.agp.components.Component;
- import ohos.agp.components.Text;
- import ohos.agp.components.TextField;
-
- import java.util.EmptyStackException;
- import java.util.regex.Pattern;
-
- /**
- * MainAbilitySlice
- *
- * @since 2021-04-29
- */
- public class MainAbilitySlice extends AbilitySlice {
- /**
- * number component
- */
- private static int[] numberComponentIds = {ResourceTable.Id_seven, ResourceTable.Id_eight, ResourceTable.Id_nine,
- ResourceTable.Id_four, ResourceTable.Id_five, ResourceTable.Id_six, ResourceTable.Id_one,
- ResourceTable.Id_two, ResourceTable.Id_three, ResourceTable.Id_zero, ResourceTable.Id_radix_point
- };
-
- /**
- * operator component
- */
- private static int[] operatorComponentIds = {ResourceTable.Id_divide, ResourceTable.Id_multiply,
- ResourceTable.Id_minus, ResourceTable.Id_divide_remainder, ResourceTable.Id_plus
- };
-
- private TextField inputText;
- private Text preResult;
-
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_main);
- initView();
- initListener();
- }
-
- private void initView() {
- if (findComponentById(ResourceTable.Id_input_text) instanceof TextField) {
- inputText = (TextField) findComponentById(ResourceTable.Id_input_text);
- inputText.requestFocus();
- }
- if (findComponentById(ResourceTable.Id_pre_result) instanceof Text) {
- preResult = (Text) findComponentById(ResourceTable.Id_pre_result);
- }
- }
-
- private void initListener() {
- findComponentById(ResourceTable.Id_number_cancel).setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- preResult.setText("");
- inputText.setText("");
- }
- });
-
- findComponentById(ResourceTable.Id_delete).setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- if (inputText.getText().isEmpty()) {
- return;
- }
- inputText.setText(inputText.getText().substring(0, inputText.getText().length() - 1));
- }
- });
-
- findComponentById(ResourceTable.Id_equal_sign).setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- calculateResult(inputText.getText(), false);
- }
- });
-
- for (int componentId : numberComponentIds) {
- findComponentById(componentId).setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- bindNumberClickListener(componentId);
- }
- });
- }
-
- for (int componentId : operatorComponentIds) {
- findComponentById(componentId).setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- bindOperatorClickListener(componentId);
- }
- });
- }
- }
-
- private void bindNumberClickListener(int componentId) {
- String oldValue = inputText.getText();
- String inputValue = "";
- if (findComponentById(componentId) instanceof Text) {
- Text text = (Text) findComponentById(componentId);
- inputValue = text.getText();
- }
-
- if (oldValue.isEmpty() && ".".equals(inputValue)) {
- return;
- }
- if ("0".equals(oldValue) && !".".equals(inputValue)) {
- inputText.setText(inputValue);
- } else {
- inputText.append(inputValue);
- }
- calculateResult(inputText.getText(), true);
- }
-
- private void bindOperatorClickListener(int componentId) {
- String oldValue = inputText.getText();
- String inputValue = "";
- if (findComponentById(componentId) instanceof Text) {
- Text text = (Text) findComponentById(componentId);
- inputValue = text.getText();
- }
- if (oldValue.isEmpty()) {
- inputText.setText(inputValue);
- } else if (MathUtil.isOperator(oldValue.substring(oldValue.length() - 1))
- && MathUtil.isOperator(inputValue)) {
- String newValue = oldValue.substring(0, oldValue.length() - 1) + inputValue;
- inputText.setText(newValue);
- } else {
- inputText.append(inputValue);
- }
- calculateResult(inputText.getText(), true);
- }
-
- private void calculateResult(String exp, Boolean isAutoCalculate) {
- if (exp.isEmpty()) {
- return;
- }
- // 只有数字 不计算
- String pattern = "(\d*\.?\d*)|(0\.\d*[1-9])";
- boolean isMatch = Pattern.matches(pattern, exp);
- if (isMatch) {
- return;
- }
- // 末位是运算符 不计算
- if (MathUtil.isOperator(exp.substring(exp.length() - 1))) {
- return;
- }
-
- String resultValue;
- try {
- resultValue = MathUtil.getResultString(exp);
- } catch (NumberFormatException | ArithmeticException | EmptyStackException e) {
- preResult.setText("错误");
- return;
- }
-
- if (isAutoCalculate) {
- preResult.setText(resultValue);
- return;
- }
- preResult.setText("");
- inputText.setText(resultValue);
- }
-
- @Override
- public void onActive() {
- super.onActive();
- }
-
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- }
复制代码 MathUtil
- package com.huawei.cookbook.utils;
-
- import java.math.BigDecimal;
- import java.util.Stack;
-
- /**
- * MathUtil
- *
- * @since 2021-04-29
- */
- public class MathUtil {
- // 数字栈中分隔数字时使用
- private static String separateStr = "@";
-
- // 保留小数位数
- private static final int DECIMAL_DIGIT = 6;
-
- // 不包含指定字符
- private static final int NOT_CONTAIN = -1;
-
- // 高优先级
- private static final int HIGH_PRIORITY = 2;
-
- // 低优先级
- private static final int LOW_PRIORITY = 1;
-
- private MathUtil() {
- }
-
- /**
- * thinking:
- * 1.Split the expression into numbers and operators, and store them in two stacks.
- * 2.Numbers directly into the digital stack
- * 3.If the priority is less than or equal to the previous operator in the stack, the
- * calculation result is stored in the digit stack, and the current operator is stored in the symbol stack.
- * If the priority is higher than that of the previous operator in the stack, the current symbol is directly
- * added to the stack.
- * 4.Obtain the two values of the digit stack and an operator of the symbol stack in sequence, perform operations
- * in sequence, and import the results into the digit stack.
- * 5.The final symbol stack is empty and the number stack contains only one digit, which is the final calculation
- * result.
- *
- * [url=home.php?mod=space&uid=3142012]@param[/url] exp Mathematical Expression String
- * [url=home.php?mod=space&uid=1141835]@Return[/url] Calculation result string
- */
- public static String getResultString(String exp) {
- Stack<String> numStack = new Stack<>();
- numStack.push(separateStr); // 数字用@分开,数字栈中@号 为了便于区分小数
- Stack<String> oprStack = new Stack<>();
-
- String[] strings = exp.split("");
- for (String singleStr : strings) {
- if (isOperator(singleStr)) {
- spiltExp(numStack, oprStack, singleStr);
- } else {
- numStack.push(singleStr);
- }
- }
- while (!oprStack.isEmpty()) {
- combineString(numStack);
- compute(numStack, oprStack);
- }
- numStack.pop();
- String resultValue = numStack.peek();
- return resultValue;
- }
-
- private static void spiltExp(Stack<String> numStack, Stack<String> oprStack, String singleStr) {
- // 运算符间的字符拼接成一个数字
- combineString(numStack);
- if (!oprStack.isEmpty()) {
- // 先处理优先级高的运算符
- while (!oprStack.isEmpty() && priority(singleStr) <= priority(oprStack.peek())) {
- combineString(numStack);
- compute(numStack, oprStack);
- }
- }
- oprStack.push(singleStr);
- }
-
- private static void compute(Stack<String> numStack, Stack<String> oprStack) {
- BigDecimal result = null;
-
- numStack.pop();
- BigDecimal rightNumber = new BigDecimal(numStack.pop());
-
- numStack.pop();
- BigDecimal leftNumber = new BigDecimal(numStack.pop());
-
- String operator = oprStack.pop();
- switch (operator) {
- case "-":
- result = leftNumber.subtract(rightNumber);
- break;
- case "+":
- result = leftNumber.add(rightNumber);
- break;
- case "%":
- result = leftNumber.divideAndRemainder(rightNumber)[1];
- break;
- case "×":
- result = leftNumber.multiply(rightNumber);
- break;
- case "÷":
- result = leftNumber.divide(rightNumber, DECIMAL_DIGIT, BigDecimal.ROUND_HALF_UP);
- break;
- default:
- break;
- }
- numStack.push(result.stripTrailingZeros().toPlainString());
- numStack.push(separateStr);
- }
-
- private static void combineString(Stack<String> stack) {
- if (separateStr.equals(stack.peek())) {
- return;
- }
- StringBuilder numberBuilder = new StringBuilder();
- while (true) {
- String string = stack.peek();
- if (separateStr.equals(string)) {
- break;
- }
- numberBuilder.insert(0, string);
- stack.pop();
- }
- stack.push(numberBuilder.toString());
- stack.push(separateStr);
- numberBuilder.delete(0, numberBuilder.length());
- }
-
- /**
- * Determines whether a string is an operator.
- *
- * @param singleStr Character string to be judged
- * @return Judgment Result
- */
- public static boolean isOperator(String singleStr) {
- String operators = "-+×÷%";
- if (operators.indexOf(singleStr) > NOT_CONTAIN) {
- return true;
- }
- return false;
- }
-
- private static int priority(String str) {
- String highOperator = "×÷%";
- if (highOperator.indexOf(str) > NOT_CONTAIN) {
- return HIGH_PRIORITY;
- }
- return LOW_PRIORITY;
- }
- }
复制代码 xml样式文件- base/graphic/background_ability_main.xml
- <?xml version="1.0" encoding="UTF-8" ?>
- <shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:shape="rectangle">
- <solid ohos:color="#E2E2E2" />
- </shape>
复制代码
- base/graphic/background_denghao.xml
- <?xml version="1.0" encoding="UTF-8" ?>
- <shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:shape="rectangle">
- <solid ohos:color="blue" />
- <corners ohos:radius="180" />
- </shape>
复制代码
- base/graphic/background_keyboard.xml
- <?xml version="1.0" encoding="UTF-8" ?>
- <shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:shape="rectangle">
- <solid ohos:color="#EEEEEE" />
- <corners ohos:radius="36" />
- </shape>
复制代码
- base/graphic/background_round.xml
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval">
- <solid
- ohos:color="#FFFFFF"/>
- <stroke
- ohos:width="3vp"
- ohos:color="#FFFFFF"/>
- </shape>
复制代码
xml布局文件- base/layout/ability_main.xml
以上代码示例仅供参考,产品化的代码需要考虑数据校验和国际化。