將 LiveData 搭配 ViewModel 使用

1. 事前準備

在先前的程式碼研究室中,您已瞭解如何使用 ViewModel 儲存應用程式資料。ViewModel 可在設定變更時保留應用程式資料。在本程式碼研究室中,您將瞭解如何整合 LiveDataViewModel 中的資料。

LiveData 類別也屬於 Android 架構元件的一部分,為可觀察的資料容器類別。

必要條件

  • 如何從 GitHub 下載原始碼,並在 Android Studio 中開啟。
  • 如何使用活動和片段在 Kotlin 中建構並執行基本 Android 應用程式。
  • 活動和片段生命週期的運作方式。
  • 如何使用 ViewModel,透過裝置設定變更保留 UI 資料。
  • 如何編寫 lambda 運算式。

課程內容

  • 如何在應用程式中使用 LiveDataMutableLiveData
  • 如何使用 LiveData 封裝儲存在 ViewModel 中的資料。
  • 如何新增觀察器方法以觀察 LiveData. 中的變化。
  • 如何在版面配置檔案中編寫繫結運算式。

建構項目

  • 針對 Unscramble 應用程式中的應用程式資料 (字詞、字詞計數和分數) 使用 LiveData
  • 新增觀察器方法,在資料變更時接收通知,並自動更新打散字詞的文字檢視區塊。
  • 在版面配置檔案中編寫繫結運算式,在基礎 LiveData 變更時觸發。分數、字詞計數和打散字詞文字檢視區塊會自動更新。

軟硬體需求

  • 已安裝 Android Studio 的電腦。
  • 先前程式碼研究室的解決方案程式碼 (含 ViewModel 的 Unscramble 應用程式)。

下載本程式碼研究室的範例程式碼

本程式碼研究室會使用您在先前程式碼研究室 (將資料儲存於 ViewModel 中) 中建構的 Unscramble 應用程式做為範例程式碼。

2. 範例應用程式總覽

本程式碼研究室會使用您在先前程式碼研究室中熟悉的 Unscramble 解決方案程式碼。應用程式會顯示打散的字詞,讓玩家進行重組。玩家可以嘗試任意次數來猜測正確字詞。目前字詞、玩家分數和字詞計數等應用程式資料會儲存在 ViewModel 中。不過,該應用程式的 UI 不會反映新的分數和字詞計數值。在本程式碼研究室中,您將使用 LiveData 實作缺少的功能。

a20e6e45e0d5dc6f.png

3. 什麼是 Livedata

LiveData 是可觀察的資料容器類別,可感知生命週期。

LiveData 特性如下:

  • LiveData 可保存資料;LiveData 包裝函式可與任何類型的資料搭配使用。
  • LiveData 可觀察,這表示當 LiveData 物件保存的資料變更時,觀察器會接收通知。
  • LiveData 可感知生命週期。將觀察器附加至 LiveData 時,觀察器會與 LifecycleOwner 建立關聯 (通常是活動或片段)。LiveData 只會更新處於有效生命週期狀態 (例如 STARTEDRESUMED) 的觀察器。如要進一步瞭解 LiveData 與觀察,請參閱這篇文章

範例程式碼中的 UI 更新

在範例程式碼中,每當您想在 UI 中顯示新的打散字詞時,系統都會明確呼叫 updateNextWordOnScreen() 方法。您可在遊戲初始化期間,以及玩家按下「提交」或「略過」按鈕時呼叫此方法。將從 onViewCreated()restartGame()onSkipWord()onSubmitWord() 方法呼叫此方法。使用 Livedata 時,您不必從多個位置呼叫此方法來更新 UI。您只會在觀察器中執行一次。

4. 將 LiveData 新增至 currentScrambledWord

在這項工作中,您會學習如何將 GameViewModel 中的目前字詞轉換為 LiveData,以使用 LiveData, 包裝任何資料。在後續工作中,您會將觀察器新增至此類 LiveData 物件,並瞭解如何觀察 LiveData

