通过列表视图检查动态生成的复选框时出现问题

时间:2011-10-12 10:19:23

标签: android listview checkbox

我知道其他成员已经提出过这个问题,某些成员也给出了解决方案,但问题是我没有找到适合我的应用程序的任何解决方案。 我正在创建一个应用程序,其中我有一个屏幕,它将显示带有列表项的动态列表视图复选框和三个文本视图(一个用于候选名称,另外两个用于clockIn和clockOut时间,将在选择日期和时间后显示日期时间选择器。。现在我的问题是当我检查第一个复选框(我有15个候选名称带复选框)时,第10个复选框自动检查,这也发生在第2个& 11日,3日和11日12等等(反之亦然)。我在提供我的适配器类和列表项xml。

import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;

import com.android.feedback.ListViewCheckBox;

public class DemoAdapter extends ArrayAdapter<String>{

    private final List<String> list;
    private final Activity context;
    LayoutInflater inflater;
    TextView CItv,COtv;
    static ViewHolder holder;
    View view;

    public DemoAdapter(Activity context, List<String> list) {
        super(context, R.layout.test_listitems,list);
        // TODO Auto-generated constructor stub

        this.context = context;
        this.list = list;
    }

    static class ViewHolder {
        protected TextView text,CItv,COtv;
        protected CheckBox checkbox;
    }


    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
          view = null;
        //  final ArrayList<Integer> checkedItems = new ArrayList<Integer>(); 
        if (convertView == null) {

            inflater = context.getLayoutInflater();
            view = inflater.inflate(R.layout.test_listitems, null);
            final ViewHolder viewHolder = new ViewHolder();
            viewHolder.CItv = (TextView)view.findViewById(R.id.CITextView);
            viewHolder.COtv = (TextView)view.findViewById(R.id.COTextView);
            viewHolder.text = (TextView) view.findViewById(R.id.empTextView);
            viewHolder.checkbox = (CheckBox) view.findViewById(R.id.empCheckBox);

            viewHolder.checkbox
                    .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView,
                                boolean isChecked) {


                            if(isChecked){  
                                Object o = getItemId(position+1);
                                String keyword = o.toString();
                                Toast.makeText(getContext(), "You selected: " + keyword, 2000).show();

                            Toast.makeText(getContext(),ListViewCheckBox.DT_selected, 2000).show();
                                //  holder.CItv.setText(ListViewCheckBox.DT_selected);
                                //  holder.COtv.setText(ListViewCheckBox.outDT_selected);
                                }

                            else{
                                Object o = getItemId(position+1);
                                String keyword = o.toString();
                                //Toast.makeText(getContext(), "You unselected: " + keyword, 2000).show();
                                holder.CItv.refreshDrawableState();
                                holder.COtv.refreshDrawableState();

                            }



                        }
                    });

            view.setTag(viewHolder);
            viewHolder.checkbox.setTag(list.get(position));
            viewHolder.checkbox.setId(position);
        } else {
            view = convertView;
            ((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
        }
        holder = (ViewHolder) view.getTag();
        holder.text.setText(list.get(position));



        return view;

        }

    }

和XML。

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


    <TableLayout android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:stretchColumns="1,2,3">

    <TableRow >


     <CheckBox    android:text=" " android:id="@+id/empCheckBox"
                  style="@style/Check" android:textColor="#000000"
                  android:textSize="12dp" 
                  android:layout_weight="1"/>

     <TextView    android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/empTextView"
                 style="@style/CICOTextView"
                 android:layout_weight="2"/>

    <TextView    android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/CITextView"
                 style="@style/CICOTextView"
                 android:text=""
                 android:layout_weight="3"/>    

    <TextView    android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:id="@+id/COTextView"
                 style="@style/CICOTextView"
                 android:text=""
                 android:layout_weight="4"/>     

    </TableRow>
    </TableLayout>
</LinearLayout>

请帮我摆脱这个问题。(ListViewCheckBox是一个生成列表并在变量DT_selected和outDT_selected中存储日期和时间值的类。)

4 个答案:

答案 0 :(得分:31)

我编辑了我的答案,因此常用信息位于顶部。你会在底部找到这个问题的实际答案......


这是回收的实际想法和过程,因此您可能会发现getView的实施和想法有什么问题(当他们找到这个问题和答案时也可能是其他人)。请参阅下面的代码示例,只需忽略类型部分,因为这是一个附加信息。

  • 第1阶段:要回收的项目创建(convertViewnull):
    这意味着您可以创建布局和所有项共享的公共状态。如果你有听众,你可以在这里添加它们,并设计它们以便以后可以对位置变化做出反应(当它被重用时)。因此,例如通过在相应视图上将位置设置为标记,以便侦听器可以捕获此信息并知道它当前正在操作的项目。您无法使用视图来存储数据。因此,当侦听器更改列表项上的状态时,您应该保留此数据(在数据数组中,在SQLite数据库中等)并在阶段2 中使用它。

  • 阶段2:给定位置的设置项状态:
    您可以设置项目的可视状态。必须在此处设置可能针对某个项目(文本,复选框状态,颜色等)单独更改的所有内容。不仅当前项目已更改,而且可能已被其他项目更改。这样,您可以确保视图未在无效状态下使用,因为它之前正在从其他列表项重用。


