news 2026/11/6 2:07:43

Android Studio 新手实战:从零做一个简易计算器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android Studio 新手实战:从零做一个简易计算器

简介:面向K12阶段学习者与Android入门开发者的期末大作业项目,围绕Android Studio实现一个可运行的简易计算器,覆盖从新建Empty Activity项目到生成APK的完整流程。资源包共48个文件,约180KB,包含Java源码、XML布局、Gradle构建脚本与properties配置,以及png界面素材、jar依赖和打包运行相关文件,目录结构对应app模块、gradle wrapper与项目配置,便于直接导入Android Studio继续修改。已有4343人学习。项目重点展示数字与运算符按钮的XML布局、MainActivity中的OnClickListener事件响应、CalculatorEngine计算逻辑封装,以及借助栈结构处理运算符优先级和结果显示;同时涉及模拟器运行、Logcat排错与APK构建发布等实践环节。适合需要完成课程作业、理解Android基础开发流程并积累调试思路的初学者参考。

1. 别急着写代码:先看清“用 Android Studio 做个简易计算器”这件事

很多人第一次打开 Android Studio,进度条转了半分钟,然后对着一屏英文菜单发愣:明明只是想做个简易计算器,怎么光是“新建项目”就有七八个模板?我刚接触时也这样,甚至以为要先学完一整套 Java 才能碰安卓。后来发现,计算器这件事说穿了就三样东西:一块能显示数字的屏幕、一堆能点的按钮、一段把按键串成算式的逻辑。它不联网、不要服务器、不用数据库,出错时屏幕会立刻告诉你哪里不对。正因为反馈快、范围小,它特别适合“想动手但不知道手往哪放”的新手。接下来按顺序说清楚:它究竟练什么、动手前补什么、第一个工程怎么跑起来。

2. 为什么新手第一份安卓作业适合拿计算器练手:四块基本功一次练全

2.1 一块屏幕一堆按钮,背后正好是安卓的四件事

把做 App 想成装修一间铺面。第一步是画户型图,决定收银台放哪、货架分几排;第二步是装开关,按哪个按钮亮哪盏灯;第三步是记账,顾客买了什么得有个本子记着;最后是把图纸变成真房子,需要施工队。放到安卓里,这四件事分别对应界面布局、事件监听、状态管理和构建打包,一件都不少。

界面布局用 XML 写,你告诉系统“上面放一块显示屏,下面放五行键盘,每行四个键”。事件监听是给每个按钮挂一个“被按下时做什么”的回调,写法上就是一行setOnClickListener。状态管理最容易被新手忽略——用户按了1、+、2,这三个字符得存在某个地方,等按下等号时才拿出来算。构建打包交给 Gradle,它把 XML、Java 代码、图片资源揉成一个 APK 装进手机。

这四件事是所有安卓 App 的公共骨架。你做一个外卖 App、一个记账 App、一个打卡 App,第一天干的活和做计算器几乎一模一样。区别只在骨架之上长出来的肉。所以拿计算器入门,不是因为它简单到没东西可学,而是因为它把这四块基本功压进了最小的一块画布,你一周之内就能把整个流程走完一遍,而不是在某个环节卡三个月还看不到成品。

我见过不少人一上手就挑“仿某电商首页”当第一个项目,结果卡在 RecyclerView 的适配器写法上出不来,三周后放弃。计算器的好处是,你每天都能看到进度:今天界面出来了,明天按钮能点出数字了,后天能算加法了。这种“每天有东西能截图发给室友看”的正反馈,对零基础的人比什么都重要。

2.2 动手前你需要补的底子:比想象中少

不用先把 Java 从头学一遍。做这个计算器真正用得上的,只有下面这几样,缺哪个补哪个,一两天能过一遍:

  • 变量和基本类型:int、double、String、boolean,知道double存小数会有误差就行。
  • 字符串拼接:StringBuilder的append、deleteCharAt、setLength(0),这套操作就是计算器的“记忆”。
  • 条件判断和循环:if、while、for,用来做输入校验和遍历按钮。
  • 类与对象:知道MainActivity是一个类,onCreate是它启动时自动跑的方法,不用深究生命周期。
  • 匿名内部类和 Lambda:v -> append("1")这种写法知道是“按钮被点击时执行的代码”就够了。
  • 异常处理:try-catch,用来接住“除以零”这种会把 App 弄崩的情况。

