一 概述:

这篇文章需求来源还是比较简单的,但做的优雅仍有值得挖掘的地方。

需求来源:一个类似饿了么这种电商优惠券的选择界面: 
其实就是 一个普通的列表,实现了单选功能, 
效果如图: 
 
(不要怪图渣了,我撸了四五遍,公司录出来的GIF就这么渣。。。)

常规方法: 
在Javabean里增加一个boolean isSelected字段, 
并在Adapter里根据这个字段的值设置“CheckBox”的选中状态。 
在每次选中一个新优惠券时,改变数据源里的isSelected字段, 
notifyDataSetChanged()刷新整个列表。 
这样实现起来很简单,代码量也很少,唯一不足的地方就是性能有损耗,不是最优雅。 
So作为一个有追求 今天比较闲 的程序员,我决心分享一波优雅方案。

本文会列举分析一下在ListView和RecyclerView中, 列表实现单选的几种方案,并推荐采用定向刷新 部分绑定的方案,因为更高效and优雅


二 RecyclerView 方案一览:

RecyclerView是我的最爱 ,所以我先说它。

1常规方案:

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

public class TestBean extends SelectedBean {private String name;public TestBean(String name,boolean isSelected) {this.name = name;setSelected(isSelected);}
}

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

public class SelectedBean {private boolean isSelected;public boolean isSelected() {return isSelected;}public void setSelected(boolean selected) {isSelected = selected;}
}

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

        Log.d("TAG", "onBindViewHolder() called with: holder = [" + holder + "], position = [" + position + "]");holder.ivSelect.setSelected(mDatas.get(position).isSelected());//“CheckBox”holder.tvCoupon.setText(mDatas.get(position).getName());//TextViewholder.ivSelect.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//实现单选,第一种方法,十分简单, Lv Rv通用,因为它们都有notifyDataSetChanged()方法// 每次点击时,先将所有的selected设为false,并且将当前点击的item 设为true, 刷新整个视图for (TestBean data : mDatas) {data.setSelected(false);}mDatas.get(position).setSelected(true);notifyDataSetChanged();}});

ViewHolder:

public static class CouponVH extends RecyclerView.ViewHolder {private ImageView ivSelect;private TextView tvCoupon;public CouponVH(View itemView) {super(itemView);ivSelect = (ImageView) itemView.findViewById(R.id.ivSelect);tvCoupon = (TextView) itemView.findViewById(R.id.tvCoupon);}}

方案优点:

简单粗暴

方案缺点:

其实需要修改的Item只有两项: 
一个当前处于选中状态的Item->普通状态 
再将当前手指点击的这个Item->选中状态 
但采用普通方案,则会刷新整个一屏可见的Item,重走他们的getView()/onBindViewHolder()方法。 
其实一个屏幕一般最多可见10+个Item,遍历一遍也无伤大雅。 
但咱们还是要有追求优雅的心,所以我们继续往下看。

2 利用Rv的notifyItemChanged()定向刷新:

本方案可以中速阅读 
⑴本方案需要在Adapter里新增一个字段:

  private int mSelectedPos = -1;//实现单选  方法二,变量保存当前选中的position

⑵在设置数据集时(构造函数,setData()方法等:),初始化 mSelectedPos 的值。

  //实现单选方法二: 设置数据集时,找到默认选中的posfor (int i = 0; i < mDatas.size(); i++) {if (mDatas.get(i).isSelected()) {mSelectedPos = i;}}

⑶onClick里代码如下:

               //实现单选方法二: notifyItemChanged() 定向刷新两个视图//如果勾选的不是已经勾选状态的Itemif (mSelectedPos!=position){//先取消上个item的勾选状态mDatas.get(mSelectedPos).setSelected(false);notifyItemChanged(mSelectedPos);//设置新Item的勾选状态mSelectedPos = position;mDatas.get(mSelectedPos).setSelected(true);notifyItemChanged(mSelectedPos);}

本方案由于调用了notifyItemChanged(),所以还会伴有“白光一闪”的动画。

方案优点:

本方案,较优雅了,不会重走一屏可见的Item的getView()/onBindViewHolder()方法, 
但仍然会重走需要修改的两个ItemgetView()/onBindViewHolder()方法,

方案缺点:

我们实际上需要修改的,只是里面“CheckBox”的值, 
按照在DiffUtil一文学习到的姿势,术语应该是“Partial bind “, 
(安利时间,没听过DiffUtil和Partial bind的 戳->:【Android】详解7.0带来的新工具类:DiffUtil) 
我们需要的只是部分绑定

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

3 Rv 实现部分绑定(推荐):

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

   //实现单选方法三: RecyclerView另一种定向刷新方法:不会有白光一闪动画 也不会重复onBindVIewHolderCouponVH couponVH = (CouponVH) mRv.findViewHolderForLayoutPosition(mSelectedPos);if (couponVH != null) {//还在屏幕里couponVH.ivSelect.setSelected(false);}else {//add by 2016 11 22 for 一些极端情况,holder被缓存在Recycler的cacheView里,//此时拿不到ViewHolder,但是也不会回调onBindViewHolder方法。所以add一个异常处理
        notifyItemChanged(mSelectedPos);}mDatas.get(mSelectedPos).setSelected(false);//不管在不在屏幕里 都需要改变数据//设置新Item的勾选状态mSelectedPos = position;mDatas.get(mSelectedPos).setSelected(true);holder.ivSelect.setSelected(true);

方案优点:

定向刷新两个Item,只修改必要的部分,不会重走onBindViewHolder(),属于手动部分绑定。代码量也适中,不多。

方案缺点:

没有白光一闪动画???(如果这算缺点)

4 Rv 利用payloads实现部分绑定(不推荐):

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

                //实现单选方法四:if (mSelectedPos != position) {//先取消上个item的勾选状态mDatas.get(mSelectedPos).setSelected(false);//传递一个payload Bundle payloadOld = new Bundle();payloadOld.putBoolean("KEY_BOOLEAN", false);notifyItemChanged(mSelectedPos, payloadOld);//设置新Item的勾选状态mSelectedPos = position;mDatas.get(mSelectedPos).setSelected(true);Bundle payloadNew = new Bundle();payloadNew.putBoolean("KEY_BOOLEAN", true);notifyItemChanged(mSelectedPos, payloadNew);}

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

@Overridepublic void onBindViewHolder(CouponVH holder, int position, List<Object> payloads) {if (payloads.isEmpty()) {onBindViewHolder(holder, position);} else {Bundle payload = (Bundle) payloads.get(0);if (payload.containsKey("KEY_BOOLEAN")) {boolean aBoolean = payload.getBoolean("KEY_BOOLEAN");holder.ivSelect.setSelected(aBoolean);}}}

方案优点:

同方法3

方案缺点:

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


三 ListView 方案一览:

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

1 常规方案:

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

方案优点:

同 二.1

方案缺点:

同 二.1

2 ListView里寻找优雅之路:

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

   //实现单选:方法二:Lv的定向刷新//如果 当前选中的View 在当前屏幕可见,且不是自己,要定向刷新一下之前的View的状态if (position != mSelectedPos) {int firstPos = mLv.getFirstVisiblePosition() - mLv.getHeaderViewsCount();//这里考虑了HeaderView的情况int lastPos = mLv.getLastVisiblePosition() - mLv.getHeaderViewsCount();if (mSelectedPos >= firstPos && mSelectedPos <= lastPos) {View lastSelectedView = mLv.getChildAt(mSelectedPos - firstPos);//取出选中的ViewCouponVH lastVh = (CouponVH) lastSelectedView.getTag();lastVh.ivSelect.setSelected(false);}//不管在屏幕是否可见,都需要改变之前的datamDatas.get(mSelectedPos).setSelected(false);//改变现在的点击的这个View的选中状态couponVH.ivSelect.setSelected(true);mDatas.get(position).setSelected(true);mSelectedPos = position;}

方案优点:

也是定向刷新 + 部分绑定 两个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

