RecyclerView Adapter 实现自动多 ViewType

前言

多Type的列表在App中很常见,例如各种电商类App的首页,甚至是购物车、订单详情页面等。我们暂且将页面上每个ViewType对应的模块称之为楼层。那么,以电商订单详情举例,可能有以下楼层:

  • 订单状态(交易成功、交易关闭等)
  • 物流信息
  • 收货地址
  • 订单商品信息列表
  • 价格相关信息
  • 订单信息(订单号、交易流水号等)
  • 其他一些展示信息

那么,我们可以通过不同的 ViewType 来区分这些模块,通常的做法是:

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
public class DetailAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int VIEW_TYPE_EMPTY = 0;
private static final int VIEW_TYPE_HEADER = 1;
private static final int VIEW_TYPE_ADDRESS = 2;
private static final int VIEW_TYPE_GOODS = 3;

private List<Model> mData = new ArrayList<>();

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == VIEW_TYPE_HEADER) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false));
} else if(viewType == VIEW_TYPE_ADDRESS) {
return new AddressViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.address, parent, false));
}
// ...
return new EmptyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.empty, parent, false));
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//
if(getItemViewType(position) == VIEW_TYPE_HEADER) {
// 绑定数据....
HeaderViewHolder headerViewHodlder = (HeaderViewHolder)holder;
} else if(getItemViewType(position) == VIEW_TYPE_ADDRESS) {
AddressViewHolder addressViewHolder = (AddressViewHolder)holder;
// 绑定数据...
}
// ...

// 或者

if(holder instanceof HeaderViewHolder) {
// ...
} else if(holder instanceof AddressViewHolder) {
// ...
}
// ...
}

@Override
public int getItemCount() {
return mData.size();
}

@Override
public int getItemViewType(int position) {
Model model = mData.get(position);
if(条件1) {
return VIEW_TYPE_HEADER;
} else if(条件2) {
return VIEW_TYPE_ADDRESS;
} else if(条件3) {
return VIEW_TYPE_GOODS;
}
return VIEW_TYPE_EMPTY;
}

class EmptyViewHolder extends RecyclerView.ViewHolder {
public EmptyViewHolder(View itemView) {
super(itemView);
}
}

class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
}
}

class AddressViewHolder extends RecyclerView.ViewHolder {
public AddressViewHolder(View itemView) {
super(itemView);
}
}

class GoodsViewHolder extends RecyclerView.ViewHolder {
public GoodsViewHolder(View itemView) {
super(itemView);
}
}
}

上面这种做法主要有以下几个弊端:

1、当 ViewType 种类特别多的情况下,Adapter里面的代码会过于臃肿,难以维护
2、每当需要新增一个 ViewType 的时候,改动范围比较大,几乎涉及到了整个Adapter,多人协作容易代码冲突
3、Adapter 的代码,复用性比较差。每个页面都需要自己重新写一个庞大的 Adapter
4、局部刷新操作实现比较麻烦。比如不通过Model,要动态更改某一个楼层View的展示,就需要查找到对应楼层的 ViewHolder,代码实现要复杂得多
5、如果不同的 ViewType 对应的 Model 类型不一样,那么Adapter里面的复杂度又会相应的上升
6、RecyclerView.ViewHolder 的构造函数,需要把View带进来,这个对于ViewHolder实现来说是不合理的。 为什么这么说呢?因为每个 ViewHolder 对应的是什么样的View,其实应该 ViewHolder 自身最清楚,不应该在 onCreateViewHolder 的时候根据 viewType 来判断要 inflate 什么布局。所以为了实现解耦,这个问题必须优化掉。

逻辑实现

为了解决上述问题,我们可以换个思路来实现。

通常情况下,服务端返回的List里面的Model,可以区分出对应的ViewType,然后对应的创建不同的 ViewHolder。那么,我们为了避免在 Adapter 里面来识别每个 Model 对象所对应的 ViewType,我们可以在组装数据的时候做一层预处理。封装的思路核心就在于以下三点:

1、BaseData, 用于对服务端数据预处理做一层包装,每个 BaseData 对应 Adapter 的一条数据,并且每个 BaseData 应该要知道自己对应 ViewHolder 类。
2、BaseViewHolder, 所有的 ViewHolder 继承的抽象基类,由子类来返回对应的布局、数据绑定等等
3、BaseAdapter, 数据类型是List<BaseData>,根据每个 BaseData对应的 ViewHolderClass,来自动生成对应的 ViewTypeViewHolderClassViewType 是一对一的关系。 onCreateViewHolder时,通过 ViewType 来找到对应的 ViewHolderClass,通过反射创建对应的 ViewHolder。在onBindViewHolder时,调用 BaseViewHolder 的 onBindViewHolder 方法。

BaseData的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class BaseData {

public BaseViewHolder lastViewHolder;

public void setLastViewHolder(BaseViewHolder lastViewHolder) {
// 当数据需要刷新时,可以通过这个找到对应的ViewHolder
this.lastViewHolder = lastViewHolder;
}

// 每一个包装数据,需要知道对应的 ViewHolder 类是什么
public abstract Class<? extends BaseViewHolder> getViewHolderClass();
}

