瞭解如何編寫與小費計算機應用程式中 UI 元素互動的 Kotlin 程式碼,方便你計算小費。
學習目標
- Android 應用程式的基本結構。
- 如何讀取 UI 中的值,將這些值寫入程式碼並進行操控。
- 如何使用資料檢視繫結 (而不是
findViewById()
) 來更輕鬆地編寫與檢視畫面互動的程式碼。 - 如何搭配
Double
資料類型使用 Kotlin 中的十進位數字。 - 如何將數字的格式設定為貨幣。
- 如何使用字串參數來動態建立字串。
- 如何在 Android Studio 中使用 Logcat 來找出應用程式的問題。
範例應用程式總覽
Tip Time 應用程式會提供小費計算機所需的所有 UI,但不顯示用於計算小費的程式碼。系統顯示「Calculate」按鈕,但該按鈕尚無法正常運作。
- 使用者可以使用「Cost of Service」
EditText
輸入服務費用。 RadioButtons
清單可讓使用者選取小費百分比,Switch
可讓使用者選擇是否應將小費四捨五入。- 小費金額顯示在
TextView
中 - 「Calculate」
Button
會通知應用程式從其他欄位取得資料並計算小費金額。

應用程式專案結構
IDE 中的應用程式專案由多個部分組成,包括 Kotlin 程式碼、XML 版面配置,以及字串和圖片等其他資源。
- 在 Android Studio 中開啟 Tip Time 專案。
- 如果系統未顯示「Project」視窗,請選取 Android Studio 左側的「Project」分頁標籤。
- 從下拉式選單中選擇 Android 檢視畫面 (如果尚未選取該檢視畫面)。