MutableLiveData

MutableLiveDataLiveData 的可變動版本,也就是可以變更儲存在其中的資料值。

  1. GameViewModel 中,將變數 _currentScrambledWord 的類型變更為 MutableLiveData<String>LiveDataMutableLiveData 屬於一般類別,因此您需要指定其保存的資料類型。
  2. _currentScrambledWord 的變數類型變更為 val,因為 LiveData/MutableLiveData 物件的值會保持不變,而且只有儲存在物件中的資料會變更。
private val _currentScrambledWord = MutableLiveData<String>()
  1. 將支援欄位 currentScrambledWord 類型變更為 LiveData<String>,因為此欄位不可變動。Android Studio 會顯示某些錯誤,您將在後續步驟中進行修正。
val currentScrambledWord: LiveData<String>
   get() = _currentScrambledWord
  1. 如要存取 LiveData 物件中的資料,請使用 value 屬性。在 getNextWord() 方法的 GameViewModel 中,於 else 區塊內,將 _currentScrambledWord 參照變更為_currentScrambledWord.value
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

5. 將觀察器附加至 LiveData 物件

在這項工作中,您將會在應用程式元件 GameFragment 中設定觀察器。您新增的觀察器會觀察應用程式資料 currentScrambledWord 的變更。LiveData 具備生命週期感知功能,代表其只會更新處於有效生命週期狀態的觀察器。因此,GameFragment 中的觀察器只會在 GameFragment 處於 STARTEDRESUMED 狀態時收到通知。

  1. GameFragment 中,刪除 updateNextWordOnScreen() 方法及其所有呼叫。由於您會將 LiveData 附加至觀察器,因此不需要使用此方法。
  2. onSubmitWord() 中,按照下列步驟修改空的 if-else 區塊。完整方法應如下所示。
private fun onSubmitWord() {
    val playerWord = binding.textInputEditText.text.toString()

    if (viewModel.isUserWordCorrect(playerWord)) {
        setErrorTextField(false)
        if (!viewModel.nextWord()) {
            showFinalScoreDialog()
        }
    } else {
        setErrorTextField(true)
    }
}
  1. currentScrambledWord LiveData 附加觀察器。在 onViewCreated() 回呼結尾的 GameFragment 中,於 currentScrambledWord 上呼叫 observe() 方法。
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()

Android Studio 會顯示缺少參數的相關錯誤。您將在下一個步驟中修正錯誤。

  1. viewLifecycleOwner 做為第一個參數傳遞至 observe() 方法。viewLifecycleOwner 代表片段的檢視畫面生命週期。此參數可協助 LiveData 留意 GameFragment 生命週期,且只有在 GameFragment 處於有效狀態 (STARTEDRESUMED) 時,才通知觀察器。
  2. 使用 newWord 函式參數將 lambda 新增為第二個參數。newWord 將包含新的打散字詞值。
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
   })

lambda 運算式是未宣告的匿名函式,但會立即以運算式的形式傳遞。lambda 運算式一律加上大括號 { }。

  1. 在 lambda 運算式的函式主體中,將 newWord 指派至打散字詞文字檢視區塊。
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. 編譯並執行應用程式。您的遊戲應用程式應可照常運作,但現在會在 LiveData 觀察器中 (而非在 updateNextWordOnScreen() 方法中) 自動更新打散字詞文字檢視區塊。

6. 將觀察器附加至分數和字詞計數

如同前一個工作,在這項工作中,您會將 LiveData 新增至應用程式的其他資料、分數和字詞計數中,使 UI 在遊戲期間能夠更新分數和字詞計數的正確值。

步驟 1:使用 LiveData 包裝分數和字詞計數

  1. GameViewModel 中,將 _score_currentWordCount 類別變數的類型變更為 val
  2. 將變數 _score_currentWordCount 的資料類型變更為 MutableLiveData,並將其初始化為 0
  3. 將支援欄位類型變更為 LiveData<Int>.
