listView 中复选框的奇怪行为

Posted

技术标签:

【中文标题】listView 中复选框的奇怪行为【英文标题】:Strange behaviour of checkBox in the listView 【发布时间】:2015-04-01 08:48:43 【问题描述】:

我有一个带有listView的片段,listview布局包含(TextView、checkBox和ImageView如下

t1........CB.......IV
t2........CB.......IV
t3........CB.......IV
t4........CB.......IV
t5........CB.......IV
t6........CB.......IV
      SaveButton

在 getView() 方法中,我检查复选框是否被选中,如果被选中,我将选中的项目添加到列表中。

问题是,最初我将所有复选框设置为未选中,但是当我选中第一项时,令人惊讶的是,自动选中下面的第三项,如果我从上面选中第二项,则第二项从下面检查,如果我从上面检查第三项,那么最后一项会自动检查?!!

请看一下 getView() 方法,让我知道我错过了什么:

getView()

@Override
public View getView(final int position, View convertView, ViewGroup parent) 
    // TODO Auto-generated method stub

    if (convertView == null) 
        LayoutInflater layoutinflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        convertView = layoutinflater.inflate(R.layout.list_items_layout, null);
    

    if (this.checkedItemsList == null) 
        this.checkedItemsList = new ArrayList<String>();
    

    final TextView tv = (TextView) convertView.findViewById(R.id.tvlist_topic);
    final CheckBox cb = (CheckBox) convertView.findViewById(R.id.cbList_hook);
    final ImageView iv = (ImageView) convertView.findViewById(R.id.ivList_delete);

    tv.setText(this.topicsList.get(position));

    cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
            // TODO Auto-generated method stub
            if (isChecked) 
                checkedItemsList.add(topicsList.get(position));
                setCheckedItemsList(checkedItemsList);
                Log.d(TAG, "size: " + checkedItemsList.size());
            
        
    );

    iv.setOnClickListener(new OnClickListener() 

        @Override
        public void onClick(View v) 
            // TODO Auto-generated method stub
            if (cb.isChecked())
                cb.setChecked(false);

            topicsList.remove(position);
            notifyDataSetChanged();
        
    );

    return convertView;

xml

<RelativeLayout 
    android:id="@+id/rl2_List"
    android:layout_
    android:layout_
    android:layout_toEndOf="@id/rl1_List"
    android:layout_centerInParent="true">
    <CheckBox
        android:id="@+id/cbList_hook"
        android:layout_
        android:layout_
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:checked="false"/>
</RelativeLayout>

<RelativeLayout 
    android:id="@+id/rl3_List"
    android:layout_
    android:layout_
    android:layout_toEndOf="@id/rl2_List"
    android:layout_alignParentEnd="true"
    android:layout_marginStart="100dp">
    <ImageView 
        android:id="@+id/ivList_delete"
        android:layout_
        android:layout_
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:clickable="true"
        android:src="@drawable/delete_icon"
        android:contentDescription="icon to delete item from the Listview"/>
</RelativeLayout>

【问题讨论】:

你可以为这堆 tb -- cb -- iv 发布你的 xml 吗? 好的,但在 xml 中,我最初将复选框设置为 false 由于ListView的回收机制,看这个解释:***.com/a/14108676/4224337。在 getView() 方法中,您必须重新检查或重新取消检查 CheckBoxes 取决于它们的状态(选中/未选中) 使用 cb.setTag(position) 试试这个***.com/questions/28275273/… 【参考方案1】:

你必须保持你的对象检查状态。

我会在您的对象中(在主题列表中)实现一个实例变量,以指示它是否被选中。

类似

public class Topic

public boolean isChecked;

... //Other instance variables

public boolean isChecked() 
    return isChecked;


public void setChecked(boolean isChecked) 
    this.isChecked = isChecked;


...
//other getters and setters


然后在您的 listAdapter getView 方法中,检查此值并相应地设置复选框。

final CheckBox cb = (CheckBox) convertView.findViewById(R.id.cbList_hook);

boolean isChecked = topicsList.get(position).isChecked();

cb.setChecked(isChecked);

//Set a tag on the cb, to know which object it corresponds with
cb.setTag(topicsList.get(position));

此外,您必须在单击对象时更改对象检查值。

cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
        // TODO Auto-generated method stub
        if (isChecked) 
            checkedItemsList.add(topicsList.get(position));
            setCheckedItemsList(checkedItemsList);
            Log.d(TAG, "size: " + checkedItemsList.size());
        
        CheckBox cb = (CheckBox) buttonView;
        Object yourObject = (Object) buttonView.getTag();
        yourObject.setChecked(isChecked);
    
);

【讨论】:

【参考方案2】:

你需要了解convertView,对于初学者来说往往比较困惑。

例如,如果您在一个巨大的列表中有 100 个 Checkbox,但一次只有 5 个在屏幕上可见,那么幼稚的方法是为 Checkbox 创建 100 个对象(事实上,这就是它在早期版本的 Android 中的工作方式) .但是,创建新对象不仅在创建期间会消耗 CPU,而且在垃圾收集期间也会消耗 CPU。这就是使用 convertView 的原因。 Android 系统只会创建 5 个对象,并在用户滚动时重复使用相同的对象。就您而言,我认为屏幕上只有 3 个对象。因此,从技术上讲,第一个滚出屏幕的对象和第四个出现在屏幕上的对象是同一个对象。

