最近突发兴趣,想自己实现一个提供四则混合运算的计算器,心动不如行动,就开始用Android来具体的实现下。但这种小Demo听着简单,实现起来还是需要一些参考。考虑到界面的美观性,我最终选择了小米手机自带的计算器来模仿。
这个计算器的图大概是这样的
以前学习栈的时候,我们知道,要实现一个能进行简单混合运算的计算器功能,需要通过两个栈将中缀表达式转换为前缀表达式(或后缀表达式),而且逻辑也比较复杂。如果对这个有兴趣的可以看我另一篇博客,里面有详细的程序流程图:https://blog.csdn.net/c_o_d_e_/article/details/108774118
在这里我就不讲上述的方法了。我仔细研究过小米计算器,发现它设计的也比较巧妙,每次输入数字都会进行运算一次,这样就不必将运算表达式转换为前/后缀表达式再去操作,省了不少麻烦。我最开始用过两种办法去模仿,都以失败告终(看来悟性不好、算法熟练度太低,人比较笨呀),后面就把前面乱七八糟写在一块的代码全部删除,然后从新开始设计算法。深思熟虑后,发现其实这个计算器的没有那么难。只是我没有想到点子上去。我的思路是:单独写一个工具类,用于处理并计算字符串,并从左到右依次计算然后返回结果。在Acticity的后台代码中就只需要每次输入数字时,就将当前的整个字符串传递给工具类,然后获取值即可。
思路清晰后,写代码就比较轻松,下面是我的工具类代码和部分核心Activity代码,界面布局由于不精通,这里就不弄上来了,后面可以去我的github仓库下载
计算器类Calculator
import java.util.regex.Pattern;
/**
* 实现计算功能的封装类
* 这里的计算只需要考虑从左到右依次计算,且不需考虑括号
*/
public class Calculator {
public static final String PRE = "(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)";//数字及小数校验正则表达式
public static final String PRE_SYMBOL = "[\\+\\-\\×\\÷\\%]";//运算符匹配表达式
/**
* 传入表达式,计算并返回结果
* @param expre 传入的表达式
* @return 计算结果
*/
public static String calculation(String expre){
//分别获取数字和运算符的数组集合
String[] sSymbol = Pattern.compile(PRE).matcher(expre).replaceAll("").split("");
String[] sNum = expre.split(PRE_SYMBOL);
String result = sNum[0];
for (int i = 1; i < sNum.length; i++) {
//这里注意在模拟器上,split后的长度不一定就是0,symbols在split后的长度问题,还没搞懂原因
result = calc(result,sNum[i],sSymbol[i-1]);
}
return result;
}
//计算两个数的结果
public static String calc(String a,String b,String symbol){
double result = 0;
switch (symbol){
case "+":
result = Double.parseDouble(a) + Double.parseDouble(b);break;
case "-":
result = Double.parseDouble(a) - Double.parseDouble(b);break;
case "×":
result = Double.parseDouble(a) * Double.parseDouble(b);break;
case "÷":
result = Double.parseDouble(a) / Double.parseDouble(b);break;
case "%":
if(b.equals("") || b == "")//只有一个数就除以100
result = Double.parseDouble(a) / 100;
else//两个数就取模运算
result = Double.parseDouble(a) % Double.parseDouble(b);
break;
default:
throw new RuntimeException("参数不合法:"+"a="+a+",b="+b+",symbol="+symbol);
}
//如果resutl结果是整数,就去掉.0后缀
return (Math.round(result)-result==0) ? String.valueOf((long)result) : String.valueOf(result);
}
}
Activity类的部分关键代码,主要是提供一种思路
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.regex.Pattern;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//输入的文本内容
private static StringBuilder InputTextContent = new StringBuilder("0");
//显示结果的文本内容
private static StringBuilder resultTextContent = new StringBuilder("=");
//输入内容的文本对象,下面统称为【输入文本框】
TextView inputText;
//计算结果的文本对象,下面统称为【计算结果文本框】
TextView resultText;
public static final String PRE = "(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)";//数字校验正则表达式
public static final String PRE_SYMBOL = "[\\+\\-\\×\\÷\\%]";//运算符匹配表达式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
inputText = (TextView)findViewById(R.id.input_text);
resultText = (TextView)findViewById(R.id.result_text);
bingButton();//绑定按钮点击事件
}
/**
* 按钮及监听事件的绑定
* 共19个按钮
*/
private void bingButton(){
Button button1 = (Button)findViewById(R.id.button1);
button1.setOnClickListener(this);//绑定事件
ImageButton button2 = (ImageButton)findViewById(R.id.button2);
button2.setOnClickListener(this);
Button button3 = (Button)findViewById(R.id.button3);
button3.setOnClickListener(this);
Button button4 = (Button)findViewById(R.id.button4);
button4.setOnClickListener(this);
Button button5 = (Button)findViewById(R.id.button5);
button5.setOnClickListener(this);
Button button6 = (Button)findViewById(R.id.button6);
button6.setOnClickListener(this);
Button button7 = (Button)findViewById(R.id.button7);
button7.setOnClickListener(this);
Button button8 = (Button)findViewById(R.id.button8);
button8.setOnClickListener(this);
Button button9 = (Button)findViewById(R.id.button9);
button9.setOnClickListener(this);
Button button10 = (Button)findViewById(R.id.button10);
button10.setOnClickListener(this);
Button button11 = (Button)findViewById(R.id.button11);
button11.setOnClickListener(this);
Button button12 = (Button)findViewById(R.id.button12);
button12.setOnClickListener(this);
Button button13 = (Button)findViewById(R.id.button13);
button13.setOnClickListener(this);
Button button14 = (Button)findViewById(R.id.button14);
button14.setOnClickListener(this);
Button button15 = (Button)findViewById(R.id.button15);
button15.setOnClickListener(this);
Button button16 = (Button)findViewById(R.id.button16);
button16.setOnClickListener(this);
Button button17 = (Button)findViewById(R.id.button17);
button17.setOnClickListener(this);
Button button18 = (Button)findViewById(R.id.button18);
button18.setOnClickListener(this);
Button button19 = (Button)findViewById(R.id.button19);
button19.setOnClickListener(this);
}
//实现OnClickListener接口的onClick方法,自定义实现其点击事件
@Override
public void onClick(View view) {
switch (view.getResources().getResourceEntryName(view.getId())){
case "button1" : // C
OptionC();
break;
case "button2" : // 回退
back();
break;
case "button3" : // ÷
addAndCalc("÷");
break;
case "button4" : // ×
addAndCalc("×");
break;
case "button5" :// 7
addAndCalc("7");
break;
case "button6" :// 8
addAndCalc("8");
break;
case "button7" :// 9
addAndCalc("9");
break;
case "button8" :// -
addAndCalc("-");
break;
case "button9" :// 4
addAndCalc("4");
break;
case "button10" :// 5
addAndCalc("5");
break;
case "button11" :// 6
addAndCalc("6");
break;
case "button12" :// +
addAndCalc("+");
break;
case "button13" :// 1
addAndCalc("1");
break;
case "button14" :// 2
addAndCalc("2");
break;
case "button15" :// 3
addAndCalc("3");
break;
case "button16" :// %
addAndCalc("%");
break;
case "button17" :// 0
addAndCalc("0");
break;
case "button18" :// .
addAndCalc(".");
break;
case "button19" :// =
// 传入表达式,计算结果,赋给结果文本框对象显示
clickEqual();
break;
default:
break;
}
//添加字符时,要将【输入文本框】字体变大
inputText.setTextSize(60);
inputText.setText(InputTextContent);
}
/**
* 各操作方法的具体实现
*/
//C 清除
public void OptionC(){
InputTextContent = new StringBuilder("0");//清空结果,恢复为初始的0
resultText.setVisibility(View.INVISIBLE);//隐藏计算结果文本框
}
//回退 清除一个数
public void back(){
if(InputTextContent.length()>1){//不止一个字符
InputTextContent.delete(InputTextContent.length()-1,InputTextContent.length());
//获取当前输入的字符前面一个字符
String lastElement = InputTextContent.substring(InputTextContent.length()-1,InputTextContent.length());
//暂未完成
}
else{
InputTextContent = new StringBuilder("0");//删除完后归零
resultText.setVisibility(View.INVISIBLE);//隐藏计算结果文本框
}
}
//点击等号
public void clickEqual(){
if(isCorrect(InputTextContent.toString())){//表达式能够计算就进行计算并显示结果
resultText.setText("="+Calculator.calculation(InputTextContent.toString()));//设置内容
resultText.setVisibility(View.VISIBLE);//设为可见
resultText.setTextSize(60);//设置大小
}
}
//每次添加一个元素到字符串,就对该字符串进行计算
public void addAndCalc(String newNext){
//获取当前输入的字符前面一个字符
String lastElement = InputTextContent.substring(InputTextContent.length()-1,InputTextContent.length());
if(
Pattern.compile(PRE_SYMBOL).matcher(newNext).find()//输入的是符号
&&
(
InputTextContent.length() == 1 && InputTextContent.toString().equals("0")//是第一次输入
||
Pattern.compile(PRE_SYMBOL).matcher(lastElement).find()//输入前的最后一个元素也是符号
)
){
//Nothing...
}else{
if(InputTextContent.length() == 1 && InputTextContent.toString().equals("0")){
//当只有一个数字且为0时,这个0就要被清除掉,从而解决刚开始输入去除0的问题
InputTextContent = new StringBuilder("");
}
//将新的数字添加到尾部
InputTextContent.append(newNext);
//将输入的数据显示到【输入文本框】
inputText.setText(InputTextContent);
//字符串符合计算表达式规则就进行运算
if(isCorrect(InputTextContent.toString())){
//将该字符串计算出来,并显示到【计算结果文本框】
resultText.setText("="+Calculator.calculation(InputTextContent.toString()));
resultText.setVisibility(View.VISIBLE);
}
}
}
//验证表达式是否能够进行计算
public boolean isCorrect(String expre){
String[] symbols = {};
String[] nums = expre.split(PRE_SYMBOL);
if(Pattern.compile(PRE_SYMBOL).matcher(expre).find())//字符串中有符号就进行拆分{
{
symbols = Pattern.compile(PRE).matcher(expre).replaceAll("").split("");
}
//判断规则就是“符号数组比数字数字少1”
if(symbols.length < nums.length)//模拟器上symbols在split后的长度问题,还没搞懂原因
return true;
return false;
}
}
晚上很晚才弄完,代码没时间优化(主要是懒,不想弄了,哈哈),不过思路还是比较简单的。