這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁。
簡介
在本課程的先前程式碼研究室中,您已改善 GuessTheWord 應用程式的程式碼。應用程式現在使用 ViewModel 物件,因此應用程式資料在裝置設定變更時 (例如螢幕旋轉和鍵盤可用性變更) 仍會保持有效。您也新增了可觀察的 LiveData,因此當觀察到的資料變更時,檢視區塊會自動收到通知。
在本程式碼研究室中,您將繼續使用 GuessTheWord 應用程式。您會將檢視區塊繫結至應用程式中的 ViewModel 類別,讓版面配置中的檢視區塊直接與 ViewModel 物件通訊。(到目前為止,應用程式中的檢視畫面都是透過應用程式的片段,與 ViewModel 間接通訊。)將資料繫結與 ViewModel 物件整合後,您就不再需要應用程式片段中的點擊處理常式,因此請移除這些處理常式。
您也會變更 GuessTheWord 應用程式,使用 LiveData 做為資料繫結來源,通知 UI 資料變更,而不使用 LiveData 觀察器方法。
必備知識
- 如何使用 Kotlin 建構基本 Android 應用程式。
- 活動和片段生命週期的運作方式。
- 如何在應用程式中使用
ViewModel物件。 - 如何在
ViewModel中使用LiveData儲存資料。 - 如何新增觀察器方法,觀察
LiveData資料的變化。
課程內容
- 如何使用資料繫結程式庫的元素。
- 如何透過資料繫結整合
ViewModel。 - 如何透過資料繫結整合
LiveData。 - 如何使用事件監聽器繫結,取代片段中的點擊事件監聽器。
- 如何將字串格式新增至資料繫結運算式。
學習內容
- GuessTheWord 版面配置中的檢視畫面會使用 UI 控制器 (片段) 轉發資訊,間接與
ViewModel物件通訊。在本程式碼研究室中,您會將應用程式的檢視區塊繫結至ViewModel物件,讓檢視區塊直接與ViewModel物件通訊。 - 您將應用程式變更為使用
LiveData做為資料繫結來源。完成這項變更後,LiveData物件會將資料變更通知 UI,不再需要LiveData觀察器方法。
在第 5 課的程式碼研究室中,您將從範例程式碼開始開發 GuessTheWord 應用程式。GuessTheWord 是雙人比手畫腳遊戲,玩家要彼此合作,盡可能獲得最高分。
第一位玩家看著應用程式中的字詞,輪流表演每個字詞,但要確保第二位玩家看不到字詞。第二位玩家嘗試猜測字詞。
如要開始遊戲,第一位玩家要在裝置上開啟應用程式,並看到一個字詞,例如「吉他」,如下方螢幕截圖所示。
第一位玩家要表演這個字詞,但不能說出該字詞。
- 第二位玩家猜對字詞後,第一位玩家會按下「Got It」按鈕,計數會增加一,並顯示下一個字詞。
- 如果第二位玩家猜不出單字,第一位玩家可以按下「略過」按鈕,這樣一來,計數就會減少 1,並跳到下一個單字。
- 如要結束遊戲,請按下「結束遊戲」按鈕。(這項功能不包含在系列第一個程式碼研究室的範例程式碼中)。
在本程式碼研究室中,您將在 ViewModel 物件中整合資料繫結與 LiveData,藉此改善 GuessTheWord 應用程式。這項功能會自動處理版面配置中的檢視區塊與 ViewModel 物件之間的通訊,並讓您使用 LiveData 簡化程式碼。
標題畫面 |
遊戲畫面 |
分數畫面 |
在這項工作中,您將找出並執行本程式碼研究室的範例程式碼。您可以使用先前程式碼研究室中建構的 GuessTheWord 應用程式做為範例程式碼,也可以下載範例應用程式。
- (選用) 如果您未使用上一個程式碼研究室的程式碼,請下載本程式碼研究室的範例程式碼。解壓縮程式碼,並在 Android Studio 中開啟專案。
- 執行應用程式並玩遊戲。
- 請注意,「Got It」按鈕會顯示下一個字詞,並將分數增加 1 分;「Skip」按鈕會顯示下一個字詞,並將分數減少 1 分。結束遊戲按鈕會結束遊戲。
- 逐一輸入所有單字,你會發現應用程式會自動前往分數畫面。
在先前的程式碼研究室中,您已使用資料繫結,以型別安全的方式存取 GuessTheWord 應用程式中的檢視區塊。但資料繫結的真正強大之處,在於直接將資料繫結至應用程式中的檢視區塊物件。
目前的應用程式架構
在應用程式中,檢視畫面是在 XML 版面配置中定義,而這些檢視畫面的資料則保存在 ViewModel 物件中。每個檢視區塊和對應的 ViewModel 之間都有 UI 控制器,做為兩者之間的中繼。

