本次博客基于下面博客
效果图
首先,先给大家看一下计算器的效果图
- 以下是科学计算器的UI设计以及功能演示(附上手算答案),可见科学计算器计算功能没问题
- 下面是演示视频以及退格设计:在计算式预览组件中滑动手指即可(左滑或者右滑)附上正确手算题目及答案
圆形按钮功能分析及代码(包含按钮动画)
按钮功能分析
- 点击后可以将这个按钮对应的字符添加到计算式字符串中(详见下面代码的OnClick事件)
- 点击时透明度发生变化以实现点击动效(详见下面代码的OnTouch事件)
- 在计算式的Row组件中手指滑动实现退格(退位)操作(详见下面代码的On)
- 外观和谐美观
代码
@Component
export struct Button1{
//计算式
@Link cal:string
//计算结果
@Link result:string|undefined|number
//是否已经按下等号的标记
@Consume flag:boolean
//按钮透明度设置
@Link button_opacity_son:number
//1为测试数据
num:string = '1'
//按钮颜色
buttonColor:number = 0x333333
//文本颜色
textColor:number = Color.White
//文本大小
textSize:number = 28
build() {
Column(){
Text(`${this.num}`)
.fontColor(this.textColor)
.fontWeight(FontWeight.Medium)
.fontSize(this.textSize)
}.justifyContent(FlexAlign.Center)
//按下按钮时,按钮的透明度改变
.onTouch((Event:TouchEvent)=>{
if(Event.type==TouchType.Down){
this.button_opacity_son = 0.5
}
if(Event.type==TouchType.Up){
this.button_opacity_son = 1;
}
})
.width(80)
.height(80)
.opacity(this.button_opacity_son)
.alignItems(HorizontalAlign.Center)
.backgroundColor(this.buttonColor)
.borderRadius(40)
.onClick(()=>{
//’AC‘功能的实现
if(this.num=='AC'){
this.flag = true;
this.cal='';
this.result = '0';
return
}
else if(this.num=='='){
//如果第一次按下等号就显示等号
if(this.flag == true) {
this.flag = false;
let EndExpression =SwitchMiddleToEnd(this.cal);
EndExpression.print_queue()
this.result = calculate_achieve(EndExpression);
}
//否则不显示等号
else {
let EndExpression = SwitchMiddleToEnd(this.cal);
EndExpression.print_queue()
this.result = calculate_achieve(EndExpression);
this.result = String(roundToDecimal(this.result));
}
}
//将按钮所对应的字符加到计算式后
else {
this.cal += this.num;
}
})
}
}
滑动删除功能
滑动删除功能的实现原理思路
- 定义一个起始坐标(sta_x)、波尔类型的标记(flag==false)以及手指滑动过程中x坐标的偏移量(distance_x)
- 获得手指按下时的x坐标作为起始坐标(sta_x),并将flag改为true代表手指开始接触屏幕
- 手指滑动计算distance_x偏移量,如果偏移量大于某个值并且flag是true时就删除计算式字符串的最后一位,即实现退格操作,同时并把flag再次置为false使得如果不松手的话无论手指怎么滑动都只能进行一次退格。
- 手指离开时flag置为true,方便进行下一次滑动退格。
滑动删除功能代码
Row() {
Text(`${this.cal}`)
.fontSize(24)
.fontColor(Color.White)
}.height(70)
.width('100%')
.justifyContent(FlexAlign.End)
.padding({ right: 35 })
//通过onTouch事件来实现滑动退格
.onTouch((Event:TouchEvent)=>{
if(Event.type==TouchType.Down){
//获得手指按下时的x坐标作为起始坐标
this.sta_x = Event.touches[0].x
this.Delete_bool = true;
}
if(Event.type==TouchType.Up){
this.Delete_bool = true
}
if(Event.type==TouchType.Move){
//移动过程中计算x轴上移动的距离
this.distance_x = Math.abs(this.sta_x - Event.touches[0].x)
//如果距离超过20就进行退格操作
if(this.distance_x>20&&this.Delete_bool == true){
this.cal = this.cal.slice(0,-1);
this.Delete_bool = false;
}
}
})
中缀表达式转后缀表达式(逆波兰表达式)
实现思路原理
- 规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级不高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
- 做法:我们这里分别构造了一个队列和一个栈,栈根据以上规则进行获得后缀表达式,而队列用来存储后缀表达式,最后将这个存储着后缀表达式的队列返回。关于栈和队列的构建可以看下面两个博客:
代码实现
//中缀表达式转后缀表达式函数
export function SwitchMiddleToEnd(cal:string){
let queue = new queue_my();
let first_fu:boolean = true
let switchStack = new CharacterStack();
let i =0;
let currentcharacter:string = '';
let flaglog:boolean = false;
let flagtriangle:boolean = false;
while(i<cal.length){
if(isCharacter(cal[i])){
currentcharacter = cal[i];
console.log('当前的符号为:'+currentcharacter);
}
else if(cal[i]=='l'){
currentcharacter = 'l'+cal[i+1];
console.log('当前的符号为:'+currentcharacter);
flaglog = true;
}
else if(isSingleCharacter1(cal[i])&&cal[i]!='l'){
currentcharacter = cal[i]+cal[i+1]+cal[i+2];
console.log('当前的符号为:'+currentcharacter);
flagtriangle = true;
}
//如果遍历到数字直接入队
if(isNumber(cal[i])){
let numberstring = '';
if(cal[i]=='π'){
queue.enqueue(String(Math.PI));
}
else {
while (isNumber(cal[i])) {
numberstring += cal[i];
i++
}
queue.enqueue(numberstring);
console.log(`${numberstring}入队`);
i--
}
}
//如果遇到第一个位置是左括号加-号
else if(cal[0]=='('&&cal[1]=='-'&&first_fu){
switchStack.push(cal[0]);
let numberstring = '-'
first_fu = false;
i = 2
while(isNumber(cal[i])){
numberstring+=cal[i]
i++
}
queue.enqueue(numberstring)
i--
console.log(`${numberstring}入队`);
console.log(`此时遍历到了:${cal[i]}`)
}
else if(cal[0]=='-'&&isNumber(cal[1])&&first_fu){
let numberstring = '-';
first_fu = false;
i=1;
while(isNumber(cal[i])){
numberstring+=cal[i];
i++
}
queue.enqueue(numberstring);
console.log(`${numberstring}入队`);
i--;
}
//如果连按几次同一个操作符
/* else if(!isNumber(cal[i])&&!isNumber(cal[i+1])&&cal[i]==cal[i+1]){
i++;
}*/
//带括号的负数在中间的时候
else if(cal[i]=='('&&cal[i+1]=='-'&&isNumber(cal[i+2])){
let numberstring = '-';
i = i+2;
while(isNumber(cal[i])){
numberstring+=cal[i];
++i
}
queue.enqueue(numberstring)
console.log(`${numberstring}已经入队`)
}
//如果栈中为空或者遇到左括号直接入栈
else if(switchStack.isEmpty()||cal[i]=='('){
if(cal[i]=='(') console.log('遇到了左括号')
switchStack.push(currentcharacter);
}
//栈顶操作符优先级小于当前操作符优先级
else if(cal[i]!=')'&&getPrecedence(switchStack.peek())<getPrecedence(currentcharacter)){
switchStack.push(currentcharacter);
console.log("放入的操作符为:"+currentcharacter);
}
//栈顶操作符优先级大于当前操作符优先级
else if(cal[i]!=')'&&getPrecedence(switchStack.peek())>=getPrecedence(currentcharacter)){
//栈顶元素出栈并且加到队列中去
while(getPrecedence(currentcharacter)<=getPrecedence(switchStack.peek()))
{queue.enqueue(String(switchStack.pop()))
if(switchStack.isEmpty()){break}
}
switchStack.push(currentcharacter);
}
//如果遇到了右括号
else if(currentcharacter==')') {
console.log('遇到了右括号')
while (switchStack.peek() != '(') {
let temp: string = String(switchStack.pop());
queue.enqueue(temp);
}
//遇到左括号时直接将左括号弹出
switchStack.pop()
// console.log('左括号出栈')
}
if(flaglog){
i = i+2;
flaglog = false;
}
else if(flagtriangle){
i = i+3;
flagtriangle = false;
}
else {
i++;
}
console.log('现在的i为:'+i);
}
//最后将剩余的操作符全部放到队列中
while(!switchStack.isEmpty()) {
queue.enqueue(String(switchStack.pop()))
}
queue.print_queue()
return queue;
}
完成代码
- 完整代码已经上传至github,大家有需要可以自取,下面是所在库链接:
鸿蒙开发科学计算器