private val _score = MutableLiveData(0)
val score: LiveData<Int>
   get() = _score

private val _currentWordCount = MutableLiveData(0)
val currentWordCount: LiveData<Int>
   get() = _currentWordCount
  1. reinitializeData() 方法開頭的 GameViewModel 中,將 _score_currentWordCount 的參照變更為 _score.value_currentWordCount.value
fun reinitializeData() {
   _score.value = 0
   _currentWordCount.value = 0
   wordsList.clear()
   getNextWord()
}
  1. nextWord() 方法內的 GameViewModel 中,將 _currentWordCount 的參照變更為 _currentWordCount.value!!
fun nextWord(): Boolean {
    return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
           getNextWord()
           true
       } else false
   }
  1. GameViewModelincreaseScore()getNextWord() 方法中,分別將 _score_currentWordCount 的參照變更為 _score.value_currentWordCount.value。Android Studio 會顯示錯誤,因為 _score 已不再是整數,而是 LiveData,您可以在後續步驟中修正此錯誤。
  2. 使用 plus() Kotlin 函式增加 _score 值,如此即可使用空值安全性執行加法。
private fun increaseScore() {
    _score.value = (_score.value)?.plus(SCORE_INCREASE)
}
  1. 同樣地,您也可以使用 inc() Kotlin 函式,使用空值安全性將值增加一。
private fun getNextWord() {
   ...
    } else {
        _currentScrambledWord.value = String(tempWord)
        _currentWordCount.value = (_currentWordCount.value)?.inc()
        wordsList.add(currentWord)
       }
   }
  1. GameFragment 中,使用 value 屬性存取 score 的值。在 showFinalScoreDialog() 方法中,將 viewModel.score 變更為 viewModel.score.value
private fun showFinalScoreDialog() {
   MaterialAlertDialogBuilder(requireContext())
       .setTitle(getString(R.string.congratulations))
       .setMessage(getString(R.string.you_scored, viewModel.score.value))
       ...
       .show()
}

步驟 2:將觀察器附加至分數和字詞計數

應用程式中的分數和字詞計數不會更新。在這項工作中,您將使用 LiveData 觀察器進行更新。

  1. onViewCreated() 方法的 GameFragment 中,刪除更新分數和字詞計數文字檢視區塊的程式碼。

移除:

binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
  1. onViewCreated() 方法結尾的 GameFragment 中,為 score 附加觀察器。將 viewLifecycleOwner 做為第一個參數傳遞至觀察器,並使用 lambda 運算式做為第二個參數。在 lambda 運算式中,將新的分數做為參數傳遞,並在函式主體中,將新分數設為文字檢視區塊。
viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })
  1. onViewCreated() 方法結尾,為 currentWordCount LiveData 附加觀察器。將 viewLifecycleOwner 做為第一個參數傳遞至觀察器,並使用 lambda 運算式做為第二個參數。在 lambda 運算式中,將新字詞計數做為參數傳遞,並在函式主體中,將新字詞計數與 MAX_NO_OF_WORDS 設定為文字檢視區塊。
viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })

在生命週期擁有者的生命週期期間 (即 GameFragment),當 ViewModel 內的分數和字詞計數值發生變化時,就會觸發新的觀察器。

  1. 執行應用程式即可見證其奧妙之處。使用一些字詞進行遊戲。畫面上的分數和字詞計數也會正確更新。建議您不要根據程式碼的部分條件更新這些文字檢視區塊。scorecurrentWordCountLiveData,且基礎值變更時,系統會自動呼叫對應的觀察器。

80e118245bdde6df.png

7. 將 LiveData 搭配資料繫結使用

