Android Kotlin 基础知识 08.3 过滤互联网数据和生成详细视图

此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。

简介

在本课程之前的 Codelab 中,您学习了如何从网络服务中获取有关火星地产的数据,以及如何创建具有网格布局的 RecyclerView 以加载和显示来自该数据的图片。在此 Codelab 中,您将完成 MarsRealEstate 应用,方法是实现按火星房产是否可供出租或购买进行过滤的功能。您还可以创建一个详情视图,以便用户在概览中点按房源照片后,看到包含该房源详细信息的详情视图。

您应当已掌握的内容

  • 如何创建和使用 fragment。
  • 如何在 fragment 之间导航,以及如何使用 Safe Args(一个 Gradle 插件)在 fragment 之间传递数据。
  • 如何使用架构组件,包括视图模型、视图模型工厂、转换和 LiveData。
  • 如何从 REST 网络服务中检索 JSON 编码的数据,并使用 Retrofit 和 Moshi 库将该数据解析为 Kotlin 对象。

学习内容

  • 如何在布局文件中使用复杂的绑定表达式。
  • 如何使用查询选项向 Web 服务发出 Retrofit 请求。

实践内容

  • 修改 MarsRealEstate 应用,以使用美元符号图标标记待售的火星房产(而不是待租的火星房产)。
  • 使用概览页面上的选项菜单创建一个 Web 服务请求,按类型过滤火星房产。
  • 为火星房产创建详情 fragment,通过导航将该 fragment 连接到概览网格,并将房产数据传递到该 fragment 中。

在此 Codelab(和相关 Codelab)中,您将使用一款名为 MarsRealEstate 的应用,该应用会显示火星上的待售房产。该应用需要连接到互联网服务器才能检索和显示房源数据,包括价格以及房源是可出售还是可租赁等详细信息。代表各项资源的图片是由 NASA 的火星探测器拍摄的真实照片。在之前的 Codelab 中,您创建了一个 RecyclerView,其中包含用于显示所有房源照片的网格布局:

在此版本的应用中,您将处理房产类型(出租与出售),并向网格布局添加一个图标,以标记待售房产:

您修改了应用的选项菜单,以过滤网格,使其仅显示待租或待售的房源:

最后,您要为单个房源创建一个详情视图,并通过导航将概览网格中的图标连接到该详情 fragment:

到目前为止,您使用的火星房产数据只有房产图片的网址。不过,您在 MarsProperty 类中定义的房源数据还包括 ID、价格和类型(出租或出售)。为了唤起您的记忆,下面是您从 Web 服务获取的 JSON 数据片段:

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

在此任务中,您将开始使用火星房产类型,以便在概览页面上添加美元符号图片,以表示待售房产。

第 1 步:更新 MarsProperty 以包含类型

MarsProperty 类定义了 Web 服务提供的每个属性的数据结构。在之前的 Codelab 中,您使用 Moshi 库将来自 Mars Web 服务的原始 JSON 响应解析为各个 MarsProperty 数据对象。

在此步骤中,您将向 MarsProperty 类添加一些逻辑,以指明房产是否出租(即类型是否为字符串 "rent" 或 "buy")。您将在多个位置使用此逻辑,因此最好将其放在数据类中,而不是复制它。

  1. 打开上一个 Codelab 中的 MarsRealEstate 应用。(如果您没有该应用,可以下载 MarsRealEstateGrid。)
  2. 打开 network/MarsProperty.kt。向 MarsProperty 类定义添加正文,并为 isRental 添加自定义 getter,如果对象属于 "rent" 类型,则返回 true。
data class MarsProperty(
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double)  {
   val isRental
       get() = type == "rent"
}

第 2 步:更新网格项布局

现在,您需要更新图片网格的商品布局,以便仅在待售房产图片上显示美元符号可绘制对象:

借助数据绑定表达式,您可以在网格项的 XML 布局中完全完成此测试。

  1. 打开 res/layout/grid_view_item.xml。这是 RecyclerView 的网格布局中每个单元格的布局文件。目前,该文件仅包含媒体资源图片的 <ImageView> 元素。
  2. 在 <data> 元素内,为 View 类添加 <import> 元素。当您想在布局文件的数据绑定表达式中使用类的组件时,可以使用导入。在这种情况下,您将使用 View.GONE 和 View.VISIBLE 常量,因此需要访问 View 类。
<import type="android.view.View"/>
  1. 使用 FrameLayout 环绕整个图片视图,以允许美元符号可绘制对象堆叠在房源图片之上。
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. 对于 ImageView,将 android:layout_height 属性更改为 match_parent,以填充新的父级 FrameLayout。
android:layout_height="match_parent"
  1. 在 FrameLayout 内,在第一个 <ImageView> 元素下方添加第二个 <ImageView> 元素。使用下方的定义。此图片显示在网格项的右下角,位于火星图片上方,并使用 res/drawable/ic_for_sale_outline.xml 中定义的可绘制对象作为美元符号图标。
<ImageView
   android:id="@+id/mars_property_type"
   android:layout_width="wrap_content"
   android:layout_height="45dp"
   android:layout_gravity="bottom|end"
   android:adjustViewBounds="true"
   android:padding="5dp"
   android:scaleType="fitCenter"
   android:src="@drawable/ic_for_sale_outline"
   tools:src="@drawable/ic_for_sale_outline"/>
  1. 将 android:visibility 属性添加到 mars_property_type 图片视图。使用绑定表达式测试属性类型,并将可见性分配给 View.GONE(对于租借)或 View.VISIBLE(对于购买)。
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

到目前为止,您只在布局中看到过绑定表达式,这些布局使用 <data> 元素中定义的单个变量。绑定表达式功能非常强大,可让您完全在 XML 布局中执行测试和数学计算等操作。在这种情况下,您可以使用三元运算符 (?:) 来执行测试(相应对象是否为出租房源?)。您需要为 true 提供一个结果(使用 View.GONE 隐藏美元符号图标),并为 false 提供另一个结果(使用 View.VISIBLE 显示该图标)。

新的完整 grid_view_item.xml 文件如下所示:

<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>
       <import type="android.view.View"/>
       <variable
           name="property"
           type="com.example.android.marsrealestate.network.MarsProperty" />
   </data>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="170dp">

       <ImageView
           android:id="@+id/mars_image"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:scaleType="centerCrop"
           android:adjustViewBounds="true"
           android:padding="2dp"
           app:imageUrl="@{property.imgSrcUrl}"
           tools:src="@tools:sample/backgrounds/scenic"/>

       <ImageView
           android:id="@+id/mars_property_type"
           android:layout_width="wrap_content"
           android:layout_height="45dp"
           android:layout_gravity="bottom|end"
           android:adjustViewBounds="true"
           android:padding="5dp"
           android:scaleType="fitCenter"
           android:src="@drawable/ic_for_sale_outline"
           android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
           tools:src="@drawable/ic_for_sale_outline"/>
   </FrameLayout>
</layout>
  1. 编译并运行应用,并注意非出租房源具有美元符号图标。

目前,您的应用会在概览网格中显示所有火星资源。如果用户在火星上寻找出租房源,那么使用图标来指明哪些房源可供出售会很有用,但页面上仍有许多房源需要滚动浏览。在此任务中,您将向概览 fragment 添加一个选项菜单,让用户能够选择仅显示出租房源、仅显示出售房源或显示所有房源。

实现此任务的一种方法是,测试概览网格中每个 MarsProperty 的类型,并仅显示匹配的属性。不过,实际的火星 Web 服务有一个查询参数或选项(称为 filter),可让您仅获取 rent 类型或 buy 类型的房产。您可以在浏览器中将此过滤查询与 realestate 网络服务网址搭配使用,如下所示:

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

