Recycler.Adapter自动化配置

xiaoxiao2021-02-28  119

AutoRecyclerAdapter

我在写大量重复RecyclerView.Adapter的时候,发现我的大部分时间都花在写重复,机械式的if与else,不论是getItemViewType,onCreateViewHolder,onBindViewHolder还是setSpanSizeLookup的getSpanSize,都充斥着大量的if与else。

写重复的代码一直困扰着我。。。

一天,我决定把关于RecyclerView.Adapter使用到的if与else都干掉,达到自动化配置的效果

AutoRecyclerAdapter是一个接近万能的Adapter,它把Recycler.Adapter里开发者需要手写的方法全部自动化,配置化。开发者只需要在外部配置Holder与model就能使用,不必重新自定义Adapter。复杂的多种类型Holder布局也不例外。能够快速的实现像淘宝,京东等首页复杂,多类型的布局。

设计AutoRecyclerAdapter的目的:化繁为简,化整为零,帮助开发者不再实现

如下图

自动化配置Recycler.Adapter 核心步骤:

使用字节码+反射动态创建ViewHolder使用ViewHolder.class.hashCode() 作为ViewTypemodel与ViewType,spanSize建立联系,不再是model添加新字段或者继承的方式ViewHolder泛型定义,动态获取需要的数据模型(model)自动类型转换ViewHolder创建可设置额外参数,支持与Activity,fragment等建立通信

Usage

以一个拥有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();

如何自动化配置,原理是什么?


1. 首先,RecyclerView.Adapter有哪些方法需要开发者去自己实现

getItemViewType方法,为Adapter区分多种Holder类型的标记。onCreateViewHolder方法,为Adapter创建多种Holder对象。创建多种类型依赖getItemViewType方法。onBindViewHolder方法,为Adapter的多种Holder对象设置数据。getItemCount方法,为Adapter设置条目个数。

一种标准的RecyclerView.Adapter作为范例:

public class StandardMultiAdapter extends RecyclerView.Adapter { public static final int SPAN_SIZE = 10; private static final int TYPE_ZHAO = 1000; private static final int TYPE_QIAN = 1001; private static final int TYPE_SUN = 1002; private static final int TYPE_LI = 1003; private static final int TYPE_ZHOU = 1004; private static final int TYPE_WU = 1005; private static final int TYPE_ZHENG = 1006; private List<Object> data = new ArrayList<>();

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

2. 干掉一些RecyclerView.Adapter里的if与else

getItemViewType(int position)getSpanSize(int position)

为什么选择这两个方法?因为它们代码结构比较简单

怎么干掉?

看到上面两个方法里给的现成的参数了吗?对,就是position。

Adapter里的List泛型Object,保证了position就对应着一个model,这里让逻辑清晰了起来。

现在,是否可以在model里添加两个字段:type与spanSize

这样我们就可以通过data.get(position).getType()拿到type给getItemViewType,getSpanSize也是同理的。

2-1 添加两个字段:type与spanSize

为每一个model都添加两个字段,或者考虑使用继承的形式

MultiType:

public class MultiType { private int type; private int spanSize;

model集成MultiType,例如:ZhaoMultiBean:

public class ZhaoMultiBean extends MultiType {

这样model里就有type与spanSize了,可以设置type与spanSize了。

2-2 添加两个字段:type与spanSize后的RecyclerView.Adapter

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被干掉了

3. 继续干掉一些RecyclerView.Adapter里的if与else

onBindViewHolder(RecyclerView.ViewHolder holder, int position)

为什么选这个,因为我看到它给了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被干掉了

4. 最后,干掉onCreateViewHolder(ViewGroup parent, int viewType)里的if与else

onCreateViewHolder方法中创建不同的对象是根据viewType来判断的,显然Holder与viewType存在键值对的关系:key:viewType,value:ViewHolder对象。

如何消灭掉if与else呢,或者说如何在外面配置根据viewType创建不同的ViewHolder呢?

如何批量创建不同的对象?而且创建对象的个数是无法确定的。

这里,我打算使用字节码+反射的方式解决这个问题。

使用键值对的形式,key: viewType,value:holder.class,这样是不是可以通过反射字节码,创建不同的对象,想创建多少就创建多少?

4-1. 设计反射创建Holder对象需要的model

创建一个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了。。。

新的问题来了:

如何简单,优雅的使用?


消灭所有if与else之后,存在的问题:

对每一个model都手动设置type与spanSize,写了很多重复繁琐的代码Holder通过字节码动态创建,如果Holder需要额外的参数如何传递给它如何简单,优雅的设置Holder.class与Holder的布局资源如何简单,优雅的设置Adapter的List,也是Holder需要的model解决了旧的问题,是否出现了一些新的问题

5-1. 对每一个model都手动设置type与spanSize,写了很多重复繁琐的代码

之前是这样的:

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的纯正。

5-2. Holder通过字节码动态创建,如果Holder需要额外的参数如何传递给它

目前为父类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)

5-3. 如何简单,优雅的使用?

以一个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();

尽量做到使用简单,优雅了。。。

5-4. 解决了旧的问题,是否出现了一些新的问题

Adapter的List,数据存储顺序决定视图呈现的顺序。List相对封闭,目前add与remove没问题,但是想要查找List某一个model,比较其中一个,处理的还不行使用上和原来的Adapter不一样了,没办法,外面配置就好了,不用自己手动写一大推if与else了由于创建holder使用了字节码反射,当holder构造器里代码报错,错误的代码难以定位。AutoRecyclerAdapter并没有长时间的进行测试,还需要慢慢使用,不断调整。

6. 最后

github地址,欢迎Star,Issues,给予鼓励和继续完善与维护的动力!AutoRecyclerAdapter

转载请注明原文地址: https://www.6miu.com/read-44132.html

最新回复(0)