RecyclerView多类型状态下写个优雅的通用Adapter

相关源码和示例

源码参考:
https://github.com/aesean/ApiDemo/blob/master/app/src/main/java/com/aesean/apidemo/widget/recyclerview/AbsMultiTypeAdapter.java

https://github.com/aesean/ApiDemo/blob/master/app/src/main/java/com/aesean/apidemo/widget/recyclerview/MultiTypeAdapterImpl.java

使用示例:
https://github.com/aesean/ApiDemo/blob/master/app/src/main/java/com/aesean/apidemo/activity/recyclerview/MultiTypeRecyclerViewActivity.java

RecyclerView面对多类型Adapter时候面临的问题

RecyclerView出来这么久了相信应该大家都在用了。使用基本三步。
1、创建一个ViewHolder类。
2、创建一个Adapter。
3、向Adapter添加或者删除数据。
当然如果大家都是在用单ViewHolder的Adapter上面的代码写起来还是很简单的,没什么问题。但是如果遇到多类型ViewType怎么办?
常规写法大家会在Adapter中复写

public int getItemViewType(int position) {
      // 通过postion拿到对应的数据,然后处理返回不同的Type
}

然后在onCreateViewHolder方法中根据不同的ViewType返回不同的ViewHolder。在onBindViewHolder进行数据绑定的时候,再判断不同的ViewHolder进行不同的绑定处理。
写的多了,会发现这里有个问题,Adapter本身要求一个ViewHolder的范型,单ViewHolder这里直接写对应的ViewHolder就行了,那多ViewType怎么办?写BaseViewHolder吗?onBindViewHolder时候还是需要继续判断ViewHolder类型。其实这时候Adapter本身要传的ViewHolder范型其实已经没什么意义了。
这还不是最主要的,如果项目复杂了,大家用RecyclerView多了,会发现,不同界面经常会遇到复用ViewHolder的情况。经常会遇到界面0是ViewHolder0,ViewHolder2,ViewHolder4的组合。界面1是ViewHolder2,ViewHolder3的组合,界面2是ViewHolder0,ViewHolder4的组合。这时候我们是每个界面都写一个Adapter吗?通常大家都会这么处理,写的多了会发现,Adapter在写大量重复代码。那怎么办?
主要是基于这种需求。所以需要用一种优雅的方式解决Adapter需要处理多个ViewHolder,而且还需要ViewHolder在不同界面能非常简单方便的被复用。

思路

我们要做什么?我们要做的就是把尽量多的重复逻辑封装起来,避免重复劳动。另外就是尽可能让复用简单,属于谁的代码就让谁处理。

既然Adapter存在绑定多个不同ViewHodler的情况,那我们最好有个通用的Adapter,这个Adapter能处理任意多个不同ViewHolder的create和bind,然后对应的界面自己去注册自己需要的ViewHolder,你需要什么ViewHolder你就注册什么ViewHolder。
我们现在重点关注Adapter的onCreateViewHolder和onBindViewHolder方法。
onCreateViewHolder是用来创建ViewHolder的,参数只有ViewGroup和ViewType,ViewGroup是让我们用来填充ViewHolder进去的。实际能用来区分不同ViewHolder的就只有ViewType了。
那ViewType是从哪里来的?是从getItemViewType来的。getItemViewType可以通过position拿到我们的数据源,然后通过判断数据源的类类型来判断它是什么ViewType。

好,现在我们需要一个Adapter必定有的保存数据的数据源。这里我们选用List(注意必须是个有序List,而且这个List会被经常读取数据,ArrayList读取效率非常高,所以这里用ArrayList是最好的选择。)。因为我们需要绑定多个ViewHolder,所以这里List的范型,需要是Object。就是支持任意类型。

回到getItemViewType,从getItemViewType的postion,我们拿到了List中保存的Data,那我们怎么知道这个Data要被绑定到什么ViewHolder上呢?所以需要有个register方法来注册ViewHolder。那这个方法参数是什么?是ViewHolder和ViewHolder对应的Data。又因为我们ViewHolder是被onCreateViewHolder时候创建的,所以这个肯定是不能传实例的。Data也不能是实例,Data应该是被调用处(通常是Http/Https Api返回)创建实例。所以这里两个参数应该都是类类型。

    public void register(Class dataType, Class viewType)

