安卓计算器

最近刚好在上移动互联网开发的课,课上老师布置了一道题,自己实现一个计算器,能满足基本的四则混合运算。布局用GridLayout

image.png

  • Java代码
代码语言:javascript
复制
package com.cqupt.calculator;

import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import java.util.Locale;
import java.util.Stack;

public class MainActivity extends AppCompatActivity {
private Button[] btn;//计算器所有按钮,一共28个
private TextView ioView;//输入和输出共用框
private TextView expressionOutputView;//表达式显示框
private String expression = "";//运算的表达式
private String input = "0";//当前输入的值

//初始化所有按钮,输入输出结果框变量
private void initial() {
    btn = new Button[18];

    //关联界面中的部件
    ioView = (TextView) findViewById(R.id.input_outputView);
    expressionOutputView = (TextView) findViewById(R.id.expressionOutputView);

    ioView.setText("0");//输入输出框初始值为0,表达式框为空

    //数字0-9
    btn[0] = (Button) findViewById(R.id.btn_0);
    btn[1] = (Button) findViewById(R.id.btn_1);
    btn[2] = (Button) findViewById(R.id.btn_2);
    btn[3] = (Button) findViewById(R.id.btn_3);
    btn[4] = (Button) findViewById(R.id.btn_4);
    btn[5] = (Button) findViewById(R.id.btn_5);
    btn[6] = (Button) findViewById(R.id.btn_6);
    btn[7] = (Button) findViewById(R.id.btn_7);
    btn[8] = (Button) findViewById(R.id.btn_8);
    btn[9] = (Button) findViewById(R.id.btn_9);
    btn[10] = (Button) findViewById(R.id.btn_dot);//小数点
    btn[11] = (Button) findViewById(R.id.btn_equal);//等于号
    btn[12] = (Button) findViewById(R.id.btn_add);//加法
    btn[13] = (Button) findViewById(R.id.btn_sub);//减法
    btn[14] = (Button) findViewById(R.id.btn_mul);//乘法
    btn[15] = (Button) findViewById(R.id.btn_div);//除法

// btn[16] = (Button) findViewById(R.id.btn_sign);//正负号
// btn[17] = (Button) findViewById(R.id.btn_sqrt);//开根
// btn[18] = (Button) findViewById(R.id.btn_mod);//取模
// btn[19] = (Button) findViewById(R.id.btn_rec);// 1/x
btn[16] = (Button) findViewById(R.id.btn_ce);// del键 btn_delete
// btn[21] = (Button) findViewById(R.id.btn_ce);// CE键
btn[17] = (Button) findViewById(R.id.btn_c);// C键
// btn[23] = (Button) findViewById(R.id.btn_mc);// MC键
// btn[24] = (Button) findViewById(R.id.btn_mr);// MR键
// btn[25] = (Button) findViewById(R.id.btn_ms);// MS键
// btn[26] = (Button) findViewById(R.id.btn_madd);// M+键
// btn[27] = (Button) findViewById(R.id.btn_msub);// M-键

    MyOnClickHandler myOnClickHandler = new MyOnClickHandler();//按钮点击事件处理类

    //为每个按钮设置监听事件
    for (Button i : btn)
        i.setOnClickListener(myOnClickHandler);

    //当MS键未被按下的之前,M+键和M-键都不可点击

// btn[23].setClickable(false);//M+
// btn[24].setClickable(false);//M-
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //初始化各个变量
    initial();
}

/*
 * 按钮点击事件处理类
 * 内部成员函数:
 * void onClick(View),点击事件调用的主函数,同时处理计算过程中的异常
 * double getRet(double,double,char),根据某个运算符计算出两个数字的结果,并返回结果
 * int someOfStr_in_anotherStr(String,String,int),从int参数位置开始查找,返回某个字符串中的任意字符在另一个字符串中第一次出现的位置
 * void calculate(void),遍历表达式字符串,根据算法计算出结果。同时处理计算过程中的异常
 * String stripTrailingZeros(String),去除小数转化的字符串后面无效的零
 * */
private class MyOnClickHandler implements View.OnClickListener {
    private String num_str = "0123456789.";//11个运算符
    private String operator = "+-*/%#";//5个运算符+1个结束符
    private double saveNum = 0.0;//保存的值
    private final String ExpressionError = "表达式错误!";//表达时错误时的报错信息
    private final String DivByZeroError = "除数不能为零!";//除数为零时的报错信息
    private final String ModByInt = "不能对小数或负数进行取余数操作!";//当对小数进行取模运算报错信息
    private final String SqrtByNegative = "不能对负数开方!";//当对负数进行开方运算保存信息
    private boolean setZero = false;//判断表达式是否需要清零,一旦使用“=”号,就需要清空表达式
    private boolean resetInputZero = false;//判断输入框是否需要清零

