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 编码的数据,并使用 RetrofitMoshi 库将该数据解析为 Kotlin 对象。

学习内容

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

您应执行的操作

  • 修改 MarsRealEstate 应用,用美元符号标记待售的火星属性(而非出租属性)。
  • 使用概览页面上的选项菜单创建按类型过滤火星属性的网络服务请求。
  • 为火星属性创建详情 Fragment,使用导航将该 Fragment 连接到概览网格,然后将属性数据传递到该 Fragment。

在此 Codelab(以及相关 Codelab)中,您将使用名为 MarsRealEstate 的应用,该应用会显示火星上待售的属性。此应用会连接到互联网服务器,以检索和显示房产数据,包括价格以及房产可供销售或出租等详细信息。代表每个物业的图片都是通过 NASA 的火星探测器拍摄的真实照片。在之前的 Codelab 中,您创建了包含所有属性照片的采用网格布局的 RecyclerView

在此版本的应用中,您可以使用房源类型(租赁与购买),并向网格布局添加一个图标来标记待售房源:

您可以修改应用的选项菜单,以过滤网格,使系统仅显示出租或出售的属性:

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

到目前为止,您使用的火星属性数据的唯一部分就是属性图片的网址。但是,您在 MarsProperty 类中定义的媒体资源数据还包含 ID、价格和类型(租借或出售)。如需刷新您的内存,您可以从网络服务中获取一段 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 类用于定义网络服务提供的每个属性的数据结构。在上一个 Codelab 中,您使用 Moshi 库将火星网络服务的原始 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.GONEView.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> 元素。请使用如下所示的定义。该图片显示在网格项的右下角,位于火星图片之上,使用 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 的类型,并仅显示匹配的属性。不过,实际的火星网络服务具有查询参数或选项(称为 filter),可让您仅获取 rentbuy 类型的属性。您可以在浏览器中使用带有 realestate 网络服务网址的此过滤条件查询,如下所示:

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

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

第 1 步:更新 Mars API 服务

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

  1. 打开 network/MarsApiService.kt。在 import 语句下方,创建一个名为 MarsApiFilterenum 来定义与网络服务所需的查询值匹配的常量。
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)使用过滤器选项发出网络服务请求。每次调用 getProperties() 时,请求网址都会包含 ?filter=type 部分,此部分会指示网络服务在响应时返回符合该查询的结果。
fun getProperties(@Query("filter") type: String):  

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

您可以在 OverviewViewModelgetMarsRealEstateProperties() 方法中从 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 对象设置所选火星属性的值。
    init {
        _selectedProperty.value = marsProperty
    }
  1. 打开 res/layout/fragment_detail.xml 并在设计视图中查看它。

    这是详情 fragment 的布局文件。其中 ImageView 用于大照片,TextView 用于房源类型(租赁或销售),而 TextView 用于价格。请注意,约束布局使用 ScrollView 封装,因此,如果视图对于显示屏来说过大(例如,当用户在横屏模式下查看它),它会自动滚动。
  2. 转到该布局的 Text 标签页。在布局顶部 <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. 添加一个 displayPropertyDetailsComplete() 方法,该方法将 _navigateToSelectedProperty 的值设为 null。您需要此元素可将导航状态标记为“完成”,并避免当用户从详情视图返回时再次触发导航。
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

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

  1. 打开 overview/PhotoGridAdapter.kt。在该类的末尾,创建一个接受带 marsProperty 参数的 lambda 的自定义 OnClickListener 类。在类内,定义一个设为 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. 通过将 onClickListener 添加到 onBindviewHolder() 方法中的网格项,使照片可点击。定义对 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 可打包

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

现在,您有来自 PhotoGridAdapter 的点击监听器来处理点按,并且有一种方法可以从视图模型触发导航。但是,您还没有向详情 Fragment 传递 MarsProperty 对象。为此,您可以使用导航组件中的 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. 编译应用。Navigation 会为您提供错误,因为 MarsProperty 不是 parcelableParcelable 接口可对对象进行序列化处理,以便在 fragment 或 activity 之间传递对象数据。在这种情况下,为了通过 Safe Args 将 MarsProperty 对象中的数据传递给详情 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 之间实现导航的最后一位。

  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. 编译并运行应用,然后点按任何火星属性照片。系统会显示相应资源的详细信息的详情片段。点按“返回”按钮返回到概览页面,您会注意到,详情屏幕仍然没有这么稀疏。您将在下一任务中将媒体资源数据添加到该详情页面。

目前,详情页面只会显示您在概览页面上看到的同一张火星照片。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> 标记。

网络服务查询选项

  • 向网络服务发送的请求可包含可选参数。
  • 如需在请求中指定查询参数,请在 Retrofit 中使用 @Query 注解。

Udacity 课程:

Android 开发者文档:

其他:

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

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

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

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

回答以下问题

问题 1

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

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

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

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

▢ 允许您在绑定表达式中引用类和类成员。

问题 2

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

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

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

▢ 使用 Query 类构建请求。

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

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

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