Android 11 开发者预览版现已推出;快来测试并分享您的反馈吧

使用 RecyclerView 创建列表   Android Jetpack 的一部分。

如果您的应用需要根据大型数据集(或频繁更改的数据)显示元素的滚动列表,您应使用本页所述的 RecyclerView

提示:首先可以在 Android Studio 中依次点击 File > New > Fragment > Fragment (List),获取一些模板代码。然后只需将该 Fragment 添加到您的 Activity 布局即可。

图 1. 使用了 RecyclerView 的列表

图 2. 还使用了 CardView 的列表

如果您想使用卡片创建列表,如图 2 所示,还要使用创建卡片式布局中所述的 CardView 微件。

要查看 RecyclerView 的一些示例代码,请查看 RecyclerView 示例应用Java | Kotlin

RecyclerView 概览

RecyclerView 微件是 ListView 的更高级、灵活版本。

RecyclerView 模型中,有几个不同的组件协同作用来显示您的数据。界面的总体容器是您添加到布局中的 RecyclerView 对象。该 RecyclerView 会用您提供的布局管理器中的视图来填充。您可以使用我们的某个标准布局管理器(例如 LinearLayoutManagerGridLayoutManager),也可以实现自己的布局管理器。

列表中的视图由“视图持有者”对象表示。这些对象是您通过扩展 RecyclerView.ViewHolder 定义的类的实例。每个视图持有者负责显示一个带有视图的项。例如,如果您的列表显示音乐收藏,则每个视图持有者都可能代表一张专辑。RecyclerView 仅会创建所需数量的视图持有者来显示屏幕上的动态内容部分,以及一些 extra。当用户滚动浏览列表时,RecyclerView 会接收屏幕外的视图并将其重新绑定到滚动显示到屏幕上的数据。

视图持有者对象由适配器管理,您可以通过扩展 RecyclerView.Adapter 创建适配器。适配器会根据需要创建视图持有者,还会将视图持有者绑定到相应数据。具体的操作是将视图持有者指定到某个位置并调用适配器的 onBindViewHolder() 方法。该方法使用视图持有者的位置,根据其列表位置确定内容应该是什么。

RecyclerView 模型会执行大量优化工作,因此您不必执行以下操作:

  • 列表首次填充时,它会创建并在列表的任意一侧绑定一些视图持有者。例如,如果视图显示的是列表位置 0 到 9,则 RecyclerView 会创建并绑定这些视图持有者,还可能创建并绑定位置 10 的视图持有者。这样,如果用户滚动列表,则下一个元素可随时显示出来。
  • 当用户滚动列表时,RecyclerView 会根据需要创建新的视图持有者。它还会保存已在屏幕外滚动的视图持有者,以便重复使用。如果用户切换滚动方向,则之前在屏幕外滚动的视图持有者可以立即恢复。另一方面,如果用户继续沿同一方向滚动,屏幕外停留时间最长的视图持有者可以重新绑定到新数据。无需创建视图持有者,也不需要扩充其视图;应用只更新视图的内容以匹配其绑定到的新项。
  • 当显示的项发生更改时,您可以通过调用适当的 RecyclerView.Adapter.notify…() 方法通知适配器。然后,适配器的内置代码仅会重新绑定受影响的项。

添加支持库

要访问 RecyclerView 微件,您需要将 v7 支持库添加到项目中,如下所示:

  1. 打开应用模块的 build.gradle 文件。
  2. 将支持库添加到 dependencies 部分。
        dependencies {
            implementation 'com.android.support:recyclerview-v7:28.0.0'
        }
        

将 RecyclerView 添加到您的布局中

现在,您可以将 RecyclerView 添加到布局文件中。例如,以下布局使用 RecyclerView 作为整个布局的唯一视图:

    <?xml version="1.0" encoding="utf-8"?>
    <!-- A RecyclerView with some commonly used attributes -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

在布局中添加了 RecyclerView 微件之后,获取对象句柄,将其连接到布局管理器,并为要显示的数据附加适配器:

Kotlin

    class MyActivity : Activity() {
        private lateinit var recyclerView: RecyclerView
        private lateinit var viewAdapter: RecyclerView.Adapter<*>
        private lateinit var viewManager: RecyclerView.LayoutManager

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.my_activity)

            viewManager = LinearLayoutManager(this)
            viewAdapter = MyAdapter(myDataset)

            recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view).apply {
                // use this setting to improve performance if you know that changes
                // in content do not change the layout size of the RecyclerView
                setHasFixedSize(true)

                // use a linear layout manager
                layoutManager = viewManager

                // specify an viewAdapter (see also next example)
                adapter = viewAdapter

            }
        }
        // ...
    }
    

