Android:ListView和CheckBox的问题

7
我有一个ListView,在每个列表项内我有一些TextView和一个CheckBox。当我选中一个CheckBox并触发我的onCheckedChangeListener时,一切都按预期工作。然而,随机的其他复选框在选中一个后也被选中了。以下是一个例子。
如果我点击第一个复选框: 8 被选中。 15 被选中。 21 被选中。 27 被选中。 33 被选中。 41 被选中。 然后,如果我滚动到顶部,直到6号才会出现选中状态。下一个是13。
基本上...出了什么问题?
6个回答

10
似乎你正在重复使用在你实现的getView()方法中传递的convertView。Android会尝试为ListView中的不同项使用相同的视图。你必须要么(1)在返回getView之前始终调用setChecked手动取消/选中返回项内的复选框,要么(2)不使用convertView而是从getView返回一个新的View。
我认为推荐使用(1)。

当用户勾选复选框时,您需要在代码中的某个位置存储该项复选框已被选中的信息。例如,如果您的ListView中有40个项目,则可以使用布尔数组来存储第i个复选框是否已被选中。然后在getView方法中,使用checkBox.setChecked(booleanArray[position])来设置复选框是否选中。 - Randy Sugianto 'Yuku'
也许是因为太晚了,我无法弄清楚如何记录位置。在getView方法中,我可以获得位置,但在需要实际存储位置的OnClick中,我不再能够访问它。 - Kyle Hughes
在getView方法中,使用setTag()来存储位置信息,并且在onClick方法中使用getTag()来获取。详情请参考:http://developer.android.com/reference/android/view/View.html#setTag(java.lang.Object) - Randy Sugianto 'Yuku'
1
永远不要使用(2)。convertView是为了正确地回收视图而存在的。如果您在listView中为每个项目都使用新视图,那么将消耗大量内存,结果会很糟糕。只需假设具有500-1000个视图的布局即可。 - VipulKumar
这个问题有什么线索吗? - gumuruh
显示剩余2条评论

6

对我来说运行良好。

public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

        final ViewHolder holder;
        final Season season = (Season) getGroup(groupPosition);
        if (convertView == null) {
            LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = vi.inflate(R.layout.season, parent, false);
            holder = new ViewHolder();
            holder.title = (TextView) convertView.findViewById(R.id.season_title);
            holder.checkBox = (CheckBox) convertView.findViewById(R.id.season_check_box);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.title.setText(season.getTitle());
        holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                season.setChecked(isChecked);
                adapter.notifyDataSetChanged();
            }
        });

        holder.checkBox.setChecked(season.isChecked()); // position is important! Must be before return statement!
        return convertView;
    }

    protected class ViewHolder {
        protected TextView title;
        protected CheckBox checkBox;
    }

我不知道这是什么季节,但对我来说似乎有效。对于那些遇到问题的人(我个人没有复选框的问题,但删除线效果会随机出现在项目上),所以:final ViewHolder holder;和setOnCheckedChangeListener是让事情正常工作的两个关键点。希望这能帮助其他人!谢谢Georgy! - erdomester
你太棒了,伙计!我进行了大量的研究和开发。这是最佳解决方案。 - madhu527
@Georgy Gobozov,请您也关注一下这个问题:https://stackoverflow.com/questions/44561788/checkbox-checked-unchecked-state-changes-on-scroll-listview-baseadapter# - user1919

1
我曾经遇到过与我的切换视图相同的问题。 经过大量搜索和阅读,我发现这个问题是由Android推广的一种视图优化引起的。 它尝试重用视图对象。 因此,基本上,当您单击开关或复选框时,您也会触发另一个视图中的更改回调。 优化确实有效,但如果您不注意,或者只是不知道行为(像我一样),就会出现一些非常奇怪的结果。
无论如何,我找到了这个简单的解决方案:
最佳实践是重用在getView()方法中传递的convertView。 这样可以优化列表视图使用的RAM数量。 所以我将我的视图存储在ViewHolder中并编写了onCheckedChanged事件。 诀窍是,在设置开关是否已选中之前(在您的情况下,检查复选框)并定义CheckedChange侦听器之前,我只需重置回调为空,这可以确保不会触发另一个开关的事件。
以下是代码片段:
...
viewHolder.switchView.setOnCheckedChangeListener(null);
viewHolder.switchView.setChecked(myObject.getStatus() > 0);
...
viewHolder.switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                ...
            }
        });

myObject是一个最终对象,其中存储了Cursor数据。

无论开关是否被选中,您都需要调用setChecked方法。

希望对某些人有所帮助!


0

