Android复杂列表的实现

原文地址:Android复杂列表的实现

初步实现

问题的开始是这样的:项目里有个页面,整个列表采用ListView实现,除了常规的列表项外,还有两个自定义的View也要随着页面滑动。Ok,listView支持addHead,而且还是多head,自定义view通过addHead方法添加到listview中,就一切ok。然而ListView毕竟渐渐过时了,打算采用RecyclerView来重构一下。虽然RecyclerView不支持addHead这种方法,但是可以通过getItemViewType方法来实现返回多种类型。
这里写图片描述

即根据业务需求,返回不同的类型的值,那么下一步,我们同时需要在onCreateViewHolder中针对不同的viewType来创建不同的ViewHolder,同样的,在onBindViewHolder中,也要处理不同的类型,特别的,如果不同类型的viewholder具有不同的方法的情况,还需要针对viewholder做一次类型转换。类似这样:
这里写图片描述

以上就是一般RecyclerView中实现多类型Item的方法,相应的变化一下,把头部和脚部当作特定类型的ItemType,并提供public方法共外部setHead即可支持添加头部。
问题进阶
上述的方法,是解决了特定业务情景下的问题,但是很明细不利于扩展和维护。首先,当列表除了头部外的部分依然会出现不同类型时,并且实际情况中,不同类型应该都是由服务器回传的数据来决定的,我们就不能在getItemViewType中简单的定义类型值来判断。
一个可能的做法是,在数据层里添加type字段,通过type字段来
这里写图片描述

然而在数据层包裹展示层需要的type字段并不是一个优雅的做法,它破坏了单一职责。同时,这么做也无法解决另一个问题:扩展性。
所谓扩展性就是Adapter最好能在数据类型变化时候,内部实现逻辑不需要改变,只是外部添加新的功能即可。那么这就要求Adapter对数据层是解耦的,不能显式的持有外部的数据。Adapter设计之初,是为了兼容千变万化的数据结构,并不是千变万化的类型结构,因此,应该考虑把不同类型的变化从Adapter内部隔离开。
这里写图片描述
1.jpg
这里写图片描述
2.jpg
GitHub上关于多类型Item的RecyclerView的实现有很多库,基本的思路是通过一个Manager类来管理多种类型中:数据和视图的对应关系。实际上,都是围绕如何解决viewType、数据、视图的对应关系来进行一系列的封装。
下面介绍两个实现的比较简洁而灵活的库:

  • AdatperDelegates
  • MultiTypeAdapter

