Android RecyclerView、ListView实现单选列表的优雅之路.
一 概述:
这篇文章需求来源还是比较简单的,但做的优雅仍有值得挖掘的地方。
需求来源:一个类似饿了么这种电商优惠券的选择界面:
其实就是 一个普通的列表,实现了单选功能,
效果如图:
(不要怪图渣了,我撸了四五遍,公司录出来的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());//TextView
holder.ivSelect.setOnClickListener(new View.OnClickListener() {
@Override
public 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 = -;//实现单选 方法二,变量保存当前选中的position
⑵在设置数据集时(构造函数,setData()方法等:),初始化 mSelectedPos
的值。
//实现单选方法二: 设置数据集时,找到默认选中的pos
for (int i = ; i < mDatas.size(); i++) {
if (mDatas.get(i).isSelected()) {
mSelectedPos = i;
}
}
⑶onClick里代码如下:
//实现单选方法二: notifyItemChanged() 定向刷新两个视图
//如果勾选的不是已经勾选状态的Item
if (mSelectedPos!=position){
//先取消上个item的勾选状态
mDatas.get(mSelectedPos).setSelected(false);
notifyItemChanged(mSelectedPos);
//设置新Item的勾选状态
mSelectedPos = position;
mDatas.get(mSelectedPos).setSelected(true);
notifyItemChanged(mSelectedPos);
}
本方案由于调用了notifyItemChanged()
,所以还会伴有“白光一闪”的动画。
方案优点:
本方案,较优雅了,不会重走一屏可见的Item的getView()/onBindViewHolder()
方法,
但仍然会重走需要修改的两个Item的getView()/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另一种定向刷新方法:不会有白光一闪动画 也不会重复onBindVIewHolder
CouponVH 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()
方法:
@Override
public void onBindViewHolder(CouponVH holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
Bundle payload = (Bundle) payloads.get();
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);//取出选中的View
CouponVH lastVh = (CouponVH) lastSelectedView.getTag();
lastVh.ivSelect.setSelected(false);
}
//不管在屏幕是否可见,都需要改变之前的data
mDatas.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实现单选列表的优雅之路.的更多相关文章
- Android 使用ListView显示信息列表
课程目标1.理解ListView的基础使用2.学会熟练运用两种适配器(ArrayAdapter.SimpleAdapter)3.学会熟练运用两种监听器(OnScrollListener.OnItemC ...
- Android BaseAdapter ListView (明星简介列表)
1.搭建布局 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" andro ...
- Android recyclerview删除item刷新列表
删除item坑 mModels.remove(i); notifyItemRemoved(i); //必须调用这行代码 notifyItemRangeChanged(i, mModels.size() ...
- Android开发技巧——自定义单选或多选的ListView
这篇其实应该是属于写自定义单选或多选的ListView的基础教程,无奈目前许多人对此的实现大多都绕了远路,反而使得这正规的写法倒显得有些技巧性了. 本文原创,转载请注明在CSDN上的出处: http: ...
- Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表
本文内容 环境 测试数据 项目结构 演示 参考资料 本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表.所谓"刷新最新"和"加载更多"是指日期.演示代码 ...
- ListView与RadioButton组合——自定义单选列表
标签: radiobuttonlistviewandroidlayout 2013-09-10 11:13 19396人阅读 评论(8) 收藏 举报 分类: Android(19) 版权声明: ...
- Android一个ListView列表之中插入两种不同的数据
http://www.cnblogs.com/roucheng/ Android一个ListView列表之中插入两种不同的数据 代码如下: public class ViewHolder{ Butto ...
- Android通过LIstView显示文件列表
[绥江一百]http://www.sj100.net 欢迎,进入绥江一百感谢点击[我的小网站,请大家多 ...
- Android学习系列(15)--App列表之游标ListView(索引ListView)
游标ListView,提供索引标签,使用户能够快速定位列表项. 也可以叫索引ListView,有的人称也为Tweaked ListView,可能更形象些吧. 一看图啥都懂了: 1. ...
随机推荐
- day36 类的三大特性---封装以及Property特性
目录 类的封装 如果真的要拿 类的property特性 setter & deleter 类属性用法 类与对象的绑定方法和非绑定方法 对象方法&类方法&静态方法 隐藏模块内的函 ...
- javascrip this指向问题深入理解
在JavaScript中this变量是一个令人难以摸清的关键字,this可谓是非常强大,充分了解this的相关知识有助于我们在编写面向对象的JavaScript程序时能够游刃有余. 1. 一般用处 对 ...
- JVM 性能调优监控工具 jps、jstack、jmap、jhat、jstat、hprof 使用详解
转自: https://my.oschina.net/feichexia/blog/196575 摘要: JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConso ...
- 为 Elasticsearch 添加中文分词,对比分词器效果
转自:http://keenwon.com/1404.html 为 Elasticsearch 添加中文分词,对比分词器效果 Posted in 后端 By KeenWon On 2014年12月12 ...
- CSS设置input默认样式
HTML <ul class="box"> <li> <input type="checkbox" name="vehi ...
- [CodeForces]1059C Sequence Transformation
构造题. 我递归构造的,发现如果N>3的话就优先删奇数,然后就把删完的提取一个公约数2,再重复操作即可. 具体原因我觉得是因为对于一个长度大于3的序列,2的倍数总是最多,要令字典序最大,所以就把 ...
- CodeVS 1013&1029
若干二叉树遍历的数据结构题. Problem 1013 传送门:http://codevs.cn/problem/1013/ 本题是一个数据结构——二叉树遍历问题. 对二叉树,给定中序遍历序列(In- ...
- MyBatis学习总结(9)——使用MyBatis Generator自动创建代码
一.构建一个环境 1. 首先创建一个表: [sql] view plaincopy CREATE TABLE t_user ( USER_ID INT NOT NULL AUTO_INCREMENT, ...
- Java开发编码规范
第一章 代码开发规范及其指南 一.1 目的 定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,减少项目组中因为换人而带来的损失.(这些规范并不是一定要绝对遵守,但是一定要让程序有 ...
- WordPress TinyMCE 编辑器增强技巧大全
说到WordPress自带的TinyMCE 编辑器,有些国人总是不太满意.针对这个情况,倡萌已经介绍了一些增强或替代的方法: WordPress编辑器增强插件:TinyMCE Advanced Wor ...