这个问题恐怕很多人在刚开始使用ListView时都接触到过,就是比如listview的item中有一个Button或一个CheckBox,你明明点击按键改变了他的背景图片或者问题,又或者勾选了CheckBox,但是你一滑动,发现下面你没操作的item也跟着改变了,然后你再滑动回去,结果原来item上面的操作又变没了。这就是listview中item复用时所产生的问题,下面这种图就是例子。
上图就是例子,下滑时,下面明明没有勾选的CheckBox也被勾选上了。当然 这里只是两个例子,群里的小伙伴问过几次,有的item点击字体变色,滑动时没了之类的,这些都同属于一个原理。那么该如何解决这个问题呢?
先分析下这个问题出现的原因:
listview作为一个能加载理论上无限数据的控件,他本是是不存在储存那么多item的空间的,他其实实现了一个复用的机制。先看下面这张图,图是百度的,我自己花了一份 ,太难看了,意思是一样的。
上图中一个listview有7个item,但是当他上滑是,item1渐渐消失,item8出来,其实这时候产生的item8出现的的部分就是item1消失的部分,这就是listview的复用,在这种机制下,其实这个listview总共就是对着7个item的操作,却完成了对N多数据的加载。但是这种方法就出现了上述的问题,他会时控件的事件错乱,比如item1中有一个checkbox勾选了,上拉后,item8复后,连勾选这个图像一起复用了,所以导致了listview不断滑动过程中,checkbox的勾选情况不断变化的情况。那么这种情况如何解决呢?
看下面代码:
public class TestAdapter extends BaseAdapter{
private List<String> list;
private Context context;
ViewHolder holder = null;
public static HashMap<Integer,Boolean> isCheck;
public static HashMap<Integer,Boolean> isSelect;
public TestAdapter(List<String> list, Context context) {
this.list = list;
this.context = context;
isCheck = new HashMap<Integer,Boolean>();
isSelect = new HashMap<Integer,Boolean>();
initData();
}
private void initData() {
for(int i = 0;i< list.size();i++){
isCheck.put(i,false);
setIsCheck(isCheck);
isSelect.put(i,false);
}
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if(convertView == null){
holder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
holder.button = (Button) convertView.findViewById(R.id.button);
holder.checkBox = (CheckBox) convertView.findViewById(R.id.check_box);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(getIsCheck().get(position)){
isCheck.put(position, false);
setIsCheck(isCheck);
}else{
isCheck.put(position, true);
setIsCheck(isCheck);
}
}
});
holder.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isSelect.get(position)) {
holder.button.setText("按下去");
isSelect.put(position, false);
notifyDataSetChanged();
} else {
holder.button.setText("弹出来");
isSelect.put(position, true);
notifyDataSetChanged();
}
}
});
if(isSelect.get(position)) {
holder.button.setText("按下去");
}else{
holder.button.setText("弹出来");
}
if(getIsCheck().get(position)){
holder.checkBox.setChecked(true);
}else{
holder.checkBox.setChecked(false);
}
return convertView;
}
class ViewHolder{
Button button;
CheckBox checkBox;
}
public static HashMap<Integer,Boolean> getIsCheck(){
return TestAdapter.isCheck;
}
public static void setIsCheck(HashMap<Integer,Boolean> isCheck){
TestAdapter.isCheck = isCheck;
}
}
在这里,我举了两个例子,一个是checkbox,一个是button,当然解决办法也是一样的,这是为了说明其实解决错乱的方法原理是一样的,不管是我写的这俩个,还是item字体颜色的变化之类的。
代码中,我用了HashMap来保存两个数据,第一个就是item的position值,这个值表示的是item真正在listview的序号而不是页面上这个item所在的序号。第二个则是这个position的事件情况,我这里用false表示未勾选,true表示勾选。
第一步:先将listview中所有的checkbox的勾选情况设为false,然后存入HashMap中,然后这段代码
holder.checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(getIsCheck().get(position)){
isCheck.put(position, false);
setIsCheck(isCheck);
}else{
isCheck.put(position, true);
setIsCheck(isCheck);
}
}
});
当你点击时,改变勾选情况,并将改变的情况在HashMap中更新,这里我写了这两个可供外部调用的方法
public static HashMap<Integer,Boolean> getIsCheck(){
return TestAdapter.isCheck;
}
public static void setIsCheck(HashMap<Integer,Boolean> isCheck){
TestAdapter.isCheck = isCheck;
}
是为外部不如需要全选或不全选时所使用的,你不用也行,那就像我button的那样写。在HashMap存入数据后,在最后一段
if(getIsCheck().get(position)){
holder.checkBox.setChecked(true);
}else{
holder.checkBox.setChecked(false);
}
中设置checkbox的勾选情况,这样事件就是根据HashMap中存入的数据来判断,而不是复用时的直接使用了,最后附一张完成图。