好了,我们把注册的类类型保存在一个Map中。用来记录每个数据类型对应的ViewHolder。又因为getItemViewType返回的是个int来区分ViewType,所以我们需要把Data的类类型映射为int,怎么映射?非常简单用hashCode。所以这个纪录data与View绑定关系的Map的key是个Int保存Data类类型的hashCode,value是ViewHolder的类类型。

再回到onCreateViewHolder方法,通过传过来的int viewType,我们去我们保存映射关系的mBindMap中拿到这个ViewType对应的ViewHolder的类类型。然后我们就可以通过反射来创建ViewHolder对象了。

这里注意还有个问题。通常我们写ViewHolder可能都会写成下面这样:

    public static class SimpleViewHolder extends RecyclerView.ViewHolder{

        public SimpleViewHolder(View itemView) {
            super(itemView);
        }
    }

然后在onCreateViewHolder中通过类似下面的代码创建SimpleViewHolder。

View view =  LayoutInflater.from(context).inflate(R.layout.view_holder_simple,parent,false);
ViewHolder viewHodler = new SimpleViewHolder(view);

其实个人认为这是非常不好的写法。为什么要让调用者来创建属于SimpleViewHolder的itemView?谁的事情交给谁做。SimpleViewHolder应该对应什么View,SimpleViewHolder自己最清楚。所以我们可以对SimpleViewHolder进行改造。

    public static class SimpleViewHolder extends RecyclerView.ViewHolder{

        public SimpleViewHolder(ViewGroup parent) {
            super(LayoutInflater.from(parentView.getContext()).inflate(R.layout.simple_view_holder, parentView, false));
        }
    }

这样onCreateViewHolder就完全不关心SimpleViewHolder应该对应什么View,这个应该让SimpelViewHolder自己决定。

回到前面onCreateViewHolder创建ViewHolder的地方。这时候我们要约定好,被注册的ViewHolder必须有个参数为ViewGroup的构造方法。
然后继续下一步onBindViewHolder,onBindViewHolder两个参数,ViewHolder实例和positon(Data实例),我们要做的就是把Data设置到ViewHolder上,一个ViewHolder该如何绑定数据,肯定是ViewHolder自身最清楚,所以这里ViewHolder必须有个绑定数据的方法。这里我们需要抽象一个ViewHolder的基类,所有ViewHolder都必须继承这个ViewHolder,然后这个ViewHodler基类,至少有个绑定数据的方法,我们暂时取名叫bindData。
好到这里我们基本就完成了,不同ViewType绑定的处理。
梳理下思路:
第一步:register注册需要的ViewHolder和ViewHolder对应的数据Data。
第二步:getViewType返回Data对应的viewType
第三步:onCreateViewHolder通过ViewType拿到对应的ViewHolder类类型,通过反射创建ViewHolder实例。
第四步:拿到postion对应的数据,设置给ViewHolder。
思路有了剩下的就是填充代码了。

Head Content和Foot的处理

在实际写的时候考虑到我们经常会遇到需要区分Head Content和Foot的情况。我们前面那些关于不同ViewType的处理其实跟Head Content Foot是不冲突的。为了方便扩展实现。所以实际写的时候抽象了一个AbsMultiTypeAdapter出来,AbsMultiTypeAdapter处理了不同Type的创建和绑定。但是AbsMultiTypeAdapter不关心数据源,所有需要调用数据源的方法(包含data的方法,比如addData,removeData)全部改为抽象方法,让子类去实现。这样子类可以根据需要来实现带有Head Content和Foot的数据源。
在MultiTypeAdapterImpl中实际实现了完整的MultiTypeAdapter的功能。MultiTypeAdapterImpl实现了任意多个类型的Adapter。具体实现方式就是把之前我们习惯的数据源从List变成List套List的List>,外层List保存不同的Type,内层List是实际对应Type包含的数据。然后就是处理Postion与Type Offset的相互转换了。详细的不再细说。

最终效果

上面说了那么多,那最终实际效果是什么?我们实现了一个MultiTypeAdapterImpl。它支持任意的ViewHolder以及它们的组合。
使用时候第一步必须通过register方法注册你需要的ViewHolder和这个ViewHolder对应的数据类型。当然ViewHolder必须继承AbsViewHolder。然后就可以通过addItem方法给Adapter添加数据了,剩下的itemView createViewHolder bindViewHolder,MultiTypeAdapterImpl会帮你处理。
相当于你的Activity里写的代码非常少。只有register和addItem。ViewHolder复用非常轻松简单。

你可能感兴趣的:(RecyclerView多类型状态下写个优雅的通用Adapter)