使用ArkTs进行计算器的实现,基于deveco-studio3.1.1,新版未尝试
1、写出大致的计算器构造在calcultorDemo.ets文件中在calculatorResult.ets中 2、进行计算机的功能部分给每个按钮分别添加点击事件完成value的计算事件,利用栈进行算术表达式的求值: 3、完整代码(一个文件)
1、写出大致的计算器构造
在calcultorDemo.ets文件中
import {calculatorResult} from './calculatorResult'import numberstack from './NumberStack'import characterstack from './CharacterStack'@Entry@Componentstruct calcultorDemo { @State value:string ='' @State result:string='' @State opacityValue:number=0 @State fontSizeValue:number=30 numbers :string[]=['(',')','÷','×','1','2','3','-','4','5','6','+','7','8','9'] numbers2:string[]=['%','0','.'] build() { Column(){ calculatorResult({value:$value,result:this.result,opacityValue:$opacityValue,fontSizeValue:$fontSizeValue}) Column(){//计算机主体页面 Grid(){ GridItem(){ Text('MC') .TextStyle() } .oneStyle()//MC(清零)工具 GridItem(){ Text('MR') .TextStyle() } .oneStyle() GridItem(){ Image($r('app.media.delete')) .fillColor(Color.Blue) .height(40) } .oneStyle() .onClick(()=>{ this.value=this.value.slice(0,this.value.length-1) this.result='' }) GridItem(){ Text('C') .TextStyle() } .oneStyle() .onClick(()=>{ this.value='' this.result='' }) ForEach(this.numbers,item=>{ GridItem(){ Text(item) .TextStyle() } .onClick(()=>{ if (this.value === '' && (item === '+' || item === '-' || item === '×' || item === '÷' || item === '%')) { return }//判断点击的第一个字符是不是运算符,若是则返回 if (this.value[this.value.length - 1] === '+' || this.value[this.value.length - 1] === '-' || this.value[this.value.length - 1] === '×' || this.value[this.value.length - 1] === '÷' || this.value[this.value.length - 1] === '%') { // 如果当前点击的是运算符,则替换最后一个运算符 if (item === '+' || item === '-' || item === '×' || item === '÷' || item === '%') { this.value = this.value.slice(0, this.value.length - 1) + item } else { this.value += item } } else { this.value = this.value.concat(item) } }) .oneStyle() }) GridItem(){ Text('=') .TextStyle() .fontColor(Color.White) } .rowStart(5) .rowEnd(6) .borderRadius(40) .backgroundColor(Color.Blue) .onClick(()=>{ this.result=total(checkParentheses(this.value+'#').cleanedExpression) this.opacityValue=1 this.fontSizeValue=50 }) ForEach(this.numbers2,item=>{ GridItem(){ Text(item) .TextStyle() } .onClick(()=>{this.value=this.value.concat(item)}) .oneStyle() }) } .width('100%') .height(500) .columnsTemplate('1fr 1fr 1fr 1fr') .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr') .columnsGap(8) .rowsGap(12) .padding(12) .backgroundColor('#fff6f0f0') .borderRadius({topLeft:20,topRight:20}) } .layoutWeight(1) .justifyContent(FlexAlign.End) } .height('100%') }}@Styles function oneStyle(){ .backgroundColor(Color.White) .height(70) .width(70) .borderRadius(40) .shadow({color:Color.Gray,radius:5})}@Extend(Text) function TextStyle() { .fontSize(25) .fontWeight(400)}
以上代码写出计算机的键盘部分,使用Grid组件进行键盘的分隔,以及对相同功能的按钮进行ForEach循环渲染减少占有空间
在calculatorResult.ets中
进行键盘输入(TextInput)和输出(Text)的编写
@Componentexport struct calculatorResult{ @Link value:string @Prop result:string @Link opacityValue:number @Link fontSizeValue:number build() { Column(){ TextInput({text:this.value}) .height(80) .margin({top:60}) .placeholderFont({size:60}) .fontSize(60) .fontWeight(450) .textAlign(TextAlign.End) .backgroundColor(Color.White) Text(this.result) .opacity(this.opacityValue) .width('100%') .height(60) .fontSize(this.fontSizeValue) .fontWeight(450) .textAlign(TextAlign.End) .animation({duration:500}) // @ts-ignore .textOverflow(TextOverflow.Clip) } }}
经过以上两个文件的渲染后,计算机的大概形状显示出来,如图所示:
2、进行计算机的功能部分
给每个按钮分别添加点击事件
1、对1,2,3,4,5,6,7,8,9,+, - , × ,÷添加存储表达式的点击事件,定义 @State value:string =‘’ 在每次进行点击时,对所点击的内容做出判断,储存所点击的内容。
.onClick(()=>{ if (this.value === '' && (item === '+' || item === '-' || item === '×' || item === '÷' || item === '%')) { return }//判断点击的第一个字符是不是运算符,若是则返回 if (this.value[this.value.length - 1] === '+' || this.value[this.value.length - 1] === '-' || this.value[this.value.length - 1] === '×' || this.value[this.value.length - 1] === '÷' || this.value[this.value.length - 1] === '%') { // 如果在上一个字符是运算符时,当前点击的是运算符,则替换最后一个运算符 if (item === '+' || item === '-' || item === '×' || item === '÷' || item === '%') { this.value = this.value.slice(0, this.value.length - 1) + item } else { //若当前点击不是运算符而是数字时,则继续输入表达式 this.value += item } } else { this.value = this.value.concat(item) } })
2、对删除按钮:定义 @State result:string=''进行计算机结果的保存,方便后续渲染在Text组件中。
.onClick(()=>{ this.value=this.value.slice(0,this.value.length-1) this.result='' })
3、对于清零工具则将value和result都置为空
.onClick(()=>{ this.value='' this.result='' })
完成value的计算事件,利用栈进行算术表达式的求值:
主要思路:
1、将中缀表达式转为后缀表达式;
2、对后缀表达式求值;
具体可参考:栈的应用-表达式求值
代码如下:
1、在calcultorDemo.ets中:
//检查括号是否多余,且若前括号多余则去掉多余的前括号function checkParentheses(expression: string): { valid: boolean, cleanedExpression: string } { let stack = [] let cleanedExpression = expression//判断表达式括号是否对应 for (let ch of expression) { if (ch === '(') { stack.push(ch) } else if (ch === ')') {//后括号多余则直接返回错误 if (stack.length === 0) { return { valid: false, cleanedExpression: expression } } stack.pop() } } //若不对应,去掉多余的前括号 while (stack.length > 0) { let index = cleanedExpression.indexOf('(') if (index !== -1) { cleanedExpression = cleanedExpression.slice(0, index) + cleanedExpression.slice(index + 1) } stack.pop() } console.log(cleanedExpression) return { valid: stack.length === 0, cleanedExpression }}//对表达式求值function total(expression:string){ characterstack.push('#') let i=0 let ch=expression[i] while (ch!='#'||characterstack.peek()!='#') { if(!Instring(ch)){//进行多位数的入栈 let numStr = ch while (i + 1 < expression.length && !Instring(expression[i + 1])) { ch = expression[++i] numStr += ch } numberstack.push(numStr) ch = expression[++i] } else{ switch (Precede(characterstack.peek(),ch)){ case '<':{ characterstack.push(ch); ch=expression[++i] break } case '>':{ let theta= characterstack.pop() let b=numberstack.pop() let a=numberstack.pop() numberstack.push(Operate(a,theta,b)) break } case '=':{ characterstack.pop() ch=expression[++i] break } } } } return numberstack.peek()}//判断ch是否为运算符function Instring(ch:string){ let num:string[]=['+','-','(',')','÷','×','#','%'] return num.includes(ch)}//判断运算符的优先级function Precede(thetal:string,thetal2:string):string{ if((thetal=='('&&thetal2==')')||(thetal=='#'&&thetal2=='#')){ return '=' } else if(thetal=='('||thetal=='#'||thetal2=='('||(thetal=='+'||thetal=='-')&&(thetal2=='×'||thetal2=='÷'||thetal2=='%')){ return '<' } else return '>'}//计算两数的运算结果function Operate(first:string,theta:string,second:string):string{ switch (theta){ case '+': return ((+first)+(+second)).toString() case '-': return ((+first)-(+second)).toString() case '×': return ((+first)*(+second)).toString() case '÷': return ((+first)/(+second)).toString() case '%': return ((+first)%(+second)).toString() } return 'NaN'}
2、在CharacterStack.ets中进行字符栈的初始化,并进行导出;
//运算符栈 class CharacterStack{ private characters:string[] constructor() { this.characters=[] } //入栈 push(item:string){ this.characters.push(item) } //出栈 pop(){ return this.characters.pop() } //返回栈顶元素 peek(){ return this.characters[this.characters.length-1] } //判断栈是否为空 isEmpty(){ return this.characters.length===0 } //清空栈内元素 clear(){ this.characters=[] } //获取栈内元素数量 size():number{ return this.characters.length }}const characterstack =new CharacterStack()export default characterstack
3、在NumberStack.ets中进行数字栈的初始化,并进行导出;
//数字栈export class NumberStack{ private numbers:string[] constructor() { this.numbers=[] } //入栈 push(item:string){ this.numbers.push(item) } //出栈 pop(){ return this.numbers.pop() } //返回栈顶元素 peek(){ return this.numbers[this.numbers.length-1] } //判断栈是否为空 isEmpty(){ return this.numbers.length===0 } //清空栈内元素 clear(){ this.numbers=[] } //获取栈内元素数量 size():number{ return this.numbers.length }}const numberstack =new NumberStack()export default numberstack
已解决问题:
1、括号不对应,对前括号去掉,或直接报错;
2、重复输入符号的问题;
3、首相为符号的问题;
存在问题:
1、“3-”不可以输出正常的3;
2、对负数不可进行计算;…(存在未发现问题)
3、完整代码(一个文件)
@Entry@Componentstruct calcultorDemo { @State value:string ='' @State result:string='' @State opacityValue:number=0 @State fontSizeValue:number=30 numbers :string[]=['(',')','÷','×','1','2','3','-','4','5','6','+','7','8','9'] numbers2:string[]=['%','0','.'] build() { Column(){ Column(){ TextInput({text:this.value}) .height(80) .margin({top:60}) .placeholderFont({size:60}) .fontSize(60) .fontWeight(450) .textAlign(TextAlign.End) .backgroundColor(Color.White) Text(this.result) .opacity(this.opacityValue) .width('100%') .height(60) .fontSize(this.fontSizeValue) .fontWeight(450) .textAlign(TextAlign.End) .animation({duration:500}) // @ts-ignore .textOverflow(TextOverflow.Clip) } Column(){//计算机主体页面 Grid(){ GridItem(){ Text('MC') .TextStyle() } .oneStyle()//MC(清零)工具 GridItem(){ Text('MR') .TextStyle() } .oneStyle() GridItem(){ Image($r('app.media.delete')) .fillColor(Color.Blue) .height(40) } .oneStyle() .onClick(()=>{ this.value=this.value.slice(0,this.value.length-1) this.result='' }) GridItem(){ Text('C') .TextStyle() } .oneStyle() .onClick(()=>{ this.value='' this.result='' }) ForEach(this.numbers,item=>{ GridItem(){ Text(item) .TextStyle() } .onClick(()=>{ if (this.value === '' && (item === '+' || item === '-' || item === '×' || item === '÷' || item === '%')) { return }//判断点击的第一个字符是不是运算符,若是则返回 if (this.value[this.value.length - 1] === '+' || this.value[this.value.length - 1] === '-' || this.value[this.value.length - 1] === '×' || this.value[this.value.length - 1] === '÷' || this.value[this.value.length - 1] === '%') { // 如果当前点击的是运算符,则替换最后一个运算符 if (item === '+' || item === '-' || item === '×' || item === '÷' || item === '%') { this.value = this.value.slice(0, this.value.length - 1) + item } else { this.value += item } } else { this.value = this.value.concat(item) } }) .oneStyle() }) GridItem(){ Text('=') .TextStyle() .fontColor(Color.White) } .rowStart(5) .rowEnd(6) .borderRadius(40) .backgroundColor(Color.Blue) .onClick(()=>{ this.result=total(checkParentheses(this.value+'#').cleanedExpression) this.opacityValue=1 this.fontSizeValue=50 }) ForEach(this.numbers2,item=>{ GridItem(){ Text(item) .TextStyle() } .onClick(()=>{this.value=this.value.concat(item)}) .oneStyle() }) } .width('100%') .height(500) .columnsTemplate('1fr 1fr 1fr 1fr') .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr') .columnsGap(8) .rowsGap(12) .padding(12) .backgroundColor('#fff6f0f0') .borderRadius({topLeft:20,topRight:20}) } .layoutWeight(1) .justifyContent(FlexAlign.End) } .height('100%') }}@Styles function oneStyle(){ .backgroundColor(Color.White) .height(70) .width(70) .borderRadius(40) .shadow({color:Color.Gray,radius:5})}@Extend(Text) function TextStyle() { .fontSize(25) .fontWeight(400)}//运算符栈class CharacterStack{ private characters:string[] constructor() { this.characters=[] } //入栈 push(item:string){ this.characters.push(item) } //出栈 pop(){ return this.characters.pop() } //返回栈顶元素 peek(){ return this.characters[this.characters.length-1] } //判断栈是否为空 isEmpty(){ return this.characters.length===0 } //清空栈内元素 clear(){ this.characters=[] } //获取栈内元素数量 size():number{ return this.characters.length }}const characterstack =new CharacterStack()//数字栈export class NumberStack{ private numbers:string[] constructor() { this.numbers=[] } //入栈 push(item:string){ this.numbers.push(item) } //出栈 pop(){ return this.numbers.pop() } //返回栈顶元素 peek(){ return this.numbers[this.numbers.length-1] } //判断栈是否为空 isEmpty(){ return this.numbers.length===0 } //清空栈内元素 clear(){ this.numbers=[] } //获取栈内元素数量 size():number{ return this.numbers.length }}const numberstack =new NumberStack()//检查括号是否多余,且若前括号多余则去掉多余的前括号function checkParentheses(expression: string): { valid: boolean, cleanedExpression: string } { let stack = [] let cleanedExpression = expression for (let ch of expression) { if (ch === '(') { stack.push(ch) } else if (ch === ')') { if (stack.length === 0) { return { valid: false, cleanedExpression: expression } } stack.pop() } } // 去掉多余的前括号 while (stack.length > 0) { let index = cleanedExpression.indexOf('(') if (index !== -1) { cleanedExpression = cleanedExpression.slice(0, index) + cleanedExpression.slice(index + 1) } stack.pop() } console.log(cleanedExpression) return { valid: stack.length === 0, cleanedExpression }}function total(expression:string){ characterstack.push('#') let i=0 let ch=expression[i] while (ch!='#'||characterstack.peek()!='#') { if(!Instring(ch)){//进行多位数的入栈 let numStr = ch while (i + 1 < expression.length && !Instring(expression[i + 1])) { ch = expression[++i] numStr += ch } numberstack.push(numStr) ch = expression[++i] } else{ switch (Precede(characterstack.peek(),ch)){ case '<':{ characterstack.push(ch); ch=expression[++i] break } case '>':{ let theta= characterstack.pop() let b=numberstack.pop() let a=numberstack.pop() numberstack.push(Operate(a,theta,b)) break } case '=':{ characterstack.pop() ch=expression[++i] break } } } } return numberstack.peek()}//判断ch是否为运算符function Instring(ch:string){ let num:string[]=['+','-','(',')','÷','×','#','%'] return num.includes(ch)}//判断运算符的优先级function Precede(thetal:string,thetal2:string):string{ if((thetal=='('&&thetal2==')')||(thetal=='#'&&thetal2=='#')){ return '=' } else if(thetal=='('||thetal=='#'||thetal2=='('||(thetal=='+'||thetal=='-')&&(thetal2=='×'||thetal2=='÷'||thetal2=='%')){ return '<' } else return '>'}//计算两数的运算结果function Operate(first:string,theta:string,second:string):string{ switch (theta){ case '+': return ((+first)+(+second)).toString() case '-': return ((+first)-(+second)).toString() case '×': return ((+first)*(+second)).toString() case '÷': return ((+first)/(+second)).toString() case '%': return ((+first)%(+second)).toString() } return 'NaN'}