一.实验内容
1.创建一个新的Android项目,并设置基本的项目结构和属性。
2.设计计算器的用户界面,包括布局和控件的添加。
3.实现计算器的基本功能,如数字输入、运算符选择以及计算结果展示。
二.实验目的
-
学习和掌握Android开发的基础知识,包括Activity、控件和布局等。
-
通过实际编写计算器应用,提高Android应用开发的实践能力。
三.成果展示
默认进入普通计算器,保留三位小数。普通计算器可以处理加、减、乘、除、%等基本运算,按“→”可以撤回操作。将手机翻到侧面,则可以成为科学计算器。科学计算器支持次方、开根号、阶乘、三角函数、十进制转二进制、高精度等高级运算。
点击APP右上角可以弹出菜单,用于修改计算器的字体大小和颜色。
我的计算器
按音量键可以调节计算器的精度(这一部分没有在视频中展示) 。把科学计算器翻回去,在普通计算器中就可以使用相应的精度了。
四.实验过程
1.创建一个新的Android项目,并设置基本的项目结构和属性
点击左上角的第一个选项,选择 File→New→New Project... ,然后选择Empty Views Activity。本次实验我用到的编程语言是java,于是将Language选择为Java。这样,就创建好了一个新的Android项目。
在res目录下的layout文件夹中创建rol.xml文件,然后进行界面的设计。
首先是显示结果的EditText的设计,我选择将layout_height的值设置为150dp,这样显示屏部分和按钮部分的比例就显得刚好,该部分的代码如下:
<EditText
android:textColor="@color/black"
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="150dp"
android:inputType="text"
android:editable="false"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:textSize="24sp"
android:gravity="end"
android:background="@android:color/transparent"
android:padding="20dp"
android:layout_marginBottom="20dp"
android:layout_alignParentTop="true" />
然后是按钮部分,我将整个按钮部分大致分为了5行,每行分为4个小单元格。
rol.xml的完整代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#CCCCCC"
android:padding="16dp">
<EditText
android:textColor="@color/black"
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="150dp"
android:inputType="text"
android:editable="false"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:textSize="24sp"
android:gravity="end"
android:background="@android:color/transparent"
android:padding="20dp"
android:layout_marginBottom="20dp"
android:layout_alignParentTop="true" />
<GridLayout
android:padding="0dp"
android:id="@+id/gridLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/show"
android:layout_marginTop="3dp"
android:columnCount="4"
android:rowCount="5">
<!-- Row 1 -->
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:layout_marginLeft="5dp"
android:id="@+id/AC"
android:layout_height="100dp"
android:layout_width="75dp"
android:tag="AC"
android:text="AC" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/back"
android:layout_marginLeft="5dp"
android:layout_height="100dp"
android:layout_marginBottom="0dp"
android:layout_width="75dp"
android:tag="back"
android:text="←" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/percent"
android:layout_marginLeft="5dp"
android:layout_height="100dp"
android:layout_marginBottom="0dp"
android:layout_width="75dp"
android:tag="%"
android:text="%" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/division"
android:layout_height="100dp"
android:layout_marginBottom="0dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="division"
android:text="÷" />
<!-- Row 2 -->
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/seven"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="7"
android:text="7" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/eight"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="8"
android:text="8" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/nine"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:tag="9"
android:layout_width="75dp"
android:text="9" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/mul"
android:layout_height="100dp"
android:layout_width="75dp"
android:layout_marginLeft="5dp"
android:tag="mul"
android:text="×" />
<!-- Row 3 -->
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/four"
android:layout_height="100dp"
android:layout_width="75dp"
android:layout_marginLeft="5dp"
android:tag="4"
android:text="4" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/five"
android:layout_height="100dp"
android:layout_width="75dp"
android:layout_marginLeft="5dp"
android:tag="5"
android:text="5" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/six"
android:layout_height="100dp"
android:layout_width="75dp"
android:layout_marginLeft="5dp"
android:tag="6"
android:text="6" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/sub"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="sub"
android:text="-" />
<!-- Row 4 -->
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/one"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="1"
android:text="1" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/two"
android:layout_height="100dp"
android:layout_width="75dp"
android:layout_marginLeft="5dp"
android:tag="2"
android:text="2" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/three"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="3"
android:text="3" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/add"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:tag="add"
android:layout_width="75dp"
android:text="+" />
<!-- Row 5 -->
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/zero"
android:layout_width="155dp"
android:layout_height="100dp"
android:layout_columnSpan="2"
android:layout_marginLeft="5dp"
android:tag="0"
android:text="0" />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/dot"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="dot"
android:text="." />
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/eval"
android:layout_height="100dp"
android:layout_marginLeft="5dp"
android:layout_width="75dp"
android:tag="eval"
android:text="=" />
</GridLayout>
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/sin"
android:layout_height="0dp"
android:layout_width="0dp"
android:visibility="gone"
/>
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/power"
android:layout_height="0dp"
android:layout_width="0dp"
android:visibility="gone"
/>
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/factorial"
android:layout_height="0dp"
android:layout_width="0dp"
android:visibility="gone"
/>
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/square"
android:layout_height="0dp"
android:layout_width="0dp"
android:visibility="gone"
/>
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/brackets"
android:layout_height="0dp"
android:layout_width="0dp"
android:visibility="gone"
/>
<Button
android:textColor="@color/black"
android:textSize="20dp"
android:id="@+id/transform"
android:layout_height="0dp"
android:layout_width="0dp"
android:visibility="gone"
/>
</RelativeLayout>
另外在res文件夹下面新建一个layout-land文件夹,用于存放横屏时的xml文件。值得注意的是需要把这个横屏xml文件复制到layout文件夹下,否则有一定概率触发报错。
横屏的row.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<EditText
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textSize="24sp"
android:height="180dp"
android:gravity="end|center_vertical"
android:padding="10dp"
android:background="@android:color/transparent"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:cursorVisible="false"
android:layout_alignParentTop="true"/>
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rowCount="4"
android:columnCount="7"
android:layout_below="@id/show">
<!-- The buttons are organized into a grid with each row containing a set of buttons. -->
<!-- Row 1 for SIN, COS, TAN, LOG, etc. -->
<Button
android:id="@+id/info"
android:textColor="@color/black"
android:text="保留3位"/>
<Button
android:id="@+id/transform"
android:textColor="@color/black"
android:text="D->B" />
<Button
android:textColor="@color/black"
android:id="@+id/one"
android:text="1" />
<Button
android:id="@+id/two"
android:textColor="@color/black"
android:text="2" />
<Button
android:id="@+id/three"
android:textColor="@color/black"
android:text="3" />
<Button
android:id="@+id/division"
android:textColor="@color/black"
android:text="÷" />
<Button
android:id="@+id/AC"
android:textColor="@color/black"
android:text="AC"/>
<!-- Row 2 for numbers and operators -->
<Button android:id="@+id/factorial"
android:textColor="@color/black"
android:text="N!" />
<Button
android:textColor="@color/black"
android:id="@+id/square"
android:text="根号" />
<Button
android:textColor="@color/black"
android:id="@+id/four"
android:text="4"/>
<Button android:textColor="@color/black"
android:id="@+id/five"
android:text="5" />
<Button android:textColor="@color/black"
android:id="@+id/six"
android:text="6" />
<Button android:textColor="@color/black"
android:id="@+id/mul"
android:text="×" />
<Button android:textColor="@color/black"
android:id="@+id/back"
android:text="←"/>
<!-- Repeat rows for other number buttons and operators -->
<!-- Last row for zero, dot, and equals, as well as the transform button -->
<Button
android:id="@+id/sin"
android:textColor="@color/black"
android:text="SIN" />
<Button
android:id="@+id/power"
android:textColor="@color/black"
android:text="^" />
<Button
android:id="@+id/seven"
android:textColor="@color/black"
android:text="7" />
<Button
android:id="@+id/eight"
android:textColor="@color/black"
android:text="8" />
<Button
android:textColor="@color/black"
android:id="@+id/nine"
android:text="9" />
<Button
android:textColor="@color/black"
android:id="@+id/sub"
android:text="-"/>
<Button
android:id="@+id/percent"
android:textColor="@color/black"
android:text="%" />
<Button
android:textColor="@color/black"
android:text="COS" />
<Button
android:textColor="@color/black"
android:text="TAN" />
<Button
android:textColor="@color/black"
android:id="@+id/dot"
android:text="." />
<Button
android:id="@+id/zero"
android:text="0"
android:textColor="@color/black"
android:layout_gravity="fill" />
<Button
android:id="@+id/brackets"
android:textColor="@color/black"
android:text="()" />
<Button
android:id="@+id/add"
android:textColor="@color/black"
android:text="+"/>
<Button
android:id="@+id/eval"
android:textColor="@color/black"
android:text="="/>
<!-- The AC, backspace, and percent buttons could be placed in the appropriate grid positions -->
</GridLayout>
</RelativeLayout>
3.实现计算器的基本功能
1.打开MainActivity.java文件。
为每个按钮控件设置点击事件监听器。
在监听器的回调方法中实现计算逻辑,根据用户输入的数字和运算符进行计算,并将结果显示在控件中。
下面是MainActivity的详细代码:
package com.example.myapplication;
import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
public class MainActivity extends AppCompatActivity {
String formula = "";//公式方程
Boolean zeroMark = true; //允许输入一个0
Boolean zeroCon = false;//允许持续输入0
Boolean docMark = true;// . 可以输入 .
Boolean resultMark = false; //结果是否计算出
Boolean transformMark = false; //是否进行二进制转化
Boolean errorMark = false;
Boolean numAndOpMark = true;
int color = 0xffffffff;
private static final String REGEX = "^\\d+$";//all is num
int precision = 3; //保留小数点位数
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); //屏幕旋转
setContentView(R.layout.col);
EditText show = findViewById(R.id.show);
show.setText("");
show.setSelectAllOnFocus(true); // 设置长按时选择全部
calculator();
}
public void calculator() {
disableShowInput(); // 不让弹出软键盘
EditText show = findViewById(R.id.show);
Button dot = findViewById(R.id.dot);
Button zero = findViewById(R.id.zero);
Button one = findViewById(R.id.one);
Button two = findViewById(R.id.two);
Button three = findViewById(R.id.three);
Button four = findViewById(R.id.four);
Button five = findViewById(R.id.five);
Button six = findViewById(R.id.six);
Button seven = findViewById(R.id.seven);
Button eight = findViewById(R.id.eight);
Button nine = findViewById(R.id.nine);
Button add = findViewById(R.id.add);
Button sub = findViewById(R.id.sub);
Button division = findViewById(R.id.division);
Button back = findViewById(R.id.back);
Button AC = findViewById(R.id.AC);
Button mul = findViewById(R.id.mul);
Button eval = findViewById(R.id.eval);
Button percent = findViewById(R.id.percent);
Button sin = findViewById(R.id.sin);
Button power = findViewById(R.id.power);
Button factorial = findViewById(R.id.factorial); //阶乘
Button square = findViewById(R.id.square); //开根号
Button brackets = findViewById(R.id.brackets);
Button transform = findViewById(R.id.transform);
dot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum(".");
}
});
zero.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("0");
}
});
one.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("1");
}
});
two.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("2");
}
});
three.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("3");
}
});
four.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("4");
}
});
five.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("5");
}
});
six.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("6");
}
});
seven.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("7");
}
});
eight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("8");
}
});
nine.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doNum("9");
}
});
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doOp("+");
}
});
sub.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doOp("-");
}
});
mul.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doOp("×");
}
});
division.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doOp("÷");
}
});
AC.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
reSetStatus();
show.setText("");
}
});
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (formula.length() > 0) {
if (!formula.contains("(D->B)")) {
if (lastStr().equals(".")) {
docMark = true;
}
formula = formula.substring(0, formula.length() - 1);
} else {
formula = formula.replace("(D->B)", "");
numAndOpMark = true;
transformMark = false;
}
show.setText(formula);
show.setTextColor(0xff000000);
}
}
});
eval.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
String result = "";
if (transformMark) { // 先判断是不是要进行二进制转化
doTransform();
return;
}
delLastOp(); // 如果式子是 4- 进行计算,我们打算将-号去掉,结果为4
try {
result = Result.getRes(formula) + ""; //将计算式放入getRes函数中进行计算
} catch (Exception e) {
show.setText(e.getMessage()); //getRes中抛出的异常进行抓取并显示
show.setTextColor(0xffff0000);
errorMark = true;
return;
}
System.out.println(result + "result");
if (result.contains("E") && !result.contains("E-")) {// 结果使用了科学计数法表示,我们要根据情况进行处理
show.setText(result);
Toast toast = Toast.makeText(getApplicationContext(), "数值太大,已用科学计数法表示", Toast.LENGTH_SHORT);
toast.show();
return;
}
// String intPart;
// if (result.length() > 12) {
if (result.contains("E-")) {
BigDecimal decimal = new BigDecimal(result);
result = decimal.toPlainString();
}
if (result.length() > 12) {
result = result.substring(0, 11);
}
result = doPrecision(result); //进行精读修改
show.setText(result);
reSetStatus();
formula = result;
resultMark = true;
docMark = true;
} catch (Exception e) {
e.printStackTrace();
}
}
});
percent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (formula.length() > 0) {
if (!numAndOpMark) {
return;
}
formula = "(" + formula + ")*0.01";
try {
show.setText(doEval(formula));
formula = doEval(formula);
} catch (Exception e) {
show.setText(e.getMessage());
}
}
}
});
sin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!numAndOpMark) {
return;
}
formula = formula + "sin(";
show.setText(formula);
}
});
square.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!numAndOpMark) {
return;
}
formula = formula + "v(";
show.setText(formula);
}
});
power.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!numAndOpMark) {
return;
}
formula = formula + "^";
show.setText(formula);
}
});
factorial.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!numAndOpMark) {
return;
}
try {
if (formula.contains(".")) {
Toast toast = Toast.makeText(getApplicationContext(), "小数不可以阶乘", Toast.LENGTH_SHORT);
toast.show();
return;
}
if (Result.getRes(formula) < 171) {
formula = formula + "!";
show.setText(formula);
} else {
Toast toast = Toast.makeText(getApplicationContext(), "数值太大!", Toast.LENGTH_SHORT);
toast.show();
show.setTextColor(0xffff0000);
resultMark = true;
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
brackets.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!numAndOpMark) {
return;
}
if (lastIsNum() || lastStr().equals(")")) {
formula = formula + ")";
} else {
formula = formula + "(";
}
show.setText(formula);
}
});
transform.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (formula.matches(REGEX)) {
formula = formula + "(D->B)";
show.setText(formula);
transformMark = true;
numAndOpMark = false;
}
}
});
}
public String doPrecision(String result) {
System.out.println(result + "rep");
String tail = result.split("\\.")[1];
String pre = result.split("\\.")[0];
int deltaPre = precision - tail.length();
if (deltaPre > 0) {
for (int i = 0; i < deltaPre; i++) {
result = result + "0";
}
} else if (deltaPre < 0) {
double doubleResult = Double.parseDouble(result);
BigDecimal bigResult = new BigDecimal(doubleResult);
result = bigResult.setScale(precision, BigDecimal.ROUND_HALF_UP).toString();
}
System.out.println(result + "pre");
return result;
}
public void reSetStatus() { //将各个监控状态设为初始状态
EditText show = findViewById(R.id.show);
show.setTextColor(0xff000000);
formula = "";
zeroMark = true;
zeroCon = false;
docMark = true;
resultMark = false;
transformMark = false;
numAndOpMark = true;
}
@SuppressLint("SetTextI18n")
public void doTransform() { //二进制转化方法
EditText show = findViewById(R.id.show);
transformMark = false;
String num = formula.split("\\(")[0];
show.setText(Integer.toBinaryString(Integer.parseInt(num)) + "B");
resultMark = true;
docMark = true;
}
public String lastStr() {
if (formula.length() > 0) {
return formula.charAt(formula.length() - 1) + "";
} else {
return "";
}
} //返回字符的最后一个字符
public String doEval(String formula) throws Exception { //做简单的计算方法
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine se = manager.getEngineByName("js");
return se.eval(formula) + "";
}
public Boolean lastIsNum() { //最后一位是不是数字
if (formula.length() == 0) {
return false;
} else {
char lastChar = formula.charAt(formula.length() - 1);
return lastChar > 47 && lastChar < 58;
}
}
public Boolean lastIsOp() { //最后一位字符是不是操作符
return isOp(lastStr());
}
public Boolean isOp(String str) { //传递的参数是不是操作符
return (str.equals("+") || str.equals("×")
|| str.equals("÷") || str.equals("-"));
}
public void doNum(String num) {
EditText show = findViewById(R.id.show);
show.setTextColor(0xff000000);
int length = formula.length();
if (!numAndOpMark) { //当使用(D->B)后我们不再允许进行任何符号的输入
return;
}
if (errorMark) { //是否出错
reSetStatus();
errorMark = false;
}
if (resultMark) { //结果是否已算出
formula = "";
resultMark = false;
}
if (num.equals(".")) { //输的字符是不是点 .000000000000
if (docMark) { // && !isOp(lastStr())
formula = formula + '.';
show.setText(formula);
docMark = false;
zeroCon = true;
}
return;
}
if (num.equals("0")) { //是不是0? 00000000000
if (zeroCon || zeroMark) {
formula = formula + '0';
}
show.setText(formula);
zeroMark = false;
return;
}
if (formula.startsWith("0") && length == 1) { //0的特殊情况 09 -> 9
formula = "";
}
if (length > 2 && formula.endsWith("0") && isOp(formula.charAt(length - 2) + "")) {
formula = formula.substring(0, length - 1); // 0的特殊情况 23 + 09 -> 23 + 9
}
formula = formula + num; //加入计算式
zeroCon = true;
show.setText(formula);
}
public void doOp(String op) {
EditText show = findViewById(R.id.show);
show.setTextColor(0xff000000);
if (!numAndOpMark || lastStr().equals(".")) {
return;
}
if (errorMark) { //是否有错误 - is ok
reSetStatus();
errorMark = false;
}
if (resultMark) { //结果是否计算出
formula = show.getText().toString();
resultMark = false;
}
if ((lastStr().equals("÷") || lastStr().equals("×")) && op.equals("-")) {
addOp(op);
return;
}
if (formula.contains("×-") || formula.contains("÷-")) {
formula = formula.substring(0, formula.length() - 2);
}
if (formula.length() == 1 && lastStr().equals("-")) { // 只有一个减号,不允许输任何操作符
return;
}
if (formula.length() == 0 || lastStr().equals("(")) { //1+(?=-
if (op.equals("-")) { // 第一个输入的字符是不是-
addOp(op);
}
return;
}
if (lastIsOp() && formula.length() > 0) { //若最后一个字符已经时操作符,那要替换掉旧的替换符
formula = formula.substring(0, formula.length() - 1);
}
addOp(op); //如果没有以上特殊情况,则加到计算式里
}
public void addOp(String op) { // 输入操作符时大部分的重复工作进行封装
EditText show = findViewById(R.id.show);
formula = formula + op;
docMark = true;
zeroMark = true;
zeroCon = false;
show.setText(formula);
}
//==================================================================================
@SuppressLint("SetTextI18n")
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) { //设置精度(通过音量键)
TextView info = findViewById(R.id.info);
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (precision > 1) {
precision--;
info.setText("保留" + precision + "位");
Toast.makeText(this, "保留" + precision + "位", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "至少保留1位", Toast.LENGTH_SHORT).show();
}
break;
case KeyEvent.KEYCODE_VOLUME_UP:
if (precision < 10) {
precision++;
Toast.makeText(this, "保留" + precision + "位", Toast.LENGTH_SHORT).show();
info.setText("保留" + precision + "位");
} else {
Toast.makeText(this, "至多保留10位", Toast.LENGTH_SHORT).show();
}
break;
}
return super.onKeyDown(keyCode, event);
}
@SuppressLint("SetTextI18n")
@Override
public void onConfigurationChanged(Configuration newConfig) { //横竖屏切换时进行设置
@SuppressLint("CutPasteId") EditText show1 = findViewById(R.id.show);
color = show1.getCurrentTextColor();
formula = show1.getText().toString(); //如果你是复制进去的式子,那必须先从show中得到式子
// TODO Auto-generated method stub
super.onConfigurationChanged(newConfig);
//启动时默认是竖屏
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
setContentView(R.layout.col);
@SuppressLint("CutPasteId") EditText show = findViewById(R.id.show);
show.setText(formula);
show.setTextColor(color);
calculator();
}
//切换就是横屏
else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
setContentView(R.layout.row);
@SuppressLint("CutPasteId") EditText show = findViewById(R.id.show);
TextView info = findViewById(R.id.info);
info.setText("保留" + precision + "位");
show.setText(formula);
show.setTextColor(color);
calculator();
}
}
//========================================================================================
@Override
public boolean onCreateOptionsMenu(Menu menu) { //生成菜单项
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
@SuppressLint("NonConstantResourceId")
@Override //菜单项无所谓
public boolean onOptionsItemSelected(MenuItem item) {
EditText show = findViewById(R.id.show);
//先判断点击的是哪个id
switch (item.getItemId()) {
case R.id.font_10:
show.setTextSize(10 * 2);
break;
case R.id.font_12:
show.setTextSize(12 * 2);
break;
case R.id.font_14:
show.setTextSize(14 * 2);
break;
case R.id.font_16:
show.setTextSize(16 * 2);
break;
case R.id.font_18:
show.setTextSize(18 * 2);
break;
case R.id.blue:
show.setTextColor(Color.BLUE);
break;
case R.id.red:
show.setTextColor(Color.RED);
break;
case R.id.green:
show.setTextColor(Color.GREEN);
break;
}
return super.onOptionsItemSelected(item);
}
//======================================================================================
public void disableShowInput() { //设置键盘不可见
EditText show = findViewById(R.id.show);
Class<EditText> cls = EditText.class;
Method method;
try {
method = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
method.setAccessible(true);
method.invoke(show, false);
} catch (Exception e) {//TODO: handle exception
}
try {
method = cls.getMethod("setSoftInputShownOnFocus", boolean.class);
method.setAccessible(true);
method.invoke(show, false);
} catch (Exception e) {//TODO: handle exception
}
}
//=============================================================================
public void delLastOp() { //写个小递归,后来想想好像用不到,但懒得改了
if (isOp(formula.charAt(formula.length() - 1) + "") || lastStr().equals(".")) {
formula = formula.substring(0, formula.length() - 1);
delLastOp();
}
}
}
为了处理计算器的算法逻辑,我在ArrayStack类里面定义了多种运算方式。用数组模拟了正则表达式的运算过程。
package com.example.myapplication;
import android.icu.math.BigDecimal;
import android.util.Log;
class ArrayStack {
private int maxSize; // 栈的大小
private double[] stack; // 数组,数组模拟栈,数据就放在该数组
private int top = -1;// top表示栈顶,初始化为-1
//构造器
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new double[this.maxSize];
}
//增加一个方法,可以返回当前栈顶的值, 但是不是真正的pop
public double peek() {
return stack[top];
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈-push
public void push(double value) {
//先判断栈是否满
if (isFull()) {
System.out.println("表达式错误");
return;
}
top++;
stack[top] = value;
}
//出栈-pop, 将栈顶的数据返回
public double pop() {
//先判断栈是否空
if (isEmpty()) {
//抛出异常
throw new RuntimeException("表达式错误");
}
double value = stack[top];
top--;
return value;
}
//显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
public void list() {
if (isEmpty()) {
System.out.println("表达式错误");
return;
}
//需要从栈顶开始显示数据
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n", i, stack[i]);
}
}
//返回运算符的优先级,优先级是程序员来确定, 优先级使用数字表示
//数字越大,则优先级就越高.
public int priority(int oper) {
if (oper == '*' || oper == '/') {
return 1;
} else if (oper == '+' || oper == '-') {
return 0;
} else if (oper == '^' || oper == '#') {
return 2;
} else if (oper == '!' || oper == 'v' || oper == '%') {
return 3;
} else if (oper == 's' || oper == 't' || oper == 'c') {
return 4;
} else {
return -1; // 假定目前的表达式只有 +, - , * , /
}
}
//判断是不是一个运算符
public boolean isNum(char val) {
return '0' <= val && val <= '9';
}
//判断是不是一个运算符
public boolean isOper(char val) {
return val == '+' || val == '-' || val == '*' || val == '/' || val == '#' || val == '^' || val == '%' || val == 's' || val == 'c' || val == 't' || val == '!' || val == 'v';
}
//判断是不是 (
public boolean isLeftBrackets(char val) {
return val == '(';
}
//判断是不是 )
public boolean isRightBrackets(char val) {
return val == ')';
}
//判断是不是 .
public boolean isPoint(char val) {
return val == '.';
}
//判断是不是 (
public boolean isTrigonometric(char val) {
return val == 's';
}
//计算方法
public double cal(double num1, double num2, int oper) {
double res = 0; // res 用于存放计算的结果
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = sub(num2, num1);// 注意顺序
break;
case '*':
res = num1 * num2;
break;
case '/':
if (num1 == 0) {
throw new RuntimeException("定义域错误");
}
res = num2 / num1;
break;
case '^':
res = Math.pow(num2, num1);
break;
default:
break;
}
return res;
}
/**
* 三角函数运算、阶乘运算或者是开根操作
*
* @param num1
* @param oper
* @return
*/
public double mixedOperation(double num1, int oper) {
double res = 1; // res 用于存放计算的结果
switch (oper) {
case 's':
res = Math.sin(num1 * Math.PI / 180);
break;
case 'c':
res = Math.cos(num1 * Math.PI / 180);
break;
case 't':
res = Math.tan(num1 * Math.PI / 180);
break;
case '!':
for (int i = 1; i <= num1; i++) {
res *= i;
}
break;
case 'v':
if (num1 < 0) {
throw new RuntimeException("定义域错误");
}
res = Math.sqrt(num1);
break;
case '%':
res = num1 / 100;
break;
case '#':
res = -num1;
break;
default:
break;
}
return res;
}
double sub(double x, double y) {
BigDecimal initResult = new BigDecimal(x - y); //计算初步结果
String temp = initResult.toString();
if (temp.contains(".")) {
temp = temp + "00000000000000000000";
} else {
temp = temp + ".00000000000000000000";
}
int lengthOfInt = getLengthOfInt(x, y); //结果整形位数
int lengthOfDec = Math.max(getLengthOfDecimal(x), getLengthOfDecimal(y));
int count = lengthOfInt + lengthOfDec + 2; //最后输出的位数 = 整形位数+小数位数+小数点位数+1(四舍五入舍去)
double tempResult = Double.parseDouble(temp.substring(0, count)); //将String转换成double,并截取多一位用于四舍五入
double power = Math.pow(10, lengthOfDec); //求10的几次方用来四舍五入
double finalResult = (double) (Math.round(tempResult * power)) / power; //四舍五入
Log.i("result", x + "-" + y + "=" + finalResult);//此处的长度取决于小数点后面位数大的
return finalResult;
}
int getLengthOfInt(double x, double y) {
int lengthOfInt;
int intX = (int) x; //x的整数部分
int intY = (int) y; //y的整数部分
lengthOfInt = String.valueOf(intX - intY).length(); //结果整形部分的位数 = 相减后整形的位数
return lengthOfInt;
}
int getLengthOfDecimal(double x) {
return String.valueOf(x).split("\\.")[1].length(); //计算x小数点后面的位数
}
}
另一个Result.java的代码如下:
package com.example.myapplication;
public class Result {
public static double getRes(String expression) {
System.out.println(expression.length());
expression = expression.replace("sin", "s");
expression = expression.replace("÷", "/");
expression = expression.replace("×", "*");
ArrayStack stack = new ArrayStack(20);
String exp = "";
int count = 0;
char[] chars = expression.toCharArray();
while (count < chars.length) {
if (count == 0 && chars[count] == '-') {
chars[count] = '#';
}
if (count >= 1 && chars[count] == '-' && chars[count - 1] == '(') {
chars[count] = '#';
}
if (count >= 1 && chars[count] == '-' && stack.priority(chars[count - 1]) > stack.priority(chars[count])) {
chars[count] = '#';
}
exp += chars[count];
count++;
}
expression = exp;
//创建两个栈,数栈,一个符号栈
ArrayStack numStack = new ArrayStack(20);
ArrayStack operatorStack = new ArrayStack(20);
//定义需要的相关变量
int index = 0;//用于扫描
double num1 = 0;
double num2 = 0;
int oper = 0;
double res = 0;
char ch = ' '; //将每次扫描得到char保存到ch
String keepNum = ""; //用于拼接 多位数
//开始while循环的扫描expression
while (true) {
//依次得到expression 的每一个字符
ch = expression.substring(index, index + 1).charAt(0);
//判断ch是什么,然后做相应的处理
if (operatorStack.isLeftBrackets(ch)) { //如果是左括号
//直接入符号栈..
operatorStack.push(ch);
} else if (operatorStack.isRightBrackets(ch)) { //如果是右括号
while (operatorStack.peek() != '(') { //循环直到能够到达 '(' 处
oper = (int) operatorStack.pop();
if (oper == 's' ||
oper == '!' ||
oper == 'v' ||
oper == '#' ||
oper == '%') { //如果栈顶为三角函数、阶乘或者是开根
num1 = numStack.pop();
res = numStack.mixedOperation(num1, oper);
//把运算的结果如数栈
numStack.push(res);
} else {
num1 = numStack.pop();
num2 = numStack.pop();
res = operatorStack.cal(num1, num2, oper);
//把运算的结果如数栈
numStack.push(res);
}
}
//然后将当前栈顶的操作符出符号栈----其实也就是 '(' 出栈
operatorStack.pop();
} else if (operatorStack.isOper(ch)) { //如果是运算符
if (operatorStack.isEmpty() || operatorStack.peek() == '(') {
//如果符号栈为空,或者栈顶为'('
operatorStack.push(ch);
} else if (operatorStack.priority(ch) > operatorStack.priority((int) operatorStack.peek())) {
//如果当前优先级比栈顶优先级高,直接入符号栈.
operatorStack.push(ch);
} else {
while (!operatorStack.isEmpty() &&
(operatorStack.priority(ch) <= operatorStack.priority((int) operatorStack.peek()))) {
oper = (int) operatorStack.pop();
if (!operatorStack.isEmpty() && (operatorStack.peek() == '!' ||
operatorStack.peek() == 's' ||
operatorStack.peek() == 'v' ||
operatorStack.peek() == '#' ||
operatorStack.peek() == '%'
)) {
num1 = numStack.pop();
res = numStack.mixedOperation(num1, oper);
numStack.push(res);//入栈
} else {
if (oper == 's' ||
oper == '!' ||
oper == 'v' ||
oper == '#' ||
oper == '%') { //如果栈顶为三角函数、阶乘或者是开根
num1 = numStack.pop();
res = numStack.mixedOperation(num1, oper);
//把运算的结果如数栈
numStack.push(res);
} else {
num1 = numStack.pop();
num2 = numStack.pop();
res = numStack.cal(num1, num2, oper);
//把运算的结果如数栈
numStack.push(res);
}
}
}
//然后将当前的操作符入符号栈
operatorStack.push(ch);
}
} else if (operatorStack.isPoint(ch) || operatorStack.isNum(ch)) { //如果是数或者是小数点,则直接入数栈
//numStack.push(ch - 48); // "1+3" '1' => 1
//1. 当处理多位数时,不能发现是一个数就立即入栈,因为可能是多位数
//2. 在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
//3. 因此我们需要定义一个变量 字符串,用于拼接
//处理多位数
keepNum += ch;
//如果ch已经是expression的最后一位,就直接入栈
if (index == expression.length() - 1) {
numStack.push(Double.parseDouble(keepNum));
} else {
//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
//注意是看后一位,不是index++
if (operatorStack.isOper(expression.substring(index + 1, index + 2).charAt(0))
|| operatorStack.isRightBrackets(expression.substring(index + 1, index + 2).charAt(0))) {
//如果后一位是运算符,则入栈 keepNum = "1" 或者 "123"
numStack.push(Double.parseDouble(keepNum));
//最后keepNum必须清空
keepNum = "";
}
}
} else if (!operatorStack.isEmpty() && (operatorStack.peek() == 's' ||
operatorStack.peek() == '!' ||
operatorStack.peek() == 'v' ||
operatorStack.peek() == '#' ||
operatorStack.peek() == '%')) { //如果栈顶为三角函数算式、开根或者是阶乘
oper = (int) operatorStack.pop();
num1 = numStack.pop();
res = numStack.mixedOperation(num1, oper);
//把运算的结果入数栈
numStack.push(res);
}
//让index + 1, 并判断是否扫描到expression最后.
index++;
if (index >= expression.length()) {
break;
}
}
//当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
while (true) {
//如果符号栈为空,则计算到最后的结果, 数栈中只有一个数字【结果】
if (operatorStack.isEmpty()) {
break;
}
if (operatorStack.peek() == 's' ||
operatorStack.peek() == '!' ||
operatorStack.peek() == 'v' ||
operatorStack.peek() == '#' ||
operatorStack.peek() == '%') { //如果栈顶为三角函数算式
oper = (int) operatorStack.pop();
num1 = numStack.pop();
res = numStack.mixedOperation(num1, oper);
numStack.push(res);//入栈
} else {
num1 = numStack.pop();
num2 = numStack.pop();
oper = (int) operatorStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);//入栈
}
}
//将数栈的最后数,pop出,就是结果
double res2 = numStack.pop();
System.out.println(res2 + "ll");
return res2;
}
}
五.实验总结
- 通过本次实验,我对于Activity、控件和布局等上课学过的知识有了更加深刻的理解,并掌握了简单项目的编写思路、编写步骤和调错步骤。相比我之前做过的计算器,这个实验让我对各种知识的运用更加炉火纯青。我将以前项目的一些技术搬运到这个实验中,让功能更加完整。为了防止在各种运算时出现bug,我在程序中增加了许多Exception的判定,计算中就没有出现计算崩溃的现象了。负数开根号、小数的阶乘之类的不合法操作都会弹出Toast。除以0、运算符不匹配则会显示定义错误。
3.学习中遇到的问题及解决
- 问题1:打开APP直接闪退
- 问题1解决方案:我尝试分析了logcat信息,发现软件崩溃了原因是空指针报错,原来是sync的时候检查机制比较弱,很难发现横屏缺少了竖屏的组件,而我把两个布局的代码都写在一起了。于是我在竖屏的界面编写了横屏的控件并把它们视为不可见。
- 问题2:如果不调用其他类,则处理输入的字符串以及对其进行计算比较困难
- 问题2解决方案:使用js运算引擎
- 问题3:横竖屏转换之后闪退bug。第一次修复之后竖屏转横屏没问题,但横屏转竖屏继续出现闪退bug。
- 问题3解决方案:在manifest.xml里声明一下就好了,然后重写一下onConfigurationChanged的内容,让程序不走销毁activity再oncreate的逻辑。现在横竖屏可以很丝滑的切换。
- 问题4:计算后,没有提供查找历史记录的功能
- 问题4解决方案:不影响基本功能,暂时不做改进了
4.学习感悟、思考等
通过这次实验,我更加深刻地理解了编程的本质:不断学习、实践、解决问题,并从中吸取经验。每一次的挑战都是成长的机会,每一个问题的解决都让我更加熟练地运用编程技能。这些经验将对我未来的编程旅程有着不可估量的价值。