在此任务中,您将修改 MarsApiService 类,以使用 Retrofit 向 Web 服务请求添加查询选项。然后,您将选项菜单连接起来,以便使用该查询选项重新下载所有火星房产数据。由于从 Web 服务获得的响应仅包含您感兴趣的属性,因此您无需更改概览网格的视图显示逻辑。

第 1 步:更新 Mars API 服务

如需更改请求,您需要重新访问本系列第一个 Codelab 中实现的 MarsApiService 类。您修改了该类,以提供过滤 API。

  1. 打开 network/MarsApiService.kt。在导入项的正下方,创建一个名为 MarsApiFilter 的 enum,以定义与 Web 服务所需的查询值相匹配的常量。
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. 修改 getProperties() 方法,使其接受过滤查询的字符串输入,并使用 @Query("filter") 对该输入进行注释,如下所示。

    在出现提示时,导入 retrofit2.http.Query。

    @Query 注解会告知 getProperties() 方法(以及 Retrofit)使用过滤条件选项发出 Web 服务请求。每次调用 getProperties() 时,请求网址都会包含 ?filter=type 部分,这会指示 Web 服务返回与相应查询匹配的结果。
fun getProperties(@Query("filter") type: String):  

第 2 步:更新概览视图模型

您可以在 OverviewViewModel 的 getMarsRealEstateProperties() 方法中从 MarsApiService 请求数据。现在,您需要更新该请求以接受过滤条件实参。

  1. 打开 overview/OverviewViewModel.kt。由于您在上一步中做出的更改,您将在 Android Studio 中看到错误。将 MarsApiFilter(可能过滤值的枚举)作为形参添加到 getMarsRealEstateProperties() 调用中。

    在收到请求时,导入 com.example.android.marsrealestate.network.MarsApiFilter。
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. 修改 Retrofit 服务中对 getProperties() 的调用,以将该过滤查询作为字符串传递。
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. 在 init {} 代码块中,将 MarsApiFilter.SHOW_ALL 作为实参传递给 getMarsRealEstateProperties(),以便在应用首次加载时显示所有属性。
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. 在该类的末尾,添加一个 updateFilter() 方法,该方法接受 MarsApiFilter 实参并使用该实参调用 getMarsRealEstateProperties()。
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

第 3 步:将 fragment 连接到选项菜单

最后一步是将溢出菜单连接到 fragment,以便在用户选择菜单选项时在视图模型上调用 updateFilter()。

  1. 打开 res/menu/overflow_menu.xml。MarsRealEstate 应用有一个现有的溢出菜单,其中提供了三个可用选项:显示所有房产、仅显示出租房产和仅显示待售房产。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@+id/show_all_menu"
       android:title="@string/show_all" />
   <item
       android:id="@+id/show_rent_menu"
       android:title="@string/show_rent" />
   <item
       android:id="@+id/show_buy_menu"
       android:title="@string/show_buy" />
</menu>
  1. 打开 overview/OverviewFragment.kt。在该类的末尾,实现 onOptionsItemSelected() 方法以处理菜单项选择。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. 在 onOptionsItemSelected() 中,使用适当的过滤条件对视图模型调用 updateFilter() 方法。使用 Kotlin when {} 代码块在选项之间切换。使用 MarsApiFilter.SHOW_ALL 作为默认过滤条件值。返回 true,因为您已处理菜单项。在收到请求时,导入 MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter)。完整的 onOptionsItemSelected() 方法如下所示。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   viewModel.updateFilter(
           when (item.itemId) {
               R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
               R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
               else -> MarsApiFilter.SHOW_ALL
           }
   )
   return true
}
  1. 编译并运行应用。应用会启动第一个概览网格,其中包含所有房源类型,并且待售房源会标有美元图标。
  2. 从选项菜单中选择租借。属性会重新加载,并且所有属性都不会显示美元图标。(仅显示出租房源。)您可能需要等待片刻,显示屏才会刷新,仅显示过滤后的媒体资源。
  3. 从选项菜单中选择购买。系统会再次重新加载媒体资源,所有媒体资源都会显示美元图标。(仅显示待售房源。)

