此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
简介
在本课程之前的 Codelab 中,您曾使用 findViewById() 函数来获取对视图的引用。当应用具有复杂的视图层次结构时,findViewById() 的开销很大,会减慢应用的速度,因为 Android 会从根开始遍历视图层次结构,直到找到所需的视图。幸运的是,我们有一个更好的办法。
为了在视图中设置数据,您使用了字符串资源并从 activity 中设置了数据。如果视图了解数据,效率会更高。幸运的是,这可以实现。
在此 Codelab 中,您将学习如何使用数据绑定来消除对 findViewById() 的需求。您还将学习如何使用数据绑定直接从视图访问数据。
您应当已掌握的内容
您应熟悉以下内容:
- 什么是 activity,以及如何在
onCreate()中设置具有布局的 activity。 - 创建文本视图并设置文本视图显示的文本。
- 使用
findViewById()获取对视图的引用。 - 为视图创建和修改基本 XML 布局。
学习内容
- 如何使用数据绑定库消除对
findViewById()的低效调用。 - 如何直接从 XML 访问应用数据。
您将执行的操作
- 修改应用以使用数据绑定而非
findViewById(),并直接从布局 XML 文件访问数据。
在此 Codelab 中,您将从 AboutMe 应用入手,然后更改该应用以使用数据绑定。完成后,应用看起来将完全一样!
AboutMe 应用的用途如下:
- 当用户打开应用时,应用会显示一个名称、一个用于输入昵称的字段、一个完成按钮、一个星形图片和可滚动的文本。
- 用户可以输入昵称,然后点按完成按钮。可编辑的字段和按钮会被一个显示所输入昵称的文本视图取代。

您可以使用在上一个 Codelab 中构建的代码,也可以从 GitHub 下载 AboutMeDataBinding-Starter 代码。
您在之前的 Codelab 中编写的代码使用 findViewById() 函数来获取对视图的引用。
每次在视图创建或重新创建后使用 findViewById() 搜索视图时,Android 系统都会在运行时遍历视图层次结构以找到该视图。如果您的应用只有少量视图,这不会造成问题。不过,生产应用可能在一个布局中包含数十个视图,即使设计得再好,也会有嵌套视图。
假设有一个线性布局,其中包含一个滚动视图,而该滚动视图又包含一个文本视图。对于大型或深层视图层次结构,查找视图可能需要足够的时间,以至于用户会明显感觉到应用变慢。将视图缓存在变量中会有所帮助,但您仍然必须在每个命名空间中为每个视图初始化一个变量。如果视图很多,并且有多个 activity,那么这也会增加费用。
一种解决方案是创建一个包含对每个视图的引用的对象。此对象(称为 Binding 对象)可供整个应用使用。此技术称为数据绑定。为应用创建绑定对象后,您可以通过该对象访问视图和其他数据,而无需遍历视图层次结构或搜索数据。

