
简介面向Android开发初学者的计算器App完整工程源自南邮课程设计覆盖从UI布局到事件处理的完整开发流程。该项目支持加减乘除、回退清空以及sin、cos、tan、lg、ln等科学运算同时处理小数计算并保证结果准确。包内含2910个文件以flat、xml配置、Java源码、class与dex编译文件为主另附APK安装包整体约26.19MB带Gradle构建体系可直接导入Android Studio运行。已有8300人在CSDN学习下载。通过本项目可深入掌握按钮、文本显示等常见UI控件的事件绑定方式理解表达式解析与运算逻辑也能对照项目结构学习Android工程的组织规范适合正在完成类似课程作业或希望快速上手App开发的学习者参考。 作为常年用Android Studio捣鼓小项目的开发者我几乎每隔一段时间就会拿“计算器”当练手题材。这个项目看着简单但要把界面布局、事件分发、逻辑状态这几个环节串起来真动手做一遍才会发现坑不少。今天就把我实现的一个简易计算器App从设计到上手的完整过程拆开聊希望给你一条能直接照着走的路。1. 计算器项目到底在练什么核心需求与方案选型1.1 为什么计算器是练手首选很多教程把计算器当成“Hello World”之后的第二个项目不是没有道理。一个计算器App麻雀虽小却覆盖了移动开发最基本的几个环节XML布局如何组织、控件如何绑定、Java或Kotlin代码如何响应点击事件、程序状态在屏幕旋转时如何保持。这些能力是后续做任何复杂App的地基。从需求层面看计算器要解决的无非就是三个问题数字和运算符怎么录入、表达式怎么计算、结果怎么展示。听起来简单但“用户连续点击多个按钮”这个行为背后是大量的状态同步和边界判断。比如用户点了个“1”又点了“”再点“2”程序需要知道当前显示的数字应该从1变成2还是从1跳到2按下“”时系统要能正确处理上一次运算的结果。如果不先做设计直接上手写监听器很容易在代码里塞满if-else最后自己都看不懂。1.2 技术栈选择Java还是Kotlin现在的Android Studio新版本默认推荐Kotlin但是考虑到网上大量教程、Stack Overflow回答还是Java写的我这次选择用Java实现。理由很简单遇到问题时搜索到的资料更全而且Java的静态类型对初学者更友好语法上没那么多的“魔法糖”。如果你已经把Kotlin的协程、扩展函数这些概念吃透了用Kotlin重写一遍逻辑也不难核心思路完全一致。界面上我采用线性布局LinearLayout嵌套网格布局GridLayout的方式而不是直接用ConstraintLayout一把梭。计算器的按键天然是网格状的GridLayout让按钮的排列和维护变得非常直观外层用垂直的LinearLayout负责把显示区域和按键区域分开代码可读性也更好。提示这里不是为了追求炫酷的界面特效而是为了让逻辑清晰、易于调试。等你的计算器功能稳定了再换成ConstraintLayout做适配优化也不迟。2. 环境准备与工程创建避坑指南2.1 下载、安装与SDK配置开始之前先把开发环境搞定。Android Studio可以直接从官网下载注意选择和你操作系统匹配的版本Windows和macOS的安装包不一样。安装过程本身没有太多坑重点在于SDK的配置——第一次启动会提示下载SDK这一步在网络状况不佳时非常折磨人。我的建议是安装时记住SDK的存放路径后续如果需要更换或者排查问题直接看这个目录。创建的工程建议选择“Empty Views Activity”模板而不是“Empty Activity”。原因在于新版模板默认使用ComposeJetpack Compose虽然Compose是趋势但计算器这种小项目用传统XML布局更容易理解视图系统的运作机制也方便对照学习事件监听、findViewById这些经典用法。注意创建新项目时如果长时间卡在Gradle同步上多半是Gradle发行包或依赖库下载缓慢。可以先在工程的gradle-wrapper.properties里修改distributionUrl指向国内镜像或者手动下载对应版本的Gradle压缩包放到Gradle的wrapper目录下能省下不少等待时间。2.2 真机调试小米手机连接经验很多人在模拟器上跑得好好的一接真机就出问题。尤其是小米手机默认情况下USB调试模式需要在“设置—更多设置—开发者选项”里打开同时还要在“开发者选项”里关闭“MIUI优化”否则Android Studio可能识别不到设备或者即使识别到了也没法安装App。连接后第一次运行手机会弹出“允许USB调试吗”的提示务必勾选“始终允许”。如果Android Studio仍然显示“No Devices”检查一下电脑上的USB驱动。Windows系统一般会自动安装但偶尔需要手动到设备管理器里更新驱动。macOS则相对省心插上就能识别。我这里还遇到过一个细节连接小米手机后Android Studio的Run按钮变灰。排查了一圈发现是手机上的“USB安装”权限没开启。这个选项也在开发者选项里弹窗名字叫“USB安装”需要允许通过USB安装应用。开启后再点RunApp就能直接装到手机上了。3. UI布局从XML布局谈设计取舍3.1 布局结构拆解计算器的界面分为两个区域上面是显示结果的文本框下面是一排排按键。显示区我用一个EditText设置成不可编辑android:focusablefalse右对齐字号稍微大一点这样既模拟了真实计算器的显示效果又避免了弹出软键盘干扰操作。按键区我用GridLayout列数设为4然后依次添加数字键0-9、小数点、加减乘除、等号、清空和退格键。GridLayout的好处是不用像LinearLayout那样搞多层嵌套直接在XML里配置columnCount和rowCount控件会自动按顺序填充。如果使用的是老版本Android Studio可能默认的GridLayout来自support库需要额外添加依赖。新版已经把它整合进框架里了直接在XML里用android.widget.GridLayout就行。3.2 控件尺寸与兼容性细节按钮的高度我设为wrap_content配合layout_weight或者设置最小高度来保证不同屏幕上的可点击区域不会太小。“可点击区域”这个点很容易被忽略实际手机上的手指接触面积如果低于某个阈值用户会频繁误触。一般建议按钮高度不低于48dp我习惯用56dp实际操作下来手感更稳。还有字体大小和显示区域的padding字体太小在真机上看着费劲太大又会让长表达式显示不全。我通常把显示区的字体设为28sp-32sp左右padding给到16dp并且设置maxLines1让超长内容自动向左滚动而不是换行。这样在横竖屏切换时表现都稳定不会出现按钮挤压或内容溢出。个性化提示网上有人问“android studio怎么设置中文”如果你实在看不惯英文界面可以通过Settings里的Plugins搜索Chinese插件进行汉化。不过我建议初期尽量用英文界面因为查资料时你会发现绝大多数报错信息、论坛答案都是英文的看多了自然就习惯了。4. 计算逻辑实现三种方案与代码落地4.1 直观但简陋的链式计算最省事的实现方式是设置两个变量分别保存“当前输入的数字”和“上一次的运算结果”再设置一个字符串变量保存“尚未执行的运算符”。用户每点一次数字就把当前显示的数字往后追加每点一次运算符就先把之前的运算结果算出来再记录新的运算符。这种方案的代码如下private double currentValue 0; private double result 0; private String pendingOperation ; public void onNumberClick(String number) { String currentText display.getText().toString(); if (currentText.equals(0)) { display.setText(number); } else { display.setText(currentText number); } } public void onOperationClick(String operation) { double inputValue Double.parseDouble(display.getText().toString()); calculate(inputValue); pendingOperation operation; display.setText(String.valueOf(result)); } private void calculate(double inputValue) { switch (pendingOperation) { case : result inputValue; break; case -: result - inputValue; break; case *: result * inputValue; break; case /: if (inputValue ! 0) { result / inputValue; } else { display.setText(错误); } break; case : result inputValue; break; } }这个方案的优点是写起来快几十行代码就能跑通。缺点也明显它不支持运算符优先级用户输入“12×3”得到的是9而不是7也没有括号功能稍微复杂一点的表达式就无能为力了。如果只是为了演示按钮事件这个方案足够但要做成一个像样的计算器它撑不起场面。4.2 支持优先级的中缀转后缀求值为了让计算器更“专业”我换成逆波兰表达式后缀表达式的方案。核心思路是先把用户输入的中缀表达式例如“12×3”转换成后缀表达式例如“1 2 3 × ”再对后缀表达式依次入栈、出栈求值。转换依赖于一个运算符栈private String infixToPostfix(String expression) { StringBuilder result new StringBuilder(); StackCharacter stack new Stack(); for (char c : expression.toCharArray()) { if (Character.isDigit(c) || c .) { result.append(c); } else if (c () { stack.push(c); } else if (c )) { while (!stack.isEmpty() stack.peek() ! () { result.append( ).append(stack.pop()); } stack.pop(); } else { result.append( ); while (!stack.isEmpty() precedence(c) precedence(stack.peek())) { result.append(stack.pop()).append( ); } stack.push(c); } } while (!stack.isEmpty()) { result.append( ).append(stack.pop()); } return result.toString(); } private int precedence(char operator) { switch (operator) { case : case -: return 1; case *: case /: return 2; default: return -1; } }求值阶段遍历后缀表达式的每个token遇到数字就压栈遇到运算符就从栈里弹出两个操作数计算结果再把结果压回去。这里有一个需要小心的点出栈的左操作数和右操作数顺序问题。后缀表达式“1 2 -”实际上对应的是“1-2”而不是“2-1”。如果先弹出的是2再弹出的是1正确的写法应该是用后弹出的数先去运算否则减法或除法会得到完全错误的结果。4.3 更灵活的做法递归下降解析中缀转后缀比较绕而且如果要扩展功能比如支持一元负号、括号嵌套代码会越来越难维护。现代计算器App里我更推荐用递归下降解析器直接对中缀表达式求值。递归下降的核心是定义几个相互递归调用的函数表达式、项、因子。表达式由项加减组成项由因子乘除组成因子是数字或括号括起来的子表达式。这样写出来的代码天然支持优先级和任意嵌套的括号读起来也直白。private int index 0; private String input; public double evaluate(String expression) { this.input expression.replace( , ); this.index 0; return parseExpression(); } private double parseExpression() { double value parseTerm(); while (index input.length() (input.charAt(index) || input.charAt(index) -)) { char op input.charAt(index); double right parseTerm(); value (op ) ? value right : value - right; } return value; } private double parseTerm() { double value parseFactor(); while (index input.length() (input.charAt(index) * || input.charAt(index) /)) { char op input.charAt(index); double right parseFactor(); if (op *) { value * right; } else { if (right 0) { return Double.NaN; } value / right; } } return value; } private double parseFactor() { if (input.charAt(index) () { index; double value parseExpression(); index; // 跳过) return value; } StringBuilder number new StringBuilder(); while (index input.length() (Character.isDigit(input.charAt(index)) || input.charAt(index) .)) { number.append(input.charAt(index)); } return Double.parseDouble(number.toString()); }这个方案我实际跑下来非常稳。代码量也不多却解决了运算符优先级、括号嵌套、小数解析这几个核心问题。除法除零的情况用NaN表示UI层检测到结果不是数字就显示“错误”。5. 事件绑定与状态保存实操记录5.1 按钮监听器的绑定方式布局文件里给每个按钮设置android:onClick属性然后在Activity里定义同名方法是目前最省事的做法。但这样做有个隐患按钮多了之后Activity里的方法会很长逻辑容易纠缠在一起。我更推荐用setOnClickListener统一处理private void bindListeners() { View.OnClickListener numberListener v - { String digit ((Button) v).getText().toString(); appendToExpression(digit); }; View.OnClickListener operatorListener v - { String op ((Button) v).getText().toString(); appendToExpression(op); }; findViewById(R.id.btn0).setOnClickListener(numberListener); findViewById(R.id.btn1).setOnClickListener(numberListener); findViewById(R.id.btnPlus).setOnClickListener(operatorListener); findViewById(R.id.btnMinus).setOnClickListener(operatorListener); // 其他按钮同样设置 }这种方式的好处是代码结构清晰新增按钮只需要在布局文件里加一行、在绑定代码里加一行不需要到处复制粘贴监听器方法。5.2 防止表达式非法输入用户输入习惯千奇百怪程序必须能容错。我做了几层校验小数点不能连续输入同一个数字里也不能出现两个小数点。做法是点击小数点时检查当前数字是否已经包含小数点如果包含就直接忽略这次点击。运算符不能连续输入。比如用户已经点了“”又点了“×”应该用后者替换前者而不是生成“×”这种非法序列。表达式不能以运算符结尾就按等号。我会在等号事件里先做一次合法性校验不合法就提示“表达式错误”。这些校验看着琐碎却是决定一个App是否“靠谱”的分水岭。很多入门教程的试例代码都是能跑但没有做任何容错用户随手乱点几下就崩溃或输出个NaN体验极差。5.3 屏幕旋转导致的状态丢失问题默认情况下屏幕旋转会销毁Activity并重新创建。如果用户正在输入一个很长的表达式一转手机输入全丢了这是不能接受的。处理方案有两种。简单粗暴的做法是在AndroidManifest.xml里给Activity配置android:configChangesorientation|screenSize让屏幕旋转时Activity不重建。但这种方式治标不治本如果应用以后要适配多窗口或深色模式还是需要处理生命周期变化。更稳妥的做法是使用ViewModel保存数据public class CalculatorViewModel extends ViewModel { public String currentExpression ; public String currentResult ; }在Activity里通过ViewModelProvider获取这个实例所有输入都写进ViewModel的字段里而不是直接写在Activity的局部变量中。这样配置变更后新创建的Activity拿到的是同一个ViewModel实例数据自然就保留了。6. 常见问题与调试过程实录6.1 Gradle下载慢/依赖失败创建一个空项目卡在Gradle同步是很常见的事。除了前面说的修改distributionUrl之外还有一个技巧在项目根目录的build.gradle文件里把仓库地址extra.mavenCentral和google()换成国内可访问的镜像源。这个修改改的是repositories配置需要分别加在buildscript和allprojects两个节点下。改完之后重新Sync速度会有明显提升。注意不同的项目模板生成的build.gradle可能结构不同老版本用allprojects新版本用dependencyResolutionManagement修改时要对应到正确的位置。6.2 按钮点击无响应如果布局文件里按钮能显示但点击没有反应优先检查三件事第一按钮是否被其他控件遮挡可以检查布局文件的相对位置或使用Android Studio自带的Layout Inspector第二监听器是否真的绑定成功了在监听器回调里打Log确认第三是否在XML里给父布局设置了clickable属性导致点击事件被子View消费了。6.3 真机运行安装失败安装失败报错INSTALL_FAILED_UPDATE_INCOMPATIBLE通常是因为手机上已经装了同包名的App但签名不同。卸载掉旧的再安装即可。如果是INSTALL_FAILED_OLDER_SDK说明build.gradle里的minSdkVersion设得比手机系统版本还高调低minSdkVersion就行。小米用户还要注意MIUI系统对后台安装应用有比较严格的限制Android Studio安装App时弹出授权窗口要及时点允许。6.4 数字格式与精度问题计算浮点数时0.10.2的结果在Java里是0.30000000000000004直接显示会很难看。我的做法是在显示结果时用BigDecimal的stripTrailingZeros和toPlainString来格式化或者简单点只保留结果需要的小数位数比如判断结果是否是整数是就显示整数值否则显示保留10位小数的值。这个细节没处理好的话会在用户第一次输入小数时就露馅。结尾一个小经验我做完这个计算器之后最大的感受是看似几百行代码的小项目真正让它“好用”的部分全在细节里。比如输入框如何显示长表达式、除零错误怎么提示、屏幕旋转怎么保存状态每一项单拎出来都不难但组合在一起就需要对整个Activity生命周期和Android视图机制有完整的认识。如果你也是刚接触Android开发建议不要急着抄代码先动手把界面搭出来再逐个实现监听器和计算逻辑。跑通一遍之后再去改一改UI风格或者加个历史记录功能你会发现自己对Android应用开发的整体理解会深入很多。本文还有配套的精品资源点击获取