将 LiveData 与 ViewModel 配合使用

1. 准备工作

在之前的 Codelab 中,您已学过如何使用 ViewModel 来存储应用数据。ViewModel 可让应用的数据在发生配置更改后继续留存。在此 Codelab 中,您将学习如何将 LiveDataViewModel 中的数据结合使用。

LiveData 类也是 Android 架构组件的一部分,是一种可观察的数据存储器类。

前提条件

  • 如何从 GitHub 下载源代码并在 Android Studio 中将其打开?
  • 如何使用 activity 和 fragment 在 Kotlin 中创建和运行基本 Android 应用?
  • activity 和 fragment 生命周期如何发挥作用?
  • 如何使用 ViewModel 在设备配置更改时保留界面数据?
  • 如何编写 lambda 表达式?

学习内容

  • 如何在应用中使用 LiveDataMutableLiveData
  • 如何使用 LiveData 来封装存储在 ViewModel 中的数据?
  • 如何添加观察器方法来观察 LiveData. 中的更改?
  • 如何在布局文件中编写绑定表达式?

构建内容

  • Unscramble 应用中,将 LiveData 用于应用的数据(单词、单词数和得分)。
  • 添加观察器方法,以便在数据发生更改时收到通知,并自动更新乱序词文本视图。
  • 在布局文件中编写绑定表达式,当底层 LiveData 发生更改时会触发该表达式。届时,系统会自动更新得分、单词数和乱序词文本视图。

所需条件

  • 一台安装了 Android Studio 的计算机。
  • 上一个 Codelab 中的解决方案代码(使用 ViewModel 的 Unscramble 应用)。

下载此 Codelab 的起始代码

此 Codelab 使用您在上一个 Codelab(将数据存储在 ViewModel 中)中构建的 Unscramble 应用作为起始代码。

2. 起始应用概览

此 Codelab 使用的是上一个 Codelab 中的 Unscramble 解决方案代码,您已熟悉这些代码。该应用显示一个乱序词供玩家猜词。玩家猜词的次数不受限制,可以一直尝试,直至猜出正确的单词为止。当前单词、玩家得分和单词数等应用数据保存在 ViewModel 中。不过,该应用的界面不会反映新的得分和单词数。在此 Codelab 中,您将使用 LiveData 来实现这些在该应用中缺失的功能。

a20e6e45e0d5dc6f.png

3. 什么是 LiveData

LiveData 是一种具有生命周期感知能力、可观察的数据存储器类。

LiveData 的部分特性如下:

  • LiveData 可存储数据;LiveData 是一种可与任何类型数据搭配使用的封装容器。
  • LiveData 是可观察的,这意味着当 LiveData 对象存储的数据发生更改时,观察器会收到通知。
  • LiveData 具有生命周期感知能力。当您将观察器附加到 LiveData 后,观察器就会与 LifecycleOwner(通常是 activity 或 fragment)相关联。LiveData 仅更新处于活跃生命周期状态(例如 STARTEDRESUMED)的观察器。您可以在此处详细了解 LiveData 和观察。

起始代码中的界面更新

在起始代码中,每次需要在界面中显示新的乱序词时,都会显式调用 updateNextWordOnScreen() 方法。在游戏初始化期间以及玩家按下 SubmitSkip 按钮时,也会调用此方法。此方法还会从 onViewCreated()restartGame()onSkipWord()onSubmitWord() 方法中调用。不过,有了 Livedata 后,您就不必从多个位置调用此方法来更新界面了。您只需在观察器中调用一次即可。

4. 向当前乱序词添加 LiveData

在此任务中,您要将 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 属性。在 GameViewModel 中,在 getNextWord() 方法内的 else 块中,将 _currentScrambledWord 的引用更改为 _currentScrambledWord.value
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

5. 将观察器附加到 LiveData 对象

在此任务中,您将在应用组件 GameFragment 中设置观察器。您添加的观察器用于观察对应用数据 currentScrambledWord 做出的更改。LiveData 具有生命周期感知能力,也就是说它仅更新处于活跃生命周期状态的观察器。因此,仅当 GameFragment 处于 STARTEDRESUMED 状态时,GameFragment 中的观察器才会收到通知。

  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 附加一个观察器。在 GameFragment 中的 onViewCreated() 回调末尾,对 currentScrambledWord 调用 observe() 方法。
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()

Android Studio 将显示一个关于缺少参数的错误。您将在下一步中修复该错误。

  1. viewLifecycleOwner 作为第一个参数传递给 observe() 方法。viewLifecycleOwner 表示 fragment 的视图生命周期。此参数可以帮助 LiveData 了解 GameFragment 的生命周期,并仅在 GameFragment 处于活跃状态(STARTEDRESUMED)时通知观察器。
  2. 添加 lambda 作为第二个参数,并以 newWord 作为函数参数。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,这样界面便会在游戏期间更新正确的得分和单词数。

第 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. GameViewModel 中的 reinitializeData() 方法的开头,将 _score_currentWordCount 的引用分别更改为 _score.value_currentWordCount.value
fun reinitializeData() {
   _score.value = 0
   _currentWordCount.value = 0
   wordsList.clear()
   getNextWord()
}
  1. GameViewModel 中的 nextWord() 方法内,将 _currentWordCount 的引用更改为 _currentWordCount.value!!
