1. 界面布局
新建项目 MyCalculator,开始布局。
2. 静态布局
代码如下:
// etc/pages/Index.ets
@Entry
@Component
struct Index {
build() {
Column() {
/**
* 运算区
*/
Column() {
TextInput({ text: '12x13' })
.height('100%')
.fontSize(32)
.enabled(false)
.fontColor(Color.Black)
.textAlign(TextAlign.End)
.backgroundColor('#f5f5f5')
}
.width('100%')
.height(86)
.alignItems(HorizontalAlign.End)
.margin({
right: 20,
top: 120
})
/**
* 结果区
*/
Column() {
Text('123')
.fontSize('32fp')
.fontColor('#182431')
}
.width('100%')
.height(42)
.alignItems(HorizontalAlign.End)
.margin({
right: 20,
bottom: 64
})
/**
* 输入面板区
*/
Column() {
Row() {
// 多行子元素
Column() {
// 多列子元素
Column() {
Column() {
Image($r('app.media.ic_clean'))
.width(32)
.height(32)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('7')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('4')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('1')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('%')
.fontSize('32fp')
.width(25)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.margin({
top: 30,
bottom: 30
})
Column() {
// 多列子元素
Column() {
Column() {
Image($r('app.media.ic_div'))
.width(32)
.height(32)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('8')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('5')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('2')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('0')
.fontSize('32fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.margin({
top: 30,
bottom: 30
})
Column() {
// 多列子元素
Column() {
Column() {
Image($r('app.media.ic_mul'))
.width(32)
.height(32)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('9')
.fontSize(32)
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('6')
.fontSize(32)
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('3')
.fontSize(32)
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Text('.')
.fontSize('42fp')
.width(19)
.height(43)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.margin({
top: 30,
bottom: 30
})
Column() {
// 多列子元素
Column() {
Column() {
Image($r('app.media.ic_del'))
.width(30)
.height(20)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Image($r('app.media.ic_min'))
.width(24)
.height(24)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Image($r('app.media.ic_add'))
.width(32)
.height(32)
}
.width(70)
.height(60)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.width('100%')
.justifyContent(FlexAlign.Center)
Column() {
Column() {
Image($r('app.media.ic_equ'))
.width(32)
.height(32)
}
.width(70)
.height(125)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor('#007DFF')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(2)
.width('100%')
.justifyContent(FlexAlign.Center)
}
.layoutWeight(1)
.margin({
top: 30,
bottom: 30
})
}
.height('100%')
.alignItems(VerticalAlign.Top)
.margin({
left: 20,
right: 20
})
}
.layoutWeight(1)
.width('100%')
.backgroundColor('#f8f8ff')
}
.height('100%')
.backgroundColor('#f5f5f5')
}
}
3. 动态布局
3.1. 定义视图模型
- PressKeysItem.ets
// ets/viewmodel/PressKeysModel.ets
export class PressKeysItem {
flag: number
width: string
height: string
value: string
source?: Resource
constructor(flag: number, width: string, height: string, value: string, source?: Resource) {
this.flag = flag
this.width = width
this.height = height
this.value = value
this.source = source
}
}
export class PressKeysInnerObject {
id: string
data: PressKeysItem
constructor(id: string, data: PressKeysItem) {
this.id = id
this.data = data
}
}
export class PressKeysOuterObject {
id: string
data: Array<PressKeysInnerObject>
constructor(id: string, data: Array<PressKeysInnerObject>) {
this.id = id
this.data = data
}
}
- PressKeysViewModel.ets
// ets/viewmodel/PressKeysViewModel.ets
import { PressKeysInnerObject, PressKeysOuterObject, PressKeysItem } from './PressKeysModel'
export class PressKeysViewModel {
getPressKeys(): Array<PressKeysOuterObject> {
return [
new PressKeysOuterObject('01',
[
new PressKeysInnerObject('010', new PressKeysItem(0, '32vp', '32vp', 'clean', $r('app.media.ic_clean'))),
new PressKeysInnerObject('011', new PressKeysItem(1, '19vp', '43vp', '7')),
new PressKeysInnerObject('012', new PressKeysItem(1, '19vp', '43vp', '4')),
new PressKeysInnerObject('013', new PressKeysItem(1, '19vp', '43vp', '1')),
new PressKeysInnerObject('014', new PressKeysItem(1, '25vp', '43vp', '%'))
]
),
new PressKeysOuterObject('02',
[
new PressKeysInnerObject('020', new PressKeysItem(0, '32vp', '32vp', 'div', $r('app.media.ic_div'))),
new PressKeysInnerObject('021', new PressKeysItem(1, '19vp', '43vp', '8')),
new PressKeysInnerObject('022', new PressKeysItem(1, '19vp', '43vp', '5')),
new PressKeysInnerObject('023', new PressKeysItem(1, '19vp', '43vp', '2')),
new PressKeysInnerObject('024', new PressKeysItem(1, '19vp', '43vp', '0'))
]
),
new PressKeysOuterObject('03',
[
new PressKeysInnerObject('030', new PressKeysItem(0, '32vp', '32vp', 'mul', $r('app.media.ic_mul'))),
new PressKeysInnerObject('031', new PressKeysItem(1, '19vp', '43vp', '9')),
new PressKeysInnerObject('032', new PressKeysItem(1, '19vp', '43vp', '6')),
new PressKeysInnerObject('033', new PressKeysItem(1, '19vp', '43vp', '3')),
new PressKeysInnerObject('034', new PressKeysItem(1, '19vp', '43vp', '.'))
]
),
new PressKeysOuterObject('04',
[
new PressKeysInnerObject('040', new PressKeysItem(0, '30.48vp', '20vp', 'del', $r('app.media.ic_del'))),
new PressKeysInnerObject('041', new PressKeysItem(0, '24vp', '24vp', 'min', $r('app.media.ic_min'))),
new PressKeysInnerObject('042', new PressKeysItem(0, '32vp', '32vp', 'add', $r('app.media.ic_add'))),
new PressKeysInnerObject('043', new PressKeysItem(0, '32vp', '32vp', 'equ', $r('app.media.ic_equ')))
]
)
]
}
}
let keysModel = new PressKeysViewModel()
export default keysModel as PressKeysViewModel
3.2. 布局代码
// etc/pages/Index.ets
import { PressKeysOuterObject, PressKeysInnerObject } from '../viewmodel/PressKeysModel'
import keysModel from '../viewmodel/PressKeysViewModel'
// 入口组件定义
@Entry
@Component
struct Index {
// 构建组件界面
build() {
Column() {
/**
* 运算区
*/
Column() {
TextInput({ text: '12x13' }) // 显示输入的格式化值
.height('100%')
.fontSize(32)
.enabled(false) // 禁用输入
.fontColor(Color.Black)
.textAlign(TextAlign.End)
.backgroundColor('#f5f5f5')
}
.width('100%')
.height(86)
.alignItems(HorizontalAlign.End)
.margin({
right: 20,
top: 120
})
/**
* 结果区
*/
Column() {
Text('123') // 显示计算结果
.fontSize('32fp')
.fontColor('#182431')
}
.width('100%')
.height(42)
.alignItems(HorizontalAlign.End)
.margin({
right: 20,
bottom: 64
})
/**
* 输入面板区
*/
Column() {
Row() {
// 遍历键盘模型,生成按键
ForEach(keysModel.getPressKeys(), (columnItem: PressKeysOuterObject, columnItemIndex: number) => {
Column() {
ForEach(columnItem.data, (keyItem: PressKeysInnerObject, keyItemIndex: number) => {
Column() {
Column() {
// 根据按键类型生成图像或文本
if (keyItem.data.flag === 0) {
Image(keyItem.data.source !== undefined ? keyItem.data.source : '')
.width(keyItem.data.width)
.height(keyItem.data.height)
} else {
Text(keyItem.data.value)
.fontSize(keyItem.data.value === '.' ? '42fp' : '32fp')
}
}
.width(70)
.height(
(columnItemIndex === keysModel.getPressKeys().length - 1 &&
keyItemIndex === columnItem.data.length - 1) ?
'125vp' : '60vp'
)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(
(columnItemIndex === keysModel.getPressKeys().length - 1 &&
keyItemIndex === columnItem.data.length - 1) ?
'#007DFF' : Color.White
)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.layoutWeight(
(columnItemIndex === keysModel.getPressKeys().length - 1 &&
keyItemIndex === columnItem.data.length - 1) ?
2 : 1
)
.width('100%')
.justifyContent(FlexAlign.Center)
}, (keyItem: PressKeysInnerObject) => keyItem.id)
}
.layoutWeight(1)
.margin({
top: 30,
bottom: 30
})
}, (columnItem: PressKeysOuterObject) => columnItem.id)
}
.height('100%')
.alignItems(VerticalAlign.Top)
.margin({
left: 20,
right: 20
})
}
.layoutWeight(1)
.width('100%')
.backgroundColor('#f8f8ff')
}
.height('100%')
.backgroundColor('#f5f5f5')
}
}
4. 计算逻辑
4.1. 常量定义文件
// ets/constants/CommonConstants.ets
export class CommonConstants {
// 定义操作符字符串
static readonly OPERATORS: string = '+-×÷';
// 定义操作符优先级字符串
static readonly OPERATORS_PRIORITY: string = '×÷';
// 定义各个操作符
static readonly ADD: string = '+';
static readonly MIN: string = '-';
static readonly MUL: string = '×';
static readonly DIV: string = '÷';
// 定义其他常量
static readonly PERCENT_SIGN: string = '%'; // 百分号
static readonly DOTS: string = '.'; // 小数点
static readonly TWO: number = 2; // 数字2
static readonly TEN: number = 10; // 数字10
static readonly ONE_HUNDRED: string = '100'; // 字符串'100'
static readonly INPUT_LENGTH_MAX: number = 9; // 最大输入长度
static readonly INDEX_TWO: number = 2; // 索引2
static readonly NUM_MAX_LEN: number = 16; // 数字最大长度
static readonly E: string = 'e'; // 科学计数法中的e
static readonly ZERO: string = '0'; // 字符串'0'
static readonly ZERO_DOTS: string = '0.'; // 字符串'0.'
}
// 定义操作符的枚举
export enum Symbol {
ADD = 'add',
MIN = 'min',
MUL = 'mul',
DIV = 'div',
CLEAN = 'clean',
DEL = 'del',
EQU = 'equ'
}
// 定义操作符优先级的枚举
export enum Priority {
HIGH = 2,
MEDIUM = 1,
LOW = 0
}
// 定义符号枚举,用于识别不同的数学符号
export enum SymbolicEnumeration {
ADD = '+',
MIN = '-',
MUL = '×',
DIV = '÷'
}
4.2. 判空工具类
// ets/uitls/CheckEmptyUtil.ets
class CheckEmptyUtil {
// 检查对象或字符串是否为空
isEmpty(obj: object | string): boolean {
return (typeof obj === 'undefined' || obj === null || obj === ''); // 判断是否为 undefined、null 或空字符串
}
// 检查字符串是否为空(去除空格后)
checkStrIsEmpty(str: string): boolean {
return str.trim().length === 0; // 如果去除空格后长度为 0,返回 true
}
// 检查数组是否为空
isEmptyArr(arr: Array<string>) {
return arr.length === 0; // 如果数组长度为 0,返回 true
}
}
export default new CheckEmptyUtil(); // 导出 CheckEmptyUtil 的单例
4.3. 计算器核心类
// ets/uitis/CalculateUtil.ets
import CheckEmptyUtil from './CheckEmptyUtil' // 导入检查空值的工具类
import { CommonConstants, Priority, SymbolicEnumeration } from '../constants/CommonConstants' // 导入常量和枚举
class CalculateUtil {
// 检查给定值是否为操作符
isSymbol(value: string) {
if (CheckEmptyUtil.isEmpty(value)) {
return // 如果值为空,返回 undefined
}
// 判断值是否在操作符列表中
return (CommonConstants.OPERATORS.indexOf(value) !== -1);
}
// 获取操作符的优先级
getPriority(value: string): number {
if (CheckEmptyUtil.isEmpty(value)) {
return Priority.LOW; // 如果值为空,默认优先级为低
}
let result = 0; // 初始化优先级结果
switch (value) {
case SymbolicEnumeration.ADD: // 如果是加法
case SymbolicEnumeration.MIN: // 如果是减法
result = Priority.MEDIUM; // 设置为中等优先级
break;
case SymbolicEnumeration.MUL: // 如果是乘法
case SymbolicEnumeration.DIV: // 如果是除法
result = Priority.HIGH; // 设置为高优先级
break;
default:
result = Priority.LOW; // 其他符号默认为低优先级
break;
}
return result; // 返回优先级结果
}
// 比较两个操作符的优先级
comparePriority(arg1: string, arg2: string): boolean {
// 检查任一操作符是否为空
if (CheckEmptyUtil.isEmpty(arg1) || CheckEmptyUtil.isEmpty(arg2)) {
return false // 如果有空值,返回 false
}
// 返回 arg1 是否优先级低于等于 arg2
return (this.getPriority(arg1) <= this.getPriority(arg2));
}
// 解析表达式,使用逆波兰表示法(后缀表达式)
// 3×5+4÷2
parseExpression(expressions: Array<string>): string {
// 检查表达式数组是否为空
if (CheckEmptyUtil.isEmpty(expressions)) {
return 'NaN' // 如果为空,返回 'NaN'
}
let len = expressions.length; // 获取表达式的长度
let outputStack: string[] = []; // 初始化操作符栈
let outputQueue: string[] = []; // 初始化输出队列
// 处理表达式数组
expressions.forEach((item: string, index: number) => {
// 处理百分号,将百分数转换为小数
if (item.indexOf(CommonConstants.PERCENT_SIGN) !== -1) {
expressions[index] = (this.mulOrDiv(item.slice(0, item.length - 1),
CommonConstants.ONE_HUNDRED, CommonConstants.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 popValue: string | undefined = outputStack.pop(); // 弹出栈顶符号
if (popValue !== undefined) {
outputQueue.push(popValue); // 将弹出的符号加入输出队列
}
}
outputStack.push(current); // 将当前符号推入栈中
} else {
outputQueue.push(current); // 如果不是符号,直接加入输出队列
}
}
}
// 将栈中剩余的符号加入输出队列
while (outputStack.length > 0) {
let popValue: string | undefined = outputStack.pop();
if (popValue !== undefined) {
outputQueue.push(popValue); // 将剩余的符号加入输出队列
}
}
// 处理输出队列,计算最终结果
return this.dealQueue(outputQueue);
}
// 处理输出队列,计算结果
dealQueue(queue: Array<string>): string {
// 检查队列是否为空
if (CheckEmptyUtil.isEmpty(queue)) {
return 'NaN'; // 如果为空,返回 'NaN'
}
let outputStack: string[] = []; // 初始化输出栈
// 遍历队列,计算结果
while (queue.length > 0) {
let current: string | undefined = queue.shift(); // 获取当前元素
if (current !== undefined) { // 如果当前元素存在
if (!this.isSymbol(current)) { // 如果不是符号,加入输出栈
outputStack.push(current);
} else {
// 弹出栈顶两个元素作为操作数
let second: string | undefined = outputStack.pop(); // 弹出第二个操作数
let first: string | undefined = outputStack.pop(); // 弹出第一个操作数
if (first !== undefined && second !== undefined) {
// 计算结果并加入输出栈
let calResultValue: string = this.calResult(first, second, current);
outputStack.push(calResultValue); // 将计算结果加入栈
}
}
}
}
// 检查输出栈的状态,确保只有一个结果
if (outputStack.length !== 1) {
return 'NaN'; // 如果不止一个元素,返回 'NaN'
} else {
// 处理结果的小数点
let end: string = outputStack[0]?.endsWith(CommonConstants.DOTS) ?
outputStack[0].substring(0, outputStack[0].length - 1) : outputStack[0];
return end; // 返回最终结果
}
}
// 计算两个操作数和符号之间的结果
calResult(arg1: string, arg2: string, symbol: string): string {
// 检查参数是否为空
if (CheckEmptyUtil.isEmpty(arg1) || CheckEmptyUtil.isEmpty(arg2) || CheckEmptyUtil.isEmpty(symbol)) {
return 'NaN'; // 如果有空值,返回 'NaN'
}
let result = 0; // 初始化计算结果
switch (symbol) {
case SymbolicEnumeration.ADD:
// 处理加法
result = this.add(arg1, arg2, CommonConstants.ADD);
break;
case SymbolicEnumeration.MIN:
// 处理减法
result = this.add(arg1, arg2, CommonConstants.MIN);
break;
case SymbolicEnumeration.MUL:
// 处理乘法
result = this.mulOrDiv(arg1, arg2, CommonConstants.MUL);
break;
case SymbolicEnumeration.DIV:
// 处理除法
result = this.mulOrDiv(arg1, arg2, CommonConstants.DIV);
break;
default:
break; // 其他操作符不处理
}
return this.numberToScientificNotation(result); // 返回结果(可能为科学计数法)
}
// 处理加法
add(arg1: string, arg2: string, symbol: string): number {
let addFlag = (symbol === CommonConstants.ADD); // 判断是否为加法
// 处理科学计数法
if (this.containScientificNotation(arg1) || this.containScientificNotation(arg2)) {
if (addFlag) {
return Number(arg1) + Number(arg2); // 直接计算加法
}
return Number(arg1) - Number(arg2); // 直接计算减法
}
// 处理零的情况
arg1 = (arg1 === CommonConstants.ZERO_DOTS) ? '0' : arg1;
arg2 = (arg2 === CommonConstants.ZERO_DOTS) ? '0' : arg2;
// 分割小数部分
let leftArr = arg1.split(CommonConstants.DOTS);
let rightArr = arg2.split(CommonConstants.DOTS);
// 获取小数部分长度
let leftLen = leftArr.length > 1 ? leftArr[1] : '';
let rightLen = rightArr.length > 1 ? rightArr[1] : '';
// 获取最大小数部分长度
let maxLen = Math.max(leftLen.length, rightLen.length);
let multiples = Math.pow(CommonConstants.TEN, maxLen); // 计算倍数
// 处理加法或减法
if (addFlag) {
return Number(((Number(arg1) * multiples + Number(arg2) * multiples) / multiples).toFixed(maxLen));
}
return Number(((Number(arg1) * multiples - Number(arg2) * multiples) / multiples).toFixed(maxLen));
}
// 处理乘法和除法
mulOrDiv(arg1: string, arg2: string, symbol: string): number {
let mulFlag = (symbol === CommonConstants.MUL); // 判断是否为乘法
// 处理科学计数法
if (this.containScientificNotation(arg1) || this.containScientificNotation(arg2)) {
if (mulFlag) {
return Number(arg1) * Number(arg2); // 直接计算乘法
}
return Number(arg1) / Number(arg2); // 直接计算除法
}
// 获取小数部分长度
let leftLen = arg1.split(CommonConstants.DOTS)[1] ? arg1.split(CommonConstants.DOTS)[1].length : 0;
let rightLen = arg2.split(CommonConstants.DOTS)[1] ? arg2.split(CommonConstants.DOTS)[1].length : 0;
// 处理乘法
if (mulFlag) {
return Number(arg1.replace(CommonConstants.DOTS, '')) *
Number(arg2.replace(CommonConstants.DOTS, '')) / Math.pow(CommonConstants.TEN, leftLen + rightLen);
}
// 处理除法
return Number(arg1.replace(CommonConstants.DOTS, '')) /
(Number(arg2.replace(CommonConstants.DOTS, '')) / Math.pow(CommonConstants.TEN, rightLen - leftLen));
}
// 检查字符串是否包含科学计数法
containScientificNotation(arg: string) {
return (arg.indexOf(CommonConstants.E) !== -1); // 判断是否含有 'e'
}
// 将结果转换为科学计数法格式
numberToScientificNotation(result: number) {
// 处理无穷大情况
if (result === Number.NEGATIVE_INFINITY || result === Number.POSITIVE_INFINITY) {
return 'NaN'; // 返回 'NaN',表示计算出错
}
let resultStr = JSON.stringify(result); // 将结果转为字符串
// 如果已经是科学计数法,直接返回
if (this.containScientificNotation(resultStr)) {
return resultStr;
}
let prefixNumber = (resultStr.indexOf(CommonConstants.MIN) === -1) ? 1 : -1; // 处理负数前缀
result *= prefixNumber; // 应用前缀符号
// 检查结果长度是否超过限制
if (resultStr.replace(CommonConstants.DOTS, '').replace(CommonConstants.MIN, '').length <
CommonConstants.NUM_MAX_LEN) {
return resultStr; // 如果长度合适,直接返回
}
// 计算科学计数法的指数部分
let suffix = (Math.floor(Math.log(result) / Math.LN10));
let prefix = (result * Math.pow(CommonConstants.TEN, -suffix) * prefixNumber); // 计算系数
return (prefix + CommonConstants.E + suffix); // 返回科学计数法表示
}
}
export default new CalculateUtil(); // 导出 CalculateUtil 的单例
4.4. 首页代码调用和实现
// etc/pages/Index.ets
import { PressKeysOuterObject, PressKeysInnerObject } from '../viewmodel/PressKeysModel'
import keysModel from '../viewmodel/PressKeysViewModel'
import CalculateUtil from '../utils/CalculateUtil'
import CheckEmptyUtil from '../utils/CheckEmptyUtil'
import { CommonConstants, Symbol } from '../constants/CommonConstants'
import Logger from '../utils/Logger'
// 入口组件定义
@Entry
@Component
struct Index {
// 输入的表达式
@State inputValue: string = ''
// 计算结果
@State calValue: string = ''
// 存储表达式的数组
private expressions: Array<string> = []
// 构建组件界面
build() {
Column() {
/**
* 运算区
*/
Column() {
TextInput({ text: this.resultFormat(this.inputValue) }) // 显示输入的格式化值
.height('100%')
.fontSize(32)
.enabled(false) // 禁用输入
.fontColor(Color.Black)
.textAlign(TextAlign.End)
.backgroundColor('#f5f5f5')
}
.width('100%')
.height(86)
.alignItems(HorizontalAlign.End)
.margin({
right: 20,
top: 120
})
/**
* 结果区
*/
Column() {
Text(this.resultFormat(this.calValue)) // 显示计算结果
.fontSize('32fp')
.fontColor('#182431')
}
.width('100%')
.height(42)
.alignItems(HorizontalAlign.End)
.margin({
right: 20,
bottom: 64
})
/**
* 输入面板区
*/
Column() {
Row() {
// 遍历键盘模型,生成按键
ForEach(keysModel.getPressKeys(), (columnItem: PressKeysOuterObject, columnItemIndex: number) => {
Column() {
ForEach(columnItem.data, (keyItem: PressKeysInnerObject, keyItemIndex: number) => {
Column() {
Column() {
// 根据按键类型生成图像或文本
if (keyItem.data.flag === 0) {
Image(keyItem.data.source !== undefined ? keyItem.data.source : '')
.width(keyItem.data.width)
.height(keyItem.data.height)
} else {
Text(keyItem.data.value)
.fontSize(keyItem.data.value === '.' ? '42fp' : '32fp')
}
}
.width(70)
.height(
(columnItemIndex === keysModel.getPressKeys().length - 1 &&
keyItemIndex === columnItem.data.length - 1) ?
'125vp' : '60vp'
)
.borderWidth(1)
.borderColor("#f5f5f5")
.borderRadius(36)
.backgroundColor(
(columnItemIndex === keysModel.getPressKeys().length - 1 &&
keyItemIndex === columnItem.data.length - 1) ?
'#007DFF' : Color.White
)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.inputValue = keyItem.data.value // 更新输入值
// 根据按键类型调用相应的输入方法
if (keyItem.data.flag === 0) {
this.inputSymbol(keyItem.data.value)
} else {
this.inputNumber(keyItem.data.value)
}
})
}
.layoutWeight(
(columnItemIndex === keysModel.getPressKeys().length - 1 &&
keyItemIndex === columnItem.data.length - 1) ?
2 : 1
)
.width('100%')
.justifyContent(FlexAlign.Center)
}, (keyItem: PressKeysInnerObject) => keyItem.id)
}
.layoutWeight(1)
.margin({
top: 30,
bottom: 30
})
}, (columnItem: PressKeysOuterObject) => columnItem.id)
}
.height('100%')
.alignItems(VerticalAlign.Top)
.margin({
left: 20,
right: 20
})
}
.layoutWeight(1)
.width('100%')
.backgroundColor('#f8f8ff')
}
.height('100%')
.backgroundColor('#f5f5f5')
}
// 输入符号处理
inputSymbol(value: string) {
if (CheckEmptyUtil.isEmpty(value)) {
return // 检查输入是否为空
}
let len: number = this.expressions.length // 获取当前表达式长度
switch (value) {
case Symbol.CLEAN:
this.expressions = [] // 清空表达式
this.calValue = ''
break;
case Symbol.DEL:
this.inputDelete(len) // 删除操作
break;
case Symbol.EQU:
if (len === 0) {
return; // 如果没有表达式,直接返回
}
// 计算结果并更新状态
(this.getResult() as Promise<boolean>).then((result: boolean) => {
if (!result) {
return
}
this.inputValue = this.calValue // 更新输入值为计算结果
this.calValue = ''
this.expressions = []
this.expressions.push(this.inputValue)
})
break
default:
this.inputOperators(len, value) // 处理其他符号输入
break;
}
this.formatInputValue() // 格式化输入值
}
// 输入数字处理
inputNumber(value: string) {
if (CheckEmptyUtil.isEmpty(value)) {
return // 检查输入是否为空
}
let len: number = this.expressions.length // 获取当前表达式长度
let last: string = len > 0 ? this.expressions[len - 1] : '' // 获取最后一个输入
let secondLast: string | undefined = len > 1 ? this.expressions[len - CommonConstants.TWO] : undefined // 获取倒数第二个输入
// 验证输入是否合法
if (!this.validateEnter(last, value)) {
return
}
// 根据当前表达式状态更新表达式
if (!last) {
this.expressions.push(value) // 如果没有输入,直接添加
} else if (!secondLast) {
this.expressions[len - 1] += value // 如果只有一个输入,拼接
}
if (secondLast && CalculateUtil.isSymbol(secondLast)) {
this.expressions[len - 1] += value // 如果倒数第二个是符号,拼接
}
if (secondLast && !CalculateUtil.isSymbol(secondLast)) {
this.expressions.push(value) // 如果倒数第二个不是符号,添加新的输入
}
this.formatInputValue(); // 格式化输入值
if (value !== CommonConstants.DOTS) {
this.getResult() // 如果输入不是小数点,计算结果
}
}
// 验证输入合法性
validateEnter(last: string, value: string) {
if (!last && value === CommonConstants.PERCENT_SIGN) {
return false // 不能在没有输入的情况下输入百分号
}
if ((last === CommonConstants.MIN) && (value === CommonConstants.PERCENT_SIGN)) {
return false // 负号后不能直接输入百分号
}
if (last.endsWith(CommonConstants.PERCENT_SIGN)) {
return false // 不能在百分号后输入其他数字
}
if ((last.indexOf(CommonConstants.DOTS) !== -1) && (value === CommonConstants.DOTS)) {
return false // 不能输入多个小数点
}
if ((last === '0') && (value !== CommonConstants.DOTS) &&
(value !== CommonConstants.PERCENT_SIGN)) {
return false // 0后不能输入非小数和百分号的数字
}
return true // 验证通过
}
// 删除操作
inputDelete(len: number) {
if (len === 0) {
return // 如果没有输入,直接返回
}
let last = this.expressions[len - 1] // 获取最后一个输入
let lastLen: number = last.length // 获取最后一个输入的长度
if (lastLen === 1) {
this.expressions.pop() // 如果长度为1,删除该输入
len = this.expressions.length
} else {
this.expressions[len - 1] = last.slice(0, last.length - 1) // 删除最后一个字符
}
if (len === 0) {
this.inputValue = ''
this.calValue = ''
return // 如果没有输入,清空值
}
if (!CalculateUtil.isSymbol(this.expressions[len - 1])) {
this.getResult() // 如果最后一个不是符号,重新计算结果
}
}
// 输入运算符处理
inputOperators(len: number, value: string) {
let last: string | undefined = len > 0 ? this.expressions[len - 1] : undefined // 获取最后一个输入
let secondLast: string | undefined = len > 1 ? this.expressions[len - CommonConstants.TWO] : undefined // 获取倒数第二个输入
if (!last && (value === Symbol.MIN)) {
this.expressions.push(this.getSymbol(value)); // 处理负号的情况
return
}
if (!last) {
return // 如果没有输入,返回
}
if (!CalculateUtil.isSymbol(last)) {
this.expressions.push(this.getSymbol(value)) // 如果最后一个不是符号,添加新的符号
return
}
if ((value === Symbol.MIN) &&
(last === CommonConstants.MIN || last === CommonConstants.ADD)) {
this.expressions.pop() // 处理负号替换
this.expressions.push(this.getSymbol(value))
return
}
if (!secondLast) {
return // 如果倒数第二个输入不存在,返回
}
if (value !== Symbol.MIN) {
this.expressions.pop() // 如果不是负号,删除最后一个符号
}
if (CalculateUtil.isSymbol(secondLast)) {
this.expressions.pop() // 如果倒数第二个也是符号,删除
}
this.expressions.push(this.getSymbol(value)) // 添加新的符号
}
// 获取符号对应的字符串
getSymbol(value: string) {
if (CheckEmptyUtil.isEmpty(value)) {
return ''
}
let symbol = ''
switch (value) {
case Symbol.ADD:
symbol = CommonConstants.ADD
break
case Symbol.MIN:
symbol = CommonConstants.MIN
break
case Symbol.MUL:
symbol = CommonConstants.MUL
break;
case Symbol.DIV:
symbol = CommonConstants.DIV
break
default:
break
}
return symbol // 返回对应的符号
}
// 深拷贝表达式数组
deepCopy(): Array<string> {
let copyExpressions: Array<string> = Array.from(this.expressions) // 使用 Array.from 深拷贝数组
return copyExpressions
}
// 计算结果
async getResult(): Promise<boolean> {
let calResult = CalculateUtil.parseExpression(this.deepCopy()) // 解析表达式
if (calResult === 'NaN') {
this.calValue = this.resourceToString($r('app.string.error')) // 处理计算错误
return false;
}
this.calValue = calResult; // 更新计算结果
return true; // 计算成功
}
// 格式化显示结果
resultFormat(value: string): string {
let reg: RegExp = (value.indexOf('.') > -1) ? new RegExp("/(\d)(?=(\d{3})+.)/g") : new RegExp("/(\d)(?=(?:\d{3})+$)/g");
return value.replace(reg, '$1,'); // 添加千位分隔符
}
// 从资源获取字符串
resourceToString(resource: Resource): string {
if (CheckEmptyUtil.isEmpty(resource)) {
return '';
}
let result = '';
try {
result = getContext(this).resourceManager.getStringSync(resource.id); // 获取资源字符串
} catch (error) {
Logger.error('[CalculateModel] getResourceString fail: ' + JSON.stringify(error)) // 记录错误
}
return result; // 返回结果
}
// 格式化输入值
formatInputValue() {
let deepExpressions: Array<string> = [];
this.deepCopy().forEach((item: string, index: number) => {
deepExpressions[index] = this.resultFormat(item); // 格式化每个输入项
});
this.inputValue = deepExpressions.join(''); // 将格式化的项连接成字符串
}
}
✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接