MultiItem用法與詳解-優雅的實現多型別RecyclerView Adapter

free46000發表於2019-03-03

前言

RecyclerView是一個大家常用的列表控制元件,在列表中不免會出現多種型別的佈局,這時adapter中多種型別的判斷就會充滿著switch的壞味道,可怕的是需求變更,增加或修改新的型別時,所有的改動都在adapter中進行,沒有一個良好的擴充套件性。
MutliItem主要就是解決這些問題,在正常使用中做到了Adapter零編碼,解放了複雜的Adapter類,本庫提供了多型別和ViewHolder建立繫結的管理器,這樣Adapter通過依賴倒置與列表中的多型別解耦,還提高了擴充套件性。在本庫中不同實體類可以直接當成資料來源繫結到adapter中,你不用去擔心item type的計算,並且對每種型別的ViewHolder也做到了隔離。
本庫的定位並不是大而全,但是會盡量做到簡單易用。

原始碼地址

Github地址:MultiItem,請大家多多關注,更多更新會首先在GitHub上體現,也會在第一時間在本平臺釋出

效果截圖

multi_item
multi_item
chat
chat

用法

新增依賴

  • 配置gradle:

Project rootbuild.gradle中新增:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}複製程式碼

Module中新增(最新版本請在原始碼地址檢視):

dependencies {
    compile 'com.github.free46000:MultiItem:0.9.7'
}複製程式碼
  • 或者你也可以直接克隆原始碼

多種型別列表用法

這裡由於單一和多種型別寫法上沒有差別,所以就不單獨貼出單一型別的列表程式碼了。
註冊多種型別ViewHolderManager,併為adapter設定多種型別資料來源:

//初始化adapter
BaseItemAdapter adapter = new BaseItemAdapter();
//為TextBean資料來源註冊ViewHolderManager管理類
adapter.register(TextBean.class, new TextViewManager());
//為更多資料來源註冊ViewHolderManager管理類
adapter.register(ImageTextBean.class, new ImageAndTextManager());
adapter.register(ImageBean.class, new ImageViewManager());

//組裝資料來源list
List<Object> list = new ArrayList<>();
list.add(new TextBean("AAA"));
list.add(new ImageBean(R.drawable.img1));
list.add(new ImageTextBean(R.drawable.img2, "BBB" + i));

//為adapter註冊資料來源list
adapter.setDataItems(list);

recyclerView.setAdapter(adapter);複製程式碼

ViewHolder管理類的子類TextViewManager類,其他類相似,下面貼出本類全部程式碼,是不是非常清晰:

public class ImageViewManager extends BaseViewHolderManager<ImageBean> {

    @Override
    public void onBindViewHolder(BaseViewHolder holder, ImageBean data) {
        //在指定viewHolder中獲取控制元件為id的view
        ImageView imageView = getView(holder, R.id.image);
        imageView.setImageResource(data.getImg());
    }

    @Override
    protected int getItemLayoutId() {
        //返回item佈局檔案id
        return R.layout.item_image;
    }
}複製程式碼

至此本庫的多種型別列表用法已經完成,並沒有修改或繼承RecyclerView Adapter類,完全使用預設實現BaseItemAdapter即可。

相同資料來源對應多個ViewHolder(聊天介面)

這是一種特殊的需求,需要在執行時通過資料來源中的某個屬性,判斷載入的佈局,典型的就是聊天功能,相同訊息資料對應左右兩種氣泡檢視,在此處貼出註冊時的關鍵程式碼,其他和多種型別列表類似:

//初始化adapter
BaseItemAdapter adapter = new BaseItemAdapter();

//為XXBean資料來源註冊XXManager管理類組合
adapter.register(MessageBean.class, new ViewHolderManagerGroup<MessageBean>(new SendMessageManager(), new ReceiveMessageManager()) {
    @Override
    public int getViewHolderManagerIndex(MessageBean itemData) {
        //根據message判斷是否本人傳送並返回對應ViewHolderManager的index值
        return itemData.getSender().equals(uid) ? 0 : 1;
    }
});

recyclerView.setAdapter(adapter);複製程式碼

設定點選監聽

點選監聽:

adapter.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(BaseViewHolder viewHolder) {
        //通過viewHolder獲取需要的資料
        toastUser(String.format("你點選了第%s位置的資料:%s", viewHolder.getItemPosition()
        , viewHolder.getItemData()));
    }
});複製程式碼

長按監聽:

adapter.setOnItemLongClickListener(new OnItemLongClickListener() {
    @Override
    public void onItemLongClick(BaseViewHolder viewHolder) {
        //通過viewHolder獲取需要的資料
        toastUser(String.format("你長按了第%s位置的資料:%s", viewHolder.getItemPosition()
                , viewHolder.getItemData()));
    }
});複製程式碼

詳解

主要流程

  • 為指定的資料來源註冊ViewHolderManager提供檢視建立繫結等工作
  • 在列表建立的過程中通過資料來源在ItemTypeManager找到對應的ViewHolderManager
  • 按照需要建立與重新整理檢視並對檢視做一些通用處理

ViewHolder管理

ViewHolder管理原始碼類為ViewHolderManager,使用者會首先註冊資料來源和本例項的對應關係,由型別管理類提供統一管理。

  • 提供了引數類,會在adapter呼叫本類方法的時候傳入並做出通用處理
  • 本類的設計使用泛型,是為了在後續回撥方法中有更直觀的型別體現,這也是強型別和泛型帶來的好處,給人在編寫程式碼的時候帶來確定感
  • 本類為抽象類需要重寫ViewHolder的建立與繫結方法,為了方便後續使用,寫了一個簡單的BaseViewHolderManager實現類,請讀者根據業務自行決定是否需要使用更靈活的基類,這裡貼出需要複寫的兩個方法,延續了Adapter中的命名規則,在使用中減少一些認知成本:
/**
 * 建立ViewHolder
 * {@link android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder}
 */
@NonNull
public abstract V onCreateViewHolder(@NonNull ViewGroup parent);

/**
 * 為ViewHolder繫結資料
 * {@link android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder}
 *
 * @param t 資料來源
 */
public abstract void onBindViewHolder(@NonNull V holder, @NonNull T t);複製程式碼

ViewHolder管理組合(相同資料來源對應多個ViewHolderManager)

組合管理原始碼類為ViewHolderManagerGroup,本例項需要一個ViewHolderManager集合,並增加通過資料來源指定哪個ViewHolderManager的方法,使用者同樣會註冊資料來源和本例項的對應關係,由型別管理類對本類中的ViewHolderManager集合進行統一註冊管理。下面貼出關鍵程式碼:

 private ViewHolderManager[] viewHolderManagers;

/**
 * @param viewHolderManagers 相同資料來源對應的所有ViewHolderManager
 */
public ViewHolderManagerGroup(ViewHolderManager... viewHolderManagers) {
    if (viewHolderManagers == null || viewHolderManagers.length == 0) {
        throw new IllegalArgumentException("viewHolderManagers can not be null");
    }
    this.viewHolderManagers = viewHolderManagers;
}

/**
 * 根據item資料來源中的屬性判斷應該返回的對應viewHolderManagers的index值
 *
 * @param itemData item資料來源
 * @return index值應該是在viewHolderManagers陣列有效範圍內
 */
public abstract int getViewHolderManagerIndex(T itemData);複製程式碼

型別管理

型別管理原始碼類為ItemTypeManager,通過資料來源className ListviewHolderManager List兩組集合對型別進行管理,並對Adapter提供註冊和對應關係查詢等方法的支援,這裡並沒有把這個地方設計靈活,如果有一些變化還是希望可以在ViewHolderManager做出適配。

  • 資料來源一對一viewHolderManager時比較簡單,關鍵程式碼:
 /**
 * 通過資料來源`className List`和`viewHolderManager List`兩組集合對型別進行管理
 *
 * @param cls     資料來源class
 * @param manager ViewHolderManager
 * @see com.freelib.multiitem.adapter.BaseItemAdapter#register(Class, ViewHolderManager)
 */
public void register(Class<?> cls, ViewHolderManager manager) {
    register(getClassName(cls), manager);
}複製程式碼
  • 資料來源一對多viewHolderManager時,關鍵程式碼:
/**
 * 通過group獲取一組ViewHolderManager迴圈註冊,並生成不同的className作為標識<br>
 * 其他類似{@link #register(Class, ViewHolderManager)}
 *
 * @param cls   資料來源class
 * @param group 多個ViewHolderManager的組合
 * @see com.freelib.multiitem.adapter.BaseItemAdapter#register(Class, ViewHolderManagerGroup)
 */
public void register(Class<?> cls, ViewHolderManagerGroup group) {
    ViewHolderManager[] managers = group.getViewHolderManagers();
    for (int i = 0, length = managers.length; i < length; i++) {
        register(getClassNameFromGroup(cls, group, managers[i]), managers[i]);
    }
    itemClassNameGroupMap.put(getClassName(cls), group);
}複製程式碼

希望大家會喜歡,多多留言交流

相關文章