在先前的工作中,您的應用程式會監聽程式碼中的資料變更。同樣地,應用程式也可以監聽版面配置中的資料變更。透過資料繫結,當可觀察的 LiveData 值變更時,系統也會通知其繫結版面配置中的 UI 元素,且可從版面配置中更新 UI。

概念:資料繫結

在先前的程式碼研究室中,您已瞭解單向的「檢視繫結」。您可以將檢視畫面繫結至程式碼,但無法將程式碼繫結至檢視畫面。

檢視繫結的重新整理程式:

檢視繫結功能可讓您更輕鬆地存取程式碼中的檢視畫面。其會為各 XML 版面配置檔案產生繫結類別。繫結類別的執行個體包含所有檢視畫面 (在對應的版面配置中有 ID 的檢視畫面) 的直接參照內容。舉例來說,Unscramble 應用程式目前使用檢視繫結,因此使用產生的繫結類別可在程式碼中參照檢視畫面。

範例:

binding.textViewUnscrambledWord.text = newWord
binding.score.text = getString(R.string.score, newScore)
binding.wordCount.text =
                  getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)

如果使用檢視繫結,就無法參照檢視畫面中的應用程式資料 (版面配置檔案)。您可以使用資料繫結完成這項操作。

資料繫結

資料繫結程式庫也屬於 Android Jetpack 程式庫的一部分。資料繫結使用宣告式格式將版面配置中的 UI 元件繫結至應用程式中的資料來源,稍後將在程式碼研究室中說明。

簡單來說,資料繫結將資料 (從程式碼) 繫結至檢視畫面 + 檢視繫結 (將檢視畫面繫結至程式碼)

在 UI 控制器中使用檢視繫結的範例

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

在版面配置檔案中使用資料繫結的範例

android:text="@{gameViewModel.currentScrambledWord}"

以上範例說明如何使用資料繫結程式庫,直接將應用程式資料指派至版面配置檔案中的檢視畫面/小工具。請注意,指派運算式中使用 @{} 語法。

使用資料繫結的主要優點在於,您可以移除活動中的許多 UI 架構呼叫,使其更加簡單且易於維護。這還可改善應用程式效能,避免發生記憶體流失及空值指標例外狀況。

步驟 1:變更資料繫結的檢視繫結

  1. build.gradle(Module) 檔案中,啟用 buildFeatures 區段下的 dataBinding 屬性。

取代

buildFeatures {
   viewBinding = true
}

buildFeatures {
   dataBinding = true
}

當 Android Studio 顯示提示時,執行 Gradle 同步處理。

  1. 如要在任何 Kotlin 專案中使用資料繫結,請套用 kotlin-kapt 外掛程式。此步驟已在 build.gradle(Module) 檔案中完成。
plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-kapt'
}

上述步驟會自動為應用程式中的每個版面配置 XML 檔案產生繫結類別,如果版面配置檔案名稱為 activity_main.xml,則自動產生的類別將會稱為 ActivityMainBinding

步驟 2:將版面配置檔案轉換為資料繫結版面配置

資料繫結版面配置檔案略有不同,且開頭為 <layout> 的根標記,接著是選用的 <data> 元素和 view 根元素。此檢視畫面元素即為非繫結版面配置檔案中的根。

  1. 開啟 game_fragment.xml,選取「程式碼」分頁標籤。
  2. 如要將版面配置轉換成資料繫結版面配置,請將根元素納入 <layout> 標記中。您也必須將命名空間定義 (開頭為 xmlns: 的屬性) 移至新的根元素。在根元素上方的 <layout> 標記中加入 <data></data> 標記。Android Studio 提供可自動執行此操作的便利方法:在根元素 (ScrollView) 上按一下滑鼠右鍵,選取「Show Context Actions」(顯示結構定義動作) >「Convert to data binding layout」(轉換為資料繫結版面配置)

