Recycler.Adapter自动化配置

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() 作为ViewType
  • model与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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值