ListView中为解决CheckBox重用而引入的新问题

ListView为了优化滑动速度,经常涉及到控件重用的问题,已经选中的CheckBox在滑动消失之后,会导致最新出现的Item由于复用的问题,与消失的Item中CheckBox选中状态一致(实际未选中)。
一般通过设置一个list列表用来存储选中item的对象的唯一标识符(可以为id或者position),在getView()中判断list中是否存在当前的id或者position。存在则设置为checked,否则设置为unchecked。

Adapter中getView函数如下所示:

@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // TODO Auto-generated method stub\
            ViewHolder holder = null;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = LayoutInflater.from(context).inflate(
                        R.layout.listviewitem, null);
                holder.idTv = (TextView) convertView.findViewById(R.id.id_tv);
                holder.nameTv = (TextView) convertView
                        .findViewById(R.id.name_tv);
                holder.selected = (CheckBox) convertView
                        .findViewById(R.id.select_cb);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            final Dialog dialog = dialogList.get(position);
            holder.idTv.setText(dialog.getId() + "");
            holder.nameTv.setText(dialog.getName());

            ***if (checkedList.contains(dialog.getId() + "")) {
                holder.selected.setChecked(true);
                Log.e(TAG, "checkedList contains" + dialog.getName());
            } else {
                holder.selected.setChecked(false);
                Log.e(TAG, "checkedList not contains" + dialog.getName());
            }***

            ***holder.selected
                    .setOnCheckedChangeListener(new OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView,
                                boolean isChecked) {
                            // TODO Auto-generated method stub
                            if (isChecked) {
                                checkedList.add(dialog.getId() + "");
                                Log.e(TAG, "add " + dialog.getName());
                            } else {
                                checkedList.remove(dialog.getId() + "");
                                Log.e(TAG, "remove " + dialog.getName());
                            }
                        }
                    });***

            return convertView;
        }

上述代码中,先对checkBox的选中状态进行判断,然后对checkBox设置监听器。
在运行过程中,如图:
ListView中为解决CheckBox重用而引入的新问题_第1张图片

我先选中AA0 AA1 AA2三个item,然后将这三个item滑出屏幕所见范围。再滑回来,发现如图所示

ListView中为解决CheckBox重用而引入的新问题_第2张图片
AA0 AA1 AA2 三个Item选中状态都消失了。查看代码逻辑,发现完全是按照处理checkBox选中状态的解决方法做的,但是结果与预想的要差很多。
如果改变代码中两个斜体加粗代码块的顺序,先对checkBox设置监听器,然后再对checkBox的选中状态进行判断。发现运行结果与预想的一样。原因肯定出在这两段代码的执行顺序上。
仔细分析一下ListView的优化方法——控件重用。问题的原因就在这里!!!

假设一共有20个Item,而屏幕可以显示10个Item,首先显示从0-9的前10个Item,当position=0的Item滑出屏幕时,position=11(而不是10,实验得出,但还没分析具体原因)的Item要使用position=0的Item使用过的所有空间。在getView中,代码是逐行,按照顺序执行的。此时先判断checkedList中是否存在position=11的item的选中状态,发现没有,则执行setChecked(false)操作,然后再对position=11的Item中checkBox设置监听。
但是!!!!
setChecked操作是会触发监听器的!!!
这就导致,当对position=11设置为checked=false时,触发了checkBox的监听器,而此时,由于还没有执行到对position=11的checkBox设置监听器代码,因此实际触发的是position=0的item中checkBox的监听器。这就导致了,position=0的item中checkBox执行了onCheckedChanged(),将position=0的Item的选中标识从checkedList中删除。因此,在重新显示position=0的Item时,系统发现checkedList中没有position=0的Item的标识,因此将其设置为未选中状态,导致出现问题。

如果改变代码的顺序,就会先设置监听器,此时改变选中状态时触发的就是position=11的Item中checkBox的监听器。但是,这样做,就会导致在checkedList出现很多重复的值,因此setChecked()方法触发了监听器,而list是可以存在重复的数值的,这样势必会导致Item较多的情况中,执行contains()时比较耗时。

因此,我们可以保留最初的代码,仍然先执行设置监听器,然后判断选中状态。在设置监听器之间,加上一行代码:holder.selected.setOnCheckedChangeListener(null)。先清空监听器,防止对setChecked进行干扰。

这样,既避免了checkedList中出现重复,也避免了选中状态消息的问题。

Demo:http://download.csdn.net/detail/liuweiballack/8798093

你可能感兴趣的:(Android经验)