    //运算符优先级表。
    // 第一维度表示先出现的运算符(栈顶元素),第二维度表示后出现(当前读入)的运算符。-1代表低于,1代表高于,0代表相等
    //运算符集合分别为"+-*/%#"
    private int[][] compare = {
            {-1, -1, 1, 1, 1, -1},         //栈顶元素为+
            {-1, -1, 1, 1, 1, -1},         //栈顶元素为-
            {-1, -1, -1, -1, -1, -1},      //栈顶元素为*
            {-1, -1, -1, -1, -1, -1},      //栈顶元素为/
            {-1, -1, -1, -1, -1, -1},      //栈顶元素为%
            {1, 1, 1, 1, 1, 0}             //栈顶元素为#
    };

    //根据某个运算符计算出两个数字的结果,并返回结果
    private double getRet(double a, double b, char c) throws Exception {
        double ret = 0;//保存结果
        switch (c) {//运算符
            case '+': {//加法
                ret = a + b;
                break;
            }
            case '-': {//减法
                ret = a - b;
                break;
            }
            case '*': {//乘法
                ret = a * b;
                break;
            }
            case '/': {//除法
                if (b == 0)
                    throw new Exception(DivByZeroError);
                ret = a / b;
                break;
            }
            case '%': {//取余数
                if (b == 0)//如果除数为零
                    throw new Exception(DivByZeroError);
                if ((a != (int) a || b != (int) b) || (a < 0 || b < 0))//如果对小数或负数进行取模操作
                    throw new Exception(ModByInt);
                ret = a - ((int) (a / b)) * b;
            }
        }
        return ret;//返回结果
    }

    //从int参数位置开始查找,返回someStr字符串中的任意字符在anotherStr字符串中第一次出现的位置
    private int someOfStr_in_anotherStr(String someStr, String anotherStr, int startIndex) {
        int index = anotherStr.length();//某个字符串中任意字符在另一个字符串中第一次出现的位置,初始值是另一个字符串的长度
        int temp;//每次查找的结果
        for (int i = 0; i < someStr.length(); i++) {
            temp = anotherStr.indexOf(someStr.charAt(i), startIndex);//每次查找的结果
            if (temp != -1 && temp < index)//如果查找成功,并且出现的位置更靠前,替换之前的值
                index = temp;
        }
        if (index == anotherStr.length())//如果还是初始值,显然查找失败,返回-1
            index = -1;
        return index;
    }

