使用 RecyclerView 创建动态列表 Android Jetpack 的一部分。
RecyclerView 可以轻松有效地显示大量数据。您提供数据并定义每个项目的外观,然后 RecyclerView 库会在需要时动态创建元素。
顾名思义,RecyclerView 会回收这些单个元素。当某个项目滚动出屏幕时,RecyclerView 不会销毁其 View。相反,RecyclerView 会将其 View 用于已滚动到屏幕上的新项目。RecyclerView 可提高性能和应用的响应速度,并降低电量消耗。
关键类
几个类协同工作来构建您的动态列表。
RecyclerView
是包含与数据对应的 View 的ViewGroup
。它本身就是一个 View,因此您可以像添加任何其他界面元素一样将RecyclerView
添加到布局中。列表中的每个单个元素都由一个 view holder 对象定义。创建 view holder 时,它没有关联的数据。创建 view holder 后,
RecyclerView
会将其与其数据绑定。您可以通过扩展RecyclerView.ViewHolder
来定义 view holder。RecyclerView
通过调用 adapter 中的方法来请求 View 并将 View 与其数据绑定。您可以通过扩展RecyclerView.Adapter
来定义 adapter。layout manager 会排列列表中的单个元素。您可以使用 RecyclerView 库提供的其中一个 layout manager,也可以自行定义。所有 layout manager 都基于该库的
LayoutManager
抽象类。
您可以在 RecyclerView 示例应用 (Kotlin) 或 RecyclerView 示例应用 (Java) 中查看所有组件如何协同工作。
实现 RecyclerView 的步骤
如果要使用 RecyclerView,需要执行以下几项操作。下文将详细介绍这些操作。
决定列表或网格的外观。通常情况下,您可以使用 RecyclerView 库的标准 layout manager 之一。
设计列表中每个元素的外观和行为。根据此设计,扩展
ViewHolder
类。您的ViewHolder
版本提供了列表项的所有功能。您的 view holder 是View
的一个包装器,该 view 由RecyclerView
管理。定义将数据与
ViewHolder
View 相关联的Adapter
。
还有高级自定义选项,可让您根据您的确切需求自定义 RecyclerView。
规划布局
RecyclerView 中的项目由 LayoutManager
类排列。RecyclerView 库提供三个 layout manager,可处理最常见的布局情况:
LinearLayoutManager
以一维列表形式排列项目。GridLayoutManager
以二维网格形式排列项目- 如果网格是垂直排列的,
GridLayoutManager
会尝试让每行中的所有元素的宽度和高度相同,但不同的行可以有不同的高度。 - 如果网格是水平排列的,
GridLayoutManager
会尝试让每列中的所有元素的宽度和高度相同,但不同的列可以有不同的宽度。
- 如果网格是垂直排列的,
StaggeredGridLayoutManager
与GridLayoutManager
类似,但它不要求同一行中的项目具有相同的高度(对于垂直网格),也不要求同一列中的项目具有相同的宽度(对于水平网格)。结果是,行或列中的项目可能彼此偏移。
您还需要设计单个项目的布局。在设计 view holder 时,您需要用到此布局,详见下一部分。
实现 adapter 和 view holder
确定布局后,需要实现 Adapter
和 ViewHolder
。这两个类协同工作,共同定义数据的显示方式。ViewHolder
是 View
的一个包装器,包含列表中单个项目的布局。Adapter
会根据需要创建 ViewHolder
对象,并设置这些 View 的数据。将 View 与其数据相关联的过程称为绑定。
定义 adapter 时,需要替换(override)三个关键方法:
onCreateViewHolder()
:RecyclerView
在需要创建新的ViewHolder
时会调用此方法。此方法会创建并初始化ViewHolder
及其关联的View
,但不会填充 View 的内容,因为ViewHolder
尚未绑定到特定数据。onBindViewHolder()
:RecyclerView
会调用此方法,将ViewHolder
与数据相关联。此方法会获取相应的数据,并使用数据填充 view holder 的布局。例如,如果RecyclerView
显示姓名列表,此方法可以在列表中找到相应的姓名,然后填充 view holder 的TextView
微件。getItemCount()
:RecyclerView
会调用此方法以获取数据集的大小。例如,在通讯录应用中,这可能是地址总数。RecyclerView 会使用此信息来确定何时没有更多项目可以显示。
下面是一个典型的简单 adapter 示例,其中包含一个嵌套的 ViewHolder
,用于显示数据列表。在此示例中,RecyclerView 会显示一个简单的文本元素列表。adapter 会传递一个字符串数组,其中包含 ViewHolder
元素的文本。
Kotlin
class CustomAdapter(private val dataSet: Array<String>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() { /** * Provide a reference to the type of views that you are using * (custom ViewHolder) */ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val textView: TextView init { // Define click listener for the ViewHolder's View textView = view.findViewById(R.id.textView) } } // Create new views (invoked by the layout manager) override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { // Create a new view, which defines the UI of the list item val view = LayoutInflater.from(viewGroup.context) .inflate(R.layout.text_row_item, viewGroup, false) return ViewHolder(view) } // Replace the contents of a view (invoked by the layout manager) override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { // Get element from your dataset at this position and replace the // contents of the view with that element viewHolder.textView.text = dataSet[position] } // Return the size of your dataset (invoked by the layout manager) override fun getItemCount() = dataSet.size }
Java
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> { private String[] localDataSet; /** * Provide a reference to the type of views that you are using * (custom ViewHolder) */ public static class ViewHolder extends RecyclerView.ViewHolder { private final TextView textView; public ViewHolder(View view) { super(view); // Define click listener for the ViewHolder's View textView = (TextView) view.findViewById(R.id.textView); } public TextView getTextView() { return textView; } } /** * Initialize the dataset of the Adapter * * @param dataSet String[] containing the data to populate views to be used * by RecyclerView */ public CustomAdapter(String[] dataSet) { localDataSet = dataSet; } // Create new views (invoked by the layout manager) @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { // Create a new view, which defines the UI of the list item View view = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.text_row_item, viewGroup, false); return new ViewHolder(view); } // Replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(ViewHolder viewHolder, final int position) { // Get element from your dataset at this position and replace the // contents of the view with that element viewHolder.getTextView().setText(localDataSet[position]); } // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { return localDataSet.length; } }
每个 View 项的布局通常在 XML 布局文件中定义。在此示例中,应用有一个名为 text_row_item.xml
的文件,如下所示
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/list_item_height"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
android:gravity="center_vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/element_text"/>
</FrameLayout>
下一步
以下代码段展示了如何使用 RecyclerView
。
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dataset = arrayOf("January", "February", "March") val customAdapter = CustomAdapter(dataset) val recyclerView: RecyclerView = findViewById(R.id.recycler_view) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = customAdapter } }
Java
RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.layoutManager = new LinearLayoutManager(this) recyclerView.setAdapter(customAdapter);
该库还提供了许多自定义实现方式。如需了解详情,请参阅高级 RecyclerView 自定义。
启用全屏显示
按照以下步骤为 RecyclerView
启用全屏显示:
- 通过调用
enableEdgeToEdge()
设置向后兼容的全屏显示。 - 如果列表项最初与系统栏重叠,则在
RecyclerView
上应用内边距。您可以通过将android:fitsSystemWindows
设置为true
或使用ViewCompat.setOnApplyWindowInsetsListener
来实现。 - 通过将
RecyclerView
上的android:clipToPadding
设置为false
,允许列表项在滚动时绘制在系统栏下方。
以下视频显示了未启用(左侧)和已启用(右侧)全屏显示的 RecyclerView
内边距代码示例
Kotlin
ViewCompat.setOnApplyWindowInsetsListener( findViewById(R.id.my_recycler_view) ) { v, insets -> val innerPadding = insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() // If using EditText, also add // "or WindowInsetsCompat.Type.ime()" to // maintain focus when opening the IME ) v.setPadding( innerPadding.left, innerPadding.top, innerPadding.right, innerPadding.bottom) insets }
Java
ViewCompat.setOnApplyWindowInsetsListener( activity.findViewById(R.id.my_recycler_view), (v, insets) -> { Insets innerPadding = insets.getInsets( WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() // If using EditText, also add // "| WindowInsetsCompat.Type.ime()" to // maintain focus when opening the IME ); v.setPadding( innerPadding.left, innerPadding.top, innerPadding.right, innerPadding.bottom ); return insets; } );
RecyclerView
XML
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
其他资源
如需了解更多 Android 测试相关信息,请参阅以下资源。