1. 欢迎
此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
简介
在上一个 Codelab 中,您学习了如何从网络服务中获取数据,以及如何将响应解析为数据对象。在本 Codelab 中,您将利用这些知识从一个网址加载和显示照片。此外,您还将回顾如何构建 RecyclerView
以及用它在概览页面上显示图片网格。
您应当已掌握的内容
- 如何创建和使用 fragment。
- 如何使用架构组件,包括视图模型、视图模型工厂、转换和
LiveData
。 - 如何从 REST 网络服务中检索 JSON,并使用 Retrofit 和 Moshi 库将该数据解析为 Kotlin 对象。
- 如何使用
RecyclerView
构建网格布局。 Adapter
、ViewHolder
和DiffUtil
如何工作。
学习内容
- 如何使用 Glide 库从一个网址加载和显示图片。
- 如何使用
RecyclerView
和网格适配器显示图片网格。 - 如何处理图片下载和显示时的潜在错误。
实践内容
- 修改 MarsRealEstate 应用,从火星资源数据中获取图片网址,并使用 Glide 加载和显示该图片。
- 将加载动画和错误图标添加到应用中。
- 使用
RecyclerView
显示火星资源图片网格。 - 将状态和错误处理添加到
RecyclerView
。
2. 应用概览
在此 Codelab 中,您将使用一款名为 MarsRealEstate 的应用,该应用会显示火星上的待售资源。该应用需要连接到互联网服务器才能检索和显示资源数据,包括价格以及资源是可出售还是可租赁等详细信息。代表各项资源的图片是由 NASA 的火星探测器拍摄的真实照片。
您在此 Codelab 中构建的应用版本会填充概览页面,该页面将显示图片网格。这些图片是您的应用从火星地产网络服务获取的资源数据的一部分。您的应用将使用 Glide 库加载和显示图片,使用 RecyclerView
为图片创建网格布局,还将妥善处理网络连接错误。
3.任务:显示互联网图片
从一个网址显示照片可能听起来非常简单,但实际上却需要完成大量工程才能正常运行。图片必须下载、在内部存储并从其压缩格式解码为 Android 可使用的图片。应将图片缓存到内存缓存和/或基于存储空间的缓存中。所有操作都必须在低优先级的后台线程中进行,以便界面保持快速响应。另外,为获得最佳网络和 CPU 性能,可能需要同时获取和解码多张图片。如何高效地从网络加载图片,这本身就可以作为一个 Codelab 来学习。
幸好,您可以使用 Glide 这个由社区开发的库来下载、缓冲、解码以及缓存图片。如果不使用 Glide,您将需要执行更多操作。
一般来说,Glide 需要以下两项内容:
- 需要加载和显示的图片的网址。
- 用于实际显示该图片的
ImageView
对象。
在此任务中,您将学习如何使用 Glide 显示地产网络服务中的单张图片。您可以在网络服务返回的资源列表中显示代表第一项火星资源的图片。下面是操作之前和之后的屏幕截图:
第 1 步:添加 Glide 依赖项
- 打开上一个 Codelab 中的 MarsRealEstate 应用。(如果您没有该应用,可以在此处下载 MarsRealEstateNetwork。)
- 运行应用以查看其功能(它会显示检索到的火星资源的总数)。
- 打开 build.gradle (Module: app)。
- 在
dependencies
部分中,为 Glide 库添加下面这行代码:
implementation "com.github.bumptech.glide:glide:$version_glide"
请注意,版本号已在项目 Gradle 文件中单独定义。
- 请点击 Sync Now,以使用新的依赖项重建项目。
第 2 步:更新视图模型
接下来,您需要更新 OverviewViewModel
类,为单项火星资源添加实时数据。
- 打开
overview/OverviewViewModel.kt
。在_response
的LiveData
的正下方,为单个MarsProperty
对象添加内部(可变)和外部(不可变)实时数据。
在收到请求时,导入 MarsProperty
类 (com.example.android.marsrealestate.network.MarsProperty
)。
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
- 在
getMarsRealEstateProperties()
方法中的try/catch {}
代码块内,找到用于将_response.value
设置为资源数的代码行。在try/catch
之后添加如下所示的测试。如果MarsProperty
对象可用,此测试会将_property
LiveData
的值设为listResult
中的第一项资源。
if (listResult.size > 0) {
_property.value = listResult[0]
}
完整的 try/catch {}
代码块现在应如下所示:
try {
val listResult = MarsApi.retrofitService.getProperties()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
- 打开
res/layout/fragment_overview.xml
文件。在<TextView>
元素中,更改android:text
以绑定到property
LiveData
的imgSrcUrl
组件:
android:text="@{viewModel.property.imgSrcUrl}"
- 运行应用。
TextView
仅显示第一项火星资源图片的网址。到目前为止,您已为该网址设置视图模型和实时数据。
第 3 步:创建绑定适配器并调用 Glide
现在,您已获得要显示的图片的网址,接下来可以开始使用 Glide 加载该图片了。在此步骤中,您将使用绑定适配器从与 ImageView
关联的 XML 属性中获取网址,并使用 Glide 通过相应网址加载图片。绑定适配器是位于视图与绑定数据之间的扩展方法,可以在数据发生更改时提供自定义行为。在这种情况下,自定义行为是指调用 Glide 将来自网址的图片加载到 ImageView
中。
- 打开
BindingAdapters.kt
。此文件将保留您在整个应用中使用的绑定适配器。 - 创建一个
bindImage()
函数,该函数将ImageView
和String
作为参数。使用@BindingAdapter
为该函数添加注解。@BindingAdapter
注解用于指示数据绑定,您需要在 XML 项具有imageUrl
属性时执行此绑定适配器。
在收到请求时,导入 androidx.databinding.BindingAdapter
和 android.widget.ImageView
。
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
- 在
bindImage()
函数中,向imgUrl
参数添加let {}
代码块:
imgUrl?.let {
}
- 在
let {}
代码块内,添加如下所示的代码行,以将网址字符串(来自 XML)转换为Uri
对象。在收到请求时,导入androidx.core.net.toUri
。
您需要让最终的 Uri
对象使用 HTTPS 架构,因为您从中拉取映像的服务器要求使用该架构。如需使用 HTTPS 架构,请将 buildUpon.scheme("https")
附加到 toUri
构建器。toUri()
方法是 Android KTX 核心库中的一个 Kotlin 扩展函数,因此它看起来像是 String
类的一部分。
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- 在
let {}
内,继续调用Glide.with()
,以将图片从Uri
对象加载到ImageView
中。在收到请求时,导入com.bumptech.glide.Glide
。
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
您可能需要将以下选项添加到模块 build.gradle
文件中:
android {
...
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
...
}
第 4 步:更新布局和 fragment
虽然 Glide 已加载图片,但目前还没显示任何内容。下一步是使用 ImageView
更新布局和 fragment,以显示图片。
- 打开
res/layout/gridview_item.xml
。这个布局资源文件稍后将用于此 Codelab 中的RecyclerView
内的每一项。在这一步中,您将暂时使用此文件来仅显示单张图片。 - 在
<ImageView>
元素上方,为数据绑定添加<data>
元素,并绑定到OverviewViewModel
类:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- 将
app:imageUrl
属性添加到ImageView
元素中,以使用新的图片加载绑定适配器:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
- 打开
overview/OverviewFragment.kt
。在onCreateView()
方法中,注释掉膨胀了FragmentOverviewBinding
类的代码行并将它分配给绑定变量。您将看到由于删除此代码行而出现的错误。这只是临时错误,您稍后将处理此错误。
//val binding = FragmentOverviewBinding.inflate(inflater)
- 添加一行代码以膨胀
GridViewItemBinding
类。如果收到请求,则导入com.example.android.marsrealestate. databinding.GridViewItemBinding
。
val binding = GridViewItemBinding.inflate(inflater)
- 请运行应用。现在,您应该会在结果列表中看到第一项
MarsProperty
的图片。
第 5 步:添加简单的加载和错误图片
Glide 会在加载图片时显示占位符图片,并在加载失败时显示错误消息(例如,图片丢失或损坏),从而提升用户体验。在此步骤中,您需要将该功能添加到绑定适配器和布局中。
- 打开
res/drawable/ic_broken_image.xml
,然后点击右侧的 Design 标签页。对于错误图片,您将使用内置图标库中提供的损坏图片图标。此矢量可绘制对象使用android:tint
属性将图标设为灰色。
- 打开
res/drawable/loading_animation.xml
。这个可绘制对象是使用<animated-rotate>
标记定义的动画。该动画会围绕中心点旋转图片可绘制对象loading_img.xml
。(您在预览中看不到这段动画。)
- 返回
BindingAdapters.kt
文件。在bindImage()
方法中,更新对Glide.with()
的调用,以在load()
和into()
之间调用apply()
函数。在收到请求时,导入com.bumptech.glide.request.RequestOptions
。
此代码可设置加载时要使用的占位符加载图片(loading_animation
可绘制对象)。此代码还可设置图片加载失败时要使用的图片(broken_image
可绘制对象)。完整的 bindImage()
方法现在应如下所示:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- 请运行应用。根据网络连接速度,您可能会短暂地看到加载图片显示为 Glide 下载内容,并显示资源图片。但是您不会看到损坏图片图标,即使您关闭网络也是如此。您将在 Codelab 的最后一个任务中修复该错误。
4.任务:使用 RecyclerView 显示图片网格
您的应用现在从互联网加载了火星资源信息。您使用第一个 MarsProperty
列表项的数据在视图模型中创建了一个 LiveData
属性,并使用该资源数据中的图片网址填充了 ImageView
。但是,应用的目标是显示图片网格,因此,您需要将 RecyclerView
与 GridLayoutManager
结合使用。
第 1 步:更新视图模型
现在,视图模型有一个 _property
LiveData
,用于保存一个 MarsProperty
对象,即网络服务的响应列表中的第一个对象。在此步骤中,您将更改该 LiveData
,以保存 MarsProperty
对象的完整列表。
- 打开
overview/OverviewViewModel.kt
。 - 将不公开的
_property
变量更改为_properties
。将类型更改为MarsProperty
对象的列表。
private val _properties = MutableLiveData<List<MarsProperty>>()
- 将外部
property
实时数据替换为properties
。也将列表添加为此处的LiveData
类型:
val properties: LiveData<List<MarsProperty>>
get() = _properties
- 向下滚动到
getMarsRealEstateProperties()
方法。在try {}
代码块内,将您在上一个任务中添加的完整测试替换为如下所示的代码行。由于MarsApi.
retrofitService
.getProperties()
会返回一个 MarsProperty
对象列表,您可以直接将该列表分配给 _properties.value
,而不是测试是否成功响应。
_properties.value = MarsApi.retrofitService.getProperties()
完整的 try/catch
代码块现在应如下所示:
try {
_properties.value = MarsApi.retrofitService.getProperties()
_response.value = "Success: Mars properties retrieved"
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
第 2 步:更新布局和 fragment
下一步是更改应用的布局和 fragment,以使用 Recycler 视图和网格布局,而非单个图片视图。
- 打开
res/layout/gridview_item.xml
。将数据绑定从OverviewViewModel
更改为MarsProperty
,并将该变量重命名为"property"
。
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
- 在
<ImageView>
中,更改app:imageUrl
属性以引用MarsProperty
对象中的图片网址:
app:imageUrl="@{property.imgSrcUrl}"
- 打开
overview/OverviewFragment.kt
。在onCreateview()
中,取消注释膨胀了FragmentOverviewBinding
的代码行。删除或注释掉膨胀了GridViewBinding
的代码行。这些更改将撤消您在上一个任务中所做的临时更改。
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
- 打开
res/layout/fragment_overview.xml
。删除整个<TextView>
元素。 - 改为添加以下
<RecyclerView>
元素,该元素对单个项使用GridLayoutManager
和grid_view_item
布局:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
第 3 步:添加照片网格适配器
现在,fragment_overview
布局具有 RecyclerView
,而 grid_view_item
布局具有单个 ImageView
。在此步骤中,您将通过 RecyclerView
适配器将数据绑定到 RecyclerView
。
- 打开
overview/PhotoGridAdapter.kt
。 - 使用如下所示的构造函数参数创建
PhotoGridAdapter
类。PhotoGridAdapter
类扩展了ListAdapter
,其构造函数需要列表项类型、视图容器以及DiffUtil.ItemCallback
实现。
在收到请求时,导入 androidx.recyclerview.widget.ListAdapter
和 com.example.android.marsrealestate.network.MarsProperty
类。在接下来的步骤中,您将实现这个构造函数中会产生错误的其他缺失部分。
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
- 点击
PhotoGridAdapter
类中的任意位置,然后按Control+i
键以实现ListAdapter
方法,即onCreateViewHolder()
和onBindViewHolder()
。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}
- 在
PhotoGridAdapter
类定义的末尾,在您刚刚添加的方法之后,为DiffCallback
添加伴生对象定义,如下所示。
在收到请求时,导入 androidx.recyclerview.widget.DiffUtil
。
DiffCallback
对象使用您想要比较的对象类型 MarsProperty
来扩展 DiffUtil.ItemCallback
。
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
- 按
Control+i
键,为此对象实现比较条件方法,即areItemsTheSame()
和areContentsTheSame()
。
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }
- 对于
areItemsTheSame()
方法,移除 TODO。使用 Kotlin 的指示性等式运算符 (===
),它会在oldItem
和newItem
的对象引用相同时返回true
。
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
- 对于
areContentsTheSame()
,请仅对oldItem
和newItem
的 ID 使用标准结构等式运算符。
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
- 在
PhotoGridAdapter
类中,在伴生对象下方,继续为MarsPropertyViewHolder
添加内部类定义,该类会扩展RecyclerView.ViewHolder
。
如果收到请求,则导入 androidx.recyclerview.widget.RecyclerView
和 com.example.android.marsrealestate.databinding.GridViewItemBinding
。
您需要使用 GridViewItemBinding
变量将 MarsProperty
绑定到布局,以便将变量传递到 MarsPropertyViewHolder
。由于基础 ViewHolder
类需要其构造函数中的一个视图,因此您需要向其传递绑定根视图。
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
- 在
MarsPropertyViewHolder
中,创建一个bind()
方法,该方法获取MarsProperty
对象作为参数,并将binding.property
设置为该对象。设置属性后,请调用executePendingBindings()
,这会导致更新立即执行。
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
- 在
onCreateViewHolder()
的PhotoGridAdapter
类中,继续移除 TODO 并添加如下所示的代码行。在收到请求时,导入android.view.LayoutInflater
。
onCreateViewHolder()
方法需要返回新的 MarsPropertyViewHolder
,方法是膨胀 GridViewItemBinding
并使用父级 ViewGroup
上下文中的 LayoutInflater
。
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
- 在
onBindViewHolder()
方法中,移除 TODO 并添加如下所示的代码行。在这一步中,您将调用getItem()
以获取与当前RecyclerView
位置关联的MarsProperty
对象,然后将该资源传递给MarsPropertyViewHolder
中的bind()
方法。
val marsProperty = getItem(position)
holder.bind(marsProperty)
第 4 步:添加绑定适配器并连接各部分
最后,使用 BindingAdapter
通过 MarsProperty
对象列表初始化 PhotoGridAdapter
。使用 BindingAdapter
设置 RecyclerView
数据会导致数据绑定自动观察 MarsProperty
对象列表的 LiveData
。然后,当 MarsProperty
列表发生更改时,系统会自动调用绑定适配器。
- 打开
BindingAdapters.kt
。 - 在文件末尾添加
bindRecyclerView()
方法,该方法会获取RecyclerView
和MarsProperty
对象列表作为参数。使用@BindingAdapter
为该方法添加注解。
在收到请求时,导入 androidx.recyclerview.widget.RecyclerView
和 com.example.android.marsrealestate.network.MarsProperty
。
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
- 在
bindRecyclerView()
函数中,将recyclerView.adapter
的类型转换为PhotoGridAdapter
,并用数据调用adapter.submitList()
。此代码会在有新列表可供使用时告知RecyclerView
。
如果收到请求,则导入 com.example.android.marsrealestate.overview.PhotoGridAdapter
。
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
- 打开
res/layout/fragment_overview.xml
。向RecyclerView
元素中添加app:listData
属性,并使用数据绑定将该属性设置为viewmodel.properties
。
app:listData="@{viewModel.properties}"
- 打开
overview/OverviewFragment.kt
。在onCreateView()
中对setHasOptionsMenu()
的调用之前,将binding.photosGrid
中的RecyclerView
适配器初始化为新的PhotoGridAdapter
对象。
binding.photosGrid.adapter = PhotoGridAdapter()
- 运行应用,您应该会看到一个包含
MarsProperty
图片的网格。在您滚动查看新图片时,该应用会先显示加载进度图标,然后再显示图片本身。如果您开启飞行模式,尚未加载的图片会显示为损坏图片图标。
5. 任务:在 RecyclerView 中添加错误处理机制
MarsRealEstate 应用会在无法获取图片时显示损坏图片图标。但在没有网络连接时,应用会显示空白屏幕。
这样的用户体验并不是很好。在此任务中,您将添加基本的错误处理机制,以便用户可以清楚地了解所发生的情况。如果无法连接互联网,应用将显示连接错误图标。而在应用提取 MarsProperty
列表时,应用将显示加载动画。
第 1 步:向视图模型添加状态
首先,您将在视图模型中创建一个 LiveData
来表示网络请求的状态。需要考虑以下三种状态:正在加载、成功和失败。在等待对 await()
的调用中的数据时,会出现加载状态。
- 打开
overview/OverviewViewModel.kt
。在文件的顶部(导入后,在类定义前),添加表示所有可用状态的enum
:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- 将
OverviewViewModel
类中的内部和外部_response
实时数据定义重命名为_status
。由于您先前在本 Codelab 中添加了对_properties
LiveData
的支持,因此完整的网络服务响应处于尚未使用状态。在这里,您需要使用LiveData
跟踪当前状态,因此可以只重命名现有变量。
此外,将类型从 String
更改为 MarsApiStatus.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
- 向下滚动到
getMarsRealEstateProperties()
方法,也将这里的_response
更新为_status
。将"Success"
字符串更改为MarsApiStatus.DONE
状态,并将"Failure"
字符串更改为MarsApiStatus.ERROR
。 - 将
try {}
代码块前面的状态设置为MarsApiStatus.LOADING
。这是协程运行以及等待数据期间显示的初始状态。完整的try/catch {}
代码块现在应如下所示:
_status.value = MarsApiStatus.LOADING
try {
_properties.value = MarsApi.retrofitService.getProperties()
_status.value = MarsApiStatus.DONE
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
- 在
catch {}
代码块中的错误状态后,将_properties
LiveData
设置为空列表。这会清除RecyclerView
。
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
第 2 步:为状态 ImageView 添加绑定适配器
现在,视图模型中有一个状态,但它只是一组状态。如何使该状态显示在应用本身中呢?在此步骤中,您将使用已连接到数据绑定的 ImageView
来显示加载和错误状态的图标。当应用处于加载状态或错误状态时,ImageView
应可见。应用完成加载后,ImageView
应不可见。
- 打开
BindingAdapters.kt
。添加一个名为bindStatus()
的新绑定适配器,该适配器获取ImageView
和MarsApiStatus
值作为参数。在收到请求时,导入com.example.android.marsrealestate.overview.MarsApiStatus
。
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- 在
bindStatus()
方法中添加一个when {}
代码块,以在不同状态之间切换。
when (status) {
}
- 在
when {}
中,为加载状态 (MarsApiStatus.LOADING
) 添加一个用例。对于此状态,请将ImageView
设为可见,然后为其分配加载动画。这与您在上一个任务中用于 Glide 的动画可绘制对象相同。在收到请求时,导入android.view.View
。
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- 为错误状态
MarsApiStatus.ERROR
添加一个用例。与针对LOADING
状态的操作类似,将状态ImageView
设置为可见,并重新使用连接错误可绘制对象。
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- 为完成状态
MarsApiStatus.DONE
添加一个用例。在这一步中,您获得了成功响应,因此请关闭状态ImageView
的可见性,将其隐藏。
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
第 3 步:向布局添加状态 ImageView
- 打开
res/layout/fragment_overview.xml
。在ConstraintLayout
内的RecyclerView
元素下,添加如下所示的ImageView
。
此 ImageView
与 RecyclerView
具有相同的限制条件。不过,宽度和高度使用 wrap_content
使图片居中,而不是拉伸图片填满视图。另请注意 app:marsApiStatus
属性,该属性会在视图模型中的状态属性发生更改时让视图调用 BindingAdapter
。
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />
- 在模拟器或设备中开启飞行模式即可模拟网络连接缺失的情况。编译并运行应用,并注意显示的错误图片:
- 点按“返回”按钮以关闭应用,然后关闭飞行模式。使用“最近”屏幕返回应用。根据网络连接速度,如果应用在开始加载图片之前查询网络服务,您可能会看到一个非常简单的旋转图标。
6. 解决方案代码
Android Studio 项目:MarsRealEstateGrid
7. 总结
- 如需简化图片管理流程,请使用 Glide 库在应用中下载、缓冲、解码以及缓存图片。
- Glide 需要具备以下两项内容才能从互联网加载图片:图片的网址,以及用于容纳图片的
ImageView
对象。如需指定这些选项,请将load()
和into()
方法与 Glide 搭配使用。 - 绑定适配器是位于视图与视图的绑定数据之间的扩展方法。绑定适配器可以在数据发生更改时提供自定义行为,例如,调用 Glide 将来自网址的图片加载到
ImageView
中。 - 绑定适配器是带有
@BindingAdapter
注解的扩展方法。 - 如需向 Glide 请求添加选项,请使用
apply()
方法。例如,将apply()
和placeholder()
搭配使用可指定加载可绘制对象,将apply()
和error()
搭配使用可指定错误可绘制对象。 - 如需生成图片网格,请将
RecyclerView
与GridLayoutManager
结合使用。 - 如需在属性发生更改时更新属性列表,请在
RecyclerView
和布局之间使用绑定适配器。
8. 了解详情
Udacity 课程:
Android 开发者文档:
其他:
9. 家庭作业
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
- 告知学生如何提交家庭作业。
- 给家庭作业评分。
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
回答以下问题
问题 1
哪个 Glide 方法用于指明将包含已加载图片的 ImageView
?
▢ into()
▢ with()
▢ imageview()
▢ apply()
问题 2
如何指定在加载 Glide 时显示的占位符图片?
▢ 使用包含可绘制对象的 into()
方法。
▢ 使用 RequestOptions()
并调用包含可绘制对象的 placeholder()
方法。
▢ 将 Glide.placeholder
属性分配给可绘制对象。
▢ 使用 RequestOptions()
并调用包含可绘制对象的 loadingImage()
方法。
问题 3
如何指明某个方法是否为绑定适配器?
▢ 对 LiveData
调用 setBindingAdapter()
方法。
▢ 将该方法放在名为 BindingAdapters.kt
的 Kotlin 文件中。
▢ 在 XML 布局中使用 android:adapter
属性。
▢ 为该方法添加 @BindingAdapter
注解。
10. 下一个 Codelab
如需本课程中其他 Codelab 的链接,请查看“Android Kotlin 基础知识”Codelab 着陆页。