鸿蒙应用实战之计算器
·
案例演示
:::info
简易计算器案例需求
- 能够完成基本的加、减、乘、除运算
- 显示区域分为上下两部分,上部分显示算术表达式,下部分显示运算的结果
- 每次点击"="键时,完成算术表达式的运算,显示出结果;为了突出运算结果将结果字体放大,算术表达式字体缩小,并伴随有过渡动画。
- 每按下一个“数字键”或者“符号键”,按键会变色,同时按键会缩小为原来的0.9倍
:::

计算器界面
:::info
界面布局分析
- 如下图所示,计算器界面整体可以采用Column垂直布局
- 显示区域分别是两个Text。一个Text用于显示算术表达式,另一个Text用于显示运算结果
- 下面是键盘区域,采用Grid网格布局,每一个按键用GridItem来表示
:::

@Entry
@Component
struct Calculator {
//所有按键显示的数据
@State keyList: string[] = [
'AC', '+/-', '%', '+',
'7', '8', '9', '-',
'4', '5', '6', '*',
'1', '2', '3', '÷',
'0', '.', '='
]
//所有的操作符号(主要用于判断按键是否是操作符号,设置不同的样式)
operatorList: string[] = ['AC', '+/-', '%', '+', '-', '*', '÷', '=']
build() {
Column() {
Column() {
Text("2561-9*3").fontWeight(FontWeight.Bold).fontSize(20)
Text("2534").fontWeight(FontWeight.Bold).fontSize(30)
}
.width("100%")
.height("30%")
.alignItems(HorizontalAlign.End)
.justifyContent(FlexAlign.End)
.padding({ left: 10, right: 10, bottom: 10 })
Column() {
Grid(undefined, {
regularSize: [1, 1],
onGetRectByIndex: (index: number) => {
if (index == 18) { // “18”指"="按键对应的index
return [4, 2, 1, 2];
}
return [0, 0, 1, 1];
}
}) {
ForEach(this.keyList, (text: string) => {
this.KeyGridItem(text)
})
}
.columnsTemplate("1fr 1fr 1fr 1fr")
.rowsTemplate("1fr 1fr 1fr 1fr 1fr")
}.width("100%")
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
@Builder
KeyGridItem(text: string) {
GridItem() {
Row() {
Text(text)
.width('100%')
.fontSize(20)
.height('100%')
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
.fontColor(this.operatorList.includes(text) ? Color.Orange : Color.Black)
}
.height(65)
.borderRadius(33)
.backgroundColor("#f8f8f8")
.stateStyles({
pressed: { //按下的样式
.backgroundColor("#f2f2f2") //背景
.scale({
//缩放
centerX: "50%",
centerY: 32.5,
x: 0.9,
y: 0.9
})
.animation({ duration: 200 }) //动画
.shadow({
//阴影
color: '#F0F0F0',
radius: 30
})
},
normal: {
.backgroundColor('#F8F8F8')
.scale({
centerX: "50%",
centerY: 32.5,
x: 1,
y: 1
})
.shadow(undefined) //取消阴影
.animation({ duration: 200 })
} //正常的样式
})
.attributeModifier(new KeyGridItemModifyer(text))
}
}
}
//自定义一个动态样式的类
class KeyGridItemModifyer implements AttributeModifier<RowAttribute> {
text: string
constructor(text: string) {
this.text = text
}
//正常状态的样式
applyNormalAttribute(instance: RowAttribute): void {
//instance设置样式的组件
if (this.text === '=') {
instance.width(155)
} else {
instance.width(65)
}
}
}
按键处理
:::info
点击计算器按键时需要更新算术表达式,给CalcGridItem中的Text添加点击事件来处理按键操作,由于数字键、符号键的操作逻辑不一样,所以需要分别进行处理。
:::
给按键添加点击事件

.onClick(()=>{
this.keyOperation(text)
})
将按键处理封装成一个keyOperation()方法,根据不同的按键进行不同的逻辑处理。
public keyOperation(text: string) {
switch (text) {
case "+":
case "-":
case "*":
case "÷":
//this.handleOperator(content);
break;
case "%":
//this.handlePercent(content)
break
case "AC":
//this.clear();
break;
case "=":
//this.calculateResult();
break;
case "+/-":
//this.toggleSign();
break;
default: //数字键操作
//this.handleNumber(content);
break;
}
}
数字按键处理
:::info
数据按键处理的需求:
- 如果表达式开头是0,则去掉开头的0
- 如果表达式末尾的数字包含小数点,则无法再次输入小数点
- 如果表达式以.开头,将开头“.”改为“0.”
- 如果表达式以"3+3.5+“格式开头,此时如果按“.”,则将按下的”.“改为"0.”
- 如果表达式以"3+3.5+0"格式结尾,此时如果按键不是"."而是数字,则截取末尾"0"的前面一部分,再拼接上数字。
:::
//处理数字键操作
handleNumber(number: string) {
//1.1 如果表达式开头是0,则去掉开头的0
if (this.currentExpression === '0' && number !== '.') {
this.currentExpression = number
return
}
//1.2 如果表达式末尾的数字包含小数点,则无法输入小数点
const regex = /(\d*\.\d+)$|(\.)$/
if (regex.test(this.currentExpression) && number === '.') {
return
}
//1.3 如果表达式以.开头,将“.”改为“0.”
//举例: .6-> 0.6
if (this.currentExpression === '.') {
this.currentExpression = "0."
}
//1.4如果表达式以"3+3.5+"格式开头,此时如果按"."则将“.”改为"0."
if (/[\+\-\*÷]$/.test(this.currentExpression) && number==='.') {
number = "0."
}
//1.5如果表达式以"3+3.5+0"格式结尾,此时如果按键不是".",则截取末尾"0"的前面一部分
if (/[\+\-\*÷][0]$/.test(this.currentExpression) && number!=='.') {
this.currentExpression = this.currentExpression.substring(0,this.currentExpression.length-1)
}
//在表达式末尾拼接数字或者点
this.currentExpression += number
}
清零处理
:::info
AC键的处理逻辑如下
- 每次按下“AC”键,将算术表达式清空,同时将元素结果归0
- 将运算完成的状态isCompleted改为false
:::
//2. AC处理
public clear() {
this.currentExpression = ''
this.result = '0'
this.isCompleted = false
}
加减乘除按键处理
:::info
加减乘除按键操作逻辑
- 正常情况直接在表达式后面拼接操作符即可
- 当然也需要处理一些非正常情况
- 如果表达式为"",则无法拼接操作符
- 如果表达式后缀以“+,-,*,÷”任何一个符号结尾,则删除最后一个符号,再拼接新的操作符
:::
//3. 加减乘除按键处理
public handleOperator(opeartor: string) {
//3.1 如果表达式为"",则无法拼接操作符
if (this.currentExpression ==='') {
return
}
//3.2 如果表达式后缀以“+,-,*,÷”任何一个符号结尾,则删除最后一个符号
if (/[\+\-\*÷]$/.test(this.currentExpression)) {
this.currentExpression = this.currentExpression.substring(0, this.currentExpression.length - 1)
}
this.currentExpression += opeartor
}
百分号处理
:::info
百分号处理逻辑
- 当输入百分号时,需要检查算术表达式末尾是否为数字,只有末尾是数字百分号才能生效
- 当输入百分号时,将表达式末尾的数字转换为小数;如:“1000060%"转换为"100000.6”
:::
handlePercent(text: string) {
//判断运算表达式的末尾是否以整数或者小数结尾
let regexp = /\d+$|\d+\.\d+$/
if (regexp.test(this.currentExpression)) {
//获取算数表达式最后的整数或者小数,使其除以100
let array = this.currentExpression.match(regexp)
//如果array不为undifine则表示有匹配的数据
if (array) {
let lastNumber = array[0] //36
//let newLastNumber = parseFloat(lastNumber)/100 //小数可能不精确
//Decimal对象处理小数不精确问题
let newLastNumber= new Decimal(parseFloat(lastNumber)).div(new Decimal(100)).toNumber()
this.currentExpression = this.currentExpression.replace(regexp,`${newLastNumber}`)
}
}
}
处理正负数
:::info
处理正负数的逻辑
- 如果表达式是一个单独的数字,则直接在数字前面加"-“号,再次点击去掉”-"号
- 如果表达式是多项式,则判断表达式是否以"*-8"或者"÷-8"结尾
- 如果以"-“结尾,则改为”" ;如:6*-8 按“+/-”则变为 6*8
- 如果以"÷-“结尾,则改为"÷”;如: 6÷-8 按“+/-”则变为 6÷8
- 如果表达式是一个多项式,则判断表达式是否以”+8“或者"-8"结尾
- 如果"+8"改为"-8"; 如: 2+8按“+/-”则改为2-8
- 如果"-8"改为"+8"; 如: 2-8按“+/-”则改为2+8
- 如果表达式一个多项式,则判断表达式是否以”*8“或者”÷8“结尾
- 如果以"8"结尾,则改为"-8"
- 如果以"*-8"结尾,则改为"*8"
:::
toggleSign() {
// 1. 如果表达式是一个单独的数字,则直接在数字前面加"-"号,再次点击去掉"-"号
if (/^-?\d*\.?\d+$/.test(this.currentExpression)) {
this.currentExpression = `${-parseFloat(this.currentExpression)}`
return
}
// 2. 如果表达式是多项式,则判断表达式是否以"*-8"或者"÷-8"结尾
// a. 如果以"*-"结尾,则改为"*" ;如:6*-8 按“+/-”则变为 6*8
// b. 如果以"÷-"结尾,则改为"÷";如: 6÷-8 按“+/-”则变为 6÷8
const regex4 = /([*÷])(\-?\d*\.{0,1}\d+)$/;
//比如:算数表示式为:7+2+3+3*-8,匹配之后的元素分布为:["*-8","*","-8"]
let match = this.currentExpression.match(regex4)
if (match) {
let sign_number = match[0] //*-8
let sign = match[1] //"*"
let number = match[2] //-8
this.currentExpression = this.currentExpression.slice(0, -sign_number.length) + sign + (-parseFloat(number))
return
}
// 3. 如果表达式是一个多项式,则判断表达式是否以”+8“或者"-8"结尾
// a. 如果"+8"改为"-8"; 如: 2+8按“+/-”则改为2-8
// b. 如果"-8"改为"+8"; 如: 2-8按“+/-”则改为2+8
const regex5 = /([+-])(\-?\d*\.{0,1}\d+)$/;
//比如:算数表示式为:7+2+3+3+8,匹配之后的元素分布为:["+8","+","8"]
let match2 = this.currentExpression.match(regex5)
if (match2) {
let sign_number = match2[0] //"+8"
let sign = match2[1] //"+"
let number = match2[2] //8
this.currentExpression = this.currentExpression.slice(0, -sign_number.length) +`${sign==="+"?"-":"+"}`+ (parseFloat(number))
return
}
}
处理等号运算
- 计算表达式工具类
import { HashMap } from '@kit.ArkTS';
export class math {
public static evaluate(expression: string): number {
const precedence = new HashMap<String, number>();
precedence.set("+", 1);
precedence.set("-", 1);
precedence.set("*", 2);
precedence.set("/", 2);
const applyOperator = (operators: string[], values: number[]) => {
const operator = operators.pop();
if (operator) {
const b = values.pop();
const a = values.pop();
if (a !== undefined && b !== undefined) {
switch (operator) {
case '+':
values.push(a + b);
break;
case '-':
values.push(a - b);
break;
case '*':
values.push(a * b);
break;
case '/':
values.push(a / b);
break;
}
}
}
};
const operators: string[] = [];
const values: number[] = [];
let i = 0;
while (i < expression.length) {
if (expression[i] === ' ') {
i++;
continue;
}
// 处理负数
if (expression[i] === '-' && (i === 0 || !math.isNumeric(expression[i - 1]) && expression[i - 1] !== ')')) {
let val = 0;
let decimal = 1.0;
let hasDecimal = false;
i++; // 跳过负号
while (i < expression.length && (math.isNumeric(expression[i]) || expression[i] === '.')) {
if (expression[i] === '.') {
hasDecimal = true;
} else {
if (hasDecimal) {
decimal /= 10;
val += parseInt(expression[i], 10) * decimal;
} else {
val = (val * 10) + parseInt(expression[i], 10);
}
}
i++;
}
values.push(-val);
i--; // 因为循环会再自增一次
} else if (math.isNumeric(expression[i]) || expression[i] === '.') {
let val = 0;
let decimal = 1.0;
let hasDecimal = false;
while (i < expression.length && (math.isNumeric(expression[i]) || expression[i] === '.')) {
if (expression[i] === '.') {
hasDecimal = true;
} else {
if (hasDecimal) {
decimal /= 10;
val += parseInt(expression[i], 10) * decimal;
} else {
val = (val * 10) + parseInt(expression[i], 10);
}
}
i++;
}
values.push(val);
i--; // 因为循环会再自增一次
} else if (precedence.hasKey(expression[i])) {
while (operators.length > 0 &&
precedence.get(operators[operators.length - 1])! >= precedence.get(expression[i])!) {
applyOperator(operators, values);
}
operators.push(expression[i]);
} else if (expression[i] === '(') {
operators.push(expression[i]);
} else if (expression[i] === ')') {
while (operators[operators.length - 1] !== '(') {
applyOperator(operators, values);
}
operators.pop(); // 移除 '('
}
i++;
}
while (operators.length) {
applyOperator(operators, values);
}
return values[0];
}
public static isNumeric(char: string): boolean {
return !isNaN(parseFloat(char));
}
}
- 开始计算
//6.计算算术表达式
public calculateResult() {
//6.1 执行算术表达式
//把"÷"替换为"/"
this.currentExpression = this.currentExpression.replace("÷","/")
this.result = math.evaluate(this.currentExpression).toString()
//6.2 计算执行的结果,以便连续计算
this.preResult = this.result
//6.3 将计算完成的标记改为true
animateTo({duration:500},()=>{
this.isCompleted = true
})
}
修改数字按键的处理逻辑,每次按下数字键之前,先判断上一次运算是否完成,如果完成了将算术表达式清空;同时将isCompleted指为false,表示下一次运算开始;
//修改处理数字键操作
handleNumber(number: string) {
//判断是否运算完成
if (this.isCompleted){
this.currentExpression = ''
this.isCompleted = false
}
//.....
}
连续计算
:::info
连续计算的业务逻辑:连续运算指的是拿着上一次的运算结果,做下一次运算
- 如果上一次计算已经完成,this.isCompleted = true,表示运算已经完成,并且记录了上一次运算的结果给preResult
- 如果上一步运算以完成,则将算术表达式替换为上一步的结果,并将isCompleted该为false,表示重新开始计算。
:::
加减乘除运算
在handleOperator()函数开头,加入如下代码
//3. 加减乘除按键处理
public handleOperator(opeartor: string) {
if (this.isCompleted){
this.currentExpression = this.preResult
this.isCompleted = false
}
//...
}
百分号运算
handlePercent()函数开头,加入如下代码
//4. 百分号处理
public handlePercent(precent: string) {
if (this.isCompleted) {
const number = parseFloat(this.preResult)
this.currentExpression = (number/100.0).toString()
this.isCompleted = false
return
}
//...
}
正负数运算
在toggleSign()函数开头,加入如下代码
//5. 处理正负数
public toggleSign() {
if (this.isCompleted) {
this.currentExpression = this.preResult
this.isCompleted = false
}
//...
}
再次计算自动归零
:::info
如果上一次运算已经完成,需要按”AC“才能进行下一次运算。我们可以在进行下一次运算时,自动归零。
:::
- 在
handleNumber()函数中,加入如下代码
public handleNumber(number: string) {
//如果已经运算完成,再次点击数字时,清空所有操作
if (this.isCompleted){
this.clear()
}
//...
}
完整代码
import { Decimal } from '@kit.ArkTS'
import { math } from '../util/math'
@Entry
@Component
struct Calculator {
//所有按键显示的数据
@State keyList: string[] = [
'AC', '+/-', '%', '+',
'7', '8', '9', '-',
'4', '5', '6', '*',
'1', '2', '3', '÷',
'0', '.', '='
]
//所有的操作符号(主要用于判断按键是否是操作符号,设置不同的样式)
operatorList: string[] = ['AC', '+/-', '%', '+', '-', '*', '÷', '=']
//算数表达式
@State currentExpression: string = ''
@State result: string = '0'
//记录一次运算是否完成
@State isCompleted:boolean = false
//记录上次运算的结果
@State preResult:string = ''
build() {
Column() {
Column() {
Text(this.currentExpression).fontWeight(FontWeight.Bold).fontSize(20)
Text(this.result).fontWeight(FontWeight.Bold).fontSize(30)
}
.width("100%")
.height("30%")
.alignItems(HorizontalAlign.End)
.justifyContent(FlexAlign.End)
.padding({ left: 10, right: 10, bottom: 10 })
Column() {
Grid(undefined, {
regularSize: [1, 1],
onGetRectByIndex: (index: number) => {
if (index == 18) { // key1是“18”按键对应的index
return [4, 2, 1, 2];
}
return [0, 0, 1, 1];
}
}) {
ForEach(this.keyList, (text: string) => {
this.KeyGridItem(text)
})
}
.columnsTemplate("1fr 1fr 1fr 1fr")
.rowsTemplate("1fr 1fr 1fr 1fr 1fr")
}.width("100%")
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
@Builder
KeyGridItem(text: string) {
GridItem() {
Row() {
Text(text)
.width('100%')
.fontSize(20)
.height('100%')
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
.fontColor(this.operatorList.includes(text) ? Color.Orange : Color.Black)
.onClick(() => {
this.keyOperation(text)
})
}
.height(65)
.borderRadius(33)
.backgroundColor("#f8f8f8")
.stateStyles({
pressed: { //按下的样式
.backgroundColor("#f2f2f2") //背景
.scale({
//缩放
centerX: "50%",
centerY: 32.5,
x: 0.9,
y: 0.9
})
.animation({ duration: 200 }) //动画
.shadow({
//阴影
color: '#F0F0F0',
radius: 30
})
},
normal: {
.backgroundColor('#F8F8F8')
.scale({
centerX: "50%",
centerY: 32.5,
x: 1,
y: 1
})
.shadow(undefined) //取消阴影
.animation({ duration: 200 })
} //正常的样式
})
.attributeModifier(new KeyGridItemModifyer(text))
.onClick(() => {
this.keyOperation(text)
})
}
}
//按键处理
keyOperation(text: string) {
switch (text) {
case "+":
case "-":
case "*":
case "÷":
this.handleOperator(text)
break
case "%":
this.handlePercent(text)
break
case "AC":
this.clear();
break;
case "=":
this.calculateResult();
break;
case "+/-":
this.toggleSign();
break;
default:
this.handleNumber(text); //处理数字键操作
break;
}
}
//处理“=”按键的操作
calculateResult() {
if (/[\+\-\*÷]$/.test(this.currentExpression)) {
this.currentExpression = this.currentExpression.slice(0,-1)
}
this.result = `${math.evaluate(this.currentExpression.replace("÷","/"))}`
//记录上一次运算的结果
this.preResult = this.result
this.isCompleted = true
}
//处理正负号按键
toggleSign() {
//如果运算已经完成,则将上一次运行的结果进行正负数处理
if (this.isCompleted){
this.currentExpression = this.preResult
this.isCompleted = false
}
// 1. 如果表达式是一个单独的数字,则直接在数字前面加"-"号,再次点击去掉"-"号
if (/^-?\d*\.?\d+$/.test(this.currentExpression)) {
this.currentExpression = `${-parseFloat(this.currentExpression)}`
return
}
// 2. 如果表达式是多项式,则判断表达式是否以"*-8"或者"÷-8"结尾
// a. 如果以"*-"结尾,则改为"*" ;如:6*-8 按“+/-”则变为 6*8
// b. 如果以"÷-"结尾,则改为"÷";如: 6÷-8 按“+/-”则变为 6÷8
const regex4 = /([*÷])(\-?\d*\.{0,1}\d+)$/;
//比如:算数表示式为:7+2+3+3*-8,匹配之后的元素分布为:["*-8","*","-8"]
let match = this.currentExpression.match(regex4)
if (match) {
let sign_number = match[0] //*-8
let sign = match[1] //"*"
let number = match[2] //-8
this.currentExpression = this.currentExpression.slice(0, -sign_number.length) + sign + (-parseFloat(number))
return
}
// 3. 如果表达式是一个多项式,则判断表达式是否以”+8“或者"-8"结尾
// a. 如果"+8"改为"-8"; 如: 2+8按“+/-”则改为2-8
// b. 如果"-8"改为"+8"; 如: 2-8按“+/-”则改为2+8
const regex5 = /([+-])(\-?\d*\.{0,1}\d+)$/;
//比如:算数表示式为:7+2+3+3+8,匹配之后的元素分布为:["+8","+","8"]
let match2 = this.currentExpression.match(regex5)
if (match2) {
let sign_number = match2[0] //"+8"
let sign = match2[1] //"+"
let number = match2[2] //8
this.currentExpression = this.currentExpression.slice(0, -sign_number.length) +`${sign==="+"?"-":"+"}`+ (parseFloat(number))
return
}
}
//处理百分号
handlePercent(text: string) {
//判断运算表达式的末尾是否以整数或者小数结尾
let regexp = /\d+$|\d+\.\d+$/
if (regexp.test(this.currentExpression)) {
//获取算数表达式最后的整数或者小数,使其除以100
let array = this.currentExpression.match(regexp)
//如果array不为undifine则表示有匹配的数据
if (array) {
let lastNumber = array[0] //36
let newLastNumber = parseFloat(lastNumber) / 100 //小数可能不精确
//Decimal对象处理小数不精确问题
//let newLastNumber= new Decimal(parseFloat(lastNumber)).div(new Decimal(100)).toNumber()
this.currentExpression = this.currentExpression.replace(regexp, `${newLastNumber}`)
}
}
}
//清零操作
clear() {
this.currentExpression = ''
this.result = '0'
}
//处理数字键操作
handleNumber(number: string) {
//判断是否运算完成
if (this.isCompleted){
this.currentExpression = ''
this.isCompleted = false
}
//1.1 如果表达式开头是0,则去掉开头的0
if (this.currentExpression === '0' && number !== '.') {
this.currentExpression = number
return
}
//1.2 如果表达式末尾的数字包含小数点,则无法输入小数点
const regex = /(\d*\.\d+)$|(\.)$/
if (regex.test(this.currentExpression) && number === '.') {
return
}
//1.3 如果表达式以.开头,将“.”改为“0.”
//举例: .6-> 0.6
if (this.currentExpression === '.') {
this.currentExpression = "0."
}
//1.4如果表达式以"3+3.5+"格式开头,此时如果按"."则将“.”改为"0."
if (/[\+\-\*÷]$/.test(this.currentExpression) && number === '.') {
number = "0."
}
//1.5如果表达式以"3+3.5+0"格式结尾,此时如果按键不是".",则截取末尾"0"的前面一部分
if (/[\+\-\*÷][0]$/.test(this.currentExpression) && number !== '.') {
this.currentExpression = this.currentExpression.substring(0, this.currentExpression.length - 1)
}
//在表达式末尾拼接数字或者点
this.currentExpression += number
}
//3. 加减乘除按键处理
public handleOperator(opeartor: string) {
//如果运行已经完成,则拿上一次运行的结果拼接上运算符
if (this.isCompleted){
this.currentExpression = this.preResult+opeartor
this.isCompleted = false
}
//3.1 如果表达式为"",则无法拼接操作符
if (this.currentExpression === '') {
return
}
//3.2 如果表达式后缀以“+,-,*,÷”任何一个符号结尾,则删除最后一个符号
if (/[\+\-\*÷]$/.test(this.currentExpression)) {
this.currentExpression = this.currentExpression.substring(0, this.currentExpression.length - 1)
}
this.currentExpression += opeartor
}
}
//自定义一个动态样式的类
class KeyGridItemModifyer implements AttributeModifier<RowAttribute> {
text: string
constructor(text: string) {
this.text = text
}
//正常状态的样式
applyNormalAttribute(instance: RowAttribute): void {
//instance设置样式的组件
if (this.text === '=') {
instance.width(155)
} else {
instance.width(65)
}
}
}
对鸿蒙感兴趣的同学,可以考免费取鸿蒙开发者认证
更多推荐


所有评论(0)