您的位置:首页 > 其它

RecyclerView、ListView实现单选列表的优雅之路

2016-11-14 09:43 459 查看
原创
2016-11-14 张旭童

郭霖



今日科技快讯
汇报一下上周五阿里双11的战报,销售总额创历史新高,达到了1207亿元,这也是阿里双11单天销售额首次突破1000亿。2014年的数字是572亿元,2015年的数字是912亿元。照这个发展速度来算的话,明年阿里双11单天销售额将有可能突破1500亿。这是不是说明中国人越来越有钱了呢?

作者简介
大家好,新的一周又开始了!本篇是 张旭童
的第三篇投稿,熟悉的朋友肯定知道,他的前两篇分享了RecyclerView的头部悬停与右侧索引,今天将带来他对单选列表的细致思考,希望大家喜欢。
张旭童
的博客地址:
http://blog.csdn.net/zxt0601
概述
这篇文章需求来源还是比较简单的,但做的优雅仍有值得挖掘的地方。
需求来源:一个类似饿了么这种电商优惠券的选择界面,其实就是 一个普通的列表,实现了单选功能。
效果如图: 



不要怪图渣了,我撸了四五遍,公司录出来的GIF就这么渣。。。
常规方法:
在 Javabean 里增加一个 boolean isSelected 字段,并在Adapter里根据这个字段的值设置“CheckBox”的选中状态。
在每次选中一个新优惠券时,改变数据源里的isSelected字段,并notifyDataSetChanged()刷新整个列表。
这样实现起来很简单,代码量也很少,唯一不足的地方就是性能有损耗,不是最优雅。
So作为一个有追求(比较闲)的程序员,我决心分享一波优雅方案。
本文会列举分析一下在ListView和RecyclerView中, 列表实现单选的几种方案,并推荐采用定向刷新 部分绑定的方案,因为更高效and优雅

RecyclerView方案一览
RecyclerView是我的最爱 ,所以我先说它。

一、常规方案

常规方案 请光速阅读,直接上码,Bean结构:



我项目里有好多单选需求,懒得写isSelected字段,所以弄了个父类供子类继承。



Acitivity 和 Adapter 其他方法都是最普通的不再赘述。
Adapter 的 onBindViewHolder() 如下:



ViewHolder:



方案优点:

简单粗暴
方案缺点:
其实需要修改的Item只有两项:

一个当前处于选中状态的Item->普通状态

再将当前手指点击的这个Item->选中状态

但采用普通方案,则会刷新整个一屏可见的Item,重走他们的 getView()/onBindViewHolder()方法。
其实一个屏幕一般最多可见10+个Item,遍历一遍也无伤大雅。
但咱们还是要有追求优雅的心,所以我们继续往下看。

二、利用Rv的notifyItemChanged()定向刷新

本方案可以中速阅读
1.
本方案需要在Adapter里新增一个字段:
//实现单选  方法二,变量保存当前选中的position
private int mSelectedPos = -1;

2.
在设置数据集时(构造函数,setData()方法等:),初始化 mSelectedPos 的值。
//实现单选方法二: 设置数据集时,找到默认选中的pos
for (int i = 0; i < mDatas.size(); i++) {
   if (mDatas.get(i).isSelected()) {
       mSelectedPos = i;
   }
}

3. onClick里代码如下:



本方案由于调用了notifyItemChanged(),所以还会伴有“白光一闪”的动画。
方案优点
本方案,较优雅了,不会重走一屏可见的Item的 getView()/onBindViewHolder()方法,但仍然会重走需要修改的两个Item的 getView()/onBindViewHolder()方法。
方案缺点
我们实际上需要修改的,只是里面“CheckBox”的值,按照在 DiffUtil 一文学习到的姿势,术语应该是“Partial bind “,(安利时间,没听过 DiffUtil 和 Partial bind 的 戳->:
【Android】详解7.0带来的新工具类:DiffUtil
http://blog.csdn.net/zxt0601/article/details/52562770
我们需要的只是部分绑定。
一个疑点: 

使用此方法在第一次选中其他Item时,切换selected状态时,查看log,并不是只重走了新旧Item的onBindViewHolder()方法,还走了两个根本不在屏幕范围里的Item的onBindViewHolder()方法。
如,本例中 在还有item 0-3 在屏幕里,默认勾选item1,我选中item0后,log显示postion 4,5,0,1 依次执行了onBindViewHolder()方法。
但是再次切换其他Item时, 会符合预期:只走需要修改的两个Item的getView()/onBindViewHolder()方法。
原因未知,有朋友知道烦请告知,多谢。

三、Rv 实现部分绑定(推荐)

利用 RecyclerView 的 findViewHolderForLayoutPosition()方法,获取某个postion的ViewHolder,按照源码里这个方法的注释,它可能返回null。所以我们需要注意判空,(空即在屏幕不可见)。
与方法2只有onClick里的代码不一样,核心还是利用mSelectedPos 字段搞事情。



方案优点
定向刷新两个Item,只修改必要的部分,不会重走onBindViewHolder(),属于手动部分绑定。代码量也适中,不多。
方案缺点
没有白光一闪动画???(如果这算缺点)

四、Rv 利用payloads实现部分绑定(不推荐)

本方案属于开拓思维,是在方案2的基础上,利用payloads和notifyItemChanged(int position, Object payload)搞事情。
不知道payloads是什么的,看不懂此方案的,我又要安利:【Android】详解7.0带来的新工具类:DiffUtil
onClick代码如下:



需要重写三参数的onBindViewHolder() 方法:



方案优点:

同方法3

方案缺点:

代码量多,实现效果和方法三一样,仅做开拓思维用,所以选择方法三。

ListView方案一览
老实说,现在如果你还在用ListView,不是历史遗留问题的话,你需要面壁思过。
但是毕竟还有人在用,就像还有人在用Android4.x,咱也要考虑这部分人的感受是不是。
一、常规方案

常规方案 和 Rv一毛一样,不上码,参考 上一:

方案优点:

同 上一

方案缺点:

同 上一
二、ListView里寻找优雅之路
此方案,思路是同 上三。
只不过 ListView 没有提供 findViewHolderForLayoutPosition() 这种方法,通过 postion 获取缓存的 ViewHolder。这是废话,因为它设计的时候就没有强迫我们使用 ViewHolder 模式,所以我们是获取不到 ViewHolder 的,那么我们另辟蹊径,直接通过 ViewGroup
的 getChildAt() 获取 子View,拿到 子View 就能拿到 ViewHolder,就能搞事情。上码:



方案优点:

也是定向刷新 + 部分绑定 两个Item,不会重走getView()。

方案缺点:

代码量貌似略多。

总结
本文写作之前,也和郭神讨论过,确实,如他所说,刷新时getView、onBindViewHolder的次数一般都是个位数(屏幕可见ItemView的数量),所以就算你采用最常规的方法实现,也无伤大雅。据郭神说,他之前写,参考是gmail的实现方案,之前看过gmail的多选功能就是采用常规方案做的。
so,如果项目时间紧急,采用常规方案也未尝不可。(我赶工时也会经常用常规方案)
本文的方案,也可以用于
列表点赞,下拉筛选器 等场景。
比如列表点赞时,重走一遍onBindViewHolder()的话,图片九宫格控件就要重新set一下数据集,有些九宫格写的不好,那里面的View都要remove,重新构建渲染一遍。此时用,便是极好的。
其实用RecyclerView+DiffUtil也能实现 定向刷新 部分绑定,可参见我上篇博文,但是有种杀鸡牛刀的感觉。
毕竟DiffUtil计算也需要时间,它在计算时也会遍历整个新旧数据集,所以本文不提供这个方案以免误导。
本文代码不再单独开一个工程,可于我github Demos里取:
https://github.com/mcxtzhang/Demos/tree/master/selectcoupondemo
更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。



如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:



阅读原文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