ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android简易计算器实战:从双栈算法到精美UI开发全解析

Android简易计算器实战:从双栈算法到精美UI开发全解析 简介面向安卓初学者的完整工程演示如何用安卓开发环境与Java语言编写一款仿MIUI风格的简易计算器重点覆盖界面布局、按钮样式、点击事件以及四则运算等核心环节。压缩包共957个文件大小21.35MB主要包含292个XML布局与资源文件、276张PNG图片、22个Java源码文件以及Gradle配置、JAR依赖库、可直接安装的APK等类型齐全、目录清晰便于按需查阅。目前已有2191人学习下载。工程内除MainActivity计算逻辑与activity_main布局外还提供自定义主题样式、构建中间文件及调试版安装包可对照理解安卓工程的编译与打包流程。通过学习可掌握约束布局、样式定制、事件监听和连续表达式解析等实用技能项目代码注释完整适合课程设计、实训作业或开发入门参考也可作为同类计算器界面设计的模板对提升安卓开发实战能力很有帮助。尤其适合需要提交安卓课程项目或快速搭建计算器应用的学习者下载后可直接导入运行边看边改快速上手。1. 别急着解压先把“计算器.zip”当作一条完整研发链路来拆直接说结论AndroidStudio 下载的“简易计算器精美UI.zip”真正值钱的不是那几十行加减乘除而是“界面、事件、状态”三件事在同一个 Activity 里怎么组织。这类 zip 资源包从网盘、论坛转手多次最大的问题不是代码跑不通而是工程结构与新版 Gradle 插件不兼容你辛辛苦苦解压、打开、同步红灯一排很多人在这里就放弃了。这篇实战笔记按照“先定产品规格再写布局再写逻辑最后美化与排错”的顺序把这个计算器项目拆到能复现的程度。适合刚学完控件的在校生、想做课程设计或入门作品的自学者也适合已经在上班但第一次从 zip 包接手别人项目的初级工程师。目标只有一个让这个压缩包里的计算器变成你亲手改过、能讲明白每一步的自己的作品。2. 先定计算器的“产品规格”功能清单与布局结构2.1 简易不等于凑合把用户故事写成四个规则打开项目开始敲代码之前最好先把“计算器应该长什么样、能做什么”写死。常见的错误是边写边加按钮最后布局换来换去代码一团乱麻。我一般会把需求钉成四条规则直接写进代码注释里第一只做整数的四则运算不做括号、不做幂运算、不做正负号切换。这不是偷懒而是让核心算法保持足够简单读者能一眼看懂栈是怎么工作的第二用户按“数字键”时输入区持续追加数字最多输入 9 位防止溢出第三用户按“运算符”时如果当前正在输入数字先把数字“落栈”再保存运算符第四按“”时把栈里的数字和运算符按优先级算完结果显示在输出区同时保留完整算式在表达式区。这四条规则回答了“按下去之后发生了什么”这个最根本的问题。很多从网上下载的计算器 zip 包代码逻辑是用连续 if 处理点击事件算 123 没问题但一遇到 23×4 这种混合运算就给出 20因为它是“按从左到右的顺序”硬算的——第一个加法结果 5然后乘以 4。所以拿到包之后先别急着改 UI先看看算法是“单步累加”还是“带优先级处理”。如果是单步累加你至少要能说出它的局限再去考虑是不是要升级成双栈方案。2.2 用 LinearLayout 拆键盘权重才是指纹级参数计算器的经典布局是“上屏 下键”我用一个纵向 LinearLayout 装两个区上半部分放表达式、结果显示下半部分放键盘。键盘区继续纵向拆分每一行是一个横向 LinearLayout行内的按钮用 layout_weight 均分行与行之间靠父容器的 weight 比例固定高度这样屏幕尺寸变化时键盘能自动拉伸而不是挤成一堆。以下是布局骨架四个按键一行总共五行代码结构完全对称!-- activity_main.xml 骨架 -- LinearLayout android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:backgroundcolor/calculator_bg LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight2 android:orientationvertical android:gravitybottom|end android:padding20dp TextView android:idid/tv_expression android:layout_widthwrap_content android:layout_heightwrap_content android:textSize24sp android:textColorcolor/text_secondary android:maxLines1/ TextView android:idid/tv_result android:layout_widthwrap_content android:layout_heightwrap_content android:textSize48sp android:textColorcolor/text_primary android:maxLines1 android:text0/ /LinearLayout LinearLayout android:layout_widthmatch_parent android:layout_height0dp android:layout_weight3 android:orientationvertical !-- 第一行C del % ÷ -- 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 android:tagclear android:onClickonButtonClick/ Button android:idid/btn_delete android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:textdel android:tagdelete android:onClickonButtonClick/ !-- 其余按钮按同样规则继续 -- /LinearLayout !-- 第 2 ~ 5 行结构完全相同只是按钮 tag 不同 -- /LinearLayout /LinearLayout这段代码里最关键的是两个layout_weight外层显示区与键盘区按 2:3 分配高度保证键盘不挤压结果区内层按键高度设为 0dp 再配合layout_weight1让每一行均分键盘高度。如果你想要 iOS 计算器那种“0 键占两列”的排布可以把最后一行里的 0 键宽度设为 0dp、layout_weight2小数点按钮权重 1逻辑上没有任何区别权重相加等于总权重就行。按钮上的tag属性和onClick是配套使用的。每个按钮的text是给人看的tag是给代码识别的身份标识。我习惯用语义化字符串而不是数字 idclear、delete、add、subtract、multiply、divide、equals以及 num0 到 num9。这样点击监听里不需要 20 个 if 分支去判断view.getId()一个switch (view.getTag().toString())就能全部收编。2.3 shape 与 ripple按下去要有“物理感”默认 Button 是直角、灰色、带一点阴影的 Material 风格直接拿来当计算器键帽视觉效果像“网页里没写样式的按钮”谈不上精美。我的做法是给每类按键单独写一个 shape 文件用圆角半径和颜色区分数字键、功能键、运算符键。!-- res/drawable/bg_button_operator.xml -- shape xmlns:androidhttp://schemas.android.com/apk/res/android android:shaperectangle solid android:color#FF9F0A / corners android:radius28dp / /shapeshape 文件只定义静态外观真正的按压反馈还得靠 selector这个稍后第四章重点说。这里要记住一个容易被忽略的参数android:stateListAnimator。Android 5.0 之后 Button 默认带高度阴影按下去时阴影会闪一下计算器密集连按时眼睛很累。在 selector 方案下我会在 Button 上写android:stateListAnimatornull把系统自带动画关掉只保留背景变化手感反而干净。3. 让按钮“动”起来统一点击事件与表达式逻辑3.1 二十个按钮只写一个监听方法xml 里已经给每个按钮的android:onClick指定了onButtonClick所以 Activity 里只需要实现这一个方法。很多老项目的 zip 包会用匿名内部类给每个按钮单独setOnClickListener代码动辄三百行维护起来非常痛苦。统一入口的好处是所有点击行为集中管理后续加“退格”“括号”时只需在 switch 里补一个 case。public class MainActivity extends AppCompatActivity { private final StringBuilder currentNumber new StringBuilder(); private final StringBuilder expression new StringBuilder(); private final StackDouble numbers new Stack(); private final StackCharacter operators new Stack(); private TextView tvExpression; private TextView tvResult; private String lastOperator ; private double lastOperand 0.0; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvExpression findViewById(R.id.tv_expression); tvResult findViewById(R.id.tv_result); } public void onButtonClick(View view) { String tag view.getTag().toString(); if (tag.startsWith(num)) { inputDigit(tag.substring(3)); } else { switch (tag) { case add: inputOperator(); break; case subtract: inputOperator(-); break; case multiply: inputOperator(*); break; case divide: inputOperator(/); break; case clear: clearAll(); break; case delete: backspace(); break; case equals: calculateResult(); break; default: break; } } } private void inputDigit(String digit) { if (currentNumber.length() 9) { currentNumber.append(digit); expression.append(digit); updateResult(currentNumber.toString()); } } // 其余方法见后续小节 }设计时我把“数字输入”与“运算符输入”分成两个方法因为二者对状态的处理完全不同。数字是追加到currentNumber字符串和表达式串里运算符则负责清空当前数字缓冲区、触发栈顶计算、把新运算符压栈属于“切换操作”混在一个方法里写很容易出现边界遗漏。这里有个值得注意的点我把expression和currentNumber分开维护而不是让它们共享同一个 StringBuilder。原因是表达式区要展示完整算式比如“125×3”而结果区只显示正在输入的数字或最终结果。如果只用一个变量回退、替换运算符时会非常被动。3.2 双栈计算不写逆波兰也能处理乘法优先级简易计算器最常见的翻车点是处理“34×2”。如果不做优先级从左到右算答案是 14但标准答案是 11。要正确处理四则运算又不引入逆波兰表达式我推荐用“数字栈 运算符栈”的双栈方案这是考研题和编译器课上调度场算法的简化版代码量不大逻辑还特别适合新手读。核心操作是inputOperator和collapse输入新运算符时先把正在输入的数字压栈然后看栈顶运算符如果栈顶优先级不低于新运算符说明栈顶那个运算可以先算完于是弹栈、弹两个数字、算完压回去最后把新运算符压栈。等号则负责把栈里剩下的运算符全部算完。private void inputOperator(char op) { // 当前输入的数字落栈 if (currentNumber.length() 0) { numbers.push(Double.parseDouble(currentNumber.toString())); currentNumber.setLength(0); } // 处理连续运算符覆盖比如输入 5 - 3 if (currentNumber.length() 0 !operators.isEmpty()) { if (operators.peek() || operators.peek() - || operators.peek() * || operators.peek() /) { operators.pop(); } } // 栈顶优先级不低于当前运算符时先把栈顶算了 while (!operators.isEmpty() precedence(operators.peek()) precedence(op)) { collapse(); } operators.push(op); expression.append(op); } private void collapse() { if (numbers.size() 2) return; double b numbers.pop(); double a numbers.pop(); char op operators.pop(); double result apply(a, b, op); numbers.push(result); } private double apply(double a, double b, char op) { switch (op) { case : return a b; case -: return a - b; case *: return a * b; case /: if (b 0.0) { runOnUiThread(this::clearAll); return 0.0; } return a / b; default: return 0.0; } } private int precedence(char op) { if (op || op -) return 1; if (op * || op /) return 2; return 0; }逐段说明逻辑。inputOperator的第一步是“清空当前数字”这一步漏掉会导致数字重复入栈比如按“1234”如果不先把 34 落栈后面collapse会拿 34 和 12 做运算变成 123434。第二段 if 处理的是“5 - 3”这种输入此时currentNumber为空若栈顶已经是运算符直接弹出旧的、压入新的避免出现连续两个运算符在栈里导致collapse时 numbers 不足。collapse每次弹出运算符栈顶和最近的两个数字。这里需要想清楚顺序先弹出来的是 b后弹出来的是 a。四则运算不满足交换律减法尤其敏感8-3 如果写成 3-8 整个结果就反了。apply内部对除法做了除零保护这是计算器最基础的安全底线我直接把clearAll当成异常出口触发后全部复位界面回到初始状态。这套双栈逻辑比“把中缀转后缀再求值”少写一个栈也不用维护表达式树对一个目标为“简易”的计算器项目刚好到位。它的短板是暂不支持括号和一元负号但第四章之后你会发现这两个功能在实际使用中几乎不会被课程设计的评分老师测试。3.3 三次“事故现场”连按运算符、除零与浮点失真第一类事故是连按运算符比如“5 × 3”。如果代码不做任何防护inputOperator会把 × 和 一起压在栈里按等号时collapse先算 5 与谁运算 nobody。我的处理方式是在新运算符入栈前检查currentNumber长度如果为 0 且栈非空说明用户刚按过运算符此时直接弹出栈顶的旧运算符用新的替换。这个逻辑在上一段代码里已经有效果是“5 ×”会被修正为“5 ×”符合大部分真机计算器的输入习惯。第二类事故是除零。Android 原生双精度浮点里1.0 / 0.0不会抛异常结果是Infinity显示在屏幕上是英文“Infinity”对中文用户来说既突兀又不专业。更隐蔽的是0.0 / 0.0结果是NaN任何后续运算都会被污染NaN 加上任何数还是 NaN而且你很难从界面上一眼看出哪里出了问题。所以apply里单独判断 b 是否为 0.0直接清空界面是性价比最高的处理。第三类事故是浮点显示。计算0.1 0.2结果不是 0.3而是 0.30000000000000004。很多新手第一次看到这串长小数以为是算法写错了其实是 IEEE 754 浮点表示的天生缺陷。在calculateResult里我一般用BigDecimal处理最终结果并去掉末尾多余的 0或者使用格式化输出private String formatNumber(double value) { BigDecimal bd new BigDecimal(Double.toString(value)); return bd.stripTrailingZeros().toPlainString(); }BigDecimal(Double.toString(value))这一步很关键不要直接写new BigDecimal(value)。直接传 double会把二进制浮点数的“脏尾巴”也带进来先转成字符串再从字符串构建 BigDecimal等价于把 0.30000000000000004 重新解析成用户看得懂的 0.3。4. 精美UI是怎么“长”出来的主题、selector与配色4.1 主题选错再好看的布局也白搭很多人把这个 zip 包解压后跑起来发现界面一片惨白按钮完全没有设计感第一反应是去改 Button 的背景色。但真正的问题往往出在主题新版本 Android Studio 创建项目默认使用Theme.MaterialComponents.DayNight.DarkActionBar或Theme.Material3如果你拿到的是老 zip 包的 manifest里面可能写的是Theme.AppCompat.Light甚至直接是android:style/Theme于是 Material 组件特有的控件外观全部失效。我的做法是统一使用Theme.MaterialComponents.DayNight.NoActionBar然后再覆盖几个关键颜色!-- res/values/themes.xml -- style nameTheme.SimpleCalculator parentTheme.MaterialComponents.DayNight.NoActionBar item nameandroid:windowBackgroundcolor/calculator_bg/item item namecolorPrimarycolor/operator_color/item item namecolorSecondarycolor/function_color/item item nameandroid:statusBarColorcolor/calculator_bg/item /styleNoActionBar的作用是去掉默认标题栏。计算器这种全屏工具类应用标题栏只会占高度、破坏沉浸感。我建议在 values、values-night 两份目录里各放一份 themes.xmlDayNight 主题会根据系统深浅模式自动切换如果你只适配深色计算器直接在values/themes.xml里把windowBackground和statusBarColor写成深色即可。4.2 按压态、禁用态用 selector 描述按钮的一生圆角 shape 只定义了静态外观按压反馈需要 selector 文件。selector 是一个状态列表Android 根据按钮当前状态自动选择对应的 drawable!-- res/drawable/bg_button_digit.xml -- selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:state_pressedtrue shape android:shaperectangle solid android:colorcolor/digit_pressed / corners android:radius28dp / /shape /item item shape android:shaperectangle solid android:colorcolor/digit_normal / corners android:radius28dp / /shape /item /selectorselector 里item的排列顺序有讲究必须把state_pressed放在前面。只有它没有被命中时才会落到下面那个无状态条件的 item。如果你反着写按下状态永远被普通状态遮蔽按压反馈出不来界面看起来就像“死”的一样。运算符等号键的 selector 同理只是颜色换成橙色系。这里要额外补一个细节Button 默认会强制把英文字母转大写android:textdel显示出来是“DEL”。如果你想要小写必须在 Button 上写android:textAllCapsfalse。这是很多人忽略的“UI 设计死角”。4.3 四个颜色值见真章主色、功能色、背景、描边精美 UI 不是靠堆元素实现的而是靠克制。计算器这种每个屏只有二十来个按钮的界面颜色体系越少越高级。我采用 iOS 计算器同款四色方案背景深色、数字键深灰、功能键浅灰、运算符橙色。calculator_bg #1C1C1E 整个界面的底色 digit_normal #333333 数字键普通态 digit_pressed #5A5A5F 数字键按压态 function_normal #A5A5A5 功能键C、del、% operator_normal #FF9F0A 运算符和等号键 text_primary #FFFFFF 主数字文本 text_secondary #A5A5A5 表达式文本这套配色的核心逻辑是“明度差”数字键在背景上要有轻微凸出感所以用了高对比的深灰运算符是整屏交互的高频键用最亮的橙色做焦点功能键的明度介于二者之间既不像数字键那么沉闷也不像运算符那么抢眼。如果你拿到一个 zip 包发现它的计算器界面花花绿绿把 Button 背景直接设成了红黄蓝紫完全可以按上面的表重做一遍。对了有一个小技巧是运营商分区。第一行的 C、del 用浅灰最后一行的等号用橙色中间运算符用中灰或橙色描边。如果全键盘按钮都是一个颜色用户需要在心里给按钮“分类”操作成本就高颜色一变手指很快就形成“右上角是清空、右下角是等号”的肌肉记忆。4.4 为什么按钮看起来“值钱”间距、字重、禁止AllCaps颜色只是第一步真正让按钮产生“高级感”的是间距和字号。计算器按键之间如果贴得太紧密容易误触视觉上也显得廉价。建议每个按钮四周留 4dp 左右的 margin行与行之间不必额外加空因为 margin 已经提供了呼吸感。字号方面结果区 TextView 建议 48sp 起步表达式区 24sp数字按钮 32sp运算符按钮 36sp。这个参差感能让视线自然聚焦到结果区。字重上数字按钮和结果区用android:textStylebold而表达式区用普通字重形成“不重要的在过去重要的在现在”的层次。另外很多网上的代码会把 Button 的android:textSize和android:layout_height写死小屏手机上按钮塞不下、大屏手机上字小得可怜。正确做法是按钮高度交给layout_weight均分文字字号引用dimen资源而不是硬编码这样后续出平板适配只需要改一个 dimens.xml 文件。5. 经典避坑合集ZIP解压、Gradle同步与运行崩溃5.1 ZIP这一关GBK乱码、伪加密与命令行解压先说现象一下载来的 zip 包在 Windows 上解压项目里的注释、Java 文件的中文全部乱码Android Studio 打开后代码一团糟。原因很典型zip 规范里文件名可以用 UTF-8 编码也可以用本地编码国内的打包者很可能用了 GBK。解决方法是解压时强制指定编码Windows 下的 Bandizip 或 7-Zip 在解压选项里选“GBK”Linux 上可以这样unzip -O gbk 计算器.zip -d calculator-O gbk让 unzip 按 GBK 解释文件名解压到calculator目录之后再用 Android Studio 以 UTF-8 打开项目文件。如果你用了 macOS 自带 unzip可能不支持-O参数此时改用ditto -x -k或者直接换用 The Unarchiver 工具更省事。再说现象二zip 包解压时提示需要密码可你明明下载的是免费资源。这可能不是真的加密而是 zip 伪加密——打包者把压缩包的加密标志位置成 1数据本体并没有加密。用十六进制编辑器打开 zip搜索 “PK\x01\x02” 中央目录记录把通用标志位第 0 个 bit 从 1 改成 0 即可解除。这里有一个通用修复脚本import sys with open(sys.argv[1], rb) as f: data bytearray(f.read()) start 0 pos data.find(bPK\x01\x02, start) while pos ! -1: flag data[pos 8] if flag 0x01: data[pos 8] flag 0xFE # 清掉 bit0 start pos 1 pos data.find(bPK\x01\x02, start) f.seek(0) f.write(data)用法是python fixzip.py your.zip脚本会把所有中央目录文件头的加密位清掉。注意如果 zip 是真正的加密包这样改会破坏文件结构解压时依然会报错伪加密则能直接通过。这个脚本我一般只在“资料包打不开”时应急用正规项目包很少出现伪加密但搜索引擎搜“zip伪加密”进来的人八成都在这个坎上。5.2 版本对应关系AGP 7.0.4到底配哪个Gradle重灾区来了。老 zip 包往往是两年前的工程build.gradle里写着classpath com.android.tools.build:gradle:4.2.0你的 Android Studio 已经把 AGP 升级到 8.x同步时不是报“旧版本 AGP 不支持”就是报“需要更高版本的 Gradle”。很多新人的第一个反应是去搜索“AGP 7.0.4下载哪个版本”然后下载一个完全不对的 Gradle 版本装上继续报错。这里说清楚最核心的匹配关系AGP 和 Gradle 是两个独立但强绑定的系统。AGP 是 Android 构建插件运行在 Gradle 里Gradle 版本过低AGP 的新 API 调不到版本过高又可能触发兼容性检查报错。常见搭配如下AGP 版本 Gradle 最低版本 推荐 JDK 4.2.2 6.7.1 JDK 8/11 7.0.4 7.0.2 JDK 11 7.4.2 7.5 JDK 11 8.1.1 8.0 JDK 17 8.5.2 8.7 JDK 17当你拿到 zip 包时先去gradle/wrapper/gradle-wrapper.properties里看distributionUrl写的 Gradle 版本再看项目根build.gradle里的 AGP 版本。两者能对齐就不动对不齐以 AGP 版本为准往上调 Gradle比以 Gradle 为准往下换 AGP 更安全因为新版 AB 对旧 AB 的兼容策略通常是“更高版本可以跑低版本工程”。相应地JDK 也要匹配。Android Studio Ladybug 之后内置 JBR 17如果你把 AGP 拉到 8.0 以上JDK 却还是 11编译时会直接提示Unsupported class file major version。在 File → Project Structure → SDK Location 里确认 Gradle JDK 选的是“当前 JBR 17”而不是随系统的 OpenJDK 8。5.3 模拟器UI卡顿别急着在布局上找原因热搜词里有“ui界面卡顿”在 Android 计算器这个项目里卡顿十有八九出在模拟器而不是代码。模拟器默认分辨率如果跟你的物理屏幕差距过大渲染计算器这种全屏界面的每一帧都要做大量像素转换Android Studio 窗口拖动时甚至出现明显的掉帧。我的建议很直接开发计算器这种小项目直接接真机调试。用 USB 连上手机开启开发者选项里的 USB 调试Android Studio 工具栏会自动识别机型点一下 Run 就安装运行。真机渲染性能是模拟器的几倍字体渲染、圆角阴影这些 UI 细节看起来也更真实。如果你是 Linux 或 Windows 环境模拟器还必须开硬件加速否则连启动都要等几分钟。遇到这种慢成幻灯片的模拟器优先去 SDK Manager 里检查是否装了Intel HAXM或Android Emulator hypervisor driver装好后再把模拟器分辨率调到 1600x900 以下。绝大多数情况下卡顿的根源是缺少 hypervisor而不是计算器里那几个 Textview 的性能问题。5.4 namespace缺失、R标红与Clean工程的正确顺序老项目的build.gradleModule 级别里通常没有namespace字段因为以前 Android 是从AndroidManifest.xml的package属性读取包名。从 AGP 8.0 开始package属性被废弃强制要求在 Module 的build.gradle里显式声明android { namespace com.example.calculator compileSdk 34 defaultConfig { applicationId com.example.calculator minSdk 21 targetSdk 33 versionCode 1 versionName 1.0 } }namespace和applicationId在正式项目里可以是不同值前者决定 R 类、BuildConfig 的包名路径后者决定应用在应用商店的唯一 ID。对于这个简易计算器两个保持一致就够。如果你改动过包名之后出现 R 红色标红别急着到处找错误先执行一次 Build → Clean Project再 Build → Rebuild Project。R 类是编译期生成的包名变了它的物理路径也要变增量编译经常缓存旧索引Clean 一下才是“后悔药”。同样常见的还有compileSdk和targetSdk的设置冲突如果你的项目compileSdk是 35targetSdk还停在 28Android 13 以上的机型会弹出分区存储、通知权限等运行时适配问题。简易项目想要少踩坑compileSdk用最新稳定版targetSdk保持 33 左右是稳妥组合既不会触发 Android 14 的严格前台服务限制又能覆盖绝大多数真机的兼容测试。6. 加两个小功能让压缩包里的计算器变成你的6.1 退格与长按清零给你一个能跑的 zip 包不是终点改两行代码、加一个自己的功能它才真正属于你。我建议从“退格”开始这是计算器使用频率最高的功能实现却只要十行不到private void backspace() { if (currentNumber.length() 0) { // 删掉当前输入数字的最后一位 currentNumber.deleteCharAt(currentNumber.length() - 1); if (expression.length() 0) { char last expression.charAt(expression.length() - 1); if (last 0 last 9) { expression.deleteCharAt(expression.length() - 1); } } updateResult(currentNumber.length() 0 ? 0 : currentNumber.toString()); } }注意这里我只能“删当前正在输入的数字”不能删运算符因为运算符一旦压栈前面可能已经算了部分结果退回再算会破坏双栈状态。如果你连运算符也想退需要把numbers.pop()出来的值回填到currentNumber把运算符从栈顶弹出代码量会翻一倍对一个“简易”项目没有必要。长按清零更像是个情怀功能计算器领域的老传统长按某个按钮直接归零。给清零按钮设置setOnLongClickListener里面调用clearAll()再配合按钮 hover 时显示“按住了”的视觉反馈整个交互立刻有了质感。6.2 旋转屏幕不丢算到一半的算式最后一个值得做的功能是旋转屏幕时保持状态。Android 默认旋转会重建 Activity你算到一半的currentNumber、栈里的数字和运算符全部清空用户心态直接崩掉。修复很简单在 Manifest 的 Activity 声明里加上activity android:name.MainActivity android:screenOrientationportrait android:configChangesorientation|screenSize /强制竖屏是计算器应用最务实的方案毕竟没有人在横屏下用计算器如果你非要支持横屏适配就得实现onSaveInstanceState把所有状态序列化恢复时重建计算栈。Override protected void onSaveInstanceState(Bundle outState) { outState.putString(expression, expression.toString()); outState.putString(current, currentNumber.toString()); outState.putDouble(lastOperand, lastOperand); outState.putString(lastOperator, lastOperator); super.onSaveInstanceState(outState); }重建 Activity 之前系统会先把数据存进 BundleonCreate里再按 key 取回来把currentNumber和栈的内容填回去。存储的粒度只到“表达式文本”和“当前数字”两个字符串栈反而可以不存拿到表达式后从头解析一遍重新压栈相当于把用户输入历史重放一遍。这样实现代码最少也不用去序列化 Stack 这种 Java 集合对象。最后说一个我个人的习惯拿到任何带 zip 的计算器项目我都会第一时间把它的布局、逻辑、配色拆成三块独立看而不是直接用“Run”。先跑起来再改一个小功能最后重构一个小模块这是从“用了别人的源码”过渡到“写自己的轮子”最稳的路。当初我第一次跑通这种项目时也交过不少“semantic 版本配错”“未连续运算符处理”的学费今天把它们写出来希望帮到你。本文还有配套的精品资源点击获取
返回列表