制作我的计算器

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。

视频:《实现菜谱二级联动导航》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/83750.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025-5-17Vue3快速上手

1、ref对比reactive 区别第2点&#xff1a;本质是指针指向问题 整体修改reactive的数据时&#xff0c;有坑 使用原则需要根据项目原本的代码灵活参考 如果要更新的数据是从服务器获取回来的&#xff0c;用Object.assign是好方法&#xff0c;需要注意的是&#xff1a;Object.a…

深度学习---模型预热(Model Warm-Up)

一、基本概念与核心定义 模型预热是指在机器学习模型正式训练或推理前&#xff0c;通过特定技术手段使模型参数、计算图或运行环境提前进入稳定状态的过程。其本质是通过预处理操作降低初始阶段的不稳定性&#xff0c;从而提升后续任务的效率、精度或性能。 核心目标&#xf…

加载渲染geojson数据

本节我们学习如何在cesium中加载geojson数据 想要加载geojson数据首先要有数据源,我们以中国地图为例 复制数据的geo api 在cesium的官网库中查询 可以看到如何在cesium中导入数据的方法 //加载geojson数据let dataGeo Cesium.GeoJsonDataSource.load("https://geo.dat…

python:pymysql概念、基本操作和注入问题讲解

python&#xff1a;pymysql分享目录 一、概念二、数据准备三、安装pymysql四、pymysql使用&#xff08;一&#xff09;使用步骤&#xff08;二&#xff09;查询操作&#xff08;三&#xff09;增&#xff08;四&#xff09;改&#xff08;五&#xff09;删 五、关于pymysql注入…

职坐标AIoT技能培训课程实战解析

职坐标AIoT技能培训课程以人工智能与物联网技术深度融合为核心&#xff0c;构建了“理论实战行业应用”三位一体的教学体系。课程体系覆盖Python编程基础、传感器数据采集、边缘计算开发、云端服务部署及智能硬件开发全链路&#xff0c;通过分层递进的知识模块帮助学员建立系统…

MySQL 用户权限管理:从入门到精通

在当今数据驱动的时代&#xff0c;数据库安全已成为企业信息安全体系的核心组成部分。作为最流行的开源关系型数据库之一&#xff0c;MySQL 的用户权限管理系统提供了强大而灵活的访问控制机制。本文将全面解析 MySQL 用户权限管理的各个方面&#xff0c;帮助数据库管理员和开发…

Java常见API文档(下)

格式化的时间形式的常用模式对应关系如下&#xff1a; 空参构造创造simdateformate对象&#xff0c;默认格式 练习.按照指定格式展示 package kl002;import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;public class Date3 {publi…

博图1200硬件组态与启保停程序编写步骤详解

一、前言 在工业自动化控制领域&#xff0c;西门子S7-1200 PLC因其性能稳定、编程灵活而广受欢迎。本文将详细介绍使用TIA Portal&#xff08;博图&#xff09;软件进行S7-1200 PLC硬件组态以及编写基本启保停程序的完整步骤&#xff0c;帮助初学者快速掌握这一基础而重要的技…

AutoMouser - 单次AI调用铸就高效自动化脚本

你是否厌倦了反复点点点的枯燥操作&#xff1f;是否希望像科幻电影那样&#xff0c;一句指令&#xff0c;万事搞定&#xff1f;如果告诉你&#xff0c;现在只需要一次AI调用&#xff0c;就能自动执行一整套鼠标脚本操作&#xff0c;你会不会觉得&#xff1a;自动化的时代&#…

双周报Vol.72:字段级文档注释支持、视图类型现为值类型,减少内存分配

双周报Vol.72&#xff1a;字段级文档注释支持、视图类型现为值类型&#xff0c;减少内存分配 更新目录 ..调用链末尾自动丢弃值语义变更字段级文档注释支持视图类型现为值类型&#xff0c;减少内存分配特效函数调用现支持样式高亮实验性支持虚拟包&#xff0c;接口与实现解耦 …

OceanBase 开发者大会:详解 Data × AI 战略,数据库一体化架构再升级

OceanBase 2025 开发者大会与5月17日在广州举行。这是继 4 月底 OceanBase CEO 杨冰宣布公司全面进入AI 时代后的首场技术盛会。会上&#xff0c;OceanBase CTO 杨传辉系统性地阐述了公司的 DataAI 战略&#xff0c;并发布了三大产品&#xff1a;PowerRAG、共享存储&#xff0c…

大小端模式和消息的加密解密

大小端模式 知识点一 什么是大小端模式 // 大端模式 // 是指数据的高字节保存在内存的低地址中 // 而数据的低字节保存在内存的高地址中 // 这样的存储模式有点儿类似于把数据当作字符串顺序处理 // 地址由小向大增加,数据从高位往低位放 …

WebRTC技术EasyRTC嵌入式音视频通信SDK助力智能电视搭建沉浸式实时音视频交互

一、方案概述​ EasyRTC是一款基于WebRTC技术的开源实时音视频通信解决方案&#xff0c;具备低延迟、高画质、跨平台等优势。将EasyRTC功能应用于智能电视&#xff0c;能够为用户带来全新的交互体验&#xff0c;满足智能电视在家庭娱乐、远程教育、远程办公、远程医疗等多种场…

Supermemory:让大模型拥有“长效记忆“

目录 引言&#xff1a;打破大语言模型的记忆瓶颈&#xff0c;迎接AI交互新范式 一、Supermemory 核心技术 1.1 透明代理机制 1.2 智能分段与检索系统 1.3 自动Token管理 二、易用性 三、性能与成本 四、可靠性与兼容性 五、为何选择 Supermemory&#xff1f; 六、对…

2025.5.17总结

周六上了一天的课&#xff0c;从早上9&#xff1a;30至下午6&#xff1a;30&#xff0c;在这个过程中&#xff0c;确实也收获了不少。 1.结识了更多的大佬和不同职业的精英。 一个在某科技公司做开发的主管甘阿碰&#xff0c;当我听到科技公司&#xff0c;还以为是公司里的一…

语音识别——通过PyAudio录入音频

PyAudio 是一个用于处理音频的 Python 库&#xff0c;它提供了录制和播放音频的功能。通过 PyAudio&#xff0c;可以轻松地从麦克风或其他音频输入设备录制音频&#xff0c;并将其保存为文件或进行进一步处理。 安装 PyAudio 在使用 PyAudio 之前&#xff0c;需要先安装它。可…

python打卡day30

模块和库的导入 知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 作业&#xff1a;自己新建几个不同路径文件尝试下如何导入 python的学习就像…

C++ —— Lambda 表达式

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 L…

十三、面向对象底层逻辑-Dubbo序列化Serialization接口

一、引言&#xff1a;分布式通信的数据桥梁 在分布式服务调用中&#xff0c;参数的跨网络传输需要将对象转化为二进制流&#xff0c;这一过程直接影响系统的性能、兼容性与安全性。Dubbo通过Serialization接口构建了可扩展的序列化体系&#xff0c;支持多种序列化协议的无缝切…

批量剪辑 + 矩阵分发 + 数字人分身源码搭建全技术解析,支持OEM

在互联网内容生态蓬勃发展的当下&#xff0c;企业与创作者对内容生产与传播效率的要求日益增长。批量剪辑、矩阵分发和数字人分身技术的融合&#xff0c;成为提升内容创作与运营效能的关键方案。从源码层面实现三者的搭建与整合&#xff0c;需要深入理解各功能技术原理&#xf…