本文介绍了RecyclerView的使用。
概述
RecyclerView
是一个新的ViewGroup
用于渲染所有基于adapter的View用类似的方式。它是ListView 和 GridView的继承者,可以从latest support-v7 version找到。其中一个原因是RecyclerView有更多的可扩展框架,尤其是因为它可以实现水平和垂直的布局。当你的数据集合元素在运行时基于用户动作或网络事件改变时使用RecyclerView
控件。
如果你想使用RecyclerView
,需要和下面的类配合使用:
RecyclerView.Adapter
- 用于处理数据集合并且和View绑定LayoutManager
- 用于帮助摆放Items的位置ItemAnimator
- 用于处理常见操作例如item的添加或移除时产生动画
此外,它还对ListView
的items的添加或移除时提供动画支持,在目前的实现中很难做此处理。RecyclerView
也开始执行ViewHolder pattern,这是推荐做法并且已和新的框架深入结合。
更多细节,请查看 this detailed overview。
比较ListView
RecyclerView
不同于ListView
主要是由于以下特性:
- Required ViewHolder in Adapters -
ListView
adapters 不需要使用ViewHolder pattern提升性能。对比而言,RecyclerView
实现的adapter需要使用ViewHolder pattern。 - Customizable Item Layouts -
ListView
只能垂直线性排列并且不能自定义。对比而言,RecyclerView
有一个RecyclerView.LayoutManager
允许任何item布局包括水平列表或交错网格。 - Easy Item Animations -
ListView
不支持item的添加或移除时产生动画。对比而言,RecyclerView
有RecyclerView.ItemAnimator
类处理item动画。 - Manual Data Source - 对于不同数据源
ListView
有adapters例如ArrayAdapter
和CursorAdapter
分别对应数组和数据库的结果。对比而言,RecyclerView.Adapter
需要自定义实现adapter提供数据。 - Manual Item Decoration -
ListView
有android:divider
属性可以方便地在list的item之间设置分隔线。对比而言,RecyclerView
需要使用RecyclerView.ItemDecoration
对象设置分隔线。 - Manual Click Detection -
ListView
有一个AdapterView.OnItemClickListener
接口绑定list中item点击事件。对比而言,RecyclerView
只支持RecyclerView.OnItemTouchListener
处理触摸事件并没有内置点击处理。
RecyclerView的组件
LayoutManagers
RecyclerView
需要一个布局管理器和一个adapter进行初始化。布局管理器摆放RecyclerView
中的item views并且决定何时重用不再显示的item views。
RecyclerView提供了这些内置布局管理器:
LinearLayoutManager
垂直或水平滚动列表显示items。GridLayoutManager
网格显示items。StaggeredGridLayoutManager
交错网格显示items。
对于创建自定义布局管理器,继承RecyclerView.LayoutManager类。
这有Dave Smith’s 讲解的自定义布局管理器。
RecyclerView.Adapter
RecyclerView
包含一个新的adapter种类。跟你已经使用adapter方法类似,但有一些特性,例如需要ViewHolder
。你需要重写两个主要方法:一个是填充view和它的view holder,另一个绑定数据到view。使用这个的好处是第一个方法只会在真正需要创建新的View时调用。不需要检查是否被回收。
ItemAnimator
RecyclerView.ItemAnimator
会对通知了adapter的ViewGroup
的修改例如添加/删除/选择产生动画。基本默认动画可以使用DefaultItemAnimator
并且工作的相当好。更多信息请查看这部分。
使用RecyclerView
使用RecyclerView有几个关键步骤:
1.在gradle文件中添加RecyclerView
support library
2.定义一个model类作为数据源
3.在activity中添加RecyclerView
用于显示items
4.创建一个自定义行布局作为可视化item
5.创建RecyclerView.Adapter
和 ViewHolder
渲染item
6.绑定adapter到数据源填充RecyclerView
下面会详细介绍这些步骤。
安装
确保在app/build.gradle
文件添加了依赖:1
2
3
4dependencies {
...
compile 'com.android.support:recyclerview-v7:25.2.0'
}
点击”Sync Project with Gradle files”让IDE下载合适的资源。
定义Model
RecyclerView支持源数据。在本例中,我们定义一个Contact
类代表数据模型通过RecyclerView显示:
1 | public class Contact { |
在布局中创建RecyclerView
在activity的布局文件res/layout/activity_users.xml
中添加RecyclerView
:
1 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
我们可以预览activity中的RecyclerView
:
现在RecyclerView
嵌入到了activity的布局文件中。然后我们定义item布局文件。
创建自定义行布局
创建adapter之前,我们先来定义下item布局。布局文件包含一个水平线性布局,包括一个textview和一个button:
在res/layout/item_contact.xml
创建这个布局文件。注意:对于layout_height
应该使用wrap_content
因为RecyclerView
版本23.2.1
之前忽略了布局参数。点击这个链接获取更多信息。
1 | <?xml version="1.0" encoding="utf-8"?> |
完成了自定义item的布局,接下来创建adapter填充数据到recycler view。
创建RecyclerView.Adapter
我们需要创建adapter填充数据到RecyclerView。adapter的角色是转换一个位置的对象到要插入到一个列表中的item。
使用RecyclerView
的adapter需要一个”ViewHolder”对象描述和提供访问每个item中所有的View。我们可以创建基本的adapter和holder在ContactsAdapter.java
中:
1 | // Create the basic adapter extending from RecyclerView.Adapter |
现在我们定义了基本的adapter和ViewHolder
,然后需要填充adapter。首先添加一个成员变量存放联系人列表并且从构造方法传递过来:
1 | public class ContactsAdapter extends |
每个adapter有三个主要方法:onCreateViewHolder
用于填充item布局和创建holder,onBindViewHolder
用于根据数据设置view属性,getItemCount
决定了item的数量。我们需要实现这三个方法:
1 | public class ContactsAdapter extends |
完成了adapter,剩下的就是绑定adapter到RecyclerView。
绑定adapter到RecyclerView
在Activity中,填充联系人集合将会显示在RecyclerView
中。
1 | public class UserListActivity extends AppCompatActivity { |
最后,编译并运行应用会看到如下图所示效果。如果你创建了足够多的items然后滚动列表,views会被回收并且默认情况下比ListView
更流畅:
通知Adapter
不像ListView,通过RecyclerView
adapter没办法直接添加或移除items。你需要改变数据源并且通知adapter改变。还有,无论何时添加或移除元素,总是修改 已存在 的列表。例如,重新初始化列表就像下面的代码是不会影响adapter,因为它持有老列表的内存引用:
1 | // 不要重新初始化被adapter使用的已存在的引用 |
而是应该直接在已存在的引用上操作:
1 | // 添加到已存在的列表 |
当通知adapter修改时有几个方法可以使用:
方法 | 描述 |
---|---|
notifyItemChanged(int pos) |
通知指定位置的item改变 |
notifyItemInserted(int pos) |
通知指定位置的item被新插入 |
notifyItemRemoved(int pos) |
通知指定位置的item被移除 |
notifyDataSetChanged() |
通知数据集改变。仅当作迫不得已才使用的方法。 |
我们可以在activity或fragment中使用:
1 | // Add a new contact |
每当我们想从RecyclerView添加或移除items时,我们需要显式通知给adapter事件。不像ListView的adapter,RecyclerView的adapter不应该依赖notifyDataSetChanged()
因为应该使用更细致的动作。更多细节请查看API文档。
还有,如果你想要更新已存在的列表,做修改之前确保拿到当前items的数量。例如,应该调用adapter的getItemCount()
记录要修改的第一条索引。
1 | // record this value before making any changes to the existing list |
比较大的修改(Diffing Larger Changes)
通常在某些情况下修改列表是很复杂的(例如列表排序)并且无法轻易确定每行是否被修改。在这种情况下,你将不得不在整个adapter上调用notifyDataSetChanged()
去更新整个屏幕,这样就没办法在修改的地方显示执行动画序列。
在support library v24.2.0添加了用于帮助计算老列表和新列表之间不同的类DiffUtil
。这个在源码中使用了相同的算法来计算行的改变(diff 实用程序),因此它通常相当快。对于大列表推荐在后台线程执行这个计算。
对于使用DiffUtil
类,首先需要添加一个类继承DiffUtil.Callback
接收新老列表:
1 |
|
然后,你需要在adapter中实现swapItems()
方法用于执行diff,然后调用dispatchUpdates()
通知adapter元素是否被插入,移除,移动,或修改:
1 | public class ContactsAdapter extends |
工作示例,请查看示例代码。
滚动到新的item
如果在列表前插入元素并且希望保持位置在顶部,我们可以设置滚动位置到第一个元素:
1 | adapter.notifyItemInserted(0); |
如果我们添加item在末尾并且希望滚动到添加的位置,我们可以通知adapter元素被添加并且可以在RecyclerView调用smoothScrollToPosition()
:1
2adapter.notifyItemInserted(contacts.size() - 1); // contacts.size() - 1 is the last element position
rvContacts.scrollToPosition(mAdapter.getItemCount() - 1); // update based on adapter
实现加载更多
当用户滚动列表到末尾实现加载更多数据并追加到列表末尾,从RecyclerView
使用addOnScrollListener()
方法并且利用这篇文章的EndlessScrollViewScrollListener指导添加onLoadMore
方法。
RecyclerView的配置
RecyclerView
有很强大的灵活性和高度可自定义化。下面介绍几个可用选项。
性能
对于明显地平滑滚动如果item是静态的并且不会改变我们也可以启用优化:
1 | recyclerView.setHasFixedSize(true); |
布局
使用布局管理器可以配置items的位置。默认情况下,我们可以使用LinearLayoutManager
, GridLayoutManager
,和 StaggeredGridLayoutManager
。线性显示item可以是垂直或水平:
1 | // Setup layout manager for items with orientation |
用网格或错落网格显示items:
1 | // First param is number of columns and second param is orientation i.e Vertical or Horizontal |
例如,错落网格就像这样:
我们也可以创建自己的自定义布局管理器。
Decorations
我们可以使用不同的decorators附加到recyclerview装饰items例如DividerItemDecoration:
1 | RecyclerView.ItemDecoration itemDecoration = new |
这个decorator在列表中的每个item之间显示分隔线正如下图所示:
网格空间装饰(Grid Spacing Decorations)
Decorators也可以用于在网格布局或错落网格的items周围添加固定的空间。复制SpacesItemDecoration.java decorator到你的工程并使用addItemDecoration
方法应用到RecyclerView
上。更多信息请参考这篇错落网格教程。
Animators
RecyclerView支持使用ItemAnimator自定义动画对于items的进入,移动,或删除。默认动画效果由DefaultItemAnimator定义,复杂的实现(查看源码)说明了动画效果被执行在指定的顺序(移除,移动和添加)的必要逻辑。
目前,在RecyclerView上使用动画的最快的方法是使用第三方类库。third-party recyclerview-animators library包含了很多你可以直接使用的动画。在app/build.gradle
文件中添加依赖:
1 | repositories { |
然后,我们可以使用所有已定义的动画在RecyclerView上:
1 | recyclerView.setItemAnimator(new SlideInUpAnimator()); |
例如,这有一个滚动列表的自定义动画:
新的ItemAnimator接口
从support library v23.1.0 版本开始,为RecyclerView
的ItemAnimator接口提供了新接口。老的接口已经被弃用为SimpleItemAnimator
。这个类库添加了一个ItemHolderInfo类,和DefaultItemAnimator
定义的MoveInfo类相似,但在动画过渡状态之间传递状态信息更通用。在DefaultItemAnimator
下个版本可能会简化这个新类的使用和修改接口。
异构Views
如果你想在RecyclerView
使用多种类型的行查看这个指导:
这对于在一个列表中包含多种类型的items很有用。
处理触摸事件
RecyclerView可以处理触摸事件:
1 | recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { |
对齐到中心
在某些情况下,我们可能想要一个水平的RecyclerView
可以让用户滚动列表。当用户滚动列表时,我们可以想要显示的items对齐到中心。例如:
LinearSnapHelper
为了实现这个对齐到中心的效果,从support library 24.2.0 版本开始,我们可以使用内置的LinearSnapHelper类:
1 | SnapHelper snapHelper = new LinearSnapHelper(); |
对于更复杂的对齐效果,查看关于自定义这些帮助类和相关示例代码。
SnappyRecyclerView
对于手动实现,我们可以创建一个名为SnappyRecyclerView
继承RecyclerView
的类实现对齐效果:
1.复制SnappyRecyclerView.java类到你的工程。
2.使用水平的LinearLayoutManager
配置新的SnappyRecyclerView
:
1 | LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); |
3.关联adapter到RecyclerView
填充数据到水平列表。
4.你可以使用snappyRecyclerView.getFirstVisibleItemPosition()
获取当前对齐item位置。
这样就可以实现对齐到中心的水平滚动列表!
添加Items的点击处理
使用Decorators添加点击监听
给RecyclerView
配置item点击处理简单的解决方案是使用decorator类管理item点击监听。使用这个ItemClickSupport
decorator,可以这样添加点击处理:
1 | ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener( |
这个类的实现包装了下面描述的接口模型。如果你使用了上面的代码,你 不需要手动处理item点击 。这篇文章概述了这个技术。
在ViewHolder中处理点击
RecyclerView没有专门提供items的点击处理不像ListView有setOnItemClickListener
方法。为了手动实现类似的效果(不是使用上面提到的decorator实现),我们可以在adapter中的ViewHolder
添加点击事件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
// ...
// Used to cache the views within the item layout for fast access
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView tvName;
public TextView tvHometown;
private Context context;
public ViewHolder(Context context, View itemView) {
super(itemView);
this.tvName = (TextView) itemView.findViewById(R.id.tvName);
this.tvHometown = (TextView) itemView.findViewById(R.id.tvHometown);
// Store the context
this.context = context;
// Attach a click listener to the entire row view
itemView.setOnClickListener(this);
}
// Handles the row being being clicked
@Override
public void onClick(View view) {
int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
User user = users.get(position);
// We can access the data within the views
Toast.makeText(context, tvName.getText(), Toast.LENGTH_SHORT).show();
}
}
}
// ...
}
如果你想在item被按下的时候添加选择的效果,我们可以在 行根布局 设置android:background
为?android:attr/selectableItemBackground
:
1 | <?xml version="1.0" encoding="utf-8"?> |
效果如下:
使用Listeners添加事件处理
在某些情况下,你可能想要使用RecyclerView
为view添加事件处理但在Activity
或 Fragment
中定义点击逻辑(例如从adapter暴露事件)。为了实现这个,在adapter中创建自定义监听:
1 | public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ViewHolder> { |
然后使用adapter添加点击处理:
1 | // In the activity or fragment |
查看这个detailed stackoverflow post说明了怎样使用RecyclerView
配置item点击处理。
实现下拉刷新
SwipeRefreshLayout
可以用于刷新RecyclerView
的内容。查看RecyclerView with SwipeRefreshLayout教程实现下拉刷新。
Swipe检测
RecyclerView(从v24.2.0版本)现在有一个OnFlingListener
方法可以用于实现自定义fling行为。下载RecyclerViewSwipeListener添加到RecyclerView你可以处理自定义swip检测:
1 | RecyclerView rvMyList; |
参考
- https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
- http://www.grokkingandroid.com/first-glance-androids-recyclerview/
- http://www.grokkingandroid.com/statelistdrawables-for-recyclerview-selection/
- http://www.bignerdranch.com/blog/recyclerview-part-1-fundamentals-for-listview-experts/
- https://developer.android.com/training/material/lists-cards.html
- http://antonioleiva.com/recyclerview/
- https://code.tutsplus.com/tutorials/getting-started-with-recyclerview-and-cardview-on-android--cms-23465
- https://code.tutsplus.com/tutorials/introduction-to-the-new-lollipop-activity-transitions--cms-23711