Android Studio计算器App开发实战:从布局到逻辑的完整指南
2026/9/8 4:04:03 网站建设 项目流程

简介:面向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”。原因在于新版模板默认使用Compose(Jetpack 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安装应用。开启后再点Run,App就能直接装到手机上了。

3. UI布局:从XML布局谈设计取舍

3.1 布局结构拆解

计算器的界面分为两个区域:上面是显示结果的文本框,下面是一排排按键。显示区我用一个EditText,设置成不可编辑(android:focusable="false"),右对齐,字号稍微大一点,这样既模拟了真实计算器的显示效果,又避免了弹出软键盘干扰操作。

按键区我用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,并且设置maxLines="1",让超长内容自动向左滚动,而不是换行。这样在横竖屏切换时表现都稳定,不会出现按钮挤压或内容溢出。

个性化提示:网上有人问“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; } }

这个方案的优点是写起来快,几十行代码就能跑通。缺点也明显:它不支持运算符优先级,用户输入“1+2×3”得到的是9而不是7;也没有括号功能,稍微复杂一点的表达式就无能为力了。如果只是为了演示按钮事件,这个方案足够;但要做成一个像样的计算器,它撑不起场面。

4.2 支持优先级的中缀转后缀求值

为了让计算器更“专业”,我换成逆波兰表达式(后缀表达式)的方案。核心思路是:先把用户输入的中缀表达式(例如“1+2×3”)转换成后缀表达式(例如“1 2 3 × +”),再对后缀表达式依次入栈、出栈求值。

转换依赖于一个运算符栈:

private String infixToPostfix(String expression) { StringBuilder result = new StringBuilder(); Stack<Character> 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:configChanges="orientation|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.1+0.2的结果在Java里是0.30000000000000004,直接显示会很难看。我的做法是在显示结果时用BigDecimal的stripTrailingZeros和toPlainString来格式化,或者简单点:只保留结果需要的小数位数,比如判断结果是否是整数,是就显示整数值,否则显示保留10位小数的值。这个细节没处理好的话,会在用户第一次输入小数时就露馅。

结尾:一个小经验

我做完这个计算器之后最大的感受是:看似几百行代码的小项目,真正让它“好用”的部分全在细节里。比如输入框如何显示长表达式、除零错误怎么提示、屏幕旋转怎么保存状态,每一项单拎出来都不难,但组合在一起就需要对整个Activity生命周期和Android视图机制有完整的认识。如果你也是刚接触Android开发,建议不要急着抄代码,先动手把界面搭出来,再逐个实现监听器和计算逻辑。跑通一遍之后,再去改一改UI风格或者加个历史记录功能,你会发现自己对Android应用开发的整体理解会深入很多。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询