- Kotlin 檔案 (或 Java 檔案) 的 java 資料夾
MainActivity
- 小費計算機邏輯的所有 Kotlin 程式碼所屬的類別- 應用程式資源的 res 資料夾
activity_main.xml
- Android 應用程式的版面配置檔案strings.xml
- 包含 Android 應用程式的字串資源- Gradle Scripts 資料夾
Gradle 是 Android Studio 使用的自動建構系統。當您變更程式碼、新增資源或對應用程式進行其他變更時,Gradle 會判斷變更的內容,並採取必要步驟來重新建構應用程式。它還會在模擬器或實體裝置上安裝您的應用程式,並控管其執行作業。
檢視區塊繫結(Binding)
為了計算小費,程式碼必須存取所有 UI 元素才能讀取使用者的輸入內容。程式碼必須先找到 View
的參照 (例如 Button
或 TextView
),然後才能呼叫 View
上的方法或存取其屬性。
Android 架構提供 findViewById()
方法,可根據您的需求執行動作,即在指定 View
ID 的情況下傳回其參照)。這種方法有用,但隨著您在應用程式中新增更多檢視畫面,而 UI 也變得更加複雜,使用 findViewById()
可能有點麻煩。
為方便起見,Android 還提供一項名為**檢視繫結(Binding)**的功能。只要提前多做一點工作,檢視繫結就能讓您在 UI 的檢視畫面中更輕鬆、更快速地呼叫方法。您必須在 Gradle 中為應用程式啟用檢視繫結,並對程式碼進行一些變更。
啟用檢視繫結
- 開啟應用程式的
build.gradle
檔案 build.gradle.kts (:app) - 在
android
部分中,新增以下行:
1 | buildFeatures { |
- 注意以下訊息:「Gradle files have changed since last project sync」。
按下「Sync Now」。 - 按下「Sync Now」。

片刻過後,您應該會在 Android Studio 視窗底部看到訊息「Gradle sync finished」。
初始化繫結物件
在先前的程式碼研究室中,您已經看到屬於 MainActivity
類別的 onCreate()
方法。這是應用程式啟動並初始化 MainActivity
時最先呼叫的內容之一。您將建立並初始化繫結物件一次,而無須為應用程式中的每個 View
呼叫 findViewById()
。

- 開啟
MainActivity.kt
(依序點選「app」>「java」>「com.example.tiptime」>「MainActivity」)。 - 將
MainActivity
類別的所有現有程式碼替換為此程式碼,設定MainActivity
以使用檢視繫結。
原程式碼:
1 | class MainActivity : AppCompatActivity() { |
改成Binding:
1 | class MainActivity : AppCompatActivity() { |
- 此行在繫結物件的類別中宣告頂層變數。之所以在此層級定義該變數,是因為將在
MainActivity
類別的多種方法中使用該變數。
1 | lateinit var binding: ActivityMainBinding |
lateinit
關鍵字是全新內容。應保證程式碼先初始化變數,然後再使用該變數。否則,您的應用程式將當機。
- 此行會初始化
binding
物件,您將使用該物件存取activity_main.xml
版面配置中的Views
。
1 | binding = ActivityMainBinding.inflate(layoutInflater) |
- LayoutInflater:將layout XML
(activity_main.xml)
文件實例化為其相應的View
物件。
- 設定Activity的內容檢視畫面。此行將指定應用程式中檢視畫面階層的根層級
binding.root
,而不是傳遞版面配置R.layout.activity_main
的資源 ID。
1 | setContentView(binding.root) |
回想父檢視畫面和子檢視畫面的概念;root(根)
層級可連結所有這些檢視畫面。
- 現在應用程式中需要
View
的參照時,可從binding
物件中取得,而無須呼叫findViewById()
。 binding
物件會自動為應用程式中每個擁有 ID 的View
定義參照。- 使用檢視繫結這種方式更為簡潔,通常您甚至無須建立變數來保留
View
的參照,只要直接在繫結物件中使用該變數即可。
1 | // Old way with findViewById() |
計算小費
使用者輕觸「Calculate」按鈕時,系統開始計算小費。須執行的動作包括檢查 UI,瞭解具體服務費用以及使用者想給的小費百分比。運用這些資訊,您可以計算服務費用的總金額並顯示小費金額。
將點擊事件監聽器新增至按鈕
第一步是新增點擊事件監聽器,以指定使用者輕觸「Calculate」按鈕時,該按鈕應執行的操作。
- 呼叫
setContentView()
之後,在「Calculate」按鈕上設定點擊事件監聽器,並讓它呼叫calculateTip()
。
1 | binding.calculateButton.setOnClickListener{ calculateTip() } |
- 仍在
MainActivity
類別中 (但在onCreate()
之外),新增名為calculateTip()
的函式。在這裡新增程式碼,檢查 UI 並計算小費。
1 | fun calculateTip() { |
取得服務費用
如要計算小費,首先須計算服務費用。文字會儲存在 EditText
中,但您必須使用它做為數字,以便用於計算。您可能還記得其他程式碼研究室中介紹過的 Int
類型,但 Int
只能保留整數。如要在應用程式中使用十進位數字,請使用名為 Double
的資料類型,而並非 Int
。
如要進一步瞭解 Kotlin 中的數字資料類型,請參閱説明文件。Kotlin 提供用於將 String
轉換為 Double
的方法 (稱為 toDouble()
)。
- 首先,請取得服務費用的文字。在
calculateTip()
方法中,取得「Cost of Service」EditText
的文字屬性,然後指派給稱為stringInTextField
的變數。
(請記住,您可以使用binding
物件存取 UI 元素,還可以根據 UI 元素採用駝峰式大小寫形式的資源 ID 來參照 UI 元素。)
1 | val stringInTextField = binding.costOfService.text //抓取服務費用的文字 |
請注意結尾處的 .text
。第一部分,binding.costOfService
參照服務費用的 UI 元素。在結尾處加上 .text
表示要取得該結果 (EditText
物件) 並從中取得 text
屬性。這就是所謂的鏈結,是 Kotlin 中一種很常見的模式。
- 接下來,將文字轉換為十進位數字。在
stringInTextField
上呼叫toDouble()
,並儲存在名為cost
的變數中。
1 | val cost = stringInTextField.toDouble() //將stringInTextField轉成Double |
透過在 Editable
上呼叫 toString()
將其轉換為 String
。
- 在
binding.costOfService.text
上呼叫toString()
並將其轉換為String
:
1 | val stringInTextField = binding.costOfService.text.toString() //抓取服務費用的文字並轉成String |
現在,stringInTextField.toDouble()
可以正常運作。
此時,calculateTip()
方法應如下所示:
1 | fun calculateTip() { |
取得小費百分比
到目前為止,您已取得服務費用。現在您需要使用者從 RadioButtons
的 RadioGroup
中選取的小費百分比。
- 在
calculateTip()
中,取得tipOptions
RadioGroup
的checkedRadioButtonId
屬性,並將其指派給名為selectedId
的變數。
1 | val selectedId = binding.tipOptions.checkedRadioButtonId |
現在您知道選取哪個 RadioButton
(R.id.option_twenty_percent
、R.id.option_eighteen_percent
或 R.id.fifteen_percent
其中之一),但還需要相應的百分比。您可以編寫一系列 if/else
陳述式,但使用 when
運算式會簡單許多。
新增下列行即可取得小費百分比。
1 | val tipPercentage = when (selectedId) { |
此時,calculateTip()
方法應如下所示:
1 | fun calculateTip() { |
計算小費並四捨五入
現在您已得知服務費用和小費百分比,要計算小費就十分容易:只要將費用乘以小費百分比,即可計算出小費,即「小費 = 服務費用 *小費百分比」。您可以視需要將該值四捨五入。
- 在
calculateTip()
中您新增的其他程式碼後面,將tipPercentage
乘以cost
,然後將計算出的值指派給名為tip
的變數。
1 | var tip = cost * tipPercentage |
請注意,使用 var
而非 val
。這是因為使用者選取該選項時,您可能需要將這個值四捨五入,因此值可能會有所異動。
如果是 Switch
元素,請檢查 isChecked
屬性,確認切換按鈕是否「開啟」。
2. 將四捨五入切換按鈕的 isChecked
屬性指派給名為 roundUp
的變數。
1 | val roundUp = binding.roundUpSwitch.isChecked |
- 新增
if
陳述式,用於在roundUp
為 true 時將小費上限指派給tip
變數。
1 | if(roundUp) { |
設定小費格式
您的應用程式幾乎可以正常運作。您已計算小費,現在只要設定小費格式並加以顯示即可。
正如您所預期的那樣,Kotlin 提供用於為不同類型的數字設定格式的方法。但小費金額會略有不同,它代表貨幣價值。不同的國家/地區使用不同的貨幣,並且在設定十進位數字的格式方面有不同的規則。例如,以美元為單位時,1234.56 的格式應為 $1,234.56 美元,但以歐元為單位時,格式應為 €1.234,56 歐元。幸好,Android 架構提供用於將數字的格式設定為貨幣的方法,因此您不必瞭解所有可能的做法。系統會根據使用者在手機上選擇的語言和其他設定,自動設定貨幣格式。如要進一步瞭解數字格式,請參閱 Android 開發人員說明文件。
- 在
calculateTip()
中的其他程式碼後面,呼叫NumberFormat.getCurrencyInstance()
。
1 | NumberFormat.getCurrencyInstance() |
系統會為您提供數字格式設定工具,以便您將數字格式設為貨幣。
- 使用數字格式設定工具時,可將
format()
方法的呼叫鏈結至tip
,並將結果指派給名為formattedTip
的變數。
1 | val formattedTip = NumberFormat.getCurrencyInstance().format(tip) |
從可能的匯入內容清單中選擇 **NumberFormat (java.text)**。
顯示小費
現在,您必須在應用程式的小費金額 TextView
元素中顯示小費。您可以將 formattedTip
指派給 text
屬性,不過最好能夠加上標籤説明金額代表的意義。在使用英文的美國,系統可能會顯示「Tip Amount: $12.34」,但使用其他語言時,數字可能需要出現在字串開頭甚或中間。Android 架構提供名為「字串參數」的機制,讓翻譯應用程式的人可視需要變更數字的顯示位置。
- 開啟
strings.xml
(「app」>「res」>「values」>「strings.xml」) - 將
tip_amount
字串從Tip Amount
變更為Tip Amount: %s
。
1 | <string name="tip_amount">Tip Amount: %s</string> |
在 %s
插入已設定格式的貨幣。
- 現在請設定
tipResult
的文字。返回calculateTip()
方法,呼叫getString(R.string.tip_amount, formattedTip
),然後將其指派給小費結果TextView
的text
屬性。
1 | binding.tipResult.text = getString(R.string.tip_amount, formattedTip) |
此時,calculateTip()
方法應如下所示:
1 | fun calculateTip() { |
開發應用程式 (以及檢視預覽畫面) 時,建議您為該 TextView
設定預留位置。
- 開啟
activity_main.xml
(依序點選「App」>「Res」>「Layout」>「activity_main.xml」)。 - 找出
tip_result
TextView
。 - 移除包含
android:text
屬性的那一行。 - 新增設定為
tools:text
屬性的Tip Amount: $10
。
1 | tools:text="Tip Amount: $10" |
由於這是預留位置,因此無須將字串擷取至資源。執行應用程式時,它不會顯示。
最後,執行應用程式。輸入費用金額並選取一些選項,然後按「Calculate」按鈕。
測試並偵錯
如果使用者未輸入任何文字,stringInTextField
顯示空白,會發生什麽?
- 在模擬器中執行應用程式,但使用「Run」>「Debug ‘app’」,而不是使用「Run」>「Run ‘app’」。
- 請嘗試費用、小費金額以及小費是否四捨五入的不同組合,並確認您在各種情況下輕觸「Calculate」時是否能夠取得預期結果。
- 現在,請嘗試刪除「Cost of Service」欄位中的所有文字,然後輕觸「Calculate」。糟糕,您的程式已當機。
在空字串或不代表有效十進位數字的字串上呼叫 toDouble()
時,將無法運作。幸好,Kotlin 也提供名為 toDoubleOrNull()
的方法,可用於處理這些問題。如果可以,系統會傳回十進位數字;如果發生問題,系統會傳回 null
。
- 在
calculateTip()
中,變更宣告cost
變數的那一行,呼叫toDoubleOrNull()
而非呼叫toDouble()
。
1 | val cost = stringInTextField.toDoubleOrNull() |
- 在此行後面加入陳述式,檢查
cost
是否為null
,如果是,系統會從該方法傳回。return
指令表示在不執行其餘指令的情況下結束方法。如果系統必須傳回一個值,您應使用包含運算式的return
指令來指定該值。
1 | if (cost == null) { |
- 再次執行應用程式。
- 如果「Cost of Service」欄位中沒有任何文字,請輕觸「Calculate」。這次,應用程式並未當機!做得好 — 您已找到並修正錯誤!
處理其他情況
如果使用者執行以下操作:
- 輸入服務費用的有效金額
- 輕觸「Calculate」以計算小費
- 刪除服務費用
- 再次輕觸「Calculate」?
第一次,系統會按預期計算並顯示小費。第二次,由於您剛剛新增的檢查,calculateTip()
方法會提早傳回結果,但應用程式仍會顯示先前的小費金額。這可能會讓使用者感到困惑,因此建議新增一些程式碼,以便在出現問題時清除小費金額。
如要確認發生這個問題,請輸入有效的服務費用,然後輕觸「Calculate」(計算),接著刪除文字後再次輕觸「Calculate」(計算)。系統仍會顯示第一次的小費值。
在剛剛新增的
if
中,在return
陳述式之前新增一行,用於將tipResult
的text
屬性設為空字串。
1 | if (cost == null) { |
這樣,系統會在從 calculateTip()
傳回結果之前清除小費金額。
- 再次執行應用程式,然後嘗試處理上述情況。再次輕觸「Calculate」時,第一次的小費值應該會消失。