此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
简介
在本课程的之前几个 Codelab 中,您改进了 GuessTheWord 应用的代码。该应用现在使用 ViewModel 对象,因此应用数据在设备配置更改(例如屏幕旋转和键盘可用性更改)后仍会保留。您还添加了可观测的 LiveData,以便在观测到的数据发生更改时自动通知视图。
在此 Codelab 中,您将继续使用 GuessTheWord 应用。您会将视图绑定到应用中的 ViewModel 类,以便布局中的视图直接与 ViewModel 对象通信。(在您的应用中,到目前为止,视图一直通过应用的 fragment 与 ViewModel 进行间接通信。)将数据绑定与 ViewModel 对象集成后,您不再需要在应用 fragment 中使用点击处理程序,因此将其移除。
您还将更改 GuessTheWord 应用,使其使用 LiveData 作为数据绑定来源,以便在不使用 LiveData 观察器方法的情况下将数据变化通知给界面。
您应当已掌握的内容
- 如何使用 Kotlin 创建基本 Android 应用。
- activity 和 fragment 生命周期如何运作。
- 如何在应用中使用
ViewModel对象。 - 如何在
ViewModel中使用LiveData存储数据。 - 如何添加观察器方法来观察
LiveData数据中的更改。
学习内容
- 如何使用数据绑定库的元素。
- 如何将
ViewModel与数据绑定集成。 - 如何将
LiveData与数据绑定集成。 - 如何使用监听器绑定来替换 fragment 中的点击监听器。
- 如何向数据绑定表达式添加字符串格式设置。
您将执行的操作
- GuessTheWord 布局中的视图使用界面控制器 (fragment) 来传达信息,从而间接与
ViewModel对象通信。在此 Codelab 中,您将应用的视图绑定到ViewModel对象,以便视图与ViewModel对象进行直接通信。 - 您将应用更改为使用
LiveData作为数据绑定来源。进行此更改后,LiveData对象会向界面通知数据中的更改,因此不再需要LiveData观察者方法。
在第 5 课的 Codelab 中,您将从起始代码开始开发 GuessTheWord 应用。GuessTheWord 是一款双人猜字谜游戏,玩家在游戏中可以协作以获得最高得分。
第一位玩家查看应用中的单词,然后依次表演每个单词,确保不让第二位玩家看到单词。第二位玩家尝试猜出该单词。
要玩这个游戏,第一个玩家需要在设备上打开应用,然后会看到一个字词,例如“吉他”,如下面的屏幕截图所示。
第一位玩家表演该字词,但要小心不要实际说出该字词。
- 当第二位玩家正确猜出单词后,第一位玩家会按 Got It 按钮,这会将计数增加 1,并显示下一个单词。
- 如果第二位玩家猜不出单词,第一位玩家可以按跳过按钮,这样一来,计数器会减 1,并跳到下一个单词。
- 如需结束游戏,请按 End Game 按钮。(此功能未包含在本系列第一个 Codelab 的起始代码中。)
在此 Codelab 中,您将通过将数据绑定与 ViewModel 对象中的 LiveData 集成来改进 GuessTheWord 应用。这可自动实现布局中的视图与 ViewModel 对象之间的通信,并让您能够通过使用 LiveData 简化代码。
广告标题画面 |
游戏界面 |
得分界面 |
在此任务中,您将找到并运行此 Codelab 的起始代码。您可以使用在上一个 Codelab 中构建的 GuessTheWord 应用作为起始代码,也可以下载起始应用。
- (可选)如果您不使用上一个 Codelab 中的代码,请下载此 Codelab 的起始代码。解压缩代码,然后在 Android Studio 中打开项目。
- 运行应用并玩游戏。
- 请注意,点按 Got It 按钮后会显示下一个单词,并且得分会增加 1 分;而点按 Skip 按钮后会显示下一个单词,并且得分会减少 1 分。结束游戏按钮用于结束游戏。
- 循环浏览所有单词,并注意应用会自动导航到得分界面。
在之前的 Codelab 中,您使用数据绑定以类型安全的方式访问 GuessTheWord 应用中的视图。但数据绑定的真正强大之处在于,它可以按照名称所示,将数据直接绑定到应用中的视图对象。
当前应用架构
在应用中,视图在 XML 布局中定义,这些视图的数据保存在 ViewModel 对象中。每个视图与其对应的 ViewModel 之间都有一个界面控制器,充当它们之间的中继。

