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<? extends ViewHolder> 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<Object>>,外层List保存不同的Type,内层List是实际对应Type包含的数据。然后就是处理Postion与Type Offset的相互转换了。详细的不再细说。

最终效果

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容