【问题标题】:Disable state changes in views within ExpandableListView when clicking them单击时禁用 ExpandableListView 中视图的状态更改
【发布时间】:2025-12-27 11:35:14
【问题描述】:

背景

当您为 listView 创建自己的自定义视图时,您可以在内部有一个复选框(或任何其他对其 drawable 具有“state_pressed”的视图),只有当它真的被点击时才会显示为已触摸,而不是单击 listView 行上的任意位置。

listView 的实现方法是在其适配器上使用下一个代码(如图所示here):

public boolean isEnabled(int position) 
  { 
  return false; 
  } 

问题

此解决方案不适用于 ExpandableListView,因为它没有此功能。

不仅如此,我认为任何点击 ExpandableListView 上的项目(即使是那些不展开的项目)都会触发 notifyDataSetChanged(或触发它的布局)。

为什么会发生?

我尝试过的

我尝试过使用 "isChildSelectable" 、"areAllItemsEnabled" 、 "setClickable" 甚至 "duplicateParentState" 。这些解决方案都没有帮助。

问题

在行中设置复选框并只允许它们自己处理触摸效果的最佳方式是什么?

另外,为什么点击项目(即使是没有孩子的项目)会触发整个 ExpandableListView 的刷新?

【问题讨论】:

    标签: android checkbox expandablelistview android-adapter


    【解决方案1】:

    如果我理解正确,您希望在 ExpandableListView 的每个父行中都有一个复选框。我之前做过类似的事情(不是使用复选框,而是使用按钮),我必须实现一个自定义适配器。应该是这样的:

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import android.content.Context;
    import android.content.Intent;
    import android.graphics.Typeface;
    import android.net.Uri;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.ViewGroup;
    import android.widget.BaseExpandableListAdapter;
    import android.widget.CheckBox;
    import android.widget.CompoundButton;
    import android.widget.CompoundButton.OnCheckedChangeListener;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class ExpandableListTest extends BaseExpandableListAdapter {
    private Context context;
    private List<String> _listDataHeader; // header titles
    // child data in format of header title, child title
    private HashMap<String, List<String>> _listDataChild;
    
    public ExpandableListTest(Context context) {
        this.context = context;
    }
    
    @Override
    public Object getChild(int groupPosition, int childPosititon) {
        if (_listDataChild != null) {
            return this._listDataChild.get(
                    this._listDataHeader.get(groupPosition))
                    .get(childPosititon);
        } else {
            return null;
        }
    }
    
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }
    
    @Override
    public View getChildView(int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
    
        final int position = groupPosition;
        final String childText = (String) getChild(groupPosition, childPosition);
    
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.child_layout, null);
        }
    
        TextView txtListChild = (TextView) convertView.findViewById(R.id.text);
    
        txtListChild.setText(childText);
    
        convertView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                // do something
            }
        });
    
        return convertView;
    }
    
    @Override
    public int getChildrenCount(int groupPosition) {
        if (_listDataChild == null) {
            return 0;
        } else {
            return this._listDataChild.get(
                    this._listDataHeader.get(groupPosition)).size();
        }
    }
    
    @Override
    public Object getGroup(int groupPosition) {
        if (_listDataHeader == null) {
            return null;
        } else {
            return this._listDataHeader.get(groupPosition);
        }
    
    }
    
    @Override
    public int getGroupCount() {
        if (_listDataHeader == null) {
            return 1;
        } else {
            return this._listDataHeader.size();
        }
    
    }
    
    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }
    
    @Override
    public View getGroupView(int pos, boolean isExpanded, View view,
            ViewGroup parent) {
    
        if (view == null) {
            LayoutInflater infalInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = infalInflater.inflate(R.layout.layout_with_checkbox, null);
        }
        getGroup(pos);
    
        CheckBox checkBox=(CheckBox)view.findViewById(R.id.checkBox);
        checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
                if(arg1){
                    //the checkBox is checked
                }
            }
        });
    
        return view;
    }
    
    @Override
    public boolean hasStableIds() {
        return false;
    }
    
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return false;
    }
    

    }

    如果您想将复选框放在子行中,只需切换 getGroupView() 和 getChildView() 中使用的代码和布局即可。

    父布局xml:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="10dp"
            android:paddingRight="10dp" >
    
            <CheckBox
                android:id="@+id/checkBox"
                android:layout_width="30dp"
                android:layout_height="wrap_content" />
    
            <TextView
                android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="40dp"
                android:layout_toRightOf="@+id/imageView1"
                android:text="Text goes here"
                android:textSize="18sp" />
        </RelativeLayout>
    
    </LinearLayout>
    

    【讨论】:

    • 复选框只处理自己的触摸事件?
    • 在我的情况下,它们实际上是可检查的 imageViews(但对于任何带有按下状态的可绘制对象的可检查视图都会发生),我在它们上使用 setOnClickListener。问题是,当您触摸行本身时,似乎您也触摸了它们,因此它们获得了“state_pressed”的可绘制效果。
    • 你试过这个代码吗?我刚刚再次测试它,它工作正常。我没有使用“Checkable ImageView”,但我使用复选框、切换按钮以及带有可绘制状态的样式对它进行了测试。他们不响应行上的触摸事件。
    • 好的,我试过你的示例(顺便说一句,你写的是“layout_with_checkbox”而不是“parent_layout.xml”)。它有一个问题:它不允许对所选项目显示任何效果。这意味着如果您单击复选框之外的任何位置,您不会得到任何触摸它的迹象。不仅如此,即使我在您的哈希图中添加了多个项目,它似乎也不允许扩展行以显示它们。就好像除了复选框之外触摸完全被阻止了......