RecyclerView Adapter 實現自動多 ViewType

前言

多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,來自動生成對應的 ViewTypeViewHolderClassViewType 是一對一的關係。 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);
    }
}

用法

每一種樓層,我們需要實現 BaseDataBaseViewHolder, 以簡單的文本展示做一個例子:

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);

以後,每當新增一個樓層,只需要新增兩個類,分別實現 BaseDataBaseViewHolder,然後adapter插入對應的 BaseData 數據即可。實現極大程度的解耦。

感謝閱讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章