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

Posted

技术标签:

【中文标题】通过列表视图检查动态生成的复选框时出现问题【英文标题】:Getting an issue while checking the dynamically generated checkbox through list view 【发布时间】:2011-12-06 00:21:36 【问题描述】:

我知道其他成员已经提出了这个问题,并且一些成员也给出了解决方案,但问题是我没有找到适合我的应用程序的任何解决方案。 我正在创建一个应用程序,其中我有一个屏幕,它将显示带有列表项的动态列表视图一个复选框和三个文本视图(一个用于候选人姓名,另外两个用于时钟输入和时钟输出时间,将在选择日期和时间后显示日期时间选择器)。现在我的问题是,当我检查第一个复选框(我有 15 个带有复选框的候选名称)时,第 10 个复选框会自动检查自己,这也发生在第 2 和第 11、第 3 和第 12 等等(反之亦然true).这里我提供了我的适配器类和列表项 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_
  android:layout_>


    <TableLayout android:layout_
                 android:layout_
                 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_
                 android:layout_
                 android:id="@+id/empTextView"
                 style="@style/CICOTextView"
                 android:layout_weight="2"/>

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

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

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

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

【问题讨论】:

请粘贴您的运行代码或编辑上面的代码。我已经多次遇到这个问题但没有得到答案。 你可以从here获取运行代码 【参考方案1】:

我编辑了我的答案,因此常见信息位于顶部。您将在底部找到此问题的实际答案...


这是回收的实际想法和过程,因此您可能会弄清楚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 对位置进行硬编码。尝试在return 之前设置viewHolder.checkbox.setTag(position),然后使用(Integer) buttonView.getTag() 而不是position+1。所以你的回收视图将保持实际位置。

当您单击复选框时,您应该将状态保留在其他地方。不要依赖 UI 状态(因为它会被回收)。所以在return之前调用viewHolder.checkbox.setChecked(persistedState)

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

【讨论】:

谢谢大家的宝贵cmets。 很好,是的,我知道这两种方法用于 ListView 中超过 1 个项目,但这是解决 ListView 项目循环的解决方案吗? 这就是它的工作原理,是的:这是回收视图的正确解决方案。我不明白这种模式有什么问题。有用。它以正确的方式做到了。用这种模式没有什么是你做不到的。我承认这不是一个简短的解决方案。但尽管如此,它还是很干净的……至少当您尝试显示 1000 多个项目时,您会这样做。 不错的解决方案我也使用了@LalitPoptani 提供的解决方案,我将在您再次解释时更改我的代码。 感谢您的好主意。我已经编辑了我的答案并试图解决这个问题。非常感谢。【参考方案2】:

试试这个,

创建一个 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

【讨论】:

非常感谢您解决了我的问题。您的解决方案对我有用。 欢迎您,如果我的回答根据您的要求是正确的,您可以勾选答案以帮助其他用户。 这对我来说似乎是一个 hack,因为您通过绕过回收机制来解决问题(因此您的整个 convertView == null 变得过时了)。你会后悔你的决定,例如100 项。还是我弄错了?! 我不这么认为。这对我来说是不好的风格。尤其是当你能做对的时候。如果您知道自己在做什么,则列表视图没有任何问题。它可能会变得很棘手,但这不是绕过常见模式的理由,因为你无法做到正确。只是我的意见... 我有。见我的updated answer。这详细解释了如何正确使用列表...【参考方案3】:

访问下面的链接并滚动到单个 vrs 多选。在这里您可以找到在列表视图中使用复选框的非常好的示例

(向下滚动到单选与多选)

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

还有

Checkbox in listview with Custom SimpleCurser binding

【讨论】:

【参考方案4】:

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

【讨论】:

以上是关于通过列表视图检查动态生成的复选框时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

复选框列表不会在动态生成的复选框 v-models 数组中更新

在由 AJAX 插入的动态创建元素上绑定事件(复选框)

动态填充 jQuery Mobile 列表视图内容

将复选框动态添加到列表视图

将 ngModel 绑定到动态复选框列表:Angular 2 / Typescript

动态生成链接菜单 vb.net