现在,您有了一个可滚动的火星房产图标网格,但该添加更多详细信息了。在此任务中,您将添加一个详情 fragment 以显示特定房产的详细信息。详情 fragment 将显示更大的图片、价格和房源类型(出租或出售)。

当用户点按概览网格中的图片时,系统会启动此 fragment。为此,您需要向 RecyclerView 网格项添加 onClick 监听器,然后导航到新 fragment。您可以通过触发 ViewModel 中的 LiveData 变化来进行导航,就像您在这些课程中一直做的那样。您还使用 Navigation 组件的 Safe Args 插件将所选 MarsProperty 信息从概览 fragment 传递到详情 fragment。

第 1 步:创建详情视图模型并更新详情布局

与您之前用于概览视图模型和 fragment 的流程类似,您现在需要为详细信息 fragment 实现视图模型和布局文件。

  1. 打开 detail/DetailViewModel.kt。正如与网络相关的 Kotlin 文件包含在 network 文件夹中,概览文件包含在 overview 中一样,detail 文件夹包含与详情视图关联的文件。请注意,DetailViewModel 类(目前为空)在构造函数中将 marsProperty 作为参数。
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. 在类定义中,为所选的火星房产添加 LiveData,以向详情视图公开相应信息。按照创建 MutableLiveData 来保存 MarsProperty 本身的常规模式,然后公开一个不可变的公共 LiveData 属性。

    在收到请求时,导入 androidx.lifecycle.LiveData 和 androidx.lifecycle.MutableLiveData。
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. 创建一个 init {} 块,并使用构造函数中的 MarsProperty 对象设置所选 Mars 属性的值。
    init {
        _selectedProperty.value = marsProperty
    }
  1. 打开 res/layout/fragment_detail.xml,然后在设计视图中查看。

    这是详情 fragment 的布局文件。它包含一个用于大照片的 ImageView、一个用于房源类型(出租或出售)的 TextView 和一个用于价格的 TextView。请注意,约束布局用 ScrollView 封装起来,这样一来,如果视图过大而无法显示(例如当用户以横屏模式查看时),系统会自动滚动。
  2. 前往相应布局的文字标签页。在布局顶部,紧挨着 <ScrollView> 元素之前,添加一个 <data> 元素,以将详情视图模型与布局相关联。
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. 将 app:imageUrl 属性添加到 ImageView 元素中。将其设置为视图模型所选属性中的 imgSrcUrl。

    使用 Glide 加载图片的绑定适配器也会自动在此处使用,因为该适配器会监控所有 app:imageUrl 属性。
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

第 2 步:在概览视图模型中定义导航

当用户点按概览模型中的照片时,应触发到显示所点击项详情的 fragment 的导航。

  1. 打开 overview/OverviewViewModel.kt。添加 _navigateToSelectedProperty MutableLiveData 属性,并使用不可变的 LiveData 对其进行公开。

    当此 LiveData 更改为非 null 值时,系统会触发导航。(您很快就会添加代码来观察此变量并触发导航。)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. 在该类的末尾,添加一个 displayPropertyDetails() 方法,用于将 _navigateToSelectedProperty 设置为所选的火星房产。
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. 添加一个将 _navigateToSelectedProperty 的值设为 null 的 displayPropertyDetailsComplete() 方法。您需要此参数来标记导航状态为完成,并避免在用户从详情视图返回时再次触发导航。
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

