扩展 RecyclerView 示例

基本 RecyclerView 示例中描述的基本应用实际上没有太多用处 - 它只是可以滚动并显示固定的照片项目列表以方便浏览。 在实际应用程序中,用户希望能够通过点击显示中的项来与应用交互。 此外,基础数据源可以更改(或由应用更改),并且显示的内容必须与这些更改保持一致。 在以下部分中,你将了解如何处理项单击事件并在基础数据源更改时更新 RecyclerView

处理项单击事件

当用户触摸 RecyclerView 中的某个项时,系统会生成一个项单击事件,以通知应用访问了哪个项。 该事件不是由 RecyclerView 生成的,相反,项视图(包装在视图持有者中)将检测这些触摸并会将这些触摸报告为单击事件。

为了说明如何处理项单击事件,以下步骤解释了如何修改基本照片查看应用以报告用户触摸了哪张照片。 当示例应用中发生项单击事件时,会发生以下序列:

  1. 照片的 CardView 检测到项单击事件,并通知适配器。

  2. 适配器将事件(带有项位置信息)转发到活动的项单击处理程序。

  3. 活动的项单击处理程序响应项单击事件。

首先,将调用 ItemClick 的事件处理程序成员添加到 PhotoAlbumAdapter 类定义中:

public event EventHandler<int> ItemClick;

接下来,将项单击事件处理程序方法添加到 MainActivity 中。 此处理程序会短暂显示一个 Toast,指示触摸了哪个照片项:

void OnItemClick (object sender, int position)
{
    int photoNum = position + 1;
    Toast.MakeText(this, "This is photo number " + photoNum, ToastLength.Short).Show();
}

接下来,需要一个代码行才能将 OnItemClick 处理程序注册到 PhotoAlbumAdapter。 创建 PhotoAlbumAdapter 后,可以立即执行此操作:

mAdapter = new PhotoAlbumAdapter (mPhotoAlbum);
mAdapter.ItemClick += OnItemClick;

在此基本示例中,处理程序注册发生在主活动的 OnCreate 方法中,但生产应用可能会在 OnResume 中注册此处理程序并在 OnPause 中取消其注册 - 有关详细信息,请参阅活动生命周期

PhotoAlbumAdapter 现在将在收到项单击事件时调用 OnItemClick。 下一步是在引发此 ItemClick 事件的适配器中创建处理程序。 以下 OnClick 方法会添加到适配器的 ItemCount 方法后面:

void OnClick (int position)
{
    if (ItemClick != null)
        ItemClick (this, position);
}

OnClick 方法是适配器的侦听器,用于侦听项视图中的项单击事件。 将此侦听器(通过项视图的视图持有者)注册到项视图之前,必须修改 PhotoViewHolder 构造函数以接受此方法作为附加参数,并向项视图 Click 事件注册 OnClick。 下面是修改后的 PhotoViewHolder 构造函数:

public PhotoViewHolder (View itemView, Action<int> listener)
    : base (itemView)
{
    Image = itemView.FindViewById<ImageView> (Resource.Id.imageView);
    Caption = itemView.FindViewById<TextView> (Resource.Id.textView);

    itemView.Click += (sender, e) => listener (base.LayoutPosition);
}

itemView 参数包含对用户触摸的 CardView 的引用。 请注意,视图持有者基类知道它所表示的项 (CardView) 的布局位置(通过 LayoutPosition 属性),并且当发生项单击事件时,该位置将传递给适配器的 OnClick 方法。 修改适配器的 OnCreateViewHolder 方法,使其将适配器的 OnClick 方法传递给视图持有者的构造函数:

PhotoViewHolder vh = new PhotoViewHolder (itemView, OnClick);

现在,生成并运行示例照片查看应用时,点击显示中的照片将出现 Toast,报告触摸了哪张照片:

点击照片卡片时出现的 Toast 示例

此示例只演示了一种使用 RecyclerView 实现事件处理程序的方法。 这里可以使用的另一种方法是将事件放置在视图持有者上并让适配器订阅这些事件。 如果示例照片应用提供了照片编辑功能,则每个 CardView 中的 ImageViewTextView 都需要单独的事件:触摸 TextView 会启动 EditView 对话框,让用户可以编辑标题,触摸 ImageView 会启动照片修饰工具,让用户可以裁剪或旋转照片。 根据应用的需求,必须制定处理和响应触摸事件的最佳方法。

要演示如何在数据集更改时更新 RecyclerView,可以修改示例照片查看应用以随机​​选择数据源中的一张照片并将其与第一张照片交换。 首先,将“随机选取”按钮添加到示例照片应用的 Main.axml 布局中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:id="@+id/randPickButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="Random Pick" />
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:scrollbars="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

接下来,在主活动的 OnCreate 方法末尾添加代码,以在布局中找到 Random Pick 按钮,并将处理程序附加到该按钮:

Button randomPickBtn = FindViewById<Button>(Resource.Id.randPickButton);

randomPickBtn.Click += delegate
{
    if (mPhotoAlbum != null)
    {
        // Randomly swap a photo with the first photo:
        int idx = mPhotoAlbum.RandomSwap();
    }
};

点击“随机选取”按钮时,此处理程序将调用相册的 RandomSwap 方法RandomSwap 方法会随即选取一张照片并将其与数据源中的第一张照片交换,然后返回随机交换的照片的索引。 使用此代码编译并运行示例应用时,点击“随机选取”按钮不会导致显示更改,因为 RecyclerView 未识别对数据源所做的更改

要在数据源更改后更新 RecyclerView,必须修改“随机选取”单击处理程序,以便为集合中已更改的每个项调用适配器的 NotifyItemChanged 方法(在本例中,有两个项已更改:第一张照片和交换的照片)。 这会导致 RecyclerView 更新其显示,使其与数据源的新状态保持一致:

Button randomPickBtn = FindViewById<Button>(Resource.Id.randPickButton);

randomPickBtn.Click += delegate
{
    if (mPhotoAlbum != null)
    {
        int idx = mPhotoAlbum.RandomSwap();

        // First photo has changed:
        mAdapter.NotifyItemChanged(0);

        // Swapped photo has changed:
        mAdapter.NotifyItemChanged(idx);
    }
};

现在,点击“随机选取”按钮时, 将更新显示,表明集合中更靠后的照片已与集合中的第一张照片交换RecyclerView

交换前第一个屏幕截图,交换后第二个屏幕截图

当然,可以调用 NotifyDataSetChanged,而不是分两次调用 NotifyItemChanged,但这样做会强制 RecyclerView 刷新整个集合,即使集合中只有两项发生了更改也是如此。 调用 NotifyItemChanged 比调用 NotifyDataSetChanged 更高效。