    //遍历表达式字符串,根据算法计算出结果
    private void calculate() throws Exception {
        Stack<Character> opStack = new Stack<>();//运算符存储栈
        Stack<Double> numStack = new Stack<>();//运算数字存储栈
        char nowOption;//当前正在读入的运算符
        char topOption;//当前运算符栈中栈顶的运算符
        int start = 0;//表达式的新起始点,它之前的表达式已经使用过,不再使用
        int end;//表达式中任意运算符位置,end和start中间的就是数字
        int compareResult;//两个运算符优先级比较的结果

        opStack.add('#');//先在运算符存储栈中放入一个结束符,当两个结束符相遇,计算正常结束
        String tempExpression = expression + "#";//将另一个结束符加入表达式最后

        while (start < tempExpression.length()) {//表达式新起点已经到表达式结尾,说明表达式读取完毕
            //在表达式中找到最近的一个运算符的位置,运算符前的字符一般来说,就是数字了(比较特殊的就是负数了)
            end = someOfStr_in_anotherStr(operator, tempExpression, start);

            //如果两个运算符之间没有任何字符,也许是负数,需要进行进一步判断
            if (end - start < 1) {//如果两个运算符之间没有数字,判断一下是否是负号
                if (tempExpression.charAt(end) == '-') {//如果是负号,重新读取后面一个运算符,就可以将这个负数一次性读出来
                    start = end;//负数的开始位置
                    end = someOfStr_in_anotherStr(operator, tempExpression, start + 1);//获得后一个运算符位置
                } else
                    throw new Exception(ExpressionError);//否则,必然是表达式有问题
            }

            //获得两个运算符之间的数字,如果生成的double类型数字出现问题,说明表达式有问题
            try {
                numStack.add(Double.valueOf(tempExpression.substring(start, end)));
            } catch (Exception e) {
                throw new Exception(ExpressionError);
            }

            start = end + 1;//表达式新的起始点

            nowOption = tempExpression.charAt(end);//当前读入的运算符

            do {
                topOption = opStack.peek();//运算符栈中栈顶的运算符

                //运算符栈顶运算符与当前读取的运算符优先级的比较结果。-1代表低于,1代表高于,0代表相等
                compareResult = compare[operator.indexOf(topOption)][operator.indexOf(nowOption)];

                if (compareResult > 0) {//如果当前运算符优先级高于运算符栈中栈顶运算符优先级,当前运算符入栈,继续读取表达式下一个字符
                    opStack.add(nowOption);
                    break;
                } else if (compareResult < 0) {//如果低于,将前面的(优先级高的)表达式先运算出结果,再往后计算

                    if (numStack.size() < 2)//如果此时数字栈中数字少于两个,显然表达式错误,连续出现了多个运算符
                        throw new Exception(ExpressionError);

                    double b = numStack.peek();//第二个数字
                    numStack.pop();
                    double a = numStack.peek();//第一个数字
                    numStack.pop();

                    double ret = getRet(a, b, topOption);//两个数字的运算结果
                    numStack.add(ret);//将结果数字压入数字栈中
                }
                opStack.pop();//丢弃当前栈顶运算符,已经使用过了
            } while (compareResult != 0);//两个运算符优先级比较结果为0,说明两个结束符相遇,计算正常完成
        }

        //如果到最后,运算符栈不为空,或者数字栈有超过一个值,说明表达式有误
        if (numStack.size() == 1 && opStack.empty()) {
            input = stripTrailingZeros(String.format(Locale.getDefault(), "%f", numStack.peek()));//运算结果存储
        } else
            throw new Exception(ExpressionError);
    }

    private String stripTrailingZeros(String input) {
        int index = input.length();
        if (input.contains(".")) {
            for (int i = input.length() - 1; i > -1; i--) {
                if (input.charAt(i) != '0') {
                    index = i + 1;
                    break;
                }
            }
            if (input.charAt(index - 1) == '.')
                index -= 1;
        }
        return input.substring(0, index);
    }

