从零打造Android计算器(安卓开发初体验)

从零打造Android计算器(安卓开发初体验)

前言:

使用Android Studio开发,SharedPreferences存储数据,java动态生成组件等技术。(可下载源码

先展示效果图,别放弃!

①初始计算器②带符号、小数点加减乘除③支持旋转操作

没有困难的代码,只有勇敢的🐕🐕!

计算器的实现难度相对于我上一篇博客课程表来说小了很多!!

不浪费时间直接开始

实现步骤:

  1. 渲染计算器所需的数字、符号、算式、结果
  2. 实现计算逻辑(需要用到数据结构相关知识,看不懂可跳过)
  3. 保存算式,即使关闭程序算式与结果仍然存在(使用SharedPreferences)

实现步骤一:
  • 考虑到每个人的安卓设备型号不一样,为了适配按钮以及算式,仍然在java代码(MainActivity)动态生成按钮,而不是在xml写死。
  • 观察发现图①按钮有五行四列,所有我们需要在activity_main.xml中设置好,并且给个id:button_grid

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <GridLayout
        android:id="@+id/button_grid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:rowCount="5"
        android:columnCount="4">
    </GridLayout>


</LinearLayout>
  • 接下来要做的就是在MainActivity中动态渲染按钮到上方activity_main.xml中的GridLayout里

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    GridLayout gridLayout;

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

        //渲染按钮
        applyDraw();

    }

    private void applyDraw() {
        //symbol数组下标,表示渲染到第几个符号或数字
        int cnt = 0;

        //symbol顺序是按照下方for循环的顺序
        final String []symbol = {"C", "÷", "×", "⋘", "7", "8", "9", "-", "4", "5", "6", "+", "1", "2", "3", "=", "0", "."};

        //通过id找到xml布局
        gridLayout = findViewById(R.id.button_grid);

        //因为是五行四列,所以需要两个循环来分别对行和列进行渲染
        //对行进行渲染
        for (int i = 0; i < 5; i++) {

            //每一行中有四列,进行每一行的列渲染
            for (int j = 0; j < 4; j++) {

                //为每一列创建一个button实例
                Button btn = new Button(this);
                //GridLayout.LayoutParams设置在此gridLayout列中button布局
                GridLayout.LayoutParams params1 = new GridLayout.LayoutParams();

                //设置在gridLayout中的方位,参数1:在第几行。参数2:占几行。参数3:权值
                params1.rowSpec = GridLayout.spec( i , 1,1);
                params1.columnSpec = GridLayout.spec(j, 1, 1);
                params1.setMargins(15,15,15,15);
                params1.width = 0;
                params1.height = 0;

                //因为有两个按钮比较特殊,会占用两个spec,要进行长度判断
                if (cnt < symbol.length) {

                    //给每一个按钮设置数组对应的值
                    String s = symbol[cnt++];
                    btn.setText(s);

                    //判断当前列是否为 = 或 0,其占用两个spec
                    if ("=".equals(s)) {
                        //占用两行一列
                        params1.rowSpec = GridLayout.spec( i , 2,1);
                        params1.columnSpec = GridLayout.spec(j, 1, 1);
                    }

                    if ("0".equals(s)) {
                        //占用两列一行
                        params1.rowSpec = GridLayout.spec( i , 1,1);
                        params1.columnSpec = GridLayout.spec(j, 2, 1);
                        //需要跳过占用的列
                        j++;
                    }

                    //按钮样式
                    btn.setTextSize(30);
                    btn.setMaxLines(5);
                    btn.setEllipsize(TextUtils.TruncateAt.END);
                    btn.setGravity(Gravity.CENTER| Gravity.CENTER_VERTICAL);

                    //监听按钮点击
                    btn.setOnClickListener(this);


                    //把按钮添加到此gridLayout中
                    gridLayout.addView(btn, params1);

                }

            }

        }
    }

    @Override
    public void onClick(View view) {
       

    }

}

这样的代码不管你跑在任何安卓设备都是适配的,因为没有写死长度宽度,全部使用权重(GridLayout.spec第三个参数代表权重)。样式可按自己的风格改

往xml里添加算式和结果的TextView后步骤一就大功告成了

