此 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.doneBu
tton
(在此示例中,视图的名称是根据 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 着陆页。