3

在我的应用程序中有一个 ExpandableListView,我需要在其中使用上下文操作菜单对多个组的几个子项进行操作。

在我的研究中,我发现对可扩展列表视图进行多项选择是不可能的,或者很难实现。所以我决定按如下方式实现我的自定义解决方案(我已经发布了下面的代码以进行澄清,它是代码的草稿,它不是最终的,我刚刚实现/硬编码了一些东西,看看它是否工作与否):

  • 我点击一个孩子打开上下文操作菜单,我在孩子的视图上打勾,我改变了那个孩子的背景
  • 每次点击每个孩子时,我都会打开相同的上下文操作菜单
  • 关闭我还没有实现的上下文菜单(我想看看这是否可行)
  • 我不会做从与我提供给适配器的地图相对应的地图中提取子项的操作
  • 我的代码一直有效,直到我单击另一个组来选择另一个孩子
  • 然后,当我打开另一个组时,选定的子项(刻度和背景)移动到该组中的子项(我刚刚打开相应的组以选择某些内容)并且最后选择的项目未选中
  • 我张贴截图澄清
  • 我没有观察到任何行为模式

    我不知道为什么会这样。

适配器:

public class CoordinateExpandableListAdapter extends BaseExpandableListAdapter {

private Context context;
private Map<String, List<String>> coordinateList;
private List<String> groupList;

public CoordinateExpandableListAdapter(Context context, List<String> groupList, Map<String, List<String>> coordinateList) {

    this.context = context;
    this.groupList = groupList;
    this.coordinateList = coordinateList;
}

@Override
public Object getChild(int groupPosition, int childPosition) {

    return coordinateList.get(groupList.get(groupPosition)).get(childPosition);
}

@Override
public long getChildId(int groupPosition, int childPosition) {

    return childPosition;
}

class ChildRowHolder {
    TextView childRowTitle;

    public ChildRowHolder(View view) {
        childRowTitle = (TextView) view.findViewById(R.id.child_item_expandable_list_view_title);
    }
}

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {

    View childRow = convertView;
    ChildRowHolder childRowHolder = null;
    String childRowTitle = (String) getChild(groupPosition, childPosition);

    if (childRow == null) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        childRow = inflater.inflate(R.layout.child_expandable_list_view_item, null);
        childRowHolder = new ChildRowHolder(childRow);
        childRow.setTag(childRowHolder);
    } else {
        childRowHolder = (ChildRowHolder) childRow.getTag();
    }

    childRowHolder.childRowTitle.setText(childRowTitle);
    return childRow;
}

@Override
public int getChildrenCount(int groupPosition) {

    return coordinateList.get(groupList.get(groupPosition)).size();
}

@Override
public Object getGroup(int groupPosition) {

    return groupList.get(groupPosition);
}

@Override
public int getGroupCount() {

    return groupList.size();
}

@Override
public long getGroupId(int groupPosition) {

    return groupPosition;
}

class GroupRowHolder {
    TextView groupRowTitle;

    public GroupRowHolder(View view) {
        groupRowTitle = (TextView) view.findViewById(R.id.group_item_expandable_list_view);
    }
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

    View groupRow = convertView;
    GroupRowHolder groupRowHolder = null;
    String coodinateCategory = (String) getGroup(groupPosition);

    if (groupRow == null) {
        LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        groupRow = inflator.inflate(R.layout.group_expandable_list_view_item, null);
        groupRowHolder = new GroupRowHolder(groupRow);
        groupRow.setTag(groupRowHolder);
    } else {
        groupRowHolder = (GroupRowHolder) groupRow.getTag();
    }

    groupRowHolder.groupRowTitle.setText(coodinateCategory);
    groupRowHolder.groupRowTitle.setTypeface(null, Typeface.BOLD);

    return groupRow;
}

@Override
public boolean hasStableIds() {

    return true;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {

    return true;
}

}

