优雅的实现多类型列表的Adapter
1引言
在开发中经常会遇到,一个列表(RecyclerView)中有多种布局类型的情况。前段时间,看到了这篇文章
[译]关于 Android Adapter,你的实现方式可能一直都有问题(http://www.jianshu.com/p/c6a44e18badb)
文中主要从设计的角度阐释如何更合理的实现多种布局类型的Adapter,本文主要从实践的角度出发,站在巨人的肩膀上,结合我个人的理解进行阐述,如果有纰漏,欢迎留言指出。
2有多种布局类型
有时候,由于应用场景的需要,列表(RecyclerView)中需要存在一种以上的布局类型。为了阐述的方便,我们先假设一种应用场景
列表中含有若干个常规的布局,在列表的中的第一个位置与第二个位置中分别为两个不同的布局,其余为常规的布局
针对这样的需求,笔者一直以来的实现方式如下

在Adapter的getItemViewType方法中返回特定位置的特定标识(根据前文需求,就是position0与position1)
在onCreateViewHolder中根据viewType参数,也就是getItemViewType的返回值来判断需要创建的ViewHolder类型
在onBindViewHolder方法中对ViewHolder的具体类型进行判断,分别为不同类型的ViewHolder进行绑定数据与逻辑处理
通过以上就能实现多类型列表的Adapter,但这样的代码写多了总会觉得别扭,特别是看到了[译]关于 Android Adapter,你的实现方式可能一直都有问题(http://www.jianshu.com/p/c6a44e18badb)这篇文章之后。
结合文章与我个人的理解,这种实现方式所存在弊端可以总结为以下几点:
类型检查与类型转型,由于在onCreateViewHolder根据不同类型创建了不同的ViewHolder,所以在onBindViewHolder需要针对不同类型的ViewHolder进行数据绑定与逻辑处理,这导致需要通过instanceof对ViewHolder进行类型检查与类型转型。
[译]关于 Android Adapter,你的实现方式可能一直都有问题中是这样说的许多年前,我在我的显示器上贴了许多的名言。其中的一个来自 Scott Meyers 写的《Effective C++》这本书(最好的IT书籍之一),它是这么说的:
不管什么时候,只要你发现自己写的代码类似于 “ if the object is of type T1, then do something, but if it’s of type T2, then do something else ”,就给自己一耳光。不利于扩展,目前的需求是列表中存在三种布局类类型,那么如果需求变动,极端一点的情况就是数据源是从服务器获取的,数据中的model决定列表中的布局类型。这种情况下,每当model改变或model类型增加,我们都要去改变adapter中很多的代码,同时Adapter还必须知道特定的model在列表中的位置(position)除非跟服务端约定好,model(位置)不变,很显然,这是不现实的。
[译]关于 Android Adapter,你的实现方式可能一直都有问题中是这样说的另外,我们实行那些 adapter 的方法违背了 SOLID 原则中的“开闭准则” 。它是这样说的:“对扩展开放,对修改封闭。” 当我们添加另一个类型或者 model 到我们的类中时,比如叫 Rabbit 和 RabbitViewHolder,我们不得不在 Adapter 里改变许多的方法。 这是对开闭原则明显的违背。添加新对象不应该修改已存在的方法。
不利于维护,这点应该是上一点的延伸,随着列表中布局类型的增加与变更,getItemViewType、onCreateViewHolder、onBindViewHolder中的代码都需要变更或增加,Adapter 中的代码会变得臃肿与混乱,增加了代码的维护成本。
首先让我摸摸自己的脸,然后结合[译]关于 Android Adapter,你的实现方式可能一直都有问题,看看如何优雅的实现多类型列表的Adapter
3优雅的实现
结合上文,我们的核心目的就是三个
避免类的类型检查与类型转型
增强Adapter的扩展性
增强Adapter的可维护性
前文提到了,当列表中类型增加或减少时Adapter中主要改动的就是getItemViewType、onCreateViewHolder、onBindViewHolder这三个方法,因此,我们就从这三个方法中开始着手。
Talk is cheap. Show me the code,围绕以上几点,开始码代码
getItemViewType
原本的代码是这样

在这段代码中,我们必须知道特定的布局类型在列表中的位置,而布局类型在列表中的位置是由数据源决定的,为了解决这个问题并且减少if之类的逻辑判断简化代码,我们可以简单粗暴的在Model中增加type标识,优化之后
getItemViewType的实现大致如下

这样的方式有很大的局限性(谁用谁知道),这里就不展开了,直接看正确的姿势,先看代码(具体可以查看源码[https://github.com/LiHongHui6/MultiTypeAdapter])


针对getItemViewType可以进行如下实现

小结:
通过接口抽象,将所有与列表相关的Model抽象为Visitable,当我们在初始化数据源时就能以List<Visitable>的形式将不同类型的Model集合在列表中;
通过访问者模式,将列表类型判断的相关代码抽取到TypeFactoryForList 中,同时所有列表类型对应的布局资源都在这个类中进行管理与维护,以这样的方式巧妙的增强了扩展性与可维护性;
getItemViewType中不再需要进行if判断,通过数据源控制列表的布局类型,同时返回的不再是简单的布局类型标识,而是布局的资源ID(通过modelList.get(position).type()获取),进一步简化代码(在onCreateViewHolder中会体现出来);
onCreateViewHolder
结合上文可以了解到,getItemViewType返回的是布局资源ID,也就是onCreateViewHolder(ViewGroup parent, int viewType)参数中的viewType,我们可以直接用viewType创建itemView,但是,问题来了,itemView创建之后,还是需要进行类型判断,创建不同的ViewHolder,针对这个问题可以分以下几个步骤解决
首先为了增强ViewHolder的灵活性,可以继承RecyclerView.ViewHolder派生出BaseViewHolder抽象类如下

不同的ViewHolder继承BaseViewHolder并实现setUpView方法即可。
然后对TypeFactory 与TypeFactoryForList 增加如下代码

最后对onCreateViewHolder方法进行如下实现

小结:
在onCreateViewHolder中以BaseViewHolder作为返回值类型。因为BaseViewHolder作为不同类型的ViewHolder的基类,可以避免在onBindViewHolder中对ViewHolder进行类型检查与类型转换,同时也可以简化onBindViewHolder方法中的代码(具体会在下文阐述);
创建不同类型的ViewHolder的相关代码被抽取到了TypeFactoryForList 中,简化了onCreateViewHolder中的代码,同时与类型相关的代码都集中在TypeFactoryForList 中,方便后期维护与拓展;
onBindViewHolder
经过以上实现,onBindViewHolder中的代码就非常的轻盈了,如下

可以看到,在onBindViewHolder中不需要对ViewHolder进行类型检查与转换,也不需要针对不同类型的ViewHoler执行不同绑定操作,不同的列表布局类型的数据绑定(逻辑代码)都交给了与其自身对应的ViewHolder处理,如下(setUpView中的代码可根据实际情况修改)

小结
onBindViewHolder中不需要进行类型检查与转换,对ItemView的数据绑定与逻辑处理都交由各自的ViewHolder进行处理。通过这样方式,让代码更整洁,更易于维护,同时也增强了扩展性。
4总结
经过如上优化之后,Adapter中的代码如下

当列表中增加类型时:
为该类型创建实现了Visitable接口的Model类
创建继承于BaseViewHolder的ViewHolder(与Model类对应)
为TypeFactory增加type方法(与Model类对应) ,同时TypeFactoryForList 实现该方法
为TypeFactoryForList增加与列表类型对应的资源ID参数
修改TypeFactoryForList 中的createViewHolder方法
可以看到,虽然Adapter中的代码量减少,但总体的代码量并没减少(可能还增多了),但是和好处比起来,增加一点代码量还是值得的
拓展性——Adapter并不关心不同的列表类型在列表中的位置,因此对于Adapter来说列表类型可以随意增加或减少,我们只需要维护好数据源即可。
可维护性——不同的列表类型由不同的ViewHolder维护,相互之间互不干扰;对类型的管理都在TypeFactoryForList 中,TypeFactoryForList 中的代码量少,代码简洁,维护成本低。
避免了类的类型检查与类型转型,这点看源码就可以知道
源码地址:
https://github.com/huolongluo/Naked-Dragon#naked-dragon
最后,可能还有待完善的地方,大家可以根据实际情况进行修改与扩展。同时,欢迎留言交流。
优雅的实现多类型列表的Adapter的更多相关文章
- 通信录列表+复杂Adapter分析
概述 最近写论文之余玩起了github,发现有个citypicker挺不错的,高仿了美团城市选择和定位的一些功能 地址链接 效果图如下: 自己手动写了一遍优化了一些内容,学到了一些姿势,下面对其中一些 ...
- 掘金 Android 文章精选合集
掘金 Android 文章精选合集 掘金官方 关注 2017.07.10 16:42* 字数 175276 阅读 50053评论 13喜欢 669 用两张图告诉你,为什么你的 App 会卡顿? - A ...
- 最全android Demo
1.BeautifulRefreshLayout-漂亮的美食下拉刷新 https://github.com/android-cjj/BeautifulRefreshLayout/tree/Beauti ...
- 140款Android开源优秀项目源码
BeautifulRefreshLayout-漂亮的美食下拉刷新 https://github.com/android-cjj/BeautifulRefreshLayout/tree/Beautifu ...
- Android 自学之列表视图ListView和ListActivity
ListView是手机系统中使用非常广泛的一种组件,它以垂直列表的形式显示所有列表项. 创建ListView有两种方式: 直接使用ListView创建. 让Activity继承ListActivity ...
- Android开发学习之LauncherActivity开发启动的列表
Android开发学习之LauncherActivity开发启动的列表 创建项目:OtherActivity 项目运行结果: 建立主Activity:OtherActivity.java [jav ...
- Android开发10.1:UI组件适配器AdapterView(创建ListView,Adapter接口)
@version:Android4.3 API18 @author:liuxinming 概述 AdapterView继承了ViewGroup,它的本质是容器 ...
- 会了这十种Python优雅的写法,让你工作效率翻十倍,一人顶十人用!
我们都知道,Python 的设计哲学是「优雅」.「明确」.「简单」.这也许很多人选择 Python 的原因.但是我收到有些伙伴反馈,他写的 Python 并不优雅,甚至很臃肿,那可能是你的姿势不对 ...
- 详解Dialog(二)——有关列表的构建
版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 前言:这段时间真是忙啊忙啊忙,元旦三天假加了两天班,已经连续六周只放一天了,天天加班到十点多,真是有一口血吐在屏幕上的感觉了,博 ...
随机推荐
- 逆向工程生成的Mapper.xml以及*Example.java详解
逆向工程生成的接口中的方法详解 在我上一篇的博客中讲解了如何使用Mybayis逆向工程针对单表自动生成mapper.java.mapper.xml.实体类,今天我们先针对mapper.java接口中的 ...
- WinForm—串口通讯
ialPort(串行端口资源) 常用属性: BaudRate 此串行端口上要使用的波特率 DataBits 每发送/接收一个字节的数据位数目 DtrEnable 在通讯过程中是否启用数据终端就绪(St ...
- ASP.NET 下使用特定身份完成windows服务的功能操作
今天部署项目的发现一个问题: 在本地Win7系统下利用Web页面完成Windows服务的功能操作(启动.停止.安装.卸载)都是正常的,而部署到Server2008系统下,再使用Web页面完成windo ...
- Linux的shell script
Linux的shell script //编辑shell: vi a.sh //子进程运行shell sh a.sh //主线程运行shell source a.sh 相关例子: #!/bin/bas ...
- java数据结构之二叉树遍历的非递归实现
算法概述递归算法简洁明了.可读性好,但与非递归算法相比要消耗更多的时间和存储空间.为提高效率,我们可采用一种非递归的二叉树遍历算法.非递归的实现要借助栈来实现,因为堆栈的先进后出的结构和递归很相似.对 ...
- DotNetOpenAuth 服务端搭建
新建项目: 安装DotNetOpenAuth: 新增OAuthController: 代码如下: public class OAuthController : Controller { private ...
- 工具类APP
应用名称 工具S 英文名称 未填写 应用描述 工具类APP 英文描述 未填写 应用官网 this 应用图标
- OpenGL11-绘制汉字最高效方法(使用Freetype)(代码已更新)
最新版本,之前的版本有些文件没有打包 视频教程请关注 http://edu.csdn.net/lecturer/lecturer_detail?lecturer_id=440 OpenGL本身并没有绘 ...
- 代码阅读——十个C开源项目
1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连 ...
- docker网络之macvlan
macvlan接口类型简单说类似于子接口,但相比子接口来说,macvlan接口拥有自己独立的mac地址,因此使用macvlan接口可以允许更多的二层操作.macvlan有四种模式:VEPA,bridg ...