简介《Android Studio实现简单计算器功能》PDF文档面向Android开发入门者完整演示了基于Android Studio从创建新项目、设计计算器界面到处理按钮点击事件的全过程。资源为单个PDF文件容量仅109KB便于下载学习。文档详细介绍了使用GridLayout布局搭建6行4列计算器界面的过程并在MainActivity中通过findViewById获取组件实例、设置监听器以及switch逻辑判断实现数字输入、四则运算、小数点和清除等核心功能同时给出了布局文件与控制文件的关键代码片段。内容覆盖项目创建、布局设计、组件关联和事件处理等关键环节步骤清晰、示例完整可直接作为课程设计或Android入门练习的参考。目前已有3708人学习下载对于希望快速掌握Android界面编程与事件监听机制的初学者是一份值得参考的实操资料。1. 一个计算器 Demo把 Android 入门最该练的三件事全串起来了提到 Android 入门项目计算器几乎是绕不开的练手题但很多初学者照着教材敲完要么布局渲染缺行少列要么等号一按直接闪退要么结果算错还不知道问题出在哪。这份资源是一个基于 GridLayout 网格布局的简单计算器实现核心就三件事用 6 行 4 列的布局文件排界面在 MainActivity 里通过 findViewById 绑定所有组件再用 switch 语句做输入分发和四则运算。它解决的正是「布局文件怎么写、事件监听怎么绑、运算逻辑怎么分」这三个入门阶段的硬骨头适合刚学完 Activity 生命周期、对 LinearLayout 和 RelativeLayout 有点基础、想试试网格布局的开发者。2. 布局文件拆解GridLayout 的 6 行 4 列怎么排才不翻车2.1 先理解 GridLayout 的填充规则默认按行排列列数由 columnCount 控制这个计算器的界面用 GridLayout 实现核心属性只有四个android:rowCount6、android:columnCount4、android:layout_columnSpan和android:layout_rowSpan。GridLayout 的排列规则和 LinearLayout 不太一样它默认是从左到右、从上到下依次填充格子每填满 columnCount 指定的列数就自动换行。也就是说只要你在 XML 里按顺序写子组件不需要手动指定每个按钮的 grid row 和 column系统会自动把第 1 个按钮放到第 1 行第 1 列第 2 个放到第 1 行第 2 列以此类推。有个细节值得注意GridLayout 里按钮如果不写layout_width和layout_height默认会按wrap_content处理按钮会根据文字内容自适配大小。但如果你希望按钮铺满格子还是要显式设置layout_widthmatch_parent之类的属性。教材里有些按钮没写宽高也能跑是因为 GridLayout 的测量规则会分配剩余空间但这依赖具体机型建议还是写清楚。2.2 完整布局文件第一行显示、第二行清除、后四行放 16 个按钮以下是整理后的activity_main.xml核心代码去掉了原文里部分冗余注释补上了宽高属性和更清晰的 id 命名方便你直接对照着敲!-- 设置一个网格布局网格为6行4列 -- GridLayout android:layout_widthmatch_parent android:layout_heightmatch_parent android:columnCount4 android:rowCount6 !-- 第一行显示计算过程及结果 -- TextView android:idid/textView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_columnSpan4 android:textSize26sp / !-- 第二行清除按钮横跨4列 -- Button android:idid/butCancel android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_columnSpan4 android:text清除 android:textSize26sp / !-- 第三行7 8 9 -- Button android:idid/but_7 android:layout_widthmatch_parent android:layout_heightwrap_content android:text7 android:textSize26sp / !-- 此处省略 8、9 的按钮声明写法与 7 一致 -- Button android:idid/but_add android:layout_widthmatch_parent android:layout_heightwrap_content android:text android:textSize26sp / !-- 第四行4 5 6 - -- !-- 第五行1 2 3 * -- !-- 第六行. 0 / -- /GridLayout从布局结构能看出一个关键设计butCancel和textView都设置了layout_columnSpan4意思是这两个组件横向占满 4 列。所以虽然 GridLayout 是 6 行 4 列但实际可用控件区域是「第一行显示、第二行清除、第三到六行每行 4 个按钮」一共能放下 16 个按钮刚好对应数字 0-9、小数点、加减乘除和等号。2.3 一个容易踩的布局坑原来自动生成的抬头不能乱改原文里提到一个很重要的翻车经历打开布局文件时一开始把 Android Studio 自动生成的抬头也就是根布局的声明部分改了结果界面只能显示文本标签和清除按钮下面 16 个按钮全不见了。这个问题的根源在于当你把根布局从默认的某种布局改成 GridLayout 时如果直接去动抬头里已有的命名空间声明或者根布局的层级关系会导致 XML 解析不完整后面的子组件被忽略。我一般会这样规避不要去改新建项目时自动生成的根布局声明直接在setContentView对应的布局文件里把原来的根布局标签替换成GridLayout然后保留原有的xmlns:android和xmlns:tools命名空间不动只在里面追加android:rowCount和android:columnCount。这样既不破坏 XML 结构又能顺利切换布局类型。3. MainActivity 绑定与事件分发从 findViewById 到 switch 的完整链路3.1 组件声明与初始化16 个按钮逐一绑定控制文件的核心任务是把布局文件里的每个 id 对应到 Java 对象。原文的做法很直接先声明一个 TextView、一个清除按钮和 15 个运算/数字按钮的成员变量然后在onCreate里逐个findViewById。代码看着冗长但每一步都好理解适合入门public class MainActivity extends AppCompatActivity { private TextView txt; private Button butp, but1, but2, but3, but4, but5, but6; private Button but7, but8, but9, but0, buta, buts, butm; private Button butdiv, butdopt, buteq; double num1 0, num2 0; double result 0; int op 0; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txt findViewById(R.id.textView); butp findViewById(R.id.butCancel); but1 findViewById(R.id.but_1); but2 findViewById(R.id.but_2); // 这里省略 but3 到 but9 的绑定 but0 findViewById(R.id.but_0); buta findViewById(R.id.but_add); buts findViewById(R.id.but_sub); butm findViewById(R.id.but_mut); butdiv findViewById(R.id.but_div); butdopt findViewById(R.id.but_dopt); buteq findViewById(R.id.but_equ); butp.setOnClickListener(new mClick()); but1.setOnClickListener(new mClick()); // 所有按钮统一绑定同一个监听器实例 buteq.setOnClickListener(new mClick()); } }这段代码的逻辑不复杂把布局里的控件引用存到成员变量然后统一给所有按钮设置同一个mClick监听器。用同一个监听器实例的好处是不用写 17 个setOnClickListener的匿名内部类代码量更少也方便在监听器内部用switch统一分发。注意findViewById在 API 26 之后返回的是泛型类型不需要强转这是和教材旧代码的一个小差异。3.2 mClick 监听器switch(v.getId()) 处理所有点击事件监听器的设计是理解整个程序的关键。它实现View.OnClickListener接口在onClick(View v)方法里通过v.getId()拿到被点击按钮的 id然后switch分发。这样写的好处是所有按钮的点击逻辑集中在一个方法里新增按钮时只需要加一个case分支不用到处找监听器。class mClick implements View.OnClickListener { public void onClick(View v) { switch (v.getId()) { case R.id.butCancel: // 清除直接清空显示文本 txt.setText(null); break; case R.id.but_1: // 获取当前显示内容拼上数字 1再显示回去 String str1 txt.getText().toString(); str1 1; txt.setText(str1); break; case R.id.but_add: // 运算符先把当前屏幕上的数字存到 num1再等下一个数字 String stradd txt.getText().toString(); if (stradd.length() 0) { return; } num1 Double.valueOf(stradd); txt.setText(null); op 1; break; // 其余 case 分支结构一致不再重复列出 } } }这个方法里有个值得注意的细节数字按钮的处理方式是「读当前文本 → 拼接新字符 → setText 回显」而不是维护一个独立的内存变量。这样做的好处是简单直观你按了什么屏幕就显示什么坏处是如果用户在按完等号后继续按数字字符串会直接拼在结果后面比如算出121224后再按5显示变成245。这个问题到第 6 章会专门讲怎么改。3.3 运算符与等号的状态流转用 op 变量记住当前操作整个计算器的状态机可以概括成一句话按数字输入 → 按运算符时把当前值存进 num1 并记住 op → 再输入数字 → 按等号时取 num2 参与计算。这里的op变量是核心的状态标记op1表示加法op2表示减法op3表示乘法op4表示除法。这个设计的优点是直观每个分支对应一种运算出问题好排查缺点是它不支持连续运算。比如输入532程序会先把5存进 num1op1然后你按了3之后屏幕显示3再按时会把3存进 num1相当于把之前的5覆盖了。到第 6 章我会给出支持连续运算的状态机改进方案。4. 运算逻辑详解数字解析、等号计算与除零保护4.1 数字输入与 Double 转换为什么不直接用整型在运算符分支里代码用Double.valueOf(stradd)把屏幕上的字符串转成 double。这里选double而不是int是有原因的相除可能出现小数5/2如果用整型计算会得到2显然不符合计算器预期。但用 double 也会引入一个新问题——浮点精度。比如0.1 0.2在 double 下会得到0.30000000000000004这在计算器场景下虽然不致命但如果你要做一个正式的 App还是需要做精度处理。case R.id.but_equ: String strequ txt.getText().toString(); // 这里没有做空判断如果直接按等号会出现 NumberFormatException num2 Double.valueOf(strequ); txt.setText(null); switch (op) { case 1: result num1 num2; txt.setText(String.valueOf(num1) String.valueOf(num2) String.valueOf(result)); break; case 2: result num1 - num2; txt.setText(String.valueOf(num1) - String.valueOf(num2) String.valueOf(result)); break; case 3: result num1 * num2; txt.setText(String.valueOf(num1) * String.valueOf(num2) String.valueOf(result)); break; case 4: if (num2 0) { txt.setText(除数不能为0); } else { result num1 / num2; txt.setText(String.valueOf(num1) / String.valueOf(num2) String.valueOf(result)); } break; default: result 0; break; } break;这里有两个边界问题值得注意。第一个是「直接按等号」屏幕上没有任何数字时Double.valueOf(strequ)会抛NumberFormatException因为空字符串转不了 double。原文的运算符分支做了非空判断但等号分支漏了。第二个是除零保护虽然写了但num2 0的判断用的是浮点相等比较这在 double 场景下不可靠。最稳妥的做法是先用 BigDecimal 或者把输入解析成字符串判断是否为 0而不是转成 double 再比。4.2 结果显示手动拼接等式的思路与局限原文在等号分支里用String.valueOf(num1) String.valueOf(num2) String.valueOf(result)的方式显示完整等式。这个做法的好处是用户在屏幕上能看到完整的计算过程而不仅仅是一个孤零零的结果坏处是代码重复度很高每个分支都要写一遍拼接逻辑。我会改成这样先用一个calculate()方法返回结果字符串再统一拼接。比如String expression num1 opSymbol num2 result; txt.setText(expression);这样不仅代码更干净后续如果要加括号、加百分号、加历史记录也只需要改calculate()内部逻辑不用动每个case分支。这个重构思路建议你在跑通原版代码之后做一遍能加深对「逻辑集中」的理解。4.3 一个隐藏的坑txt.setText(null) 到底清没清原文多处用了txt.setText(null)来清空显示。这个写法其实有隐患setText(null)在 TextView 内部会做一些特殊处理某些机型上可能不会按预期清空文本或者导致后续getText().toString()拿到一个特殊值。我建议一律用txt.setText()来清空语义更明确也避免空指针相关的边缘问题。5. 避坑记录五个必踩的坑与解决方式5.1 改了布局抬头只剩显示和清除按钮现象运行后界面只显示顶部的 TextView 和「清除」按钮下面 16 个按钮全部消失没有报错。原因手动替换根布局标签时把原有的命名空间声明或者根标签属性误删了导致 XML 解析到某一层后直接忽略后续子组件但不会报崩溃。解决新建项目时自动生成的根布局声明保留不动只把标签名改成GridLayout在根标签内增加rowCount和columnCount属性然后继续写子组件。改完记得用布局编辑器右侧的 Preview 预览确认 6 行都在。5.2 用 equals(null) 判断空文本结果完全不生效现象点击运算符时明明屏幕上没有输入任何数字程序却没有 return而是拿着空字符串去Double.valueOf直接闪退。原因.equals(null)返回的是 false因为空字符串本身是一个对象和 null 是两个概念。原文在运算符分支写的是if(stradd.equals(null))这个条件永远是 false等于没写。解决改成stradd.length() 0或者stradd.isEmpty()也可以在解析前 catchNumberFormatException。等号分支建议同样加一层空判断。5.3 连续按运算符前面的数字被静默覆盖现象输入5 3 按第二个加号时,程序把3存进了 num1之前输入的5丢了最终结果变成325这种错误结果。原因所有运算符分支都执行「读取屏幕文本 → 写入 num1 → 清屏」没有判断 num1 里是否已经有值。第二次按运算符时num1 被无条件覆盖。解决在运算符分支里加一个状态判断只有 num1 为空时才赋值否则先把上一次的运算结果算出来再继续。具体实现见第 6 章的状态机方案。5.4 小数点可以重复输入按出 1.2.3现象连续按小数点TextView 显示1.2.3到等号阶段Double.valueOf(1.2.3)直接崩溃。原因数字拼接逻辑没有检查当前文本是否已包含小数点。解决在case R.id.but_dopt分支里用strd.contains(.)做判断包含则忽略该次点击。这是计算器类 App 最基本的输入合法性校验。5.5 除零提示之后点数字屏幕清空变成只有新数字现象输入8/0屏幕显示「除数不能为0」此时再按数字键 5屏幕变成5之前输入的8和/全没了。原因除零异常时没有重置状态op 还停留在旧值num1 和 num2 的值还残留在内存里。下一次按键行为建立在脏状态之上。解决除零提示后立即重置 num1、num2、op 和屏幕文本让计算器回到初始状态。这个习惯对所有异常分支都适用——报错之后一定要把状态清干净不然就是一路脏数据跑下去。6. 让计算器支持连续运算小状态机改造与验证方法如果你已经照着原版代码跑通了一定会对「不能连续运算」「等号后不能接着输入」这两个限制很不爽。我建议做一个小改造把原来的op单一变量升级成两个状态位。一个是lastOperator记录最近一次按下的运算符一个是waitingForOperand标记当前是否在等待输入新数字。改造思路是这样的初始时waitingForOperand false。按数字时如果waitingForOperand为 true就直接用新数字覆盖屏幕显示不再拼接否则沿用原有的拼接逻辑。按运算符时把当前屏幕值写入 num1设置lastOperator再把waitingForOperand置为 true。按等号时只有lastOperator不是 0 才执行计算算完后把结果写回屏幕保持waitingForOperand true这样用户可以直接按下一个运算符继续算。case R.id.but_1: if (waitingForOperand) { txt.setText(1); // 上一次操作结束后直接开启新数字 waitingForOperand false; } else { txt.setText(txt.getText().toString() 1); } break;验证方法也很简单跑一遍5 3 8确认结果正确后不按清除继续按 2 看能不能得到10。再去按8 / 0 确认弹出除零提示后按清除再输入1 1 看是否回到正常状态。这三条用例能覆盖绝大多数状态流转路径。从那以后我每写完一个计算器 Demo都会强制走一遍「连续运算 → 错误输入 → 清除恢复」这三个流程确认状态没有残留再收工。希望这篇拆解能帮你把布局、监听、状态管理这三层基本功练扎实少走我当初走过的弯路。本文还有配套的精品资源点击获取