至于 Kotlin,它确实是现在安卓开发的主流语言,写起来也更短。但我建议零基础第一版还是用 Java:网上能搜到的中文报错解释更多,编译器的错误提示更直白,而且学校课程设计大多也要求 Java。等你把这套流程跑通一遍,再花一个下午转 Kotlin,你会发现只是语法换了个皮,思路一模一样。

顺带说一句,哪怕你还在 K12 阶段、只在学校机房摸过几次电脑,这些内容也够用。真正卡住新手的从来不是语法,而是“不知道下一步点哪里”。

2.3 给自己定一个能验收的小目标

没有验收标准的项目会无限拖。我建议把它拆成四档,每档都能独立运行、独立截图:

档位做完之后的样子大概花的时间
v0工程能跑起来,屏幕上显示一个 0半天
v1界面完整,按数字键屏幕上数字会变一天
v2按等号能算出加减乘除的结果一天
v3支持先乘除后加减、除零不闪退、有退格键一到两天

先做到 v1 再想 v2,别一上来就琢磨“怎么支持括号”。第一版能跑通的意义远大于写得漂亮。等你把 v3 做出来,这个作业已经能拿去交了,剩下的时间再考虑加历史记录、换配色、做科学计算器。

3. 第一步先让工程跑起来:装好 Android Studio 到看见第一个界面

3.1 装什么、选什么:三个让你少走弯路的选择

下载就去官网,一个安装包,里面自带 JDK、SDK 和 Gradle,不需要你另外装 Java 环境,也不需要手动配环境变量。这一点和几年前不一样,很多人看的还是老教程,照着装了三遍 JDK,最后冲突了。

安装过程里有三个选择值得提前说:

第一,SDK 的存放路径别带中文和空格。这是一个纯粹的血泪经验——路径里出现中文,Gradle 有时会在编译阶段报一个跟路径毫无关系的错,你搜半天搜不到原因。默认路径一般在用户目录下,直接下一步就行。

第二,第一次启动会下载一批 SDK 组件,几百兆到一两个 G,视你选的组件而定。这个过程需要网络稳定,中途别关窗口,也别因为看起来卡住就强退。如果实在慢,可以在设置里换一个下载镜像源,具体位置在 SDK Manager 的 HTTP 代理那一栏,填镜像地址即可。

第三,模拟器怎么选。创建虚拟设备时挑一个 Pixel 系列的机型,系统镜像选不带应用商店的那个版本,体积小、启动快。内存给到 2G 就够跑计算器。如果你有安卓手机,我更推荐直接用真机:在手机设置里连点七次“版本号”打开开发者选项,勾上 USB 调试,插上数据线,Android Studio 顶部设备列表里会直接出现你的手机型号。真机调试比模拟器快得多,而且能立刻感受到按钮在真实屏幕上的手感。

3.2 从 New Project 到屏幕上出现文字

打开 Android Studio,走这条路径:

  1. 点New Project。
  2. 模板列表里选Empty Views Activity。注意不是Empty Activity——那个是 Compose 模板,代码结构完全不一样,新手选错了会一脸懵。
  3. 填三个字段:Name 写Calculator,Package name 保持默认的com.example.calculator,Language 选Java。
  4. Minimum SDK 选API 24左右。这个数字的意思是“最低支持到哪个版本的安卓系统”,选太低会缺新特性,选太高会筛掉一批旧手机,24 是个稳妥的中间值。
  5. 点Finish,等底部的 Gradle 进度条走完。

