CleanRecyclerViewAdapter-一種新的多樣式RecyclerView的實現方案
RecyclerViewAdapter" target="_blank" rel="nofollow,noindex">Github地址
前言
首先,我想先感謝下MultiType 這個開源專案,我的部分靈感來源於此。
因為業務的需要,我們可能會需要在一個列表中展示非常多樣式的元素,這樣的話,我們會需要寫很多的viewholder,給每種元素宣告一個viewtype,在adapter中寫一長串的判斷語句來createviewholder。如果要展示的專案樣式非常多,那麼我們的adapter會非常臃腫,還需要定義非常多的viewtype。當可能要修改、刪除、新增一個新的樣式的時候,都需要在adapter中做修改,違反了對修改關閉的原則。
優點
-
ViewHolder和Adapter解耦,ViewHolder的管理分佈在多個ViewHolderFactory中, 增刪ViewHolder只需要在相應的ViewHolderFactory中進行。
-
無需定義繁多的ViewType,無需在Adapter中寫很多的判斷語句。
-
便於ViewHolder的複用,在需要將ViewHolderFactory中包含的ViewHolder展示在多個Adapter中時,只需要將該ViewHolderFactory通過註解生成的ViewHolderFactoryListCreator的靜態方法返回的List新增到相應的Adapter中。
-
從現有程式碼切換成本較低,對現有程式碼影響較小。只需要將現有的ViewHolder改自繼承BaseCleanViewHolder,併為每種資料型別增加一個ViewHolderFactory即可。
如何引用
在專案的build.gradle檔案中的dependicies中新增以下依賴。
implementation 'com.baymax.clean_adapter:clean_adapter:0.0.7' implementation 'com.baymax.clean_adapter:clean_adapter_annotation:0.0.7' annotationProcessor 'com.baymax.clean_adapter:clean_adapter_compiler:0.0.7'
如何使用
建立ViewHolder
所有的ViewHolder都需要繼承自BaseCleanViewHolder,並實現onBindViewHolder方法。
public class AppleViewHolder extends BaseCleanViewHolder<Fruit> { private TextView fruitName; public AppleViewHolder(ViewGroup parent, MarketInfo marketInfo) { super(parent, R.layout.layout_apple_viewholder); fruitName = findViewById(R.id.name); } @Override public void onBindViewHolder(Fruit fruit) { fruitName.setText(fruit.name); } }
BaseCleanViewHolder有兩個建構函式。一般來說,重寫帶ViewGroup引數的建構函式,便於在ViewHolderFactory的抽象類中通過反射來初始化ViewHolder。
/** * 一般情況下請重寫本建構函式 * 本建構函式可以確保所有的ViewHolder擁有相同的引數,比如單引數ViewGroup,雙引數ViewGroup和ExtraData, * 這樣可以寫一個實現了{@link IViewHolderFactory}的抽象類 * 實現{@link IViewHolderFactory#create(ViewGroup, Class, Object)}方法,在該方法中使用反射初始化ViewHolder * 這樣可以減少大量的初始化ViewHolder的程式碼 */ public BaseCleanViewHolder(ViewGroup parent, @LayoutRes int layoutId) { super(LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false)); }
而傳入View的建構函式則可以用在組合方式展示ViewHolder的情況。
/** * 一般在用到組合方式的時候重寫本建構函式 * 如果遇到一個數據結構A包含另外一個數據結構B,而B已經有了自己的ViewHolder的情況下, * 可以在A的ViewHolder中使用組合方式引用B的ViewHolder,B的ViewHolder可以利用本建構函式來初始化。 */ public BaseCleanViewHolder(View itemView) { super(itemView); }
BaseCleanViewHolder中還提供了多個便利的方法可以呼叫。
我們可以保證onBindViewHolder傳入的引數,就是範型所指定的資料型別,也可以保證這個引數不為空,所以你可以放心地使用。
建立ViewHolderFactory
ViewHolderFactory需要實現IViewHolderFactory介面。
ViewHolderFactory用來將資料結構及其對應的ViewHolder進行繫結。如果一個列表中有N種資料結構,那麼就應該有N個ViewHolderFactory與其對應。
如果列表中包含了很多ViewHolder需要展示,那麼在create方法中需要寫很多ViewHolder初始化的程式碼,很是臃腫。為了避免這種情況,在確保了所有ViewHolder都擁有相同建構函式及引數的情況下,可以寫一個抽象類來通過反射建立ViewHolder。
public abstract class AbstractFoodMaterialViewHolderFactory<Item> implements IViewHolderFactory<Item, MarketInfo> { @Override public BaseCleanViewHolder create(ViewGroup parent, Class viewHolderClass, MarketInfo marketInfo) { BaseCleanViewHolder viewHolder = null; try { Constructor constructor = viewHolderClass.getConstructor(ViewGroup.class, MarketInfo.class); viewHolder = (BaseCleanViewHolder) constructor.newInstance(parent, marketInfo); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } if (viewHolder == null) { try { Constructor constructor = viewHolderClass.getConstructor(ViewGroup.class); viewHolder = (BaseCleanViewHolder) constructor.newInstance(parent); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } if (viewHolder == null && BuildConfig.DEBUG) { throw new IllegalArgumentException(viewHolderClass + "不是標準建構函式引數,請重寫ViewHolderFactory的create方法自行初始化"); } if (viewHolder == null) { viewHolder = new BaseCleanViewHolder(parent, R.layout.dummy_view_holder) { @Override public void onBindViewHolder(Object o) { } }; } return viewHolder; } }
如果一個數據結構只對應一個ViewHolder,那麼很簡單,如下所示。
@ViewHolderFactory(category = "FoodMaterial") public class VegetableViewHolderFactory extends AbstractFoodMaterialViewHolderFactory<Vegetable> { @Override public Class getItemClass() { return Vegetable.class; } @Override public Class[] getViewHolderClassList() { return new Class[]{ CabbageViewHolder.class }; } @Override public Class getViewHolderClass(Vegetable vegetable) { return CabbageViewHolder.class; } }
如果一個數據結構對應多個ViewHolder,那麼需要在getViewHolderClassList方法中返回所有ViewHolder的class的陣列,並且在getViewHolderClass方法中,根據條件返回不同的ViewHolder的class。
@ViewHolderFactory(category = "FoodMaterial") public class FruitViewHolderFactory extends AbstractFoodMaterialViewHolderFactory<Fruit> { @Override public Class getItemClass() { return Fruit.class; } @Override public Class[] getViewHolderClassList() { return new Class[]{ AppleViewHolder.class, OrangeViewHolder.class }; } @Override public Class getViewHolderClass(Fruit fruit) { switch (fruit.type) { case Fruit.APPLE: return AppleViewHolder.class; case Fruit.ORANGE: return OrangeViewHolder.class; default: return CleanViewHolderGenerateHelper.DummyCleanViewHolder.class; } } }
所有的ViewHolderFactory上都必須新增@ViewHolderFactory註解,該註解有一個category引數,在編譯時會生成一個名為$categoryViewHolderFactoryListCreator的類,如果有多種不同的category,則會生成多個以category開頭的ViewHolderFactoryListCreator類。該類提供了一個靜態方法,返回了包含所有相同category的ViewHolderFactory的List。
public final class FoodMaterialViewHolderFactoryListCreator { public static List<Object> createFactoryList() { List<Object> factoryList = new ArrayList<Object>(); factoryList.add(new FruitViewHolderFactory()); factoryList.add(new MeatViewHolderFactory()); factoryList.add(new FoodMaterialAreaViewHolderFactory()); factoryList.add(new VegetableViewHolderFactory()); return factoryList; } }
如果我們的ViewHolder根據業務邏輯放在了多個不同的Module中,那麼不同Module中的category必須保證不能相同,否則在編譯的時候,會出現不同Module中有同名類導致編譯不過的問題。
或者雖然在一個Module中,但是想做一個區分。也可以定義不同的category。
建立ViewHolderGenerateHelper
在clean_adapter中,有一個名為CleanViewHolderGenerateHelper的類,該類的建構函式接收ViewHolderFactory的List,即我們在上一步編譯之後得到的ViewHolderFactoryListCreator靜態方法返回的結果。
在上一步中,我們在編譯之後會得到一個或者多個ViewHolderFactoryListCreator,假如這些ViewHolderFactory包含的ViewHolder都想在一個列表中展示,那麼可以將這些ViewHolderFactoryListCreator返回的結果合併到一個List並傳入ViewHolderGenerateHelper中。為了便於管理,建議為每個Adapter建立一個自己的ViewHolderGenerateHelper。如果後期有新加的category對應的ViewHolder要展示,那麼修改這個類即可。
public class FoodMaterialViewHolderGenerateHelper implements IViewHolderGenerateHelper<MarketInfo> { private CleanViewHolderGenerateHelper<MarketInfo> cleanViewHolderGenerateHelper; public FoodMaterialViewHolderGenerateHelper() { List<Object> foodMaterialList = new ArrayList<>(); foodMaterialList.addAll(FoodMaterialViewHolderFactoryListCreator.createFactoryList()); cleanViewHolderGenerateHelper = new CleanViewHolderGenerateHelper<>(foodMaterialList); } @Override public int getItemType(Object item) { return cleanViewHolderGenerateHelper.getItemType(item); } @Override public BaseCleanViewHolder createViewHolder(ViewGroup parent, int itemType, MarketInfo marketInfo) { return cleanViewHolderGenerateHelper.createViewHolder(parent, itemType, marketInfo); } }
如果有一組ViewHolder需要展示在多個列表中,那麼可以很方便的進行復用,只需要將其對應的ViewHolderFactoryListCreator生成的List新增到不同的Adapter中就可以實現了。
建立Adapter
我們提供了一個基類BaseCleanAdapter,如果沒有什麼特別的需求,那麼你的Adapter實際上很簡單,繼承BaseCleanAdapter,指定範型即可,沒有別的需要做的了。
public class MarketAdapter extends BaseCleanAdapter<Object, MarketInfo> { public MarketAdapter(IViewHolderGenerateHelper<MarketInfo> viewHolderGenerateHelper, MarketInfo marketInfo) { super(viewHolderGenerateHelper, marketInfo); } }
展示列表
好了,下面可以使用我們的Adapter來展示資料了。只需要給Adapter傳入我們之前建立的對應的ViewHolderGenerateHelper即可。
MarketInfo marketInfo = new MarketInfo("Baymax SuperMarket"); MarketAdapter marketAdapter = new MarketAdapter(new FoodMaterialViewHolderGenerateHelper(), marketInfo); RecyclerView foodMaterialList = findViewById(R.id.food_material_list); foodMaterialList.setAdapter(marketAdapter); foodMaterialList.setLayoutManager(new LinearLayoutManager(this)); marketAdapter.updateData(createMarketData(), null);
ExtraData
在前面的介紹中,可以看到有一個ExtraData的範型。
平時在我們的業務開發中,可能會遇到一些需要在ViewHolder中訪問上級資料的情況,比如Avtivity、Fragment中的資料,僅僅通過onBindViewHolder傳入的引數是不夠的。那麼可以定義一個數據結構,將ViewHolder中可能需要的資料封裝進去,通過ViewHolder的建構函式傳入。
總結
使用本專案,可以比較方便地從現有的程式碼進行切換,可以方便地對不同業務型別的列表展示元素進行管理,可以方便地在多個列表中複用ViewHolder,可以方便地增刪ViewHolder,減少程式碼耦合。