Android小项目——仿iPhone计算器


计算器

  • 前言
  • 界面设计
    • 界面展示
    • 整体框架
    • 按钮布局
    • 应用名字图标
  • MainActivity
  • 核心算法
    • 中缀转后缀表达式
    • 后缀表达式计算

前言 一个模仿iPhoneUI的计算器,支持大数运算,带括号的运算。
界面设计 界面展示 Android小项目——仿iPhone计算器
文章图片

Android小项目——仿iPhone计算器
文章图片

如图所示,为该计算器UI界面。主要采用LinearLayout布局。并且在其中嵌套LinearLayout
整体框架 【Android小项目——仿iPhone计算器】首先,在整体框架内添加一下几种属性:
  • 设置背景颜色为黑色
  • 定义orientation为垂直布局
  • 设置fitsSystemWindowstrue,让状态栏可改变

这里还设置了一个取消标题栏的效果。只需在AndroidManifest.xml文件内的onCreate()方法内加入以下代码即可
ActionBar actionBar = getSupportActionBar(); //隐藏标题栏 if (actionBar != null) { actionBar.hide(); }

并且,我们发现状态栏也变成了透明色,这里我们直接在setContentView()前添加一段代码即可。代码如下:
if (Build.VERSION.SDK_INT >= 21) {//状态栏透明 View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); getWindow().setStatusBarColor(Color.TRANSPARENT); }

如此整体布局大概完成。
按钮布局 选择在LinearLayout里面嵌套LinearLayout
  • 输入框
    这里输入显示框选择了一个字体自适应的属性。android:autoSizeTextType="uniform感兴趣的朋友可以了解一下,但是一旦使用了这个属性,我们在TextView里设置的hint就会失效。
  • 按钮
    这里的按钮实现了一个点击变色的效果。方法很简单,只需在drawavle文件下,先创建按前和按后的状态的xml文件。接着再新建一个xml文件将这两种状态引入。用android:state_pressed:true控制按钮变色状态。最后将文件引入activity_main并在每一个按钮添加android:background=""将各自的按钮变化引入。
    代码如下:

应用名字图标 修改AndroidMainfest.xml内的android:label="计算器"来修改软件名称。
并且在draeable中添加png图片文件,再回到AndroidMainfest.xml,修改android:icon="@drawable/"引入图片文件即可。
MainActivity 应该考虑到的异常
  • 输入多个0只显示一个
  • .4自动补充为0.4
  • 1(优化为1*(
  • 1.1.1不允许输入第二个小数点
  • 除零异常
  • 右括号未添加提醒
  • 输入-自动显示为(-
  • 4.3优化为4.03
代码如下
package com.example.androidcalculator; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import android.content.DialogInterface; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener {int left_brackets_flag = 0; //左括号数量,与有括号匹配使用 boolean minus_add = false; //负号添加 boolean brackets_add = false; //括号添加 boolean zero = false; //判断0的数量 TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= 21) {//状态栏透明 View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); getWindow().setStatusBarColor(Color.TRANSPARENT); } setContentView(R.layout.activity_main); ActionBar actionBar = getSupportActionBar(); //隐藏标题栏 if (actionBar != null) { actionBar.hide(); } Button button_0 = (Button) findViewById(R.id.button_0); Button button_1 = (Button) findViewById(R.id.button_1); Button button_2 = (Button) findViewById(R.id.button_2); Button button_3 = (Button) findViewById(R.id.button_3); Button button_4 = (Button) findViewById(R.id.button_4); Button button_5 = (Button) findViewById(R.id.button_5); Button button_6 = (Button) findViewById(R.id.button_6); Button button_7 = (Button) findViewById(R.id.button_7); Button button_8 = (Button) findViewById(R.id.button_8); Button button_9 = (Button) findViewById(R.id.button_9); Button button_point = (Button) findViewById(R.id.button_point); Button button_add = (Button) findViewById(R.id.button_add); Button button_mul = (Button) findViewById(R.id.button_mul); Button button_div = (Button) findViewById(R.id.button_div); Button button_minus = (Button) findViewById(R.id.button_minus); Button button_delete = (Button) findViewById(R.id.button_delete); Button button_AC = (Button) findViewById(R.id.button_AC); Button button_equal = (Button) findViewById(R.id.button_equal); Button button_left_bracket = (Button) findViewById(R.id.button_left_bracket); Button button_right_bracket = (Button) findViewById(R.id.button_right_bracket); textView = (TextView) findViewById(R.id.textView); //调用监听器 button_0.setOnClickListener(this); button_1.setOnClickListener(this); button_2.setOnClickListener(this); button_3.setOnClickListener(this); button_4.setOnClickListener(this); button_5.setOnClickListener(this); button_6.setOnClickListener(this); button_7.setOnClickListener(this); button_8.setOnClickListener(this); button_9.setOnClickListener(this); button_point.setOnClickListener(this); button_add.setOnClickListener(this); button_minus.setOnClickListener(this); button_mul.setOnClickListener(this); button_div.setOnClickListener(this); button_equal.setOnClickListener(this); button_add.setOnClickListener(this); button_delete.setOnClickListener(this); button_AC.setOnClickListener(this); button_left_bracket.setOnClickListener(this); button_right_bracket.setOnClickListener(this); }//数字输出 public String figure(int number, String str, boolean zero){ if (str.length() != 0 && str.charAt(str.length() - 1) == ')') str+="*"+number; else { if (zero == true) str=str.substring(0,str.length()-1); str+=number; } return str; }@Override public void onClick(View view) { int number; Character dig = '0'; String str = null; str = textView.getText().toString(); //获取textView里的字符串AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); //警告if (str.length() == 0 || str.charAt(str.length() - 1) != '0') {//判断数字前零的存在。 zero = false; }switch (view.getId()) { case R.id.button_0: if (str.length() == 0 || str.charAt(str.length() - 1) != '/') {//判断除数是否为零 if (str.length() == 0 || (!dig.isDigit(str.charAt(str.length() - 1)) && str.charAt(str.length() - 1) != '.')) { str += "0"; zero = true; } if (zero == false) {//清除多余的0 if (str.length() == 1 && str.charAt(0) == '0') { zero = true; break; } str += "0"; } textView.setText(str); } else { dialog.setTitle("注意!"); dialog.setMessage("除数不能为0!"); dialog.setCancelable(false); dialog.setPositiveButton("OK", new DialogInterface. OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); //dialog. setNegativeButton( "Cancel",new DialogInterface . //OnClickListener( ) { //@Override //public void onClick(DialogInterface dialog,int which) { //} //}); dialog.show(); } break; case R.id.button_1: str = figure(1, str, zero); textView.setText(str); break; case R.id.button_2: str = figure(2, str, zero); textView.setText(str); break; case R.id.button_3: str = figure(3, str, zero); textView.setText(str); break; case R.id.button_4: str = figure(4, str, zero); textView.setText(str); break; case R.id.button_5: str = figure(5, str, zero); textView.setText(str); break; case R.id.button_6: str = figure(6, str, zero); textView.setText(str); break; case R.id.button_7: str = figure(7, str, zero); textView.setText(str); break; case R.id.button_8: str = figure(8, str, zero); textView.setText(str); break; case R.id.button_9: str = figure(9, str, zero); textView.setText(str); break; case R.id.button_point: if (str.length() > 0 && !dig.isDigit(str.charAt(str.length() - 1))) break; if (str.length() == 0) { str += "0."; } else {//遍历字符串,防止输入第二个”.“ int len = str.length() - 1; while (len >= 0 && dig.isDigit(str.charAt(len))) { len--; } if (len >= 0 && str.charAt(len) == '.') { Toast.makeText(MainActivity.this, "不能再添加小数点!", Toast.LENGTH_SHORT).show(); break; } else { str += "."; } } textView.setText(str); break; case R.id.button_add: if(str.length() != 0 && str.charAt(str.length()-1)=='.') str+="0"+"+"; if (str.length() != 0 && (str.charAt(str.length() - 1) == ')' || dig.isDigit(str.charAt(str.length() - 1)))) {//加号补全 if (brackets_add) { str += ")+"; brackets_add = false; } else str += "+"; } textView.setText(str); break; case R.id.button_minus: if(str.length() != 0 && str.charAt(str.length()-1)=='.') str+="0"+"-"; if (str.length() == 0 || str.charAt(str.length() - 1) == '(') { minus_add = true; str += "-"; } else if (str.charAt(str.length() - 1) == ')' || dig.isDigit(str.charAt(str.length() - 1))) { if (brackets_add) { str += ")-"; left_brackets_flag--; brackets_add = false; } else { str += "-"; minus_add = false; } } else if (str.length() - 2 >= 0 && (dig.isDigit(str.charAt(str.length() - 2)) || str.charAt(str.length() - 2) == ')')) {//负数补全 str += "(-"; left_brackets_flag++; brackets_add = true; minus_add = true; } textView.setText(str); break; case R.id.button_mul: if(str.length() != 0 && str.charAt(str.length()-1)=='.') str+="0"+"*"; if (str.length() != 0 && (str.charAt(str.length() - 1) == ')' || dig.isDigit(str.charAt(str.length() - 1)))) { if (brackets_add) { str += ")*"; left_brackets_flag--; brackets_add = false; } else { str += "*"; }} textView.setText(str); break; case R.id.button_div: if(str.length() != 0 && str.charAt(str.length()-1)=='.') str+="0"+"/"; if (str.length() != 0 && (str.charAt(str.length() - 1) == ')' || dig.isDigit(str.charAt(str.length() - 1)))) { if (brackets_add) { str += ")/"; left_brackets_flag--; brackets_add = false; } else str += "/"; } textView.setText(str); break; case R.id.button_equal: if (left_brackets_flag != 0) { dialog.setTitle("注意!"); dialog.setMessage("未添加右括号!"); dialog.setCancelable(false); dialog.setPositiveButton("OK", new DialogInterface. OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); dialog.show(); break; } if (str.length() == 0) break; if (brackets_add) { str += ")"; left_brackets_flag--; brackets_add = false; } String s = InfixToSuffix.Suffix(InfixToSuffix.Infix(str)); textView.setText(s); str = s; break; case R.id.button_left_bracket: if (str.length() == 0) { str += "("; left_brackets_flag++; } else if (str.charAt(str.length() - 1) != ')') { if (!dig.isDigit(str.charAt(str.length() - 1))) str += "("; else { str += "*("; brackets_add = true; } left_brackets_flag++; } textView.setText(str); break; case R.id.button_right_bracket: if (str.length() != 0 && left_brackets_flag > 0 && (str.charAt(str.length() - 1) == ')' || dig.isDigit(str.charAt(str.length() - 1)))) { if (brackets_add) { brackets_add = false; } str += ")"; left_brackets_flag--; } textView.setText(str); break; case R.id.button_AC: str = ""; left_brackets_flag = 0; brackets_add = false; textView.setText(str); break; case R.id.button_delete: if (str.length() == 0 || str.length() == 1) { str = ""; left_brackets_flag = 0; } else { if (str.charAt(str.length() - 1) == '(') { if (brackets_add) brackets_add = false; left_brackets_flag--; } else if (str.charAt(str.length() - 1) == ')') left_brackets_flag++; str = str.substring(0, str.length() - 1); if (str.charAt(str.length() - 1) == '-' && minus_add) str = str.substring(0, str.length() - 1); } textView.setText(str); break; } } }

核心算法 中缀转后缀表达式 首先,初始化一个队列和一个栈,其中队列用于存储结果,栈用来存储运算符,再new一个StringBuilder用来储存数字。
List list= new ArrayList<>(); //储存中间结果结果队列 Stack stack= new Stack<>(); //运算符栈 StringBuilder sb = new StringBuilder(""); //储存数字

遍历字符串
遇到数字直接添加到队列中。
  • 判断多位数:
    先判断第一个字符,不是小数点不是数字不是左括号,直接添加到字符串内。然后判断数字最后一位,如果为操作符或者字符串最后一位那该数字就结束了。
遇到运算符与stack栈顶元素比较优先级
  • 空栈直接入栈
  • 如果优先级比栈顶高,入栈
  • 如果优先级小于栈顶操作符,出栈,继续比较下一个操作符
遇到括号
  • 左括号直接入栈
  • 右括号,一次弹出栈内元素,直到遇到左括号,再删除这对括号即可。
最后输入到队列内即可。
代码如下:
List list = new ArrayList<>(); //储存中间结果结果队列 Stack stack = new Stack<>(); //运算符栈 StringBuilder sb = new StringBuilder(""); //储存数字for (int i = 0; i < str.length(); i++) {// 如果是数字 if ((i == 0 && str.charAt(i) != '(') || (i != 0 && isDigit(str.charAt(i), str.charAt(i - 1)))) { sb.append(str.charAt(i)); // 如果是最后一位 或者下一位是字符,数字添加到队列,sb清空 if (i == str.length() - 1 || (i + 1 < str.length() && isSymbol(str.charAt(i + 1)))) { list.add(sb.toString()); sb = new StringBuilder(""); }// 如果是括号 } else if (isBracket(str.charAt(i))) { // 如果是左括号 直接入栈 if (str.charAt(i) == '(') { stack.push(str.charAt(i)); } else { // 右括号 // 将元素出栈 添加到list直到遇到'(',将这一对 '(' ')' 舍去 char temp; while ((temp = stack.pop()) != '(') { list.add(temp + ""); } }// 如果是操作符 } else if (isOperation(str.charAt(i))) { while (true) { // 空的栈直接入栈 if (stack.isEmpty()) { stack.push(str.charAt(i)); break; // 如果栈顶的符号优先级小于 扫描到的符号 入栈 } else if (getPriority(stack.peek()) < getPriority(str.charAt(i))) { stack.push(str.charAt(i)); break; // 栈顶的符号优先级大于等于 扫描到的符号 出栈给list,并继续扫描栈顶下一个符号 } else { list.add(stack.pop() + ""); } } } }// 将剩余的符号全部入list while (!stack.isEmpty()) { list.add(stack.pop() + ""); } return list; }// 获取优先级 public static int getPriority(char ch) { switch (ch) { case '(': return 0; case '+': case '-': return 1; case '*': case '/': return 2; } throw new RuntimeException("Error"); }//符号判断 public static boolean isSymbol(char ch) { return isOperation(ch) || isBracket(ch); }//操作符 public static boolean isOperation(char ch) { return ch == '+' || ch == '-' || ch == '*' || ch == '/'; }//括号判断 public static boolean isBracket(char ch) { return ch == '(' || ch == ')'; }public static boolean isDigit(char ch, char leftBracket) { // 如果前一个是左括号 右边的数字可能带有正负号 if (leftBracket == '(') { return ch == '-' || ch == '+' || (ch >= 48 && ch <= 57); } // 前一个符号不是左括号只能是数字或小数点 return (ch >= 48 && ch <= 57) || ch == '.'; }

后缀表达式计算 新建一个栈,遍历字符串,遇到运算数进栈,遇到运算符,则在两数之间做运算。
//后缀表达式 public static String Suffix(List suffixExp) {Stack numStack = new Stack<>(); for (String str : suffixExp) { // 如果是操作符 if (str.length() == 1 && isOperation(str.charAt(0))) { BigDecimal num2 = numStack.pop(); BigDecimal num1 = numStack.pop(); numStack.push(calcValueOfTwoNum(num1, num2, str)); } else { numStack.push(new BigDecimal(str)); } } return String.valueOf(numStack.peek()); }//后缀表达式计算 public static BigDecimal calcValueOfTwoNum(BigDecimal num1, BigDecimal num2, String op) { switch (op) { case "+": return num1.add(num2); case "-": return num1.subtract(num2); case "*": return num1.multiply(num2); case "/": if (num2.signum() == 0) { throw new RuntimeException("Error"); } // 除法保留2位小数,四舍五入 return num1.divide(num2, 2, RoundingMode.HALF_UP); } throw new RuntimeException("Error"); }

项目源代码github地址:
Android Calculator

参考文章:
android设置透明状态栏

    推荐阅读