完整的activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginTop="30dp">
        <TextView
            android:id="@+id/tex_M"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:text="M"
            android:textSize="20dp"
            android:layout_margin="5dp"/>
        <TextView
            android:id="@+id/tex_count"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:layout_margin="10dp"
            android:text=""
            android:gravity="right"
            android:textSize="35dp"/>
        <TextView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:textSize="50dp"
            android:textColor="@color/black"
            android:textStyle="bold"
            android:text=""
            android:gravity="right"/>

    </LinearLayout>

    <GridLayout
        android:id="@+id/button_grid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:rowCount="5"
        android:columnCount="4">
    </GridLayout>


</LinearLayout>
实现步骤二:

当我们点击按钮发现并没有反应,细心观察发现我们并没有在MainActivity重写的onClick方法中添加内容。这很关键,当我们点击按钮会触发这个方法,下面我们来实现这个方法!

  • 我们需要把点击的按钮记录到算式中,如果按下=按钮则生成结果(对应之前activity_main.xml中TextView)

  • 之前我们提过需要使用数据结构。为什么呢,因为计算机不能像人一样识别加减乘除符号或者数字,这个时候我们就需要使用到数据结构中的栈【不懂的可以去了解一下,就是简单的先进后出(FILO)原则】结构。

    例:1+2*3-4 注意:-4相当于+(-4)

  1. 处理字符串使他们分为两个数组分别保存:
String s = "1+2*3-4";
String[] Num = {"+","*","+"};
String[] Characters = {"1","2","3","-4"};
  1. 使用栈来保存第二优先级运算符(+),直接运算第一优先级(*和÷)

在这里插入图片描述

