RecyclerView/ListView嵌套CheckBox选中状态错乱解决方案

在讨论这个问题之前应该先了解ListView的复用机制
ListView复用的原理:ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)也是耗时操作,所以必将影响性能。
Android提供了一个叫做Recycler(反复循环器)的构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView,就会大大改善性能。
如果一个屏幕最多显示7个Item,当Item1滑出屏幕,此时Item1 的View被添加进Recycler中,相应的在下部要产生一个Item8,这时调用getView方法,convertView参数就是Item1 的View。 其原理图如下:
这里写图片描述
ListView的复用虽然大大提升了性能,但是却也带来很多问题。比如在加载图片时,由于下边的item复用了上边的item,造成下边item刚加载出来时显示的还是上边被复用的item的图片,等到这个新的item加载图片完毕时才会正常显示,这就是convert view复用造成listview图片加载错乱的问题。
与上边问题相似,在listview的item中存在CheckBox时也会由于复用convert view导致CheckBox的选中状态错乱,本片内容将解决由于复用导致CheckBox选中状态错乱的问题。
先看下存在问题的效果图
这里写图片描述

上图中只选中了北京市和天津市,当下滑ListView时发现下边的河南省和山东省也被选中了,再往下滑四川省和台湾省也被选中。其实可以发现一个规律,每一屏都会有两个条目被选中,其实这两个被选中的条目就是因为复用了第一屏的两个被选中的条目所导致的。
先看下ListView没有优化前的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends AppCompatActivity {
private ListView mListView;
private List<String> mStringList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStringList=new ArrayList<>();
mStringList.add("北京市");mStringList.add("天津市");mStringList.add("上海市");mStringList.add("重庆市");
mStringList.add("河北省");mStringList.add("山西省");mStringList.add("辽宁省");mStringList.add("河南省");
mStringList.add("山东省");mStringList.add("湖北省");mStringList.add("湖南省");mStringList.add("江西省");
mStringList.add("福建省");mStringList.add("陕西省");mStringList.add("四川省");mStringList.add("台湾省");
mListView= (ListView) findViewById(R.id.lv_main);
MyAdapter adapter=new MyAdapter(this,mStringList);
mListView.setAdapter(adapter);

}
}

MainActivity中的代码为ListView适配数据和适配Adapter,不作过多解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* Created by zhpan on 2016/9/24.
*/

public class MyAdapter extends BaseAdapter {

List<String> mStringList;
Context mContext;

public MyAdapter(Context context, List<String> stringList) {
mStringList = stringList;
mContext=context;
}

@Override
public long getItemId(int position) {
return 0;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
MyViewHolder holder;
if(convertView==null){
convertView=View.inflate(mContext,R.layout.item,null);
holder=new MyViewHolder();
holder.mTextView= (TextView) convertView.findViewById(R.id.tv_item);
holder.mCheckBox= (CheckBox) convertView.findViewById(R.id.cb_item);
convertView.setTag(holder);
}else {
holder= (MyViewHolder) convertView.getTag();
}

holder.mTextView.setText(mStringList.get(position));

return convertView;
}

@Override
public int getCount() {
return mStringList.size();
}

@Override
public Object getItem(int position) {
return null;
}

public static class MyViewHolder {
TextView mTextView;
CheckBox mCheckBox;
}
}

在Adapter中只是复用了convertView,没有对CheckBox做任何处理,那么这样写的代码是存在上图中的CheckBox选中状态错乱问题的。为了解决这个问题我们需要对CheckBox的选中状态做下保存,可以在Adapter中声明一个Map集合用来保存被选中的CheckBox。修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* Created by zhpan on 2016/9/24.
*/

public class MyAdapter extends BaseAdapter {

List<String> mStringList;
Context mContext;
private Map<Integer,Boolean> map=new HashMap<>();// 存放已被选中的CheckBox

public MyAdapter(Context context, List<String> stringList) {
mStringList = stringList;
mContext=context;
}

@Override
public long getItemId(int position) {
return 0;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
MyViewHolder holder;
if(convertView==null){
convertView=View.inflate(mContext,R.layout.item,null);
holder=new MyViewHolder();
holder.mTextView= (TextView) convertView.findViewById(R.id.tv_item);
holder.mCheckBox= (CheckBox) convertView.findViewById(R.id.cb_item);
convertView.setTag(holder);
}else {
holder= (MyViewHolder) convertView.getTag();
}

holder.mTextView.setText(mStringList.get(position));
holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked==true){
map.put(position,true);
}else {
map.remove(position);
}
}
});

if(map!=null&&map.containsKey(position)){
holder.mCheckBox.setChecked(true);
}else {
holder.mCheckBox.setChecked(false);
}

return convertView;
}

@Override
public int getCount() {
return mStringList.size();
}

@Override
public Object getItem(int position) {
return null;
}

public static class MyViewHolder {
TextView mTextView;
CheckBox mCheckBox;
}
}

针对这个问题我们子Adapter中加入了一个Map集合,其中Map的key用来存储条被选中的checkbox的position,value用来存储checkbox被选中。代码中还添加了checkbox的监听事件,在监听事件中判断点击的checkbox是否被选中,如果被选中了则将position添加到集合,并设置状态未true,否则就将该checkbox从集合中移除。然后通过if语句判断集合中是否存在该checkbox,如果存在则证明是被选中的,遂将该checkbox设置为选中状态setChecked(true),否则证明checkbox没有选中则设置setChecked(false)。这样就解决了checkbox选中状态错乱的问题。
看下优化后的效果图
这里写图片描述

可以看到优化后CheckBox的选中状态不会再出现错乱问题了。

源码下载


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!