已删除/编辑了已回复的答案,但建议实施getItemViewTypegetViewTypeCount,以便每个列表项都有自己的视图类型。 The edited answer现在展示如何按照此处描述的方式解决问题。

重新实现getItemViewTypegetViewTypeCount有效,但您明显误解了它的用法(比较下面的示例和/或this answer)。

这两种方法可以使用两个(或更多)完全不同的列表项(例如,常用列表项和仅包含标题的分隔符),而不是为了避免回收可以重用的视图

如果您正在使用它们来解决您的问题,您可能不理解我之前解释过程。所以例如你有1000个项目并且你进行了视图类型 hack 然后你创建了1000个视图(层次结构)而不是10个可以重复使用 的视图。如果您只有20个左右的项目,那就不重要了,但如果您将这种技术用于大型列表,那么您只是浪费(珍贵)内存!

以下是一个例子:

void getItemViewType(int position) {
    return isItemAtPositionSeperator(position) ? 1 : /* normal item */ 0;
}

void int getViewTypeCount() {
    return 2; // normal item and separator
}

void View getView(int position, View convertView, ViewGroup parent) {
    int type = getItemViewType(position);

    // phase 1: see my explanation 
    if (convertView == null) {
        if (type == 0) {
            // setup your common item view - inflate it and set to convertView
        } else {
            // setup separator view - inflate it and set to convertView
        }
    }

    // phase 2: see my explanation 
    if (type == 0) {
        // set the state of the common item view based on the position
        // rely on the fact that convertView contains the view hierarchy
        // you created in convertView == null && type == 0
    } else {
        // set state of the separator based on the position
        // rely on the fact that convertView contains the view hierarchy
        // you created in convertView == null && type != 0 (else part)
    }

    return convertView;
}

问题的实际答案......

我知道问题是什么,但现在想不出优雅的解决方案......

您的问题是,在创建视图时,您使用viewHolder.checkbox.setOnCheckedChangeListener设置了一次点击侦听器。因此,当您滚动并且点击行为适用于错误的列表项时,它会被循环/重复用于项目。

尽量不要使用外部final position 硬编码这个位置。尝试在viewHolder.checkbox.setTag(position)之前设置return,然后使用(Integer) buttonView.getTag()代替position+1。因此,您的回收视图将保持实际位置。

当您单击复选框时,您应该将状态保留在其他位置。不要依赖于UI状态(因为它将被回收)。请在viewHolder.checkbox.setChecked(persistedState)之前致电return

我希望这是有道理的,你明白了......; - )

答案 1 :(得分:13)

试试这个,

创建一个POJO类,它将维护Checkbox所选项目的状态,如

public class Model {

    private String name;
    private boolean selected;

    public Model(String name) {
        this.name = name;
        selected = false;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}

这就是你必须在适配器中应用getView()方法的东西。

public View getView(int position, View convertView, ViewGroup parent) {

        checkBoxCounter = 0;      
        checkBoxInitialized = 0;   
        if (convertView == null) {
            final ViewHolder viewHolder = new ViewHolder();
            LayoutInflater inflator = context.getLayoutInflater();
            convertView = inflator.inflate(R.layout.main, null);
            viewHolder.text = (TextView) convertView.findViewById(R.id.label);
            viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.check);

            viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    Model element = (Model) viewHolder.checkbox.getTag();
                    element.setSelected(buttonView.isChecked());

                    if(checkBoxCounter <= checkBoxInitialized){
                    // increment counter, when we scroll the List it execute onCheckedChanged everytime so by using this stuff we can maintain the state
                    checkBoxCounter++;
                }
                else{
                    Model element = (Model) viewHolder.checkbox.getTag();
                    element.setSelected(buttonView.isChecked());

                    if(element.isSelected())
                    Toast.makeText(getContext(), "You selected "+ element.getName(), Toast.LENGTH_LONG).show();
                    else
                        Toast.makeText(getContext(), "Not selected "+ element.getName(), Toast.LENGTH_LONG).show();
                    }
                }
            });
            convertView.setTag(viewHolder);
            viewHolder.checkbox.setTag(list.get(position));
        } 
        else{
            ((ViewHolder) convertView.getTag()).checkbox.setTag(list.get(position));
        }

        ViewHolder viewHolder = (ViewHolder) convertView.getTag();
        viewHolder.text.setText(list.get(position).getName());
        viewHolder.checkbox.setChecked(list.get(position).isSelected());
        return convertView;
    }

有关其工作原理的进一步研究,您可以查看完整的example。您还可以查看How ListView Works

更新:我最近在博客上为此类问题添加了解决方案。 ListView with CheckBox Scrolling Issue

答案 2 :(得分:4)

访问以下链接并滚动到单个vrs Multiselection。在这里,您可以找到在listview中使用复选框的非常好的示例

(向下滚动到Single vrs.Multiiselection)

http://www.vogella.de/articles/AndroidListView/article.html

以及

Checkbox in listview with Custom SimpleCurser binding

答案 3 :(得分:0)

您应该使用布尔数组来跟踪每个列表项的选中状态,在setOnCheckedChangeListener()内记录更改,然后在{{1 }}。