ExpandableListView 中的子项重叠会不自觉地导致复选框在单独的组中签入

Overlap of children in ExpandableListView involuntarily causes checkboxes to check in separate groups

我使用的是导航抽屉,其中包含一个 ExpandableListView。 ExpandableListView 包含带有子项的组,每个子项旁边都有复选框。选中某些检查时,也会检查展开组下方的折叠组内的检查。

谁能解释一下为什么会发生这种情况,我的情况有哪些潜在的解决方法?

我会提供图片和我的代码来更好地解释这个问题。

这是我的群组。

这里是 'Assignments' 展开的(未选择任何内容)。

这里是 'Archived' 展开的(没有选择任何东西)。

所以,现在我检查 'Assignments' 下的 'Subject 4'...

不由自主地,折叠 'Archived' 内部的 'Overdue' 也被选中。

同样,当我自愿在'Archived'下检查'Completed'时...

'Subject 3' 也是在折叠 'Assignments' 中不由自主地选择的。

这是我的代码:

    <?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout2"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_commentary_behind_nav"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|top"
            android:text="Swipe from left, and eventually this \nframe will hold fragments that change depending on which checkboxes are selected" />
    </FrameLayout>
    <!-- The navigation drawer -->
        <ExpandableListView
            android:id="@+id/left_drawer2"
            android:layout_width="250dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:background="@color/white"
            android:choiceMode="multipleChoice"
            android:dividerHeight="0dp"
            />

</android.support.v4.widget.DrawerLayout>

这是我的来源:

 public class MainNavigationActivity extends ActionBarActivity implements OnChildClickListener {

private DrawerLayout drawer;
private ExpandableListView drawerList;
private CheckBox checkBox;
private ActionBarDrawerToggle actionBarDrawerToggle;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_drawer_layout_test);

    setGroupData();
    setChildGroupData();

    initDrawer();
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    getSupportActionBar().setHomeButtonEnabled(true);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.drawer_layout_test, menu);
    return true;
}