    //点击事件调用的主函数,同时处理计算过程中的异常
    @Override
    public void onClick(View view) {
        String current = ((Button) view).getText().toString();//获得当前输入的字符

        try {
            /*
             *将输入的字符分为三类:数字或小数点(数字类),运算符(+-*%/),其他操作符(倒数,正负号,等号)
             * 数字类的输入,直接加入输入框
             * 运算符输入,将之前输入框的数字字符和当前的运算符一起加入表达式,并清空输入框,等待下一次输入
             * 如果输入框不为零,并且当前按下运算符,如+-*%,说明计算需要当前输入框的值,那么不清零
             * */

            //如果输入内容是数字或小数点,直接加入表达式字符串
            if (num_str.contains(current)) {

                //是否需要将输入框重置为零
                if (resetInputZero) {
                    input = "0";//将输入框重置为零
                    resetInputZero = false;//不需要将输入框重置为零
                }

                //输入框默认值为零。如果当前输入框是零,应该用新输入的数字替换初始值零。
                //其后的输入内容直接累加到输入框中。
                //但是如果在零状态下输入小数点,应该累加。
                if (input.equals("0") && !current.equals(".")) {
                    input = current;
                } else if (!current.equals(".") || !input.contains(".")) {
                    input += current;
                }
            } else if (operator.contains(current)) {//如果输入的是运算符
                //如果输入框中没有数字或小数点,说明是报错信息,不可以直接点击运算符。应该清零输入框。
                if (someOfStr_in_anotherStr(num_str, input, 0) == -1) {
                    input = "0";
                }
                //一旦点击运算符,那么就不需要将输入框清零,因为输入框的值会参与后续运算
                resetInputZero = false;

                //如果输入框结果不为零,将输入框的结果加上运算符一起存入表达式中
                //输入框的结果可能不是很合理,需要处理以后再加入表达式。如,小数结尾多余的零
                if (!input.equals("0")) {
                    expression += stripTrailingZeros(input) + current;//将input输入字符串的内容存到表达式字符串中
                    input = "0";//将input置零
                }
            } else {//输入的是既不是运算符也不是数字,是其他操作符
                if (someOfStr_in_anotherStr(num_str, input, 0) == -1) {
                    input = "0";
                }
                switch (current)//12个运算符
                {
                    case "=": {//等号
                        //如果输入框不为零(防止表达式出错),才加入表达式
                        //同时,表达式中必须有操作符存在,才进行表达式求解
                        if (!input.equals("0") && someOfStr_in_anotherStr(operator, expression, 0) != -1) {

                            expression += stripTrailingZeros(input);//将最后输入的内容加入表达式
                            calculate();//对表达式求解
                            expression += "=";//显示结果的时候,多显示个等号,感觉比较友好

                            //进行等号运算后,表达式和输入框都可以考虑清零
                            setZero = true;
                            resetInputZero = true;
                        }
                        break;
                    }
                    case "+/-": {//正负号
                        //输入框的值为初始值零,那么这些操作没有任何意义
                        if (!input.equals("0")) {
                            //获取输入框的值,转化为小数后,去除小数后面无效的零
                            //Windows计算器似乎认为,负数不需要将输入框清零
                            input = stripTrailingZeros(String.format(Locale.getDefault(), "%f", -Double.valueOf(input)));
                        }
                        break;
                    }
                    case "sqrt": {//开根
                        if (!input.equals("0")) {
                            if (Double.valueOf(input) < 0)//负数不能开方运算
                                throw new Exception(SqrtByNegative);
                            input = stripTrailingZeros(String.format(Locale.getDefault(), "%f", Math.sqrt(Double.valueOf(input))));
                            resetInputZero = true;//输入框需要置零
                        }
                        break;
                    }
                    case "1/x": {//倒数
                        if (!input.equals("0")) {
                            input = stripTrailingZeros(String.format(Locale.getDefault(), "%f", 1 / Double.valueOf(input)));
                            resetInputZero = true;//输入框需要置零
                        }
                        break;
                    }
                    case "C": {//清空表达式和输入框
                        expression = "";
                        input = "0";
                        break;
                    }
                    case "CE": {//清除输入框
                        input = "0";
                        break;
                    }
                    case "Del": {//删除输入框的一个字符
                        if (input.length() == 1)//当输入框剩最后一个字符,还要删除,就恢复初始值零
                            input = "0";
                        if (!input.equals("0")) {//如果输入框不是零,那么删掉它最后一位
                            input = input.substring(0, input.length() - 1);
                        }
                        break;
                    }
                    case "MC": {//清除保存的值
                        if(saveNum!=0) {
                            saveNum = 0.0;//保存值归零
                            //清除保存值后,M+和M-不可用了
                            btn[23].setClickable(false);
                            btn[24].setClickable(false);
                        }
                        break;
                    }
                    case "MR": {//将输入框input的值替换为保存的值
                        if (saveNum != 0) {
                            input = stripTrailingZeros(String.format(Locale.getDefault(), "%f", saveNum));
                            resetInputZero = true;//输入框需要置零
                        }
                        break;
                    }
                    case "MS": {//保存当前输入框input的值
                        if(someOfStr_in_anotherStr(num_str,input,0)!=-1) {
                            saveNum = Double.valueOf(input);

                            //M+和M-可用了
                            btn[23].setClickable(true);
                            btn[24].setClickable(true);

                            resetInputZero = true;//输入框需要置零
                        }
                        break;
                    }
                    case "M+": {//保存值+=当前输入框的值
                        saveNum += Double.valueOf(input);
                        resetInputZero = true;//输入框需要置零
                        break;
                    }
                    case "M-": {//保存值-=当前输入框的值
                        saveNum -= Double.valueOf(input);
                        resetInputZero = true;//输入框需要置零
                        break;
                    }
                }
            }
        } catch (Exception e) {
            //如果计算时报错了,清空表达式,输出报错信息
            expression = "";
            input = e.getLocalizedMessage();
            resetInputZero = true;//输入框清零
        }

        //显示表达式和当前输入的值
        expressionOutputView.setText(expression);
        ioView.setText(input);

        //如果已经进行等号运算,清空表达式
        if (setZero) {
            expression = "";
            setZero = false;//不需要清空表达式
        }
    }
}

}

  • 布局文件