例如:
- 知道了按钮在
game_fragment.xml布局文件中定义为Button视图。 - 当用户点按知道了按钮时,
GameFragmentfragment 中的点击监听器会调用GameViewModel中的相应点击监听器。 - 得分会在
GameViewModel中更新。
Button 视图和 GameViewModel 不会直接通信,它们需要 GameFragment 中的点击监听器。
传递到数据绑定中的 ViewModel
如果布局中的视图可以直接与 ViewModel 对象中的数据通信,而无需依赖界面控制器作为中介,那么情况会更简单。

ViewModel 对象保存了 GuessTheWord 应用中的所有界面数据。通过将 ViewModel 对象传递到数据绑定中,您可以自动执行视图与 ViewModel 对象之间的一些通信。
在此任务中,您将 GameViewModel 和 ScoreViewModel 类与其对应的 XML 布局相关联。您还设置了监听器绑定来处理点击事件。
第 1 步:为 GameViewModel 添加数据绑定
在此步骤中,您将 GameViewModel 与相应的布局文件 game_fragment.xml 相关联。
- 在
game_fragment.xml文件中,添加一个类型为GameViewModel的数据绑定变量。如果您在 Android Studio 中遇到错误,请清理并重新构建项目。
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- 在
GameFragment文件中,将GameViewModel传递到数据绑定中。
为此,请将viewModel分配给您在上一步中声明的binding.gameViewModel变量。将以下代码放在onCreateView()内,在viewModel初始化之后。如果您在 Android Studio 中遇到错误,请清理并重新构建项目。
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModel第 2 步:使用监听器绑定处理事件
监听器绑定是在触发 onClick()、onZoomIn() 或 onZoomOut() 等事件时运行的绑定表达式。监听器绑定编写为 lambda 表达式。
数据绑定会创建一个监听器,并将其设置在视图上。当监听的事件发生时,监听器会对 lambda 表达式进行求值。监听器绑定适用于 Android Gradle 插件 2.0 或更高版本。如需了解详情,请参阅布局和绑定表达式。
在此步骤中,您需要将 GameFragment 中的点击监听器替换为 game_fragment.xml 文件中的监听器绑定。
- 在
game_fragment.xml中,将onClick属性添加到skip_button。定义绑定表达式,并在GameViewModel中调用onSkip()方法。此绑定表达式称为监听器绑定。
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />- 同样,将
correct_button的点击事件绑定到GameViewModel中的onCorrect()方法。
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />- 将
end_game_button的点击事件绑定到GameViewModel中的onGameFinish()方法。
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />- 在
GameFragment中,移除用于设置点击监听器的语句,并移除点击监听器调用的函数。您不再需要这些信息。
要移除的代码:
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}第 3 步:为 ScoreViewModel 添加数据绑定
在此步骤中,您将 ScoreViewModel 与相应的布局文件 score_fragment.xml 相关联。
- 在
score_fragment.xml文件中,添加ScoreViewModel类型的绑定变量。此步骤与上面针对GameViewModel执行的操作类似。
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout- 在
score_fragment.xml中,将onClick属性添加到play_again_button。定义监听器绑定,并在ScoreViewModel中调用onPlayAgain()方法。
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />- 在
ScoreFragment的onCreateView()内,初始化viewModel。然后,初始化binding.scoreViewModel绑定变量。
viewModel = ...
binding.scoreViewModel = viewModel- 在
ScoreFragment中,移除为playAgainButton设置点击监听器的代码。如果 Android Studio 显示错误,请清理并重建项目。
要移除的代码:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }- 运行应用。应用应该会像以前一样运行,但现在按钮视图会直接与
ViewModel对象通信。视图不再通过ScoreFragment中的按钮点击处理程序进行通信。
排查数据绑定错误消息
当应用使用数据绑定时,编译过程会生成用于数据绑定的中间类。应用可能会出现一些错误,在您尝试编译应用之前,Android Studio 不会检测到这些错误,因此您在编写代码时不会看到警告或红色代码。但在编译时,您会收到来自生成的中间类的神秘错误。
如果您收到难以理解的错误消息,请执行以下操作:
- 仔细查看 Android Studio Build 窗格中的消息。如果您看到以
databinding结尾的位置,则表示数据绑定存在错误。 - 在布局 XML 文件中,检查使用数据绑定的
onClick属性是否存在错误。找到 lambda 表达式调用的函数,并确保该函数存在。 - 在 XML 的
<data>部分中,检查数据绑定变量的拼写。
例如,请注意以下属性值中函数名称 onCorrect() 的拼写错误:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
另请注意 XML 文件 <data> 部分中 gameViewModel 的拼写错误:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>在您编译应用之前,Android Studio 不会检测到此类错误,然后编译器会显示如下所示的错误消息:
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
数据绑定非常适合与 ViewModel 对象搭配使用的 LiveData。现在,您已向 ViewModel 对象添加了数据绑定,接下来可以纳入 LiveData 了。
在此任务中,您将更改 GuessTheWord 应用,使其使用 LiveData 作为数据绑定来源,以将数据变化通知给界面,而无需使用 LiveData 观察者方法。
第 1 步:向 game_fragment.xml 文件添加 word LiveData
在此步骤中,您要将当前单词文本视图直接绑定到 ViewModel 中的 LiveData 对象。
- 在
game_fragment.xml中,将android:text属性添加到word_text文本视图。
使用绑定变量 gameViewModel 将其设置为 GameViewModel 中的 LiveData 对象 word。
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />请注意,您无需使用 word.value。您可以改用实际的 LiveData 对象。LiveData 对象显示 word 的当前值。如果 word 的值为 null,则 LiveData 对象会显示一个空字符串。
- 在
GameFragment的onCreateView()中,初始化gameViewModel后,将当前 activity 设置为binding变量的生命周期所有者。这定义了上述LiveData对象的范围,允许该对象自动更新布局中的视图,game_fragment.xml。
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this- 在
GameFragment中,移除LiveDataword的观察器。
要移除的代码:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})- 运行应用并玩游戏。现在,系统会在界面控制器中更新当前字词,而无需使用观察器方法。
第 2 步:将得分 LiveData 添加到 score_fragment.xml 文件
在此步骤中,您将 LiveData score 绑定到得分 fragment 中的得分文本视图。
- 在
score_fragment.xml中,将android:text属性添加到得分文本视图。将scoreViewModel.score分配给text属性。由于score是整数,因此使用String.valueOf()将其转换为字符串。
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />- 在
ScoreFragment中,初始化scoreViewModel后,将当前 activity 设置为binding变量的生命周期所有者。
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this- 在
ScoreFragment中,移除score对象的观察者。
要移除的代码:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- 运行应用并玩游戏。请注意,得分 fragment 中的得分显示正确,且得分 fragment 中没有观察者。
第 3 步:使用数据绑定添加字符串格式设置
在布局中,您可以添加字符串格式设置以及数据绑定。在此任务中,您将格式化当前字词,为其添加引号。您还可以设置得分字符串的格式,使其以 Current Score 为前缀,如下图所示。

