Thursday, January 30, 2014

Contextual Action Bar with Android's ExpandableListView

I have put together a sample project on Github illustrating that the Contextual Action Bar pattern can be applied to Android's ExpandableListView, if your use case implies that you either want to select groups or the childs and do not need a mixed selection.

The magic is quite simple, it mainly consists in storing the type of selection in the ActionMode.Callback's onItemCheckedStateChanged() method:

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position,
                                          long id, boolean checked) {
      int count = lv.getCheckedItemCount();
      if (count == 1) {
        expandableListSelectionType = ExpandableListView.getPackedPositionType(
            lv.getExpandableListPosition(position));
      }
      mode.setTitle(String.valueOf(count));
      configureMenu(mode.getMenu(), count);
    }

Then you need to implement onChildClick() and onGroupClick() listeners that check the list items once action mode is active:

    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
      if (mActionMode != null)  {
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
          int flatPosition = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
          parent.setItemChecked(
              flatPosition,
              !parent.isItemChecked(flatPosition));
          return true;
        }
      }
      return false;
    }
    @Override
    public boolean onChildClick(ExpandableListView parent, View v,
        int groupPosition, int childPosition, long id) {
      if (mActionMode != null)  {
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
          int flatPosition = parent.getFlatListPosition(
              ExpandableListView.getPackedPositionForChild(groupPosition,childPosition));
          parent.setItemChecked(
              flatPosition,
              !parent.isItemChecked(flatPosition));
        }
        return true;
      }
      return false;
    }

The sample project also illustrates a technique of defining the context actions as part of four groups, depending on two combined criteria:
  • actions for groups or children
  • bulk actions that can be applied to multiple items, and single item actions

3 comments:

  1. Hello! I'm just trying to do exactly this but I am having a problem. I have the following schema for example (with children expanded):

    - Group 1
    + Child 1
    - Group 2
    + Child 2

    When I click on Group 2, I expect that position in onActionItemClicked will be 1 (Group 1 = 0, Group 2 = 1). But I am getting position = 2! And it is because the Child 1 is expanded. I am trying to collapse everything in onCreateActionMode but it doesn't work. So with everything collapse works fine, but in cases like that it doesn't work. Any help? Thank you.

    ReplyDelete
    Replies
    1. Did you look into the example project at Github? the position you get in onActionItemClicked is the position in the list as it is currently laid out. You call
      long pos = getExpandableListPosition(position) to get the "packed" position.
      and then
      int groupPos = ExpandableListView.getPackedPositionGroup(pos);
      int childPos = ExpandableListView.getPackedPositionChild(pos)
      respectively

      Delete
  2. How to add click to phone call on child list view ?

    ReplyDelete