进度条走完之后,你会看到一个默认生成的页面,中间写着Hello World。点顶部的绿色三角运行,选好设备,几十秒后模拟器或手机上就会出现这个页面。到这一步,你已经跨过了新手最容易放弃的那道坎——环境能跑通,后面都是在这个地基上加东西。

如果 Gradle 那一步报了一屏红字,先别慌。滚动到最上面,找第一条Caused by,那才是真正的病因。绝大多数情况是网络下载依赖失败,重试一次或者换个镜像源就好。

3.3 认识三份关键文件,顺手改一行字

工程跑起来之后,左侧的项目结构里文件成百上千,但做计算器真正要动的只有三份:

  • app/src/main/java/com/example/calculator/MainActivity.java:程序入口,逻辑写在这。
  • app/src/main/res/layout/activity_main.xml:界面长什么样,写在这。
  • app/build.gradle:告诉构建工具用哪个安卓版本、引哪些库,绝大多数时候不用碰。

现在做个小实验,验证你真的找对了文件。打开activity_main.xml,把里面TextView的android:text="Hello World!"改成android:text="我的第一个计算器",然后重新运行:

<!-- activity_main.xml 里的这一段,只改 text 属性的值 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我的第一个计算器" android:textSize="24sp" />

wrap_content的意思是“宽度刚好包住内容”,textSize的单位sp是专门给文字用的尺寸单位,它会跟随系统字体大小设置变化,用dp写文字在某些手机上会显得别扭。改完点运行,屏幕上那行字变了,说明你已经掌握了“改 XML → 重新运行 → 看效果”这个循环,剩下的界面工作只是把这个循环重复几十次。

4. 界面和逻辑一次做通:从 activity_main.xml 到按下等号出结果

4.1 布局从哪下手:一个垂直容器装下显示区和键盘

安卓的布局是树形嵌套:最外层一个大容器,里面套小容器,小容器里再放控件。计算器的结构非常简单,从上到下就两块——上面一块显示数字,下面一块是键盘。所以最外层用垂直的LinearLayout,把屏幕竖着切成两段。

切的比例靠layout_weight。这个属性的含义是“把剩下的空间按份数分”:显示区给 2 份,键盘区给 5 份,那么屏幕高度就被分成 7 份,显示区占 2 份。用它的前提是把高度写成0dp,很多新手写成wrap_content再加 weight,会发现完全不起作用,这是最常见的布局坑之一。

键盘区再往下切:五行,每行一个横向的LinearLayout,行内四个按钮,每个按钮宽度写0dp、layout_weight="1",四等分。这样不管屏幕多宽,按钮都会自动摊平,不会出现右边空一块的情况。按钮高度写match_parent,跟着行高走。

这套“垂直套水平、处处用 weight”的结构看起来朴素,但它是所有计算器界面的通用骨架,你去看任何一个开源计算器,布局思路都差不多。等以后学了ConstraintLayout,可以用更少的嵌套实现同样的效果,但那不是现在的重点。

4.2 一层层写 activity_main.xml:显示区加五行键盘

二十个按钮如果每个都写五六个属性,XML 会膨胀到三百行。正确做法是先定义一个样式,让所有按键共用。在app/src/main/res/values/styles.xml里加一段:

<!-- 所有计算器按键共用的样式,避免每个按钮重复写六行属性 --> <style name="CalcButton"> <item name="android:layout_width">0dp</item> <item name="android:layout_height">match_parent</item> <item name="android:layout_weight">1</item> <item name="android:layout_margin">4dp</item> <item name="android:textSize">22sp</item> <item name="android:textAllCaps">false</item> </style>

textAllCaps这一项必须写。安卓的Button默认会把文字全部转成大写,你写“C”它显示“C”没问题,但你要是写个“清空”,它会变成“清空”还好——可一旦写英文单词比如del,屏幕上就变成DEL,视觉上很突兀。设成false就按你写的原样显示。

