一个易维护的RecyclerView.Adapter的实现

使用 RecyclerView 实现下面的效果:

  1. 有两种布局,红色布局和蓝色布局
  2. 偶数位置的是红色布局,奇数位置的是蓝色布局

按照一般的方法来实现 Adapter 的话,代码如下

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int TYPE_RED = 0;
private static final int TYPE_BLUE = 1;

private List<String> mDataSource;

public MyAdapter(List<String> dataSource) {
mDataSource = dataSource;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_RED) {
return new RedViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_red, parent, false));
} else {
return new BlueViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_blue, parent, false));
}
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == TYPE_RED) {
RedViewHolder redViewHolder = (RedViewHolder) holder;
redViewHolder.tv.setText(mDataSource.get(position));
} else {
BlueViewHolder blueViewHolder = (BlueViewHolder) holder;
blueViewHolder.tv.setText(mDataSource.get(position));
}
}

@Override
public int getItemCount() {
return mDataSource == null ? 0 : mDataSource.size();
}

@Override
public int getItemViewType(int position) {
if (position % 2 == 0) {
return TYPE_RED;
} else {
return TYPE_BLUE;
}
}

private static class RedViewHolder extends RecyclerView.ViewHolder {

TextView tv;

RedViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}

private static class BlueViewHolder extends RecyclerView.ViewHolder {

TextView tv;

BlueViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
}

因为有多种布局,所以重点关注的是如何判断在某一个位置要如何展示哪一个布局,RecyclerView.Adapter 提供了 int getItemViewType(int position) 方法让我们重写,给每一个布局指定一个唯一标识 ItemViewType

按照效果图:偶数位置的是红色布局,奇数位置的是蓝色布局,所以方法重写如下

1
2
3
4
5
6
7
8
9
10
11
12
13
// 布局的唯一标识
private static final int TYPE_RED = 0;
private static final int TYPE_BLUE = 1;

// 布局的判断
@Override
public int getItemViewType(int position) {
if (position % 2 == 0) {
return TYPE_RED;
} else {
return TYPE_BLUE;
}
}

有了布局的唯一标识 ItemViewType 之后,就可以在
RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 中根据这个唯一标识,创建和绑定不同的 ViewHolder 了。

缺陷

这个写法没什么毛病,但是在维护上会有一些问题。如果将来要增加一个新的布局,假设位置的尾数逢 5 的地方需要显示绿色布局,就需要改动几个地方

  1. 增加或布局的唯一标识,也就是改动这个地方
1
2
3
private static final int TYPE_RED = 0;
private static final int TYPE_BLUE = 1;
private static final int TYPE_GREEN = 2; // 增加一个标识
  1. 增加一个 ViewHolder
1
2
3
4
5
6
7
8
9
private static class GreenViewHolder extends RecyclerView.ViewHolder {

TextView tv;

GreenViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
  1. 修改 int getItemViewType(int position) 的 if-else 判断
1
2
3
4
5
6
7
8
9
10
11
@Override
public int getItemViewType(int position) {
// 这里的 if - else 判断需要修改
if (position % 2 == 0) {
return TYPE_RED;
} else if (position % 5 == 0){
return TYPE_GREEN;
} else {
return TYPE_BLUE;
}
}
  1. 修改 RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 方法,增加一个 ViewHolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_RED) {
return new RedViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_red, parent, false));
} else if (viewType == TYPE_GREEN){
// 增加一个 GreenViewHolder
return new GreenViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_green, parent, false));
} else {
return new BlueViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_blue, parent, false));
}
}
  1. 修改 void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 方法,处理 GreenViewHolder 的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == TYPE_RED) {
RedViewHolder redViewHolder = (RedViewHolder) holder;
redViewHolder.tv.setText(mDataSource.get(position));
} else if (holder.getItemViewType() == TYPE_GREEN){
// 处理 GreenViewHolder 的逻辑
GreenViewHolder greenViewHolder = (GreenViewHolder) holder;
greenViewHolder.tv.setText(mDataSource.get(position));
} else {
BlueViewHolder blueViewHolder = (BlueViewHolder) holder;
blueViewHolder.tv.setText(mDataSource.get(position));
}
}

发现问题

可以看到增加一个布局,需要改动很多地方。问题的根源就在于 if-else 判断这一块代码。如果让一个类来帮我们在 int getItemViewType(int position) 方法中返回唯一标识,同时在 RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 方法中帮我们根据 itemViewType 拿到 ViewHolder,这样就能大幅度地减少代码。

实现

可以看到 itemViewType 和 ViewHolder 是一一对应的关系,那么就可以使用 HashMap<Integer, RecyclerView.ViewHolder> 来存放 itemViewType 和 ViewHolder。

接着再定义一个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AdapterDelegate<T> {

/**
* 确定唯一标识
*/
boolean isForViewType(List<T> items, int position);

/**
* 创建 ViewHolder
*/
RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

/**
* 绑定 ViewHolder
*/
void onBindViewHolder(List<T> items, int position, RecyclerView.ViewHolder holder);
}

