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,来自动生成对应的 ViewType。ViewHolderClass 与 ViewType 是一对一的关系。 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) { this.lastViewHolder = lastViewHolder; }
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) { super(createRootLayout(context));
this.mContext = context;
initView(); }
private void initView() { mRootView = LayoutInflater.from(mContext).inflate(getLayoutId(), (ViewGroup) itemView, false); ((ViewGroup) itemView).addView(mRootView);
onViewCreated(); }
@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;
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()); }
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) { Class<? extends BaseViewHolder> clazz = mViewHolderMap.get(viewType);
BaseViewHolder viewHolder = null; try { 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) { 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()) { type = mItemTypeMap.get(getItem(position).getViewHolderClass()); } return type; } public BaseData getItem(int position) { return mData.get(position); } }
|
用法
每一种楼层,我们需要实现 BaseData和 BaseViewHolder, 以简单的文本展示做一个例子:
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);
|
以后,每当新增一个楼层,只需要新增两个类,分别实现 BaseData 和 BaseViewHolder,然后adapter插入对应的 BaseData 数据即可。实现极大程度的解耦。
感谢阅读。