数据绑定具有以下优势:
- 与使用
findByView()的代码相比,代码更短、更清楚易懂且更易于维护。 - 数据和视图清晰分离。在本课程的后续内容中,数据绑定的这一优势将变得越来越重要。
- Android 系统只会遍历一次视图层次结构来获取每个视图,并且此过程发生在应用启动期间,而不是在用户与应用互动时的运行时。
- 您可以在访问视图时获得类型安全。(类型安全表示编译器会在编译时验证类型,并且它会在您尝试为变量指定错误类型时抛出错误。)
在此任务中,您将设置数据绑定,并使用数据绑定将对 findViewById() 的调用替换为对绑定对象的调用。
第 1 步:启用数据绑定
如需使用数据绑定,您需要在 Gradle 文件中启用数据绑定,因为默认情况下数据绑定处于停用状态。这是因为数据绑定会增加编译时间,并可能会影响应用启动时间。
- 如果您没有之前 Codelab 中的 AboutMe 应用,请从 GitHub 获取 AboutMeDataBinding-Starter 代码。在 Android Studio 中打开该文件。
- 打开
build.gradle (Module: app)文件。 - 在
android部分中,在右花括号之前,添加一个dataBinding部分,并将enabled设置为true。
dataBinding {
enabled = true
}- 收到提示时,同步项目。如果您未收到提示,请依次选择 File > Sync Project with Gradle Files。
- 您可以运行应用,但不会看到任何变化。
第 2 步:更改布局文件,使其可与数据绑定搭配使用
如需使用数据绑定,您需要使用 <layout> 标记封装 XML 布局。这样一来,根类就不再是视图组,而是包含视图组和视图的布局。然后,绑定对象可以了解布局及其中的视图。
- 打开
activity_main.xml文件。 - 切换到文本标签页。
- 添加
<layout></layout>作为<LinearLayout>周围的最外层标记。
<layout>
<LinearLayout ... >
...
</LinearLayout>
</layout>
- 选择 Code > Reformat code 以修正代码缩进。
布局的命名空间声明必须位于最外层的标记中。
- 从
<LinearLayout>中剪切命名空间声明,然后将其粘贴到<layout>标记中。起始<layout>标记应如下所示,并且<LinearLayout>标记应仅包含视图属性。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
- 构建并运行应用,以验证您是否正确完成了此操作。
第 3 步:在主 activity 中创建绑定对象
向主 activity 添加对绑定对象的引用,以便您可以使用该引用来访问视图:
- 打开
MainActivity.kt文件。 - 在
onCreate()之前,在顶层为绑定对象创建一个变量。此变量通常称为binding。binding的类型(即ActivityMainBinding类)由编译器专门为此主 activity 创建。该名称衍生自布局文件的名称,即activity_main + Binding。
private lateinit var binding: ActivityMainBinding- 如果 Android Studio 提示,请导入
ActivityMainBinding。如果您未收到提示,请点击ActivityMainBinding,然后按Alt+Enter(在 Mac 上,按Option+Enter)以导入此缺失的类。(如需了解更多键盘快捷键,请参阅键盘快捷键。)import语句应类似于下图所示的语句。
import com.example.android.aboutme.databinding.ActivityMainBinding接下来,您将当前 setContentView() 函数替换为执行以下操作的指令:
- 创建绑定对象。
- 使用
DataBindingUtil类中的setContentView()函数将activity_main布局与MainActivity相关联。此setContentView()函数还会处理视图的一些数据绑定设置。
- 在
onCreate()中,将setContentView()调用替换为以下代码行。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
- 导入
DataBindingUtil。
import androidx.databinding.DataBindingUtil第 4 步:使用绑定对象替换对 findViewById() 的所有调用
您现在可以将对 findViewById() 的所有调用替换为对绑定对象中视图的引用。创建绑定对象时,编译器会根据布局中视图的 ID 生成绑定对象中视图的名称,并将其转换为驼峰式命名法。因此,举例来说,如果绑定对象中的 done_button 为 doneButton,则 nickname_edit 会变为 nicknameEdit,而 nickname_text 会变为 nicknameText。
- 在
onCreate()中,将使用findViewById()查找done_button的代码替换为引用绑定对象中按钮的代码。
将以下代码:findViewById<Button>(R.id.done_button)
替换为:binding.doneButton
用于在onCreate()中设置点击监听器的完成代码应如下所示。
binding.doneButton.setOnClickListener {
addNickname(it)
}- 对
addNickname()函数中对findViewById()的所有调用执行相同操作。
将所有出现的findViewById<View>(R.id.id_view)替换为binding.idView。请按以下方式操作:
- 删除
editText和nicknameTextView变量的定义及其对findViewById()的调用。这会导致出现错误。 - 通过从
binding对象(而非已删除的变量)获取nicknameText、nicknameEdit和doneButton视图来修复这些错误。 - 将
view.visibility替换为binding.doneButton.visibility。使用binding.doneButton而不是传入的view可使代码更一致。
结果是以下代码:
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE- 功能没有变化。(可选)您现在可以移除
view参数,并将view的所有用法更新为在此函数内使用binding.doneButton。
nicknameText需要String,而nicknameEdit.text是Editable。使用数据绑定时,必须将Editable显式转换为String。
binding.nicknameText.text = binding.nicknameEdit.text.toString()- 您可以删除灰显的导入项。
- 使用
apply{}将函数 Kotlin 化。
binding.apply {
nicknameText.text = nicknameEdit.text.toString()
nicknameEdit.visibility = View.GONE
doneButton.visibility = View.GONE
nicknameText.visibility = View.VISIBLE
}- 构建并运行应用,此时应用的外观和运行方式应该与之前完全一样。
您可以利用数据绑定功能,直接向视图提供数据类。此技巧可简化代码,对于处理更复杂的情况非常有用。
在此示例中,您将为名称和昵称创建一个数据类,而不是使用字符串资源来设置名称和昵称。您可以使用数据绑定功能,使视图能够访问数据类。
第 1 步:创建 MyName 数据类
- 在 Android Studio 的
java目录中,打开MyName.kt文件。如果您没有此文件,请创建一个名为MyName.kt的新 Kotlin 文件。 - 为名称和昵称定义数据类。使用空字符串作为默认值。
data class MyName(var name: String = "", var nickname: String = "")第 2 步:向布局添加数据
在 activity_main.xml 文件中,名称目前是在 TextView 中通过字符串资源设置的。您需要将对名称的引用替换为对数据类中数据的引用。
- 在文本标签页中打开
activity_main.xml。 - 在布局顶部,在
<layout>和<LinearLayout>标记之间插入<data></data>标记。您可以在此处将视图与数据相关联。
<data>
</data>在数据标记内,您可以声明用于保存对类的引用的命名变量。
- 在
<data>标记内,添加<variable>标记。 - 添加一个
name参数,使变量的名称为"myName"。添加一个type参数,并将类型设置为MyName数据类的完全限定名称(软件包名称 + 变量名称)。
<variable
name="myName"
type="com.example.android.aboutme.MyName" />现在,您可以引用 myName 变量,而不是使用字符串资源作为名称。
- 将
android:text="@string/name"替换为以下代码。
@={} 是一项指令,用于获取大括号内引用的数据。
myName 引用您之前定义的 myName 变量,该变量指向 myName 数据类并从该类中提取 name 属性。
android:text="@={myName.name}"第 3 步:创建数据
现在,您已在布局文件中添加了对数据的引用。接下来,您将创建实际数据。
- 打开
MainActivity.kt文件。 - 在
onCreate()上方,创建一个私有变量,按照惯例也将其命名为myName。为变量分配MyName数据类的实例,并传入名称。
private val myName: MyName = MyName("Aleks Haecky")- 在
onCreate()中,将布局文件中的myName变量的值设置为您刚刚声明的myName变量的值。您无法直接在 XML 中访问该变量。您需要通过绑定对象访问它。
binding.myName = myName- 这可能会显示错误,因为您需要在做出更改后刷新绑定对象。构建应用后,该错误应该会消失。
第 4 步:在 TextView 中使用数据类来表示昵称
最后一步是也使用数据类来表示 TextView 中的昵称。
- 打开
activity_main.xml。 - 在
nickname_text文本视图中,添加text属性。在数据类中引用nickname,如下所示。
android:text="@={myName.nickname}"- 在
ActivityMain中,将nicknameText.text = nicknameEdit.text.toString()
替换为用于在myName变量中设置昵称的代码。
myName?.nickname = nicknameEdit.text.toString()设置昵称后,您希望代码使用新数据刷新界面。为此,您必须使所有绑定表达式失效,以便使用正确的数据重新创建它们。
- 在设置昵称后添加
invalidateAll(),以便使用更新后的绑定对象中的值刷新界面。
binding.apply {
myName?.nickname = nicknameEdit.text.toString()
invalidateAll()
...
}- 构建并运行应用,此时应用应该像以前一样正常运行。
Android Studio 项目:AboutMeDataBinding
使用数据绑定替换对 findViewById() 的调用的步骤:
- 在
build.gradle文件的 android 部分中启用数据绑定:dataBinding { enabled = true } - 在 XML 布局中使用
<layout>作为根视图。 - 定义绑定变量:
private lateinit var binding: ActivityMainBinding - 在
MainActivity中创建一个绑定对象,替换setContentView:binding = DataBindingUtil.setContentView(this, R.layout.activity_main) - 将对
findViewById()的调用替换为对绑定对象中视图的引用。例如:findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
(在此示例中,视图的名称是根据 XML 中视图的id以驼峰命名法生成的。)
将视图绑定到数据的步骤:
- 为您的数据创建一个数据类。
- 在
<layout>标记内添加<data>代码块。 - 定义一个具有名称和数据类类型的
<variable>。
<data>
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
</data>- 在
MainActivity中,创建一个包含数据类实例的变量。例如:private val myName: MyName = MyName("Aleks Haecky")
- 在绑定对象中,将变量设置为您刚刚创建的变量:
binding.myName = myName
- 在 XML 中,将视图的内容设置为您在
<data>块中定义的变量。使用点分表示法访问数据类中的数据。android:text="@={myName.name}"
Udacity 课程:
Android 开发者文档:
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
- 告知学生如何提交家庭作业。
- 给家庭作业评分。
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
回答以下问题
问题 1
为什么要尽量减少对 findViewById() 的显式和隐式调用?
- 每次调用
findViewById()时,它都会遍历视图层次结构。 findViewById()在主线程或界面线程中运行。- 此类调用会减慢界面的响应速度。
- 您的应用崩溃的可能性较低。
问题 2
您会如何描述数据绑定?
例如,您可以就数据绑定提出以下问题:
- 数据绑定的核心思想是在编译时创建一个对象,将两段相距较远的信息连接/映射/绑定在一起,这样您就不必在运行时查找数据。
- 向您显示这些绑定的对象称为绑定对象。
- 绑定对象由编译器创建。
问题 3
以下哪一项不是数据绑定的优势?
- 代码更短、更清楚易懂且更易于维护。
- 数据和视图清晰分离。
- Android 系统仅遍历一次视图层次结构即可获取每个视图。
- 调用
findViewById()会生成编译器错误。 - 用于访问视图的类型安全。
问题 4
<layout> 代码的功能是什么?
- 您可以在布局中将其封装在根视图周围。
- 系统会为布局中的所有视图创建绑定。
- 它用于指定使用数据绑定的 XML 布局中的顶级视图。
- 您可以在
<layout>内使用<data>标记将变量绑定到数据类。
问题 5
以下哪一项是在 XML 布局中引用绑定数据的正确方法?
android:text="@={myDataClass.property}"android:text="@={myDataClass}"android:text="@={myDataClass.property.toString()}"android:text="@={myDataClass.bound_data.property}"
开始学习下一课:
如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。