第三步之后取出栈顶符号和栈顶数组进行相加。(说难也不难

上onclick方法的代码(包含一些逻辑判断和输入的合法性判断,核心就是上面的内容)

	@Override
    public void onClick(View view) {
        //式子文本
        TextView textView = findViewById(R.id.tex_count);
        //最终结果
        TextView res = findViewById(R.id.result);
        //之前的式子
        String frontText = textView.getText().toString();

        //按下的按钮
        Button push = (Button) view;
        String pressText = push.getText().toString();

        //判断按下的按钮是否是运算符
        boolean isCharacter = pressText.equals("+")
                || pressText.equals("-")
                || pressText.equals("×")
                || pressText.equals("÷")
                || pressText.equals(".");


        //删除一个按钮值操作
        if (pressText.equals("⋘")) {
            //如果此次操作文本框为空或者最后一个字符,全部清空
            if ("".equals(frontText) || frontText.length() == 1) {
                textView.setText("");
                res.setText("");
                return;
            }
            //删除最后一个值
            textView.setText(frontText.substring(0, frontText.length() - 1));
            return;
        }

        //第一个按下的除了-号都不放行
        if ("".equals(frontText)) {
            if (isCharacter && !pressText.equals("-")) {
                return;
            }
        }

        //第一个是减号,第二个不能是运算符
        if ("-".equals(frontText)) {
            if (isCharacter) {
                return;
            }
        }

        //归零上述式子
        if (push.getText().toString().equals("C")) {
            textView.setText("");
            res.setText("");
            return;
        }

        //核心算法
        //计算上述式子
        if (push.getText().toString().equals("=")) {

            //没有算式
            if ("".equals(frontText)) {
                return;
            }

            //如果最后一个是运算符不允许计算
            char c = frontText.substring(frontText.length() - 1).charAt(0);
            if (c < '0' || c > '9') {
                return;
            }

            //String式子
            String formula = textView.getText().toString();

            //把数字和加减乘除分开
            String[] Num = formula.split("[\\+|\\-|\\×|\\÷]");
            String[] Characters = formula.split("[0-9]+\\.?[0-9]*");

            //没有输入运算符
            if (Num.length > 0 && Characters.length == 0) {
                return;
            }

            //分别定义上述数组下标
            int numPoint;
            int characterPoint;

            //分别创建运算符栈与数字栈
            Stack<String> stackCharacter = new Stack<>();
            Stack<String> stackNum = new Stack<>();


            //当输入为负数为首时
            if ("".equals(Num[0])) {

                if (Characters.length == 1) {

                    double num0 = Double.parseDouble(Num[1]);

                    res.setText(String.valueOf(-num0));
                    return;
                }
                //计数器
                characterPoint = 1;
                numPoint = 1;

                //把第一个符号数字分别压入栈,计数器加一
                stackNum.push("-" + Num[numPoint]);
                numPoint++;
            } else {
                //把第一个符号数字分别压入栈,计数器加一
                characterPoint = 1;
                numPoint = 0;
                stackNum.push(Num[numPoint]);
                numPoint++;//下标加一

            }
            if (Characters[characterPoint].equals("-")) {
                stackCharacter.push("+");
                characterPoint++;
                //第二个数字入栈
                if ("".equals(Num[numPoint])) {
                    numPoint++;
                }
                stackNum.push("-" + Num[numPoint]);
                numPoint++;//下标加一
            } else {
                stackCharacter.push(Characters[characterPoint]);
                characterPoint++;
                if ("".equals(Num[numPoint])) {
                    numPoint++;
                }
                stackNum.push(Num[numPoint]);
                numPoint++;//下标加一
            }



            //如果符号栈不为空
            while (!stackCharacter.isEmpty()) {

                //如果栈顶元素是乘
                if (stackCharacter.peek().equals("×")) {

                    //取出最顶符号
                    stackCharacter.pop();


                    //取出最顶两个数字
                    BigDecimal b1 = new BigDecimal(stackNum.pop());
                    BigDecimal b2 = new BigDecimal(stackNum.pop());

                    Double mul =  b1.multiply(b2).doubleValue();

                    //结果压入栈
                    stackNum.push(mul.toString());

                } else if (stackCharacter.peek().equals("×-")) {
                    //取出最顶符号
                    stackCharacter.pop();


                    //取出最顶两个数字
                    BigDecimal b1 = new BigDecimal(stackNum.pop());
                    BigDecimal b2 = new BigDecimal(stackNum.pop());

                    Double mul =  -b1.multiply(b2).doubleValue();

                    //结果压入栈
                    stackNum.push(mul.toString());
                }
                //如果栈顶元素是除
                else if (stackCharacter.peek().equals("÷")) {

                    //取出最顶符号
                    stackCharacter.pop();

                    //取出最顶两个数字
                    String pop1 = stackNum.pop();
                    String pop2 = stackNum.pop();
                    BigDecimal b1 = new BigDecimal(pop1);
                    BigDecimal b2 = new BigDecimal(pop2);

                    if (0 == b1.compareTo(BigDecimal.ZERO)) {
                        res.setText("不能除以0");
                        return;
                    }


                    Double div = b2.divide(b1, 12, BigDecimal.ROUND_HALF_UP).doubleValue();


                    //结果压入栈
                    stackNum.push(div.toString());
                } else if (stackCharacter.peek().equals("÷-")) {
                    //取出最顶符号
                    stackCharacter.pop();


                    //取出最顶两个数字
                    BigDecimal b1 = new BigDecimal(stackNum.pop());
                    BigDecimal b2 = new BigDecimal(stackNum.pop());

                    if (0 == b1.compareTo(BigDecimal.ZERO)) {
                        res.setText("不能除以0");
                        return;
                    }

                    Double div = -b2.divide(b1, 12, BigDecimal.ROUND_HALF_UP).doubleValue();

                    //结果压入栈
                    stackNum.push(div.toString());
                }

                //判断数组是否还有符号
                if (characterPoint < Characters.length) {
                    //把当前下标数组值压入栈
                    if (Characters[characterPoint].equals("-")) {
                        stackCharacter.push("+");
                        characterPoint++;
                        //把当前下标数字压入栈
                        if ("".equals(Num[numPoint])) {
                            numPoint++;
                        }
                        stackNum.push("-" + Num[numPoint]);
                        numPoint++;//下标加一
                    } else {
                        stackCharacter.push(Characters[characterPoint]);
                        characterPoint++;
                        //把当前下标数字压入栈
                        if ("".equals(Num[numPoint])) {
                            numPoint++;
                        }
                        stackNum.push(Num[numPoint]);
                        numPoint++;//下标加一
                    }
                }
                //数组无符号
                else {
                    if (!stackCharacter.isEmpty()) {
                        //取出栈顶符号
                        String temChar = stackCharacter.pop();
                        //取出最顶两个数字
                        String pop1 = stackNum.pop();
                        String pop2 = stackNum.pop();
                        Double temNum = Double.parseDouble(pop2) + Double.parseDouble(pop1);

                        //结果压入栈
                        stackNum.push(temNum.toString());
                    }


                }

            }

            //最后的结果
            res.setText(stackNum.pop());
            return;

        }

        //没按 C 或 = 式子继续通过按钮增加
        if (!"".equals(frontText)) {
            //上一个输入的是运算符
            if (frontText.substring(frontText.length() - 1).equals("+")
                    || frontText.substring(frontText.length() - 1).equals("-")
                    || frontText.substring(frontText.length() - 1).equals("×")
                    || frontText.substring(frontText.length() - 1).equals("÷")
                    || frontText.substring(frontText.length() - 1).equals(".")) {
                //这次输入的也是运算符
                if (isCharacter) {
                    if (pressText.equals(".")) {
                        return;
                    }
                    if (frontText.substring(frontText.length() - 1).equals("-")) {
                        return;
                    }

                    if (frontText.substring(frontText.length() - 1).equals("×")
                            || frontText.substring(frontText.length() - 1).equals("÷")) {
                        if (pressText.equals("-")) {

                            textView.setText(frontText + pressText);
                            return;
                        }
                    }

                    //把上一个运算符替换成这次输入的
                    String substring = frontText.substring(0, frontText.length() - 1) + pressText;
                    textView.setText(substring);
                    return;


                }

            }


            //  非法操作按钮 . 如2.2.222
            //保证最后一个 . 的位置在运算符后面,如2.5+5.8。此时我们判断的是5.8的点而不是2.5的点的位置
            int max = Math.max(Math.max(frontText.lastIndexOf("+"), frontText.lastIndexOf("-")),
                    Math.max(frontText.lastIndexOf("×"), frontText.lastIndexOf("÷")));
            if (frontText.lastIndexOf(".") > max && pressText.equals(".")) {
                return;
            }

        }

        //将按下的按钮保存到算式
        textView.setText(frontText + pressText);

    }

至此,核心功能完成!!🍺

实现步骤三:

这部分内容是非核心功能,只是为了让用户拥有良好体验

  • MainActivity中重写暂停方法,在暂停方法中实现

MainActivity

    @Override
    protected void onPause() {
        //式子文本
        TextView textView = findViewById(R.id.tex_count);
        //最终结果
        TextView res = findViewById(R.id.result);

        //存储上次计算式子和结果
        SharedPreferences sp = getSharedPreferences("LAST_FORMULA", MODE_PRIVATE);
        sp.edit().putString("LAST_FORMULA",textView.getText().toString()).apply();//apply才会写入到xml配置文件里面

        SharedPreferences sp1 = getSharedPreferences("LAST_RESULT", MODE_PRIVATE);
        sp1.edit().putString("LAST_RESULT",res.getText().toString()).apply();//apply才会写入到xml配置文件里面
        super.onPause();
    }

  • 保存的sp,sp1内容我们该如何获取呢?

我们再在m写一个方法来获取sp,sp1的内容,并把它们渲染到对应的TextView中即可

    private void getLastFormula() {
        //获取上次运算式子和结果
        SharedPreferences sp1 = getSharedPreferences("LAST_FORMULA", MODE_PRIVATE);
        SharedPreferences sp2 = getSharedPreferences("LAST_RESULT", MODE_PRIVATE);
        //如果SoundCode,获取的值是空的,则会弹出后面的默认值
        String obtain = sp1.getString("LAST_FORMULA", "");
        String obtain2 = sp2.getString("LAST_RESULT", "");

        //式子文本
        TextView textView = findViewById(R.id.tex_count);
        //最终结果
        TextView res = findViewById(R.id.result);
        //上次运算结果放入这次文本框
        textView.setText(obtain);
        res.setText(obtain2);
    }

注意在oncreate方法调用此方法

实现步骤四:

创建一个新的activity_main.xml

res->layout-land->activity_main.xml

稍微改一下之前的activity_main.xml布局就可以了:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:orientation="vertical"
        android:layout_marginTop="30dp">
        <TextView
            android:id="@+id/tex_M"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:text="M"
            android:textSize="20dp"
            android:layout_margin="5dp"/>
        <TextView
            android:id="@+id/tex_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:maxLines="2"
            android:text=""
            android:gravity="right"
            android:textSize="35dp"/>
        <TextView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:textSize="40dp"
            android:textColor="@color/black"
            android:textStyle="bold"
            android:text=""
            android:gravity="right"/>

    </LinearLayout>

    <GridLayout
        android:id="@+id/button_grid"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="7"
        android:orientation="vertical"
        android:rowCount="5"
        android:columnCount="4">
    </GridLayout>


</LinearLayout>

运行

完结!!!

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code534

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值