制作我的計算器

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.pswp.cn/diannao/83750.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83750.shtml
英文地址,請注明出處:http://en.pswp.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…