想一想手机上常用的应用,您会发现几乎每一个应用都至少有一个列表。通话记录屏幕、“通讯录”应用和您爱用的社交媒体应用都会显示一个数据列表。如下面的屏幕截图所示,这些应用有的只显示了一个简单的单词或词组列表,而有的则显示了更复杂的列表项,例如包含文字和图片的卡片。无论列表的内容是什么,显示数据列表都是 Android 中最常见的界面任务之一。
为了帮助您构建包含列表的应用,Android 提供了 RecyclerView
。RecyclerView
旨在通过重复使用(即循环使用)滚动出屏幕的视图来非常高效地显示列表项,即使是大型列表也可以高效显示。当列表项滚动出屏幕之后,RecyclerView
会重复使用相应视图来显示即将显示的下一个列表项。也就是说,用新内容填充该列表项,而该列表项会再滚动到屏幕上。RecyclerView
的这种行为可以节省大量处理时间,并能让列表滚动更顺畅。
在下图显示的序列中,可以看到一个填充了数据 ABC
的视图。当该视图滚动出屏幕之后,RecyclerView
会重复使用该视图来显示新数据 XYZ
。
在此 Codelab 中,您将构建一个 Affirmations 应用。Affirmations 是一个简单的应用,在滚动列表中以文字形式显示十句积极的自我肯定话语。然后,在后续的 Codelab 中,您将进一步为每句自我肯定话语添加一张励志图片,并完善应用界面。
前提条件
- 在 Android Studio 中,基于模板创建一个项目。
- 将字符串资源添加到应用。
- 在 XML 文件中定义一个布局。
- 了解 Kotlin 中的类和继承(包括抽象类)。
- 继承一个现有类并替换其方法。
- 参阅 developer.android.com 上的文档,了解 Android 框架提供的类。
学习内容
- 如何使用
RecyclerView
显示数据列表? - 如何将代码构成软件包?
- 如何将 Adapter 与
RecyclerView
配合使用以自定义各列表项的外观?
构建内容
- 一个使用
RecyclerView
显示自我肯定话语字符串列表的应用。
所需条件
- 一台安装了 Android Studio 4.1 或更高版本的计算机。
创建空 Activity 项目
创建新项目之前,请确保您使用的是 Android Studio 4.1 或更高版本。
- 在 Android Studio 中,使用 Empty Activity 模板启动一个新的 Kotlin 项目。
- 输入 Affirmations 作为应用的 Name,输入 com.example.affirmations 作为 Package name,然后选择 API Level 19 作为 Minimum SDK。
- 点击 Finish 创建项目。
创建 Affirmations 应用的下一步是添加资源。您需要将以下内容添加到项目中。
- 字符串资源:它们将在应用中显示为自我肯定话语。
- 数据来源:它将向应用提供自我肯定话语的列表。
添加自我肯定话语字符串
- 在 Project 窗口中,依次打开 app > res > values > strings.xml。此文件目前只有一个资源,即应用的名称。
- 在
strings.xml
中,将以下自我肯定话语添加为单独的字符串资源。将其分别命名为affirmation1
、affirmation2
,依此类推。
自我肯定话语的文字稿
I am strong. I believe in myself. Each day is a new opportunity to grow and be a better version of myself. Every challenge in my life is an opportunity to learn from. I have so much to be grateful for. Good things are always coming into my life. New opportunities await me at every turn. I have the courage to follow my heart. Things will unfold at precisely the right time. I will be present in all the moments that this day brings.
完成后,strings.xml
文件应如下所示。
<resources>
<string name="app_name">Affirmations</string>
<string name="affirmation1">I am strong.</string>
<string name="affirmation2">I believe in myself.</string>
<string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
<string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
<string name="affirmation5">I have so much to be grateful for.</string>
<string name="affirmation6">Good things are always coming into my life.</string>
<string name="affirmation7">New opportunities await me at every turn.</string>
<string name="affirmation8">I have the courage to follow my heart.</string>
<string name="affirmation9">Things will unfold at precisely the right time.</string>
<string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>
现在,您已添加了字符串资源,可以在代码中将其引用为 R.string.affirmation1
或 R.string.affirmation2
了。
创建新软件包
按照逻辑组织代码可以帮助您和其他开发者理解、维护和扩展代码。就像您可以将文书整理成文件和文件夹一样,您同样可以将代码组织成文件和软件包。
什么是软件包?
- 在 Android Studio 的 Project 窗口 (Android) 中,查看 app > java 下的 Affirmations 应用的新项目文件。它们应该与下面的屏幕截图相似。图中显示了三个软件包:一个用于您的代码 (com.example.affirmations),另外两个用作测试文件(com.example.affirmations (androidTest) 和 com.example.affirmations (test))。
- 请注意,软件包的名称由几个单词组成,单词间以句点进行分隔。
软件包的使用方式有两种。
- 为代码的不同部分创建不同的软件包。例如,开发者通常会将处理数据的类和构建界面的类分成不同的软件包。
- 在代码中使用来自其他软件包的代码。为了使用来自其他软件包的类,您需要在构建系统的依赖项中定义这些类。此外,使用
import
将其导入代码中也是一种标准做法。如此一来,您就可以使用这些类的简短名称(例如TextView
)而无需使用其完全限定名称(例如android.widget.TextView
)。举例来说,您已对sqrt
(import kotlin.math.sqrt
) 和View
(import android.view.View
) 等类使用过import
语句。
在 Affirmations 应用中,除了导入 Android 类和 Kotlin 类之外,您还需要将应用分为多个软件包。即使应用所用的类并不太多,最好也使用软件包将类按功能分组。
为软件包命名
您可以对软件包随意命名,只要是全局唯一名称即可;任何其他已发布的软件包都不能同名。由于已有软件包的数量非常庞大,要随机想出唯一的名称也非常困难;因此,为了更易于创建和理解软件包名称,编程人员遵循相应的命名惯例。
- 软件包名称通常采用从一般到具体的结构,名称的每个部分均采用小写字母,各部分之间以句点分隔。重要提示:句点只是名称的组成部分。它既不表示代码中的层次结构,也并非一定要遵循某种文件夹结构!
- 由于互联网域名具有全局唯一性,因此使用域名(通常是您的域名或您企业的域名)作为名称的第一个部分是一种惯例。
- 您可以选择软件包的名称,用它来表示软件包的内容以及软件包彼此间的关系。
- 对于我们此处所用的这种代码示例,
com.example
后跟应用名称是常用的命名方式。
以下是一些预定义软件包名称及其内容的示例:
kotlin.math
- 数学函数和常量。android.widget
- 视图,例如TextView
。
创建软件包
- 在 Android Studio 的 Project 窗格中,右键点击 app > java > com.example.affirmations,然后依次选择 New > Package。
- 在 New Package 弹出式窗口中,注意建议的软件包名称前缀。建议的软件包名称的第一个部分是右键点击的软件包的名称。虽然使用软件包名称不会创建一个软件包层次结构,但我们可以通过重复使用一部分名称来说明内容之间的关系及其组织方式!
- 在弹出式窗口中,将 model 附加到建议的软件包名称末尾。开发者通常使用 model 作为对数据建模的类或表示数据的类的软件包名称。
- 按 Enter 键。系统将在 com.example.affirmations(根)软件包下创建一个新软件包。这个新软件包将包含应用中定义的任何与数据相关的类。
创建 Affirmation 数据类
在此任务中,您将创建一个名为 Affirmation.
的类。一个 Affirmation
对象实例代表一句自我肯定话语,并包含这句自我肯定话语字符串的资源 ID。
- 右键点击 com.example.affirmations.model 软件包,然后依次选择 New > Kotlin File/Class。
- 在弹出式窗口中,选择 Class,然后输入
Affirmation
作为类的名称。系统将在model
软件包中创建一个名为Affirmation.kt
的新文件。 - 在类定义前添加
data
关键字,使Affirmation
成为数据类。这样做会出错,因为数据类必须至少定义一个属性。
Affirmation.kt
package com.example.affirmations.model
data class Affirmation {
}
在创建 Affirmation
的实例时,您需要传入自我肯定话语字符串的资源 ID。资源 ID 是一个整数。
- 将
val
整数参数stringResourceId
添加到Affirmation
类的构造函数中。这样就可以消除错误了。
package com.example.affirmations.model
data class Affirmation(val stringResourceId: Int)
创建要作为数据源的类
应用中显示的数据可能来自不同的来源(例如,来自应用项目内,或者来自需要连接到互联网才能下载数据的外部来源)。因此,数据格式未必正好是您需要的格式。应用的其余部分本身不应该关心数据的来源或数据的原始格式。您可以也应该在单独的 Datasource
类中隐藏此数据准备工作,由该类来为应用准备数据。
准备数据是一项需要单独处理的工作,因此请将 Datasource
类放在单独的 data 软件包中。
- 在 Android Studio 的 Project 窗口中,右键点击 app > java > com.example.affirmations,然后依次选择 New > Package。
- 输入
data
作为软件包名称的最后一部分。 - 右键点击
data
软件包,然后选择 new Kotlin File/Class。 - 输入
Datasource
作为类名称。 - 在
Datasource
类中,创建一个名为loadAffirmations()
的函数。
loadAffirmations()
函数需要返回 Affirmations
的列表。为此,您可以创建一个列表,并使用每个资源字符串的 Affirmation
实例填充该列表。
- 将
List<Affirmation>
声明为loadAffirmations()
方法的返回值类型。 - 在
loadAffirmations()
的主体中,添加return
语句。 - 在
return
关键字之后,调用listOf<>()
创建一个List
。 - 在尖括号
<>
内,将列表项的类型指定为Affirmation
。根据需要导入com.example.affirmations.model.Affirmation
。 - 在圆括号内创建
Affirmation
,传入R.string.affirmation1
作为资源 ID,如下所示。
Affirmation(R.string.affirmation1)
- 将其余
Affirmation
对象添加到所有自我肯定话语的列表,以英文逗号分隔。完成后的代码应如下所示。
Datasource.kt
package com.example.affirmations.data
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
class Datasource {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1),
Affirmation(R.string.affirmation2),
Affirmation(R.string.affirmation3),
Affirmation(R.string.affirmation4),
Affirmation(R.string.affirmation5),
Affirmation(R.string.affirmation6),
Affirmation(R.string.affirmation7),
Affirmation(R.string.affirmation8),
Affirmation(R.string.affirmation9),
Affirmation(R.string.affirmation10)
)
}
}
[可选] 在 TextView 中显示自我肯定话语列表的大小
如需验证您能否创建自我肯定话语列表,您可以调用 loadAffirmations()
并在 Empty Activity 应用模板附带的 TextView
中显示返回的自我肯定话语列表的大小。
- 在
layouts/activity_main.xml
中,为模板附带的TextView
指定textview
作为id
。 - 在
MainActivity
中,在onCreate()
方法中的现有代码之后,获取对textview
的引用。
val textView: TextView = findViewById(R.id.textview)
- 然后,添加代码以创建自我肯定话语列表并显示其大小。创建一个
Datasource
,调用loadAffirmations()
,获取返回的列表的大小并将其转换为字符串,然后将其指定为textView
的text
。
textView.text = Datasource().loadAffirmations().size.toString()
- 运行应用。屏幕应如下所示。
- 删除您刚在
MainActivity
中添加的代码。
在此任务中,您将设置一个 RecyclerView
来显示 Affirmations
的列表。
创建和使用 RecyclerView
需要编写许多代码段。您可以将其想象成劳动分工。下图所示为其概览,至于每段代码的详情,则有待您在实现过程中逐一了解。
- 列表项 - 要显示的列表的一个数据项。代表应用中的一个
Affirmation
对象。 - Adapter - 获取数据并准备数据以供
RecyclerView
显示。 - ViewHolder - 供
RecyclerView
用于和重用于显示自我肯定话语的视图池。 - RecyclerView - 屏幕上的视图。
将 RecyclerView 添加到布局中
Affirmations 应用由一个名为 MainActivity
的 activity 及其名为 activity_main
.xml
的布局文件组成。首先,您需要将 RecyclerView
添加到 MainActivity
的布局中。
- 打开
activity_main.xml
(app > res > layout > activity_main.xml)。 - 切换到 Split 视图(如果您尚未使用该视图)。
- 删除
TextView
。
当前布局使用的是 ConstraintLayout
。如果想在布局中放置多个子视图,那么 ConstraintLayout
就是理想而又灵活的选择。不过,因为您的布局只有一个子视图 RecyclerView
,所以您可以切换到更简单的名为 FrameLayout
的 ViewGroup
,这才是只包含一个子视图时应该使用的选择。
- 在 XML 文件中,将
ConstraintLayout
替换为FrameLayout
。完成后的布局应如下所示。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</FrameLayout>
- 切换到 Design 视图。
- 在 Palette 中,选择 Containers,然后找到 RecyclerView。
- 将 RecyclerView 拖放到布局中。
- 如果系统显示 Add Project Dependency 弹出式窗口,请阅读里面的内容并点击 OK。(如果未显示此弹出式窗口,无需执行任何操作。)
- 等待 Android Studio 完成操作并且
RecyclerView
显示在布局中。 - 根据需要将
RecyclerView
的layout_width
和layout_height
属性更改为match_parent
,以便RecyclerView
能够填满整个屏幕。 - 将
RecyclerView
的资源 ID 设为recycler_view
。
RecyclerView
支持以线性列表或网格等不同方式显示列表项。列表项的排列由 LayoutManager
处理。Android 框架针对基本的列表项布局提供了布局管理器。Affirmations 应用以垂直列表的形式显示列表项,因此您可以使用 LinearLayoutManager
。
- 切换回 Code 视图。在 XML 代码中的
RecyclerView
元素内,添加LinearLayoutManager
作为RecyclerView
的布局管理器属性,如下所示。
app:layoutManager="LinearLayoutManager"
为了能够滚动浏览超出屏幕长度的垂直项目列表,您需要添加一个垂直滚动条。
- 在
RecyclerView
内,添加设为vertical
的android:scrollbars
属性。
android:scrollbars="vertical"
最终的 XML 布局应如下所示:
activity_main.xml
<FrameLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />
</FrameLayout>
- 运行应用。
项目应能不出任何问题地顺利编译和运行。不过,由于缺少一段重要代码,应用中只会显示白色背景。现在,您已将数据源和 RecyclerView
添加到布局中,但 RecyclerView
尚无有关如何显示 Affirmation
对象的信息。
为 RecyclerView 实现 Adapter
应用需要通过某种方式从 Datasource
获取数据并为其设置格式,这样才能让每个 Affirmation
在 RecyclerView
中显示为一个列表项。
Adapter 是一种设计模式,可将数据调整为可供 RecyclerView
使用的内容。在本例中,您需要一个能完成以下任务的 Adapter:从 loadAffirmations()
返回的列表中获取 Affirmation
实例并将其转换为列表项视图,以便在 RecyclerView.
中显示它。
当您运行应用时,RecyclerView
会使用 Adapter 确定如何在屏幕上显示您的数据。RecyclerView
要求 Adapter 为列表中的第一个数据项创建新的列表项视图。有了此视图后,它会要求 Adapter 提供用于绘制该列表项的数据。此过程不断重复,直到 RecyclerView
不再需要更多视图来填满屏幕。如果屏幕上一次只能同时容纳 3 个列表项视图,那么 RecyclerView
只会要求 Adapter 准备这 3 个列表项视图(而不是全部 10 个列表项视图)。
在此步骤中,您将构建一个 Adapter,用于调整 Affirmation
对象实例,使其可在 RecyclerView
中显示。
创建 Adapter
Adapter 包含多个部分,您需要编写大量代码,而且这些代码比您在本课程中迄今为止完成的代码更加复杂。如果您刚开始无法完全理解细节也没关系。当您完成使用 RecyclerView
的整个应用后,您就能更深入地了解各部分如何组合到一起了。今后,当您使用 RecyclerView
创建应用时,您还可以重复使用此代码作为基础。
为列表项创建布局
RecyclerView
中的每个列表项都有自己的布局,您可以在单独的布局文件中定义这些布局。由于您只需要显示字符串,因此可将 TextView
用于列表项布局。
- 在 res > layout 中,新建名为
list_item.xml
的空 File。 - 在 Code 视图中打开
list_item.xml
。 - 添加
id
为item_title
的TextView
。 - 针对
layout_width
和layout_height
添加wrap_content
,如下面的代码所示。
请注意,您不需要对布局使用 ViewGroup
,因为稍后我们会膨胀此列表项布局并将其作为子视图添加到父 RecyclerView
中。
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
或者,您也可以使用 File > New > Layout Resource File,将 File name 设为 list_item.xml
,并以 TextView
作为 Root element。然后,更新生成的代码以匹配上面的代码。
创建 ItemAdapter 类
- 在 Android Studio 的 Project 窗格中,右键点击 app > java > com.example.affirmations,然后依次选择 New > Package。
- 输入
adapter
作为软件包名称的最后一部分。 - 右键点击
adapter
软件包,然后依次选择 New > Kotlin File/Class。 - 输入
ItemAdapter
作为类名称,完成创建操作,系统将打开ItemAdapter.kt
文件。
您需要将一个参数添加到 ItemAdapter
的构造函数中,以便将自我肯定话语列表传入 Adapter。
- 将一个参数添加到
ItemAdapter
的构造函数中,该参数是名为dataset
、类型为List<Affirmation>
的val
。根据需要导入Affirmation
。 - 由于
dataset
仅在此类中使用,因此请将其设为private
。
ItemAdapter.kt
import com.example.affirmations.model.Affirmation
class ItemAdapter(private val dataset: List<Affirmation>) {
}
ItemAdapter
需要有关如何解析字符串资源的信息。这些信息与有关应用的其他信息一起存储在 Context
对象实例中,您可将此实例传入 ItemAdapter
实例。
- 将一个参数添加到
ItemAdapter
的构造函数中,该参数是名为context
、类型为Context
的val
。请将其作为构造函数中的第一个参数。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
}
创建 ViewHolder
RecyclerView
不会直接与列表项视图交互,而是处理 ViewHolders
。一个 ViewHolder
代表 RecyclerView
中的一个列表项视图,可根据需要对其重复使用。ViewHolder
实例存储着对列表项布局中各个视图的引用(因此被命名为“view holder”,意思是视图存储器)。这样,用新数据更新列表项视图就会更轻松。ViewHolder 还会添加一些信息,供 RecyclerView
用于在屏幕上高效移动视图。
- 在
ItemAdapter
类中,在ItemAdapter
的右花括号之前,创建一个ItemViewHolder
类。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
class ItemViewHolder()
}
- 在一个类中定义另一个类称为创建嵌套类。
- 由于
ItemViewHolder
仅由ItemAdapter
使用,因此在ItemAdapter
内创建它可以显示此关系。这并非强制性要求,但这样做有助于其他开发者了解您的程序的结构。
- 将类型为
View
的private
val
view
作为参数添加到ItemViewHolder
类构造函数。 - 将
ItemViewHolder
设为RecyclerView
.ViewHolder
的子类,并将view
参数传入父类构造函数。 - 在
ItemViewHolder
内,定义类型为TextView
的val
属性textView
。为其分配您在list_item
.xml
中定义的 ID 为item_title
的视图。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
}
替换 Adapter 方法
- 添加代码以从抽象类
RecyclerView.Adapter
扩展您的ItemAdapter
。在尖括号中,将ItemAdapter.ItemViewHolder
指定为 ViewHolder 类型。
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
}
您会看到一条错误消息,因为您需要从 RecyclerView.Adapter
实现一些抽象方法。
- 将光标放在
ItemAdapter
上,然后按 Command+I(在 Windows 上,则请按 Control+I)。系统将列出您需要实现的方法:getItemCount()
、onCreateViewHolder()
和onBindViewHolder()
。
- 使用 Shift+click 将三个函数全部选中,然后点击 OK。
这样就会使用正确的参数创建这三个方法的桩,如下所示。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
TODO("Not yet implemented")
}
您应该不会再看到错误消息。接下来,您需要实现这些方法,使其能对您的应用正确发挥作用。
实现 getItemCount()
getItemCount()
方法需要返回数据集的大小。应用的数据位于您传入 ItemAdapter
构造函数的 dataset
属性中,您可以使用 size
获取其大小。
- 将
getItemCount()
替换为:
override fun getItemCount() = dataset.size
下面是更简洁的编写方式:
override fun getItemCount(): Int {
return dataset.size
}
实现 onCreateViewHolder()
onCreateViewHolder()
方法由布局管理器调用,用于为 RecyclerView
创建新的 ViewHolder(如果不存在可重复使用的 ViewHolder)。请注意,一个 ViewHolder 代表一个列表项视图。
onCreateViewHolder()
方法接受两个参数并返回一个新的 ViewHolder
。
parent
参数,即新列表项视图将作为子视图附加到的视图组。父项是RecyclerView
。viewType
参数,当同一个RecyclerView
中存在多种列表项视图类型时,此参数可发挥重要作用。如果在RecyclerView
内显示不同的列表项布局,就会有不同的列表项视图类型。您只能循环使用列表项视图类型相同的视图。针对您的情况而言,只有一个列表项布局和一个列表项视图类型,因此您不必担心此参数。
- 在
onCreateViewHolder()
方法中,从提供的上下文(parent
的context
)中获取LayoutInflater
的实例。布局膨胀器知道如何将 XML 布局膨胀为视图对象的层次结构。
val adapterLayout = LayoutInflater.from(parent.context)
- 获取
LayoutInflater
对象实例后,添加一个句点,后跟另一个方法调用以膨胀实际的列表项视图。传入 XML 布局资源 IDR.layout.list_item
和parent
视图组。第三个布尔值参数是attachToRoot
。此参数需为false
,因为RecyclerView
会在需要的时候替您将此列表项添加到视图层次结构中。
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
现在,adapterLayout
存储着对列表项视图的引用(稍后我们可从中找到
子视图,例如 TextView
)。
- 在
onCreateViewHolder()
中,返回根视图为adapterLayout
的新ItemViewHolder
实例。
return ItemViewHolder(adapterLayout)
以下就是截至目前为止 onCreateViewHolder()
的代码。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
实现 onBindViewHolder()
需要替换的最后一个方法是 onBindViewHolder()
。此方法由布局管理器调用,以便替换列表项视图的内容。
onBindViewHolder()
方法有两个参数:其一是先前由 onCreateViewHolder()
方法创建的 ItemViewHolder
;其二是一个 int
,它代表当前列表项的 position
。在此方法中,您将根据位置从数据集中找到正确的 Affirmation
对象。
- 在
onBindViewHolder()
内,创建一个val
item
并获取dataset
中位于给定position
的列表项。
val item = dataset[position]
最后,您需要更新 ViewHolder 引用的所有视图,以便反映此列表项的正确数据。在本例中,只有一个视图:ItemViewHolder
内的 TextView
。设置 TextView
的文本以显示此列表项的 Affirmation
字符串。
- 有了
Affirmation
对象实例,您可以通过调用item.stringResourceId
查找相应的字符串资源 ID。不过,这是一个整数,您需要查找它与实际字符串值的映射。
在 Android 框架中,您可以使用字符串资源 ID 调用 getString()
,它会返回与其关联的字符串值。getString()
是 Resources
类中的一个方法,您可以通过 context
获取 Resources
类的实例。
这意味着,您可以调用 context.resources.getString()
并传入字符串资源 ID。返回的字符串可在 holder
ItemViewHolder
中设为 textView
的 text
。简言之,这一行代码会更新 ViewHolder 以显示这句自我肯定话语字符串。
holder.textView.text = context.resources.getString(item.stringResourceId)
完成后的 onBindViewHolder()
方法应如下所示。
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
以下是完成后的 Adapter 代码。
ItemAdapter.kt
package com.example.affirmations.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
/**
* Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
*/
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just an Affirmation object.
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
}
现在,您已实现了 ItemAdapter
,接下来您需要指示 RecyclerView
使用此 Adapter。
修改 MainActivity 以使用 RecyclerView
最后,您需要使用 Datasource
和 ItemAdapter
类在 RecyclerView
中创建和显示列表项。您可以在 MainActivity
中执行此操作。
- 打开
MainActivity.kt
。 - 在
MainActivity,
中,转到onCreate()
方法。在调用setContentView(R.layout.activity_main).
之后,插入以下步骤中所述的新代码。 - 创建
Datasource
的实例,并对其调用loadAffirmations()
方法。将返回的自我肯定话语列表存储在名为myDataset
的val
中。
val myDataset = Datasource().loadAffirmations()
- 创建一个名为
recyclerView
的变量,并使用findViewById()
查找对布局中RecyclerView
的引用。
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
- 为了指示
recyclerView
使用您创建的ItemAdapter
类,请创建一个新的ItemAdapter
实例。ItemAdapter
应该有两个参数:此 activity 的上下文 (this
) 和myDataset
中的自我肯定话语。 - 将
ItemAdapter
对象分配给recyclerView
的adapter
属性。
recyclerView.adapter = ItemAdapter(this, myDataset)
- 由于 activity 布局中的
RecyclerView
布局大小是固定的,因此您可以将RecyclerView
的setHasFixedSize
参数设为true
。只有在提高性能时才需要使用此设置。如果您知道内容的更改不会更改RecyclerView
的布局大小,请使用此设置。
recyclerView.setHasFixedSize(true)
- 完成后,
MainActivity
的代码应与下面的代码类似。
MainActivity.kt
package com.example.affirmations
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize data.
val myDataset = Datasource().loadAffirmations()
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = ItemAdapter(this, myDataset)
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
}
}
- 运行应用。屏幕上应显示自我肯定话语字符串的列表。
祝贺您!您刚刚创建了一个使用 RecyclerView
和自定义 Adapter 显示数据列表的应用。请花些时间检查您创建的代码,了解不同组件能否协同工作。
此应用已有显示自我肯定话语所需的全部组件,但还不能说已完全准备好投入使用。界面还可以稍加改进。在下一个 Codelab 中,您将改进代码、学习如何在应用中添加图像并完善界面。
此 Codelab 的解决方案代码位于下方所示的项目和模块中。请注意,有些 Kotlin 文件位于不同的软件包中,如文件开头的 package
语句所示。
res/values/strings.xml
<resources>
<string name="app_name">Affirmations</string>
<string name="affirmation1">I am strong.</string>
<string name="affirmation2">I believe in myself.</string>
<string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
<string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
<string name="affirmation5">I have so much to be grateful for.</string>
<string name="affirmation6">Good things are always coming into my life.</string>
<string name="affirmation7">New opportunities await me at every turn.</string>
<string name="affirmation8">I have the courage to follow my heart.</string>
<string name="affirmation9">Things will unfold at precisely the right time.</string>
<string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>
affirmations/data/Datasource.kt
package com.example.affirmations.data
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
class Datasource {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1),
Affirmation(R.string.affirmation2),
Affirmation(R.string.affirmation3),
Affirmation(R.string.affirmation4),
Affirmation(R.string.affirmation5),
Affirmation(R.string.affirmation6),
Affirmation(R.string.affirmation7),
Affirmation(R.string.affirmation8),
Affirmation(R.string.affirmation9),
Affirmation(R.string.affirmation10)
)
}
}
affirmations/model/Affirmation.kt
package com.example.affirmations.model
data class Affirmation(val stringResourceId: Int)
affirmations/MainActivty.kt
package com.example.affirmations
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize data.
val myDataset = Datasource().loadAffirmations()
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = ItemAdapter(this, myDataset)
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
}
}
affirmations/adapter/ItemAdapter.kt
package com.example.affirmations.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
/**
* Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
*/
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just an Affirmation object.
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
}
src/main/res/layout/activty_main.xml
<FrameLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />
</FrameLayout>
src/main/res/layout/list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
RecyclerView
微件可帮助您显示数据列表。RecyclerView
使用 Adapter 模式来调整和显示数据。ViewHolder
为RecyclerView
创建并存储视图。RecyclerView
附带内置LayoutManagers
。RecyclerView
将列表项的布局工作委托给LayoutManagers
。
为实现 Adapter,请执行以下操作:
- 为 Adapter 创建一个新类,例如
ItemAdapter
。 - 创建用于代表单个列表项视图的自定义
ViewHolder
类。从RecyclerView.ViewHolder
类进行扩展。 - 修改
ItemAdapter
类,以便从RecyclerView
.Adapter
类通过自定义ViewHolder
类进行扩展。 - 在 Adapter 内实现以下方法:
getItemsCount()
、onCreateViewHolder()
和onBindViewHolder()
。