我在写大量重复RecyclerView.Adapter的时候,发现我的大部分时间都花在写重复,机械式的if与else,不论是getItemViewType,onCreateViewHolder,onBindViewHolder还是setSpanSizeLookup的getSpanSize,都充斥着大量的if与else。
写重复的代码一直困扰着我。。。
一天,我决定把关于RecyclerView.Adapter使用到的if与else都干掉,达到自动化配置的效果
AutoRecyclerAdapter是一个接近万能的Adapter,它把Recycler.Adapter里开发者需要手写的方法全部自动化,配置化。开发者只需要在外部配置Holder与model就能使用,不必重新自定义Adapter。复杂的多种类型Holder布局也不例外。能够快速的实现像淘宝,京东等首页复杂,多类型的布局。
以一个拥有7个不同的ViewHolder的界面为例,类似很多商城首页的布局
模拟服务器传来7种不同的List集合,需要设计7种不同的ViewHolder
1, 设置7种ViewHolder,ViewHolder支持设置额外参数
autoRecyclerAdapter = new AutoRecyclerAdapter(); manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return autoRecyclerAdapter.getSpanSize(position); } }); autoRecyclerAdapter.setHolder(AutoBannerHolder.class, R.layout.item_banner, this) .setHolder(AutoTypeAHolder.class, R.layout.item_type_a) .setHolder(AutoTypeBHolder.class, R.layout.item_type_b) .setHolder(AutoTypeCHolder.class, R.layout.item_type_c) .setHolder(AutoTypeDHolder.class, R.layout.item_type_d) .setHolder(AutoTypeEHolder.class, R.layout.item_type_e) .setHolder(AutoTypeFHolder.class, R.layout.item_type_f);2, 设置网络请求得到的7种不同List
autoRecyclerAdapter.setDataListSpan(AutoBannerHolder.class, zhaoList, SPAN_SIZE) .setDataListSpan(AutoTypeAHolder.class, qianList, SPAN_SIZE / 5) .setDataListSpan(AutoTypeBHolder.class, sunList, SPAN_SIZE) .setDataListSpan(AutoTypeCHolder.class, liList, SPAN_SIZE / 2) .setDataListSpan(AutoTypeDHolder.class, zhouList, SPAN_SIZE / 5) .setDataListSpan(AutoTypeEHolder.class, wuList, SPAN_SIZE) .setDataListSpan(AutoTypeFHolder.class, zhengList, SPAN_SIZE / 2) .notifyDataSetChanged();getItemViewType(int position)
@Override public int getItemViewType(int position) { Object object = data.get(position); if (object instanceof ZhaoBean) { return TYPE_ZHAO; } else if (object instanceof QianBean) { return TYPE_QIAN; } else if (object instanceof SunBean) { return TYPE_SUN; } else if (object instanceof LiBean) { return TYPE_LI; } else if (object instanceof ZhouBean) { return TYPE_ZHOU; } else if (object instanceof WuBean) { return TYPE_WU; } else if (object instanceof ZhengBean) { return TYPE_ZHENG; } return TYPE_ZHENG; }onCreateViewHolder(ViewGroup parent, int viewType)
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_ZHAO) { return new BannerHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_banner, parent, false)); } else if (viewType == TYPE_QIAN) { return new TypeAHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_a, parent, false)); } else if (viewType == TYPE_SUN) { return new TypeBHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_b, parent, false)); } else if (viewType == TYPE_LI) { return new TypeCHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_c, parent, false)); } else if (viewType == TYPE_ZHOU) { return new TypeDHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_d, parent, false)); } else if (viewType == TYPE_WU) { return new TypeEHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_e, parent, false)); } else if (viewType == TYPE_ZHENG) { return new TypeFHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_f, parent, false)); } return new TypeFHolder( LayoutInflater.from(parent.getContext()).inflate(R.layout.item_type_f, parent, false)); }onBindViewHolder(RecyclerView.ViewHolder holder, int position)
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object object = data.get(position); int viewType = getItemViewType(position); if (viewType == TYPE_ZHAO) { ((BannerHolder) holder).bind(position, (ZhaoBean) object); } else if (viewType == TYPE_QIAN) { ((TypeAHolder) holder).bind(position, (QianBean) object); } else if (viewType == TYPE_SUN) { ((TypeBHolder) holder).bind(position, (SunBean) object); } else if (viewType == TYPE_LI) { ((TypeCHolder) holder).bind(position, (LiBean) object); } else if (viewType == TYPE_ZHOU) { ((TypeDHolder) holder).bind(position, (ZhouBean) object); } else if (viewType == TYPE_WU) { ((TypeEHolder) holder).bind(position, (WuBean) object); } else if (viewType == TYPE_ZHENG) { ((TypeFHolder) holder).bind(position, (ZhengBean) object); } }getItemCount()
@Override public int getItemCount() { return data == null ? 0 : data.size(); }setSpanSizeLookup的getSpanSize(int position) 为了方便我也写到Adapter里了:
public int getSpanSize(int position) { int viewType = getItemViewType(position); if (viewType == TYPE_ZHAO) { return SPAN_SIZE; } else if (viewType == TYPE_QIAN) { return SPAN_SIZE / 5; } else if (viewType == TYPE_SUN) { return SPAN_SIZE; } else if (viewType == TYPE_LI) { return SPAN_SIZE / 2; } else if (viewType == TYPE_ZHOU) { return SPAN_SIZE / 5; } else if (viewType == TYPE_WU) { return SPAN_SIZE; } else if (viewType == TYPE_ZHENG) { return SPAN_SIZE / 2; } return SPAN_SIZE; }PS:可能有上面写法的一些疑问:
1.为什么Adapter使用的List泛型为Object?
为了让Adapter代码达到简洁,position与model一一对应,达到万物皆对象的形式。意思是想要添加新的holder,必须往List添加model。object类型的model需要时在类型转换。
2.Adapter使用了List泛型为Object,会出现类型转换异常吗?
只要add的顺序正确,类型转换时根据position进行的,应该都不会出现错误。数据存储顺序决定视图呈现顺序。
3.在getItemViewType中使用instanceof进行获取viewType,若出现相同的model而viewType不同呢?
是的,上面代码是有这个问题。解决的话只能在model里添加区分viewType的字段,添加新字段作为区分类型的形式。
4.为什么要在Adapter中实现getSpanSize这样的方法?
setSpanSizeLookup的getSpanSize本身是对position对应的Holder进行类似权重的设置,Adapter是控制Holder的地方,写在Adapter很符合逻辑。
如果对上面较为标准的RecyclerView.Adapter写法不存在疑问了,接下来我们来尝试干掉一些if与else
为什么选择这两个方法?因为它们代码结构比较简单
看到上面两个方法里给的现成的参数了吗?对,就是position。
Adapter里的List泛型Object,保证了position就对应着一个model,这里让逻辑清晰了起来。
现在,是否可以在model里添加两个字段:type与spanSize
这样我们就可以通过data.get(position).getType()拿到type给getItemViewType,getSpanSize也是同理的。
为每一个model都添加两个字段,或者考虑使用继承的形式
MultiType:
public class MultiType { private int type; private int spanSize;model集成MultiType,例如:ZhaoMultiBean:
public class ZhaoMultiBean extends MultiType {这样model里就有type与spanSize了,可以设置type与spanSize了。
List泛型Object变为MultiType,为了使用type与spanSize:
public class StandardMultiAdapter extends RecyclerView.Adapter { private List<MultiType> data = new ArrayList<>();更新后的getItemViewType:
@Override public int getItemViewType(int position) { MultiType multiType = data.get(position); return multiType.getType(); }更新后的getSpanSize:
public int getSpanSize(int position) { MultiType multiType = data.get(position); return multiType.getSpanSize(); }当然,外面是需要设置好type与SpanSize的(SpanSize看需要设置,默认可不设置)
一般Adapter的数据都是集合,为每一个model都要设置type与SpanSize
ZhaoMultiBean multiBean = new ZhaoMultiBean(bean.getText(), bean.getStr(), bean.getIcon()); multiBean.setType(type); multiBean.setSpanSize(spanSize);酱紫getItemViewType(int position)与getSpanSize(int position)if与else被干掉了
为什么选这个,因为我看到它给了position的参数,和getItemViewType给的一样。。。
Holder是根据不同的ViewType创建出来的,每一个Holder所需要的model都不一样。
这里是可以data.get(position)拿到对应position的MultiType对象,其实也是Holder所需要的model
每一个Holder所需要的model都不一样,这个怎么解决,该怎么自动强转呢?
我们可以设置一个父类抽象的Holder,在类名上填写泛型,以后每一个子类都集成它,然后就可以解决强转的问题了
public abstract class AutoHolder<M> extends RecyclerView.ViewHolder { public AutoHolder(View itemView) { super(itemView); } public abstract void bind(int position, M bean); }然后在onBindViewHolder里这样做:
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { MultiType multiType = data.get(position); if(holder instanceof MultiHolder) { MultiHolder multiHolder = (MultiHolder) holder; multiHolder.bind(position, multiType); } }通过position获取到一个对象MultiType,通过Holder的bind方法传递进去,而Holder里bind参数是填写泛型的,这样就达到自动强转的目的了
酱紫onBindViewHolder的if与else被干掉了
onCreateViewHolder方法中创建不同的对象是根据viewType来判断的,显然Holder与viewType存在键值对的关系:key:viewType,value:ViewHolder对象。
如何消灭掉if与else呢,或者说如何在外面配置根据viewType创建不同的ViewHolder呢?
如何批量创建不同的对象?而且创建对象的个数是无法确定的。
这里,我打算使用字节码+反射的方式解决这个问题。
使用键值对的形式,key: viewType,value:holder.class,这样是不是可以通过反射字节码,创建不同的对象,想创建多少就创建多少?
创建一个Holder需要哪些?
Holder.class:Holder的字节码对象itemView:也就是Holder的UI布局所以,可以这样设计model:
public class AutoHolderPackage<H extends AutoHolder> { private Class<H> holderClass; private int holderLayoutRes;然后在Adapter创建一个Holder与viewType的键值对集合:
protected SparseArray<AutoHolderPackage> holderPackageMap = new SparseArray<>();接下来,字节码+反射动态创建不同的Holder:
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { AutoHolderPackage holderPackage = holderPackageMap.get(viewType); if (holderPackage == null) { throw new RuntimeException( "not find viewType is: ( " + viewType + " ) holder, viewType error"); } int holderLayoutRes = holderPackage.getHolderLayoutRes(); View itemView = LayoutInflater.from(parent.getContext()).inflate(holderLayoutRes, parent, false); Class holderClass = holderPackage.getHolderClass(); try { Constructor constructor = holderClass.getConstructor(View.class); AutoHolder autoHolder = (AutoHolder) constructor.newInstance(itemView); holderList.add(autoHolder); return autoHolder; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } throw new RuntimeException("( " + holderClass + " ) constructor error"); }在外面只需要把Holder.class与布局资源设置到AutoHolderPackage与viewType形成键值对的形式就完成了。
敲黑板了,注意:Holder.class的viewType要和model设置的viewType对应上
所以,使用Holder.class.hashCode()作为viewType更合适。
现在,已经消灭了RecyclerView.Adapter里面的if与else了。。。
之前是这样的:
ZhaoMultiBean multiBean = new ZhaoMultiBean(bean.getText(), bean.getStr(), bean.getIcon()); multiBean.setType(type); multiBean.setSpanSize(spanSize);对Adapter的每一个model都设置type与spanSize,又要集成MultiType对象,又要设置。。。
有时候,model的字段不能乱加或者不能乱继承,这样子怎么办?
现在是这样:
public class AutoPackage { private int type; private Object autoPackage; private int spanSize;换一种思路,把需要的model与需要的type,spanSize都包在一起,而且是代码自动包,这样解决了对原来的model的入侵。
private <M> AutoRecyclerAdapter setDataObject(int type, M bean, int spanSize) { AutoPackage autoPackage = new AutoPackage(type, bean, spanSize); packageList.add(index, autoPackage); return this; }这样子,Adapter的List也要改变了:
protected List<AutoPackage> packageList = new ArrayList<>();使用方式:
autoRecyclerAdapter.setDataListSpan(AutoBannerHolder.class, zhaoList, SPAN_SIZE)不用在对model设置type和其他处理了,保证了model的纯正。
目前为父类AutoHolder增加三个参数,全部为Object类型,在Adapter设置Holder字节码时,可传递任意对象,传递到AutoHolder中。
public abstract class AutoHolder<M> extends RecyclerView.ViewHolder { protected Object obj1; protected Object obj2; protected Object obj3; public AutoHolder(View itemView, Object obj1, Object obj2, Object obj3) { super(itemView); this.obj1 = obj1; this.obj2 = obj2; this.obj3 = obj3; } public abstract void bind(int position, M bean); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { AutoHolderPackage holderPackage = holderPackageMap.get(viewType); if (holderPackage == null) { throw new RuntimeException( "not find viewType is: ( " + viewType + " ) holder, viewType error"); } int holderLayoutRes = holderPackage.getHolderLayoutRes(); View itemView = LayoutInflater.from(parent.getContext()).inflate(holderLayoutRes, parent, false); Class holderClass = holderPackage.getHolderClass(); Object obj1 = holderPackage.getObj1(); Object obj2 = holderPackage.getObj2(); Object obj3 = holderPackage.getObj3(); try { Constructor constructor = holderClass.getConstructor(View.class, Object.class, Object.class, Object.class); AutoHolder autoHolder = (AutoHolder) constructor.newInstance(itemView, obj1, obj2, obj3); holderList.add(autoHolder); return autoHolder; }使用方式:
autoRecyclerAdapter.setHolder(AutoBannerHolder.class, R.layout.item_banner, this)以一个Adapter需要多种Holder为例:
设置Holder
autoRecyclerAdapter.setHolder(AutoBannerHolder.class, R.layout.item_banner, this) .setHolder(AutoTypeAHolder.class, R.layout.item_type_a) .setHolder(AutoTypeBHolder.class, R.layout.item_type_b) .setHolder(AutoTypeCHolder.class, R.layout.item_type_c) .setHolder(AutoTypeDHolder.class, R.layout.item_type_d) .setHolder(AutoTypeEHolder.class, R.layout.item_type_e) .setHolder(AutoTypeFHolder.class, R.layout.item_type_f);网络请求获取数据,可以无视这个。。。:
//net work request data List<ZhaoBean> zhaoList = ModelHelper.getZhaoList(1); List<QianBean> qianList = ModelHelper.getQianList(10); List<SunBean> sunList = ModelHelper.getSunList(1); List<LiBean> liList = ModelHelper.getLiList(4); List<ZhouBean> zhouList = ModelHelper.getZhouList(10); List<WuBean> wuList = ModelHelper.getWuList(1); List<ZhengBean> zhengList = ModelHelper.getZhengList(30);设置Adapter的model:
autoRecyclerAdapter.setDataListSpan(AutoBannerHolder.class, zhaoList, SPAN_SIZE) .setDataListSpan(AutoTypeAHolder.class, qianList, SPAN_SIZE / 5) .setDataListSpan(AutoTypeBHolder.class, sunList, SPAN_SIZE) .setDataListSpan(AutoTypeCHolder.class, liList, SPAN_SIZE / 2) .setDataListSpan(AutoTypeDHolder.class, zhouList, SPAN_SIZE / 5) .setDataListSpan(AutoTypeEHolder.class, wuList, SPAN_SIZE) .setDataListSpan(AutoTypeFHolder.class, zhengList, SPAN_SIZE / 2) .notifyDataSetChanged();尽量做到使用简单,优雅了。。。
github地址,欢迎Star,Issues,给予鼓励和继续完善与维护的动力!AutoRecyclerAdapter