第 3 步:在网格适配器和 fragment 中设置点击监听器

  1. 打开 overview/PhotoGridAdapter.kt。在该类的末尾,创建一个自定义 OnClickListener 类,该类接受带有 marsProperty 参数的 lambda。在该类中,定义一个设置为 lambda 形参的 onClick() 函数。
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. 向上滚动到 PhotoGridAdapter 的类定义,并向构造函数添加一个私有 OnClickListener 属性。
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. 通过在 onBindviewHolder() 方法中向网格项添加 onClickListener,使照片可供点击。在对 getItem() and bind() 的调用之间定义点击监听器。
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. 打开 overview/OverviewFragment.kt。在 onCreateView() 方法中,将初始化 binding.photosGrid.adapter 属性的代码行替换为如下所示的代码行。

    此代码会将 PhotoGridAdapter.onClickListener 对象添加到 PhotoGridAdapter 构造函数,并使用传入的 MarsProperty 对象调用 viewModel.displayPropertyDetails()。这会触发视图模型中用于导航的 LiveData。
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

第 4 步:修改导航图并使 MarsProperty 可 parcelable

当用户点按概览网格中的照片时,应用应导航到详情 fragment,并传递所选火星地产的详细信息,以便详情视图可以显示该信息。

目前,您有一个来自 PhotoGridAdapter 的点击监听器来处理点按操作,以及一种从视图模型触发导航的方法。但您尚未将 MarsProperty 对象传递给详情 fragment。为此,您可以使用 Navigation 组件中的 Safe Args。

  1. 打开 res/navigation/nav_graph.xml。点击 Text 标签页可查看导航图的 XML 代码。
  2. 在详情 fragment 的 <fragment> 元素内,添加如下所示的 <argument> 元素。此实参(称为 selectedProperty)的类型为 MarsProperty。
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. 编译应用。导航会因 MarsProperty 不是 parcelable 而显示错误。借助 Parcelable 接口,对象可以序列化,以便在 fragment 或 activity 之间传递对象的数据。在这种情况下,为了让 MarsProperty 对象中的数据通过 Safe Args 传递到详情 fragment,MarsProperty 必须实现 Parcelable 接口。好消息是,Kotlin 提供了一个简单的快捷方式来实现该接口。
  2. 打开 network/MarsProperty.kt。向类定义添加 @Parcelize 注释。

    在收到请求时,导入 kotlinx.android.parcel.Parcelize。

    @Parcelize 注释使用 Kotlin Android 扩展功能自动实现此类的 Parcelable 接口中的方法。您无需执行任何其他操作!
