使用RecyclerView,一句代码就够了
作者:互联网
## 前言
RecyclerView出来有好几年了,它的重要性不言而喻。然而RecyclerView只提供了基本的View复用功能,相关功能如刷新、点击等都需要开发者自己实现,每个项目实现一遍RecyclerView功能集成又无必要,因此出现了许多RecyclerView封装的“轮子”,Github上一搜多如牛毛。
## 简介
轮子虽多,各有特点。有时候还是自己造的最适合,OneRecyclerView这个轮子的特点如下:
- 用很少的代码(主要Java代码300多行)实现了RecyclerView集成的大部分功能,包括下拉刷新、加载更多、多种ViewType、多列显示、自定义HeaderView和空数据EmptyView
- 实现多种ViewType的方式很巧妙,不需要复杂的映射关系,不需要注册类型,不需要反射
- 一句代码即可调用,多种ViewType也是如此
## 效果图
![orv_base.gif](http://upload-images.jianshu.io/upload_images/1896166-93c24e523c58b95c.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)![orv_types.gif](http://upload-images.jianshu.io/upload_images/1896166-4b83acf88ac22f71.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)![orv_columns.gif](http://upload-images.jianshu.io/upload_images/1896166-ed40f129970be276.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)![orv_empty.gif](http://upload-images.jianshu.io/upload_images/1896166-a837134846d73388.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 使用方法
1. 在布局文件中添加OneRecyclerView
```xml
```
2. 实现自定义ViewHolder
```java
class UserInfoVH extends OneVH {
public UserInfoVH(ViewGroup parent) {//1.设置item布局文件
super(parent, R.layout.item_user_simple);
}
@Override
public void bindView(int position, final UserInfo o) {//2.处理点击事件和设置数据
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), o.getName(), Toast.LENGTH_SHORT).show();
}
});
TextView tvName = itemView.findViewById(R.id.tv_name);
tvName.setText(o.getName());
}
}
```
包括item的布局文件、给item设置数据和点击事件
3. 一句代码使用OneRecyclerView
```java
mOneRecyclerView.init(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestData(false);
}
},
new OneLoadingLayout.OnLoadMoreListener() {
@Override
public void onl oadMore() {
requestData(true);
}
},
new OnCreateVHListener() {
@Override
public OneVH onCreateHolder(ViewGroup parent) {
return new UserInfoVH(parent);
}
@Override
public boolean isCreate(int position, Object o) {
return position % 3 > 0;
}
}
);
```
调用OneRecyclerView的init()方法,传入下拉刷新监听、加载更多监听和创建ViewHolder监听即可。
> OneRecyclerView的init()方法最后一个参数是可变参数,针对多种ItemType情况:
实现多个ViewHolder,用OnCreateVHListener包装并传入
4. 添加自定义header
```java
View header = View.inflate(this, R.layout.layout_header, null);
mOneRecyclerView.addHeader(header);
```
> 可添加多个header,多次调用addHader()方法即可,header的显示完全由自身控制
5. 设置多列显示的列数
```java
mOneRecyclerView.setSpanCount(3);
```
> 不设置默认是1列;多列显示与多种ViewType一般不会同时用到,根据具体需求选择其一
## 原理分析
### 下拉刷新
使用官方的SwipeRefreshLayout
```xml
```
布局文件中使用SwipeRefreshLayout,在里面放入RecyclerView,然后setOnRefreshListener设置刷新监听
### 加载更多
给RecyclerView.Adapter的`ItemCount + 1`,并使用一个单独的ViewType。当划到底部时,显示一个loading布局;获取到数据后再隐藏这个布局
```xml
<framelayout android:layout_height="wrap_content" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
```
这里只放了一个ProgressBar,可以自定义
### ViewHolder封装
对于一个通用的库来说:
1. 外部调用者使用的数据类型
2. RecyclerView的Item布局
3. Item加载数据的方式
4. Item点击事件的处理
这几点都是不确定的,第一点需要使用泛型,即由调用者定义数据类型;后面三点都可以在ViewHolder中处理。这里封装一个继承自`RecyclerView.ViewHolder`的抽象类`OneVH`,具体由调用者实现
```java
public abstract class OneVH extends RecyclerView.ViewHolder {
public OneVH(View itemView) {
super(itemView);
}
public OneVH(ViewGroup parent, int layoutRes) {
super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
}
public abstract void bindView(int position, T t);
}
```
一个简单的`OneVH`实现类如下
```java
class TextVH extends OneVH {
public TextVH(ViewGroup parent) {//1.设置item布局文件
super(parent, android.R.layout.simple_list_item_1);
}
@Override
public void bindView(int position, final UserInfo o) {//2.处理点击事件和设置数据
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), o.getName(), Toast.LENGTH_SHORT).show();
}
});
TextView tvName = itemView.findViewById(android.R.id.text1);
tvName.setText(o.getName());
tvName.setBackgroundColor(Color.GREEN);
}
}
```
### OnCreateVHListener封装
封装好ViewHolder之后,并不是直接创建ViewHolder对象传入Adapter的,因为Adapter中的onCreateViewHolder方法创建ViewHolder的时机和数量是不确定的,所以需要定义一个接口OnCreateVHListener
```java
public interface OnCreateVHListener{
/**
* 创建ViewHolder
* @param parent RecyclerView
* @return S extends OneVH
*/
S onCreateHolder(ViewGroup parent);
}
```
Adapter中需要创建ViewHolder时,调用OnCreateVHListener的onCreateHolder方法,返回一个自定义的OneVH实现对象
```java
@Override
public S onCreateViewHolder(ViewGroup parent, int viewType) {
...
return onCreateVHListener.onCreateHolder(parent);
}
```
> 因为OnCreateVHListener能返回具体OneVH实现对象,所以Adapter只依赖OnCreateVHListener,不依赖OneVH。也就是说外部调用者只需要传一个OnCreateVHListener实现类给OneRecyclerView就行(OneRecyclerView传给Adapter)
---
###### 经过以上步骤,已经能很方便地使用单一ViewType的RecyclerView了。调用者只要实现自己的ViewHolder,不需要关心其他。下面介绍在此基础上多种ViewType的实现 ######
---
### 实现多种ViewType
首先分析一下ViewType的原理。Adapter中跟ViewType相关的主要是`getItemViewType()`和`onCreateViewHolder()`方法
```java
@Override
public int getItemViewType(int position) {
}
@Override
public S onCreateViewHolder(ViewGroup parent, int viewType) {
}
```
`getItemViewType()`方法用于确定当前位置的item属于哪种类型,返回一个表示类型viewType的int值
`onCreateViewHolder()`方法则是根据类型viewType获取对应的ViewHolder
常规思路思考,这里需要记录两种对应关系:
1. 位置position与viewType的对应关系
2. viewType与ViewHolder的对应关系
那就需要两个Map来保存,也许再加一个类管理它们。能否将这两个Map合并成一个?或者根本不用Map?
**先考虑第二个对应关系**,由于前面已经将ViewHolder类型绑定到OnCreateVHListener接口上,不同的viewType也就对应了不同的OnCreateVHListener对象。比如有3种Item类型,那么就有3个OnCreateVHListener对象,这3个viewType用3个不同int值分别对应。
其实用Map是一种冗余,3个或更多int值完全可以用`0,1,2...`表示,那么3个OnCreateVHListener可以直接用`List`保存,每个OnCreateVHListener在List中的序号就是它的viewType!
**接着考虑第一个对应关系**,根据position获取viewType值,在有了`List`的基础上,viewType就是OnCreateVHListener在List中的序号。直接获取这个序号并不好实现,能否先获取OnCreateVHListener,再遍历`List`获得它的位置?
根据position获取OnCreateVHListener也不方便,这个对应关系是调用者定义的,需要给外部提供一种很自然的定义方式,而不是注册类型或定义一个Manager类。**其实可以不用考虑对应关系,每个OnCreateVHListener只需要知道对应的position是不是自己就行了。**
这样在OnCreateVHListener接口中添加一个`isCreate(int position, T t)`方法,参数是位置position和对应位置的数据,调用者通过这两个参数判断该位置是不是对应的ViewHolder
```java
public interface OnCreateVHListener{
/**
* 创建ViewHolder
* @param parent RecyclerView
* @return S extends OneVH
*/
S onCreateHolder(ViewGroup parent);
/**
* 根据当前位置或数据判断是否创建S类型的ViewHolder
* @param position
* @param t
* @return
*/
boolean isCreate(int position, T t);
}
```
在Adapter的getItemViewType方法中遍历`List`,调用`isCreate()`方法,如果结果是true,就返回当前序号,这个序号就是viewType
```java
@Override
public int getItemViewType(int position) {
...
int pos = position - headerVHList.size();
T t = data.get(pos);
for(int i = 0; i < listeners.size(); i++){
OnCreateVHListener<s,t> listener = listeners.get(i);
if(listener.isCreate(pos, t)){
return i;
}
}
return TYPE_NORMAL_MIN;
}
```
最终,position、viewType、ViewHolder、OnCreateVHListener就全部关联起来了。代价只是在Adapter中添加一个`List`(初始化时传进来)、OnCreateVHListener接口中添加一个方法。
> 虽然在getItemViewType方法里进行了遍历操作,但是考虑到99%的列表Item类型是个位数,而且判断类型不是耗时操作,带来的性能影响可以忽略不计
OnCreateVHListener里面定义的两个方法,不仅关联了ViewHolder类型,还关联了与position的对应关系。外部调用者使用几种Item类型,传入几个OnCreateVHListener实现类就行了。实际使用如下
```java
mOneRecyclerView.init(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestData(false);
}
},
new OneLoadingLayout.OnLoadMoreListener() {
@Override
public void onl oadMore() {
requestData(true);
}
},
new OnCreateVHListener() {
@Override
public OneVH onCreateHolder(ViewGroup parent) {
return new UserInfoVH(parent);
}
@Override
public boolean isCreate(int position, Object o) {
return position % 3 > 0;
}
},
new OnCreateVHListener() {
@Override
public OneVH onCreateHolder(ViewGroup parent) {
return new TextVH(parent);
}
@Override
public boolean isCreate(int position, Object o) {
return position % 3 == 0;
}
}
);
```
`OneRecyclerView.init()`方法前面两个参数分别是下拉刷新和加载更多的回调,后面两个参数给OneRecyclerView的初始化方法传入了两个OnCreateVHListener,分别对应两个OneVH子类:UserInfoVH和TextVH。前者的`isCreate()`在`position % 3 > 0`时返回true,后者的`isCreate()`在`position % 3 == 0`时返回true。也就是位置`0,3,6,9...`显示TextVH对应的布局,位置`1,2,4,5,7,8...`显示UserInfoVH对应的布局。这是一种交替显示的效果(如最上面图二orv_types.gif所示)。
## 总结
说了这么多,其实实现代码并不复杂,只用到常见的继承、封装、多态、接口、抽象类、泛型,数据结构只用到List。原因一方面是软件设计的高级技术自己还有待学习;另一方面,的确,实现一个具备常见功能、简单易用的RecyclerView小框架,这些就够了。自己实现一遍或者研究一遍代码会对RecyclerView的原理和的Java基础技术有较好的理解。
Github地址如下,欢迎`fork`和`star`
> https://github.com/rome753/OneRecyclerView
标签:ViewHolder,parent,int,代码,就够,OnCreateVHListener,position,RecyclerView,public 来源: https://www.cnblogs.com/rome753/p/16491459.html