ListView里面如果有CheckBox跟RadioButton的话,而且Adapter复用了convertView的话,基本上是会100%出现滑动错乱的,比如像下面这样的
出现这个问题的根本原因还是在convertView复用的时候没有对CheckBox做处理,我们知道,假设手机屏幕一屏能显示7个item,则刚开始会一次性创建7个convertView,当屏幕往上滑的时候,第1个item不可见时,系统会将它缓存起来复用给后面的item,所以在滑动到第8个item的时候,getView()方法中的convertView就是第1个item的。重用convertView可以避免大量重复的inflate跟findViewById,会使ListView的性能大大提升,但是带来的问题就是,第8个item的convertView其实是第1个复用下来的,如果我们在第1个item上做了操作,而在第8个item上没有刷新数据,就会导致上面的错乱问题。
解决这个问题有两种办法:
在Adapter中使用ArrayList保存所有CheckBox的选中状态,然后在OnCheckedChangeListener中监听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
71public class ListViewAdapter1 extends BaseAdapter {
private List<String> cities;
private LayoutInflater mInflater;
// 创建一个List用于保存CheckBox的选中状态
private List<Boolean> checks = new ArrayList<>();
public ListViewAdapter1(Context context, List<String> cities) {
this.cities = cities;
mInflater = LayoutInflater.from(context);
for (int i = 0; i < cities.size(); i++) {
// 刚开始,所有的CheckBox都是未选中的
checks.add(false);
}
}
public int getCount() {
return cities.size();
}
public Object getItem(int position) {
return cities.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.listview_item, parent, false);
holder.cbListView = (CheckBox) convertView.findViewById(R.id.cb_listView);
holder.tvListView = (TextView) convertView.findViewById(R.id.tv_listView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tvListView.setText(cities.get(position));
holder.cbListView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
// 当CheckBox被选中,将相应position的值设置为true
checks.set(position, true);
} else {
// 当CheckBox取消选中,将对应position的值设置为false
checks.set(position,false);
}
}
});
// 根据相应位置的checks值设置CheckBox的选中状态
holder.cbListView.setChecked(checks.get(position));
return convertView;
}
class ViewHolder {
CheckBox cbListView;
TextView tvListView;
}
}第二种方法原理跟第一种一样,也是监听CheckBox的选中状态然后进行保存,只不错用的是
Map<Integer,Boolean>
保存,下面是代码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
68public class ListViewAdapter extends BaseAdapter {
private List<String> cities;
private LayoutInflater mInflater;
private Map<Integer,Boolean> checkBoxState = new HashMap<>();
public ListViewAdapter(Context context, List<String> cities) {
this.cities = cities;
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return cities.size();
}
public Object getItem(int position) {
return cities.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null){
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.listview_item,parent,false);
holder.cbListView = (CheckBox) convertView.findViewById(R.id.cb_listView);
holder.tvListView = (TextView) convertView.findViewById(R.id.tv_listView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tvListView.setText(cities.get(position));
holder.cbListView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked){
// 若CheckBox为选中状态,保存当前位置的选中信息
checkBoxState.put(position,true);
} else {
// 若CheckBox取消选中,则将其从HashMap中移除
checkBoxState.remove(position);
}
}
});
if (checkBoxState.size() != 0 && checkBoxState.containsKey(position)){
holder.cbListView.setChecked(true);
} else {
holder.cbListView.setChecked(false);
}
return convertView;
}
class ViewHolder{
CheckBox cbListView;
TextView tvListView;
}
}
处理之后就不会出现滑动错乱了
以上就是解决这个问题的两种方式,此方法同样也适用于RecyclerView。
其实在这里还会有另一个隐藏的问题,那就是如果ListView中包含CheckBox跟Button等,则ListView默认会失去焦点,这个时候设置了setOnItemClickListener()
即item的点击监听是不起作用的,点击ListView的item是不响应的,解决这个问题的办法就是在item的根布局中设置descendantFocusability属性,比如
1 | <?xml version="1.0" encoding="utf-8"?> |
该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系。
属性的值有三种:
- beforeDescendants:viewgroup会优先其子类控件而获取到焦点
- afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
- blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点