然后写activity_main.xml:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="12dp"> <!-- 显示区:2 份高度,文字靠右下,最多两行 --> <TextView android:id="@+id/tvDisplay" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:gravity="bottom|end" android:maxLines="2" android:padding="12dp" android:text="0" android:textColor="#111111" android:textSize="40sp" /> <!-- 第 1 行:C、退格、除、乘 --> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal"> <Button android:id="@+id/btnClear" style="@style/CalcButton" android:text="C" /> <Button android:id="@+id/btnBack" style="@style/CalcButton" android:text="⌫" /> <Button android:id="@+id/btnDiv" style="@style/CalcButton" android:text="÷" /> <Button android:id="@+id/btnMul" style="@style/CalcButton" android:text="×" /> </LinearLayout> <!-- 第 2 行:7、8、9、减 --> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal"> <Button android:id="@+id/btn7" style="@style/CalcButton" android:text="7" /> <Button android:id="@+id/btn8" style="@style/CalcButton" android:text="8" /> <Button android:id="@+id/btn9" style="@style/CalcButton" android:text="9" /> <Button android:id="@+id/btnSub" style="@style/CalcButton" android:text="−" /> </LinearLayout> <!-- 第 3 行:4、5、6、加 --> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal"> <Button android:id="@+id/btn4" style="@style/CalcButton" android:text="4" /> <Button android:id="@+id/btn5" style="@style/CalcButton" android:text="5" /> <Button android:id="@+id/btn6" style="@style/CalcButton" android:text="6" /> <Button android:id="@+id/btnAdd" style="@style/CalcButton" android:text="+" /> </LinearLayout> <!-- 第 4 行:1、2、3、等号 --> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal"> <Button android:id="@+id/btn1" style="@style/CalcButton" android:text="1" /> <Button android:id="@+id/btn2" style="@style/CalcButton" android:text="2" /> <Button android:id="@+id/btn3" style="@style/CalcButton" android:text="3" /> <Button android:id="@+id/btnEq" style="@style/CalcButton" android:text="=" /> </LinearLayout> <!-- 第 5 行:0 占三格,小数点占一格 --> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal"> <Button android:id="@+id/btn0" style="@style/CalcButton" android:layout_weight="3" android:text="0" /> <Button android:id="@+id/btnDot" style="@style/CalcButton" android:text="." /> </LinearLayout> </LinearLayout>

@+id/xxx这个写法是在告诉系统“新建一个叫 xxx 的控件编号”,后面 Java 代码里用R.id.xxx就能找到它。@style/CalcButton是引用刚才定义的样式。第 5 行的0键单独把layout_weight覆盖成 3,其余属性照样式走,这样它自然占三格宽,小数点占一格,视觉上刚好对齐。

写完点运行,你会看到一个能按但没反应的键盘。按下去按钮会有水波纹,但屏幕上的数字纹丝不动——这很正常,界面和逻辑是两套东西,现在只做完了前一半。

4.3 让按钮有反应:findViewById 和点击监听

打开MainActivity.java,把默认代码替换成下面这个骨架。先只做“按数字,屏幕上的数字跟着变”这一件事:

package com.example.calculator; import android.os.Bundle; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private TextView tvDisplay; // 显示区控件 private StringBuilder expr = new StringBuilder(); // 用户按下的整串内容 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 必须先设布局 tvDisplay = findViewById(R.id.tvDisplay); // 再找控件,顺序反了就是空指针 // 十个数字键的 id 放进数组,用循环统一绑定,省掉十遍重复代码 int[] digitIds = {R.id.btn0, R.id.btn1, R.id.btn2, R.id.btn3, R.id.btn4, R.id.btn5, R.id.btn6, R.id.btn7, R.id.btn8, R.id.btn9}; for (int i = 0; i < digitIds.length; i++) { final String digit = String.valueOf(i); findViewById(digitIds[i]).setOnClickListener(v -> append(digit)); } findViewById(R.id.btnDot).setOnClickListener(v -> append(".")); findViewById(R.id.btnClear).setOnClickListener(v -> { expr.setLength(0); // 清空表达式 render("0"); }); findViewById(R.id.btnBack).setOnClickListener(v -> { if (expr.length() > 0) expr.deleteCharAt(expr.length() - 1); render(expr.length() == 0 ? "0" : expr.toString()); }); } // 追加一个字符并刷新显示 private void append(String s) { expr.append(s); render(expr.toString()); } // 把内部的 * 和 / 换成屏幕上好看的 × 和 ÷ private void render(String s) { tvDisplay.setText(s.replace("*", "×").replace("/", "÷")); } }