private void initDrawer() {
    drawer = (DrawerLayout) findViewById(R.id.drawer_layout2);
    drawerList = (ExpandableListView) findViewById(R.id.left_drawer2);
    drawerList.setAdapter(new NewAdapter(this, groupItem, childItem));
    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
        drawerList.setIndicatorBounds(310, 350);
    } else {
        drawerList.setIndicatorBoundsRelative(310, 350);
    }


    drawerList.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
        @Override
        public boolean onGroupClick(ExpandableListView parent, View v,
                                    int groupPosition, long id){
            Fragment fragment;
            FragmentManager fragmentManager;
            Bundle args;
            switch (groupPosition) {
                case 0:
                    fragment = new AssignmentManagerFragment();
                    fragmentManager = getFragmentManager();
                    args = new Bundle();
                    args.putString(AssignmentManagerFragment.ARG_PARAM1, "" + groupPosition);
                    fragment.setArguments(args);
                    fragmentManager.beginTransaction().replace(R.id.content_frame2, fragment).commit();
                    if (drawerList.isGroupExpanded(groupPosition)) {
                        drawerList.collapseGroup(groupPosition);
                    } else {
                        drawerList.expandGroup(groupPosition, false);
                    }
                    Toast.makeText(getBaseContext(), "Clicked On grooop: " + v.getTag() + "|" + groupPosition,
                            Toast.LENGTH_LONG).show();
                    break;

                case 1:
                    fragment = new AssignmentManagerFragment();
                    fragmentManager = getFragmentManager();
                    args = new Bundle();
                    args.putString(AssignmentManagerFragment.ARG_PARAM1, "" + groupPosition);
                    fragment.setArguments(args);
                    fragmentManager.beginTransaction().replace(R.id.content_frame2, fragment).commit();
                    if (drawerList.isGroupExpanded(groupPosition)) {
                        drawerList.collapseGroup(groupPosition);
                    } else {
                        drawerList.expandGroup(groupPosition, false);
                    }
                    Toast.makeText(getBaseContext(), "Clicked On grooop: " + v.getTag() + "|" + groupPosition,
                            Toast.LENGTH_LONG).show();
                    break;

                case 2:
                    fragment = new SubjectManagerFragment();
                    fragmentManager = getFragmentManager();
                    args = new Bundle();
                    args.putString(SubjectManagerFragment.ARG_PARAM1, "" + groupPosition);
                    fragment.setArguments(args);
                    fragmentManager.beginTransaction().replace(R.id.content_frame2, fragment).commit();
                    Toast.makeText(getBaseContext(), "Clicked On grooop: " + v.getTag() + "|" + groupPosition,
                            Toast.LENGTH_SHORT).show();
                    drawer.closeDrawer(drawerList);
                    break;

                // for now, just SubjectManager, but soon they will lead to Settings then H/Feedback
                case 3:
                    fragment = new SubjectManagerFragment();
                    fragmentManager = getFragmentManager();
                    args = new Bundle();
                    args.putString(SubjectManagerFragment.ARG_PARAM1, "" + groupPosition);
                    fragment.setArguments(args);
                    fragmentManager.beginTransaction().replace(R.id.content_frame2, fragment).commit();
                    if (drawerList.isGroupExpanded(groupPosition)) {
                        drawerList.collapseGroup(groupPosition);
                    } else {
                        drawerList.expandGroup(groupPosition, false);
                    }
                    Toast.makeText(getBaseContext(), "Clicked On grooop: " + v.getTag() + "|" + groupPosition,
                            Toast.LENGTH_LONG).show();
                    break;

                case 4:
                    fragment = new SubjectManagerFragment();
                    fragmentManager = getFragmentManager();
                    args = new Bundle();
                    args.putString(SubjectManagerFragment.ARG_PARAM1, "" + groupPosition);
                    fragment.setArguments(args);
                    fragmentManager.beginTransaction().replace(R.id.content_frame2, fragment).commit();
                    if (drawerList.isGroupExpanded(groupPosition)) {
                        drawerList.collapseGroup(groupPosition);
                    } else {
                        drawerList.expandGroup(groupPosition, false);
                    }
                    Toast.makeText(getBaseContext(), "Clicked On grooop: " + v.getTag() + "|" + groupPosition,
                            Toast.LENGTH_LONG).show();
                    break;
            }
            return true;
        }

    });

    drawerList.setOnChildClickListener(this);


    actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawer, R.string.drawer_open,
            R.string.drawer_close) {
        public void onDrawerClosed(View view) {
            super.onDrawerClosed(view);
            getSupportActionBar().setTitle("open");
            invalidateOptionsMenu();
        }

         // Called when a drawer has settled in a completely open state.

        public void onDrawerOpened(View drawerView) {
            super.onDrawerOpened(drawerView);
            getSupportActionBar().setTitle("close");
            invalidateOptionsMenu();
        }
    };

    drawer.setDrawerListener(actionBarDrawerToggle);
    actionBarDrawerToggle.setDrawerIndicatorEnabled(true);
    actionBarDrawerToggle.syncState();

}

@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    // Sync the toggle state after onRestoreInstanceState has occurred.
    actionBarDrawerToggle.syncState();
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    actionBarDrawerToggle.onConfigurationChanged(newConfig);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Pass the event to ActionBarDrawerToggle, if it returns
    // true, then it has handled the app icon touch event
    if (actionBarDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle your other action bar items...

    return super.onOptionsItemSelected(item);
}

public void setGroupData() {
    groupItem.add("Assignments");
    groupItem.add("Archived");
    groupItem.add("Subjects");
    groupItem.add("Settings");
    groupItem.add("Help / Feedback");

}

ArrayList<String> groupItem = new ArrayList<String>();
ArrayList<Object> childItem = new ArrayList<Object>();

