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

8d48f58c2bdccb52.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. GameFragment 中,在 onCreateView() 方法的一開始,變更 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_VERBATIMTtsSpan 建立關聯,使文字轉語音引擎逐字元大聲讀出打散的字詞。
  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. 解決方案程式碼

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

  1. 前往專案所在的 GitHub 存放區頁面。
  2. 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下列螢幕截圖中,分支版本名稱為「main」

1e4c0d2c081a8fd2.png

  1. 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。

1debcf330fd04c7b.png

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

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Open」

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。

8d1fda7396afe8e5.png

  1. 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「Run」按鈕 8de56cba7583251f.png,即可建構並執行應用程式。請確認應用程式的建構作業符合預期。

13. 摘要

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

14. 瞭解詳情

網誌文章