使用ArkTs进行计算器的实现,基于deveco-studio3.1.1,新版未尝试
1、写出大致的计算器构造
在calcultorDemo.ets文件中
import {calculatorResult} from './calculatorResult'
import numberstack from './NumberStack'
import characterstack from './CharacterStack'
@Entry
@Component
struct 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)的编写
@Component
export 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
@Component
struct 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'
}