AdapterDelegates的思路是使用自定义的Adapter来“hook”原来的RecyclerView的Adapter,主要的Adapter方法如onBindViewHolder和onCreateViewHolder方法都被劫持使用adpter内部的一个Manager类来实现,参看下面的类图会更加容易理解。
这里写图片描述
3.jpg
上图是这个库的基本类图,省略了两个非必要的类,其中只列出了一些典型的方法和对象。以onBindViewHolder()为例,可以看到从最顶层开始,这个方法会一步步往下调用,一直到AdapterDelegate这层,这一层也是最终面向使用者需要关心的层次,通过继承抽象类AdapterDelegate,实现其中的方法,来完成业务逻辑和UI表现,代码如下,和普通的RV.Adapter方法没有区别:
这里写图片描述
但是通过这一层的封装,成功的把多类型的情况分隔开,每种类型只需要在各种的AdapterDelegate中去编写业务逻辑就可以,Adapter中的职业就非常简单,只需要持有AdapterDelegateManager,由这个Manager类来维护每种类型具体对应的AdapterDelegate,而由AdapterDelegate维护UI和数据的绑定关系。
这里写图片描述
4.jpg
如此,面对多类型的情况或者在已有的业务基础上增加了新的类型,都不再用去修改Adapter的基本实现,只要做两件事:
编写类型的AdapterDelegate来实现UI展示、数据绑定、点击事件等工作
通过AdapterDelegateManager注册新的AdapterDelegate
下面是一个demo例子(gif画质比较渣,将就着看。。)
这里写图片描述
5.jpg
整个列表是一个RecyclerView,包含了两种不同类型的头部,简单的Item类型和可横向滑动展示的Item类型共计4种。来看看这个RecyclerView的Adapter实现:
这里写图片描述
从代码里可以看到,整个Adapter是非常简洁和清晰的,业务逻辑归于Delegate当中解决,viewType和类型的映射关系放到delegateManager中处理。具体Delegate的代码就不贴了,和常规单类型Adapter的写法一致。下面再看看另一个库的思路:MuliTypeAdapter.
这里就不自己画类图了,从其作者的文档中引用一幅图,如下:
这里写图片描述
6.jpg
从上文所说的基本原则来分析,我们应重点关注其如何实现viewType字段和类型的映射,以及如何和RV.Adaper交互。从类名和继承关系来看,我们可以知道,MultiTypeAdapter应该是充当之前所说的Manage的角色,同时,这个类实现了两个接口:

  • TypePool
  • FlatTypeAdapter
    因此,维护viewType和类型映射关系就必然会体现在其中。而类Items是一个继承ArrayList的空类,表明了这个类将是所有数据结构的基类。最后,唯一单独没有联系的ItemViewProvider< C,V>则可以推断为用来实现业务逻辑和UI展示。如此,基本要素都一一对应上,接下来看看它是如何实现其中的功能。
    这里写图片描述
    从MuliTypeAdapter的几个重点方法可以看出,其调用的方法几乎都是接口或者抽象类的空方法,这侧面体现出来此库的高度可定制性,所有的方法实现都可以由具体的实现类来决定。
    从getViewType方法中可以看到,其返回值由indexOf方法确定,而这个方法定义在TypePool接口中,由MultiTypePool实现,当然我们也可以自己实现然后替换掉。从MultiTypePool的源码中分析:
    这里写图片描述
    可以看到,不同于AdapteDelegate中绑定viewType和Delegate,在这里,它将数据类Class和ItemViewProvider进行了绑定,分别用两个ArrayList来存储对象,用index索引作为viewType的值。如下图示意:
    这里写图片描述
    7.jpg
    当Adapter中注册类型时,将两者绑定;getViewType时,则首先通过position拿到数据类型,再通过数据类型拿到对应的UI类型;onBindViewHolder时,同样通过position拿到数据类型,拿到ItemViewProvider,继而调用ItemViewProvider的onBindViewHolder方法去交由实现类处理。以上应该可以基本明白该库是如何维护viewType、数据类型和UI类型的映射关系的。
    而在编写Adapter的过程中,特别是多类型的Adapter过程中,常常会发现自己不得不在onBindVieHolder方法中,对holder转型来调用其内部方法,或者对数据转型来使用其字段值,大量的类型转换既显得臃肿又影响速度。既然我们已经把不同类型的情况已经独立成一个个ItemViewProvider(或者AdapterDelegate,另一个库中的称呼),那么在相应的实现类中,我们也希望能正确的分发数据类型和视图类型。
    在AdatperDelegates库中,如果我们的业务实现类直接继承与AdapterDelegate来编写,是这样的:
    这里写图片描述
    可以看到还是没有避免类型转换。作者其实也意识到这点,因此提供了一个AbsListItemAdapterDelegate类来供我们继承,其内部通过泛型预先帮我们做好类型转换,再分发下去:
    这里写图片描述
    MuliTypeAdapter则干脆的多,在定义ItemViewProvider的抽象方法时就已经考虑了这个问题,解决方案和上述一致,但是写法上看起来更为优雅:
    这里写图片描述
    当然,这样做本质是在内层做好转型再分发,如果要真正意思上的避免转型,可以采用访问者模式(参见:Writing Better Adapter)
    关于MuliTypeAdapter的Demo就不做了,其官方上例子已经很详尽。并且,除了之前提到的核心逻辑外,其还提供了全局类型池设计、数据二次分发设计(即没有讨论的FlatTypeAdapter接口),感兴趣的可以继续了解。
    上述两个库,都做到了对不同类型Item的分离,每次组装一个列表时,只需要把数据源正确的组装好,adapter内部会通过各自实现的Manager来定位对应的UI来展示。在实际开发中,可能的问题或许是不同Item之间的关联性,比如一个头部类型的带有联动其他Item的交互的话,就需要打破这种独立性(此时需要通过构造函数等方法传入其他对象的实例)。另外,对于常见的头部、列表、脚部的需求来说,实际上在此都是当作三种类型来处理,那么对于服务器回传的列表数据,我们需要自行包裹上头部、脚部的数据类型,这样才能正确的被处理,也是相对麻烦之处
    参考文章:

  • Effective-MultiType

  • AdapterDelegates
  • Writing-Better-Adapters 译文及示例
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值