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。
視頻:《實現菜譜二級聯動導航》。