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.etsexport class PressKeysItem {flag: numberwidth: stringheight: stringvalue: stringsource?: Resourceconstructor(flag: number, width: string, height: string, value: string, source?: Resource) {this.flag = flagthis.width = widththis.height = heightthis.value = valuethis.source = source}
}export class PressKeysInnerObject {id: stringdata: PressKeysItemconstructor(id: string, data: PressKeysItem) {this.id = idthis.data = data}
}export class PressKeysOuterObject {id: stringdata: Array<PressKeysInnerObject>constructor(id: string, data: Array<PressKeysInnerObject>) {this.id = idthis.data = data}
}
- PressKeysViewModel.ets
// ets/viewmodel/PressKeysViewModel.etsimport { 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.etsimport { 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; // 数字2static readonly TEN: number = 10; // 数字10static readonly ONE_HUNDRED: string = '100'; // 字符串'100'static readonly INPUT_LENGTH_MAX: number = 9; // 最大输入长度static readonly INDEX_TWO: number = 2; // 索引2static readonly NUM_MAX_LEN: number = 16; // 数字最大长度static readonly E: string = 'e'; // 科学计数法中的estatic 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.etsclass 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.etsimport 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 是否优先级低于等于 arg2return (this.getPriority(arg1) <= this.getPriority(arg2));}// 解析表达式,使用逆波兰表示法(后缀表达式)// 3×5+4÷2parseExpression(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.etsimport { 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)})breakdefault: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.ADDbreakcase Symbol.MIN:symbol = CommonConstants.MINbreakcase Symbol.MUL:symbol = CommonConstants.MULbreak;case Symbol.DIV:symbol = CommonConstants.DIVbreakdefault: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(''); // 将格式化的项连接成字符串}
}
-THE END-
代码与视频教程
完整案例代码与视频教程请参见:
代码:Code-05-03.zip。
视频:《实现菜谱二级联动导航》。