@Parcelize
data class MarsProperty (
  1. 将 MarsProperty 的类定义更改为扩展 Parcelable。

    在收到请求时,导入 android.os.Parcelable。

    MarsProperty 类定义现在如下所示:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

第 5 步:连接 fragment

您仍然没有进行导航,实际的导航发生在 fragment 中。在此步骤中,您将添加最后几段代码,以实现概览 fragment 和详情 fragment 之间的导航。

  1. 打开 overview/OverviewFragment.kt。在 onCreateView() 中,在初始化照片网格适配器的代码行下方,添加如下所示的代码行,以从概览视图模型中观测 navigatedToSelectedProperty。

    在收到请求时,导入 androidx.lifecycle.Observer 和 androidx.navigation.fragment.findNavController。

    观察者会测试 MarsProperty(即 lambda 中的 it)是否不为 null,如果不为 null,则会通过 findNavController() 从 fragment 获取导航控制器。调用 displayPropertyDetailsComplete() 以告知视图模型将 LiveData 重置为 null 状态,这样当应用返回到 OverviewFragment 时,您就不会意外地再次触发导航。
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. 打开 detail/DetailFragment.kt。在 onCreateView() 方法中,在对 setLifecycleOwner() 的调用下方添加以下代码行。此行代码从 Safe Args 获取所选的 MarsProperty 对象。

    请注意,这里使用了 Kotlin 的非 null 断言运算符 (!!)。如果 selectedProperty 不存在,则表示发生了严重错误,您实际上希望代码抛出 null 指针。(在生产代码中,您应以某种方式处理该错误。)
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. 接下来,添加此行以获取新的 DetailViewModelFactory。您将使用 DetailViewModelFactory 获取 DetailViewModel 的实例。起始应用包含 DetailViewModelFactory 的实现,因此您只需在此处对其进行初始化即可。
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. 最后,添加此行以从工厂获取 DetailViewModel 并连接所有部件。
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. 编译并运行应用,然后点按任意火星资源照片。系统会显示相应房源的详情 fragment。点按“返回”按钮返回到概览页面,并注意详情屏幕仍然有点空。您将在下一个任务中完成向该详情页面添加房源数据的操作。

目前,详情页面仅显示您在概览页面上看到的同一张火星照片。MarsProperty 类还具有房源类型(出租或出售)和房源价格。详情界面应同时包含这两个值,如果出租房源表明价格是每月价格,则会很有帮助。您可以在视图模型中使用 LiveData 转换来实现这两个目标。

  1. 打开 res/values/strings.xml。起始代码包含以下字符串资源,可帮助您为详情视图构建字符串。对于价格,您将使用 display_price_monthly_rental 资源或 display_price 资源,具体取决于属性类型。
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
  1. 打开 detail/DetailViewModel.kt。在该类的底部,添加如下所示的代码。

    如果收到请求,则导入 androidx.lifecycle.Transformations。

    此转换使用第一个任务中的相同测试来测试所选房产是否为出租房产。如果房源是出租房源,转换会通过 Kotlin when {} 开关从资源中选择合适的字符串。这两个字符串都需要在末尾添加一个数字,因此您需要在后面连接 property.price。
val displayPropertyPrice = Transformations.map(selectedProperty) {
   app.applicationContext.getString(
           when (it.isRental) {
               true -> R.string.display_price_monthly_rental
               false -> R.string.display_price
           }, it.price)
}
  1. 导入生成的 R 类,以获取对项目中字符串资源的访问权限。
import com.example.android.marsrealestate.R
  1. 在 displayPropertyPrice 转换之后,添加如下所示的代码。此转换会根据房源类型是否为出租房源来连接多个字符串资源。
val displayPropertyType = Transformations.map(selectedProperty) {
   app.applicationContext.getString(R.string.display_type,
           app.applicationContext.getString(
                   when (it.isRental) {
                       true -> R.string.type_rent
                       false -> R.string.type_sale
                   }))
}
  1. 打开 res/layout/fragment_detail.xml。还剩最后一步,那就是将新字符串(您使用 LiveData 转换创建的字符串)绑定到详情视图。为此,您需要将属性类型文本的文本字段值设置为 viewModel.displayPropertyType,并将价格值文本的文本字段值设置为 viewModel.displayPropertyPrice。
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. 编译并运行应用。现在,所有房源数据都会以适当的格式显示在详情页面上。

Android Studio 项目:MarsRealEstateFinal

绑定表达式

  • 在 XML 布局文件中使用绑定表达式对绑定数据执行简单的程序化操作,例如数学运算或条件测试。
  • 如需在布局文件中引用类,请在 <data> 标记中使用 <import> 标记。

Web 服务查询选项

  • 对 Web 服务的请求可以包含可选参数。
  • 如需在请求中指定查询参数,请在 Retrofit 中使用 @Query 注释。

Udacity 课程:

Android 开发者文档:

其他:

此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:

  • 根据需要布置作业。
  • 告知学生如何提交家庭作业。
  • 给家庭作业评分。

讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。

如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。

回答以下问题

问题 1

XML 布局文件中的 <import> 标记有什么作用?

▢ 将一个布局文件添加到另一个布局文件中。

▢ 在布局文件中嵌入 Kotlin 代码。

▢ 提供对数据绑定属性的访问权限。

▢ 使您能够在绑定表达式中引用类和类成员。

问题 2

如何向 Retrofit 中的 REST Web 服务调用添加查询选项?

▢ 将查询附加到请求网址的末尾。

▢ 将查询的参数添加到发出请求的函数中,并使用 @Query 为该参数添加注解。

▢ 使用 Query 类构建请求。

▢ 使用 Retrofit 构建器中的 addQuery() 方法。

开始学习下一课:9.1:代码库

如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。