几处值得说明的地方。onCreate是页面启动时自动执行的方法,setContentView负责把 XML 变成真实的视图树,findViewById是在这棵树里按编号找控件——这两行的顺序绝对不能反,反了就是新手最常见的闪退原因。循环里i从 0 走到 9,final String digit是必须的写法,因为 Lambda 内部引用外部变量时要求这个变量不再变化,不加final在某些 Java 版本上会直接编译报错。

StringBuilder是整个 App 的“记忆本”。用户每按一个键就往末尾追加一个字符,屏幕上就显示这个本子的内容。这个设计看起来很土,但它让你后面接入真正的求值逻辑变得非常容易——因为算式本来就是自己拼出来的。

现在运行,按1、2、3,屏幕应该依次显示1、12、123;按退格会一个一个删掉;按C回到0。到这一步,v1 目标达成,你已经能做“会响应”的界面了。

4.4 把表达式算出结果:字符串管状态加双栈求值

接下来是真正的计算。最朴素的想法是“按一次运算符就算一次”,比如按1+2=得到 3。这种做法叫两步式,写起来最快,但它算不对1 + 2 × 3——按顺序算会得到 9,正确答案是 7。想要先乘除后加减,就得把整个算式当成一个整体来处理。

我在MainActivity里加三个方法:绑定运算符和等号、求值、格式化结果。

// 接在 onCreate 末尾:运算符和等号 findViewById(R.id.btnAdd).setOnClickListener(v -> appendOp("+")); findViewById(R.id.btnSub).setOnClickListener(v -> appendOp("-")); findViewById(R.id.btnMul).setOnClickListener(v -> appendOp("*")); findViewById(R.id.btnDiv).setOnClickListener(v -> appendOp("/")); findViewById(R.id.btnEq).setOnClickListener(v -> calculate()); // 运算符不能连按,也不能出现在开头 private void appendOp(String op) { if (expr.length() == 0) return; char last = expr.charAt(expr.length() - 1); if (last == '+' || last == '-' || last == '*' || last == '/') return; expr.append(op); render(expr.toString()); } private void calculate() { if (expr.length() == 0) return; try { double result = evaluate(expr.toString()); expr.setLength(0); expr.append(format(result)); // 结果放回表达式,可以接着往下算 render(expr.toString()); } catch (Exception e) { expr.setLength(0); render("错误"); // 除零、格式非法都在这里兜住 } } // 整数就显示整数,小数最多留 8 位 private String format(double v) { if (Math.abs(v - Math.rint(v)) < 1e-9) return String.valueOf((long) Math.rint(v)); return String.valueOf(Math.round(v * 1e8) / 1e8); } // 双栈求值:数字栈存操作数,符号栈存运算符 private double evaluate(String s) { java.util.Deque<Double> nums = new java.util.ArrayDeque<>(); java.util.Deque<Character> ops = new java.util.ArrayDeque<>(); int i = 0; while (i < s.length()) { char c = s.charAt(i); if (Character.isDigit(c) || c == '.') { int j = i; while (j < s.length() && (Character.isDigit(s.charAt(j)) || s.charAt(j) == '.')) j++; nums.push(Double.parseDouble(s.substring(i, j))); i = j; continue; } // 当前运算符优先级不高于栈顶时,先把栈顶算掉 while (!ops.isEmpty() && priority(ops.peek()) >= priority(c)) { nums.push(apply(ops.pop(), nums.pop(), nums.pop())); } ops.push(c); i++; } while (!ops.isEmpty()) { nums.push(apply(ops.pop(), nums.pop(), nums.pop())); } return nums.pop(); } private int priority(char op) { return (op == '*' || op == '/') ? 2 : 1; } // 注意参数顺序:先弹出的是右操作数,后弹出的才是左操作数 private double apply(char op, double b, double a) { switch (op) { case '+': return a + b; case '-': return a - b; case '*': return a * b; default: if (b == 0) throw new ArithmeticException("divide by zero"); return a / b; } }

