前言

本文是我的筆記以及Android的知識積累;

MultiType 的特性

  1. 輕盈,整個類庫只有 14 個類檔案,aar 或 jar 包大小隻有 13 KB
  2. 周到,支援 data type <–> item view binder 之間 一對一 和 一對多 的關係繫結
  3. 靈活,幾乎所有的部件(類)都可被替換、可繼承定製,面向介面 / 抽象程式設計
  4. 純粹,只負責本分工作,專注多型別的列表檢視 型別分發,絕不會去影響 views 的內容或行為
  5. 高效,沒有效能損失,記憶體友好,最大限度發揮 RecyclerView 的複用性
  6. 可讀,程式碼清晰乾淨、設計精巧,極力避免複雜化,可讀性很好,為拓展和自行解決問題提供了基礎

這是對MultiType的特性的釋義,可能用起來沒有什麼問題,但是要知道為什麼,如何實現?請繼續看…

常用寫法

private Adapter extent RecyclerView.Adapter {

    # 資料來源

    # 構造方法

    # onCreatViewHolder(...){}

    # onBindViewHodler(...){}

    # getItemCount(){}

    # getItemViewType(){}

}

實際寫起來可能也沒有什麼大的問題,但是對於多ItemType的情況下,我們需要控制不同的type型別,在後續的需要、業務改動中,可能還需要調整,時間長了,即使是自己所寫的程式碼也會感到無從下手,MultiType就應運而生。

核心思想

  1. 擴充套件 RecyclerView.Adapter 類,
    • 當前所有的資料儲存
      • List data 儲存所有的資料來源
      • List itemDataClassName 儲存所以資料來源型別的 className (能夠唯一標識的,存取規則保持一致即可)
      • List 儲存所有對應與 itemData 管理類
    • 將檢視、資料繫結操作放到 Manager 的具體實現類中去處理
    • 使用 List itemDataClassName 的下標指代 ItemType
  2. 擴充套件 RecyclerView.ViewHolder 類,可做一些功能的拓展
  3. ViewHolderManager 其主要職責:返回檢視、處理資料與檢視的繫結

其中比較新穎、有趣的想法:
- 所有的ItemType有資料型別集合中的下標所自行控制,這就讓我們不用再關心不同position對應的itemType是多少的問題;
- register 操作,對列表所載入資料型別註冊進 adapter

原始碼

結合上面的思想,精簡出了3個java類,就可以基本實現:

/**
 * Adapter的主要實現。
 *
 * @author egan on 2018/4/20.
 * @date 2018/4/20 上午10:26.
 */
public class BaseAdapter extends RecyclerView.Adapter<BaseViewHolder> {

    // 資料來源
    private List data = new ArrayList<>();

    // 儲存 資料來源型別
    private List<String> dataTypes = new ArrayList<>();
    // 儲存 資料來源型別對應的 manager
    private List<ViewHolderManager> dataManagers = new ArrayList<>();

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 根據 viewType 獲取 獲取預訂型別,>> manage >>> 獲得ViewHolder
        // itemViewType 的值即對應 資料型別的下標
        return dataManagers.get(viewType).onCreateViewHolder(parent);
    }

    @Override
    public int getItemViewType(int position) {
        // return super.getItemViewType(position);
        Object o = data.get(position);
        // 根據資料物件去預訂的型別中去獲取對應
        return dataTypes.indexOf(o.getClass().getName());
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        // itemViewType 的值即對應 資料型別的下標
        int index = getItemViewType(position);
        // 獲取 Item 的資料
        Object itemData = data.get(position);
        // 獲取 Item 資料型別對應的 manager
        ViewHolderManager itemViewHolderManager = dataManagers.get(index);
        itemViewHolderManager.onBindViewHolder(itemData, holder);
    }

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

    // 將型別註冊進入 adapter 中,表明該 adapter包含哪些型別
    public void register(Class o, ViewHolderManager manager) {
        if (dataTypes.contains(o.getName())) {
            dataManagers.set(dataTypes.indexOf(o.getName()), manager);
        } else {
            dataTypes.add(o.getName());
            dataManagers.add(manager);
        }
    }

    public void setData(List data) {
        this.data = data;
        notifyDataSetChanged();
    }
}
/**
 * 基本的 ViewHolder,用於自己的處理
 *
 * @author egan on 2018/4/20.
 * @date 2018/4/20 上午10:02.
 */
public class BaseViewHolder extends RecyclerView.ViewHolder {

    public BaseViewHolder(View itemView) {
        super(itemView);
    }

    public int getItemPosition() {
        return this.getAdapterPosition();
    }

}
/**
 * 其子類處理View檢視的返回以及檢視與資料的繫結.
 *
 * @author egan on 2018/4/20.
 * @date 2018/4/20 上午10:10.
 */
public abstract class ViewHolderManager<T> {

    public BaseViewHolder onCreateViewHolder(ViewGroup parent) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(getItemLayoutId(), parent, false);
        return new BaseViewHolder(itemView);
    }

    public abstract void onBindViewHolder(T t, BaseViewHolder viewHolder);

    @LayoutRes
    public abstract int getItemLayoutId();

    private <V extends View> V findViewById(View itemView, @IdRes int viewId) {
        return itemView.findViewById(viewId);
    }

    protected <V extends View> V findViewById(BaseViewHolder viewHolder, @IdRes int viewId) {
        return findViewById(viewHolder.itemView, viewId);
    }
}

用法

測試用實體類.

class Test1 {
    public Test1(String name) {
        this.name = name;
    }

    String name;
}

class Test2 {
    public Test2(int age) {
        this.age = age;
    }

    int age;
}
class Test1Manager extends ViewHolderManager<Test1> {
    @Override
    public void onBindViewHolder(Test1 test1, BaseViewHolder viewHolder) {
        TextView text1 = findViewById(viewHolder, android.R.id.text1);
        TextView text2 = findViewById(viewHolder, android.R.id.text2);

        text1.setText("這是 Test 1");
        text2.setText("姓名 >>> " + test1.name);
    }

    @Override
    public int getItemLayoutId() {
        return android.R.layout.simple_list_item_2;
    }
}

class Test2Manager extends ViewHolderManager<Test2> {
    @Override
    public void onBindViewHolder(Test2 test1, BaseViewHolder viewHolder) {
        TextView text1 = findViewById(viewHolder, android.R.id.text1);
        TextView text2 = findViewById(viewHolder, android.R.id.text2);

        text1.setText("這是 Test 2");
        text2.setText("年齡 >>> " + test1.age);
    }

    @Override
    public int getItemLayoutId() {
        return android.R.layout.simple_list_item_2;
    }
}

具體使用 Demo

// 偽造資料來源
List<Object> allData = new ArrayList<>();
allData.add(new Test1("maka 1"));
allData.add(new Test1("maka 2"));
allData.add(new Test2(1));
allData.add(new Test2(2));

// 建立 Adapter 例項,並註冊 Model 與 Manager 
BaseAdapter baseAdapter = new BaseAdapter();
baseAdapter.register(Test1.class, new Test1Manager());
baseAdapter.register(Test2.class, new Test2Manager());

// 繫結 RecyclerView 與 Adapter
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(baseAdapter);

// 重新整理資料來源
baseAdapter.setData(allData);