例如:
- 「我知道了」按鈕在
game_fragment.xml版面配置檔案中定義為Button檢視區塊。 - 使用者輕觸「我知道了」按鈕時,
GameFragment片段中的點擊事件監聽器會呼叫GameViewModel中的對應點擊事件監聽器。 - 分數會在
GameViewModel中更新。
Button 檢視區塊和 GameViewModel 不會直接通訊,而是需要 GameFragment 中的點擊事件監聽器。
傳遞至資料繫結的 ViewModel
如果版面配置中的檢視畫面直接與 ViewModel 物件中的資料通訊,而不必依賴 UI 控制器做為中介,就會簡單許多。

ViewModel 物件會保存「猜單字」應用程式中的所有 UI 資料。將 ViewModel 物件傳遞至資料繫結後,您就能自動化處理部分檢視區塊與 ViewModel 物件之間的通訊。
在這項工作中,您要將 GameViewModel 和 ScoreViewModel 類別與對應的 XML 版面配置建立關聯。您也設定了監聽器繫結,用來處理點擊事件。
步驟 1:為 GameViewModel 新增資料繫結
在這個步驟中,您會將 GameViewModel 與對應的版面配置檔案 game_fragment.xml 建立關聯。
- 在
game_fragment.xml檔案中,新增GameViewModel型別的資料繫結變數。如果 Android Studio 中發生錯誤,請清除專案再重新建構。
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- 在
GameFragment檔案中,將GameViewModel傳遞至資料繫結。
如要執行這項操作,請將viewModel指派給您在上一個步驟中宣告的binding.gameViewModel變數。將這段程式碼放在onCreateView()內,並在viewModel初始化後執行。如果 Android Studio 中發生錯誤,請清除專案再重新建構。
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModel步驟 2:使用事件監聽器繫結來處理事件
事件監聽器繫結是指在觸發 onClick()、onZoomIn() 或 onZoomOut() 等事件時執行的繫結運算式。事件監聽器繫結都會寫成 lambda 運算式。
資料繫結會建立監聽器,並在檢視區塊中設定監聽器。當監聽的事件發生時,監聽器會評估 lambda 運算式。事件監聽器繫結適用於 Android Gradle 外掛程式 2.0 以上版本。詳情請參閱「版面配置和繫結運算式」。
在這個步驟中,您會將 GameFragment 中的點擊事件監聽器,替換為 game_fragment.xml 檔案中的事件監聽器繫結。
- 在
game_fragment.xml中,將onClick屬性新增至skip_button。定義繫結運算式,並在GameViewModel中呼叫onSkip()方法。這個繫結運算式稱為「事件監聽器繫結」。
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />- 同樣地,請將
correct_button的點擊事件繫結至GameViewModel中的onCorrect()方法。
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />- 將
end_game_button的點擊事件繫結至GameViewModel中的onGameFinish()方法。
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />- 在
GameFragment中,移除設定點按事件監聽器的陳述式,並移除點按事件監聽器呼叫的函式。你不再需要這些檔案。
要移除的程式碼:
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}步驟 3:為 ScoreViewModel 新增資料繫結
在這個步驟中,您會將 ScoreViewModel 與對應的版面配置檔案 score_fragment.xml 建立關聯。
- 在
score_fragment.xml檔案中,新增ScoreViewModel型別的繫結變數。這個步驟與您為上述GameViewModel所做操作類似。
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout- 在
score_fragment.xml中,將onClick屬性新增至play_again_button。定義監聽器繫結,並在ScoreViewModel中呼叫onPlayAgain()方法。
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />- 在
ScoreFragment的onCreateView()內,初始化viewModel。然後初始化binding.scoreViewModel繫結變數。
viewModel = ...
binding.scoreViewModel = viewModel- 在
ScoreFragment中,移除設定playAgainButton點擊事件監聽器的程式碼。如果 Android Studio 顯示錯誤,請清除並重建專案。
要移除的程式碼:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }- 執行應用程式。應用程式應可照常運作,但現在按鈕檢視區塊會直接與
ViewModel物件通訊。檢視畫面不再透過ScoreFragment中的按鈕點擊處理常式進行通訊。
排解資料繫結錯誤訊息
應用程式使用資料繫結時,編譯程序會產生用於資料繫結的中介類別。應用程式可能發生錯誤,但 Android Studio 要等到您嘗試編譯應用程式時才會偵測到,因此您在編寫程式碼時不會看到警告或紅色程式碼。但在編譯時,您會收到來自產生的中介類別的隱晦錯誤。
如果收到難以理解的錯誤訊息,請按照下列步驟操作:
- 仔細查看 Android Studio「Build」窗格中的訊息。如果看到結尾為
databinding的位置,表示資料繫結發生錯誤。 - 在版面配置 XML 檔案中,檢查使用資料繫結的
onClick屬性是否有錯誤。找出 lambda 運算式呼叫的函式,並確認該函式存在。 - 在 XML 的
<data>區段中,檢查資料繫結變數的拼字。
舉例來說,請注意下列屬性值中函式名稱 onCorrect() 的拼字錯誤:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
另請注意 XML 檔案 <data> 區段中 gameViewModel 的拼字錯誤:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>Android Studio 不會偵測到這類錯誤,直到您編譯應用程式為止,編譯器隨後會顯示類似下列內容的錯誤訊息:
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
資料繫結可與搭配 ViewModel 物件使用的 LiveData 完美搭配。現在您已將資料繫結新增至 ViewModel 物件,可以開始加入 LiveData。
在這項工作中,您會變更 GuessTheWord 應用程式,使用 LiveData 做為資料繫結來源,通知 UI 資料變更,而不使用 LiveData 觀察器方法。
步驟 1:將 word LiveData 新增至 game_fragment.xml 檔案
在這個步驟中,您要將目前的字詞文字檢視區塊直接繫結至 ViewModel 中的 LiveData 物件。
- 在
game_fragment.xml中,將android:text屬性新增至word_text文字檢視區塊。
使用繫結變數 gameViewModel,將其設為 GameViewModel 中的 LiveData 物件 word。
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />請注意,您不必使用 word.value。請改用實際的 LiveData 物件。LiveData 物件會顯示 word 的目前值。如果 word 的值為空值,LiveData 物件會顯示空字串。
- 在
GameFragment的onCreateView()中,初始化gameViewModel後,將目前的活動設為binding變數的生命週期擁有者。這會定義上述LiveData物件的範圍,讓物件自動更新版面配置中的檢視區塊game_fragment.xml。
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this- 在
GameFragment中,移除LiveDataword的觀察器。
要移除的程式碼:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})- 執行應用程式,然後進行遊戲。現在,系統會更新目前字詞,而 UI 控制器中沒有觀察器方法。
步驟 2:將分數 LiveData 新增至 score_fragment.xml 檔案
在這個步驟中,您要將 LiveData score 繫結至分數片段中的分數文字檢視區塊。
- 在
score_fragment.xml中,將android:text屬性新增至分數文字檢視區塊。將scoreViewModel.score指派給text屬性。由於score是整數,請使用String.valueOf()將其轉換為字串。
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />- 在
ScoreFragment中初始化scoreViewModel後,將目前的活動設為binding變數的生命週期擁有者。
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this- 在
ScoreFragment中,移除score物件的觀察器。
要移除的程式碼:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- 執行應用程式,然後進行遊戲。請注意,分數片段中的分數會正確顯示,分數片段中沒有觀察者。
步驟 3:使用資料繫結新增字串格式
在版面配置中,您可以新增字串格式設定和資料繫結。在這項工作中,您會格式化目前的字詞,在前後加上引號。您也會格式化分數字串,在前面加上「Current Score」,如下圖所示。

