1. 事前準備
在本程式碼研究室中,您將編寫小費計算機的程式碼,與先前在程式碼研究室「為 Android 建立 XML 版面配置」中建立的 UI 搭配使用。
必要條件
- 「為 Android 建立 XML 版面配置」程式碼研究室中的程式碼。
- 瞭解如何在模擬器中或裝置上從 Android Studio 執行 Android 應用程式。
課程內容
- Android 應用程式的基本結構。
- 如何讀取 UI 中的值,將這些值寫入程式碼並進行操控。
- 如何使用檢視區塊繫結 (而不是
findViewById()
) 來更輕鬆地編寫與檢視區塊互動的程式碼。 - 如何搭配
Double
資料類型使用 Kotlin 中的十進位數字。 - 如何將數字的格式設定為貨幣。
- 如何使用字串參數來動態建立字串。
- 如何在 Android Studio 中使用 Logcat 來找出應用程式的問題。
建構項目
- 一款小費計算機應用程式,帶有可正常運作的「Calculate」按鈕。
軟硬體需求
- 已安裝最新穩定版 Android Studio 的電腦。
- Tip Time 應用程式的範例程式碼,其中包含小費計算機的版面配置。
2. 範例應用程式總覽
上一個程式碼研究室中的 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 會判斷變更的內容,並採取必要步驟來重新建構應用程式。它還會在模擬器或實體裝置上安裝您的應用程式,並控管其執行作業。
建構應用程式時還須用到其他資料夾和檔案,但這些是您在本程式碼研究室和接下來的程式碼研究室中將會使用的主要資料夾和檔案。
3. 檢視區塊繫結
為了計算小費,程式碼必須存取所有 UI 元素才能讀取使用者的輸入內容。您可以回想一下,在先前的程式碼研究室中,程式碼必須先找到 View
的參照 (例如 Button
或 TextView
),然後才能呼叫 View
上的方法或存取其屬性。Android 架構提供 findViewById()
方法,可根據您的需求執行動作,即在指定 View
ID 的情況下傳回其參照)。這種方法有用,但隨著您在應用程式中新增更多檢視區塊,而 UI 也變得更加複雜,使用 findViewById()
可能有點麻煩。
為方便起見,Android 還提供一項名為檢視區塊繫結的功能。只要提前多做一點工作,檢視區塊繫結就能讓您在 UI 的檢視區塊中更輕鬆、更快速地呼叫方法。您必須在 Gradle 中為應用程式啟用檢視區塊繫結,並對程式碼進行一些變更。
啟用檢視區塊繫結
- 開啟應用程式的
build.gradle
檔案 (Gradle Scripts > build.gradle (模組:Tip_Time.app)) - 在
android
部分中,新增以下行:
buildFeatures { viewBinding = true }
- 注意以下訊息:「Gradle files have changed since last project sync」。
- 按下「Sync Now」。
片刻過後,您應該會在 Android Studio 視窗底部看到訊息「Gradle sync finished」。您可以視需要關閉 build.gradle
檔案。
初始化繫結物件
在先前的程式碼研究室中,您已經看到屬於 MainActivity
類別的 onCreate()
方法。這是應用程式啟動並初始化 MainActivity
時最先呼叫的內容之一。您將建立並初始化繫結物件一次,而無須為應用程式中的每個 View
呼叫 findViewById()
。
- 開啟
MainActivity.kt
(依序點選「app」>「java」>「com.example.tiptime」>「MainActivity」)。 - 將
MainActivity
類別的所有現有程式碼替換為此程式碼,設定MainActivity
以使用檢視區塊繫結:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
- 此行在繫結物件的類別中宣告頂層變數。之所以在此層級定義該變數,是因為將在
MainActivity
類別的多種方法中使用該變數。
lateinit var binding: ActivityMainBinding
lateinit
關鍵字是全新內容。應保證程式碼先初始化變數,然後再使用該變數。否則,您的應用程式將當機。
- 此行會初始化
binding
物件,您將使用該物件存取activity_main.xml
版面配置中的Views
。
binding = ActivityMainBinding.inflate(layoutInflater)
- 設定活動的內容檢視區塊。此行將指定應用程式中檢視區塊階層的根層級
binding.root
,而不是傳遞版面配置R.layout.activity_main
的資源 ID。
setContentView(binding.root)
您可以回想起父檢視區塊和子檢視區塊的概念;該根層級可連結所有這些檢視區塊。
現在,在應用程式中需要 View
的參照時,可從 binding
物件中取得,而無須呼叫 findViewById()
。binding
物件會自動為應用程式中每個擁有 ID 的 View
定義參照。使用檢視區塊繫結這種方式更為簡潔,通常您甚至無須建立變數來保留 View
的參照,只要直接在繫結物件中使用該變數即可。
// Old way with findViewById()
val myButton: Button = findViewById(R.id.my_button)
myButton.text = "A button"
// Better way with view binding
val myButton: Button = binding.myButton
myButton.text = "A button"
// Best way with view binding and no extra variable
binding.myButton.text = "A button"
這真是太棒了!
4. 計算小費
使用者輕觸「Calculate」按鈕時,系統開始計算小費。須執行的動作包括檢查 UI,瞭解具體服務費用以及使用者想給的小費百分比。運用這些資訊,您可以計算服務費用的總金額並顯示小費金額。
將點擊事件監聽器新增至按鈕
第一步是新增點擊事件監聽器,以指定使用者輕觸「Calculate」按鈕時,該按鈕應執行的操作。
- 在
onCreate()
的MainActivity.kt
中,呼叫setContentView()
之後,請在「Calculate」按鈕上設定點擊事件監聽器,並讓它呼叫calculateTip()
。
binding.calculateButton.setOnClickListener{ calculateTip() }
- 仍在
MainActivity
類別中 (但在onCreate()
之外),新增名為calculateTip()
的輔助方法。
fun calculateTip() {
}
請在這裡新增程式碼,以便檢查 UI 並計算小費。
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.calculateButton.setOnClickListener{ calculateTip() }
}
fun calculateTip() {
}
}
取得服務費用
如要計算小費,首先須計算服務費用。文字會儲存在 EditText
中,但您需要數字格式才能計算。您可能還記得其他程式碼研究室中介紹過的 Int
類型,但 Int
只能保留整數。如要在應用程式中使用十進位數字,請使用名為 Double
的資料類型,而並非 Int
。如要進一步瞭解 Kotlin 中的數字資料類型,請參閱説明文件。Kotlin 提供用於將 String
轉換為 Double
的方法 (稱為 toDouble()
)。
- 首先,請取得服務費用的文字。在
calculateTip()
方法中,取得「Cost of Service」EditText
的文字屬性,然後指派給稱為stringInTextField
的變數。請記住,您可以使用binding
物件存取 UI 元素,還可以根據 UI 元素採用駝峰式大小寫形式的資源 ID 來參照 UI 元素。
val stringInTextField = binding.costOfService.text
請注意結尾處的 .text
。第一部分,binding.costOfService
參照服務費用的 UI 元素。在結尾處加上 .text
表示要取得該結果 (EditText
物件) 並從中取得 text
屬性。這就是所謂的鏈結,是 Kotlin 中一種很常見的模式。
- 接下來,將文字轉換為十進位數字。在
stringInTextField
上呼叫toDouble()
,並儲存在名為cost
的變數中。
val cost = stringInTextField.toDouble()
但這種做法行不通 — 必須在 String
上呼叫 toDouble()
。結果顯示,EditText
的 text
屬性為 Editable
,因為它代表可以變更的文字。幸好,您可以透過在 Editable
上呼叫 toString()
將其轉換為 String
。
- 在
binding.costOfService.text
上呼叫toString()
並將其轉換為String
:
val stringInTextField = binding.costOfService.text.toString()
現在,stringInTextField.toDouble()
可以正常運作。
此時,calculateTip()
方法應如下所示:
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
}
取得小費百分比
到目前為止,您已取得服務費用。現在您需要使用者從 RadioButtons
的 RadioGroup
中選取的小費百分比。
- 在
calculateTip()
中,取得tipOptions
RadioGroup
的checkedRadioButtonId
屬性,並將其指派給名為selectedId
的變數。
val selectedId = binding.tipOptions.checkedRadioButtonId
現在您知道選取哪個 RadioButton
(R.id.option_twenty_percent
、R.id.option_eighteen_percent
或 R.id.fifteen_percent
其中之一),但還需要相應的百分比。您可以編寫一系列 if/else
陳述式,但使用 when
運算式會簡單許多。
- 新增下列行即可取得小費百分比。
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
此時,calculateTip()
方法應如下所示:
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
}
計算小費並四捨五入
現在您已得知服務費用和小費百分比,要計算小費就十分容易:只要將費用乘以小費百分比,即可計算出小費,即小費 = 服務費用 *小費百分比。您可以視需要將該值四捨五入。
- 在
calculateTip()
中您新增的其他程式碼後面,將tipPercentage
乘以cost
,然後將計算出的值指派給名為tip
的變數。
var tip = tipPercentage * cost
請注意,使用 var
而非 val
。這是因為使用者選取該選項時,您可能需要將這個值四捨五入,因此值可能會有所異動。
如果是 Switch
元素,請檢查 isChecked
屬性,確認切換按鈕是否「開啟」。
- 將四捨五入切換按鈕的
isChecked
屬性指派給名為roundUp
的變數。
val roundUp = binding.roundUpSwitch.isChecked
字詞「四捨五入」是指將小數點四捨五入至最接近的整數值,但在這種情況下,您只會無條件進位或無條件進位至最接近的指定基數倍數。您可以使用 ceil()
函式來執行這項操作。有數個函式都採用該名稱,但您需要的是在 kotlin.math
中定義的函式。您可以新增 import
陳述式,但在這種情況下,較簡單的做法是直接使用 kotlin.math.ceil()
告知 Android Studio 您所需的函式。
如要使用多個數學函式,新增 import
陳述式會是比較簡單的方式。
- 新增
if
陳述式,用於在roundUp
為 true 時將小費上限指派給tip
變數。
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
此時,calculateTip()
方法應如下所示:
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
val roundUp = binding.roundUpSwitch.isChecked
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
}
設定小費格式
您的應用程式幾乎可以正常運作。您已計算小費,現在只要設定小費格式並加以顯示即可。
正如您所預期的那樣,Kotlin 提供用於為不同類型的數字設定格式的方法。但小費金額會略有不同,它代表貨幣價值。不同的國家/地區使用不同的貨幣,並且在設定十進位數字的格式方面有不同的規則。例如,以美元為單位時,1234.56 的格式應為 $1,234.56 美元,但以歐元為單位時,格式應為 €1.234,56 歐元。幸好,Android 架構提供用於將數字的格式設定為貨幣的方法,因此您不必瞭解所有可能的做法。系統會根據使用者在手機上選擇的語言和其他設定,自動設定貨幣格式。如要進一步瞭解數字格式,請參閱 Android 開發人員說明文件。
- 在
calculateTip()
中的其他程式碼後面,呼叫NumberFormat.getCurrencyInstance()
。
NumberFormat.getCurrencyInstance()
系統會為您提供數字格式設定工具,以便您將數字格式設為貨幣。
- 使用數字格式設定工具時,可將
format()
方法的呼叫鏈結至tip
,並將結果指派給名為formattedTip
的變數。
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
- 請注意,
NumberFormat
顯示為紅色。這是因為 Android Studio 無法自動判斷出您需要哪個版本的NumberFormat
。 - 將游標懸停在
NumberFormat
上方,然後在顯示的彈出式視窗中選擇「Import」。 - 從可能的匯入內容清單中選擇 NumberFormat (java.text)。Android Studio 會在
MainActivity
檔案的頂端加上import
陳述式,但NumberFormat
不再顯示為紅色。
顯示小費
現在,您必須在應用程式的小費金額 TextView
元素中顯示小費。您可以將 formattedTip
指派給 text
屬性,不過最好能夠加上標籤説明金額代表的意義。在使用英文的美國,系統可能會顯示「Tip Amount: $12.34」,但使用其他語言時,數字可能需要出現在字串開頭甚或中間。Android 架構提供名為「字串參數」的機制,讓翻譯應用程式的人可視需要變更數字的顯示位置。
- 開啟
strings.xml
(「app」>「res」>「values」>「strings.xml」) - 將
tip_amount
字串從Tip Amount
變更為Tip Amount: %s
。
<string name="tip_amount">Tip Amount: %s</string>
在 %s
插入已設定格式的貨幣。
- 現在請設定
tipResult
的文字。返回MainActivity.kt
中的calculateTip()
方法,呼叫getString(R.string.tip_amount, formattedTip)
,然後將其指派給小費結果TextView
的text
屬性。
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
此時,calculateTip()
方法應如下所示:
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
val roundUp = binding.roundUpSwitch.isChecked
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
就快大功告成了。開發應用程式 (以及檢視預覽畫面) 時,建議您為該 TextView
設定預留位置,這之後會派上用場。
- 開啟
activity_main.xml
(依序點選「App」>「Res」>「Layout」>「activity_main.xml」)。 - 找出
tip_result
TextView
。 - 移除包含
android:text
屬性的那一行。
android:text="@string/tip_amount"
- 為設定為
Tip Amount: $10
的tools:text
屬性新增一行。
tools:text="Tip Amount: $10"
由於這是預留位置,因此無須將字串擷取至資源。執行應用程式時,它不會顯示。
- 請注意,工具文字會顯示在版面配置編輯器中。
- 執行應用程式。輸入費用金額並選取一些選項,然後按「Calculate」按鈕。
恭喜,它可以正常運作!如果沒有取得正確的小費金額,請返回本部分的步驟 1,確認您已對程式碼進行了所有必要的變更。
5. 測試並偵錯
您已經在各個步驟中執行過該應用程式,確保它符合預期,但現在必須進行一些額外的測試。
現在,不妨思考使用 calculateTip()
方法時,資訊是如何在應用程式中移動的,以及每個步驟可能會出現哪些問題。
例如,在此行中:
val cost = stringInTextField.toDouble()
如果 stringInTextField
不代表數字,會發生什麽?如果使用者未輸入任何文字,stringInTextField
顯示空白,會發生什麽?
- 在模擬器中執行應用程式,但使用「Run」>「Debug ‘app'」,而不是使用「Run」>「Run ‘app'」。
- 請嘗試費用、小費金額以及小費是否四捨五入的不同組合,並確認您在各種情況下輕觸「Calculate」時是否能夠取得預期結果。
- 現在,請嘗試刪除「Cost of Service」欄位中的所有文字,然後輕觸「Calculate」。糟糕,您的程式已當機。
偵錯當機問題
處理錯誤的第一步是找出問題。Android Studio 會將系統中發生的情況保留在記錄中,讓您瞭解問題所在。
- 按下 Android Studio 底部的「Logcat」按鈕,或在選單中選擇「View」>「Tools Windows」>「Logcat」。
- 「Logcat」視窗顯示在 Android Studio 的底部,其中填滿奇怪的文字。
這段文字是「堆疊追蹤」,其中會列出當機時將會呼叫哪個方法。
- 在 Logcat 文字中向上捲動,直到找出包含
FATAL EXCEPTION
文字的行。
2020-06-24 10:09:41.564 24423-24423/com.example.tiptime E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.tiptime, PID: 24423
java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
at com.example.tiptime.MainActivity$onCreate$1.onClick(MainActivity.kt:17)
- 向下閱讀,直到找出
NumberFormatException
所在的那一行。
java.lang.NumberFormatException: empty String
畫面右側顯示 empty String
。例外狀況的類型會指出它與數字格式相關,其餘部分會顯示問題的基本資訊:找到空白的 String
,它原本應該是包含值的 String
。
- 繼續往下閱讀,即可看到
parseDouble()
的呼叫。 - 在這些呼叫下方,找出
calculateTip
所在的那一行。請注意,其中也包含MainActivity
類別。
at com.example.tiptime.MainActivity.calculateTip(MainActivity.kt:22)
- 仔細查看該行,您可以看到在程式碼中進行呼叫的確切位置,即
MainActivity.kt
中的第 22 行 (如果您採用不同的方式輸入程式碼,可能會是另一個數字)。該行會將String
轉換為Double
,並將結果指派給cost
變數。
val cost = stringInTextField.toDouble()
- 查看 Kotlin 說明文件,瞭解適用於
String
的toDouble()
方法。此方法稱為String.toDouble()
。 - 頁面會顯示「Exceptions:
NumberFormatException
- if the string is not a valid representation of a number」。
「例外狀況」是系統表示發生問題的方式。在這種情況下,問題在於 toDouble()
無法將空白的 String
轉換為 Double
。雖然 EditText
具有 inputType=numberDecimal
,但您仍可輸入 toDouble()
無法處理的值 (例如空字串)。
瞭解空值
在空字串或不代表有效十進位數字的字串上呼叫 toDouble()
時,將無法運作。幸好,Kotlin 也提供名為 toDoubleOrNull()
的方法,可用於處理這些問題。如果可以,系統會傳回十進位數字;如果發生問題,系統會傳回 null
。
「空值」是代表「無任何值」的特殊值。它與值為 0.0
的 Double
或不帶任何字元的空白 String
(""
) 不同。Null
表示無任何值,沒有 Double
或 String
。許多方法會預期一個值,並且可能不知道如何處理 null
,而這會導致應用程式當機,因此 Kotlin 會嘗試限制使用 null
的位置。您將在日後的課程中詳細瞭解。
應用程式會檢查 toDoubleOrNull()
是否傳回 null
,然後視情況採取不同的行動,以免應用程式當機。
- 在
calculateTip()
中,變更宣告cost
變數的那一行,呼叫toDoubleOrNull()
而非呼叫toDouble()
。
val cost = stringInTextField.toDoubleOrNull()
- 在此行後面加入陳述式,檢查
cost
是否為null
,如果是,系統會從該方法傳回。return
指令表示在不執行其餘指令的情況下結束方法。如果系統必須傳回一個值,您應使用包含運算式的return
指令來指定該值。
if (cost == null) {
return
}
- 再次執行應用程式。
- 如果「Cost of Service」欄位中沒有任何文字,請輕觸「Calculate」。這次,應用程式並未當機!做得好 — 您已找到並修正錯誤!
處理其他情況
並非所有錯誤都會導致應用程式當機,但結果有時可能會讓使用者感到困惑。
以下是您必須考量的其他情況。如果使用者執行以下操作,會發生什麼事:
- 輸入服務費用的有效金額
- 輕觸「Calculate」以計算小費
- 刪除服務費用
- 再次輕觸「Calculate」?
第一次,系統會按預期計算並顯示小費。第二次,由於您剛剛新增的檢查,calculateTip()
方法會提早傳回結果,但應用程式仍會顯示先前的小費金額。這可能會讓使用者感到困惑,因此建議新增一些程式碼,以便在出現問題時清除小費金額。
- 如要確認發生這個問題,請輸入有效的服務費用,然後輕觸「Calculate」(計算),接著刪除文字後再次輕觸「Calculate」(計算)。系統仍會顯示第一次的小費值。
- 在剛剛新增的
if
中,在return
陳述式之前新增一行,用於將tipResult
的text
屬性設為空字串。
if (cost == null) {
binding.tipResult.text = ""
return
}
這樣,系統會在從 calculateTip()
傳回結果之前清除小費金額。
- 再次執行應用程式,然後嘗試處理上述情況。再次輕觸「Calculate」時,第一次的小費值應該會消失。
恭喜!您已建立可正常運作的 Android 小費計算機應用程式,並處理一些極端情況!
6. 採用完善的程式設計做法
小費計算機現在可以正常運作,但您可以採用完善的程式設計做法,讓程式碼更臻完美,日後更容易使用。
- 開啟
MainActivity.kt
(依序點選「app」>「java」>「com.example.tiptime」>「MainActivity」)。 - 看看
calculateTip()
方法的開頭,您會發現它帶有一條波浪形的灰色底線。
- 將游標懸停在
calculateTip()
上方時,系統會顯示訊息「Function ‘calculateTip' could be private」,下方還會顯示建議「Make ‘calculateTip' ‘private」。
回想先前的程式碼研究室,private
代表系統只會向屬於該類別 (在這種情況下為 MainActivity
類別) 中的程式碼顯示方法或變數。MainActivity
之外的程式碼沒有理由呼叫 calculateTip()
,因此您可以放心地將該程式碼設為 private
。
- 選擇「Make ‘calculateTip' ‘private」,或者在
fun calculateTip()
前新增private
關鍵字。calculateTip()
下方的灰色線條消失。
檢查程式碼
灰色線條很細,容易被忽略。您可以瀏覽整個檔案,尋找更多灰色線條,但其實有更簡便的方式能確保您找到所有建議。
- 在
MainActivity.kt
仍然開啟時,從選單中選擇「Analyze」>「Inspect Code…」。系統隨即顯示名為「Specify Inspection Scope」的對話方塊。 - 選擇以 File 開頭的檔案,然後按「OK」。這樣可將檢查範圍限制為僅包含
MainActivity.kt
。 - 畫面底部會出現「Inspection Results」視窗。
- 按一下「Kotlin」旁邊的灰色三角形圖示,然後再按一下「Style issues」旁邊的同一個圖示,直到系統顯示兩則訊息。第一項訊息的內容為「Class member can have ‘private' visibility」。
- 按一下灰色三角形圖示,直到畫面上顯示「Property 'binding' is private」訊息,然後按一下該訊息。Android Studio 會顯示
MainActivity
中的部分程式碼,並醒目顯示binding
變數。 - 按「Make ‘binding' ‘private'」按鈕。Android Studio 會從「Inspection Results」中移除問題。
- 如果查看程式碼中的
binding
,您會發現 Android Studio 在宣告之前新增關鍵字private
。
private lateinit var binding: ActivityMainBinding
- 按一下結果中的灰色三角形圖示,直到系統顯示「Variable declaration could be inlined」訊息。Android Studio 會再次顯示部分程式碼,但這次會醒目顯示
selectedId
變數。 - 查看程式碼時,您會發現
selectedId
僅使用過兩次:第一次是在指派tipOptions.checkedRadioButtonId
值的醒目顯示的那一行,第二次是when
中的下一行。 - 按「Inline variable」按鈕。Android Studio 會將
when
運算式中的selectedId
替換為先前指派給該行的值。它會完全移除不再需要的上一行!
val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
這項功能很酷!程式碼減少一行,變數減少一個。
移除不必要的變數
Android Studio 不會再顯示檢查作業的結果。不過,仔細查看程式碼時,您會發現與您剛剛變更的模式類似的模式:roundUp
變數指派給某一行,在下一行中使用,在其他任何位置都不使用。
- 從指派
roundUp
的行複製=
右側的運算式。
val roundUp = binding.roundUpSwitch.isChecked
- 將下一行中的
roundUp
替換為您剛剛複製的運算式binding.roundUpSwitch.isChecked
。
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
- 刪除包含
roundUp
的行,因為不再需要使用該行。
您執行的操作與 Android Studio 幫助您使用 selectedId
變數執行的操作相同。同樣地,程式碼減少一行,變數減少一個。這些異動雖然微小,但可以讓程式碼更簡潔易懂。
(選用) 移除重複的程式碼
應用程式運作正常後,您可以尋找其他機會來清理程式碼,讓程式碼更簡潔。舉例來說,如果您沒有在服務費用中輸入值,應用程式會將 tipResult
更新為空字串 ""
。如果顯示值,請使用 NumberFormat
來設定格式。可以在應用程式中的其他位置套用此功能,例如顯示小費 0.0
而非空字串。
如要減少非常類似的程式碼重複出現,您可以將這兩行程式碼擷取至其專屬函式中。這個輔助函式可以將 Double
形式的小費金額做為輸入內容,設定其格式,並更新畫面上的 tipResult
TextView
。
- 識別
MainActivity.kt
中的重複程式碼。這些程式碼行可以在calculateTip()
函式中使用多次,一次用於0.0
情況,一次用於一般情況。
val formattedTip = NumberFormat.getCurrencyInstance().format(0.0)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
- 將重複的程式碼移到專屬函式。對程式碼進行的一項變更是採用小費參數,以便在多個位置使用程式碼。
private fun displayTip(tip : Double) {
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
- 請更新
calculateTip()
函式以使用displayTip()
輔助函式,並檢查0.0
。
MainActivity.kt
private fun calculateTip() {
...
// If the cost is null or 0, then display 0 tip and exit this function early.
if (cost == null || cost == 0.0) {
displayTip(0.0)
return
}
...
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
// Display the formatted tip value on screen
displayTip(tip)
}
附註
雖然應用程式現在可正常運作,但尚未準備發布正式版。您必須進行更多測試。並且,您必須新增視覺設計,並遵循質感設計指南。以下程式碼研究室也會說明如何變更應用程式主題和應用程式圖示。
7. 解決方案程式碼
本程式碼研究室的解決方案程式碼如下。
MainActivity.kt
(請注意第一行:如果您的套件名稱與 com.example.tiptime
不同,請替換套件名稱)
package com.example.tiptime
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.tiptime.databinding.ActivityMainBinding
import java.text.NumberFormat
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.calculateButton.setOnClickListener { calculateTip() }
}
private fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDoubleOrNull()
if (cost == null) {
binding.tipResult.text = ""
return
}
val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
if (binding.roundUpSwitch.isChecked) {
tip = kotlin.math.ceil(tip)
}
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
}
修改 strings.xml
<string name="tip_amount">Tip Amount: %s</string>
修改 activity_main.xml
...
<TextView
android:id="@+id/tip_result"
...
tools:text="Tip Amount: $10" />
...
修改應用程式模組的 build.gradle
android {
...
buildFeatures {
viewBinding = true
}
...
}
8. 摘要
- 檢視區塊繫結可讓您更輕鬆地編寫與應用程式中 UI 元素互動的程式碼。
- Kotlin 中的
Double
資料類型可以儲存十進位數字。 - 使用
RadioGroup
的checkedRadioButtonId
屬性找出已選取的RadioButton
。 - 使用
NumberFormat.getCurrencyInstance()
取得格式設定工具,用於將數字格式設定為貨幣。 - 您可以使用字串參數 (例如
%s
) 來建立動態字串,這些字串仍然可以輕鬆翻譯成其他語言。 - 測試非常重要!
- 您可以使用 Android Studio 中的 Logcat 來排解應用程式當機等問題。
- 堆疊追蹤會顯示呼叫的方法清單。如果程式碼會產生例外狀況,這項功能就能派上用場。
- 例外狀況表示系統存在程式碼沒有預期到的問題。
Null
表示「無任何值」。- 並非所有程式碼都可以處理
null
值,因此請謹慎使用。 - 使用「Analyze」>「Inspect Code」取得改善程式碼的建議。
9. 更多程式碼研究室可用於改善 UI
您讓小費計算機正常運作,太棒了!您會發現,仍有許多方式可以改善 UI,讓應用程式看起來更精美。如果您有興趣,請查閱這些額外的程式碼研究室,進一步瞭解如何變更應用程式主題和應用程式圖示,以及如何遵循 Tip Time 應用程式的質感設計指南中的最佳做法!
10. 瞭解詳情
11. 自行練習
- 使用上一個練習中的烹飪單位轉換器應用程式,針對邏輯和計算項目新增程式碼,在毫升和液體盎司等單位之間來回轉換。