转载请注明:http://blog.csdn.net/binbinqq86/article/details/49148571
最近在项目中需要实现一个列表,可以进行多选,当然首选的方案就是listview+checkbox,关于checkbox的选中状态会采用一个集合(Map<Integer,Boolean>)来保存,当listview来回滑动的时候,根据保存的每个position的checkbox的勾选状态来进行显示,这样就可以防止listview的复用机制导致的状态错乱问题,想的挺美好,结果在实际操作中,却仍然会出现错乱的情况,不知道有没有人碰到过这样的情况(此处写下本人的经历,仅供参考,写的不好的话也请大家不要喷)。
下面先上整个代码,再来进行分析:
package com.binbin.listview_with_checkbox;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
public class ThirdActivity extends ListActivity{
private List<String> list=new ArrayList<>();
private MyAdapter adapter;
private ListView lv;
private Map<Integer,Boolean> isChecked;
private int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lv=getListView();
setContentView(lv);
adapter=new MyAdapter();
lv.setAdapter(adapter);
}
private void initData(){
isChecked=new HashMap<>();
for(int i=0;i<20;i++){
list.add("第"+i+"条测试数据");
isChecked.put(i, false);
}
}
private class MyAdapter extends BaseAdapter{
public MyAdapter(){
initData();
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
if(convertView==null){
convertView=LayoutInflater.from(ThirdActivity.this).inflate(R.layout.item, null, false);
holder=new ViewHolder();
holder.cb=(CheckBox) convertView.findViewById(R.id.cb);
holder.tv=(TextView) convertView.findViewById(R.id.tv);
convertView.setTag(holder);
}else{
holder=(ViewHolder) convertView.getTag();
}
holder.tv.setText(list.get(position));
holder.cb.setChecked(isChecked.get(position));
// System.out.println("@@@@@@@@@@@"+position+holder.cb.toString());
final ViewHolder hol=holder;
holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO Auto-generated method stub
if(isChecked){
ThirdActivity.this.isChecked.put(position, true);
}else{
ThirdActivity.this.isChecked.put(position, false);
}
}
});
return convertView;
}
}
static class ViewHolder{
CheckBox cb;
TextView tv;
}
}
布局我就不上传了,很简单,就是一个checkbox+textview,这段代码看起来也非常的简单,理论上也找不到什么破绽,可是为什么就产生了错乱的效果了呢?其实原因还是在于listview的复用机制,我自己在测试中listview总共产生了8个实例(可以通过代码打印看到,当滑动到第九个的时候,实例的id与第一个是一样的)。那么问题到底出在哪呢,我们来一步一步分析,把每个checkbox的状态值保存在map中,这个应该没问题,当勾选的时候触发onCheckedChangeListener,把当前position当作key,保存当前位置的状态值,上面代码当我们打印的时候会发现,在listview滑动的时候也触发了onCheckedChangeListener方法,并且里面的position的值与外边的不一致(里面的打印的还是上一次勾选的position),这就解释了为什么会错乱了:初始化checkbox状态放在了监听器前面,这样当滑动后checkbox用的还是原来的监听器,而里面的位置也是原来的,所以就可能产生状态值改变的情况,进而导致错乱。
解决方案:把checkbox状态初始化放到监听器后面执行
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
if(convertView==null){
convertView=LayoutInflater.from(ThirdActivity.this).inflate(R.layout.item, null, false);
holder=new ViewHolder();
holder.cb=(CheckBox) convertView.findViewById(R.id.cb);
holder.tv=(TextView) convertView.findViewById(R.id.tv);
convertView.setTag(holder);
}else{
holder=(ViewHolder) convertView.getTag();
}
holder.tv.setText(list.get(position));
/**
* listview的复用机制导致产生固定数目的item实例(打印对象实例可证实)
* 每次上下滑动第一个item不可见,新滑上来的item可见(其实是复用的第一个item的实例)
* 如果初始化checkbox的状态放在监听器之前,则会产生错乱:由于第一个checkbox的监听器已经注册过,实例也已经存在
* 在滑动的时候新的item(其实是复用的第一个item的实例)根据positon是没有选中的,所以会触发监听器(旧的监听器)
* 而此刻监听器中的positon仍然是第一个item的position,所以改变了保存在map中第一个item的状态值
* 所以此时再滑动回来,第一个item中的状态已经改变,最终导致错乱
* 解决方法:把监听器放在初始化后面,这样每次的监听器都是最新new出来的(虽然item对象是复用的),所以position也是最新的
*/
// System.out.println("@@@@@@@@@@@"+position+holder.cb.toString());
final ViewHolder hol=holder;
holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// TODO Auto-generated method stub
if(isChecked){
ThirdActivity.this.isChecked.put(position, true);
}else{
ThirdActivity.this.isChecked.put(position, false);
}
// System.out.println("$$$$$$$$$$$$$$$$$$$$"+position+isChecked);
}
});
holder.cb.setChecked(isChecked.get(position));
return convertView;
}
这样保证每次都是新的监听器,位置也是最新的,虽然滑动的时候仍然会触发onCheckedChangeListener,但是不会改变原来已经勾选的checkbox的状态值,也就不会发生错乱了。
另外还有两种其他的解决方案,就不在此处列出了,需要的朋友可以去自行下载,里面包含了三种listview+checkbox的不同处理方式:http://download.csdn.net/detail/binbinqq86/9180557
本文探讨了在Android项目中使用ListView配合CheckBox实现多选时遇到的状态错乱问题。通过使用Map来保存每个position的CheckBox状态,以防止因ListView复用机制引起的错误。然而,在实践中仍可能出现错乱。作者分享了自己的经历,并提出将CheckBox状态初始化放在监听器后面执行作为解决方案。同时,提供了包含三种不同处理方式的代码下载链接以供参考。

1万+

被折叠的 条评论
为什么被折叠?



