简介:面向Android开发初学者,这是一套基于Android Studio与Java实现的简易计算器完整工程,界面参考MIUI计算器风格,适合作为理解Android应用开发全流程的入门项目。压缩包共957个文件,约21.35MB,涵盖xml布局与主题样式、png图标与背景图片、java业务逻辑源码、class编译产物、json配置以及可直接安装的apk文件,类型丰富。已有2191人参与学习下载。工程中通过activity_main.xml设计计算器UI,结合ConstraintLayout与其他布局实现自适应排列,并用Button、TextView和OnClickListener完成按键交互;MainActivity.java中封装了加减乘除等运算方法,可处理连续表达式;同时涉及styles.xml自定义按钮风格、Logcat调试、构建APK等环节。此外,资源中还包含Gradle构建脚本与调试相关文件,导入Android Studio后即可运行体验,也可在此基础上扩展科学计算、历史记录等功能,作为课程设计或毕业设计的实用参考。读者可借此系统练习Android UI设计、事件处理、计算逻辑与资源管理,为后续开发积累完整经验。
1. 用Android Studio写一个计算器,为什么值得你认真做一遍
新手的第一个Android项目,十有八九是计算器。Android Studio新建工程完成后,对着白底黑字的默认界面发呆是常态:按钮怎么排列、点击怎么响应、表达式怎么解析、UI怎么做才不像课堂作业。市面上的教程大多只给一个LinearLayout加十几个Button,能算但质感离系统自带计算器差得远。这一类以“使用AndroidStudio编写简易计算器(精美UI)”命名的zip工程,核心价值不在抄,而在于演示一个小型Android应用该怎么组织:布局、样式、状态管理和运算逻辑各归其位。这里按我复现这类工程的经验,从空工程到可打包的完整应用拆开讲,适合刚学完Activity和布局、想交付第一个作品的人,也适合面试前想用一个完整项目证明动手能力的开发者。
2. 项目配置与资源文件:动手写代码前先管好这三件事
2.1 新建工程时的默认值怎么改:Empty Views Activity、SDK与语言选择
新建项目不少人直接点Next接受默认模板,于是拿到一个Compose + Material3的骨架。计算器是控件密集、状态简单的应用,传统View体系里用GridLayout加Button,逻辑比Compose直观得多。非要用Compose当然也能写,但为了照着教程和网上现成代码排错,我建议新建工程时选Empty Views Activity,语言选Kotlin。官方模板里的Compose工程带了大量依赖,对新手来说启动时间更长,出问题更难定位。
Minimum SDK选23还是24取决于你要覆盖的设备。选23可以覆盖Android 6.0以上的全部设备,但如果你要用的某个AndroidX库要求API 24起步,编译期会报错。计算器这个项目用到的都是基础控件,API 23完全够。Target SDK保持Android Studio默认给的当前版本,不用刻意降。
Package name建议取com.example.calculator。不用纠结正式上架时的域名,后面改起来不麻烦;如果一开始就取个奇怪的名字,后面在代码里搜包名替换反而容易漏。工程创建完,记得等Gradle第一次同步结束再改文件,否则一边下载依赖一边改配置,经常触发莫名其妙的sync失败。第一次sync的耗时和网络关系很大,Gradle要拉取Android Gradle Plugin和Kotlin插件,耐心等它是值得的。
还有一个容易被忽略的点:新建工程时Android Studio会让你选择Gradle JDK。默认选项通常是“Embedded JDK”,也就是IDE内置的JBR版本。如果本机另外装了JDK 17,而工程里的Gradle版本是7.x,运行时会报Unsupported class file major version。遇到这种问题,在Project Structure里把Gradle JDK改成JDK 17,并确认Android Gradle Plugin版本在7.2以上。
2.2 colors.xml 与主题:精美UI的第一层地基
计算器的UI质感不在按钮有多花哨,而在颜色和间距是否统一。Android工程默认的colors.xml里只有purple_500、purple_700等Material默认色,计算器用不上。新建res/values/colors.xml,按深色计算器的标准配色定义六种颜色:
<resources> <color name="calc_bg">#1C1C1E</color> <color name="calc_display">#FFFFFF</color> <color name="btn_number_bg">#333333</color> <color name="btn_operator_bg">#FF9500</color> <color name="btn_func_bg">#A5A5A5</color> <color name="btn_text_light">#FFFFFF</color> <color name="btn_text_dark">#000000</color> </resources>配色取自主流系统计算器的深色模式:背景是近黑的深灰,数字键用比背景亮一档的中灰,运算符用橙色做视觉强调,AC、正负号这类功能键用浅灰。颜色名里的calc_前缀是个人习惯,为的是和库里的默认色区分开,后面写selector时一眼能认出是自己定义的。注意按钮文字颜色也要在colors.xml里定义,数字键上文字是白色,功能键浅灰底上用黑色,这个对比关系直接决定UI的干净程度。
接着改themes.xml。默认主题是Theme.Material3.DayNight,带ActionBar。计算器不需要标题栏,把主题改成Theme.MaterialComponents.NoActionBar,并指定状态栏颜色:
<style name="Theme.Calculator" parent="Theme.MaterialComponents.NoActionBar"> <item name="colorPrimary">@color/calc_bg</item> <item name="android:statusBarColor">@color/calc_bg</item> </style>android:statusBarColor只在API 21以上生效,而Minimum SDK是23,所以不用担心旧版本。Android 15开始强制要求edge-to-edge,状态栏默认透明,很多以前能用的statusBarColor设置开始失效。如果你用的compileSdk是35,运行在Android 15设备上会看到状态栏区域透出后面的布局颜色,这时要在onCreate里调用enableEdgeToEdge()并把根布局的fitsSystemWindows设为true。这个细节后面避坑章节还会展开。
提示:主题的parent不要用Theme.Material3.NoActionBar搭配从旧工程抄来的控件属性。MaterialComponents和Material3的控件样式不完全兼容,Button的默认背景和minWidth在两种主题下表现不同,混用容易遇到布局对不上。
2.3 解压zip工程后怎么打开:Gradle、JDK与SDK的三方对齐
标题里的zip是一个完整的Android Studio工程。拿到压缩包后,正确做法是先解压,再用Android Studio的Open按钮选择解压后的根目录,让IDE自己识别settings.gradle。不要在欢迎页用Import Project(Gradle)的旧选项,也不要新建工程再往进拷文件。工程里的.gradle和.idea目录记录了创建者的本机SDK路径和Gradle版本,你用本地新版Android Studio打开时,IDE会提示Gradle版本不符,按提醒升级或降级到对应版本即可。
最常见的失败是sync时报错Could not find com.android.tools.build:gradle:7.4.2。这时检查三处:File菜单的Project Structure里SDK Location是否指向你机器上的SDK;gradle-wrapper.properties里的distributionUrl是否指向能访问的Gradle版本;Gradle JDK设置是否和本地安装的JDK大版本匹配。Android Gradle Plugin 7.x要求JDK 11,8.x要求JDK 17,版本对不上时sync会卡在很模糊的提示上。这本质上不是工程代码的问题,而是编译链路的版本匹配问题。
还有一点:zip工程如果是从网上下载的,Windows系统解压时可能出现文件路径过长或带有非法字符的情况。Android Studio打开后界面空白或某些资源文件读不到,先看文件管理器里解压是否完整。macOS和Linux上解压相对省心,Windows用户建议用7-Zip解压到纯英文路径,比如D:\Projects\Calculator,路径里带中文或空格有时会让NDK或CMake的构建挂掉。解压完成后第一件事不是点Run,而是先看Build窗口里sync是否通过,sync通过后再运行,能少踩一半的坑。
3. 精美UI布局:把按钮排成系统计算器的手感
3.1 三种网格方案对比:为什么GridLayout最适合计算器
计算器界面本质上是一个5行4列的网格,外加顶部一条显示区。实现网格的方式有三种:嵌套LinearLayout、GridLayout、ConstraintLayout。直接用LinearLayout嵌套五层,每层一个水平LinearLayout,代码量看起来少,但每个按钮之间要对齐的话,每一行的weight和margin都要单独调,加一个按钮要数半天括号。GridLayout是Android原生提供的网格容器,设置columnCount之后按xml顺序自动填充,最贴近计算器的结构。ConstraintLayout适合复杂页面,但在这里有点浪费——为20个按钮每个写一组layout_constraint,维护成本高于收益。
三者的取舍可以归纳成一张表:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| LinearLayout嵌套 | 直观、易理解 | 层数深、间距难统一 | 新手练习 |
| GridLayout | 代码少、网格规则 | 行高控制不够灵活 | 计算器、键盘类界面 |
| ConstraintLayout | 定位灵活 | 按钮多时代码冗长 | 不规则仪表盘、自定义界面 |
实际工程里,我一般采用GridLayout包一层,columnCount="4",每个按钮设layout_margin="6dp",让按键之间有均匀缝隙。行间距如果也想要,不用额外包View,直接在GridLayout的useDefaultMargins属性上做区分,这个属性会把默认的系统margin套到每个子View上,但不同设备表现不一致,建议还是手动写margin。GridLayout还有一个容易翻车的点:如果设置了rowCount="5"但子View数量不是20个(比如漏掉一个按钮),GridLayout会把最后一行空出来,造成底部大块空白。所以这里只设columnCount,不设rowCount,让系统按子View数量自动分行。
3.2 按钮按压反馈:一份selector解决80%的UI质感问题
原生Button自带Ripple效果,但计算器这种高频点按的界面,水波纹和深色背景混在一起,看不清按下的是哪颗键。精美UI的观感差别,很大程度上来自按压时颜色的明确变化。在res/drawable下新建btn_bg_selector.xml:
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false"> <shape android:shape="rectangle"> <solid android:color="#1C1C1E"/> </shape> </item> <item android:state_pressed="true"> <shape android:shape="rectangle"> <solid android:color="#FF9500"/> </shape> </item> <item> <shape android:shape="rectangle"> <solid android:color="#333333"/> <corners android:radius="24dp"/> </shape> </item> </selector>selector里item的顺序是血泪经验:state_enabled="false"必须放最前面,state_pressed="true"其次,不带state的默认item放最后。系统按顺序逐个匹配,第一个条件成立的item生效。如果默认item写在前面,它会拦截所有状态,按压反馈就永远不出现。按钮按下变橙色,松手变回原来的灰色,视觉反馈和系统计算器一致。
要做到这个效果,还需要在Button上设置android:stateListAnimator="@null"。Android 5.0以上Button默认带一个高度抬起的动画,selector的颜色变化会在按下瞬间被动画覆盖,两者叠加看着像按钮在抖。圆角corners的radius是24dp不是随意取的,和按钮高度的比例有关——按钮高度约64dp时,24dp的圆角让边缘接近半圆但保留一点方感,太圆会显得偏女性化,太小则生硬。
更精细的做法是给数字键、运算符键、功能键分别写selector,因为它们的底色和按压色不同。数字键按住变浅灰,运算符键按住变深橙,功能键按住变深灰。实现上只是把上面selector里的颜色换掉,然后每个Button设置不同的background即可。功能键上的文字是黑色,还需要单独指定android:textColor。我见过一些工程的按钮背景直接用android:backgroundTint指定颜色,按下时的反馈交给Ripple,但Ripple在浅色按钮上几乎不可见,selector仍然是计算器这类界面的可靠方案。
3.3 显示区TextView:字体、对齐与自动缩小的三件套
计算器顶部显示区看似只是一个TextView,但难点在于数字多了怎么办。如果不做处理,一行放不下时会换行,整个布局塌掉。正确配置是限高、右对齐、自动缩小字号:
<TextView android:id="@+id/display" android:layout_width="match_parent" android:layout_height="120dp" android:gravity="end|center_vertical" android:maxLines="1" android:textColor="@color/calc_display" android:textSize="64sp" app:autoSizeTextType="uniform" app:autoSizeMinTextSize="24sp" app:autoSizeMaxTextSize="64sp" />自动缩小的属性在API 26以上原生支持,为了兼容更低版本要使用app:前缀的AppCompatTextView。如果你在xml里写了android:autoSizeTextType,它在旧设备上会被忽略,这就是为什么明明照着文档写了却在小屏手机上不会缩号。显示区的字体建议用sans-serif-mediu,m即FontFamily="sans-serif-medium",数字的笔画比默认的sans-serif略粗,在深色背景上更清晰。
这里有一个交互细节:显示区的文字更新时,计算过程表达式和当前输入数字要区分。一般做法是显示区只显示当前输入或结果,过程中间表达式放在上方一行较浅的小字。新手工程常常只用一个TextView,导致输入“12+3”时屏幕上只显示一个3,用户完全不知道前面按过什么。想做成精致UI,至少要加一个processTextView显示表达式,再用一个resultTextView显示当前值。两行的高度差和字体色差,都源于这一步。processTextView放上方的布局顺序是固定的,把它放在GridLayout之前,高度设40dp左右,文字字号20sp、颜色半透明白。
4. 计算逻辑落地:状态、解析与绑定三层分开写
4.1 中缀转后缀:不引入第三方库的四则运算解析
计算器的核心是表达式解析。网上能搜到不少简易计算器zip,其中用eval、ScriptEngine甚至反射调用JavaScript引擎的都有。这类方案代码量最少,但要在Android上运行时引入额外库或依赖,而且精度和行为不可控。自己写解析器,面试时也能聊得清楚,毕竟四则运算的解析是每个程序员都应该能写出来的东西。
经典做法是中缀转后缀(RPN),再对后缀表达式求值。判一个数字是否结束,靠的是扫描时遇到运算符或字符串结尾。下面是中缀转后缀的Kotlin实现:
fun infixToRpn(expression: String): List<String> { val output = mutableListOf<String>() val stack = ArrayDeque<Char>() val num = StringBuilder() for (ch in expression) { when { ch.isDigit() || ch == '.' -> num.append(ch) else -> { if (num.isNotEmpty()) { output.add(num.toString()) num.clear() } while (stack.isNotEmpty() && priority(stack.first()) >= priority(ch) ) { output.add(stack.removeFirst().toString()) } stack.addFirst(ch) } } } if (num.isNotEmpty()) output.add(num.toString()) while (stack.isNotEmpty()) { output.add(stack.removeFirst().toString()) } return output } fun priority(op: Char): Int = when (op) { '+', '-' -> 1 '*', '/' -> 2 else -> 0 }这段代码里的ArrayDeque当栈用,addFirst入栈,removeFirst出栈,stack.first()是栈顶。没有处理括号,因为简易计算器没有括号键,但带括号的表达式如果混进来会被当成普通运算符处理,所以调用前要做好表达式清洗。priority函数里等号和其他非法字符返回0,是为了让它们在转后缀时被当成低优先级运算符弹出,之后求值阶段再报错。
转成后缀之后,求值就机械了:从左到右扫描,遇到数字入栈,遇到运算符弹出两个操作数,算完再入栈:
fun evalRpn(tokens: List<String>): Double { val stack = ArrayDeque<Double>() for (token in tokens) { when (token) { "+" -> { val b = stack.removeFirst() val a = stack.removeFirst() stack.addFirst(a + b) } "-" -> { val b = stack.removeFirst() val a = stack.removeFirst() stack.addFirst(a - b) } "*" -> { val b = stack.removeFirst() val a = stack.removeFirst() stack.addFirst(a * b) } "/" -> { val b = stack.removeFirst() val a = stack.removeFirst() stack.addFirst(a / b) } else -> stack.addFirst(token.toDouble()) } } return stack.removeFirst() }注意减法时a和b的顺序:后缀表达式"3 5 -"对应的是3-5,而不是5-3,所以弹出来时先弹b再弹a。这一步写反是所有RPN实现里最高频的翻车点。除法同理。0.1+0.2不等于0.3是Java/Kotlin层面逃不掉的精度问题,简易计算器可以在显示层做四舍五入,保留10位小数,写成DecimalFormat("#.##########")可以解决大部分视觉误差。
4.2 按钮事件绑定:一个接口绑定全部按钮
20个按钮逐个写setOnClickListener会占掉onCreate几十行。常见的整洁写法是Activity实现View.OnClickListener,用一个id列表绑定,在onClick里用when分发:
class MainActivity : AppCompatActivity(), View.OnClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) listOf( 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, R.id.btn_dot, R.id.btn_add, R.id.btn_sub, R.id.btn_mul, R.id.btn_div, R.id.btn_eq, R.id.btn_ac ).forEach { id -> findViewById<Button>(id).setOnClickListener(this) } } override fun onClick(v: View?) { when (v?.id) { R.id.btn_0 -> inputDigit("0") R.id.btn_dot -> inputDot() R.id.btn_add -> inputOperator("+") R.id.btn_sub -> inputOperator("-") R.id.btn_mul -> inputOperator("*") R.id.btn_div -> inputOperator("/") R.id.btn_eq -> evaluate() R.id.btn_ac -> clearAll() else -> Unit } } }注意绑定列表里没有放btn_sign(正负号)和btn_percent(百分号),因为这两个键的功能依赖当前输入状态,如果当前输入为空,按了也应该无响应。与其在onClick里判空,不如直接不绑定,让它们保持不可用状态,视觉上更符合系统计算器。这个取舍可以延伸到所有按钮:不是所有按钮在所有状态下都可点,UI上应该同步反映出来。
绑定事件时,如果你的compileSdk版本较新,findViewById可以被ViewBinding替代,布局里所有带id的控件自动生成为同名字段。但对计算器这种控件数量固定的小界面,findViewById的样板代码并不多,ViewBinding的好处主要体现在列表项或复杂布局上,这里不强上。
4.3 状态管理:三个变量管住输入、表达式和重复等号
计算器的状态要管住三样东西:正在输入的数字、已经确认的表达式、上一个运算符。很多zip工程翻车就翻在这里,它们用一个TextView.text当全局状态,每次点击都从TextView里抠字符串再拼回去,除数和连续等号会出现错乱。
推荐的三个成员变量:
| 变量 | 类型 | 作用 |
|---|---|---|
| currentInput | String | 当前正在输入的数字,可能含小数点 |
| expression | String | 已确认的左侧表达式,如“12+” |
| lastOperator | Char? | 记录等号前一个运算符,支持连按等号 |
对应的事件处理在4.2的inputDigit、inputOperator、evaluate中展开。核心逻辑是:用户按运算符时,把currentInput拼进expression,并清空currentInput;按等号时,把当前输入拼进表达式,转后缀、求值、显示结果,同时把本次运算符和操作数保留,用于连按等号时的二次计算。连续等号“12+3=15,再按=得18”的实现方式,是在evaluate末尾判断lastOperator和lastOperand是否为空,如果用户没有输入新数字就重复上一次运算。
小数点要单独处理:currentInput里已经包含'.'时,再按小数点应该无效。这是输入合法性检查的一部分,不能等到解析时报错。这类输入层校验如果做得完整,解析层就只需要关心纯数学表达式,出错的概率大幅下降。我见过不少新手把“连续按运算符”“按完等号又按数字”这些交互状态全堆在if else里,最后代码像意大利面。用三个变量加一个isNewInput布尔值,把“当前输入是否是新数字”这个状态单独表示,处理起来会清晰很多。
5. 计算器必踩的5个坑:现象、原因与解决
5.1 模拟器卡在starting up,App迟迟不显示
现象:Android Studio里的模拟器启动后长时间停在“Starting up”的加载界面,计算器App装了但打开黑屏,或者界面刷新很慢,点击按钮有明显延迟。
原因:模拟器冷启动本身就要拉系统镜像,计算器工程体积小不代表系统镜像小。如果AVD配置里Graphics选的是Software,渲染走CPU,UI界面卡顿是必然的。另一个常见原因是电脑开了Hyper-V或WHPX,和Android Emulator自带的HAXM/GVM冲突,模拟器进程反复重启。
解决:在AVD Manager里编辑模拟器配置,Graphics改成Hardware - GLES 2.0,Boot option改成Cold boot;关闭无关的虚拟化软件冲突。实际开发中,计算器这类小工程用真机调试更省心,用USB连手机开USB调试,秒级安装,还能真实感受按钮的按压反馈,模拟器上的触摸反馈和真机是两回事。如果坚持用模拟器,第一次启动时多等几分钟,之后的热启动会快很多,不要一看到starting up就杀掉进程重来。
5.2 软键盘弹出抢占半边屏幕
现象:在模拟器或真机上,每按一次数字键,系统软键盘就弹出来,把计算器按键挡住大半,点其他按键也躲不开。
原因:TextView有焦点且允许输入法连接。虽然计算器所有输入都靠Button,但显示区TextView默认有焦点,软键盘被唤起。
解决:在根布局上加上android:focusable="true"和android:focusableInTouchMode="true",让根布局优先持有焦点。同时给activity配置windowSoftInputMode="stateHidden|adjustNothing",明确声明不需要软键盘窗口。这两步加完之后,模拟器再打开App就不会有输入法弹出了。注意focusable和focusableInTouchMode要同时设置,只设前者在触屏设备上不生效。
5.3 除零不崩溃但显示奇怪字符
现象:按“1÷0=”,显示Infinity;按“0÷0=”,显示NaN,有的机器上还会白屏。
原因:现代计算器把除法当Double运算,0.0做除数不会抛异常,IEEE 754标准里规定了结果是Infinity或NaN。显示层直接把Double.toString的结果丢给TextView,屏幕上就出现了Infinity。某些库或系统版本对Double.toString(“NaN”)处理异常,进而崩溃。
解决:在evaluate()求值完成后统一判断结果:
val result = evalRpn(rpn) if (result.isNaN() || result.isInfinite()) { display.text = "错误" clearAll() return }isNaN和isInfinite是Double自带的方法,不需要引入额外库。注意这个判断要放在任何格式化之前,比如BigDecimal或DecimalFormat对Infinity的行为不尽相同,绕过了等于没拦。清空状态是为了避免用户接着按数字时把“错误”两个字拼进表达式里。
5.4 旋转屏幕后表达式清空
现象:手机上把计算器横过来,输了一半的表达式和当前数字全部消失,屏幕显示0。
原因:Activity默认在旋转时被销毁重建,成员变量全部归零。计算器没有做状态保存,也没有锁定屏幕方向。
解决:开发学习阶段,最省事的办法是在AndroidManifest.xml里给MainActivity加android:screenOrientation="portrait"。如果希望支持横屏,就在activity里重写onSaveInstanceState,把currentInput、expression、lastOperator三个变量写进Bundle,onCreate里再取回。只有锁竖屏时,才不需要处理这一层。锁定方向后,布局里就不用再考虑横屏的适配,对这个小项目来说是最优解。
5.5 高DPI和定制ROM下按钮文字截断
现象:小米、华为等ROM上,“AC”“%”文字显示不全,按钮背景是圆角但文字被裁掉一半;有些ROM上按钮的按压颜色看起来不正常。
原因:各厂商对系统默认字体做了修改,英文字母和中文字符的宽度度量不完全一致。Button的padding在不同ROM上也有差异,MaterialButton还自带了inset机制,如果不小心给Button设置了过大的minWidth,布局权重分配会把可用宽度压缩,文字就放不下。
解决:文字截断优先排查按钮的padding和minWidth。在xml里给Button设置android:padding="0dp"并用android:minWidth="0dp"覆盖系统默认值,让宽度完全由GridLayout的weight决定。如果用的是androidx.appcompat.widget.AppCompatButton,还要注意背景Drawable的inset,需要设置android:insetLeft="4dp"这类inset参数。最后的兜底手段是把字号适度调小,比如把运算符键的textSize从28sp降到24sp,视觉差别不大,但留出了文字内边距。这类问题在模拟器上几乎复现不了,只能在真机上验证,调试时多拿一台国产ROM的手机有好处。
6. 进阶玩法:把简易计算器做成能上架的作品
拿到一个能跑的zip工程只是开始。想让这个计算器在作品集或面试里立住,接下来最值得做的是四件事:历史记录、振动反馈、主题跟随、签名打包。历史记录用SharedPreferences存JSON数组就够,Room在这里是大炮打蚊子,限制最多50条,超出就删最早的。存取时可以复用同一个工具类,getSharedPreferences("calc_history", MODE_PRIVATE),序列化用org.json里的JSONArray,不需要额外依赖。
振动反馈复用系统服务即可,但注意Android 13以上要检查VIBRATE权限是否被用户关闭,否则直接崩溃:
val vibrator = if (Build.VERSION.SDK_INT >= 31) { getSystemService(VibratorManager::class.java).defaultVibrator } else { @Suppress("DEPRECATION") getSystemService(VIBRATOR_SERVICE) as Vibrator } vibrator?.vibrate(VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE))20毫秒的振动时长是短促反馈的常用值,超过50毫秒就会感觉手机在震而不是在点按。振动要放在按钮的onClick里而不是onTouch里,否则滑动经过按钮也会触发,体验会变得很廉价。主题跟随系统是把颜色资源放进values-night目录,计算器这种深色为主的界面,白天模式反而要重新设计一套浅色配色,工作量比想象中大,但做完之后“跟随系统”这一条在作品集里是实打实的亮点。
签名打包前记得先把keystore备份到项目外的目录,我见过太多人换电脑后keystore丢失导致无法更新应用。最后的加分项是写几个JUnit用例,把中缀转后缀的边界case铺开:除零、连续小数点、连续等号、超大数字的精度。我自己最大的教训就是把测试留到功能全做完才补,真到补的时候已经不想动了。从一开始就给计算器写测试,后面每改一个按钮的状态逻辑都能立刻看到回归结果。希望帮到你。
本文还有配套的精品资源,点击获取