前言
多Type的列表在App中很常見,例如各種電商類App的首頁,甚至是購物車、訂單詳情頁面等。我們暫且將頁面上每個ViewType對應的模塊稱之爲樓層。那麼,以電商訂單詳情舉例,可能有以下樓層:
- 訂單狀態(交易成功、交易關閉等)
- 物流信息
- 收貨地址
- 訂單商品信息列表
- 價格相關信息
- 訂單信息(訂單號、交易流水號等)
- 其他一些展示信息
那麼,我們可以通過不同的 ViewType 來區分這些模塊,通常的做法是:
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
的實現如下:
public abstract class BaseData {
public BaseViewHolder lastViewHolder;
public void setLastViewHolder(BaseViewHolder lastViewHolder) {
// 當數據需要刷新時,可以通過這個找到對應的ViewHolder
this.lastViewHolder = lastViewHolder;
}
// 每一個包裝數據,需要知道對應的 ViewHolder 類是什麼
public abstract Class<? extends BaseViewHolder> getViewHolderClass();
}
BaseViewHolder
的實現如下:
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
的實現如下:
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);
}
}
用法
每一種樓層,我們需要實現 BaseData
和 BaseViewHolder
, 以簡單的文本展示做一個例子:
public class NormalTextData extends BaseData {
public String content;
public NormalTextData(String content) {
this.content = content;
}
@Override
public Class<? extends BaseViewHolder> getViewHolderClass() {
return NormalTextViewHolder.class;
}
}
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);
}
}
List<BaseData> data = new ArrayList<>();
data.add(new NormalTextData("展示一行文本"));
BaseAdapter adapter = new BaseAdapter(context);
adapter.setData(data);
recyclerview.setAdapter(adapter);
以後,每當新增一個樓層,只需要新增兩個類,分別實現 BaseData 和 BaseViewHolder,然後adapter插入對應的 BaseData 數據即可。實現極大程度的解耦。
感謝閱讀。