本程式碼研究室是 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 是一款雙人「角色」風格的遊戲,玩家透過協作的方式取得最高分。
第一位玩家會查看應用程式中的字詞,並逐一執行每個動作,這樣使用者就不會看到這個單字。第二位玩家會嘗試猜測字詞。
如要玩遊戲,第一位玩家會在裝置上開啟應用程式,然後看到「吉他」這個字詞,如下方的螢幕截圖所示。
第一個玩家會說出字詞,請小心不要實際說出這個字。
- 當第二位玩家正確猜出字時,第一位玩家按下 [我知道了] 按鈕,這會增加一個數字,並顯示下一個字詞。
- 如果第二名玩家猜到字詞,第一個玩家按下 [略過] 按鈕可將音量減少 1,直接跳到下一個字詞。
- 如要結束遊戲,請按下 [結束遊戲] 按鈕。(該系列中第一個程式碼研究室的範例程式碼並未加入這項功能)。
在這項工作中,您將下載並執行啟動應用程式,並檢查程式碼。
步驟 1:開始使用
- 下載 GuessTheWord 範例程式碼,然後在 Android Studio 中開啟專案。
- 在 Android 裝置或模擬器上執行應用程式。
- 輕觸按鈕。請注意,[略過] 按鈕可顯示下一個字詞並減少 1 個分數;「我知道了」按鈕則顯示下一個字詞並增加 1 個分數。系統並未提供 [結束遊戲] 按鈕,因此當您輕觸按鈕時,系統也不會顯示任何動作。
步驟 2:執行程式碼逐步操作說明
- 在 Android Studio 中探索程式碼,以瞭解應用程式的運作原理。
- 請務必查看下述檔案,這一點特別重要。
MainActivity.kt
這個檔案只包含範本產生的預設程式碼。
res/layout/main_activity.xml
這個檔案包含應用程式的主要版面配置。使用者瀏覽應用程式時,NavHostFragment
會代管其他片段。
UI 片段
範例程式碼包含 com.example.android.guesstheword.screens
套件中三個不同套件的三個片段:
- 標題畫面的
title/TitleFragment
- 遊戲畫面:
game/GameFragment
- 分數畫面:
score/ScoreFragment
Screen/title/TitleFragment.kt
標題片段是指應用程式啟動時,第一個顯示的畫面。點擊處理常式設定為 [播放] 按鈕,即可前往遊戲畫面。
畫面/遊戲/GameFragment.kt
這是主要片段,其中大部分動作都是發生在遊戲中:
- 變數是針對目前字詞和目前分數所定義。
resetList()
方法中定義的wordList
是遊戲中所使用的字詞範例清單。onSkip()
方法是 [略過] 按鈕的點擊處理常式。這會將分數降低 1 分,然後使用nextWord()
方法顯示下一個字詞。onCorrect()
方法是 Got It 按鈕的點擊處理常式。此方法與onSkip()
方法類似。唯一的差別在於此方法會為分數加上 1,而非減去。
Screen/score/ScoreFragment.kt
ScoreFragment
是遊戲的最終畫面,會顯示玩家的最終得分。在這個程式碼研究室中,您實作了導入這個畫面並顯示最終分數。
res/navigation/main_navigation.xml
導覽圖表顯示透過導覽方式連結片段的方式:
- 使用者可以透過標題片段前往遊戲片段。
- 使用者可在遊戲片段中瀏覽至分數片段。
- 使用者可以在分數片段中返回遊戲片段。
在這項工作中,您會找到關於 GuessTheWord 入門應用程式的問題。
- 執行範例程式碼,然後透過幾個字詞玩遊戲,接著輕觸每個字詞後輕觸 [略過] 或 [我知道了]。
- 遊戲畫面現在會顯示字詞和目前的分數。旋轉裝置或模擬器變更螢幕方向。請注意,目前的分數遺失。
- 用幾個字來玩遊戲。如果遊戲畫面顯示分數,請關閉應用程式再重新開啟。請注意,由於應用程式的狀態尚未儲存,因此遊戲從頭重新開始。
- 透過幾個字玩遊戲,然後輕觸 [結束遊戲] 按鈕。請注意,這不會發生任何變化。
應用程式問題:
- 此外,在設定變更期間 (例如裝置螢幕方向改變,或應用程式關閉及重新啟動時),啟動應用程式並不會儲存及還原應用程式狀態。
如要解決這個問題,可以使用onSaveInstanceState()
回呼。不過,使用onSaveInstanceState()
方法時,您必須撰寫額外的程式碼來儲存組合中的狀態,並實作邏輯來擷取狀態。此外,可儲存的資料量最少。 - 使用者輕觸 [結束遊戲] 按鈕時,遊戲畫面不會瀏覽至分數畫面。
您可以使用本程式碼研究室所熟悉的應用程式架構元件來解決這些問題。
應用程式架構
應用程式架構是一種設計應用程式的設計方法,並且讓各類別間的關係,讓程式碼有條不紊、在特定情況下運作良好,而且易於使用。在這組四個程式碼研究室中,您對 GuessTheWord 應用程式所做的改善,都遵循 Android 應用程式架構規範,而且您使用了 Android 架構元件。Android 應用程式架構與 MVVM (模型檢視模型) 架構模式類似。
GuessTheWord 應用程式按照疑慮區隔設計原則來分為不同類別,並分成兩類,分屬不同的類別。在本課程的第一堂程式碼研究室中,您參與的課程皆為 UI 控制器、ViewModel
和 ViewModelFactory
。
UI 控制器
UI 控制器是 UI 類別,例如 Activity
或 Fragment
。UI 控制器只能包含用來處理 UI 和作業系統互動的邏輯,例如顯示視圖及擷取使用者輸入內容。請勿在 UI 控制器中加入決策邏輯,例如決定顯示文字的邏輯。
在 GuessTheWord 範例程式碼中,使用者介面控制器分為三個片段: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
(如果沒有的話),則會建立新的現有金鑰。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
。輕觸裝置或模擬器上的 [播放] 按鈕。遊戲畫面隨即開啟。
如 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
不應包含片段、活動或檢視的參照,因為活動、片段和檢視不會改變配置的設定。
以下比較 GameFragment
使用者介面資料在加入 ViewModel
前及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
。
現在,請在兩個位置使用相同的方法:
- 將
onSkip()
和onCorrect()
方法從GameFragment
複製到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
中,因此在設定變更時,這些資料仍會保留下來。
在這項工作中,您實作了 [結束遊戲] 按鈕的點擊事件監聽器。
- 在
GameFragment
中,新增名為onEndGame()
的方法。使用者輕觸 [結束遊戲] 按鈕時,系統會呼叫onEndGame()
方法。
private fun onEndGame() {
}
- 在
GameFragment
的onCreateView()
方法中,找出用來設定 [我知道了] 和 [略過] 按鈕點擊事件監聽器的程式碼。在這兩行的正下方,設定 [結束遊戲] 按鈕的點擊事件監聽器。使用繫結變數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
套件下,建立名為另一個 Kotlin 類別並命名為ScoreViewModelFactory
。此類別將負責為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()
- 執行應用程式並玩遊戲。瀏覽部分或所有字詞,然後輕觸 [結束遊戲]。請注意,分數片段現在會顯示最終分數。
- 選用:透過篩選
ScoreViewModel
,檢查 Logcat 中的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 和作業系統互動行為的邏輯;這類使用者介面不得包含要顯示在使用者介面中的資料。將資料放入ViewModel
。 ViewModel
類別會儲存及管理 UI 相關資料。ViewModel
類別可讓資料繼續套用設定變更,例如畫面旋轉。ViewModel
是建議的 Android 架構元件之一。ViewModelProvider.Factory
是可用來建立ViewModel
物件的介面。
下表比較了使用者介面控制器和用來保存資料的 ViewModel
例項:
UI 控制器 | ViewModel |
UI 控制器的範例就是您在這個程式碼研究室中建立的 | 在這個程式碼研究室中建立的 |
不包含任何要在使用者介面中顯示的資料。 | 包含 UI 控制器在使用者介面中顯示的資料。 |
包含用來顯示資料的程式碼,以及點擊事件監聽器等使用者事件程式碼。 | 包含資料處理的程式碼。 |
系統會在每項設定變更時刪除並重新建立。 | 只有在相關 UI 控制器永久消失時 (例如活動、活動結束或片段停止) 才會解除片段的刪除作業。 |
內含資料檢視。 | 不得包含活動、片段或視圖的參照,因為它們不會改變設定,但 |
包含與關聯 | 未包含任何相關聯的使用者介面控制器的參照。 |
Udacity 課程:
Android 開發人員說明文件:
其他:
這個部分會列出在代碼研究室中,受老師主導的課程作業的可能學生作業。由老師自行決定要執行下列動作:
- 視需要指派家庭作業。
- 告知學生如何提交家庭作業。
- 批改家庭作業。
老師可視需要使用這些建議,並視情況指派其他合適的家庭作業。
如果您是自行操作本程式碼研究室,歡迎透過這些家庭作業來測試自己的知識。
回答這些問題
第 1 題
為避免在裝置設定變更期間遺失資料,請將應用程式資料儲存到哪個類別?
ViewModel
LiveData
Fragment
Activity
第 2 題
ViewModel
不可內含任何片段、活動或檢視的參照。下列敘述是否正確?
- 正確
- 不正確
第 3 題
何時會刪除 ViewModel
?
- 當相關聯的 UI 控制器在裝置螢幕方向變更時遭到刪除並重新建立時。
- 變更方向。
- 做為活動或片段的相關聯 UI 控制器完成或卸離時。
- 使用者按下 [返回] 按鈕時。
第 4 題
ViewModelFactory
介面的用途為何?
- 將
ViewModel
物件執行個體化。 - 在螢幕變更時保留資料。
- 重新整理畫面上顯示的資料。
- 在應用程式資料變更時收到通知。
開始下一堂課:
如要瞭解本課程中其他程式碼研究室的連結,請參閱 Android Kotlin 基礎程式碼程式碼到達網頁。