evaluate的思路是:从左往右扫,遇到数字就压进数字栈;遇到运算符,先看符号栈顶的运算符优先级是不是不低于当前这个,如果是不低于,说明前面那段可以算了,弹出两个数字算一次再压回去,直到栈顶优先级更低,才把当前运算符压栈。扫描结束后把剩下的运算符清空。1 + 2 * 3走一遍:压 1,遇+压栈,压 2,遇*时栈顶是+优先级更低,所以*直接压栈,压 3,扫描结束,先算2*3=6,再算1+6=7。结果是 7,正确。

apply的参数顺序是个隐蔽的坑。栈是后进先出,第一次pop出来的是右边的数,第二次才是左边的。如果按直觉写成a - b却传反了,你会发现9 - 3算出来是-6。我第一次写这段的时候在这上面卡了半小时,最后靠打日志才看出来。

这个求值器只认二元运算符,遇到开头是负号、或者带括号的表达式会直接抛异常,然后被catch兜住显示“错误”。这不是 bug,是范围限制,知道边界在哪比假装它什么都能算更重要。

完成 v3 之前,calculate里已经处理了除零——1 ÷ 0会显示“错误”而不是闪退。到这,你的计算器 v3 目标全部达成。

5. 新手避坑与排查:四个让计算器“玄学”的细节

5.1 模拟器起不来,一直卡在安装

现象:点运行后,底部进度条停在Installing APK或者Waiting for device,等五分钟没动静,偶尔弹一句Could not connect to emulator。

原因通常有两个。一是电脑的虚拟化支持没打开,模拟器本质上是一个跑在你电脑里的完整安卓系统,需要 CPU 的虚拟化指令才能流畅运行,这个开关在 BIOS 里,默认可能是关的。二是给模拟器分配的内存太小,或者选的系统镜像和你的 CPU 架构不匹配,跑起来像在泥里走路。

解决办法按成本从低到高排:第一,换真机调试,插上数据线打开 USB 调试就能用,速度比模拟器快得多,这是我最推荐新手走的路;第二,在模拟器设置里把内存调到 2G 以上,镜像换成 x86_64 版本;第三,进 BIOS 打开虚拟化选项。别在模拟器上耗超过一个小时,它只是工具,不是学习内容本身。

5.2 改了布局,运行后还是老样子

现象:明明把按钮文字改成了“清空”,重新运行后屏幕上还是“C”,甚至改完的 XML 在预览里已经变了,装到设备上却没变。

原因多半是运行配置指向了旧的模块,或者增量构建缓存没刷新。安卓的构建过程分好几步,有时候 Gradle 判断“这次只改了一个字符串,不用重新打包”,结果打了个旧的进去。另一种情况是你改的是layout目录下的另一个同名文件,比如做适配时复制了一份activity_main.xml到别的目录,实际加载的是那一份。

排查顺序:先看Run Configuration里选的 Module 是不是app;再执行一次Build → Clean Project然后Rebuild Project,强制全量重来;最后在activity_main.xml里随便加一个显眼的控件比如一个大红方块,看它出不出来,出来了说明文件找对了,没出来说明加载的根本不是这个文件。养成改完先 Rebuild 的习惯,能省掉一半“我明明改了”的困惑。

5.3 一点等号就闪退

现象:前面按键都正常,一按=,App 直接退回桌面,Logcat 里刷出一片红字。

原因有三个高发点。一是findViewById返回了null,然后你调用了它的方法,这类空指针最常见的原因是id拼错了,XML 里写btnEq,Java 里写成btnEqual,编译器不会报错,运行时才炸。二是把findViewById写在了setContentView前面,视图树还没建好,自然找不到控件。三是Double.parseDouble收到了空字符串或者"."这种非法内容,抛NumberFormatException。

