這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁。
標題畫面 | 遊戲畫面 | 分數畫面 |
簡介
在本程式碼研究室中,您將瞭解 Android 架構元件之一的 ViewModel
:
- 您可以使用
ViewModel
類別,以注重生命週期的方式儲存及管理 UI 相關資料。ViewModel
類別可在裝置設定變更時保留資料,例如螢幕旋轉和鍵盤可用性變更。 - 您可以使用
ViewModelFactory
類別,例項化並傳回在設定變更後仍然有效的ViewModel
物件。
必備知識
- 如何使用 Kotlin 建構基本 Android 應用程式。
- 如何使用導覽圖在應用程式中實作導覽功能。
- 如何新增程式碼,在應用程式的目的地之間導覽,以及在導覽目的地之間傳遞資料。
- 活動和片段生命週期的運作方式。
- 如何將記錄資訊新增至應用程式,並在 Android Studio 中使用 Logcat 讀取記錄。
課程內容
- 如何使用建議的 Android 應用程式架構。
- 如何在應用程式中使用
Lifecycle
、ViewModel
和ViewModelFactory
類別。 - 如何透過裝置設定變更保留 UI 資料。
- 什麼是工廠方法設計模式,以及如何使用。
- 如何使用
ViewModelProvider.Factory
介面建立ViewModel
物件。
學習內容
- 在應用程式中新增
ViewModel
,儲存應用程式資料,確保資料在設定變更後仍然有效。 - 使用
ViewModelFactory
和工廠方法設計模式,以建構函式參數例項化ViewModel
物件。
在第 5 課的程式碼研究室中,您將從範例程式碼開始開發 GuessTheWord 應用程式。GuessTheWord 是雙人比手畫腳遊戲,玩家要彼此合作,盡可能獲得最高分。
第一位玩家看著應用程式中的字詞,輪流表演每個字詞,但要確保第二位玩家看不到字詞。第二位玩家嘗試猜測字詞。
如要開始遊戲,第一位玩家要在裝置上開啟應用程式,並看到一個字詞,例如「吉他」,如下方螢幕截圖所示。
第一位玩家要表演這個字詞,但不能說出該字詞。
- 第二位玩家猜對字詞後,第一位玩家會按下「Got It」按鈕,計數會增加一,並顯示下一個字詞。
- 如果第二位玩家猜不出單字,第一位玩家可以按下「略過」按鈕,這樣一來,計數就會減少 1,並跳到下一個單字。
- 如要結束遊戲,請按下「結束遊戲」按鈕。(這項功能不包含在系列第一個程式碼研究室的範例程式碼中)。
在這項工作中,您將下載並執行範例應用程式,然後檢查程式碼。
步驟 1:開始使用
- 下載 GuessTheWord 範例程式碼,並在 Android Studio 中開啟專案。
- 在 Android 裝置或模擬器上執行應用程式。
- 輕觸按鈕。請注意,「略過」按鈕會顯示下一個字詞,並將分數減少 1 分;「答對了」按鈕會顯示下一個字詞,並將分數增加 1 分。「End Game」按鈕尚未實作,因此輕觸該按鈕不會有任何反應。
步驟 2:逐步檢查程式碼
- 在 Android Studio 中瀏覽程式碼,瞭解應用程式的運作方式。
- 請務必查看下列檔案,這些檔案特別重要。
MainActivity.kt
這個檔案只包含範本產生的預設程式碼。
res/layout/main_activity.xml
這個檔案包含應用程式的主要版面配置。使用者瀏覽應用程式時,NavHostFragment
會代管其他片段。
UI 片段
範例程式碼在 com.example.android.guesstheword.screens
套件下有三個不同套件中的三個片段:
title/TitleFragment
,適用於標題畫面game/GameFragment
適用於遊戲畫面score/ScoreFragment
,用於顯示分數畫面
screens/title/TitleFragment.kt
應用程式啟動時,系統會顯示標題片段,點按處理常式會設為「Play」按鈕,以便導覽至遊戲畫面。
screens/game/GameFragment.kt
這是主要片段,也是大部分遊戲動作發生處:
- 變數是根據目前字詞和目前分數所定義。
resetList()
方法中定義的wordList
是遊戲中使用的字詞範例清單。onSkip()
方法是「Skip」按鈕的點擊處理常式。將分數減 1,然後使用nextWord()
方法顯示下一個字。onCorrect()
方法是「我知道了」按鈕的點擊處理常式。這個方法的實作方式與onSkip()
方法類似。唯一的差別在於這個方法會將分數加 1,而不是減 1。
screens/score/ScoreFragment.kt
ScoreFragment
是遊戲的最後一個畫面,會顯示玩家的最終分數。在本程式碼研究室中,您將新增實作項目,顯示這個畫面並顯示最終得分。
res/navigation/main_navigation.xml
導覽圖顯示片段如何透過導覽功能連結:
- 使用者可以從標題片段前往遊戲片段。
- 使用者可以從遊戲片段前往分數片段。
- 使用者可以從分數片段返回遊戲片段。
在這項工作中,您會找出 GuessTheWord 範例應用程式的問題。
- 執行範例程式碼,透過幾個字詞進行遊戲,並在每個字詞後輕觸「略過」或「我知道了」。
- 遊戲畫面現在會顯示一個字詞和目前的分數。旋轉裝置或模擬器變更螢幕方向。請注意,目前的分數會消失。
- 使用更多字詞進行遊戲。當遊戲畫面顯示分數時,請關閉並重新開啟應用程式。你會發現遊戲會從頭開始,因為應用程式狀態未儲存。
- 透過數個字詞進行遊戲,然後輕觸「結束遊戲」按鈕。請注意,畫面不會有任何變化。
應用程式問題:
- 設定變更時 (例如裝置螢幕方向變更,或應用程式關閉並重新啟動),範例應用程式不會儲存及還原應用程式狀態。
您可以使用onSaveInstanceState()
回呼解決這個問題。不過,使用onSaveInstanceState()
方法時,您必須編寫額外的程式碼將狀態儲存在套件中,並實作邏輯以擷取該狀態。此外,可儲存的資料量極少。 - 使用者輕觸「End Game」按鈕時,遊戲畫面不會導向分數畫面。
您可以使用在本程式碼研究室所學到的應用程式架構元件來解決這些問題。
應用程式架構
應用程式架構可協助您設計應用程式的類別,以及類別之間的關係,讓程式碼井然有序、在特定情境中運作良好,且易於使用。在本系列四個程式碼研究室中,您對 GuessTheWord 應用程式所做的改善,都遵循 Android 應用程式架構指南,並使用 Android 架構元件。Android 應用程式架構與 MVVM (模型-檢視區塊-檢視區塊模型) 架構模式類似。
GuessTheWord 應用程式遵循關注點分離設計原則,並劃分為多個類別,每個類別負責處理不同的關注點。在本課程的第一個程式碼研究室中,您會使用 UI 控制器、ViewModel
和 ViewModelFactory
類別。
UI 控制器
UI 控制器是以 UI 為基礎的類別,例如 Activity
或 Fragment
。UI 控制器只能包含處理 UI 和作業系統互動的邏輯,例如顯示檢視畫面和擷取使用者輸入內容。請勿將決策邏輯 (例如決定要顯示的文字) 放入 UI 控制器。
在 GuessTheWord 範例程式碼中,UI 控制器是三個片段:GameFragment
、ScoreFragment,
和 TitleFragment
。遵循「關注點分離」設計原則,GameFragment
只負責在畫面上繪製遊戲元素,以及瞭解使用者何時輕觸按鈕,除此之外一概不負責。使用者輕觸按鈕時,這項資訊會傳遞至 GameViewModel
。
ViewModel
ViewModel
會存放要顯示在 ViewModel
相關片段或活動中的資料。ViewModel
可以對資料執行簡單的計算和轉換,方便 UI 控制器顯示資料。在這個架構中,ViewModel
會執行決策。GameViewModel
會保存分數值、字詞清單和目前字詞等資料,因為這些資料會顯示在畫面上。GameViewModel
也包含商業邏輯,可執行簡單的計算,決定資料的目前狀態。
ViewModelFactory
ViewModelFactory
會例項化 ViewModel
物件,可包含或不包含建構函式參數。
在後續的程式碼研究室中,您將瞭解與 UI 控制器和 ViewModel
相關的其他 Android 架構元件。
ViewModel
類別專為儲存及管理 UI 相關資料而設計。在這個應用程式中,每個 ViewModel
都與一個片段相關聯。
在這項工作中,您要在應用程式中新增第一個 ViewModel
,也就是 GameFragment
的 GameViewModel
。您也會瞭解 ViewModel
可感知生命週期的意義。
步驟 1:新增 GameViewModel 類別
- 開啟
build.gradle(module:app)
檔案,在dependencies
區塊中,為ViewModel
新增 Gradle 依附元件。
如果您使用最新版程式庫,解決方案應用程式應可正常編譯。如果沒有,請嘗試解決問題,或還原至下方顯示的版本。
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- 在
screens/game/
資料夾中,建立名為GameViewModel
的新 Kotlin 類別。 - 讓
GameViewModel
類別擴充抽象類別ViewModel
。 - 如要進一步瞭解
ViewModel
如何感知生命週期,請新增含有log
陳述式的init
區塊。
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
步驟 2:覆寫 onCleared() 並新增記錄
在您卸離相關片段或活動完成後,系統會將 ViewModel
刪除。在 ViewModel
刪除之前,系統會呼叫 onCleared()
回呼來清除資源。
- 在
GameViewModel
類別中,覆寫onCleared()
方法。 - 在
onCleared()
中新增記錄陳述式,以追蹤GameViewModel
生命週期。
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
步驟 3:將 GameViewModel 與遊戲片段建立關聯
ViewModel
必須與 UI 控制器建立關聯。如要建立兩者之間的關聯,請在 UI 控制器內建立 ViewModel
的參照。
在這個步驟中,您會在對應的 UI 控制器 (GameFragment
) 中建立 GameViewModel
的參照。
- 在
GameFragment
類別中,於頂層新增GameViewModel
類型的欄位做為類別變數。
private lateinit var viewModel: GameViewModel
步驟 4:初始化 ViewModel
在螢幕旋轉等設定變更期間,系統會重新建立片段等 UI 控制器。不過,ViewModel
例項會繼續存留。如果您使用 ViewModel
類別建立 ViewModel
執行個體,每次重新建立片段時,系統都會建立新物件。請改為使用 ViewModelProvider
建立 ViewModel
執行個體。
ViewModelProvider
的運作方式:
ViewModelProvider
會傳回現有的ViewModel
(如有),或建立新的ViewModel
(如尚不存在)。ViewModelProvider
會建立與指定範圍 (活動或片段) 相關聯的ViewModel
例項。- 只要範圍維持有效,系統就會保留建立的
ViewModel
。舉例來說,如果範圍是片段,系統會保留ViewModel
,直到片段分離為止。
使用 ViewModelProviders.of()
方法建立 ViewModelProvider
,藉此初始化 ViewModel
:
- 在
GameFragment
類別中,初始化viewModel
變數。將這段程式碼放在onCreateView()
內,繫結變數定義之後。使用ViewModelProviders.of()
方法,並傳入相關聯的GameFragment
內容和GameViewModel
類別。 - 在
ViewModel
物件的初始化上方,新增記錄陳述式,記錄ViewModelProviders.of()
方法呼叫。
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
- 執行應用程式。在 Android Studio 中開啟「Logcat」窗格,然後篩選
Game
。輕觸裝置或模擬器上的「Play」按鈕。遊戲畫面隨即開啟。
如 Logcat 所示,GameFragment
的onCreateView()
方法會呼叫ViewModelProviders.of()
方法來建立GameViewModel
。您新增至GameFragment
和GameViewModel
的記錄陳述式會顯示在 Logcat 中。
- 在裝置或模擬器上啟用自動旋轉設定,並變更螢幕方向數次。每次都會刪除並重新建立
GameFragment
,因此每次都會呼叫ViewModelProviders.of()
。但GameViewModel
只會建立一次,而且不會在每次呼叫時重新建立或刪除。
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- 離開遊戲,或離開遊戲片段。
GameFragment
已刪除。相關聯的GameViewModel
也會遭到刪除,並呼叫onCleared()
回呼。
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel
會在設定變更時保留,因此適合存放需要在設定變更後繼續留存的資料:
- 將要顯示在畫面上的資料,以及處理該資料的程式碼,放在
ViewModel
中。 ViewModel
不得包含片段、活動或檢視區塊的參照,因為活動、片段和檢視區塊在設定變更後會失效。
為了進行比較,以下說明在您新增 ViewModel
前後,啟動器應用程式如何處理 GameFragment
UI 資料:ViewModel
- 新增
ViewModel
前:
應用程式進行設定變更 (例如螢幕旋轉) 時,遊戲片段會遭到刪除並重新建立。資料會遺失。 - 新增
ViewModel
並將遊戲片段的 UI 資料移至ViewModel
後:片段需要顯示的所有資料現在都是ViewModel
。
應用程式經過設定變更後,ViewModel
會保留下來,資料也會一併保留。
在這項工作中,您要將應用程式的 UI 資料移至 GameViewModel
類別,以及處理資料的方法。這樣做是為了在設定變更期間保留資料。
步驟 1:將資料欄位和資料處理作業移至 ViewModel
將下列資料欄位和方法從 GameFragment
移至 GameViewModel
:
- 移動
word
、score
和wordList
資料欄位。請確認word
和score
不是private
。
請勿移動繫結變數GameFragmentBinding
,因為其中包含對檢視區塊的參照。這個變數用於擴充版面配置、設定點擊事件監聽器,以及在畫面上顯示資料,這些都是片段的責任。 - 移動
resetList()
和nextWord()
方法。這些方法會決定要在畫面上顯示的字詞。 - 在
onCreateView()
方法中,將resetList()
和nextWord()
的方法呼叫移至GameViewModel
的init
區塊。
這些方法必須位於init
區塊中,因為您應該在建立ViewModel
時重設字詞清單,而不是每次建立片段時都重設。您可以刪除GameFragment
的init
區塊中的記錄陳述式。
GameFragment
中的 onSkip()
和 onCorrect()
點擊事件處理常式包含處理資料和更新 UI 的程式碼。更新 UI 的程式碼應保留在片段中,但處理資料的程式碼需要移至 ViewModel
。
目前請在兩個位置加入相同的方法:
- 將
GameFragment
中的onSkip()
和onCorrect()
方法複製到GameViewModel
。 - 在
GameViewModel
中,請確認onSkip()
和onCorrect()
方法不是private
,因為您會從片段參照這些方法。
以下是重構後的 GameViewModel
類別程式碼:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
以下是重構後的 GameFragment
類別程式碼:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}
步驟 2:更新 GameFragment 中對點擊事件處理常式和資料欄位的參照
- 在
GameFragment
中,更新onSkip()
和onCorrect()
方法。移除更新分數的程式碼,改為呼叫viewModel
中對應的onSkip()
和onCorrect()
方法。 - 由於您已將
nextWord()
方法移至ViewModel
,遊戲片段無法再存取該方法。
在GameFragment
的onSkip()
和onCorrect()
方法中,將對nextWord()
的呼叫替換為updateScoreText()
和updateWordText()
。這些方法會在畫面上顯示資料。
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- 在
GameFragment
中,將score
和word
變數更新為使用GameViewModel
變數,因為這些變數現在位於GameViewModel
中。
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- 在
GameViewModel
的nextWord()
方法中,移除對updateWordText()
和updateScoreText()
方法的呼叫。這些方法現在會從GameFragment
呼叫。 - 建構應用程式,確保沒有錯誤。如有錯誤,請清除專案再重新建構。
- 執行應用程式,使用一些字詞進行遊戲。在遊戲畫面中旋轉裝置,請注意,螢幕方向變更後,目前的分數和字詞會保留。
做得好!現在所有應用程式資料都會儲存在 ViewModel
中,因此在設定變更期間會保留下來。
在這項工作中,您要實作「End Game」按鈕的點擊事件監聽器。
- 在
GameFragment
中,新增名為onEndGame()
的方法。使用者輕觸「End Game」按鈕時,系統會呼叫onEndGame()
方法。
private fun onEndGame() {
}
- 在
GameFragment
的onCreateView()
方法中,找出為「Got It」和「Skip」按鈕設定點擊事件監聽器的程式碼。在這兩行下方,為「End Game」按鈕設定點選監聽器。使用繫結變數binding
。在點擊事件監聽器內,呼叫onEndGame()
方法。
binding.endGameButton.setOnClickListener { onEndGame() }
- 在
GameFragment
中,新增名為gameFinished()
的方法,將應用程式導覽至分數畫面。使用 Safe Args 將分數當做引數傳遞。
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}
- 在
onEndGame()
方法中,呼叫gameFinished()
方法。
private fun onEndGame() {
gameFinished()
}
- 執行應用程式並進行遊戲,然後循環瀏覽一些字詞。輕觸「結束遊戲」 按鈕。請注意,應用程式會導覽至分數畫面,但不會顯示最終分數。您將在下一個工作修正此問題。
使用者結束遊戲時,ScoreFragment
不會顯示分數。您希望 ViewModel
保留要由 ScoreFragment
顯示的分數。您將使用工廠方法模式,在 ViewModel
初始化期間傳遞分數值。
工廠方法模式是一種建立設計模式,會使用工廠方法建立物件。工廠方法會傳回相同類別的例項。
在這項工作中,您要建立 ViewModel
,其中包含分數片段的參數化建構函式,以及用於例項化 ViewModel
的工廠方法。
- 在
score
套件下,建立名為ScoreViewModel
的新 Kotlin 類別。這個類別將做為分數片段的ViewModel
。 - 從
ViewModel.
擴充ScoreViewModel
類別,並新增最終分數的建構函式參數。新增含有記錄陳述式的init
區塊。 - 在
ScoreViewModel
類別中,新增名為score
的變數,以儲存最終分數。
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- 在
score
套件下,建立另一個名為ScoreViewModelFactory
的 Kotlin 類別。這個類別負責例項化ScoreViewModel
物件。 - 從
ViewModelProvider.Factory
擴充ScoreViewModelFactory
類別。新增最終分數的建構函式參數。
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- 在
ScoreViewModelFactory
中,Android Studio 會顯示有關未實作抽象成員的錯誤。如要解決錯誤,請覆寫create()
方法。在create()
方法中,傳回新建立的ScoreViewModel
物件。
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
- 在
ScoreFragment
中,為ScoreViewModel
和ScoreViewModelFactory
建立類別變數。
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- 在
ScoreFragment
的onCreateView()
中,初始化binding
變數後,初始化viewModelFactory
。使用ScoreViewModelFactory
。將引數套件中的最終分數做為建構函式參數,傳遞至ScoreViewModelFactory()
。
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- 在
onCreateView(
中,初始化viewModelFactory
後,請初始化viewModel
物件。呼叫ViewModelProviders.of()
方法,傳入相關聯的分數片段內容和viewModelFactory
。這會使用viewModelFactory
類別中定義的工廠方法建立ScoreViewModel
物件.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- 在
onCreateView()
方法中,初始化viewModel
後,將scoreText
檢視區塊的文字設為ScoreViewModel
中定義的最終分數。
binding.scoreText.text = viewModel.score.toString()
- 執行應用程式,然後進行遊戲。瀏覽部分或所有字詞,然後輕觸「結束遊戲」。請注意,分數片段現在會顯示最終分數。
- 選用:在 Logcat 中依
ScoreViewModel
篩選,檢查ScoreViewModel
記錄。系統應會顯示分數值。
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
在這項工作中,您實作了 ScoreFragment
,以便使用 ViewModel
。您也學會如何使用 ViewModelFactory
介面,為 ViewModel
建立參數化建構函式。
恭喜!您已變更應用程式的架構,改用 Android 架構元件 ViewModel
。您已解決應用程式的生命週期問題,現在遊戲資料在設定變更後仍可保留。您也學會如何使用 ViewModelFactory
介面,建立用於建立 ViewModel
的參數化建構函式。
Android Studio 專案:GuessTheWord
- Android 應用程式架構指南建議將具有不同責任的類別分離。
- UI 控制器是以 UI 為基礎的類別,例如
Activity
或Fragment
。UI 控制器只能包含處理 UI 和作業系統互動的邏輯,不應包含要在 UI 中顯示的資料。將該資料存放在ViewModel
中。 ViewModel
類別會儲存和管理 UI 相關資料。ViewModel
類別可在螢幕旋轉等變更時保留資料。ViewModel
是建議使用的 Android 架構元件之一。ViewModelProvider.Factory
是可用於建立ViewModel
物件的介面。
下表比較 UI 控制器與為其保留資料的 ViewModel
例項:
UI 控制器 | ViewModel |
您在本程式碼研究室中建立的 | 您在本程式碼研究室中建立的 |
不含任何要在使用者介面中顯示的資料。 | 內含 UI 控制器在 UI 中顯示的資料。 |
包含用於顯示資料的程式碼,以及使用者事件程式碼,例如點擊監聽器。 | 包含資料處理的程式碼。 |
每次設定變更時都會銷毀並重新建立。 | 只有在相關聯的 UI 控制器永久消失時才會遭到刪除,例如活動完成時 (如果是活動),或是片段卸離時 (如果是片段)。 |
包含檢視畫面。 | 不得包含活動、片段或檢視區塊的參照,因為這些項目不會在設定變更後保留,但 |
包含相關聯 | 不包含任何相關聯的 UI 控制器參照。 |
Udacity 課程:
Android 開發人員說明文件:
其他:
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
回答問題
第 1 題
為了避免在裝置設定變更期間遺失資料,你應該在哪個類別之中儲存應用程式資料?
ViewModel
LiveData
Fragment
Activity
第 2 題
ViewModel
不可內含任何片段、活動或檢視的參照。下列敘述是否正確?
- 是
- 錯誤
第 3 題
ViewModel
會在何時遭到刪除?
- 相關聯的 UI 控制器在裝置螢幕方向變更時遭到刪除並重新建立時。
- 螢幕方向改變時。
- 做為活動或片段的相關聯 UI 控制器完成或卸離時。
- 當使用者按下返回按鈕時。
第 4 題
ViewModelFactory
介面的用途為何?
- 建立
ViewModel
物件的例項。 - 在變更螢幕方向期間保留資料。
- 重新整理顯示於螢幕上的資料。
- 在應用程式資料變更時收到通知。
開始下一個課程:
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面。