
简介面向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是它启动时自动跑的方法不用深究生命周期。匿名内部类和 Lambdav - 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走这条路径点New Project。模板列表里选Empty Views Activity。注意不是Empty Activity——那个是 Compose 模板代码结构完全不一样新手选错了会一脸懵。填三个字段Name 写CalculatorPackage name 保持默认的com.example.calculatorLanguage 选Java。Minimum SDK 选API 24左右。这个数字的意思是“最低支持到哪个版本的安卓系统”选太低会缺新特性选太高会筛掉一批旧手机24 是个稳妥的中间值。点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:textHello World!改成android:text我的第一个计算器然后重新运行!-- activity_main.xml 里的这一段只改 text 属性的值 -- TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text我的第一个计算器 android:textSize24sp /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_weight1四等分。这样不管屏幕多宽按钮都会自动摊平不会出现右边空一块的情况。按钮高度写match_parent跟着行高走。这套“垂直套水平、处处用 weight”的结构看起来朴素但它是所有计算器界面的通用骨架你去看任何一个开源计算器布局思路都差不多。等以后学了ConstraintLayout可以用更少的嵌套实现同样的效果但那不是现在的重点。4.2 一层层写 activity_main.xml显示区加五行键盘二十个按钮如果每个都写五六个属性XML 会膨胀到三百行。正确做法是先定义一个样式让所有按键共用。在app/src/main/res/values/styles.xml里加一段!-- 所有计算器按键共用的样式避免每个按钮重复写六行属性 -- style nameCalcButton item nameandroid:layout_width0dp/item item nameandroid:layout_heightmatch_parent/item item nameandroid:layout_weight1/item item nameandroid:layout_margin4dp/item item nameandroid:textSize22sp/item item nameandroid:textAllCapsfalse/item /styletextAllCaps这一项必须写。安卓的Button默认会把文字全部转成大写你写“C”它显示“C”没问题但你要是写个“清空”它会变成“清空”还好——可一旦写英文单词比如del屏幕上就变成DEL视觉上很突兀。设成false就按你写的原样显示。然后写activity_main.xml?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding12dp !-- 显示区2 份高度文字靠右下最多两行 -- TextView android:idid/tvDisplay android:layout_widthmatch_parent android:layout_height0dp android:layout_weight2 android:gravitybottom|end android:maxLines2 android:padding12dp android:text0 android:textColor#111111 android:textSize40sp / !-- 第 1 行C、退格、除、乘 -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btnClear stylestyle/CalcButton android:textC / Button android:idid/btnBack stylestyle/CalcButton android:text⌫ / Button android:idid/btnDiv stylestyle/CalcButton android:text÷ / Button android:idid/btnMul stylestyle/CalcButton android:text× / /LinearLayout !-- 第 2 行7、8、9、减 -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn7 stylestyle/CalcButton android:text7 / Button android:idid/btn8 stylestyle/CalcButton android:text8 / Button android:idid/btn9 stylestyle/CalcButton android:text9 / Button android:idid/btnSub stylestyle/CalcButton android:text− / /LinearLayout !-- 第 3 行4、5、6、加 -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn4 stylestyle/CalcButton android:text4 / Button android:idid/btn5 stylestyle/CalcButton android:text5 / Button android:idid/btn6 stylestyle/CalcButton android:text6 / Button android:idid/btnAdd stylestyle/CalcButton android:text / /LinearLayout !-- 第 4 行1、2、3、等号 -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn1 stylestyle/CalcButton android:text1 / Button android:idid/btn2 stylestyle/CalcButton android:text2 / Button android:idid/btn3 stylestyle/CalcButton android:text3 / Button android:idid/btnEq stylestyle/CalcButton android:text / /LinearLayout !-- 第 5 行0 占三格小数点占一格 -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn0 stylestyle/CalcButton android:layout_weight3 android:text0 / Button android:idid/btnDot stylestyle/CalcButton android:text. / /LinearLayout /LinearLayoutid/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 走到 9final String digit是必须的写法因为 Lambda 内部引用外部变量时要求这个变量不再变化不加final在某些 Java 版本上会直接编译报错。StringBuilder是整个 App 的“记忆本”。用户每按一个键就往末尾追加一个字符屏幕上就显示这个本子的内容。这个设计看起来很土但它让你后面接入真正的求值逻辑变得非常容易——因为算式本来就是自己拼出来的。现在运行按1、2、3屏幕应该依次显示1、12、123按退格会一个一个删掉按C回到0。到这一步v1 目标达成你已经能做“会响应”的界面了。4.4 把表达式算出结果字符串管状态加双栈求值接下来是真正的计算。最朴素的想法是“按一次运算符就算一次”比如按12得到 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.DequeDouble nums new java.util.ArrayDeque(); java.util.DequeCharacter 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*36再算167。结果是 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 里写btnEqJava 里写成btnEqual编译器不会报错运行时才炸。二是把findViewById写在了setContentView前面视图树还没建好自然找不到控件。三是Double.parseDouble收到了空字符串或者.这种非法内容抛NumberFormatException。解决的关键是学会看 Logcat。红色那一行里会写异常类型和行号比如NullPointerException at MainActivity.java:47直接跳到你代码的第 47 行问题基本就在那一行附近。我建议在calculate里对表达式做一次判空和末尾字符检查把非法输入挡在求值之前这样即使逻辑有漏洞App 也不会崩。5.4 输入 1 ÷ 0或者连点运算符结果不对劲现象连按两个加号屏幕上出现12按等号显示“错误”或者5 ÷ 0之后整个界面卡住怎么按都没反应。原因是没有做输入校验。用户的手指不会按你的预期走连点、乱按是常态。appendOp里那两行判断就是干这个的表达式为空时不允许按运算符上一个字符是运算符时也不允许再按。除零则在apply里主动抛异常由calculate的try-catch接住然后把表达式清空、显示“错误”让用户能重新开始而不是卡在一个死状态里。顺着这个思路还能想到不少边界小数点能不能连按两个、数字太长会不会撑破显示区、结果超过double范围怎么办。每想到一个就加一条判断。这类“防呆设计”看起来不起眼但它决定了一个 App 是“能用”还是“敢给别人用”。你把这个计算器发给室友玩两分钟他能按出来的所有奇怪组合都是你该补的校验。6. 下一步学什么从“能跑”到“敢拿出手”的三级台阶写完这个计算器你手里已经有一个能跑、能演示、能交作业的完整安卓项目了。接下来往哪走我给一个按投入产出比排的顺序别本文还有配套的精品资源点击获取