BaseViewHolder 的实现如下:

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
public abstract class BaseViewHolder<T extends BaseData> extends RecyclerView.ViewHolder {

private static FrameLayout createRootLayout(Context context) {
FrameLayout layout = new FrameLayout(context);
layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return layout;
}

protected Context mContext;

protected View mRootView;

public BaseViewHolder(@NonNull Context context) {
// 每个楼层最外围会包一个 FrameLayout,主要是为了避免 BaseViewHolder 创建之前就要先创建好View(懒加载思路)
super(createRootLayout(context));

this.mContext = context;

initView();
}

private void initView() {
mRootView = LayoutInflater.from(mContext).inflate(getLayoutId(), (ViewGroup) itemView, false);
((ViewGroup) itemView).addView(mRootView);

onViewCreated();
}

// 每个 ViewHolder 对应的 Layout,子类实现
@LayoutRes
protected abstract int getLayoutId();

protected abstract void onViewCreated();

@CallSuper
public void onBindViewHolder(T data) {
data.setLastViewHolder(this);
bindData(data);
}

// 绑定数据,子类实现
protected abstract void bindData(T data);
}

BaseAdapter 的实现如下:

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
public abstract class BaseAdapter extends RecyclerView.Adapter<BaseViewHolder> {

protected Context mContext;

// 记录 ViewHolderClass 与 ViewType 映射关系,两个Map主要是为了反向查找更方便
private ArrayMap<Class<? extends BaseViewHolder>, Integer> mItemTypeMap = new ArrayMap<>();
private ArrayMap<Integer, Class<? extends BaseViewHolder>> mViewHolderMap = new ArrayMap<>();

protected List<BaseData> mData = new ArrayList<>();

private int itemViewType = 0;

public BaseAdapter(Context context) {
this.mContext = context;
}

public void setData(List<BaseData> data) {
mData.clear();
if (data != null) {
genItemType(data);
mData.addAll(data);
}
notifyDataSetChanged();
}

public void appendData(List<BaseData> data) {
if (CollectionUtil.isEmpty(data)) {
return;
}
genItemType(data);
int start = mData.size();
mData.addAll(data);
notifyItemRangeInserted(start, data.size());
}

// 自动生成自增长的ViewType
private void genItemType(List<BaseData> data) {
if (data == null) {
return;
}
for (BaseData item : data) {
Class<? extends BaseViewHolder> clazz = item.getViewHolderClass();
if (!mItemTypeMap.containsKey(item.getViewHolderClass())) {
mItemTypeMap.put(clazz, itemViewType);
mViewHolderMap.put(itemViewType, clazz);

itemViewType++;
}
}
}

@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 通过 ViewType 找到对应的 ViewHolderClass
Class<? extends BaseViewHolder> clazz = mViewHolderMap.get(viewType);

BaseViewHolder viewHolder = null;
try {
// 反射创建ViewHolder,解耦
Constructor<? extends BaseViewHolder> ct = clazz.getDeclaredConstructor(Context.class);
ct.setAccessible(true);
viewHolder = ct.newInstance(mContext);
} catch (Exception e) {
e.printStackTrace();
}
return viewHolder;
}

@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
//noinspection unchecked
holder.onBindViewHolder(getItem(position));
}

@Override
public int getItemCount() {
return mData.size();
}

@Override
public int getItemViewType(int position) {
int type = 0;
if (position >= 0 && position < mData.size()) {
// 通过 ViewHolderClass 找到对应的 ViewType
type = mItemTypeMap.get(getItem(position).getViewHolderClass());
}
return type;
}
public BaseData getItem(int position) {
return mData.get(position);
}
}

用法

每一种楼层,我们需要实现 BaseDataBaseViewHolder, 以简单的文本展示做一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NormalTextData extends BaseData {

public String content;

public NormalTextData(String content) {
this.content = content;
}

@Override
public Class<? extends BaseViewHolder> getViewHolderClass() {
return NormalTextViewHolder.class;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class NormalTextViewHolder extends BaseViewHolder<NormalTextData> {

private TextView contentView;

public NormalTextViewHolder(Context context) {
super(context);
}

@Override
protected int getLayoutId() {
return R.layout.hm_order_cell_text;
}

@Override
protected void onViewCreated() {
contentView = findViewById(R.id.text_content);
}

@Override
public void bindData(NormalTextData data) {
contentView.setText(data.content);
}
}
1
2
3
4
5
6
7
List<BaseData> data = new ArrayList<>();
data.add(new NormalTextData("展示一行文本"));

BaseAdapter adapter = new BaseAdapter(context);
adapter.setData(data);

recyclerview.setAdapter(adapter);

以后,每当新增一个楼层,只需要新增两个类,分别实现 BaseDataBaseViewHolder,然后adapter插入对应的 BaseData 数据即可。实现极大程度的解耦。

感谢阅读。