具有包含 CheckBoxes 的自定义适配器的 Listview

Posted

技术标签:

【中文标题】具有包含 CheckBoxes 的自定义适配器的 Listview【英文标题】:Listview with custom adapter containing CheckBoxes 【发布时间】:2012-09-20 18:01:53 【问题描述】:

我有一个使用自定义适配器的 ListView,如下所示:

private class CBAdapter extends BaseAdapter implements OnCheckedChangeListener

    Context context;
    public String[] englishNames;
    LayoutInflater inflater;
    CheckBox[] checkBoxArray;
    LinearLayout[] viewArray;
    private boolean[] checked;

    public CBAdapter(Context con, String[] engNames)
        context=con;
        englishNames=engNames;
        inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        checked= new boolean[englishNames.length];
        for(int i=0; i<checked.length; i++)
            checked[i]=false;
            //Toast.makeText(con, checked.toString(),Toast.LENGTH_SHORT).show();
        
        checkBoxArray = new CheckBox[checked.length];
        viewArray = new LinearLayout[checked.length];
    

    public int getCount() 
        return englishNames.length;
    

    public Object getItem(int position) 
        // TODO Auto-generated method stub
        return null;
    

    public long getItemId(int position) 
        // TODO Auto-generated method stub
        return 0;
    

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

        if(viewArray[position] == null)

            viewArray[position]=(LinearLayout)inflater.inflate(R.layout.record_view_start,null);

            TextView tv=(TextView)viewArray[position].findViewById(R.id.engName);
            tv.setText(englishNames[position]);

            checkBoxArray[position]=(CheckBox)viewArray[position].findViewById(R.id.checkBox1);
        

        checkBoxArray[position].setChecked(checked[position]);
        checkBoxArray[position].setOnCheckedChangeListener(this);
        return viewArray[position];
    


    public void checkAll(boolean areChecked)
        for(int i=0; i<checked.length; i++)
            checked[i]=areChecked;
            if(checkBoxArray[i] != null)
                checkBoxArray[i].setChecked(areChecked);
        
        notifyDataSetChanged();
    

    public void onCheckedChanged(CompoundButton cb, boolean isChecked) 
        for(int i=0; i<checked.length; i++)
            if(cb == checkBoxArray[i])
                checked[i]=isChecked;
        




    
    public boolean itemIsChecked(int i)
        return checked[i];
    


布局相当简单,所以除非有人认为它们相关,否则我不会发布它们。

问题是某些复选框没有响应。它似乎是第一次显示布局时可见的那些。您必须向下滚动才能按预期工作的任何内容。

任何指针表示赞赏。

【问题讨论】:

【参考方案1】:

答案中的代码有效但效率低下(您实际上可以看到这一点,只需滚动 ListView 并检查 Logcat 以查看垃圾收集器是否在工作)。下面是一种改进的getView 方法,它将回收视图:

@Override
public View getView(int position, View convertView, ViewGroup parent) 
     LinearLayout view = (LinearLayout) convertView;
     if (view == null) 
          view = (LinearLayout) inflater.inflate(R.layout.record_view_start, parent, false);
     
     TextView tv = (TextView) view.findViewById(R.id.engName);
     tv.setText(getItem(position));
     CheckBox cBox = (CheckBox) view.findViewById(R.id.checkBox1);
     cBox.setTag(Integer.valueOf(position)); // set the tag so we can identify the correct row in the listener
     cBox.setChecked(mChecked[position]); // set the status as we stored it        
     cBox.setOnCheckedChangeListener(mListener); // set the listener    
     return view;


OnCheckedChangeListener mListener = new OnCheckedChangeListener() 

     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)    
         mChecked[(Integer)buttonView.getTag()] = isChecked; // get the tag so we know the row and store the status 
     
;

关于您问题中的代码,起初我认为这是错误的,因为您设置行的方式,但我不明白为什么适配器会在您从列表中分离行视图时出现这种行为。此外,我什至测试了代码,它在CheckBoxes 方面运行良好(但内存处理非常差)。也许您正在做其他事情导致适配器无法工作?

【讨论】:

如何为带有子复选框的 EcxpandableListView 执行此操作? @RicNjesh 您需要使用getChildView() 方法,只要需要子行就会调用该方法。 这种情况下mcheck是什么,如何声明? @MuneemHabib 这是一个布尔数组,与ListView 中的项目数一样大。在适配器的构造函数中声明。 @Luksprog 谢谢先生,它帮助了我很多。我已经解决了我的问题【参考方案2】:

首先让我说你放弃了使用适配器的主要好处之一:可重用视图。对每个创建的View 进行硬引用具有达到内存上限的高风险。您应该在 convertView 为非空时重用它,并在 convertView 为空时创建您的视图。有许多教程向您展示如何做到这一点。

适配器中使用的视图通常具有由父级View 附加到它们的OnClickListener,因此您可以在ListView 上设置OnItemClickListener。这将取代单个视图上的任何触摸侦听器。尝试在 XML 中的 CheckBox 上设置 android:clickable="true"

【讨论】:

感谢您对 convertView 的提醒。我一直想知道这个参数是干什么用的。我想我可能对原始问题有解决方案,但我仍在测试。我很快就会再发帖。【参考方案3】:

这可能不是最优雅或最有效的解决方案,但它适用于我的情况。出于某种原因,尝试从视图数组或使用 convertView 重用视图会使每件事都变得摇摆不定,并且 CheckBoxes 无法响应。

唯一有效的方法是在每次调用 getView() 时创建一个新视图。

    public View getView(final int position, View convertView, ViewGroup parent) 
        LinearLayout view;
        view=(LinearLayout)inflater.inflate(R.layout.record_view_start,null);


        TextView tv=(TextView)view.findViewById(R.id.engName);
        tv.setText(englishNames[position]);

        CheckBox cBox=(CheckBox)view.findViewById(R.id.checkBox1);
        cBox.setChecked(checked[position]);
        cBox.setOnCheckedChangeListener(new OnCheckedChangeListener()

            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
                checked[position]=isChecked;
            
        );
        return view;
    

我调用单独定义的 onCheckedChangedListener,然后通过 id 识别哪个 CheckBox,而不是为每个 CheckBox 设置一个新的侦听器,这一事实也阻碍了找到该解决方案。

到目前为止,我还没有将其标记为正确答案,因为我希望其他人可能对每次都相当浪费地重建视图有一些意见。

【讨论】:

在 *** 上有很多关于 ListView 和自定义适配器的问题,该适配器使用带有 CheckBox 的行,您应该更多地查看这些解决方案。您的代码有效,但效率非常低,每次滚动 ListView 时,您都会创建新视图。 我完全同意 Luksprog 但经过多次尝试,使用适用于其他人的解决方案,我找不到除此之外的解决方案。希望垃圾收集能够跟上浪费的视图,并且不会导致内存问题,并且旧手机不会减慢太多。在我的应用程序中,在大多数情况下,列表视图不会有太多条目,因此应将滚动保持在最低限度。 您的代码现在可以工作的原因是因为您每次都对行进行膨胀,并且避免了 OnCheckedChangeListener 弄乱了 CheckBox 状态。您永远不应忽略 getView 方法中的视图回收。这是您改进的getView 方法,它应该做您想做的事情,但也会回收视图。 gist.github.com/3917222 优秀。这完全符合预期。您的代码直接删除,仅从“已检查”数组中删除了“m”。 (这是我在 java 中的第一个项目,我不知道这些约定。)我现在有两个问题要问。 1.您能否将其重新发布为答案,以便我可以将其设置为已接受? 2.您能否准确解释为什么我的代码在原始帖子中没有这样做时有效。我觉得答案可能会阐明我正在努力解决的其他一些概念。

以上是关于具有包含 CheckBoxes 的自定义适配器的 Listview的主要内容,如果未能解决你的问题,请参考以下文章

具有多个子项目的项目列表的自定义适配器?

从自定义适配器获取片段中的 UI 元素 ID

片段中的自定义列表适配器

C中动态内存分配器的自定义实现

在 ListView 的自定义适配器中从 URL 加载图像(Android Studio)

具有静态单元的 UItableView 包含 2 个 UItableview,每个都有具有动态高度的自定义单元