1. 项目概述:Android Studio计算器开发入门
刚接触Android开发时,计算器项目就像编程界的"Hello World"——它涵盖了界面设计、事件处理和基础逻辑实现三大核心技能点。不同于控制台打印,这个看似简单的应用需要处理用户交互、数据验证和状态管理,是检验移动开发基本功的绝佳试金石。
我在2014年第一次用Eclipse开发Android计算器时,还停留在拼接字符串表达式的阶段。如今使用Android Studio配合Kotlin语言,可以通过更优雅的方式实现相同功能。这个教程将带你从零开始,用现代Android开发工具链构建一个支持加减乘除的计算器,过程中会特别强调那些官方文档不会告诉你的实战技巧。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境准备
2.1 Android Studio安装避坑指南
官网下载Android Studio时,建议选择包含Android SDK的捆绑包(约1GB)。安装过程中最容易出问题的环节是SDK路径设置:
- 绝对不要使用包含中文或空格的路径,如
C:\用户\桌面\AndroidSDK会导致编译时出现神秘错误 - 推荐路径模式:
C:\DevTools\Android\SDK - 安装完成后,在
File > Settings > Appearance & Behavior > System Settings > Android SDK中勾选至少一个Android版本(推荐API 28以上)
注意:首次启动时如果卡在"Fetching Android SDK component information",需要检查网络连接或配置HTTP代理
2.2 项目创建关键参数
新建项目时这些选项会影响后续开发:
- 模板选择:
Empty Activity - 语言选择:Kotlin(Java也可但需要额外配置)
- Minimum SDK:API 26(覆盖85%以上设备)
- 勾选
Use legacy android.support libraries避免兼容性问题
创建完成后,建议立即在build.gradle(Module:app)中将compileSdkVersion和targetSdkVersion统一设置为最新稳定版(当前为33)
3. 界面设计实战
3.1 布局文件编写技巧
计算器界面通常采用GridLayout实现按钮矩阵,但实际开发中我更推荐ConstraintLayout:
xml复制<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 结果显示框 -->
<TextView
android:id="@+id/tvResult"
android:layout_width="0dp"
android:layout_height="80dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textSize="32sp"
android:gravity="end|center_vertical"
android:padding="16dp"
android:background="@android:color/darker_gray"/>
<!-- 数字按钮示例 -->
<Button
android:id="@+id/btn7"
android:layout_width="0dp"
android:layout_height="80dp"
app:layout_constraintWidth_percent="0.25"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvResult"
android:text="7"
android:textSize="24sp"/>
<!-- 其他按钮类似 -->
</androidx.constraintlayout.widget.ConstraintLayout>
关键技巧:
- 使用
layout_constraintWidth_percent实现等宽按钮 - 为操作符按钮设置不同背景色:
android:backgroundTint="@color/teal_200" - 添加点击效果:创建
res/drawable/btn_selector.xml定义不同状态样式
3.2 字体与主题优化
在res/values/themes.xml中自定义主题:
xml复制<style name="Theme.Calculator" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:textViewStyle">@style/CalculatorTextStyle</item>
</style>
<style name="CalculatorTextStyle" parent="android:Widget.TextView">
<item name="android:fontFamily">sans-serif-medium</item>
</style>
4. 业务逻辑实现
4.1 计算器状态管理
创建Calculator类处理核心逻辑:
kotlin复制class Calculator {
private var currentInput = StringBuilder()
private var previousOperand: Double? = null
private var currentOperator: Char? = null
fun appendNumber(number: Char) {
if (number == '.' && currentInput.contains('.')) return
currentInput.append(number)
}
fun setOperator(op: Char) {
previousOperand = currentInput.toString().toDoubleOrNull()
currentOperator = op
currentInput.clear()
}
fun calculate(): Double {
val current = currentInput.toString().toDoubleOrNull() ?: return 0.0
return when (currentOperator) {
'+' -> previousOperand!! + current
'-' -> previousOperand!! - current
'×' -> previousOperand!! * current
'÷' -> if (current != 0.0) previousOperand!! / current
else Double.NaN
else -> current
}
}
fun clear() {
currentInput.clear()
previousOperand = null
currentOperator = null
}
}
4.2 按钮事件绑定最佳实践
在MainActivity中使用视图绑定替代findViewById:
kotlin复制class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val calculator = Calculator()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 数字按钮统一处理
listOf(binding.btn0, binding.btn1, ..., binding.btn9).forEach { btn ->
btn.setOnClickListener { onNumberClick(btn.text[0]) }
}
// 操作符按钮
binding.btnAdd.setOnClickListener { onOperatorClick('+') }
binding.btnSubtract.setOnClickListener { onOperatorClick('-') }
// ...其他操作符
}
private fun onNumberClick(number: Char) {
calculator.appendNumber(number)
updateDisplay()
}
private fun onOperatorClick(op: Char) {
calculator.setOperator(op)
updateDisplay()
}
private fun updateDisplay() {
binding.tvResult.text = calculator.currentInput.ifEmpty { "0" }
}
}
5. 高级功能扩展
5.1 历史记录功能实现
添加Room数据库支持计算历史存储:
- 在
build.gradle添加依赖:
groovy复制implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
- 创建实体类和DAO:
kotlin复制@Entity
data class CalculationHistory(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val expression: String,
val result: String,
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface HistoryDao {
@Insert
suspend fun insert(history: CalculationHistory)
@Query("SELECT * FROM CalculationHistory ORDER BY timestamp DESC")
fun getAll(): Flow<List<CalculationHistory>>
}
- 在ViewModel中调用:
kotlin复制class CalculatorViewModel(application: Application) : AndroidViewModel(application) {
private val historyDao = CalculatorDatabase.getDatabase(application).historyDao()
fun saveHistory(expr: String, result: String) {
viewModelScope.launch {
historyDao.insert(CalculationHistory(expression = expr, result = result))
}
}
val historyItems: Flow<List<CalculationHistory>> = historyDao.getAll()
}
5.2 横竖屏适配方案
在res/layout-land中创建横向布局文件,使用更宽的按钮布局。关键配置:
xml复制<!-- land布局中使用两行操作符按钮 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintWidth_percent="0.2"
app:layout_constraintEnd_toEndOf="parent">
<Button android:id="@+id/btnAdd" .../>
<Button android:id="@+id/btnSubtract" .../>
<!-- 第一列操作符 -->
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintWidth_percent="0.2"
app:layout_constraintEnd_toStartOf="@id/opsColumn2">
<Button android:id="@+id/btnMultiply" .../>
<Button android:id="@+id/btnDivide" .../>
<!-- 第二列操作符 -->
</LinearLayout>
6. 调试与优化技巧
6.1 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 按钮点击无响应 | 1. 未设置clickListener 2. 视图被遮挡 |
1. 检查事件绑定代码 2. 使用Layout Inspector查看视图层级 |
| 计算结果错误 | 1. 操作符优先级问题 2. 未处理除零错误 |
1. 添加括号明确优先级 2. 添加 if (current == 0.0) return Double.NaN |
| 横竖屏切换数据丢失 | 未保存Activity状态 | 重写onSaveInstanceState保存当前输入 |
6.2 性能优化建议
- 避免在
onClick中执行耗时操作 - 使用
ViewModel保存计算器状态 - 对频繁调用的计算方法添加
@Throws注解:
kotlin复制@Throws(ArithmeticException::class)
fun safeDivide(a: Double, b: Double): Double {
if (b == 0.0) throw ArithmeticException("Division by zero")
return a / b
}
7. 项目进阶方向
当基础计算器完成后,可以考虑以下扩展:
- 添加科学计算功能(三角函数、对数等)
- 实现表达式解析(支持括号和复杂运算)
- 增加主题切换功能(日间/夜间模式)
- 添加单元测试和UI自动化测试
- 发布到Google Play商店
我在实际项目中发现,使用BigDecimal替代Double可以解决浮点数精度问题,但会略微降低性能。对于普通计算器,推荐在显示结果时做四舍五入处理:
kotlin复制fun formatResult(value: Double): String {
return if (value % 1 == 0.0) {
value.toLong().toString()
} else {
"%.4f".format(value).trimEnd('0').trimEnd('.')
}
}
