计算器
- 前言
- 界面设计
-
- 界面展示
- 整体框架
- 按钮布局
- 应用名字图标
- MainActivity
- 核心算法
-
- 中缀转后缀表达式
- 后缀表达式计算
前言 一个模仿iPhoneUI的计算器,支持大数运算,带括号的运算。
界面设计 界面展示
文章图片
文章图片
如图所示,为该计算器UI界面。主要采用
LinearLayout
布局。并且在其中嵌套LinearLayout
。整体框架 【Android小项目——仿iPhone计算器】首先,在整体框架内添加一下几种属性:
- 设置背景颜色为黑色
- 定义
orientation
为垂直布局 - 设置
fitsSystemWindows
为true
,让状态栏可改变
这里还设置了一个取消标题栏的效果。只需在
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("");
//储存数字
遍历字符串
遇到数字直接添加到队列中。
- 判断多位数:
先判断第一个字符,不是小数点不是数字不是左括号,直接添加到字符串内。然后判断数字最后一位,如果为操作符或者字符串最后一位那该数字就结束了。
- 空栈直接入栈
- 如果优先级比栈顶高,入栈
- 如果优先级小于栈顶操作符,出栈,继续比较下一个操作符
- 左括号直接入栈
- 右括号,一次弹出栈内元素,直到遇到左括号,再删除这对括号即可。
代码如下:
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设置透明状态栏
推荐阅读
- 笔记|Android开发实战——计算器
- Android|Android——一个神奇的计算器APP
- 要把微博、贴吧变成即时聊天,总共分几步()
- Android|手机网络连接类型判断 2/3/4G网络 WiFi和无网络 工具类
- ui|广工工作室各方向学习指南
- Android|2022-02-26 AndroidR 11 调用文件管理器并返回选中文件的路径
- java|Meta元宇宙OS要黄(300人研发团队解散,关闭VR/AR操作系统研发)
- android|android 系统升级 方法,安卓系统怎么升级 浅谈安卓系统更新升级的几种方法