您的解决方案是将View 对象视为可视元素并将状态保存在其他位置。我认为另一个答案建议使用代码,因此,我将避免建议代码并鼓励您理解这个概念并自己找出解决方案。

【讨论】:

我现在很困惑。【参考方案3】:

如果你使用ViewHolder 模式会更好。它将防止每次用户滚动列表时增加视图的开销。此外,数据源中是否应该存在复选框(您用于填充 ListView 的数据结构;在本例中为主题列表)。假设数据结构的模型类命名为Model:

public class Model 
 private boolean mChecked = false;

 public boolean isChecked() 
  return this.mChecked;
 
 public void setChecked(boolean checked) 
  this.mChecked = checked;
 

现在在 ListView 的适配器类中

public class SomeAdapter extends BaseAdapter 

 private ArrayList<Model> topicsList; //assuming this is filled with data
 static class ViewHolder  //the view holder's class
  TextView text;
  CheckBox checkBox;
  ImageView imageView;
 


 @Override
 public View getView(final int position, View convertView, ViewGroup parent) 
    // TODO Auto-generated method stub
    ViewHolder holder;
    if (convertView == null) 
        LayoutInflater layoutinflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        convertView = layoutinflater.inflate(R.layout.list_items_layout, null);
        TextView tv = (TextView) convertView.findViewById(R.id.tvlist_topic);
        CheckBox cb = (CheckBox) convertView.findViewById(R.id.cbList_hook);
        ImageView iv = (ImageView) convertView.findViewById(R.id.ivList_delete);
        holder = new ViewHolder(); 
        holder.textView = tv;
        holder.checkBox = cb;
        holder.imageView = iv;
        convertView.setTag(viewHolder);
     else 
        viewHolder = (ViewHolder) converView.getTag();
    

    if (this.checkedItemsList == null) 
        this.checkedItemsList = new ArrayList<String>();
    


    TextView textView = viewHolder.textView;
    CheckBox checkBox = viewHolder.checkBox;
    ImageView imageView = viewHolder.imageView;    

    textView.setText(this.topicsList.get(position));
    if (this.topicsList.get(position).isChecked()) 
     checkBox.setChecked(true);
     else 
     checkBox.setChecked(false);
    

    checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() 

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
            // TODO Auto-generated method stub
            if (isChecked) 
                SomeAdapter.this.topicsList.get(position).setChecked(isChecked);
              checkedItemsList.add(SomeAdapter.this.topicsList.get(position));
                setCheckedItemsList(checkedItemsList);
                Log.d(TAG, "size: " + checkedItemsList.size());
            
        
    );

    imageView.setOnClickListener(new OnClickListener() 

        @Override
        public void onClick(View v) 
            // TODO Auto-generated method stub
            if (checkBox.isChecked()) 
                checkBox.setChecked(false);
                SomeAdapter.this.topicsList.get(position).setChecked(false);
                topicsList.remove(position);
                notifyDataSetChanged();
             else  //if needed
                checkBox.setChecked(true); 
                SomeAdapter.this.topicsList.get(position).setChecked(true);
                //perform other operations here
            

        
    );

    return convertView;
 

【讨论】:

【参考方案4】:

你必须这样做

@Override
public View getView(final int position, View convertView, ViewGroup parent) 
// TODO Auto-generated method stub

if (convertView == null) 
    LayoutInflater layoutinflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    convertView = layoutinflater.inflate(R.layout.list_items_layout, null);


if (this.checkedItemsList == null) 
    this.checkedItemsList = new ArrayList<String>();


final TextView tv = (TextView) convertView.findViewById(R.id.tvlist_topic);
final CheckBox cb = (CheckBox) convertView.findViewById(R.id.cbList_hook);
final ImageView iv = (ImageView) convertView.findViewById(R.id.ivList_delete);

tv.setText(this.topicsList.get(position));
cb.setTag(position);
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
        // TODO Auto-generated method stub
        if (isChecked) 
            checkedItemsList.add(topicsList.get(Integer.parseInt(buttonView.getTag().toString())));
            setCheckedItemsList(checkedItemsList);
            Log.d(TAG, "size: " + checkedItemsList.size());
        
    
);



return convertView;

【讨论】:

这并不能完全解决问题,但他可以绕过它来解决他的问题。这将有助于准确设置checkedItemsList。 如果它不能解决问题,那么它在技术上也不是一个答案,是吗? 您能否编辑您的答案以提供一些解释。不仅仅是一段代码。

以上是关于listView 中复选框的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

QT 5.1.1:QWebview 中的复选框在 Win 7 (x64) / Win 8 下显示奇怪的行为

TableView 中 RadioButton 的奇怪行为

列表视图项目的不可预测的行为复选框单击

Android Checkbox listview全选(禁用/启用)[重复]

如何在 ListView 内的复选框中获取位置

禁用listView android中复选框的更改