public void setChildGroupData() {
    /**
     * Add Data For Assignments
     */
    ArrayList<String> child = new ArrayList<String>();

    ArrayList<SubjectInfo> sial = new ArrayList<SubjectInfo>();
    List<SubjectInfo> sil = SubjectInfo.listAll(SubjectInfo.class);
    sial.addAll(sil);
    for (int go = 0; go < sial.size(); go++) {
        child.add(sial.get(go).subjectName);
    }
    childItem.add(child);

    /**
     * Add Data For Archived
     */
    child = new ArrayList<String>();
    child.add("Archived");
    child.add("Completed");
    child.add("Overdue");
    childItem.add(child);

    /**
     * Add empty children for Subjects, Settings, and H/Feedback
     */
    child = new ArrayList<String>();
    childItem.add(child);
    childItem.add(child);
    childItem.add(child);
}

@Override
public boolean onChildClick(ExpandableListView parent, View v,
                            int groupPosition, int childPosition, long id) {
    Toast.makeText(this, "Clicked On Child: " + v.getTag() + childPosition + "|" + groupPosition,
            Toast.LENGTH_SHORT).show();
    checkBox = (CheckBox) v.findViewById(R.id.show_child_subject_checkBox);
    checkBox.setChecked(!checkBox.isChecked());
    //Update SugarRecord value for SubjectInfo
    SubjectInfo si = SubjectInfo.findById(SubjectInfo.class, (long) (childPosition+1));
    si.subjectChecked = checkBox.isChecked();
    si.save();
    return true;
}
}

这是我的适配器:

@SuppressWarnings("unchecked")
public class NewAdapter extends BaseExpandableListAdapter {
public ArrayList<String> groupItem, tempChild;
public ArrayList<Object> Childtem = new ArrayList<Object>();
public LayoutInflater minflater;
public Activity activity;
private final Context context;

public NewAdapter(Context context,ArrayList<String> grList, ArrayList<Object> childItem) {
    this.context = context;
    groupItem = grList;
    this.Childtem = childItem;
}

public void setInflater(LayoutInflater mInflater, Activity act) {
    this.minflater = mInflater;
    activity = act;
}

@Override
public Object getChild(int groupPosition, int childPosition) {
    return null;
}

@Override
public long getChildId(int groupPosition, int childPosition) {
    return 0;
}

@Override
public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent)
{
    tempChild = (ArrayList<String>) Childtem.get(groupPosition);
    TextView text = null;

    if (convertView == null)
    {   LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = layoutInflater.inflate(R.layout.drawer_submenu_item,parent,false);
    }
    text = (TextView) convertView.findViewById(R.id.submenu_textView);
    text.setText(tempChild.get(childPosition));

    convertView.setTag(tempChild.get(childPosition));
    return convertView;
}


@Override
public int getChildrenCount(int groupPosition) {
    switch (groupPosition) {
        case 0:
            //Expands with the size of the cereal array
            return ((ArrayList<String>) Childtem.get(groupPosition)).size();
        case 1:
            //Expands with the size of the Archived Cereal Array
            return ((ArrayList<String>) Childtem.get(groupPosition)).size();
        default:
            //List does not expand
            return 0;
    }
}

@Override
public Object getGroup(int groupPosition) {
    return null;
}

@Override
public int getGroupCount() {
    return groupItem.size();
}

@Override
public void onGroupCollapsed(int groupPosition) {
    super.onGroupCollapsed(groupPosition);
}

@Override
public void onGroupExpanded(int groupPosition) {
    super.onGroupExpanded(groupPosition);
}

@Override
public long getGroupId(int groupPosition) {
    return 0;
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)
{
    if (convertView == null)
    {
        LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = layoutInflater.inflate(R.layout.drawer_group_item,parent,false);

    }

    ((TextView) convertView).setText(groupItem.get(groupPosition));
    convertView.setTag(groupItem.get(groupPosition));

    return convertView;
}

@Override
public boolean hasStableIds() {
    return false;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
    return true; //was false, and was why toast wasn't presenting. Now that True, it does.
}

}

以及我的 SubjectInfo 代码(包括已检查的布尔值):