f356fc45e8fe91b1.png

  1. 版面配置應如下所示:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools">

   <data>

   </data>

   <ScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <androidx.constraintlayout.widget.ConstraintLayout
         ...
       </androidx.constraintlayout.widget.ConstraintLayout>
   </ScrollView>
</layout>
  1. onCreateView() 方法開頭的在 GameFragment 中,變更 binding 變數的執行個體化,以使用資料繫結。

取代

binding = GameFragmentBinding.inflate(inflater, container, false)

binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
  1. 編譯程式碼;您應能夠順利編譯。您的應用程式現在會使用資料繫結,版面配置中的檢視畫面也可存取應用程式資料。

8. 新增資料繫結變數

在這項工作中,您必須在版面配置檔案中加入屬性,以便存取 viewModel 中的應用程式資料。您將初始化程式碼中的版面配置變數。

  1. game_fragment.xml<data> 標記中,新增名為 <variable> 的子標記,宣告名為 gameViewModel 且類型為 GameViewModel 的屬性。您會使用此方法將 ViewModel 中的資料繫結至版面配置。
<data>
   <variable
       name="gameViewModel"
       type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>

請注意,gameViewModel 的類型包含套件名稱。請確認此套件名稱與應用程式中的套件名稱相符。

  1. gameViewModel 宣告下方,在 Integer 類型的 <data> 標記中加入另一個變數,並將其命名為 maxNoOfWords。您將使用此方法繫結至 ViewModel 中的變數,以儲存每場遊戲的字詞數。
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>
  1. onViewCreated() 方法的開頭的 GameFragment 中,初始化版面配置變數 gameViewModelmaxNoOfWords
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.gameViewModel = viewModel

   binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
  1. LiveData 可觀察生命週期,因此您必須將生命週期擁有者傳遞給版面配置。在 onViewCreated() 方法內的 GameFragment 中,在繫結變數的初始化下方新增下列程式碼。
   // Specify the fragment view as the lifecycle owner of the binding.
   // This is used so that the binding can observe LiveData updates
   binding.lifecycleOwner = viewLifecycleOwner

提醒您,您在實作 LiveData 觀察器時,也實作類似功能。您已將 viewLifecycleOwner 做為其中一個參數傳遞給 LiveData 觀察器。

9. 使用繫結運算式

繫結運算式會寫入屬性 (例如 android:text) 版面配置內,並參照版面配置屬性。版面配置屬性會透過 <variable> 標記在資料繫結版面配置檔案的頂部進行宣告。當任何相依變數有所變更時,「DB 程式庫」將執行繫結運算式 (進而更新檢視畫面)。使用資料繫結程式庫時,此變更偵測是無須付費的最佳化功能。

繫結運算式的語法

繫結運算式以 @ 符號開頭,並加上大括號 {}。在以下範例中,TextView 文字設為 user 變數的 firstName 屬性:

範例:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

步驟 1:將繫結運算式新增至目前字詞

在此步驟中,您可以將目前的字詞文字檢視區塊繫結至 ViewModel 中的 LiveData 物件。

  1. game_fragment.xml 中,請將 text 屬性新增至 textView_unscrambled_word 文字檢視區塊。使用新的版面配置變數 gameViewModel,並將 @{gameViewModel.currentScrambledWord} 指派給 text 屬性。
<TextView
   android:id="@+id/textView_unscrambled_word"
   ...
   android:text="@{gameViewModel.currentScrambledWord}"
   .../>
  1. GameFragment 中,移除 currentScrambledWordLiveData 觀察器程式碼:片段中不再需要使用觀察器程式碼。版面配置會直接收到 LiveData 的變更更新。

移除

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. 執行您的應用程式,應用程式應可照常運作。不過,打散字詞文字檢視區塊目前使用繫結運算式更新 UI,而非 LiveData 觀察器。

步驟 2:將繫結運算式新增至分數和字詞計數

資料繫結運算式的資源

資料繫結運算式可透過下列語法參照應用程式資源。

範例:

android:padding="@{@dimen/largePadding}"

在上述範例中,系統會為 padding 屬性指派 dimen.xml 資源檔案的 largePadding 值。

您也可以傳遞版面配置屬性做為資源參數。

範例:

android:text="@{@string/example_resource(user.lastName)}"

strings.xml

<string name="example_resource">Last Name: %s</string>

在上述範例中,example_resource 是具有 %s 預留位置的字串資源。您會將 user.lastName 做為資源參數傳入繫結運算式,其中 user 是版面配置變數。

在此步驟中,您會將繫結運算式新增至分數和字詞計數文字檢視區塊,並傳入資源參數。這個步驟與您為上述 textView_unscrambled_word 所做操作類似。

  1. game_fragment.xml 中,使用以下繫結運算式更新 word_count 文字檢視區塊的 text 屬性。使用 word_count 字串資源,並將 gameViewModel.currentWordCountmaxNoOfWords 做為資源參數傳入。
<TextView
   android:id="@+id/word_count"
   ...
   android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
   .../>
  1. 使用以下繫結運算式更新 score 文字檢視區塊的 text 屬性。使用 score 字串資源,並傳入 gameViewModel.score 做為資源參數。
<TextView
   android:id="@+id/score"
   ...
   android:text="@{@string/score(gameViewModel.score)}"
   ... />
  1. GameFragment 中移除 LiveData 觀察器。您已無需使用這些觀察器,繫結運算式會在對應 LiveData 變更時更新 UI。

移除:

viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })

viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })
  1. 執行應用程式,使用一些字詞進行遊戲。現在,您的程式碼會使用 LiveData 和繫結運算式更新 UI。

7880e60dc0a6f95c.png 9ef2fdf21ffa5c99.png

恭喜!你已瞭解如何將 LiveData 觀察器搭配 LiveData 使用,以及將 LiveData 搭配繫結運算式使用。

10. 在啟用 TalkBack 的情況下測試 Unscramble 應用程式

在完成本課程後,您會想要建構可供眾多使用者存取的應用程式。部分使用者可能會使用 Talkback 來存取及瀏覽您的應用程式。TalkBack 是 Android 裝置隨附的 Google 螢幕閱讀器。TalkBack 的互動朗讀功能讓您不看螢幕也能輕鬆使用裝置。

啟用 TalkBack 後,請確保玩家可以進行遊戲。

  1. 按照這些操作說明在裝置上啟用 TalkBack。
  2. 返回 Unscramble 應用程式。
  3. 請參閱操作說明,使用 Talkback 探索應用程式。向右滑動即可逐一瀏覽螢幕元素,向左滑動後即可往反方向瀏覽。在任意位置輕觸兩下即可選取。確認您可以透過滑動手勢找到應用程式中的所有元素。
  4. 確認 Talkback 使用者可以瀏覽畫面上的每個項目。
  5. 觀察 Talkback 是否嘗試將打散的字詞讀做一個字詞。這可能會讓玩家感到困惑,因為這不是真正的字詞。
  6. 使 Talkback 大聲讀出打散字詞中的個別字元,可獲得更優異的使用者體驗。在 GameViewModel 中,將打散字詞的 String 轉換為 Spannable 字串。Spannable 字串是附加額外資訊的字串。在這個範例中,我們想要將字串與 TYPE_VERBATIM,TtsSpan 建立關聯,使文字轉語音引擎逐字元大聲讀出打散的字詞。
  7. GameViewModel, 中,使用以下程式碼修改 currentScrambledWord 變數的宣告方式:
val currentScrambledWord: LiveData<Spannable> = Transformations.map(_currentScrambledWord) {
    if (it == null) {
        SpannableString("")
    } else {
        val scrambledWord = it.toString()
        val spannable: Spannable = SpannableString(scrambledWord)
        spannable.setSpan(
            TtsSpan.VerbatimBuilder(scrambledWord).build(),
            0,
            scrambledWord.length,
            Spannable.SPAN_INCLUSIVE_INCLUSIVE
        )
        spannable
    }
}

