基于Kotlin+Compose完成一个简易的计算器
/ 定义运算符优先级'+' to 1,'-' to 1,'*' to 2,'/' to 2// 运算数栈和运算符栈var i = 0var allowUnary = true // 允许负号作为一元运算符// 处理数字// 连续读取数字字符// 处理运算符// 处理一元负号(当负号出现在表达式开头或运算符后)// 执行优先级更高的已有运算!!) {ops.add(c)i++// 跳过空格' ' -
基于Kotlin+Compose完成一个简易的计算器
一个简易的能完成四则运算的计算器

完成之后大概是这样,下面是我的详细步骤
1、创建项目,初始化内部函数
-
先创建一个Empty Compose Activity,并将他的Greeting函数命名为Calculator,同时修改主项目中的函数名
-
@Composable fun CalculatorApp() { }
2、创建按钮组件,便于复用
- 由上图可知,本计算器12个按钮有相同的样式,故可以将他们封装在一个函数里面复用,减少代码堆叠
// 自定义按钮组件
@Composable
fun CalculatorButton(
symbol: String, // 按钮显示文本
onClick: () -> Unit // 点击回调
) {
Button(
onClick = onClick,
modifier = Modifier
.size(75.dp) // 按钮尺寸
.clip(CircleShape) // 圆形形状
) {
Text(text = symbol, fontSize = 30.sp) // 按钮文字
}
}
这里自定义函数传入了文本以及点击的回调事件,设置了按钮尺寸、形状以及文字尺寸等
3、创建界面UI
接下来是完整页面的创建
-
第一步:定义状态变量
-
@Composable fun CalculatorApp() { var input by remember { mutableStateOf("0") } // 输入表达式 var result by remember { mutableStateOf(0.0) } // 计算结果 // 按钮布局定义(二维列表) val buttonLists = listOf( listOf("7", "8", "9", "/"), listOf("4", "5", "6", "*"), listOf("1", "2", "3", "-"), listOf("AC", "0", "=", "+") ) }
首先定义了两个状态变量input和result,分别用于存储用户输入的值和等于之后的计算结果,通过remember和mutableStateOf确保重组后变量能够保留
input之所以定义为字符串,是因为用户输入的文本需要不断追加,使用字符串便于追加
同时定义了一个二维列表用于存储按钮的文本,为后面的循环做准备
-
第二步:通过Column布局定义文本及按钮
-
Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { // 结果显示区域 Text( text = input, fontSize = 48.sp, modifier = Modifier .fillMaxWidth(), textAlign = TextAlign.End // 右对齐 ) }首先定义了一个顶部文本区域,显示用户输入的内容,同时定义了一些布局,例如边距、文字对齐方式等
// 按钮区域列
Column(verticalArrangement = Arrangement.spacedBy(30.dp)) {
// 遍历按钮行
buttonLists.forEach { row ->
Row(horizontalArrangement = Arrangement.spacedBy(27.dp)) {
// 遍历行中的每个按钮
row.forEach { btn ->
CalculatorButton(symbol = btn) {
}
}
}
}
}
然后是代码的核心区域,按钮布局
我通过两次遍历,第一次遍历出四行按钮,第二次遍历实现每一个按钮,此时你的页面应该已经出现了如上图所示的按钮界面,只是还不能正常点击,接下来是关于注册点击时间
// 按钮区域列
Column(verticalArrangement = Arrangement.spacedBy(30.dp)) {
// 遍历按钮行
buttonLists.forEach { row ->
Row(horizontalArrangement = Arrangement.spacedBy(27.dp)) {
// 遍历行中的每个按钮
row.forEach { btn ->
CalculatorButton(symbol = btn) {
// 处理按钮点击事件
when(btn) {
"0","1","2","3","4","5","6","7","8","9" ->
// 数字处理:如果当前是0则替换,否则追加
if (input == "0") input = btn else input += btn
"AC" -> input = "0" // 清空输入
"=" -> {
}
else -> input += btn // 运算符处理
}
}
}
}
}
}
这里我通过when处理不同按钮以实现不同的点击事件
首先,当按钮输入0-9时,先判断文本是否为0:如果为0,则将用户输入的数字替换掉零;否则在原本文本后面追加
当用户输入AC时,清空输入恢复成0
用户输入=时,我们需要做计算操作,我将单独定义一个函数计算操作结果
用户输入其他,即±*/时,直接在文本后面追加即可
3、定义计算函数
fun evaluateExpression(expr: String): Double {
// 定义运算符优先级
val precedence = mapOf(
'+' to 1,
'-' to 1,
'*' to 2,
'/' to 2
)
// 运算数栈和运算符栈
val nums = mutableListOf<Double>()
val ops = mutableListOf<Char>()
var i = 0
var allowUnary = true // 允许负号作为一元运算符
while (i < expr.length) {
when (val c = expr[i]) {
// 处理数字
in '0'..'9' -> {
var num = 0
// 连续读取数字字符
while (i < expr.length && expr[i] in '0'..'9') {
num = num * 10 + (expr[i++] - '0')
}
nums.add(num.toDouble())
allowUnary = false
}
// 处理运算符
'+', '-', '*', '/' -> {
// 处理一元负号(当负号出现在表达式开头或运算符后)
if (c == '-' && allowUnary) {
nums.add(0.0)
}
// 执行优先级更高的已有运算
while (ops.isNotEmpty() && precedence[ops.last()]!! >= precedence[c]!!) {
compute(nums, ops)
}
ops.add(c)
i++
allowUnary = true
}
// 跳过空格
' ' -> i++
// 非法字符处理
else -> throw IllegalArgumentException("无效字符: $c")
}
}
// 执行剩余运算
while (ops.isNotEmpty()) {
compute(nums, ops)
}
return nums.last()
}
/**
* 执行单次运算
*/
private fun compute(nums: MutableList<Double>, ops: MutableList<Char>) {
val b = nums.removeAt(nums.lastIndex)
val a = nums.removeAt(nums.lastIndex)
when (ops.removeAt(ops.lastIndex)) {
'+' -> nums.add(a + b)
'-' -> nums.add(a - b)
'*' -> nums.add(a * b)
'/' -> {
if (b == 0.0) throw ArithmeticException("除零错误")
nums.add(a / b)
}
}
}
该函数通过中缀表达式计算结果,需要我们传入文本此时的值,同时返回一个Double类型的结果
-
函数解析:
1、首先定义运算符优先级,乘除优先级高于加减
// 定义运算符优先级 val precedence = mapOf( '+' to 1, '-' to 1, '*' to 2, '/' to 2 )2、定义数字栈和符号栈,同时加入是否存在负数的判断
// 运算数栈和运算符栈 val nums = mutableListOf<Double>() val ops = mutableListOf<Char>() var i = 0 var allowUnary = true // 允许负号作为一元运算符3、通过while循环处理用户输入的表达式,将其压入栈
- 如果输入为数字,将其压入数字栈(如果是多位数需要*10连续计算)
- 当输入为运算符时,先进行优先级判断,如果优先级高于栈中最后一个字符,则将其压入栈,否则执行计算,先计算优先级更高的运算(具体函数在下面,此举为了保证栈会最先弹出优先计算的运算符)
- 如果为空格直接跳过
- 执行剩余运算
while (i < expr.length) { when (val c = expr[i]) { // 处理数字 in '0'..'9' -> { var num = 0 // 连续读取数字字符 while (i < expr.length && expr[i] in '0'..'9') { num = num * 10 + (expr[i++] - '0') } nums.add(num.toDouble()) allowUnary = false } // 处理运算符 '+', '-', '*', '/' -> { // 处理一元负号(当负号出现在表达式开头或运算符后) if (c == '-' && allowUnary) { nums.add(0.0) } // 执行优先级更高的已有运算 while (ops.isNotEmpty() && precedence[ops.last()]!! >= precedence[c]!!) { compute(nums, ops) } ops.add(c) i++ allowUnary = true } // 跳过空格 ' ' -> i++ // 非法字符处理 else -> throw IllegalArgumentException("无效字符: $c") } } // 执行剩余运算 while (ops.isNotEmpty()) { compute(nums, ops) } return nums.last()4、定义单次计算函数
- 弹出数字栈的两个数字,同时弹出运算符栈中的运算符,此运算符应为优先级最高的运算符
- 运算结束之后,将运算结果压入数字栈
// 执行单次计算 private fun compute(nums: MutableList<Double>, ops: MutableList<Char>) { val b = nums.removeAt(nums.lastIndex) val a = nums.removeAt(nums.lastIndex) when (ops.removeAt(ops.lastIndex)) { '+' -> nums.add(a + b) '-' -> nums.add(a - b) '*' -> nums.add(a * b) '/' -> { if (b == 0.0) throw ArithmeticException("除零错误") nums.add(a / b) } } }
4、将该函数应用于按钮中
"=" -> {
try {
// 计算结果
result = evaluateExpression(input)
input = result.toString()
} catch (e: Exception) {
input = "Error" // 错误处理
}
}
如果输入正确,则将函数的结果赋给result,同时转换成字符串传给input,便于用户后面继续追加计算
如果输入错误,则直接将文本替换成Error
此时,一个简易的计算器已经搭建完成,他可以执行简单的四则运算。你也可以继续完善其功能,如增加小数点、括号、开根号、美化界面等等
完整代码如下:
package com.example.calculator
import android.graphics.Paint
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.calculator.ui.theme.CalculatorTheme
import java.lang.Exception
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CalculatorTheme {
Surface(modifier = Modifier.fillMaxSize()) {
CalculatorApp() // 调用主界面组件
}
}
}
}
}
@Composable
fun CalculatorApp() {
// 定义状态变量
var input by remember { mutableStateOf("0") } // 输入表达式
var result by remember { mutableStateOf(0.0) } // 计算结果
// 按钮布局定义(二维列表)
val buttonLists = listOf(
listOf("7", "8", "9", "/"),
listOf("4", "5", "6", "*"),
listOf("1", "2", "3", "-"),
listOf("AC", "0", "=", "+")
)
// 主布局列
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// 结果显示区域
Text(
text = input,
fontSize = 48.sp,
modifier = Modifier
.fillMaxWidth(),
textAlign = TextAlign.End // 右对齐
)
// 按钮区域列
Column(verticalArrangement = Arrangement.spacedBy(30.dp)) {
// 遍历按钮行
buttonLists.forEach { row ->
Row(horizontalArrangement = Arrangement.spacedBy(27.dp)) {
// 遍历行中的每个按钮
row.forEach { btn ->
CalculatorButton(symbol = btn) {
// 处理按钮点击事件
when(btn) {
"0","1","2","3","4","5","6","7","8","9" ->
// 数字处理:如果当前是0则替换,否则追加
if (input == "0") input = btn else input += btn
"AC" -> input = "0" // 清空输入
"=" -> {
try {
// 计算结果
result = evaluateExpression(input)
input = result.toString()
} catch (e: Exception) {
input = "Error" // 错误处理
}
}
else -> input += btn // 运算符处理
}
}
}
}
}
}
}
}
// 自定义按钮组件
@Composable
fun CalculatorButton(
symbol: String, // 按钮显示文本
onClick: () -> Unit // 点击回调
) {
Button(
onClick = onClick,
modifier = Modifier
.size(75.dp) // 按钮尺寸
.clip(CircleShape) // 圆形形状
) {
Text(text = symbol, fontSize = 30.sp) // 按钮文字
}
}
fun evaluateExpression(expr: String): Double {
// 定义运算符优先级
val precedence = mapOf(
'+' to 1,
'-' to 1,
'*' to 2,
'/' to 2
)
// 运算数栈和运算符栈
val nums = mutableListOf<Double>()
val ops = mutableListOf<Char>()
var i = 0
var allowUnary = true // 允许负号作为一元运算符
while (i < expr.length) {
when (val c = expr[i]) {
// 处理数字
in '0'..'9' -> {
var num = 0
// 连续读取数字字符
while (i < expr.length && expr[i] in '0'..'9') {
num = num * 10 + (expr[i++] - '0')
}
nums.add(num.toDouble())
allowUnary = false
}
// 处理运算符
'+', '-', '*', '/' -> {
// 处理一元负号(当负号出现在表达式开头或运算符后)
if (c == '-' && allowUnary) {
nums.add(0.0)
}
// 执行优先级更高的已有运算
while (ops.isNotEmpty() && precedence[ops.last()]!! >= precedence[c]!!) {
compute(nums, ops)
}
ops.add(c)
i++
allowUnary = true
}
// 跳过空格
' ' -> i++
// 非法字符处理
else -> throw IllegalArgumentException("无效字符: $c")
}
}
// 执行剩余运算
while (ops.isNotEmpty()) {
compute(nums, ops)
}
return nums.last()
}
/**
* 执行单次运算
*/
private fun compute(nums: MutableList<Double>, ops: MutableList<Char>) {
val b = nums.removeAt(nums.lastIndex)
val a = nums.removeAt(nums.lastIndex)
when (ops.removeAt(ops.lastIndex)) {
'+' -> nums.add(a + b)
'-' -> nums.add(a - b)
'*' -> nums.add(a * b)
'/' -> {
if (b == 0.0) throw ArithmeticException("除零错误")
nums.add(a / b)
}
}
}
// 预览组件
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
CalculatorTheme {
CalculatorApp()
}
}
更多推荐



所有评论(0)