fun nextWord(): Boolean {
    return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
           getNextWord()
           true
       } else false
   }
  1. GameViewModel 中的 increaseScore()getNextWord() 方法内,将 _score_currentWordCount 的引用分别更改为 _score.value_currentWordCount.valueAndroid Studio 会显示错误,因为 _score 不再是整数而是 LiveData,您将在后续步骤中修复该错误。
  2. 使用 plus() Kotlin 函数来增加 _score 的值,该函数会以确保 null 安全的形式执行加法。
private fun increaseScore() {
    _score.value = (_score.value)?.plus(SCORE_INCREASE)
}
  1. 同样,使用 inc() Kotlin 函数,以确保 Null 安全的形式将值加一。
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. GameFragment 中的 onViewCreated() 方法内,删除用于更新得分和单词数文本视图的代码。

移除:

binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
  1. GameFragment 中的 onViewCreated() 方法的末尾,为 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 值更改时,与其绑定的布局中的界面元素也会收到通知,并可在布局中更新界面。

概念:数据绑定

在之前的 Codelab 中,您已了解过视图绑定,它是一种单向绑定。您可以将视图绑定到代码,但反之则不然。

视图绑定复习:

视图绑定是一项能让您更轻松地在代码中访问视图的功能。它会为每个 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 库的一部分。数据绑定使用声明性格式将布局中的界面组件绑定到应用中的数据源,本 Codelab 稍后将对此进行说明。

简而言之,数据绑定就是将数据(从代码)绑定到视图 + 视图绑定(将视图绑定到代码):

在界面控制器中使用视图绑定的示例

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

在布局文件中使用数据绑定的示例

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

上面的示例显示了如何使用数据绑定库直接在布局文件中将应用数据赋值给视图/微件。请注意赋值表达式中 @{} 语法的使用。

使用数据绑定的主要优势在于,您可以移除 activity 中的许多界面框架调用,使其维护起来更简单、方便。还可以提高应用性能,并且有助于防止内存泄漏以及避免发生 null 指针异常。

第 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 根元素。此 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 声明下的 <data> 标记内,添加类型为 Integer 的另一个变量,并将其命名为 maxNoOfWords。您将使用此变量绑定到 ViewModel 中的变量,以存储每局游戏的单词数。
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>
  1. GameFragment 中的 onViewCreated() 方法开头,初始化布局变量 gameViewModelmaxNoOfWords
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.gameViewModel = viewModel

   binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
  1. LiveData 是生命周期感知型可观察对象,因此您必须将生命周期所有者传递给布局。在 GameFragment 中的 onViewCreated() 方法内,在绑定变量的初始化下方添加以下代码。
   // 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> 标记进行声明。当任何因变量发生更改时,“数据绑定库”将运行绑定表达式(并因此更新视图)。这项更改检测功能是一项了不起的优化,如果您使用数据绑定库,就可以免费获得这项功能。

绑定表达式的语法

绑定表达式以 @ 符号开头并用花括号 {} 括起来。在以下示例中,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 观察器代码:fragment 中不再需要此观察器代码。布局会直接收到对 LiveData 所做更改的更新。

移除:

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. 运行应用,此时应用应该像以前一样工作。不过,现在乱序词文本视图使用绑定表达式来更新界面,而非使用 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 更改时更新界面。

移除:

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 和绑定表达式来更新界面。

7880e60dc0a6f95c.png 9ef2fdf21ffa5c99.png

恭喜!您已经学习了如何将 LiveDataLiveData 观察器搭配使用以及如何将 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 字符串。在下一个 Codelab 中,您将详细了解 LiveData 转换,这种转换能让您根据相应 LiveData 的值返回不同的 LiveData 实例。

  1. 运行 Unscramble 应用,使用 Talkback 探索该应用。TalkBack 现在应该会读出乱序词的一个个字符。

如需详细了解如何让您的应用使用起来更没有障碍,请参阅这些准则

11. 删除未使用的代码

最好删除解决方案代码中无用、未使用或不需要的代码。这样做既方便维护代码,又能让新的团队成员更轻松地进一步理解代码。

  1. GameFragment 中,删除 getNextScrambledWord()onDetach() 方法。
  2. GameViewModel 中,删除 onCleared() 方法。
  3. 删除源文件顶部的任何未使用的导入。这些导入显示为灰色。

您不再需要日志语句,如果愿意,您可将其从代码中删除。

  1. [可选] 删除您在上一个 Codelab 中为了了解 ViewModel 生命周期而在源文件(GameFragment.ktGameViewModel.kt)中添加的 Log 语句。

12. 解决方案代码

此 Codelab 的解决方案代码可在下面显示的项目中找到。

  1. 进入为此项目提供的 GitHub 代码库页面。
  2. 验证分支名称是否与此 Codelab 中指定的分支名称一致。例如,在以下屏幕截图中,分支名称为 main

1e4c0d2c081a8fd2.png

  1. 在项目的 GitHub 页面上,点击 Code 按钮,以打开一个弹出式窗口。

1debcf330fd04c7b.png

  1. 在弹出式窗口中,点击 Download ZIP 按钮,将项目保存到计算机上。等待下载完成。
  2. 在计算机上找到该文件(很可能在 Downloads 文件夹中)。
  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(通常是 activity 或 fragment)相关联。LiveData 仅更新处于活跃生命周期状态(例如 STARTEDRESUMED)的观察器。您可以在此处详细了解 LiveData 和观察。
  • 应用可以使用数据绑定和绑定表达式,从布局监听 LiveData 更改。
  • 绑定表达式在布局中引用布局属性的特性属性(例如 android:text)中编写。

14. 了解更多内容

博文