
简介面向K12阶段初学者的安卓期末大作业资源基于Android Studio开发一个简易计算器应用完整覆盖界面设计、按钮事件监听、计算逻辑封装、运算符优先级处理以及测试调试与发布流程。压缩包共包含48个文件其中11个XML布局与配置负责界面搭建12个PNG素材提供按钮与图标资源3个Java文件实现主活动与计算引擎另附Gradle构建脚本和项目配置文件整体仅180KB结构清晰可直接导入开发环境运行。项目采用栈数据结构处理运算符优先级支持数字输入、四则运算与结果实时刷新布局文件使用XML编写Java代码通过setOnClickListener实现事件响应并配有模拟器调试和Logcat排错思路。目前已有4331人学习下载适合作为期末作业参考或安卓入门实践项目。通过完整项目源码、布局文件及构建脚本学习者既能直接运行体验也能对照理解事件监听、计算逻辑与界面分离的安卓开发范式并经历从创建、调试到打包APK的完整流程。1. 安卓期末大作业为什么简易计算器是最稳的选择每到期末用 Android Studio 交安卓期末大作业的时候“简易计算器实现”都是搜索量最大的选题之一。它不依赖数据库、不依赖第三方 SDK核心就两件事用 XML 把界面画出来用 Java 把四则运算串起来正好覆盖课程在 UI 布局、事件监听、数据逻辑三个方向的评分点。这个方案真正有弹性的地方在于加减乘除就能及格把横竖屏状态恢复、边界输入校验、历史记录这些加上去就能冲高分。后续内容从新建工程讲到答辩自检代码可以直接抄关键参数会解释为什么这样设容易翻车的边界条件也会单独列出来。2. 在 Android Studio 搭项目与界面新建工程的 4 个关键选项和 16 键布局完整代码写代码之前先把项目的骨架定下来。这里说的不是配色而是模板、语言、SDK 版本和布局容器。这四个选项决定了后面几小时是顺利推进还是一直跟环境较劲很多同学栽在第一步不是因为不会写计算器而是因为模板选错或者 Gradle 同步反复失败。2.1 新建项目的四个关键选择Empty Activity、Java、API 24 与 Gradle 配置打开 Android Studio 新建项目时最稳的模板是 Empty Activity不是 Basic Activity。Basic Activity 会自带 FloatingActionButton、菜单、资源文件等一堆用不到的东西期末项目里这些没用反而容易触发资源重复或者主题不匹配的错误。Empty Activity 只有 MainActivity 和 activity_main.xml路径最短踩坑最少。如果你的 Android Studio 界面还是英文可以去 Settings 里的 Plugins 搜索 Chinese Language Pack 安装后重启这是“Android Studio 怎么设置中文”这个问题最常见的解法不装也不影响写代码。第二个关键是语言下拉框。我建议选 Java理由在第 3 章展开这里只需要知道绝大多数安卓课程用的就是 Java照着老师的课件改代码最不容易出问题。第三Minimum SDK 选 API 24。这个版本对应 Android 7.0覆盖了现在的绝大多数真机和模拟器同时不用像 API 21 那样处理大量老机型兼容问题。最后项目名和包名不要出现中文、空格和数字开头用 Calculator 和 com.example.calculator 就能顺利走到创建完成。点 Finish 之后Android Studio 会开始 Gradle 同步。第一次建项目下载依赖比较慢网上一搜“Android Studio Gradle 配置详解”能找到一堆排查方向最常见的是 gradle-wrapper.properties 里的 distributionUrl 下载超时以及 build.gradle 里的插件版本和本地环境不匹配。我一般建议耐心等一次完整同步不要反复取消重来否则本地 Gradle 缓存容易处于半残状态后面 build 更慢。2.2 计算器界面选 LinearLayout 还是 GridLayout期末场景下的选型对比计算器界面的核心需求是上方一个结果显示区下方 16 个按钮排成 4 行 4 列每个按钮等宽等高。实现这种矩阵布局新手最常纠结的是 LinearLayout 嵌套和 GridLayout 二选一。对比项LinearLayout 嵌套GridLayout学习成本低只用 orientation 和 weight中要理解 columnCount、rowCount、columnWeight等分布局每行按钮设 0dp 宽度和 weight1规则统一需额外处理权重属性写错位置按钮直接错位调试难度层级多但结构直观哪里不对一眼看出属性报错时经常是整块叠在一起答辩讲解每个属性都能解释清楚代码短但要讲清 span 和 weight 不太容易我一般推荐 LinearLayout 嵌套。很多同学想用 GridLayout 展示“自己会用新布局”结果在 layout_columnWeight 上翻车按钮挤成一团答辩现场越改越乱。至于“Android Studio 自定义组件”这种更高级的做法比如封装一个 CalculatorButton要处理自定义属性 attrs 和样式资源复杂度对期末来说太高风险大于收益。2.3 手写 XML 布局显示区、清除键与 16 个按钮的完整代码在 activity_main.xml 里用代码视图粘贴下面内容整体结构是一个垂直方向的 LinearLayout里面放 1 个 TextView 和 4 个水平方向的 LinearLayout每个水平行放 4 个 Button。?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 !-- 结果显示区 -- TextView android:idid/tv_display android:layout_widthmatch_parent android:layout_height80dp android:layout_marginBottom8dp android:background#EFEFEF android:gravityend|center_vertical android:maxLines1 android:padding16dp android:text0 android:textSize36sp / !-- 第一行7 8 9 ÷ -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn_7 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text7 / Button android:idid/btn_8 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text8 / Button android:idid/btn_9 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text9 / Button android:idid/btn_divide android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text÷ / /LinearLayout !-- 第二行4 5 6 × -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn_4 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text4 / Button android:idid/btn_5 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text5 / Button android:idid/btn_6 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text6 / Button android:idid/btn_multiply android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text× / /LinearLayout !-- 第三行1 2 3 - -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn_1 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text1 / Button android:idid/btn_2 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text2 / Button android:idid/btn_3 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text3 / Button android:idid/btn_minus android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text- / /LinearLayout !-- 第四行C 0 -- LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:orientationhorizontal Button android:idid/btn_clear android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:textC / Button android:idid/btn_0 android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text0 / Button android:idid/btn_equal android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text / Button android:idid/btn_plus android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:text / /LinearLayout /LinearLayout这段布局的核心是 layout_weight 和 0dp 的配合。横向的每个 Button 宽度设为 0dp再设 weight1四个按钮就会均分父容器宽度纵向每个按钮高度用 match_parent所在行的 LinearLayout 高度设 0dp 和 weight1四行均分剩余高度。整个键盘区无论屏幕多大都会自动铺满不会出现某一行特别高、某一列特别宽的情况。TextView 里的 gravity 用 end|center_vertical让数字从右向左显示模拟真实计算器的输入习惯textSize 用 36sp 比较醒目maxLines1 防止结果太长自动换行把布局顶乱。按钮上的文本直接写在 XML 里严格来说应该抽到 strings.xml但期末项目里硬编码不影响功能省一层跳转反而少一个问题。这段 XML 在 Android Studio 的 Design 视图里可以实时预览。如果按钮显示不出来检查每个 Button 的 id 是否重复以及代码是不是少粘了一个半括号这类错误在 Build 时会直接报出来Logcat 里能看到具体行号。3. 从按键到结果上屏中缀转后缀求值器与 MainActivity 完整绑定代码布局搭完下一步就是让按钮真正“能算”。这一章要解决的问题是按“7 8 × 2 ”时程序如何知道先算乘法再算加法最后显示 23 而不是 30。答案是用栈把中缀表达式转换成后缀表达式再做单次遍历求值。3.1 Java 还是 Kotlin期末大作业的选型逻辑先说语言选型。如果学校课程明确用 Kotlin那就用 Kotlin否则我建议 Java。原因很实际期末大作业的重点是把功能跑通Java 的语法和老师课件上的示例一致考试或答辩时被问到“这段代码什么意思”解释起来成本低。Kotlin 的 null 安全、扩展函数、协程虽然现代但当你不熟悉它时每写一行都可能在跟编译器计较。还有一个更隐蔽的坑网上搜“Java 高级计算器”很多方案会用 ScriptEngine 之类的动态求值工具。这个类在标准 Java 里能用但在 Android 里被移除了所以这种代码直接拿到 Android Studio 里一定编译不过。这也是我坚持用手写栈求值的根本原因它不依赖任何第三方库逻辑完全可控。3.2 四则运算求值器用栈实现中缀转后缀新建一个 Calculator.java把下面代码完整放进去。这个类不依赖 Activity可以单独用 JUnit 测试也可以被 MainActivity 直接调用。import java.text.DecimalFormat; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; public class Calculator { public static String evaluate(String expr) { try { ListString tokens tokenize(expr); ListString postfix infixToPostfix(tokens); double result calcRpn(postfix); if (Double.isNaN(result) || Double.isInfinite(result)) { return 除数不能为 0; } return formatResult(result); } catch (Exception e) { return 表达式错误; } } private static ListString tokenize(String expr) { ListString tokens new ArrayList(); StringBuilder num new StringBuilder(); for (int i 0; i expr.length(); i) { char c expr.charAt(i); if (Character.isDigit(c) || c .) { num.append(c); } else { if (num.length() 0) { tokens.add(num.toString()); num.setLength(0); } tokens.add(String.valueOf(c)); } } if (num.length() 0) { tokens.add(num.toString()); } return tokens; } private static ListString infixToPostfix(ListString tokens) { ListString output new ArrayList(); DequeString stack new ArrayDeque(); MapString, Integer priority new HashMap(); priority.put(, 1); priority.put(-, 1); priority.put(*, 2); priority.put(/, 2); for (String token : tokens) { if (isNumber(token)) { output.add(token); } else { while (!stack.isEmpty() priority.containsKey(stack.peek()) priority.get(stack.peek()) priority.get(token)) { output.add(stack.pop()); } stack.push(token); } } while (!stack.isEmpty()) { output.add(stack.pop()); } return output; } private static double calcRpn(ListString postfix) { DequeDouble stack new ArrayDeque(); for (String token : postfix) { if (isNumber(token)) { stack.push(Double.parseDouble(token)); } else { double b stack.pop(); double a stack.pop(); switch (token) { case : stack.push(a b); break; case -: stack.push(a - b); break; case *: stack.push(a * b); break; case /: stack.push(a / b); break; } } } return stack.pop(); } private static boolean isNumber(String s) { try { Double.parseDouble(s); return true; } catch (NumberFormatException e) { return false; } } private static String formatResult(double value) { if (value Math.floor(value) !Double.isInfinite(value)) { return String.valueOf((long) value); } DecimalFormat df new DecimalFormat(0.######); return df.format(value); } }这段代码的逻辑分成四步。tokenize 把 “123” 拆成字符串列表 “12”、“”、“3”小数点和数字会拼在一起运算符单独成项。infixToPostfix 是核心乘除优先级设为 2加减设为 1遇到新运算符时只要栈顶运算符优先级大于等于自己就先弹出去从而实现先乘除后加减。calcRpn 拿到后缀表达式后遇到数字就入栈遇到运算符就弹出两个数计算结果再入栈最后栈顶就是答案。evaluate 方法里还做了两层防护计算结果如果是 Infinity 或 NaN说明发生了除零返回中文提示整个流程任何异常都会被捕获返回“表达式错误”。formatResult 把整数值转成不带小数点的字符串比如 4.0 显示成 4同时用 DecimalFormat 限制小数位数避免 0.10.2 这种浮点误差在屏幕上暴露出很长一串 0.30000000000000004。3.3 MainActivity 完整绑定代码数字、运算符、等号和清除回到 MainActivity.java先删除模板生成的代码改成下面内容。布局文件里一共有 16 个按钮如果用 findViewById 一个个绑定代码会又长又容易漏。我这里用数组加循环把相同行为的按钮归成一组分别绑定数字和运算符。import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private TextView tvDisplay; private final StringBuilder expression new StringBuilder(); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvDisplay findViewById(R.id.tv_display); // 数字按钮统一绑定 int[] digitIds { R.id.btn_0, R.id.btn_1, R.id.btn_2, R.id.btn_3, R.id.btn_4, R.id.btn_5, R.id.btn_6, R.id.btn_7, R.id.btn_8, R.id.btn_9 }; for (int id : digitIds) { findViewById(id).setOnClickListener(v - appendInput(((Button) v).getText().toString())); } // 运算符按钮统一绑定 int[] operatorIds { R.id.btn_plus, R.id.btn_minus, R.id.btn_multiply, R.id.btn_divide }; for (int id : operatorIds) { findViewById(id).setOnClickListener(v - appendInput(((Button) v).getText().toString())); } findViewById(R.id.btn_equal).setOnClickListener(v - calculate()); findViewById(R.id.btn_clear).setOnClickListener(v - clear()); } private void appendInput(String input) { // 表达式不能以运算符开头 if (expression.length() 0 input.matches([\\-*/])) { return; } // 禁止连续输入两个运算符 String last expression.length() 0 ? expression.substring(expression.length() - 1) : ; if (last.matches([\\-*/]) input.matches([\\-*/])) { return; } expression.append(input); tvDisplay.setText(expression.toString()); } private void calculate() { if (expression.length() 0) { return; } String result Calculator.evaluate(expression.toString()); if (表达式错误.equals(result) || 除数不能为 0.equals(result)) { expression.setLength(0); tvDisplay.setText(result); } else { expression.setLength(0); expression.append(result); tvDisplay.setText(result); } } private void clear() { expression.setLength(0); tvDisplay.setText(0); } }这里有两个输入校验值得注意。第一个校验拦截了“以运算符开头”的输入比如刚打开应用直接按“”不会产生 “2” 这种非法表达式。第二个校验禁止连续运算符比如输入 1 之后连按两次“”第二次会被忽略这样就不会出现 “12” 导致求值时栈弹出空元素崩溃。calculate 方法里用了一个技巧如果结果是正常数字就把结果重新写回 expression这样用户可以在结果上继续运算。比如按下 “5 3 ” 得到 8再按 “” 和 “2” 再按 “”实际上算的是 “82”符合真实计算器的操作习惯。如果结果是错误提示就直接清空表达式防止用户在“除数不能为 0”后面继续追加数字。这段代码里的 v - 写法是 Java 8 Lambda。Android Studio 默认支持如果你们的课程环境还停在 Java 7把 lambda 换成匿名内部类效果一样只是代码会长一点不影响逻辑。绑定按钮时有一个 Android Studio 快捷键可以派上用场选中重复代码后按 CtrlAltM 抽取方法可以把初始化绑定的代码收拢成一个 setupButtonListeners() 方法让 onCreate 更短。4. 计算器避坑指南答辩前必须自查的 5 个典型问题下面这几个坑单独拿出来讲因为它们不是在正常点击路径上能碰到的而是要在边界操作下才会暴露。我按出现频率从高到低排列每个都按“现象、原因、解决”展开如果你能把这五关都过掉答辩时的稳定性已经超过大多数同学。4.1 按钮被拉伸变形layout_weight 没有配合 0dp现象同一行里四个按钮宽度不一样文字被截断或者换一台分辨率不同的模拟器后布局完全变形。原因Button 默认宽度是 wrap_content文字短的按钮就窄文字长的按钮就宽。有人为了让按钮看起来整齐直接写死 layout_width60dp这在当前模拟器上没问题切到高密度屏幕或大字体模式马上翻车。解决一行内所有 Button 的 layout_width 都写 0dplayout_weight 写 1。LinearLayout 会按权重把剩余空间均分给子 View。“0dp weight1”是线性布局做等比划分的标准写法不要改成 wrap_content也不要写成固定 dp否则权重分配会被系统忽略。高度方向的等分同理每行 LinearLayout 设 height0dp 和 layout_weight1四行按钮自动均分屏幕高度不会出现上面一行被压扁、下面一行空一大块的情况。注意如果一行里有按钮的宽度是 0dp另一行里是 wrap_content两侧对齐一定会失败。检查时直接看四个按钮是否全部是“0dp weight1”。4.2 旋转屏幕后计算结果归零Activity 重建导致数据丢失现象横屏算出一个结果转回竖屏后屏幕变成 0之前输入的内容全部消失。原因旋转屏幕时 Android 会销毁当前 Activity再重新创建一个。MainActivity 里的 expression 只是内存对象重建后自然被清空。这不算 bug而是 Android 生命周期机制不理解这个机制花多少时间重写计算逻辑都没用。解决把需要保留的字符串存进 onSaveInstanceState 的 Bundle在 onCreate 里判断 savedInstanceState 非空后恢复。真正需要保存的数据只有两个当前表达式内容和屏幕上显示的文本。这套代码不长第 5 章会直接给出。如果用的是 Kotlin也可以考虑 rememberSaveable但原理一样核心是“字符串在重建后要能塞回来”。4.3 连续点运算符直接闪退输入校验缺失现象按 1、、、2、 之后App 闪退Logcat 里出现 NumberFormatException 或者 EmptyStackException。原因两个加号都被拼进表达式变成“12”。tokenize 时第二个加号前面没有数字求值阶段栈里没有足够的操作数pop 时抛出异常。如果不加校验靠求值器兜底是兜不住的。解决在 appendInput 的入口做两层校验。第一层expression 为空时不允许输入运算符第二层最后一个字符已经是运算符时新的运算符直接忽略。这样输入层面就把非法组合挡掉了求值器永远拿到的都是数字和运算符交替出现的合法表达式。4.4 5÷2 显示 2 而不是 2.5整数除法陷阱现象5 ÷ 2 的结果显示 29 ÷ 4 显示 2但预期应该是 2.5 和 2.25。原因求值过程用了 int 类型Java 整数除法会直接丢弃小数部分。有的同学在 tokenize 之后用 Integer.parseInt 把数字转成整数或者在栈里存 int都会触发这个坑。解决求值器从头到尾使用 double 运算只在显示阶段做格式化。判断结果是整数时转成 long 去掉小数点不是整数就保留小数。注意顺序是先 double 运算再格式化不能在输入时就转 int否则小数信息在第一步就丢了。手写测试时5÷2 必须显示 2.5这是计算器及格的基本线。4.5 0.10.2 显示一长串小数浮点精度问题现象0.1 0.2 显示成 0.30000000000000004看起来像程序算错了。原因double 用二进制表示十进制小数0.1 和 0.2 都无法被二进制精确表示相加之后误差被放大。这个问题在 Java、Kotlin、C、JavaScript 里都存在不是代码听写错误。解决在 formatResult 里用 DecimalFormat(0.######) 限制最多 6 位小数既有精度又不会暴露二进制尾巴。如果用 BigDecimal注意要用 BigDecimal.valueOf 而不是 new BigDecimal(double)前者会按十进制字符串精确转换后者会把二进制近似值整个带进来得到一长串更离谱的数字。这五个问题处理完之后我还会做一轮乱按测试切换屏幕、连续运算符、除零、表达式为空时按等号把所有能想到的反常操作都过一遍。因为这些边界行为不会在正常开发时出现但它们恰恰是答辩老师最爱动手试的地方。5. 答辩前的最后一道工序横竖屏状态保存与演示自检到这一步计算器已经能正常计算但还差一个容易被老师当场抓住的细节旋转屏幕后状态丢失。这一章补上状态保存再给一套可以照着做的自检动作。5.1 onSaveInstanceState 保存表达式和显示文本在 MainActivity 里加一个方法并在 onCreate 里加一小段恢复逻辑Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(expression, expression.toString()); outState.putString(display, tvDisplay.getText().toString()); }Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ...原有按钮绑定代码... if (savedInstanceState ! null) { expression.append(savedInstanceState.getString(expression, )); tvDisplay.setText(savedInstanceState.getString(display, 0)); } }这段代码只保存两个字符串不涉及任何复杂对象。旋转屏幕时系统先把当前表达式和显示内容放进 BundleActivity 重建之后再读回来填进控件。points: 1onSaveInstanceState 只在异常销毁时被调用正常按返回键不会触发2恢复逻辑要放在 setContentView 和 tvDisplay 的 findViewById 之后否则控件还没创建就 setText 会空指针。如果是内存不足导致 Activity 被系统回收返回时也会走同一套恢复路径所以这招不是只防旋转而是通用的状态恢复方案。5.2 三个破坏性自检动作我现在的习惯是交付前把手机转两圈然后按下面顺序测试第一算出一个结果后旋转屏幕确认结果还在第二连续输入 1 2 确认不会闪退第三按下 5 ÷ 0 确认屏幕上出现“除数不能为 0”而不是乱码。这三个动作都是破坏性操作正常使用不会碰到但答辩老师为了验证代码质量经常专门挑这种边缘路径按。如果你还有余力可以再加一个历史记录功能用一个 ArrayList 保存每次等号之前的表达式和结果下方加一个 ListView 展示。这个功能不需要数据库内存列表就能撑住演示场景除非老师要求重启 App 后记录还在才需要用 SharedPreferences 做持久化。选择简易计算器作为安卓期末大作业最大的好处是东西不大但五脏俱全。布局考到了权重和适配逻辑考到了栈和表达式解析生命周期考到了状态恢复这三块正好覆盖课程的主要考点。把边界条件处理好答辩时被追问“为什么这里不会崩溃”你能讲出校验和异常处理的理由比报出一个页面更让老师信服。希望帮到你。本文还有配套的精品资源点击获取