基本 RecyclerView 示例
为了了解 RecyclerView
在典型应用程序中的工作原理,本主题探讨一个使用 RecyclerView
显示大量照片的简单代码示例:
RecyclerViewer 使用 CardView 实现 RecyclerView
布局中的每个照片项。 由于 RecyclerView
的性能优势,此示例应用能够快速滚动浏览大量照片,且不会出现明显的延迟。
示例数据源
在此示例应用中,“相册”数据源(由 PhotoAlbum
类表示)提供包含项目内容的 RecyclerView
。
PhotoAlbum
是带标题的照片集合;实例化时,可获取含 32 张照片的现成集合:
PhotoAlbum mPhotoAlbum = new PhotoAlbum ();
PhotoAlbum
中的每个照片实例都会公开属性,这些属性允许你读取其图像资源 ID,PhotoID
及其标题字符串,Caption
。 照片集的组织方式使每张照片都能由索引器访问。 例如,以下代码行会访问集合中第十张照片的图像资源 ID 和标题:
int imageId = mPhotoAlbum[9].ImageId;
string caption = mPhotoAlbum[9].Caption;
PhotoAlbum
还提供一种 RandomSwap
方法,可以调用该方法将集合中的第一张照片与集合中的其他位置随机选择的照片进行交换:
mPhotoAlbum.RandomSwap ();
由于 PhotoAlbum
的实现详细信息与理解 RecyclerView
无关,因此此处未显示 PhotoAlbum
源代码。
布局和初始化
布局文件 Main.axml,由 LinearLayout
中的单个 RecyclerView
组成:
<?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">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:scrollbars="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
请注意,必须使用完全限定名称 android.support.v7.widget.RecyclerView,因为 RecyclerView
打包在支持库中。 MainActivity
的 OnCreate
方法会初始化此布局、实例化适配器并准备基础数据源:
public class MainActivity : Activity
{
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
PhotoAlbumAdapter mAdapter;
PhotoAlbum mPhotoAlbum;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Prepare the data source:
mPhotoAlbum = new PhotoAlbum ();
// Instantiate the adapter and pass in its data source:
mAdapter = new PhotoAlbumAdapter (mPhotoAlbum);
// Set our view from the "main" layout resource:
SetContentView (Resource.Layout.Main);
// Get our RecyclerView layout:
mRecyclerView = FindViewById<RecyclerView> (Resource.Id.recyclerView);
// Plug the adapter into the RecyclerView:
mRecyclerView.SetAdapter (mAdapter);
此代码执行以下操作:
实例化
PhotoAlbum
数据源。将相册数据源传递给适配器的构造函数,
PhotoAlbumAdapter
(本指南稍后会进行定义)。 请注意,最佳做法是将数据源作为参数传递给适配器的构造函数。从布局中获取
RecyclerView
。通过调用
RecyclerView
SetAdapter
方法将适配器插入实例,RecyclerView
如上所示。
布局管理器
RecyclerView
中的每个项目都由包含照片图像和照片标题的 CardView
组成(下方“视图持有者”部分介绍了详细信息)。 预定义 LinearLayoutManager
用于在垂直滚动排列中布局每个 CardView
项:
mLayoutManager = new LinearLayoutManager (this);
mRecyclerView.SetLayoutManager (mLayoutManager);
此代码驻留在主活动的 OnCreate
方法中。 布局管理器的构造函数需要上下文,因此使用上面所示的 this
传递 MainActivity
。
可以插入一个自定义布局管理器,该管理器可以并行显示两个 CardView
项,实现翻页动画效果以遍历照片集合,而不是使用预定义的 LinearLayoutManager
。 本指南稍后将介绍如何通过交换其他布局管理器来修改布局的示例。
视图持有者
视图持有者类称为 PhotoViewHolder
。 每个 PhotoViewHolder
实例都保存对关联行项 ImageView
和 TextView
的引用,该项在 CardView
中按下图所示布局:
PhotoViewHolder
派生自 RecyclerView.ViewHolder
,并包含用于存储对上述布局中显示的 ImageView
和 TextView
引用的属性。
PhotoViewHolder
由两个属性和一个构造函数组成:
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView Caption { get; private set; }
public PhotoViewHolder (View itemView) : base (itemView)
{
// Locate and cache view references:
Image = itemView.FindViewById<ImageView> (Resource.Id.imageView);
Caption = itemView.FindViewById<TextView> (Resource.Id.textView);
}
}
在此代码示例中,PhotoViewHolder
构造函数会传递对 PhotoViewHolder
包装的父项视图 (CardView
) 的引用。 请注意,会始终将父项视图转发到基本构造函数。 PhotoViewHolder
构造函数对父项视图调用 FindViewById
,以分别将结果存储在 Image
和 Caption
属性中,以查找其每个子视图引用、ImageView
和 TextView
。 适配器稍后使用新数据更新此 CardView
的子视图时,会从这些属性检索视图引用。
有关 RecyclerView.ViewHolder
的详细信息,请参阅 RecyclerView.ViewHolder 类引用。
适配器
适配器使用特定照片的数据加载每个 RecyclerView
行。 例如,对于位于行位置 P 的给定照片,适配器将找到数据源中 P 位置的相关数据,并将此数据复制到 RecyclerView
集合中 P 位置的行项。 适配器使用视图持有者查找位于该位置的 ImageView
和 TextView
的引用,这样用户滚动浏览集合并重复使用视图时,不必为这些视图重复调用 FindViewById
。
在 RecyclerViewer 中,适配器类派生自 RecyclerView.Adapter
以创建 PhotoAlbumAdapter
:
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public PhotoAlbum mPhotoAlbum;
public PhotoAlbumAdapter (PhotoAlbum photoAlbum)
{
mPhotoAlbum = photoAlbum;
}
...
}
mPhotoAlbum
成员包含传入构造函数的数据源(相册);构造函数会将相册复制到此成员变量中。 实现以下必需的 RecyclerView.Adapter
方法:
OnCreateViewHolder
– 实例化项布局文件和视图持有者。OnBindViewHolder
– 将指定位置的数据加载到存储在给定视图持有者中的视图引用中。ItemCount
– 返回数据源中的项数。
布局管理器在定位 RecyclerView
中的项时调用这些方法。 以下各节将检查这些方法的实现。
OnCreateViewHolder
当 RecyclerView
需要新的视图持有者来表示项时,布局管理器将调用 OnCreateViewHolder
。 OnCreateViewHolder
从视图的布局文件中扩充项视图,并将视图包装在新 PhotoViewHolder
实例中。 PhotoViewHolder
构造函数会查找和存储对布局中子视图的引用,如“视图持有者”中所述。
每行项由一个 CardView
表示,其中包含 ImageView
(对于照片)和 TextView
(对于标题)。 此布局驻留在 PhotoCardView.axml 文件中:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true"
card_view:cardCornerRadius="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/imageView"
android:scaleType="centerCrop" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#333333"
android:text="Caption"
android:id="@+id/textView"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="4dp" />
</LinearLayout>
</android.support.v7.widget.CardView>
</FrameLayout>
此布局表示 RecyclerView
中的单个行项。 OnBindViewHolder
方法(如下所述)将数据从数据源复制到此布局的 ImageView
和 TextView
。
OnCreateViewHolder
为 RecyclerView
中给定的照片位置扩充此布局,并实例化新的 PhotoViewHolder
实例(该实例会查找并缓存对关联 CardView
布局中 ImageView
和 TextView
子视图的引用):
public override RecyclerView.ViewHolder
OnCreateViewHolder (ViewGroup parent, int viewType)
{
// Inflate the CardView for the photo:
View itemView = LayoutInflater.From (parent.Context).
Inflate (Resource.Layout.PhotoCardView, parent, false);
// Create a ViewHolder to hold view references inside the CardView:
PhotoViewHolder vh = new PhotoViewHolder (itemView);
return vh;
}
生成的视图持有者实例 vh
会返回给调用方(布局管理器)。
OnBindViewHolder
当布局管理器准备好在 RecyclerView
的可见屏幕区域中显示特定视图时,它会调用适配器的 OnBindViewHolder
方法,以便用数据源中的内容填充指定行位置的项。 OnBindViewHolder
会获取指定行位置的照片信息(照片的图像资源和照片标题的字符串),并将此数据复制到关联的视图。 视图通过存储在视图持有者对象(通过 holder
参数传入)中的引用来定位:
public override void
OnBindViewHolder (RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
// Load the photo image resource from the photo album:
vh.Image.SetImageResource (mPhotoAlbum[position].PhotoID);
// Load the photo caption from the photo album:
vh.Caption.Text = mPhotoAlbum[position].Caption;
}
传入视图持有者对象必须先强制转换为派生视图持有者类型(在本例中为 PhotoViewHolder
),然后才能使用它。
适配器会将图像资源加载到视图持有者的 Image
属性所引用的视图中,并将标题文本复制到视图持有者 Caption
属性引用的视图中。 此关联视图与其数据绑定。
请注意,OnBindViewHolder
是直接处理数据结构的代码。 在这种情况下,OnBindViewHolder
了解如何将 RecyclerView
项位置映射到数据源中的关联数据项。 在这种情况下,映射非常简单,因为位置可用作相册中的数组索引;但是,更复杂的数据源可能需要额外的代码才能建立此类映射。
ItemCount
ItemCount
方法会返回数据收集中的项数。 在示例照片查看器应用中,项目计数是指相册中照片的数量:
public override int ItemCount
{
get { return mPhotoAlbum.NumPhotos; }
}
有关 RecyclerView.Adapter
的详细信息,请参阅 RecyclerView.Adapter 类引用。
放在一起后如下所示
示例照片应用生成的 RecyclerView
实现由创建数据源、布局管理器和适配器的 MainActivity
代码组成。 MainActivity
会创建 mRecyclerView
实例、实例化数据源和适配器,并插入布局管理器和适配器:
public class MainActivity : Activity
{
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
PhotoAlbumAdapter mAdapter;
PhotoAlbum mPhotoAlbum;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
mPhotoAlbum = new PhotoAlbum();
SetContentView (Resource.Layout.Main);
mRecyclerView = FindViewById<RecyclerView> (Resource.Id.recyclerView);
// Plug in the linear layout manager:
mLayoutManager = new LinearLayoutManager (this);
mRecyclerView.SetLayoutManager (mLayoutManager);
// Plug in my adapter:
mAdapter = new PhotoAlbumAdapter (mPhotoAlbum);
mRecyclerView.SetAdapter (mAdapter);
}
}
PhotoViewHolder
会查找和缓存视图引用:
public class PhotoViewHolder : RecyclerView.ViewHolder
{
public ImageView Image { get; private set; }
public TextView Caption { get; private set; }
public PhotoViewHolder (View itemView) : base (itemView)
{
// Locate and cache view references:
Image = itemView.FindViewById<ImageView> (Resource.Id.imageView);
Caption = itemView.FindViewById<TextView> (Resource.Id.textView);
}
}
PhotoAlbumAdapter
会实现三个必需的方法替代:
public class PhotoAlbumAdapter : RecyclerView.Adapter
{
public PhotoAlbum mPhotoAlbum;
public PhotoAlbumAdapter (PhotoAlbum photoAlbum)
{
mPhotoAlbum = photoAlbum;
}
public override RecyclerView.ViewHolder
OnCreateViewHolder (ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From (parent.Context).
Inflate (Resource.Layout.PhotoCardView, parent, false);
PhotoViewHolder vh = new PhotoViewHolder (itemView);
return vh;
}
public override void
OnBindViewHolder (RecyclerView.ViewHolder holder, int position)
{
PhotoViewHolder vh = holder as PhotoViewHolder;
vh.Image.SetImageResource (mPhotoAlbum[position].PhotoID);
vh.Caption.Text = mPhotoAlbum[position].Caption;
}
public override int ItemCount
{
get { return mPhotoAlbum.NumPhotos; }
}
}
编译并运行此代码后,它将创建基本照片查看应用,如以下屏幕截图所示:
如果未绘制阴影(如上方屏幕截图所示),请编辑 Properties/AndroidManifest.xml 并将以下属性设置添加到 <application>
元素:
android:hardwareAccelerated="true"
此基本应用仅支持浏览相册。 它不会响应项触摸事件,也不会处理基础数据中的更改。 扩展 RecyclerView 示例中添加了此功能。
更改 LayoutManager
由于 RecyclerView
的灵活性,可以轻松修改应用以使用不同的布局管理器。 在下面的示例中,对其进行了修改,以水平滚动的网格布局而不是垂直线性布局显示相册。 为此,将修改布局管理器实例化以使用 GridLayoutManager
,如下所示:
mLayoutManager = new GridLayoutManager(this, 2, GridLayoutManager.Horizontal, false);
此代码更改将垂直方向上的 LinearLayoutManager
替换为 GridLayoutManager
,该网格由两行组成,并在水平方向上滚动。 再次编译并运行应用时,你将看到照片是以网格形式显示的,而且滚动是水平而不是垂直的:
仅需更改一行代码,即可修改照片查看应用,使其使用不同的布局和不同的行为。 请注意,无需修改适配器代码或布局 XML 即可改变布局样式。
在下一主题“扩展 RecyclerView 示例”中,此基本示例应用将扩展为处理项单击事件,并在基础数据源发生更改时更新 RecyclerView
。