Android RecyclerView、ListView实现单选列表的优雅之路.相关推荐

  1. 九、android的ListView实现数据列表展示

    基于上一篇第八节的数据库操作为基础,对数据库中的内容在android界面上进行列表展示 1.工程结构: 列表显示示意图: 列表显示效果图: 2.界面的列表展示配置文件 item.xml: <?x ...

  2. Android 通过ListView实现联系人列表字母索引

    转载:Android自定义View--实现联系人列表字母索引_Code-Porter的博客-CSDN博客_android 字母索引 学习大神的思路和逻辑,很清晰. 这篇博文也不错:安卓仿手机联系人右侧 ...

  3. android xml 列表展示,Android中ListView实现展示列表数据

    1.在activity_main.xml中添加一个ListView xmlns:tools="http://schemas.android.com/tools" android:l ...

  4. android listview 风格,【Android】listview卡片式列表的实现

    首先需要把listview的XML改一下风格,着重注意粗体部分. 特别是 android:clipToPadding="false" 并且要记得,ListView的父控件背景颜色设 ...

  5. Android recyclerView/listview的点击变色、点击换背景颜色

    一言不合就上图 点击变色: 这里没有用到什么高大上的技术点,只是静下心好好想想"面向对象",利用一点小逻辑. 我这里用的是recyclerView控件,在recyclerView的 ...

  6. Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表

    本文内容 环境 测试数据 项目结构 演示 参考资料 本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表.所谓"刷新最新"和"加载更多"是指日期.演示代码 ...

  7. Android中ListView与RadioButton结合----自定义单选列表

    有时候我们需要制作自定义的单选列表,但是会遇到一些问题,比如多选,假选问题,所以网上找了找资料,整理一个demo出来,贴一下代码: [html] view plaincopy <ListView ...

  8. Android RecyclerVIew 列表实现 编辑、单选、全选、删除、动画效果(附源码)

    前言 眼下都2020年了,你不会还在使用ListView吧? 正文 因为最近写的一个项目里面有关于列表的编辑相关的功能,其实也是类似与腾讯视频的观看历史的列表操作,你可以先尝试一下,写这个主要是业务逻 ...

  9. android listview 增加单选 复选,ListView里面加入CheckBox如何实现单选?

    这个的问题也就是点击的时候 checkbox的选项会乱的问题 这个问题的解决很好解决,一个map或者实体类中加个字段就可以搞定.如果想彻底掌握也不麻烦,我这个人不太喜欢贴代码,谈谈原理仅作参考 了解l ...

最新文章

  1. 利用linux自带的邮箱服务实现邮件转发
  2. Mathemmatica 新函数
  3. Docker启动失败 提示An error occurred和乱码
  4. day3 and homework
  5. Activity 生命周期与状态保存
  6. Sports Nutrition
  7. Spring boot 配置文件,输入key值,自动补全--- 通过安装插件实现
  8. EntityFramework中IEnumerable和IQueryable的含义和区别
  9. 【老生谈算法】matlab实现追赶法算法——追赶法算法
  10. 概念(理论)---积分方程1:赋范线性空间,线性算子,有界线性算子和连续线性算子
  11. 两台android相互ADB实现一台安卓手机给另一台安卓手机ADB
  12. 高速光通信-调制概述
  13. linux tty core code,linux tty core 源码分析(8)
  14. 黑群晖frp内网穿透配置
  15. 流程图制作: BPMN流程图在线绘制
  16. Python 如何反方向迭代一个序列
  17. Vue学习7-MinUI组件与项目托管到码云上
  18. img标签 src路径正确 但图片不显示
  19. view里面内容左对齐或者右对齐
  20. Latex 编译报错: Misplaced omit.

热门文章

  1. java程序结构_java程序的三种结构
  2. Selenium POM简介
  3. ios把数据传递到另一个页面_iOS 应用之间的跳转和数据传递
  4. 蒙太奇经典例子_剧本中如何写好蒙太奇?这15个硬核案例告诉你!
  5. 微型计算机中普片编码,【单选题】微型计算机中普遍使用的字符编码是A. ASCII码B. BCD 码C. 拼音码D. 补码...
  6. python外星人入侵游戏run_game报错_《python编程从入门到实践》书中的外星人入侵小游戏报错...
  7. C ++ 指针 | 指针与二维数组_5
  8. 2.图像作为函数 | 如何把图像看作函数_2
  9. 2019年春计算机应用基础随堂作业,华南理工2019年《计算机应用基础》随堂练习 doc...
  10. 合成迪丽热巴下海_热巴生图颜值回春,被韩媒吹上天,她是找了杨幂的医生?...