官方文档地址:https://developer.android.com/guide/topics/ui/layout/recyclerview#top_of_page
如果你的应用需要显示基于大型数据集(或经常需要更改的数据)的滚动元素列表,你应该使用RecyclerView来完成这项工作
左图是一个使用了RecyclerView的列表,右图则是在RecyclerView还用上了CardView
RecyclerView概述
RecyclerView是一个比ListView更高级也更加灵活的控件。
RecyclerView这个模型是由几个不同的组件协同工作以显示数据的,呈现出的包含用户界面的容器就是一个RecyclerView对象,这个对象是开发者添加到xml布局文件中的。填充RecyclerView的视图来自开发者提供的布局管理器(LayoutManager),开发者可以使用谷歌提供的标准布局管理器,如LinearLayoutManager或GridLayoutManager,当然也可以使用自定义的LayoutManager。
列表中的每个item视图就是一个ViewHolder,这些ViewHolder对象是开发者通过继承RecyclerView.ViewHolder定义的类的实例。每一个ViewHolder都负责显示item视图,例如,如果你的列表需要显示音乐集,则每个ViewHolder就可能代表一个专辑。RecyclerView创建的仅仅是那些显示出来的动态内容所需的ViewHolder,它们都在手机屏幕内,除此之外还有一些额外的视图。当用户滚动列表时,RecyclerView会获取屏幕外的视图并将它们重新绑定到屏幕上正在滚动的数据中。
ViewHolder对象由适配器(Adapter)管理,开发者可以通过继承RecyclerView.Adapter来创建适配器,并在Adapter中根据需要来创建ViewHolder。Adapter会将ViewHolder绑定到它的数据中,具体就是Adapter将ViewHolder分配到列表中的一个位置上,这个操作是调用Adapter的onBindViewHolder()方法来完成的,这个方法通过ViewHolder在列表中的位置来确定显示的具体内容是什么。
RecyclerView本身做了很多优化工作,如下:
- 首次填充列表时,它会在列表中创建并绑定一些ViewHolder。举个例子,如果列表显示的视图位置是从0~9,则RecyclerView会创建并绑定这些ViewHolder,它可能也会创建并绑定处在位置10的ViewHolder。如此一来,当用户要开始滚动列表时,下一个元素已经准备好了。
- 当用户滚动列表时,RecyclerView会根据需要来创建新的ViewHolder,它还可以保存已经滚动到屏幕外的ViewHolder,因此这些ViewHolder是可以重复利用的。如果用户切换了列表的滚动方向,则滚动到屏幕外的那些ViewHolder是可以回到屏幕内的。另一方面,如果用户继续以当前的方向滚动列表,则可以将已经不在屏幕内的那些ViewHolder重新绑定到新的数据中。ViewHolder是不需要被创建或填充视图的,取而代之的是,应用程序只需要更新ViewHolder的内容,从而匹配它所绑定的新item即可。
- 当显示的item发生变化时,开发者可以通过调用适当的RecyclerView.Adapter.notify ...()方法来通知Adapter。然后Adapter会重新绑定发生了变化的item。
添加支持库
要想使用RecyclerView,需要将v7支持库添加到项目中,如下所示:
- 打开app module的build.gradle文件
- 将支持库添加到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添加到布局之后,接下来就是获取这个RecyclerView对象,将其连接到布局管理器,并为其指定Adapter:
public class MyActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
//如果你确定内容的变化不会更改RecyclerView的布局大小,可以使用这个设置来提高性能
mRecyclerView.setHasFixedSize(true);
//使用线性布局管理器
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// 指定适配器
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
}
// ...
}
新建一个Adapter
为了将所有数据都添加给列表,开发者必须新建一个继承了RecyclerView.Adapter的类,这个类负责帮助item创建视图,并在需要时更新item中的视图和数据。
以下代码示例显示了一个Adapter的简单实现:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] mDataset;
// 提供对每个数据项的视图的引用,较为复杂的item可能会包含多个视图,你可以访问ViewHolder中的所有view
public static class MyViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTextView;
public MyViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
// 提供合适的构造函数(这取决于数据集的类型)
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// 创建新的视图(由布局管理器调用)
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// 创建一个新视图
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
...
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
// 替换视图的内容(由布局管理器调用)
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
//更新视图ui
holder.mTextView.setText(mDataset[position]);
}
// 返回数据集的大小(由布局管理器调用)
@Override
public int getItemCount() {
return mDataset.length;
}
}
布局管理器(LayoutManager)会调用Adapter的onCreateViewHolder()方法,这个方法会创建一个RecyclerView.ViewHolder,并设置其用于显示内容的视图。ViewHolder的类型必须与Adapter类签名中声明的类型匹配,ViewHolder通过填充xml布局文件来设置视图。如上代码中,因为还没由分配数据给ViewHolder,因此onCreateViewHolder()实际上还未设置视图的内容。
在创建完一个ViewHolder之后,LayoutManager会将数据到ViewHolder中,这个过程是通过LayoutManager调用Adapter的onBindViewHolder()方法来实现的,与此同时还会将ViewHolder的位置信息传递给RecyclerView。onBindViewHolder()方法需要获取数据来填充ViewHolder的布局。举个例子,如果RecyclerView正在显示一个name列表,则该方法可能会在列表中找到name,并设置给ViewHolder中的TextView。
如果列表需要更新,可以调用Adapter对象的相关通知方法,如notifyItemChanged(),如此一来,LayoutManager会重新绑定需要更新的ViewHolder,从而允许它数据被更新。
自定义你的RecyclerView
虽然标准类已经提供了大多数开发人员所需的所有功能,但你仍然可以自定义一个RecyclerView满足特定需求。在很多时候,你需要做的自定义其实就是为不同的ViewHolder设计不同的视图,并且使用数据去更新这些视图。但如果你的应用程序有其他的特定要求,你还是可以通过多种方式来修改原有的一些标准,如下描述了一些常见的自定义方式:
修改布局
RecyclerView使用LayoutManager在屏幕上定位各个item,并确定那些对于用户不可见的item视图何时重用。如果要重用(或回收)视图的话,LayoutManager可能会要求Adapter使用与新的数据来替换视图中内容,通过这种方式来回收view可以避免创建不必要的视图或执行代价较大的findViewById(),从而提高了性能。Android支持库中有三个标准的LayoutManager,每个LayoutManager都提供了许多自定义选项:
- LinearLayoutManager将item以线性样式排列,使用带有LinearLayoutManager的RecyclerView提供的功能类似于以前的ListView布局。
- GridLayoutManager将item以网格样式排列,就像棋盘上的一个个方块一样。使用带有GridLayoutManager的RecyclerView提供的功能类似于以前的GridView布局。
- StaggeredGridLayoutManager也是将item以网格样式排列,但每个item的长或宽并不固定。
添加item动画
每当item发生变化时,RecyclerView都会使用animator,也就是动画来配合这个变化的过程。这个animator是一个抽象类的对象,它继承了RecyclerView.ItemAnimator类。在默认情况下,RecyclerView使用DefaultItemAnimator来提供动画,如果开发者想自己来提供自定义的动画,可以通过继承RecyclerView.ItemAnimator类来完成。
启用列表item选中
通过使用recyclerview-selection库,开发者可以使用触摸或鼠标输入在RecyclerView列表中选中item。你可以控制所选item的可视化表示,还可以持有选中后行为的控制,例如哪些item可以选中,以及可以选择的item有多少个。
要向RecyclerView实例添加选中支持,请按照下列步骤操作:
- 确定要使用的选择键类型,然后构建ItemKeyProvider。你可以使用三种选择键类型来标识所选的item:Parcelable(以及所有子类,如Uri),String和Long。有关选择键类型的详细信息,请到文档中查看SelectionTracker.Builder。
- 实现ItemDetailsLookup接口,ItemDetailsLookup能够使选择库在给定MotionEvent的情况下访问有关RecyclerView中item的信息。
- 在RecyclerView中更新item的视图,从而体现出用户是选择还是取消选择。selection库并不会为所选item提供默认的ui效果,所以你必须在onBindViewHolder()时设置视图的具体内容。建议的方法如下:
(1)在onBindViewHolder()中,根据是否选择了该item来调用View的setActivated()方法(不是setSelected())。
(2)为了表示出激活状态,需要更新视图样式,建议使用颜色状态列表资源来配置样式。
- 使用ActionMode为用户提供 选择执行 这个操作的工具。注册SelectionTracker.SelectionObserver,以便在选择更改时收到通
知。首次创建选择时,启动ActionMode将其表示给用户,并提供相应的操作。例如,你可以向ActionMode栏添加删除按钮,同时ActionMode栏上的后退箭头用来清除选择,如果用户最后一次清除了选择,不要忘记终止。
-
在事件处理结束时执行辅助操作,通过点击它来确定用户是否正在激活item或者正在拖拽Item等。通过注册适当的监听器来做出反应。更多详细信息,请到文档中SelectionTracker.Builder。
-
使用SelectionTracker.Builder来组合所有内容。下面的示例代码显示了如何使用Long选择键将这些片段放在一起:
SelectionTracker tracker = new SelectionTracker.Builder<>(
"my-selection-id",
recyclerView,
new StableIdKeyProvider(recyclerView),
new MyDetailsLookup(recyclerView),
StorageStrategy.createLongStorage())
.withOnItemActivatedListener(myItemActivatedListener)
.build();
为了构建SelectionTracker实例,你的应用程序必须提供用于将RecyclerView初始化为SelectionTracker.Builder的相同RecyclerView.Adapter。因此,在创建RecyclerView.Adapter之后,你很可能需要在创建后将SelectionTracker实例注入RecyclerView.Adapter。否则你将无法在onBindViewHolder()方法中查看item的选定状态。
7. 在Activity生命周期事件中包括选择。为了保持Activity生命周期事件中的选中状态,必须分别在Activity的 onSaveInstanceState()和onRestoreInstanceState()方法中调用选择跟踪器的onSaveInstanceState()和 onRestoreInstanceState()方法。除此之外,还必须为SelectionTracker.Builder构造函数提供唯一的选择ID。这个ID是必需 的,因为Activity或Fragment可能具有多个不同的可选列表,所有这些列表都需要以其保存状态保留。