我之前也遇到了类似的问题,经过大量阅读后,我是这样解决的:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.listview, null);
            holder = new ViewHolder();
            holder.nameView = (TextView)convertView.findViewById(R.id.textView1);
            holder.numberView = (TextView)convertView.findViewById(R.id.textView2);
            holder.cb = (CheckBox)convertView.findViewById(R.id.checkBox1);
            convertView.setTag(holder);                
        } else {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.nameView.setText(mData.get(position).toString());
        holder.numberView.setText(mNumber.get(position).toString());
        holder.cb.setChecked(false);
        holder.cb.setTag(position);



        if(selected.indexOf(mNumber.get(position).toString()) >= 0) // Check whether this row checkbox was checked, for that we previously stored the textview text in selected variable
        {

        holder.cb.setChecked(true);
        }

        return convertView;
    }

}

这里我所做的是在getView()中取消所有复选框的选中状态,然后手动重新勾选需要根据其对应的TextView检查的复选框。 因此,如果用户在选中第一个复选框后向下滚动,则视图中的所有复选框都将取消选中状态,如果再次向上滚动,则所有复选框都将取消选中状态,但是再次单击的那个复选框将再次被勾选。


0
@Override
public int getViewTypeCount() {
    return getCount();
}

@Override
public int getItemViewType(int position) {
    return position;
}

@Override
public int getCount() {
    return names.length;
}

@Override
public long getItemId(int position) {
    return 0;
}

在自定义适配器中添加此方法,这对我有效。


0

通过保持CheckBox的状态,可以轻松解决问题。下面是一个自定义且完整解释的示例类代码:

 public class AreaDataAdapter extends ArrayAdapter<String> {
    private List<String> areaList;
    private Activity context;
    ArrayList<Boolean> positionArray;

    public AreaDataAdapter(Context context, int textViewResourceId,
            List<String> offersAreaList) {
        super(context, textViewResourceId, offersAreaList);
        // TODO Auto-generated constructor stub
        this.context=context;
        this.areaList = offersAreaList;

         positionArray = new ArrayList<Boolean>(offersAreaList.size());
            for(int i =0;i<offersAreaList.size();i++){
                positionArray.add(false);
          }

    }

    public AreaDataAdapter(Activity context, List<String> offersAreaList) {
        super(ShowOffersActivity.this, R.layout.filterlist_row, offersAreaList);
        this.context=context;
        this.areaList=offersAreaList;

         positionArray = new ArrayList<Boolean>(offersAreaList.size());
            for(int i =0;i<offersAreaList.size();i++){
                positionArray.add(false);
          }

    }

    public View getView(final int position, View convertView,ViewGroup parent) {
        View row=convertView;
        FilterViewHolder holder;
        if (row==null) {
            LayoutInflater inflater=getLayoutInflater();

            row=inflater.inflate(R.layout.filterlist_row, parent, false);
            holder = new FilterViewHolder();
            holder.filterCheckBox = (CheckBox)row.findViewById(R.id.filter_checkbox);
            holder.filterCheckBox.setTypeface(fontFace);

            row.setTag(holder);
        } else {
            //holder = (View) row;
            holder = (FilterViewHolder) row.getTag();

            /* When a listview recycles views , it recycles its present state as well as listeners attached to it.
             * if the checkbox was checked and has a onCheckedChangeListener set, both will remain a part of 
             * recycled view based on position. So it is our responsibility to reset all states and remove
             *  previous listeners.
             *  The listener was removed as below:-
             */
            holder.filterCheckBox.setOnCheckedChangeListener(null);

        }

            holder.filterCheckBox.setText(areaList.get(position));
            holder.filterCheckBox.setFocusable(false);
            holder.filterCheckBox.setChecked(positionArray.get(position));
            holder.filterCheckBox.setText(areaList.get(position));

            holder.filterCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    // TODO Auto-generated method stub
                    if (isChecked) {
                        //change state of concern item in 'positionArray' array to 'True'
                         positionArray.set(position, true);
                        //add the checked item in to area list which used to filter offers.
                        filterOffersByThisAreaList.add(areaList.get(position));

                    } else {
                       //change state of concern item in 'positionArray' array to 'True'
                         positionArray.set(position, true);
                        //remove the unchecked item in to area list which used to filter offers.
                        filterOffersByThisAreaList.remove(areaList.get(position));

                    }
                }
            });
        return row;
    }

}

以下是用于ListView包含这些CheckBox的自定义行(filterlist_row)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<CheckBox
    android:id="@+id/filter_checkbox"
    style="@style/CodeFont"
    android:button="@drawable/bg_custom_checkbox"
    android:text="Indian" />

<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#c9c9c3" />

</LinearLayout>

自定义 CheckBox 背景的方法如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:drawable="@drawable/checkbox_s" android:state_checked="true"></item>
 <item android:drawable="@drawable/checkbox_ns" android:state_checked="false"></item>

</selector>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接