可扩展列表视图的片段:

    expandableListView = (ExpandableListView) getActivity().findViewById(R.id.coordinate_expandable_list_view);
    CoordinateExpandableListAdapter expandableListAdapter = new CoordinateExpandableListAdapter(getActivity(), groupList, coordinateList);
    expandableListView.setAdapter(expandableListAdapter);

    registerForContextMenu(expandableListView);
    expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {

        @Override
        public boolean onChildClick(ExpandableListView parent, View childView, int groupPosition, int childPosition, long id) {

            // Start the CAB using the ActionMode.Callback defined above
            actionMode = getActivity().startActionMode(actionModeCallback);

            ArrayList<Integer> positions = new ArrayList<Integer>();
            positions.add(groupPosition);
            positions.add(childPosition);

            Integer key = -1;
            if ((key = isPositionsAdded(positions)) == null) {
                selectedMap.put(selectedMapKey++, positions);
                ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
                tick.setVisibility(View.VISIBLE);
                childView.setBackgroundColor(getResources().getColor(R.color.backgroung_expandable_list_view_child));
            } else {
                selectedMap.remove(key);
                ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
                tick.setVisibility(View.GONE);
                childView.setBackgroundColor(getResources().getColor(R.color.background_color));
            }

            return true;
        }

    });
    expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {

        @Override
        public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
            // TODO Auto-generated method stub
            return false;
        }
    });
}

private Integer isPositionsAdded(ArrayList<Integer> positions) {

    if (selectedMap.containsValue(positions)) {
        for (Map.Entry<Integer, ArrayList<Integer>> entry : selectedMap.entrySet()) {
            if (entry.getValue().equals(positions)) {
                return entry.getKey();
            }
        }
    }
    return null;
}

private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {// Called when the action mode is created; startActionMode() was called

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.coordinate_list_context_menu, menu);
        return true;
    }

    // Called each time the action mode is shown. Always called after onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false; // Return false if nothing is done
    }


    // Called when the user selects a contextual menu item
    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            case R.id.delete:
                deleteCurrentItems();
                mode.finish(); // Action picked, so close the CAB
                return true;
            default:
                return false;
        }
    }

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        actionMode = null;
    }
};

屏幕截图: - 首先我从组 2 中选择项目 1、2

在此处输入图像描述

  • 第二我打开了第 1 组,选择从所选项目到第 1 组的子项

在此处输入图像描述

最糟糕的是,这种行为并不一致,有时它会按预期工作,但大多数时候它不会,也许它这样做的时候纯粹是巧合

编辑

正如我在下面的评论中提到的,我发布了我的工作自定义代码草稿,供人们在这个问题中寻找答案。它不是最终代码,但它正在工作并提供有关解决方案的想法。

Jay Soyer 关于 3rd 方库的建议看起来相当不错,并在生产中进行了测试。

当我单击一个孩子时,会调用 setOnChildClickListener onClick。对位置进行第一次验证(是否添加到自定义数据结构中),然后我相应地选择/取消选择子项。

expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {

        @Override
        public boolean onChildClick(ExpandableListView parent, View childView, int groupPosition, int childPosition, long id) {

            // check / not check a child
            ArrayList<Object> position = new ArrayList<Object>();
            position.add(groupPosition);
            position.add(childPosition);
            position.add(childView);

            if (expandableListAdapter.isPositionAleadyAdded(position) == null) {
                expandableListAdapter.selectChild(position);
            } else {
                expandableListAdapter.deSelectChild(position);
            }

            // Start the CAB using the ActionMode.Callback
            // defined above
            actionMode = getActivity().startActionMode(actionModeCallback);

            // set title of contextual action mode
            setContextualMenuTitle((ActionMode) actionMode);

            return true;
        }
    });

之后,当我单击一个组以展开/收缩该组时,将调用适配器 getGroupView 和 getChildView 方法。在这些方法中,我再次进行了类似的验证。我将组位置、子位置和视图保存在列表 (ArrayList) 中。

private List<ArrayList<Object>> selectedChildren = new ArrayList<ArrayList<Object>>();


@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {

    View childView = convertView;
    ChildRowHolder childRowHolder = null;
    String childRowTitle = (String) getChild(groupPosition, childPosition);

    if (childView == null) {
        LayoutInflater inflater = ((Activity) context).getLayoutInflater();
        childView = inflater.inflate(R.layout.child_expandable_list_view_item, null);
        childRowHolder = new ChildRowHolder(childView);
        childView.setTag(childRowHolder);
    } else {
        childRowHolder = (ChildRowHolder) childView.getTag();
    }

    childRowHolder.childRowTitle.setText(childRowTitle);

    // select / deselect child
    ArrayList<Object> position = new ArrayList<Object>();
    position.add(groupPosition);
    position.add(childPosition);
    position.add(childView);
    if (isPositionAleadyAdded(position) != null) {
        selectChild(position);
    } else {
        deSelectChild(position);
    }

    return childView;
}

