1. 准备工作
在之前的 Codelab 中,您已学过如何使用 ViewModel
来存储应用数据。ViewModel
可让应用的数据在发生配置更改后继续留存。在此 Codelab 中,您将学习如何将 LiveData
与 ViewModel
中的数据结合使用。
LiveData
类也是 Android 架构组件的一部分,是一种可观察的数据存储器类。
前提条件
- 如何从 GitHub 下载源代码并在 Android Studio 中将其打开?
- 如何使用 activity 和 fragment 在 Kotlin 中创建和运行基本 Android 应用?
- activity 和 fragment 生命周期如何发挥作用?
- 如何使用
ViewModel
在设备配置更改时保留界面数据? - 如何编写 lambda 表达式?
学习内容
- 如何在应用中使用
LiveData
和MutableLiveData
? - 如何使用
LiveData
来封装存储在ViewModel
中的数据? - 如何添加观察器方法来观察
LiveData.
中的更改? - 如何在布局文件中编写绑定表达式?
构建内容
- 在 Unscramble 应用中,将
LiveData
用于应用的数据(单词、单词数和得分)。 - 添加观察器方法,以便在数据发生更改时收到通知,并自动更新乱序词文本视图。
- 在布局文件中编写绑定表达式,当底层
LiveData
发生更改时会触发该表达式。届时,系统会自动更新得分、单词数和乱序词文本视图。
所需条件
- 一台安装了 Android Studio 的计算机。
- 上一个 Codelab 中的解决方案代码(使用
ViewModel
的 Unscramble 应用)。
下载此 Codelab 的起始代码
此 Codelab 使用您在上一个 Codelab(将数据存储在 ViewModel 中)中构建的 Unscramble 应用作为起始代码。
2. 起始应用概览
此 Codelab 使用的是上一个 Codelab 中的 Unscramble 解决方案代码,您已熟悉这些代码。该应用显示一个乱序词供玩家猜词。玩家猜词的次数不受限制,可以一直尝试,直至猜出正确的单词为止。当前单词、玩家得分和单词数等应用数据保存在 ViewModel
中。不过,该应用的界面不会反映新的得分和单词数。在此 Codelab 中,您将使用 LiveData
来实现这些在该应用中缺失的功能。
3. 什么是 LiveData
LiveData
是一种具有生命周期感知能力、可观察的数据存储器类。
LiveData
的部分特性如下:
LiveData
可存储数据;LiveData
是一种可与任何类型数据搭配使用的封装容器。LiveData
是可观察的,这意味着当LiveData
对象存储的数据发生更改时,观察器会收到通知。LiveData
具有生命周期感知能力。当您将观察器附加到LiveData
后,观察器就会与LifecycleOwner
(通常是 activity 或 fragment)相关联。LiveData
仅更新处于活跃生命周期状态(例如STARTED
或RESUMED
)的观察器。您可以在此处详细了解LiveData
和观察。
起始代码中的界面更新
在起始代码中,每次需要在界面中显示新的乱序词时,都会显式调用 updateNextWordOnScreen()
方法。在游戏初始化期间以及玩家按下 Submit 或 Skip 按钮时,也会调用此方法。此方法还会从 onViewCreated()
、restartGame()
、onSkipWord()
和 onSubmitWord()
方法中调用。不过,有了 Livedata
后,您就不必从多个位置调用此方法来更新界面了。您只需在观察器中调用一次即可。
4. 向当前乱序词添加 LiveData
在此任务中,您要将 GameViewModel
中的当前单词转换为 LiveData
,以此来了解如何使用 LiveData,
封装任何数据。在后续任务中,您将向这些 LiveData
对象添加观察器并了解如何观察 LiveData
。
MutableLiveData
MutableLiveData
是 LiveData
的可变版本,也就是说,其中存储的数据的值是可以更改的。
- 在
GameViewModel
中,将变量_currentScrambledWord
的类型更改为MutableLiveData
<String>
。LiveData
和MutableLiveData
是泛型类,因此需要指定其存储的数据的类型。 - 将
_currentScrambledWord
的变量类型更改为val
,因为LiveData
/MutableLiveData
对象的值将保持不变,且只有该对象中存储的数据会发生更改。
private val _currentScrambledWord = MutableLiveData<String>()
- 将后备字段
currentScrambledWord
的类型更改为LiveData<String>
,因为它不可变。Android Studio 会显示一些错误,您将在后续步骤中修复这些错误。
val currentScrambledWord: LiveData<String>
get() = _currentScrambledWord
- 要访问
LiveData
对象中的数据,请使用value
属性。在GameViewModel
中,在getNextWord()
方法内的else
块中,将_currentScrambledWord
的引用更改为_currentScrambledWord.value
。
private fun getNextWord() {
...
} else {
_currentScrambledWord.value = String(tempWord)
...
}
}
5. 将观察器附加到 LiveData 对象
在此任务中,您将在应用组件 GameFragment
中设置观察器。您添加的观察器用于观察对应用数据 currentScrambledWord
做出的更改。LiveData
具有生命周期感知能力,也就是说它仅更新处于活跃生命周期状态的观察器。因此,仅当 GameFragment
处于 STARTED
或 RESUMED
状态时,GameFragment
中的观察器才会收到通知。
- 在
GameFragment
中,删除updateNextWordOnScreen()
方法以及对它的所有调用。您不需要此方法,因为您要将一个观察器附加到LiveData
上。 - 在
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)
}
}
- 为
currentScrambledWord
LiveData
附加一个观察器。在GameFragment
中的onViewCreated()
回调末尾,对currentScrambledWord
调用observe()
方法。
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()
Android Studio 将显示一个关于缺少参数的错误。您将在下一步中修复该错误。
- 将
viewLifecycleOwner
作为第一个参数传递给observe()
方法。viewLifecycleOwner
表示 fragment 的视图生命周期。此参数可以帮助LiveData
了解GameFragment
的生命周期,并仅在GameFragment
处于活跃状态(STARTED
或RESUMED
)时通知观察器。 - 添加 lambda 作为第二个参数,并以
newWord
作为函数参数。newWord
将包含新乱序词的值。
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
{ newWord ->
})
lambda 表达式是一种未声明但会立即以表达式形式传递的匿名函数。lambda 表达式始终括在花括号 { } 中。
- 在 lambda 表达式的函数主体中,将
newWord
赋值给乱序词文本视图。
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
{ newWord ->
binding.textViewUnscrambledWord.text = newWord
})
- 编译并运行应用。游戏应用的运行应该和之前完全一样,但现在乱序词的文本视图会在
LiveData
观察器中自动更新,而不是在updateNextWordOnScreen()
方法中更新。
6. 将观察器附加到得分和单词数
与上一个任务一样,在此任务中,您将向应用中的其他数据(得分和单词数)添加 LiveData
,这样界面便会在游戏期间更新正确的得分和单词数。
第 1 步:使用 LiveData 封装得分和单词数
- 在
GameViewModel
中,将_score
和_currentWordCount
类变量的类型更改为val
。 - 将变量
_score
和_currentWordCount
的数据类型更改为MutableLiveData
,并将其初始化为0
。 - 将后备字段类型更改为
LiveData<Int>
。
private val _score = MutableLiveData(0)
val score: LiveData<Int>
get() = _score
private val _currentWordCount = MutableLiveData(0)
val currentWordCount: LiveData<Int>
get() = _currentWordCount
- 在
GameViewModel
中的reinitializeData()
方法的开头,将_score
和_currentWordCount
的引用分别更改为_score.
value
和_currentWordCount.
value
。
fun reinitializeData() {
_score.value = 0
_currentWordCount.value = 0
wordsList.clear()
getNextWord()
}
- 在
GameViewModel
中的nextWord()
方法内,将_currentWordCount
的引用更改为_currentWordCount.
value!!
。
fun nextWord(): Boolean {
return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
getNextWord()
true
} else false
}
- 在
GameViewModel
中的increaseScore()
和getNextWord()
方法内,将_score
和_currentWordCount
的引用分别更改为_score.
value
和_currentWordCount.
value
。Android Studio 会显示错误,因为_score
不再是整数而是LiveData
,您将在后续步骤中修复该错误。 - 使用
plus()
Kotlin 函数来增加_score
的值,该函数会以确保 null 安全的形式执行加法。
private fun increaseScore() {
_score.value = (_score.value)?.plus(SCORE_INCREASE)
}
- 同样,使用
inc()
Kotlin 函数,以确保 Null 安全的形式将值加一。
private fun getNextWord() {
...
} else {
_currentScrambledWord.value = String(tempWord)
_currentWordCount.value = (_currentWordCount.value)?.inc()
wordsList.add(currentWord)
}
}
- 在
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
观察器对它们进行更新。
- 在
GameFragment
中的onViewCreated()
方法内,删除用于更新得分和单词数文本视图的代码。
移除:
binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
- 在
GameFragment
中的onViewCreated()
方法的末尾,为score
附加观察器。将viewLifecycleOwner
作为第一个参数传入观察器,并传入 lambda 表达式作为第二个参数。在 lambda 表达式内,将新得分作为参数传递,并在函数主体内将新得分设为文本视图。
viewModel.score.observe(viewLifecycleOwner,
{ newScore ->
binding.score.text = getString(R.string.score, newScore)
})
- 在
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
内的得分和单词数值更改时,就会触发新观察器。
- 运行应用,亲眼见识一下这种神奇的功能。玩一下游戏,猜几个单词。屏幕上的得分和单词数现在也正确更新了。请注意,这些文本视图并非根据代码中的某些条件而更新。
score
和currentWordCount
是LiveData
,当底层的值发生更改时,会自动调用相应的观察器。
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 步:将视图绑定更改为数据绑定
- 在
build.gradle(Module)
文件中的buildFeatures
部分下,启用dataBinding
属性。
将
buildFeatures {
viewBinding = true
}
替换为
buildFeatures {
dataBinding = true
}
当 Android Studio 提示时,执行 Gradle 同步。
- 若要在任何 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 元素就是非绑定布局文件中的根。
- 打开
game_fragment.xml
,选择 code 标签页。 - 为了将布局转换为数据绑定布局,请将根元素封装在
<layout>
标记中。您还必须将命名空间定义(以xmlns:
开头的属性)移至新的根元素。在根元素上方的<layout>
标记内,添加<data></data>
标记。Android Studio 提供了一种便捷方式来自动执行此操作:右键点击根元素 (ScrollView
),然后依次选择 Show Context Actions > Convert to data binding layout。
- 您的布局应如下所示:
<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>
- 在
GameFragment
中的onCreateView()
方法开头,将binding
变量的实例化更改为使用数据绑定。
将
binding = GameFragmentBinding.inflate(inflater, container, false)
替换为
binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
- 编译代码;您应该能够顺利编译。应用现在使用的是数据绑定,布局中的视图可以访问应用数据。
8. 添加数据绑定变量
在此任务中,您将在布局文件中添加属性,以访问 viewModel
中的应用数据。您将在代码中初始化布局变量。
- 在
game_fragment.xml
中的<data>
标记内添加名为<variable>
的子标记,声明一个名为gameViewModel
且类型为GameViewModel
的属性。您将使用此属性将ViewModel
中的数据绑定到布局。
<data>
<variable
name="gameViewModel"
type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>
请注意,类型 gameViewModel
包含软件包名称。请确保此软件包名称与应用中的软件包名称匹配。
- 在
gameViewModel
声明下的<data>
标记内,添加类型为Integer
的另一个变量,并将其命名为maxNoOfWords
。您将使用此变量绑定到 ViewModel 中的变量,以存储每局游戏的单词数。
<data>
...
<variable
name="maxNoOfWords"
type="int" />
</data>
- 在
GameFragment
中的onViewCreated()
方法开头,初始化布局变量gameViewModel
和maxNoOfWords
。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.gameViewModel = viewModel
binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
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
对象。
- 在
game_fragment.xml
中,将text
属性添加到textView_unscrambled_word
文本视图。使用新的布局变量gameViewModel
,并将@{gameViewModel.currentScrambledWord}
赋值给text
属性。
<TextView
android:id="@+id/textView_unscrambled_word"
...
android:text="@{gameViewModel.currentScrambledWord}"
.../>
- 在
GameFragment
中,移除currentScrambledWord
的LiveData
观察器代码:fragment 中不再需要此观察器代码。布局会直接收到对LiveData
所做更改的更新。
移除:
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
{ newWord ->
binding.textViewUnscrambledWord.text = newWord
})
- 运行应用,此时应用应该像以前一样工作。不过,现在乱序词文本视图使用绑定表达式来更新界面,而非使用
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
执行的操作类似。
- 在
game_fragment.xml
中,使用以下绑定表达式更新word_count
文本视图的text
属性。使用word_count
字符串资源并传入gameViewModel.currentWordCount
和maxNoOfWords
作为资源参数。
<TextView
android:id="@+id/word_count"
...
android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
.../>
- 使用以下绑定表达式更新
score
文本视图的text
属性。使用score
字符串资源并传入gameViewModel.score
作为资源参数。
<TextView
android:id="@+id/score"
...
android:text="@{@string/score(gameViewModel.score)}"
... />
- 从
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)
})
- 运行应用并玩一下游戏,猜几个单词。现在,您的代码使用
LiveData
和绑定表达式来更新界面。
恭喜!您已经学习了如何将 LiveData
与 LiveData
观察器搭配使用以及如何将 LiveData
与绑定表达式搭配使用。
10. 在启用 Talkback 的情况下测试 Unscramble 应用
正如您在本课程中所了解的,您需要构建可供尽可能多的用户访问的应用。有些用户可能会使用 TalkBack 访问您的应用并在其中导航。TalkBack 是 Android 设备随附的 Google 屏幕阅读器。TalkBack 会为您提供语音反馈,这样即使您不看屏幕也能轻松使用设备。
在启用 Talkback 的情况下,确保玩家可以玩游戏。
- 按照这些说明在您的设备上启用 TalkBack。
- 返回 Unscramble 应用。
- 按照这些说明,通过 TalkBack 探索您的应用。向右滑动可按顺序浏览屏幕元素,向左滑动即可按相反顺序浏览。点按任意位置两次即可选择。确认您可以通过滑动手势访问应用的所有元素。
- 确保 TalkBack 用户能够导航到屏幕上的每一项。
- 请注意,Talkback 会尝试将乱序词作为一个单词读出来。这可能会让玩家感到困惑,因为乱序词并不是真正的单词。
- 让 Talkback 读出乱序词的一个个字符会带来更好的用户体验。在
GameViewModel
中,将乱序词String
转换为Spannable
字符串。Spannable 字符串是附加了一些额外信息的字符串。在本例中,我们需要将该字符串与类型为TYPE_VERBATIM
的TtsSpan
相关联,让文字转语音引擎逐个字符地读出乱序词。 - 在
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
实例。
- 运行 Unscramble 应用,使用 Talkback 探索该应用。TalkBack 现在应该会读出乱序词的一个个字符。
如需详细了解如何让您的应用使用起来更没有障碍,请参阅这些准则。
11. 删除未使用的代码
最好删除解决方案代码中无用、未使用或不需要的代码。这样做既方便维护代码,又能让新的团队成员更轻松地进一步理解代码。
- 在
GameFragment
中,删除getNextScrambledWord()
和onDetach()
方法。 - 在
GameViewModel
中,删除onCleared()
方法。 - 删除源文件顶部的任何未使用的导入。这些导入显示为灰色。
您不再需要日志语句,如果愿意,您可将其从代码中删除。
- [可选] 删除您在上一个 Codelab 中为了了解
ViewModel
生命周期而在源文件(GameFragment.kt
和GameViewModel.kt
)中添加的Log
语句。
12. 解决方案代码
此 Codelab 的解决方案代码可在下面显示的项目中找到。
- 进入为此项目提供的 GitHub 代码库页面。
- 验证分支名称是否与此 Codelab 中指定的分支名称一致。例如,在以下屏幕截图中,分支名称为 main。
- 在项目的 GitHub 页面上,点击 Code 按钮,以打开一个弹出式窗口。
- 在弹出式窗口中,点击 Download ZIP 按钮,将项目保存到计算机上。等待下载完成。
- 在计算机上找到该文件(很可能在 Downloads 文件夹中)。
- 双击 ZIP 文件进行解压缩。系统将创建一个包含项目文件的新文件夹。
在 Android Studio 中打开项目
- 启动 Android Studio。
- 在 Welcome to Android Studio 窗口中,点击 Open。
注意:如果 Android Studio 已经打开,则改为依次选择 File > Open 菜单选项。
- 在文件浏览器中,前往解压缩的项目文件夹所在的位置(很可能在 Downloads 文件夹中)。
- 双击该项目文件夹。
- 等待 Android Studio 打开项目。
- 点击 Run 按钮 以构建并运行应用。请确保该应用按预期构建。
13. 总结
LiveData
可存储数据;LiveData
是一种可用于任何数据的封装容器。LiveData
是可观察的,这意味着当LiveData
对象存储的数据发生更改时,观察器会收到通知。LiveData
具有生命周期感知能力。当您将观察器附加到LiveData
后,观察器就会与LifecycleOwner
(通常是 activity 或 fragment)相关联。LiveData 仅更新处于活跃生命周期状态(例如STARTED
或RESUMED
)的观察器。您可以在此处详细了解LiveData
和观察。- 应用可以使用数据绑定和绑定表达式,从布局监听 LiveData 更改。
- 绑定表达式在布局中引用布局属性的特性属性(例如
android:text
)中编写。
14. 了解更多内容
- LiveData 概览
- LiveData observer API 参考文档
- 数据绑定
- 双向数据绑定
博文