解决的关键是学会看 Logcat。红色那一行里会写异常类型和行号,比如NullPointerException at MainActivity.java:47,直接跳到你代码的第 47 行,问题基本就在那一行附近。我建议在calculate里对表达式做一次判空和末尾字符检查,把非法输入挡在求值之前,这样即使逻辑有漏洞,App 也不会崩。

5.4 输入 1 ÷ 0,或者连点运算符,结果不对劲

现象:连按两个加号,屏幕上出现1++2,按等号显示“错误”;或者5 ÷ 0之后整个界面卡住,怎么按都没反应。

原因是没有做输入校验。用户的手指不会按你的预期走,连点、乱按是常态。appendOp里那两行判断就是干这个的:表达式为空时不允许按运算符,上一个字符是运算符时也不允许再按。除零则在apply里主动抛异常,由calculate的try-catch接住,然后把表达式清空、显示“错误”,让用户能重新开始,而不是卡在一个死状态里。

顺着这个思路还能想到不少边界:小数点能不能连按两个、数字太长会不会撑破显示区、结果超过double范围怎么办。每想到一个,就加一条判断。这类“防呆设计”看起来不起眼,但它决定了一个 App 是“能用”还是“敢给别人用”。你把这个计算器发给室友玩两分钟,他能按出来的所有奇怪组合,都是你该补的校验。

6. 下一步学什么:从“能跑”到“敢拿出手”的三级台阶

写完这个计算器,你手里已经有一个能跑、能演示、能交作业的完整安卓项目了。接下来往哪走,我给一个按投入产出比排的顺序,别

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 2:06:10

Datacom-HCIP 备考指南:路由交换与自动化运维核心考点梳理

简介&#xff1a;这份题库面向备考华为Datacom HCIP认证&#xff08;H12-831&#xff09;的考生&#xff0c;尤其适合已具备一定数通基础、需要集中刷题冲刺的学员。内容来自培训机构整理&#xff0c;题型覆盖较全&#xff0c;题目与考试真题重合度较高&#xff0c;可用于熟悉考…

作者头像 李华
网站建设 2026/11/6 2:06:01

Eclipse离线安装SVN插件site-1.8.22全攻略:版本兼容与避坑指南

简介&#xff1a;面向 Eclipse、MyEclipse 开发者的 SVN 版本控制集成插件包&#xff0c;site-1.8.22 对应 Subclipse 1.8.x 稳定分支&#xff0c;用于在 IDE 内直接完成代码提交、更新、差异对比与冲突解决&#xff0c;免去切换外部客户端的麻烦&#xff0c;适合需要与服务器端…

作者头像 李华
网站建设 2026/11/6 2:05:40

C#与SharpPcap实现ARP抓包:局域网设备台账与IP冲突告警

简介&#xff1a;面向需要在局域网内排查设备与IP冲突的C#开发者&#xff0c;这份工程以SharpPcap抓取ARP报文为核心&#xff0c;实现在线设备发现与冲突设备列表汇总。作者将抓包逻辑封装为独立类&#xff0c;并配有调用示例、WinPcap安装文件及SharpPcap、PacketDotNet依赖库…

作者头像 李华
网站建设 2026/11/6 2:05:14

C++从零手写卷积神经网络:MNIST训练与im2col实战

简介&#xff1a;这份 C 深度学习实践包面向希望从底层理解神经网络、提升工程实现能力的初学者与进阶开发者&#xff0c;围绕卷积神经网络与全连接神经网络的从零搭建展开&#xff0c;并用 MNIST 手写数字数据集完成训练与精度验证&#xff0c;适合课程设计、自学复现和底层原…

作者头像 李华
网站建设 2026/11/6 2:03:25

笔记++23

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/6 2:02:42

18|DeepSeek Harness研究资料更新后,怎样找出还引用旧证据的笔记?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华