iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0

前言

昨天我們簡單講解了Jetpack Compose的Layout方法,今天我們將結合先前的內容做一個總結和複習,剛好我前面在看大學時寫的程式碼是其中一份期中作業,那個作業要求我們寫一個可以運行簡單的加減乘除的計算機,剛好就藉著這個題目來進行複習和驗收。

APP實作部分

1.主要的 UI 結構

首先我們需要先幫我的APP設計好UI的架構,也就是軀體,下面是我們的程式碼

  • kotlin
@Composable
fun Calculator() {
    var displayText by remember { mutableStateOf("0") } // 用於顯示計算過程和結果
    var currentInput by remember { mutableStateOf("") } // 用於儲存當前輸入的完整計算式

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // 顯示區域
        Text(
            text = displayText,
            fontSize = 32.sp,
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
                .background(Color.LightGray)
                .padding(16.dp),
            maxLines = 3
        )

        Spacer(modifier = Modifier.height(16.dp))

        // 按鈕配置
        val buttons = listOf(
            listOf("7", "8", "9", "/"),
            listOf("4", "5", "6", "*"),
            listOf("1", "2", "3", "-"),
            listOf("C", "0", "DEL", "+"),
            listOf("=")
        )

        for (row in buttons) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                for (buttonText in row) {
                    CalculatorButton(text = buttonText) {
                        onButtonClick(buttonText, currentInput, displayText) { newDisplayText, newCurrentInput ->
                            displayText = newDisplayText
                            currentInput = newCurrentInput
                        }
                    }
                }
            }
        }
    }
}

我們透過Column()來將計算機顯示算式的區域和下面的按鍵進行排列,中間用Spacer()來將兩者分開

  • kotlin
val buttons = listOf(
            listOf("7", "8", "9", "/"),
            listOf("4", "5", "6", "*"),
            listOf("1", "2", "3", "-"),
            listOf("C", "0", "DEL", "+"),
            listOf("=")
        )

然後我們使用listOf()創建一個buttons的列表以便於後續計算機按鍵button建立時使用,listOf()是Kotlin中用來建立不可變列表(List)的標準函式,用於創建一個包含指定元素的列表,List與二維陣列不同的是,該結構本身可以包含不同大小的子列表,這樣的結構更具彈性,例如第五行只有一個元素 ["="],而前四行都有四個元素。

for (row in buttons) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                for (buttonText in row) {
                    CalculatorButton(text = buttonText) {
                        onButtonClick(buttonText, currentInput, displayText) { newDisplayText, newCurrentInput ->
                            displayText = newDisplayText
                            currentInput = newCurrentInput
                        }
                    }
                }
            }
        }

後續透過兩個內外For迴圈來建立按鈕佈局,外部的for迴圈(row in buttons),目的是將buttons中每一列的listOf()透過Row來進行排列,而內部的for迴圈(buttonText in row),目的是迭代每一行中的按鈕,buttonText代表該按鈕的文字內容,例如"7","8"等。而其中使用的CalculatorButton()是一個自定義的組件,代碼如下

  • kotlin
@Composable
fun CalculatorButton(text: String, onClick: () -> Unit) {
    Button(
        onClick = onClick,
        modifier = Modifier
            .padding(4.dp)
            .size(64.dp)
    ) {
        Text(text = text, fontSize = 24.sp)
    }
}

同時我們也定義一個onButtonClick()來處理按鈕點擊後觸發的事件,程式碼如下

  • kotlin
fun onButtonClick(
    input: String,
    currentInput: String,
    displayText: String,
    updateState: (String, String) -> Unit
) {
    when (input) {
        "C" -> {
            // 清除所有內容
            updateState("0", "")
        }
        "DEL" -> {
            // 刪除當前輸入的最後一個字符
            val newInput = if (currentInput.isNotEmpty()) currentInput.dropLast(1) else ""
            val newDisplayText = if (newInput.isEmpty()) "0" else newInput
            updateState(newDisplayText, newInput)
        }
        in "0".."9", "+", "-", "*", "/" -> {
            // 處理數字和運算符的輸入
            val newInput = if (currentInput == "0") input else currentInput + input
            updateState(newInput, newInput)
        }
        "=" -> {
            // 計算結果
            try {
                val result = calculateExpression(currentInput)
                val finalDisplayText = "$currentInput = $result"
                updateState(finalDisplayText, result.toString())
            } catch (e: Exception) {
                updateState("Error", "")
            }
        }
    }
}

到這邊,一個簡易計算機APP的身體就做好了,有了身體,再來我們要為其添加靈魂,也就是處理計算機運算的部份了。

2.計算機運算

在上面onButtonClick()中,"="按鈕觸發的部分有個calculateExpression(),當我們按下等於鍵時將前面操作中存下來用於記憶運算過程的字串進行運算處理,程式碼如下

  • kotlin
fun calculateExpression(expression: String): Double {
    return object : Any() {
        var pos = -1
        var ch = 0

        fun nextChar() {
            ch = if (++pos < expression.length) expression[pos].toInt() else -1
        }

        fun eat(charToEat: Int): Boolean {
            while (ch == ' '.toInt()) nextChar()
            if (ch == charToEat) {
                nextChar()
                return true
            }
            return false
        }

        fun parse(): Double {
            nextChar()
            val x = parseExpression()
            if (pos < expression.length) throw RuntimeException("Unexpected: " + ch.toChar())
            return x
        }

        fun parseExpression(): Double {
            var x = parseTerm()
            while (true) {
                when {
                    eat('+'.toInt()) -> x += parseTerm() // addition
                    eat('-'.toInt()) -> x -= parseTerm() // subtraction
                    else -> return x
                }
            }
        }

        fun parseTerm(): Double {
            var x = parseFactor()
            while (true) {
                when {
                    eat('*'.toInt()) -> x *= parseFactor() // multiplication
                    eat('/'.toInt()) -> x /= parseFactor() // division
                    else -> return x
                }
            }
        }

        fun parseFactor(): Double {
            if (eat('+'.toInt())) return parseFactor() // unary plus
            if (eat('-'.toInt())) return -parseFactor() // unary minus

            var x: Double
            val startPos = pos
            if (eat('('.toInt())) { // parentheses
                x = parseExpression()
                eat(')'.toInt())
            } else if ((ch >= '0'.toInt() && ch <= '9'.toInt()) || ch == '.'.toInt()) { // numbers
                while ((ch >= '0'.toInt() && ch <= '9'.toInt()) || ch == '.'.toInt()) nextChar()
                x = expression.substring(startPos, pos).toDouble()
            } else {
                throw RuntimeException("Unexpected: " + ch.toChar())
            }

            return x
        }
    }.parse()
}

上面的程式是一個使用遞迴下降解析法的簡單算術表達式解析器和計算器。它能解析並計算包含加、減、乘、除和括號的數學表達式,正確處理運算符的優先級。透過內部的解析函式,該函式逐層解析數字、因子、項和整個表達式,並在遇到不同的運算符時進行相應的計算,最終回傳計算結果。

後話

今天的內容就到這邊為止,後面calculateExpression()的說明比較簡短一點,我們有機會再補上詳細的說明(主要是我從六點的熊貓訂單到現在系統標準時間 UTC+0800 2024年 9月 22日 21:48:50 都一直被跳過還沒來,我該去覓食了),好了感謝你能看到這邊,我們明天再見。


上一篇
Day7:Jetpack Compose的Layout方法
下一篇
Day9:確認並完善專案架構與功能列表
系列文
github裡永遠有一個還沒做的SideProject :用Kotlin來開發點沒用的酷東西30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言