/**
 * This method return the key in the selected child map if it exists already. Null otherwise.
 * 
 * @param position the group and child position
 * @return the key of the element of the map if it exists already, null otherwise
 */
public ArrayList<Object> isPositionAleadyAdded(ArrayList<Object> position) {

    for (ArrayList<Object> entry : selectedChildren) {
        if (entry.get(0) == position.get(0)
                && entry.get(1) == position.get(1)) {
            return position;
        }
    }

    return null;
}

/**
 * This method does the selection of a child.
 * 
 * @param childView view of child
 * @param position position of child
 */
public void selectChild(ArrayList<Object> position) {

    if (isPositionAleadyAdded(position) == null) {
        selectedChildren.add(position);
    }
    LinearLayout childView = (LinearLayout) position.get(2);
    ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
    tick.setVisibility(View.VISIBLE);
    childView.setBackgroundColor(context.getResources().getColor(R.color.backgroung_expandable_list_view_child));
}

/**
 * This method un selects a child.
 * 
 * @param childView view of child
 * @param position position of child
 */
public void deSelectChild(ArrayList<Object> position) {

    ArrayList<Object> pos = null;
    int elemNo = -1;
    if ((pos = isPositionAleadyAdded(position)) != null) {
        elemNo = getNoElemInSelectedChildred(pos);
        selectedChildren.remove(elemNo);
    }
    LinearLayout childView = (LinearLayout) position.get(2);
    ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
    tick.setVisibility(View.GONE);
    childView.setBackgroundColor(context.getResources().getColor(R.color.background_color));
}

private int getNoElemInSelectedChildred(ArrayList<Object> position) {

    int index = 0;
    for (ArrayList<Object> entry : selectedChildren) {
        if (entry.get(0) == position.get(0) && entry.get(1) == position.get(1)) {
            return index;
        } else {
            index++;
        }
    }
    return -1;
}

/*
 * This method clears the map of all children.
 */
public void unSelectAllChildren() {

    for (ArrayList<Object> entry : selectedChildren) {
        LinearLayout childView = (LinearLayout) entry.get(2);
        ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
        tick.setVisibility(View.GONE);
        childView.setBackgroundColor(context.getResources().getColor(R.color.background_color));
    }
    selectedChildren.clear();
}

public List<ArrayList<Object>> getSelectedChildren() {
    return selectedChildren;
}

我设法在单击侦听器上捕获上下文菜单刻度按钮以调用 unSelectAllChildren 方法

private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
   ...
    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(final ActionMode mode) {

        int doneButtonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
        LinearLayout layout = (LinearLayout) getActivity().findViewById(doneButtonId);
        ImageView doneview = (ImageView) layout.getChildAt(0);
        doneview.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                expandableListAdapter.unSelectAllChildren();
                setContextualMenuTitle(mode);
            }
        });
    }
};
4

1 回答 1

1

问题围绕着稳定的 ID。不幸的是,Android 提供的文档很少,不仅涉及您为什么需要它们,还涉及如何使用它们。在您的情况下,您正确返回truehasStableIds()但实际上未能返回稳定的 ID。

您覆盖getGroupId()但也需要覆盖getChildId(). 此外,您的getGroupId()实现仍然不正确。Id 不仅在所有位置中必须是唯一的……它还必须是稳定的。这意味着,Dell组的 ID必须相同,无论它存储在什么位置。仅返回 Id 的职位本身不符合条件。

你是正确的,如果你想使用它,你必须手动实现选择模式,是的,这是一个痛苦的过程。虽然您可能已经投入大量时间推出自己的解决方案,但要知道有一个第三方库已经为您提供了解决方案。它在生产代码中使用和测试,非常可靠。还有大量示例代码和演示应用程序供您参考。

该库特别包含一个PatchedExpandabeListAdapter,它修补了使用ExpandableListAdapter... 以包含选择模式的一些问题。您将使用这个人来编写您自己的自定义适配器(关于数据管理)。如果您甚至不想担心这一点,它还提供了Rolodex 适配器,它基本上可以处理除视图生成之外的所有事情。

于 2015-03-31T00:44:39.063 回答