public class SubjectInfo extends SugarRecord<SubjectInfo> {

public String subjectName;
public int subjectGrade;
public boolean subjectArchived;
public boolean subjectChecked;

// still deciding on whether subjects can be archived or not... if they can then all assignments
// should be archived with them (and can be restored the same way an assignment can.)
// otherwise, deleting a subject would mean (deleting/archiving all assignments) > (user option)

public SubjectInfo () {}

public SubjectInfo (String name, int grade, boolean isArchived, boolean isChecked) {
    subjectName = name;
    subjectGrade = grade;
    subjectArchived = isArchived;
    subjectChecked = isChecked;
}

}

这里是我对 drawer_group_item 和 drawer_submenu_item 的布局:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:background="@color/white"
android:text="hello, fix?"
android:minHeight="?android:attr/listPreferredItemHeightLarge"
/>

子菜单...

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<TextView
    android:id="@+id/submenu_textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:gravity="center_vertical"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:paddingLeft="32dp"
    android:paddingRight="16dp"
    android:textAppearance="?android:attr/textAppearanceListItemSmall" />

<CheckBox
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/show_child_subject_checkBox"
    android:checked="false"
    android:paddingRight="20dp"
    android:layout_centerVertical="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:focusable="false"/>

编辑:

现在我可以看到您的代码,我立即注意到三个主要问题:

  • 您没有在 getChildView() 中设置复选框的初始状态。现在要正确设置该复选框,您需要访问存储复选框值的 SubjectInfo。但是,您的适配器甚至无法访问 SubjectInfo object!您剥离了名称并将其发送给适配器。您可以为复选框创建另一个 ArrayList 布尔值,但为什么要复制?您已经在 SubjectInfo 中拥有数据,只需将那些 object 放入适配器中,这样您就可以使用它们的数据来创建 child 视图。

  • 您的 onChildClick() 没有考虑 child 所在的组。因此,如果用户单击 [=92= 下的 child ],它甚至没有 SubjectInfo,您正在使用 child 索引并找到它的 SubjectInfo。换句话说,您的 onChildClick() 将每个 child 视为一个主题 child。您需要检查 groupPosition 的值以确保用户单击了主题 child.

  • 在您的 setChildGroupData() 中,您有以下代码:

    child = new ArrayList<String>();
    childItem.add(child);
    childItem.add(child);
    childItem.add(child);
    

    这意味着每个组都在访问同一个列表。因此,当您将 child 项目放入主题组时,相同的 child 项目也会显示在设置组中。

我能给你的最好建议是开始尝试将 ExpandableListView 更像是一个抽象的分层列表数据结构,而不是 visual/touchable 元素。 TextView 映射到 StringCheckBox 映射到 booleanRadioButtons 映射到您定义的 enum。然后确保获取视图的方法使用此数据,并且视图中有更新数据的回调方法。适配器接口就是要像这样与数据交互,所以如果你能搞定那个数据结构,你就会解决很多问题。


您没有提供 NewAdapter class 的代码,但我敢打赌我知道发生了什么:您可能正在显示带有垃圾视图数据的回收视图。

adapter的视图回收机制classes意味着当你得到一个回收视图时,你必须初始化视图中的everything,因为你没有知道它在被回收之前处于什么状态。

由此得出的结论是,适配器必须提供 当前和 up-to-date 模型,以便适配器视图可以显示正确的数据 在任何给定时间。这对您意味着:

  • 您的适配器数据模型(即 SubjectInfo)必须有一个布尔标志来跟踪复选框的状态。此布尔标志将在 getView() 中用于将初始复选框显示设置为选中或未选中。你也没有 post 你的 SubjectInfo class,但是你的代码没有引用 class 中的任何布尔值,所以我假设你没有那个。

  • 您的复选框需要有一个 OnCheckedChangedListener,它将更新适配器数据模型中相应的布尔标志,并调用 notifyDataSetChanged() 让适配器视图知道它需要刷新。

如果您已经这样做了,那么 post 您的 NewAdapterSubjectInfo 代码,我们可以尝试找出其他可能出错的地方。

P.S。代码行

checkBox.setChecked(!checkBox.isChecked());

可以替换为

checkBox.toggle();