Java

    public class MyActivity extends Activity {
        private RecyclerView recyclerView;
        private RecyclerView.Adapter mAdapter;
        private RecyclerView.LayoutManager layoutManager;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.my_activity);
            recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);

            // 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);

            // use a linear layout manager
            layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);

            // specify an adapter (see also next example)
            mAdapter = new MyAdapter(myDataset);
            recyclerView.setAdapter(mAdapter);
        }
        // ...
    }
    

添加列表适配器

要将所有数据输入列表中,您必须扩展 RecyclerView.Adapter 类。此对象会创建项的视图,并在原始项不再可见时用新数据项替换部分视图的内容。

以下代码示例展示了一个数据集的简单实现,该数据集由一个使用 TextView 微件显示的字符串数组组成:

Kotlin

    class MyAdapter(private val myDataset: Array<String>) :
            RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

        // 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 a string in this case that is shown in a TextView.
        class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)

        // Create new views (invoked by the layout manager)
        override fun onCreateViewHolder(parent: ViewGroup,
                                        viewType: Int): MyAdapter.MyViewHolder {
            // create a new view
            val textView = LayoutInflater.from(parent.context)
                    .inflate(R.layout.my_text_view, parent, false) as TextView
            // set the view's size, margins, paddings and layout parameters
            ...
            return MyViewHolder(textView)
        }

        // Replace the contents of a view (invoked by the layout manager)
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.text = myDataset[position]
        }

        // Return the size of your dataset (invoked by the layout manager)
        override fun getItemCount() = myDataset.size
    }
    

Java

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
        private String[] mDataset;

        // 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
        public static class MyViewHolder extends RecyclerView.ViewHolder {
            // each data item is just a string in this case
            public TextView textView;
            public MyViewHolder(TextView v) {
                super(v);
                textView = v;
            }
        }

        // Provide a suitable constructor (depends on the kind of dataset)
        public MyAdapter(String[] myDataset) {
            mDataset = myDataset;
        }

        // Create new views (invoked by the layout manager)
        @Override
        public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                       int viewType) {
            // create a new view
            TextView v = (TextView) LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.my_text_view, parent, false);
            ...
            MyViewHolder vh = new MyViewHolder(v);
            return vh;
        }

        // Replace the contents of a view (invoked by the layout manager)
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.setText(mDataset[position]);

        }

        // Return the size of your dataset (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return mDataset.length;
        }
    }
    

布局管理器会调用适配器的 onCreateViewHolder() 方法。该方法需要构造一个 RecyclerView.ViewHolder 并设置用于显示其内容的视图。ViewHolder 的类型必须与 Adapter 类签名中声明的类型一致。通常,它会通过扩充 XML 布局文件来设置视图。由于视图持有者尚未分配到任何特定数据,因此该方法实际上不会设置视图的内容。

布局管理器随后会将视图持有者绑定到相应数据。具体操作是调用适配器的 onBindViewHolder() 方法并将视图持有者的位置传入 RecyclerViewonBindViewHolder() 方法需要获取适当的数据,并使用它填充视图持有者的布局。例如,如果 RecyclerView 显示名称列表,该方法可能会在列表中找到适当的名称,并填充视图持有者的 TextView 微件。

如果列表需要更新,请对 RecyclerView.Adapter 对象调用通知方法,例如 notifyItemChanged()。然后,布局管理器会重新绑定任何受影响的视图持有者,使其数据得到更新。

提示ListAdapter 类有助于确定当列表发生更改时需要更新列表中的哪些项目。

自定义 RecyclerView

您可以自定义 RecyclerView 对象,以满足您的特定需求。标准类提供了大多数开发者会用到的所有功能;在很多情况下,您需要进行的自定义只限于为每个视图持有者设计视图,然后编写代码以使用适当的数据更新这些视图。不过,如果您的应用有特定要求,您可以通过多种方式修改标准行为。以下几个部分介绍了其他一些常见的自定义设置。

修改布局

RecyclerView 使用布局管理器将各项内容放置在屏幕上,并确定何时重复使用不再对用户可见的项目视图。要重复使用(或循环访问)视图,布局管理器可能会让适配器使用数据集中的不同元素替换视图的内容。以这种方式回收视图可避免创建不必要的视图或进行代价高昂的 findViewById() 查找,从而提高性能。Android 支持库包含三个标准布局管理器,每个管理器都提供了许多自定义选项:

如果这些布局管理器都不符合您的需求,您可以通过扩展 RecyclerView.LayoutManager 抽象类来创建自己的布局管理器。

为列表项添加动画

每当某项内容发生变化时,RecyclerView 会使用 animator 来更改其外观。该 animator 是扩展抽象 RecyclerView.ItemAnimator 类的对象。默认情况下,RecyclerView 使用 DefaultItemAnimator 来提供动画。如果您想提供自定义动画,可以通过扩展 RecyclerView.ItemAnimator 来定义自己的 animator 对象。

启用列表项选择

借助 recyclerview-selection 库,用户可以通过轻触或鼠标输入选择 RecyclerView 列表中的项。您仍然可以控制所选项的视觉呈现效果。您也仍然可以控制用于约束选择行为的政策,例如符合入选条件的项以及可以选择的项数。

要为 RecyclerView 实例添加选择支持,请按以下步骤操作:

  1. 确定要使用的选择键类型,然后构建 ItemKeyProvider

    有三种键类型可供您标识所选项:Parcelable(以及所有子类,如 Uri)、StringLong。如需详细了解选择键类型,请参阅 SelectionTracker.Builder

  2. 实现 ItemDetailsLookup
  3. ItemDetailsLookup 使选择库能够访问获得 MotionEventRecyclerView 项的相关信息。它实际上是由 RecyclerView.ViewHolder 实例备份(或从中提取)的 ItemDetails 实例的 factory。

  4. 更新 RecyclerView 中的 Views 项,以反映用户已将其选中或取消选择。

    选择库不会为所选项提供默认视觉装饰。您必须在实现 onBindViewHolder() 时提供此设置。 建议采用如下方法:

  5. 使用 ActionMode 为用户提供对所选项执行操作所需的工具。
  6. 注册 SelectionTracker.SelectionObserver 以在选择更改时接收通知。首次选择时,请启动 ActionMode 以向用户表示这一点,并提供特定于该选择的操作。例如,您可以向 ActionMode 栏添加删除按钮,然后在栏上连接返回箭头以取消选择。当选择变空时(如果用户上次取消选择),请不要忘记终止操作模式。

  7. 执行任何经过解释的辅助操作
  8. 在事件处理流水线的最后,库可能会判断出用户试图通过点按某个项来激活它或试图拖放某个项或一组选定项。请通过注册适当的监听器来回应这些解释。要了解详情,请参阅 SelectionTracker.Builder

  9. 使用 SelectionTracker.Builder 汇编所有内容
  10. 以下示例演示了如何使用 Long 选择键将这些部分组合在一起:

    Kotlin

        var tracker = SelectionTracker.Builder(
            "my-selection-id",
            recyclerView,
            StableIdKeyProvider(recyclerView),
            MyDetailsLookup(recyclerView),
            StorageStrategy.createLongStorage())
                .withOnItemActivatedListener(myItemActivatedListener)
                .build()
        

    Java

        SelectionTracker tracker = new SelectionTracker.Builder<>(
                "my-selection-id",
                recyclerView,
                new StableIdKeyProvider(recyclerView),
                new MyDetailsLookup(recyclerView),
                StorageStrategy.createLongStorage())
                .withOnItemActivatedListener(myItemActivatedListener)
                .build();
        

    要构建 SelectionTracker 实例,您的应用必须向 SelectionTracker.Builder 提供您之前初始化 RecyclerView 时所用的同一个 RecyclerView.Adapter。因此,创建 RecyclerView.Adapter 后,您很可能需要在 SelectionTracker 实例一经创建后将其注入到 RecyclerView.Adapter 中。否则,您将无法通过 onBindViewHolder() 方法检查某项内容的已选中状态。

  11. 将选中状态包含到 Activity 生命周期事件中。
  12. 为了在不同的 Activity 生命周期 事件之间保留选中状态,您的应用必须分别从 Activity 的 onSaveInstanceState()onRestoreInstanceState() 方法调用选择跟踪器的 onSaveInstanceState()onRestoreInstanceState() 方法。您的应用还必须向 SelectionTracker.Builder 构造函数提供唯一的选择 ID。此 ID 是必需的,因为 Activity 或 Fragment 可能具有多个不同的可选择列表,而所有这些列表都需要保持在其已保存状态中。

    其他资源

    Sunflower 演示应用中使用了 RecyclerView