关于listView+checkBox多选错乱的分析

本文探讨了在Android项目中使用ListView配合CheckBox实现多选时遇到的状态错乱问题。通过使用Map来保存每个position的CheckBox状态,以防止因ListView复用机制引起的错误。然而,在实践中仍可能出现错乱。作者分享了自己的经历,并提出将CheckBox状态初始化放在监听器后面执行作为解决方案。同时,提供了包含三种不同处理方式的代码下载链接以供参考。

转载请注明: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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值