這個變數現在是 LiveData<Spannable>,而不是 LiveData<String>。您不必費心瞭解所有詳細運作方式,但實作方法會使用 LiveData 轉換,將目前打散的字詞 String 轉換為 Spannable 字串,並由無障礙服務妥善處理。在接下來的程式碼研究室中,您將進一步瞭解 LiveData 轉換,其可讓您根據對應的 LiveData 值傳回不同的 LiveData 執行個體。

  1. 執行 Unscramble 應用程式,透過 Talkback 探索應用程式。現在,TalkBack 應可讀出打散字詞中的個別字元。

如要進一步瞭解如何讓應用程式更容易使用,請參閱這些原則

11. 刪除未使用的程式碼

刪除無效、未使用、不需要的解決方案程式碼是良好做法。這樣不但可以輕鬆維護程式碼,也能讓新進團隊成員更容易瞭解程式碼。

  1. GameFragment 中,刪除 getNextScrambledWord()onDetach() 方法。
  2. GameViewModel 中刪除 onCleared() 方法。
  3. 刪除來源檔案頂端的任何未使用匯入項目。這些項目將會顯示為灰色。

您不再需要使用記錄陳述式,如有需要,也可將其從程式碼中刪除。

  1. [選擇性] 刪除在先前程式碼研究室中所新增來源檔案 (GameFragment.ktGameViewModel.kt) 中的 Log 陳述式,藉此瞭解 ViewModel 生命週期。

12. 解決方案程式碼

本程式碼研究室的解決方案程式碼位於下方所示專案中。

如要取得本程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作。

取得程式碼

  1. 按一下所提供的網址。系統會在瀏覽器中開啟專案的 GitHub 頁面。
  2. 在專案的 GitHub 頁面中,按一下「程式碼」按鈕,開啟對話方塊。

5b0a76c50478a73f.png

  1. 在對話方塊中按一下「Download ZIP」(下載 ZIP) 按鈕,將專案儲存至電腦。等待下載作業完成。
  2. 在電腦上尋找檔案 (可能位於「下載」資料夾中)。
  3. 按兩下 ZIP 檔案,將檔案解壓縮。這項操作會建立含有專案檔案的新資料夾。

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」(歡迎使用 Android Studio) 視窗中,按一下「Open an existing Android Studio project」(開啟現有的 Android Studio 專案)。

36cc44fcf0f89a1d.png

注意:如果 Android Studio 已開啟,則請選取「檔案」>「新增」>「匯入專案」選單選項。

21f3eec988dcfbe9.png

  1. 在「匯入專案」對話方塊中,找出解壓縮的專案資料夾位置 (可能位於「下載」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「執行」按鈕 11c34fc5e516fb1c.png 即可建構並執行應用程式,請確認應用程式的建構符合預期。
  5. 在「Project」(專案) 工具視窗中瀏覽專案檔案,查看應用程式的設定方式。

13. 摘要

  • LiveData 可保存資料;LiveData 包裝函式可與任何資料搭配使用
  • LiveData 可觀察,這表示當 LiveData 物件保存的資料變更時,觀察器會接收通知。
  • LiveData 可感知生命週期。將觀察器附加至 LiveData 時,觀察器會與 LifecycleOwner 建立關聯 (通常是活動或片段)。LiveData 只會更新處於有效生命週期狀態 (例如 STARTEDRESUMED) 的觀察器。如要進一步瞭解 LiveData 與觀察,請參閱這篇文章
  • 應用程式可以透過資料繫結和繫結運算式監聽版面配置中的 LiveData 變更。
  • 繫結運算式會寫入屬性 (例如 android:text) 版面配置內,並參照版面配置屬性。

14. 瞭解詳情

網誌文章