- 在
string.xml中新增下列字串,用於設定word和score文字檢視區塊的格式。%s和%d是目前字詞和目前分數的預留位置。
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>- 在
game_fragment.xml中,更新word_text文字檢視區塊的text屬性,以使用quote_format字串資源。傳入gameViewModel.word。這會將目前字詞做為引數傳遞至格式字串。
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />- 設定
score文字檢視區塊的格式,類似於word_text。在game_fragment.xml中,將text屬性新增至score_text文字檢視區塊。使用字串資源score_format,其中包含一個以%d預留位置表示的數值引數。將LiveData物件score做為引數,傳遞至這個格式化字串。
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />- 在
GameFragment類別的onCreateView()方法中,移除score觀察器程式碼。
要移除的程式碼:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- 清理、重建及執行應用程式,然後進行遊戲。請注意,遊戲畫面會顯示目前的字詞和分數。

恭喜!您已在應用程式中將 LiveData 和 ViewModel 與資料繫結整合。這樣一來,版面配置中的檢視畫面就能直接與 ViewModel 通訊,不必在片段中使用點擊處理常式。您也使用 LiveData 物件做為資料繫結來源,自動向 UI 通知資料異動情形,而不需要 LiveData 觀察器方法。
Android Studio 專案:GuessTheWord
- 資料繫結程式庫可與
ViewModel和LiveData等 Android 架構元件完美搭配運作。 - 應用程式中的版面配置可以繫結至架構元件中的資料,這有助於管理 UI 控制器的生命週期,並通知資料變更。
ViewModel 資料繫結
- 您可以使用資料繫結,將
ViewModel與版面配置建立關聯。 ViewModel物件會保留 UI 資料。將ViewModel物件傳遞至資料繫結,即可自動化處理檢視區塊與ViewModel物件間的部分通訊作業。
如何將 ViewModel 與版面配置建立關聯:
- 在版面配置檔案中,新增
ViewModel型別的資料繫結變數。
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>- 在
GameFragment檔案中,將GameViewModel傳遞至資料繫結。
binding.gameViewModel = viewModel事件監聽器繫結
- 事件監聽器繫結是版面配置中的繫結運算式,會在觸發點擊事件 (例如
onClick()) 時執行。 - 事件監聽器繫結都會寫成 lambda 運算式。
- 使用事件監聽器繫結,將 UI 控制器中的點按事件監聽器替換為版面配置檔案中的事件監聽器繫結。
- 資料繫結會建立監聽器,並在檢視區塊中設定監聽器。
android:onClick="@{() -> gameViewModel.onSkip()}"將 LiveData 新增至資料繫結
LiveData物件可做為資料繫結來源,自動向 UI 通知資料異動情形。- 您可以將檢視區塊直接繫結至
ViewModel中的LiveData物件。當ViewModel中的LiveData變更時,版面配置中的檢視區塊會自動更新,不需要 UI 控制器中的觀察器方法。
android:text="@{gameViewModel.word}"- 如要讓
LiveData資料繫結正常運作,請將目前活動 (UI 控制器) 設為 UI 控制器中binding變數的生命週期擁有者。
binding.lifecycleOwner = this使用資料繫結格式化字串
- 使用資料繫結時,您可以透過
%s(適用於字串) 和%d(適用於整數) 等預留位置,設定字串資源的格式。 - 如要更新檢視區塊的
text屬性,請將LiveData物件當做引數傳遞至格式字串。
android:text="@{@string/quote_format(gameViewModel.word)}"Udacity 課程:
Android 開發人員說明文件:
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
回答問題
第 1 題
以下有關事件監聽器繫結的敘述,何者「不」正確?
- 事件監聽器繫結指的是會在事件發生時執行的繫結運算式。
- 事件監聽器繫結適用於所有版本的 Android Gradle 外掛程式。
- 事件監聽器繫結都會寫成 lambda 運算式。
- 事件監聽器繫結類似於方法的參照,但可讓您執行任意的資料繫結運算式。
第 2 題
假設應用程式內含這個字串資源:<string name="generic_name">Hello %s</string>
以下哪一個是使用資料繫結運算式,為字串設定格式的正確語法?
android:text= "@{@string/generic_name(user.name)}"android:text= "@{string/generic_name(user.name)}"android:text= "@{@generic_name(user.name)}"android:text= "@{@string/generic_name,user.name}"
第 3 題
何時會評估並執行事件監聽器繫結運算式?
LiveData保存的資料有所變更時- 活動藉由變更設定而重新建立時
- 當發生類似
onClick()的事件時 - 活動進入背景時
開始下一個課程:
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面。