- 在
string.xml中,添加以下字符串,您将使用这些字符串来格式化word和score文本视图。%s和%d是当前字词和当前得分的占位符。
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>- 在
game_fragment.xml中,更新word_text文本视图的text属性,以使用quote_format字符串资源。传入gameViewModel.word。这会将当前字词作为实参传递给格式设置字符串。
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />- 设置
score文本视图的格式,使其类似于word_text。在game_fragment.xml中,将text属性添加到score_text文本视图。使用字符串资源score_format,该资源接受一个数值实参,由%d占位符表示。将LiveData对象score作为实参传递给此格式化字符串。
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />- 在
GameFragment类中,移除onCreateView()方法内的score观察器代码。
要移除的代码:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- 清理、重新构建并运行应用,然后玩游戏。请注意,游戏界面中当前单词和得分的格式。

恭喜!您已在应用中将 LiveData 和 ViewModel 与数据绑定集成。这样,布局中的视图就可以直接与 ViewModel 通信,而无需使用 fragment 中的点击处理程序。您还使用 LiveData 对象作为数据绑定来源,自动将数据变化通知给界面,而无需使用 LiveData 观察器方法。
Android Studio 项目:GuessTheWord
- 数据绑定库可与
ViewModel和LiveData等 Android 架构组件无缝协作。 - 应用中的布局可以绑定到架构组件中的数据,这些组件已经可帮助您管理界面控制器的生命周期并通知数据变化。
ViewModel 数据绑定
- 您可以使用数据绑定将
ViewModel与布局相关联。 ViewModel对象用于保存界面数据。通过将ViewModel对象传递到数据绑定中,您可以自动执行视图与ViewModel对象之间的一些通信。
如何将 ViewModel 与布局相关联:
- 在布局文件中,添加一个类型为
ViewModel的数据绑定变量。
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>- 在
GameFragment文件中,将GameViewModel传递到数据绑定中。
binding.gameViewModel = viewModel监听器绑定
- 监听器绑定是布局中在触发点击事件(例如
onClick())时运行的绑定表达式。 - 监听器绑定编写为 lambda 表达式。
- 使用监听器绑定,将界面控制器中的点击监听器替换为布局文件中的监听器绑定。
- 数据绑定会创建一个监听器,并将其设置在视图上。
android:onClick="@{() -> gameViewModel.onSkip()}"向数据绑定添加 LiveData
LiveData对象可用作数据绑定来源,以自动将数据变化通知给界面。- 您可以将视图直接绑定到
ViewModel中的LiveData对象。当ViewModel中的LiveData发生更改时,布局中的视图可以自动更新,而无需界面控制器中的观察器方法。
android:text="@{gameViewModel.word}"- 为了使
LiveData数据绑定正常运行,请将当前 activity(界面控制器)设置为界面控制器中binding变量的生命周期所有者。
binding.lifecycleOwner = this使用数据绑定设置字符串格式
- 使用数据绑定,您可以格式化带有占位符(例如
%s表示字符串,%d表示整数)的字符串资源。 - 如需更新视图的
text属性,请将LiveData对象作为实参传递给格式化字符串。
android:text="@{@string/quote_format(gameViewModel.word)}"Udacity 课程:
Android 开发者文档:
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
- 告知学生如何提交家庭作业。
- 给家庭作业评分。
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
回答以下问题
问题 1
以下关于监听器绑定的表述中哪一项是不正确的?
- 监听器绑定是在事件发生时运行的绑定表达式。
- 监听器绑定适用于所有版本的 Android Gradle 插件。
- 监听器绑定编写为 lambda 表达式。
- 监听器绑定类似于方法引用,但允许您运行任意数据绑定表达式。
问题 2
假设您的应用包含以下字符串资源:<string name="generic_name">Hello %s</string>
以下哪一项是使用数据绑定表达式设置字符串格式的正确语法?
android:text= "@{@string/generic_name(user.name)}"android:text= "@{string/generic_name(user.name)}"android:text= "@{@generic_name(user.name)}"android:text= "@{@string/generic_name,user.name}"
问题 3
何时会评估并运行监听器绑定表达式?
- 当
LiveData保留的数据发生更改时 - 当因配置更改而重新创建 activity 时
- 当发生
onClick()等事件时 - 当 activity 进入后台时
开始学习下一课:
如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。