把确定唯一标识、创建 ViewHolder 和 绑定 ViewHolder 的工作交给实现接口的具体的 ViewHolder 来实现。

最终效果

MyAdapter 现在只有这些代码

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
33
34
35
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private AdapterDelegatesManager<String> mAdapterDelegatesManager;

private List<String> mDataSource;

public MyAdapter(List<String> dataSource) {
mDataSource = dataSource;
mAdapterDelegatesManager = new AdapterDelegatesManager<>();
mAdapterDelegatesManager // 在这里绑定 ViewHolder
.addDelegate(new RedViewHolder())
.addDelegate(new GreenViewHolder())
.addDelegate(new BlueViewHolder());
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return mAdapterDelegatesManager.onCreateViewHolder(parent, viewType);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
mAdapterDelegatesManager.onBindViewHolder(mDataSource, position, holder);
}

@Override
public int getItemCount() {
return mDataSource == null ? 0 : mDataSource.size();
}

@Override
public int getItemViewType(int position) {
return mAdapterDelegatesManager.getItemViewType(mDataSource, position);
}
}

剩下的就是定义 ViewHolder 实现 AdapterDelegate 接口就行

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* RedViewHolder
*/
private static class RedViewHolder implements AdapterDelegate<String> {

@Override
public boolean isForViewType(List<String> items, int position) {
return position % 2 == 0;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_red, parent, false));
}

@Override
public void onBindViewHolder(List<String> items, int position, RecyclerView.ViewHolder holder) {
ViewHolder redViewHolder = (ViewHolder) holder;
redViewHolder.tv.setText(items.get(position));
}

private class ViewHolder extends RecyclerView.ViewHolder {

TextView tv;

ViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
}


/**
* BlueViewHolder
*/
private static class BlueViewHolder implements AdapterDelegate<String> {

@Override
public boolean isForViewType(List<String> items, int position) {
return position % 2 != 0;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_blue, parent, false));
}

@Override
public void onBindViewHolder(List<String> items, int position, RecyclerView.ViewHolder holder) {
ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.tv.setText(items.get(position));
}

private class ViewHolder extends RecyclerView.ViewHolder {

TextView tv;

ViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
}


/**
* GreenViewHolder
*/
private static class GreenViewHolder implements AdapterDelegate<String> {

@Override
public boolean isForViewType(List<String> items, int position) {
return position % 5 == 0;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_green, parent, false));
}

@Override
public void onBindViewHolder(List<String> items, int position, RecyclerView.ViewHolder holder) {
ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.tv.setText(items.get(position));
}

private class ViewHolder extends RecyclerView.ViewHolder {

TextView tv;

ViewHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
}

核心代码的实现

这个类是按照 JOE’S GREAT ADAPTER HELL ESCAPE 介绍的思想来实现的,核心指出就在于 itemViewType 和 ViewHolder 是一一对应的关系

AdapterDelegate 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

public interface AdapterDelegate<T> {

boolean isForViewType(List<T> items, int position);

RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

void onBindViewHolder(List<T> items, int position, RecyclerView.ViewHolder holder);
}

AdapterDelegatesManager 的实现

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.ViewGroup;

import java.util.List;

public class AdapterDelegatesManager<T> {

/**
* Key: ItemViewType
* Value: AdapterDelegate
*/
private SparseArray<AdapterDelegate<T>> mDelegates = new SparseArray<>();

public AdapterDelegatesManager<T> addDelegate(AdapterDelegate<T> delegate) {
if (delegate == null) {
throw new NullPointerException("AdapterDelegate of item can not be null");
} else {
int newItemViewType = mDelegates.size();
mDelegates.put(newItemViewType, delegate);
}
return this;
}

public int getItemViewType(List<T> items, int position) {
int delegateCount = mDelegates.size();
for (int itemViewType = 0; itemViewType < delegateCount; itemViewType++) {
AdapterDelegate<T> delegate = mDelegates.valueAt(itemViewType);
if (delegate.isForViewType(items, position)) {
return itemViewType;
}
}
throw new IllegalArgumentException(
"No AdapterDelegate added that matches position=" + position + " in data source");
}

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
AdapterDelegate delegate = mDelegates.get(viewType);
if (delegate == null) {
throw new NullPointerException("No AdapterDelegate added for ViewType " + viewType);
} else {
return delegate.onCreateViewHolder(parent);
}
}

public void onBindViewHolder(List<T> items, int position, RecyclerView.ViewHolder viewHolder) {
AdapterDelegate<T> delegate = mDelegates.get(viewHolder.getItemViewType());
if (delegate == null) {
throw new NullPointerException(
"No AdapterDelegate added for ViewType " + viewHolder.getItemViewType());
} else {
delegate.onBindViewHolder(items, position, viewHolder);
}
}
}

参考来源

http://hannesdorfmann.com/android/adapter-delegates
https://github.com/sockeqwe/AdapterDelegates