代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="7"
android:columnCount="4"
tools:context=".MainActivity">
<!--6行4列
实现占满整个屏幕-->
<TextView
android:id="@+id/expressionOutputView"
android:layout_columnSpan="4"
android:layout_gravity="fill_horizontal"
android:layout_rowWeight="2"
android:text="0"
android:textSize="50sp"
/>

&lt;TextView
    android:id=&#34;@+id/input_outputView&#34;
    android:layout_columnSpan=&#34;4&#34;
    android:layout_gravity=&#34;fill_horizontal&#34;
    android:layout_rowWeight=&#34;2&#34;
    android:text=&#34;0&#34;
    android:textSize=&#34;50sp&#34; /&gt;
&lt;!--跨四列 自动填充 权重2--&gt;
&lt;Button
    android:id=&#34;@+id/btn_c&#34;
    android:text=&#34;clear&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;
    android:textColor=&#34;#E91E63&#34;/&gt;
&lt;Button
    android:id=&#34;@+id/btn_ce&#34;
    android:text=&#34;CE&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;
&lt;Button
    android:id=&#34;@+id/btn_div&#34;
    android:text=&#34;/&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;
&lt;Button
    android:id=&#34;@+id/btn_mul&#34;
    android:text=&#34;*&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;
&lt;Button
    android:id=&#34;@+id/btn_1&#34;
    android:text=&#34;1&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;
&lt;Button
    android:id=&#34;@+id/btn_2&#34;
    android:text=&#34;2&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;
&lt;Button
    android:id=&#34;@+id/btn_3&#34;
    android:text=&#34;3&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_sub&#34;
    android:text=&#34;-&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_4&#34;
    android:text=&#34;4&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_5&#34;
    android:text=&#34;5&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_6&#34;
    android:text=&#34;6&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_add&#34;
    android:text=&#34;+&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_7&#34;
    android:text=&#34;7&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_8&#34;
    android:text=&#34;8&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_9&#34;
    android:text=&#34;9&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_equal&#34;
    android:text=&#34;=&#34;
    android:layout_rowSpan=&#34;2&#34;
    android:layout_gravity=&#34;fill_vertical&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;2&#34;
    android:textSize=&#34;26sp&#34;
    android:textColor=&#34;#FFFFFF&#34;
    android:background=&#34;#E91E63&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_0&#34;
    android:text=&#34;0&#34;
    android:layout_columnSpan=&#34;2&#34;
    android:layout_gravity=&#34;fill_horizontal&#34;
    android:layout_columnWeight=&#34;2&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

&lt;Button
    android:id=&#34;@+id/btn_dot&#34;
    android:text=&#34;.&#34;
    android:layout_columnWeight=&#34;1&#34;
    android:layout_rowWeight=&#34;1&#34;
    android:textSize=&#34;26sp&#34;/&gt;

</GridLayout>