RecyclerView多型別封裝實現之V-Holder
最近恰好專案中用到的RV多種型別,然後把這塊做了一些簡要的封裝,市面上也有很多這方面的實現,感覺有些用著不是很爽,於是就開始造輪子,雖然說不提倡造輪子。
進入正題咋們先看看RecyclerView多型別處理傳統的寫法:
首先定義三種常量表示三種item型別
public static final int TYPE_1 = 0; public static final int TYPE_2 = 1; public static final int TYPE_3 = 2;
然後重寫getItemViewType方法 根據條件返回對應的item的型別
@Override public final int getItemViewType(int position) { TypeBean mTypeBean = mData.get(position) if(mTypeBean == 0){ return TYPE_1; }else if(mTypeBean == 01){ return TYPE_2; }else{ return TYPE_3; } }
上面定義不同型別常量之後並且getItemViewType返回了對應的型別常量,於是在onCreateViewHolder里根據viewtype來判斷 所引用的item佈局型別並且建立不同佈局的holder。
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; //根據viewType來判斷不同的佈局並且建立其ViewHolder if (viewType == TYPE_PULL_IMAGE) { view =View.inflate(parent.getContext(),R.layout.item_1,null); return new ItemHolder1(view); } else if (viewType == TYPE_RIGHT_IMAGE) { view =View.inflate(parent.getContext(),R.layout.item_2,null); return new ItemHolder2(view); } else { view =View.inflate(parent.getContext(),R.layout.item_3,null); return new ItemHolder3(view); } }
在onBindViewHolder方法中繫結資料。
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { }
建立對應的ViewHolder
//ItemHolder1 static class ItemHolder1 extends RecyclerView.ViewHolder { public ItemHolder1(View view) { super(view); //初始化控制元件 ... } } //ItemHolder2 static class ItemHolder2 extends RecyclerView.ViewHolder { public ItemHolder2(View view) { super(view); //初始化控制元件 ... } } //ItemHolder3 static class ItemHolder3 extends RecyclerView.ViewHolder { public ItemHolder3(View view) { super(view); //初始化控制元件 ... } }
這樣傳統的多型別RecyclerView大致可以實現了,但是如果每一個頁面都諸如此類的寫下去,相對來說非常繁瑣,並且程式碼冗餘量也非常大,同時複用性也非常差,於是乎,便對其提取,封裝,首先我們肯定是要對getItemViewType()方法進行處理,避免定義大量型別的常量,此為問題一。其次就是不同型別的ViewHolder建立,抽取onCreateViewHolder中程式碼,此為問題二。先把ViewHolde抽象:
public abstract class VHolder<T, VH extends ViewHolder> { protected abstract @NonNull VH onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent); protected abstract void onBindViewHolder(@NonNull VH holder, @NonNull T item); protected void onBindViewHolder(@NonNull VH holder, @NonNull T item, @NonNull List<Object> payloads) { onBindViewHolder(holder, item); } }
VHolder中泛型引數是資料Bean和ViewHolder子類,定義了2個抽象方法onCreateViewHolder和onBindViewHolder延用了官方的形式,因此我們的ViewHolder需繼承VHolder,對VHolder進一步抽取封裝
public abstract class AbsItemHolder<T, VH extends AbsHolder> extends VHolder<T, VH> { protected Context mContext; public AbsItemHolder(Context context) { this.mContext = context; } @Override protected @NonNull VH onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return createViewHolder(inflater.inflate(getLayoutResId(), parent, false)); } public abstract int getLayoutResId(); public abstract VH createViewHolder(View view); } public abstract class AbsHolder extends RecyclerView.ViewHolder { private final SparseArray<View> views; public View convertView; public AbsHolder(final View view) { super(view); this.views = new SparseArray<>(); this.convertView = view; } public <T extends View> T getViewById(@IdRes int viewId) { View view = views.get(viewId); if (view == null) { view = itemView.findViewById(viewId); views.put(viewId, view); } return (T) view; } }
至此我們的AbsItemHolder基類編寫完成。
一般來說通常情況下,客戶端展示的型別基本資料都是和服務端提前約定好的返回什麼樣資料結構。因此客戶端要展示什麼樣的UI基本都是定死的,那麼也就是說假如約定的有8中型別的UI,那麼客戶端展示的UI型別就是小於或等於8種類型。那麼我們期望對於這種多型別的item是一個可配置的,接下來我們通過Builder模式來配置。寫一個DelegateAdapter繼承自系統的RecyclerView.Adapter,在其內部寫一個靜態的Builder類,
public class DelegateAdapter extends RecyclerView.Adapter<ViewHolder> { privateList<Class<?>> classes; private List<VHolder<?, ?>> vHolders public DelegateAdapter(Builder builder) { this.classes = builder.classes; this.vHolders = builder.vHolders; } public static class Builder<T> { private final List<Class<?>> classes; private final List<VHolder<?, ?>> vHolders; public Builder() { this.classes = new ArrayList<>(); this.vHolders = new ArrayList<>(); } public Builder bind(Class<? extends T> clazz, VHolder itemView) { classes.add(clazz); vHolders.add(binder); return this; } public DelegateAdapter build() { return new DelegateAdapter(this); } } }
我們希望結果是這樣,乾淨利落。
DelegateAdapteradapter = new DelegateAdapter.Builder() .bind(Item1.class, new ItemHolder1(this)) .bind(Item2.class, new ItemHolder2(this)) .bind(Item3.class, new ItemHolder3(this)) .build();
到這裡我們發現對應的不同型別的Item.class和ItemHolder都是是有序的儲存在list裡面,那麼我們是不是可以通過獲取list的索引作為我們的getItemViewType的型別表示呢,答案肯定是No problem,
@Override public final int getItemViewType(int position) { //獲取position位置對應的Item.class Object itemBean=datas.get(position); //找出Item.class在classes中儲存的index int index = classes.indexOf(itemBean.getClass()); return index; }
獲取到了item型別的標識,那麼接下來我們可以建立對應的ViewHolder
@Override public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){ if (null == inflater) { inflater = LayoutInflater.from(parent.getContext()); } //通過viewType獲取vHolders中VHolder; VHolder<?, ?> vHolder = vHolders.getItemView(viewType); //上文中定義了抽象類VHolder,這裡就通過vHolder分發呼叫onCreateViewHolder方法 return vHolder.onCreateViewHolder(inflater, parent); }
同理onBindViewHolder也是通過vHolder分發呼叫onBindViewHolder方法
@Override public final void onBindViewHolder(ViewHolder holder, final int position, @NonNull List<Object> payloads) { VHolder vHolder = vHolders.getItemView(holder.getItemViewType()); vHolder.onBindViewHolder(holder, datas.get(position), payloads); }
貌似感覺封裝的差不多,接下來我們再改造原始的實現方式:
首先定義adapter,沒毛病。
DelegateAdapteradapter = new DelegateAdapter.Builder() .bind(Item1.class, new ItemHolder1(this)) .bind(Item2.class, new ItemHolder2(this)) .bind(Item3.class, new ItemHolder3(this)) .build();
然後編寫對應的ViewHolder,如ItemHolder1是這樣的:
public class ItemHolder1 extends AbsItemHolder<Item1, ItemHolder1.ViewHolder> { public ItemHolder1(Context context) { super(context); } // item的佈局 @Override public int getLayoutResId() { return R.layout.item_1; } @Override public ViewHolder createViewHolder(View view) { return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Item1 item) { //繫結資料 } static class ViewHolder extends AbsHolder { ViewHolder(@NonNull final View itemView) { super(itemView); } } }
ItemHolder2是這樣的:
public class ItemHolder2 extends AbsItemHolder<Item2, ItemHolder2.ViewHolder> { public ItemHolder2(Context context) { super(context); } // item的佈局 @Override public int getLayoutResId() { return R.layout.item_2; } @Override public ViewHolder createViewHolder(View view) { return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Item2 item) { //繫結資料 } static class ViewHolder extends AbsHolder { ViewHolder(@NonNull final View itemView) { super(itemView); } } }
ItemHolder3是這樣的:
public class ItemHolder3 extends AbsItemHolder<Item3, ItemHolder3.ViewHolder> { public ItemHolder3(Context context) { super(context); } // item的佈局 @Override public int getLayoutResId() { return R.layout.item_3; } @Override public ViewHolder createViewHolder(View view) { return new ViewHolder(view); } @Override protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Item3 item) { //繫結資料 } static class ViewHolder extends AbsHolder { ViewHolder(@NonNull final View itemView) { super(itemView); } } }
把每一種型別都拆分出來,提高複用性,後續只需要編寫對應的ViewHolder就行。但是在實際開發中我們除了資料模型一對一以外還會出現一種模型對應多種型別,例如:
{ "data":[ { "title":"雙11主會場", "topicDesc":"超級紅包", "imgUrl":"", "Type":1 }, { "title":"雙11主會場", "topicDesc":"超級紅包", "imgUrl":"", "Type":2 }, { "title":"雙11主會場", "topicDesc":"超級紅包", "imgUrl":"", "Type":3 }, ] }
以上這個資料模型就是一種模型對應多種型別,根據每個模型的Type的取值來展示不同的UI,因此在前面的adapter中繼續改造,
/** * 資料型別一對多 * * @param clazz * @param vHolders * @return */ public Builder bindArray(Class<? extends T> clazz, VHolder... vHolders) { this.clazz = clazz; this.vHolders = vHolders; return this; }
由於是一對多,所以需要bean並且根據不同的type返回不同viewholder,因此藉助介面回撥處理:
定義Chain介面獲取vHolders中的索引 public interface Chain<T> { int indexItem(int var1, @NonNull T var2); } 定義OneToMany介面返回當前模型中type對應的viewholder public interface OneToMany<T> { /** * 連結一對多 * @param position * @param t * @return */ Class<? extends VHolder<T, ?>> onItemView(int position, T t); } 通過ChainOneToMany實現Chain介面獲取到當前模型中type對應的viewholder在vHolders中的索引 class ChainOneToMany<T> implements Chain<T> { private final OneToMany<T> oneToMany; private final VHolder<T, ?>[] vHolders; public ChainOneToMany( OneToMany<T> oneToMany, VHolder<T, ?>[] vHolders) { this.oneToMany = oneToMany; this.vHolders = vHolders; } @Override public int indexItem(int position, T t) { Class<?> aClass = oneToMany.onItemView(position, t); for (int i = 0; i < vHolders.length; i++) { if (vHolders[i].getClass().equals(aClass)) { return i; } } return -1; } }
因此對之前的Builder進行改進
public static class Builder<T> { private final List<Class<?>> classes; private final List<VHolder<?, ?>> vHolders; private final List<Chain<?>> chains; public Builder() { this.classes = new ArrayList<>(); this.vHolders = new ArrayList<>(); this.chains = new ArrayList<>(); } public Builder bind(Class<? extends T> clazz, VHolder itemView) { classes.add(clazz); vHolders.add(binder); chains.add(new DefaultChain<>()); return this; } public Builder withClass(OneToMany<T> oneToMany) { chain<T> chain = new ChainOneToMany(oneToMany, vHolders); for (VHolder itemView : vHolders) { classes.add(clazz); vHolders.add(itemView); chains.add(chain); } return this; } public DelegateAdapter build() { return new DelegateAdapter(this); } }
改進後的的結果:
DelegateAdapteradapter = new DelegateAdapter.Builder() //一對一 .bind(Item1.class, new ItemHolder1(this)) .bind(Item2.class, new ItemHolder2(this)) .bind(Item3.class, new ItemHolder3(this)) //一對多 .bindArray(ItemData.class, new ItemHolder4(this), new ItemType5(this),new ItemHolder6(this)) .withClass(new OneToMany<ItemData>() { @Override public Class<? extends VHolder<ItemData, ?>> onItemView(int position, ItemData itemData) { if (itemData.type==1) { return ItemHolder4.class; }else if (itemData.type==2) { return ItemHolder5.class; }else if (itemData.type==3) { return ItemHolder6.class; } return ItemHolder4.class; } }) .build();
基本上實現資料一對一和一對多的形式,如果單一的實現這種多型別的功能,作用不是很大,大部分的頁面都具有重新整理或者載入更多的功能,於是乎,整合成了多型別的的重新整理庫github地址:ofollow,noindex">https://github.com/SelfZhangTQ/TRecyclerView 小碼自己已經商用,歡迎star,感謝支援
實戰使用專案
github地址:https://github.com/SelfZhangTQ/T-MVVM 歡迎star,感謝支援