Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import com.android.internal.R;
     20 
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Canvas;
     24 import android.graphics.Rect;
     25 import android.graphics.drawable.ColorDrawable;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.util.AttributeSet;
     30 import android.view.ContextMenu;
     31 import android.view.SoundEffectConstants;
     32 import android.view.View;
     33 import android.view.ContextMenu.ContextMenuInfo;
     34 import android.widget.ExpandableListConnector.PositionMetadata;
     35 
     36 import java.util.ArrayList;
     37 
     38 /**
     39  * A view that shows items in a vertically scrolling two-level list. This
     40  * differs from the {@link ListView} by allowing two levels: groups which can
     41  * individually be expanded to show its children. The items come from the
     42  * {@link ExpandableListAdapter} associated with this view.
     43  * <p>
     44  * Expandable lists are able to show an indicator beside each item to display
     45  * the item's current state (the states are usually one of expanded group,
     46  * collapsed group, child, or last child). Use
     47  * {@link #setChildIndicator(Drawable)} or {@link #setGroupIndicator(Drawable)}
     48  * (or the corresponding XML attributes) to set these indicators (see the docs
     49  * for each method to see additional state that each Drawable can have). The
     50  * default style for an {@link ExpandableListView} provides indicators which
     51  * will be shown next to Views given to the {@link ExpandableListView}. The
     52  * layouts android.R.layout.simple_expandable_list_item_1 and
     53  * android.R.layout.simple_expandable_list_item_2 (which should be used with
     54  * {@link SimpleCursorTreeAdapter}) contain the preferred position information
     55  * for indicators.
     56  * <p>
     57  * The context menu information set by an {@link ExpandableListView} will be a
     58  * {@link ExpandableListContextMenuInfo} object with
     59  * {@link ExpandableListContextMenuInfo#packedPosition} being a packed position
     60  * that can be used with {@link #getPackedPositionType(long)} and the other
     61  * similar methods.
     62  * <p>
     63  * <em><b>Note:</b></em> You cannot use the value <code>wrap_content</code>
     64  * for the <code>android:layout_height</code> attribute of a
     65  * ExpandableListView in XML if the parent's size is also not strictly specified
     66  * (for example, if the parent were ScrollView you could not specify
     67  * wrap_content since it also can be any length. However, you can use
     68  * wrap_content if the ExpandableListView parent has a specific size, such as
     69  * 100 pixels.
     70  *
     71  * @attr ref android.R.styleable#ExpandableListView_groupIndicator
     72  * @attr ref android.R.styleable#ExpandableListView_indicatorLeft
     73  * @attr ref android.R.styleable#ExpandableListView_indicatorRight
     74  * @attr ref android.R.styleable#ExpandableListView_childIndicator
     75  * @attr ref android.R.styleable#ExpandableListView_childIndicatorLeft
     76  * @attr ref android.R.styleable#ExpandableListView_childIndicatorRight
     77  * @attr ref android.R.styleable#ExpandableListView_childDivider
     78  */
     79 public class ExpandableListView extends ListView {
     80 
     81     /**
     82      * The packed position represents a group.
     83      */
     84     public static final int PACKED_POSITION_TYPE_GROUP = 0;
     85 
     86     /**
     87      * The packed position represents a child.
     88      */
     89     public static final int PACKED_POSITION_TYPE_CHILD = 1;
     90 
     91     /**
     92      * The packed position represents a neither/null/no preference.
     93      */
     94     public static final int PACKED_POSITION_TYPE_NULL = 2;
     95 
     96     /**
     97      * The value for a packed position that represents neither/null/no
     98      * preference. This value is not otherwise possible since a group type
     99      * (first bit 0) should not have a child position filled.
    100      */
    101     public static final long PACKED_POSITION_VALUE_NULL = 0x00000000FFFFFFFFL;
    102 
    103     /** The mask (in packed position representation) for the child */
    104     private static final long PACKED_POSITION_MASK_CHILD = 0x00000000FFFFFFFFL;
    105 
    106     /** The mask (in packed position representation) for the group */
    107     private static final long PACKED_POSITION_MASK_GROUP = 0x7FFFFFFF00000000L;
    108 
    109     /** The mask (in packed position representation) for the type */
    110     private static final long PACKED_POSITION_MASK_TYPE  = 0x8000000000000000L;
    111 
    112     /** The shift amount (in packed position representation) for the group */
    113     private static final long PACKED_POSITION_SHIFT_GROUP = 32;
    114 
    115     /** The shift amount (in packed position representation) for the type */
    116     private static final long PACKED_POSITION_SHIFT_TYPE  = 63;
    117 
    118     /** The mask (in integer child position representation) for the child */
    119     private static final long PACKED_POSITION_INT_MASK_CHILD = 0xFFFFFFFF;
    120 
    121     /** The mask (in integer group position representation) for the group */
    122     private static final long PACKED_POSITION_INT_MASK_GROUP = 0x7FFFFFFF;
    123 
    124     /** Serves as the glue/translator between a ListView and an ExpandableListView */
    125     private ExpandableListConnector mConnector;
    126 
    127     /** Gives us Views through group+child positions */
    128     private ExpandableListAdapter mAdapter;
    129 
    130     /** Left bound for drawing the indicator. */
    131     private int mIndicatorLeft;
    132 
    133     /** Right bound for drawing the indicator. */
    134     private int mIndicatorRight;
    135 
    136     /**
    137      * Left bound for drawing the indicator of a child. Value of
    138      * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorLeft.
    139      */
    140     private int mChildIndicatorLeft;
    141 
    142     /**
    143      * Right bound for drawing the indicator of a child. Value of
    144      * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorRight.
    145      */
    146     private int mChildIndicatorRight;
    147 
    148     /**
    149      * Denotes when a child indicator should inherit this bound from the generic
    150      * indicator bounds
    151      */
    152     public static final int CHILD_INDICATOR_INHERIT = -1;
    153 
    154     /** The indicator drawn next to a group. */
    155     private Drawable mGroupIndicator;
    156 
    157     /** The indicator drawn next to a child. */
    158     private Drawable mChildIndicator;
    159 
    160     private static final int[] EMPTY_STATE_SET = {};
    161 
    162     /** State indicating the group is expanded. */
    163     private static final int[] GROUP_EXPANDED_STATE_SET =
    164             {R.attr.state_expanded};
    165 
    166     /** State indicating the group is empty (has no children). */
    167     private static final int[] GROUP_EMPTY_STATE_SET =
    168             {R.attr.state_empty};
    169 
    170     /** State indicating the group is expanded and empty (has no children). */
    171     private static final int[] GROUP_EXPANDED_EMPTY_STATE_SET =
    172             {R.attr.state_expanded, R.attr.state_empty};
    173 
    174     /** States for the group where the 0th bit is expanded and 1st bit is empty. */
    175     private static final int[][] GROUP_STATE_SETS = {
    176          EMPTY_STATE_SET, // 00
    177          GROUP_EXPANDED_STATE_SET, // 01
    178          GROUP_EMPTY_STATE_SET, // 10
    179          GROUP_EXPANDED_EMPTY_STATE_SET // 11
    180     };
    181 
    182     /** State indicating the child is the last within its group. */
    183     private static final int[] CHILD_LAST_STATE_SET =
    184             {R.attr.state_last};
    185 
    186     /** Drawable to be used as a divider when it is adjacent to any children */
    187     private Drawable mChildDivider;
    188     private boolean mClipChildDivider;
    189 
    190     // Bounds of the indicator to be drawn
    191     private final Rect mIndicatorRect = new Rect();
    192 
    193     public ExpandableListView(Context context) {
    194         this(context, null);
    195     }
    196 
    197     public ExpandableListView(Context context, AttributeSet attrs) {
    198         this(context, attrs, com.android.internal.R.attr.expandableListViewStyle);
    199     }
    200 
    201     public ExpandableListView(Context context, AttributeSet attrs, int defStyle) {
    202         super(context, attrs, defStyle);
    203 
    204         TypedArray a =
    205             context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ExpandableListView, defStyle,
    206                     0);
    207 
    208         mGroupIndicator = a
    209                 .getDrawable(com.android.internal.R.styleable.ExpandableListView_groupIndicator);
    210         mChildIndicator = a
    211                 .getDrawable(com.android.internal.R.styleable.ExpandableListView_childIndicator);
    212         mIndicatorLeft = a
    213                 .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
    214         mIndicatorRight = a
    215                 .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
    216         mChildIndicatorLeft = a.getDimensionPixelSize(
    217                 com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft, CHILD_INDICATOR_INHERIT);
    218         mChildIndicatorRight = a.getDimensionPixelSize(
    219                 com.android.internal.R.styleable.ExpandableListView_childIndicatorRight, CHILD_INDICATOR_INHERIT);
    220         mChildDivider = a.getDrawable(com.android.internal.R.styleable.ExpandableListView_childDivider);
    221 
    222         a.recycle();
    223     }
    224 
    225 
    226     @Override
    227     protected void dispatchDraw(Canvas canvas) {
    228         // Draw children, etc.
    229         super.dispatchDraw(canvas);
    230 
    231         // If we have any indicators to draw, we do it here
    232         if ((mChildIndicator == null) && (mGroupIndicator == null)) {
    233             return;
    234         }
    235 
    236         int saveCount = 0;
    237         final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    238         if (clipToPadding) {
    239             saveCount = canvas.save();
    240             final int scrollX = mScrollX;
    241             final int scrollY = mScrollY;
    242             canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
    243                     scrollX + mRight - mLeft - mPaddingRight,
    244                     scrollY + mBottom - mTop - mPaddingBottom);
    245         }
    246 
    247         final int headerViewsCount = getHeaderViewsCount();
    248 
    249         final int lastChildFlPos = mItemCount - getFooterViewsCount() - headerViewsCount - 1;
    250 
    251         final int myB = mBottom;
    252 
    253         PositionMetadata pos;
    254         View item;
    255         Drawable indicator;
    256         int t, b;
    257 
    258         // Start at a value that is neither child nor group
    259         int lastItemType = ~(ExpandableListPosition.CHILD | ExpandableListPosition.GROUP);
    260 
    261         final Rect indicatorRect = mIndicatorRect;
    262 
    263         // The "child" mentioned in the following two lines is this
    264         // View's child, not referring to an expandable list's
    265         // notion of a child (as opposed to a group)
    266         final int childCount = getChildCount();
    267         for (int i = 0, childFlPos = mFirstPosition - headerViewsCount; i < childCount;
    268              i++, childFlPos++) {
    269 
    270             if (childFlPos < 0) {
    271                 // This child is header
    272                 continue;
    273             } else if (childFlPos > lastChildFlPos) {
    274                 // This child is footer, so are all subsequent children
    275                 break;
    276             }
    277 
    278             item = getChildAt(i);
    279             t = item.getTop();
    280             b = item.getBottom();
    281 
    282             // This item isn't on the screen
    283             if ((b < 0) || (t > myB)) continue;
    284 
    285             // Get more expandable list-related info for this item
    286             pos = mConnector.getUnflattenedPos(childFlPos);
    287 
    288             // If this item type and the previous item type are different, then we need to change
    289             // the left & right bounds
    290             if (pos.position.type != lastItemType) {
    291                 if (pos.position.type == ExpandableListPosition.CHILD) {
    292                     indicatorRect.left = (mChildIndicatorLeft == CHILD_INDICATOR_INHERIT) ?
    293                             mIndicatorLeft : mChildIndicatorLeft;
    294                     indicatorRect.right = (mChildIndicatorRight == CHILD_INDICATOR_INHERIT) ?
    295                             mIndicatorRight : mChildIndicatorRight;
    296                 } else {
    297                     indicatorRect.left = mIndicatorLeft;
    298                     indicatorRect.right = mIndicatorRight;
    299                 }
    300 
    301                 lastItemType = pos.position.type;
    302             }
    303 
    304             if (indicatorRect.left != indicatorRect.right) {
    305                 // Use item's full height + the divider height
    306                 if (mStackFromBottom) {
    307                     // See ListView#dispatchDraw
    308                     indicatorRect.top = t;// - mDividerHeight;
    309                     indicatorRect.bottom = b;
    310                 } else {
    311                     indicatorRect.top = t;
    312                     indicatorRect.bottom = b;// + mDividerHeight;
    313                 }
    314 
    315                 // Get the indicator (with its state set to the item's state)
    316                 indicator = getIndicator(pos);
    317                 if (indicator != null) {
    318                     // Draw the indicator
    319                     indicator.setBounds(indicatorRect);
    320                     indicator.draw(canvas);
    321                 }
    322             }
    323 
    324             pos.recycle();
    325         }
    326 
    327         if (clipToPadding) {
    328             canvas.restoreToCount(saveCount);
    329         }
    330     }
    331 
    332     /**
    333      * Gets the indicator for the item at the given position. If the indicator
    334      * is stateful, the state will be given to the indicator.
    335      *
    336      * @param pos The flat list position of the item whose indicator
    337      *            should be returned.
    338      * @return The indicator in the proper state.
    339      */
    340     private Drawable getIndicator(PositionMetadata pos) {
    341         Drawable indicator;
    342 
    343         if (pos.position.type == ExpandableListPosition.GROUP) {
    344             indicator = mGroupIndicator;
    345 
    346             if (indicator != null && indicator.isStateful()) {
    347                 // Empty check based on availability of data.  If the groupMetadata isn't null,
    348                 // we do a check on it. Otherwise, the group is collapsed so we consider it
    349                 // empty for performance reasons.
    350                 boolean isEmpty = (pos.groupMetadata == null) ||
    351                         (pos.groupMetadata.lastChildFlPos == pos.groupMetadata.flPos);
    352 
    353                 final int stateSetIndex =
    354                     (pos.isExpanded() ? 1 : 0) | // Expanded?
    355                     (isEmpty ? 2 : 0); // Empty?
    356                 indicator.setState(GROUP_STATE_SETS[stateSetIndex]);
    357             }
    358         } else {
    359             indicator = mChildIndicator;
    360 
    361             if (indicator != null && indicator.isStateful()) {
    362                 // No need for a state sets array for the child since it only has two states
    363                 final int stateSet[] = pos.position.flatListPos == pos.groupMetadata.lastChildFlPos
    364                         ? CHILD_LAST_STATE_SET
    365                         : EMPTY_STATE_SET;
    366                 indicator.setState(stateSet);
    367             }
    368         }
    369 
    370         return indicator;
    371     }
    372 
    373     /**
    374      * Sets the drawable that will be drawn adjacent to every child in the list. This will
    375      * be drawn using the same height as the normal divider ({@link #setDivider(Drawable)}) or
    376      * if it does not have an intrinsic height, the height set by {@link #setDividerHeight(int)}.
    377      *
    378      * @param childDivider The drawable to use.
    379      */
    380     public void setChildDivider(Drawable childDivider) {
    381         mChildDivider = childDivider;
    382         mClipChildDivider = childDivider != null && childDivider instanceof ColorDrawable;
    383     }
    384 
    385     @Override
    386     void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
    387         int flatListPosition = childIndex + mFirstPosition;
    388 
    389         // Only proceed as possible child if the divider isn't above all items (if it is above
    390         // all items, then the item below it has to be a group)
    391         if (flatListPosition >= 0) {
    392             final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
    393             PositionMetadata pos = mConnector.getUnflattenedPos(adjustedPosition);
    394             // If this item is a child, or it is a non-empty group that is expanded
    395             if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
    396                     pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
    397                 // These are the cases where we draw the child divider
    398                 final Drawable divider = mChildDivider;
    399                 final boolean clip = mClipChildDivider;
    400                 if (!clip) {
    401                     divider.setBounds(bounds);
    402                 } else {
    403                     canvas.save();
    404                     canvas.clipRect(bounds);
    405                 }
    406                 divider.draw(canvas);
    407                 if (clip) {
    408                     canvas.restore();
    409                 }
    410                 pos.recycle();
    411                 return;
    412             }
    413             pos.recycle();
    414         }
    415 
    416         // Otherwise draw the default divider
    417         super.drawDivider(canvas, bounds, flatListPosition);
    418     }
    419 
    420     /**
    421      * This overloaded method should not be used, instead use
    422      * {@link #setAdapter(ExpandableListAdapter)}.
    423      * <p>
    424      * {@inheritDoc}
    425      */
    426     @Override
    427     public void setAdapter(ListAdapter adapter) {
    428         throw new RuntimeException(
    429                 "For ExpandableListView, use setAdapter(ExpandableListAdapter) instead of " +
    430                 "setAdapter(ListAdapter)");
    431     }
    432 
    433     /**
    434      * This method should not be used, use {@link #getExpandableListAdapter()}.
    435      */
    436     @Override
    437     public ListAdapter getAdapter() {
    438         /*
    439          * The developer should never really call this method on an
    440          * ExpandableListView, so it would be nice to throw a RuntimeException,
    441          * but AdapterView calls this
    442          */
    443         return super.getAdapter();
    444     }
    445 
    446     /**
    447      * Register a callback to be invoked when an item has been clicked and the
    448      * caller prefers to receive a ListView-style position instead of a group
    449      * and/or child position. In most cases, the caller should use
    450      * {@link #setOnGroupClickListener} and/or {@link #setOnChildClickListener}.
    451      * <p />
    452      * {@inheritDoc}
    453      */
    454     @Override
    455     public void setOnItemClickListener(OnItemClickListener l) {
    456         super.setOnItemClickListener(l);
    457     }
    458 
    459     /**
    460      * Sets the adapter that provides data to this view.
    461      * @param adapter The adapter that provides data to this view.
    462      */
    463     public void setAdapter(ExpandableListAdapter adapter) {
    464         // Set member variable
    465         mAdapter = adapter;
    466 
    467         if (adapter != null) {
    468             // Create the connector
    469             mConnector = new ExpandableListConnector(adapter);
    470         } else {
    471             mConnector = null;
    472         }
    473 
    474         // Link the ListView (superclass) to the expandable list data through the connector
    475         super.setAdapter(mConnector);
    476     }
    477 
    478     /**
    479      * Gets the adapter that provides data to this view.
    480      * @return The adapter that provides data to this view.
    481      */
    482     public ExpandableListAdapter getExpandableListAdapter() {
    483         return mAdapter;
    484     }
    485 
    486     /**
    487      * @param position An absolute (including header and footer) flat list position.
    488      * @return true if the position corresponds to a header or a footer item.
    489      */
    490     private boolean isHeaderOrFooterPosition(int position) {
    491         final int footerViewsStart = mItemCount - getFooterViewsCount();
    492         return (position < getHeaderViewsCount() || position >= footerViewsStart);
    493     }
    494 
    495     /**
    496      * Converts an absolute item flat position into a group/child flat position, shifting according
    497      * to the number of header items.
    498      *
    499      * @param flatListPosition The absolute flat position
    500      * @return A group/child flat position as expected by the connector.
    501      */
    502     private int getFlatPositionForConnector(int flatListPosition) {
    503         return flatListPosition - getHeaderViewsCount();
    504     }
    505 
    506     /**
    507      * Converts a group/child flat position into an absolute flat position, that takes into account
    508      * the possible headers.
    509      *
    510      * @param flatListPosition The child/group flat position
    511      * @return An absolute flat position.
    512      */
    513     private int getAbsoluteFlatPosition(int flatListPosition) {
    514         return flatListPosition + getHeaderViewsCount();
    515     }
    516 
    517     @Override
    518     public boolean performItemClick(View v, int position, long id) {
    519         // Ignore clicks in header/footers
    520         if (isHeaderOrFooterPosition(position)) {
    521             // Clicked on a header/footer, so ignore pass it on to super
    522             return super.performItemClick(v, position, id);
    523         }
    524 
    525         // Internally handle the item click
    526         final int adjustedPosition = getFlatPositionForConnector(position);
    527         return handleItemClick(v, adjustedPosition, id);
    528     }
    529 
    530     /**
    531      * This will either expand/collapse groups (if a group was clicked) or pass
    532      * on the click to the proper child (if a child was clicked)
    533      *
    534      * @param position The flat list position. This has already been factored to
    535      *            remove the header/footer.
    536      * @param id The ListAdapter ID, not the group or child ID.
    537      */
    538     boolean handleItemClick(View v, int position, long id) {
    539         final PositionMetadata posMetadata = mConnector.getUnflattenedPos(position);
    540 
    541         id = getChildOrGroupId(posMetadata.position);
    542 
    543         boolean returnValue;
    544         if (posMetadata.position.type == ExpandableListPosition.GROUP) {
    545             /* It's a group, so handle collapsing/expanding */
    546 
    547             /* It's a group click, so pass on event */
    548             if (mOnGroupClickListener != null) {
    549                 if (mOnGroupClickListener.onGroupClick(this, v,
    550                         posMetadata.position.groupPos, id)) {
    551                     posMetadata.recycle();
    552                     return true;
    553                 }
    554             }
    555 
    556             if (posMetadata.isExpanded()) {
    557                 /* Collapse it */
    558                 mConnector.collapseGroup(posMetadata);
    559 
    560                 playSoundEffect(SoundEffectConstants.CLICK);
    561 
    562                 if (mOnGroupCollapseListener != null) {
    563                     mOnGroupCollapseListener.onGroupCollapse(posMetadata.position.groupPos);
    564                 }
    565             } else {
    566                 /* Expand it */
    567                 mConnector.expandGroup(posMetadata);
    568 
    569                 playSoundEffect(SoundEffectConstants.CLICK);
    570 
    571                 if (mOnGroupExpandListener != null) {
    572                     mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
    573                 }
    574 
    575                 final int groupPos = posMetadata.position.groupPos;
    576                 final int groupFlatPos = posMetadata.position.flatListPos;
    577 
    578                 final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
    579                 smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
    580                         shiftedGroupPosition);
    581             }
    582 
    583             returnValue = true;
    584         } else {
    585             /* It's a child, so pass on event */
    586             if (mOnChildClickListener != null) {
    587                 playSoundEffect(SoundEffectConstants.CLICK);
    588                 return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
    589                         posMetadata.position.childPos, id);
    590             }
    591 
    592             returnValue = false;
    593         }
    594 
    595         posMetadata.recycle();
    596 
    597         return returnValue;
    598     }
    599 
    600     /**
    601      * Expand a group in the grouped list view
    602      *
    603      * @param groupPos the group to be expanded
    604      * @return True if the group was expanded, false otherwise (if the group
    605      *         was already expanded, this will return false)
    606      */
    607     public boolean expandGroup(int groupPos) {
    608         boolean retValue = mConnector.expandGroup(groupPos);
    609 
    610         if (mOnGroupExpandListener != null) {
    611             mOnGroupExpandListener.onGroupExpand(groupPos);
    612         }
    613 
    614         return retValue;
    615     }
    616 
    617     /**
    618      * Collapse a group in the grouped list view
    619      *
    620      * @param groupPos position of the group to collapse
    621      * @return True if the group was collapsed, false otherwise (if the group
    622      *         was already collapsed, this will return false)
    623      */
    624     public boolean collapseGroup(int groupPos) {
    625         boolean retValue = mConnector.collapseGroup(groupPos);
    626 
    627         if (mOnGroupCollapseListener != null) {
    628             mOnGroupCollapseListener.onGroupCollapse(groupPos);
    629         }
    630 
    631         return retValue;
    632     }
    633 
    634     /** Used for being notified when a group is collapsed */
    635     public interface OnGroupCollapseListener {
    636         /**
    637          * Callback method to be invoked when a group in this expandable list has
    638          * been collapsed.
    639          *
    640          * @param groupPosition The group position that was collapsed
    641          */
    642         void onGroupCollapse(int groupPosition);
    643     }
    644 
    645     private OnGroupCollapseListener mOnGroupCollapseListener;
    646 
    647     public void setOnGroupCollapseListener(
    648             OnGroupCollapseListener onGroupCollapseListener) {
    649         mOnGroupCollapseListener = onGroupCollapseListener;
    650     }
    651 
    652     /** Used for being notified when a group is expanded */
    653     public interface OnGroupExpandListener {
    654         /**
    655          * Callback method to be invoked when a group in this expandable list has
    656          * been expanded.
    657          *
    658          * @param groupPosition The group position that was expanded
    659          */
    660         void onGroupExpand(int groupPosition);
    661     }
    662 
    663     private OnGroupExpandListener mOnGroupExpandListener;
    664 
    665     public void setOnGroupExpandListener(
    666             OnGroupExpandListener onGroupExpandListener) {
    667         mOnGroupExpandListener = onGroupExpandListener;
    668     }
    669 
    670     /**
    671      * Interface definition for a callback to be invoked when a group in this
    672      * expandable list has been clicked.
    673      */
    674     public interface OnGroupClickListener {
    675         /**
    676          * Callback method to be invoked when a group in this expandable list has
    677          * been clicked.
    678          *
    679          * @param parent The ExpandableListConnector where the click happened
    680          * @param v The view within the expandable list/ListView that was clicked
    681          * @param groupPosition The group position that was clicked
    682          * @param id The row id of the group that was clicked
    683          * @return True if the click was handled
    684          */
    685         boolean onGroupClick(ExpandableListView parent, View v, int groupPosition,
    686                 long id);
    687     }
    688 
    689     private OnGroupClickListener mOnGroupClickListener;
    690 
    691     public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener) {
    692         mOnGroupClickListener = onGroupClickListener;
    693     }
    694 
    695     /**
    696      * Interface definition for a callback to be invoked when a child in this
    697      * expandable list has been clicked.
    698      */
    699     public interface OnChildClickListener {
    700         /**
    701          * Callback method to be invoked when a child in this expandable list has
    702          * been clicked.
    703          *
    704          * @param parent The ExpandableListView where the click happened
    705          * @param v The view within the expandable list/ListView that was clicked
    706          * @param groupPosition The group position that contains the child that
    707          *        was clicked
    708          * @param childPosition The child position within the group
    709          * @param id The row id of the child that was clicked
    710          * @return True if the click was handled
    711          */
    712         boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
    713                 int childPosition, long id);
    714     }
    715 
    716     private OnChildClickListener mOnChildClickListener;
    717 
    718     public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
    719         mOnChildClickListener = onChildClickListener;
    720     }
    721 
    722     /**
    723      * Converts a flat list position (the raw position of an item (child or group)
    724      * in the list) to an group and/or child position (represented in a
    725      * packed position). This is useful in situations where the caller needs to
    726      * use the underlying {@link ListView}'s methods. Use
    727      * {@link ExpandableListView#getPackedPositionType} ,
    728      * {@link ExpandableListView#getPackedPositionChild},
    729      * {@link ExpandableListView#getPackedPositionGroup} to unpack.
    730      *
    731      * @param flatListPosition The flat list position to be converted.
    732      * @return The group and/or child position for the given flat list position
    733      *         in packed position representation. #PACKED_POSITION_VALUE_NULL if
    734      *         the position corresponds to a header or a footer item.
    735      */
    736     public long getExpandableListPosition(int flatListPosition) {
    737         if (isHeaderOrFooterPosition(flatListPosition)) {
    738             return PACKED_POSITION_VALUE_NULL;
    739         }
    740 
    741         final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
    742         PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
    743         long packedPos = pm.position.getPackedPosition();
    744         pm.recycle();
    745         return packedPos;
    746     }
    747 
    748     /**
    749      * Converts a group and/or child position to a flat list position. This is
    750      * useful in situations where the caller needs to use the underlying
    751      * {@link ListView}'s methods.
    752      *
    753      * @param packedPosition The group and/or child positions to be converted in
    754      *            packed position representation. Use
    755      *            {@link #getPackedPositionForChild(int, int)} or
    756      *            {@link #getPackedPositionForGroup(int)}.
    757      * @return The flat list position for the given child or group.
    758      */
    759     public int getFlatListPosition(long packedPosition) {
    760         PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition
    761                 .obtainPosition(packedPosition));
    762         final int flatListPosition = pm.position.flatListPos;
    763         pm.recycle();
    764         return getAbsoluteFlatPosition(flatListPosition);
    765     }
    766 
    767     /**
    768      * Gets the position of the currently selected group or child (along with
    769      * its type). Can return {@link #PACKED_POSITION_VALUE_NULL} if no selection.
    770      *
    771      * @return A packed position containing the currently selected group or
    772      *         child's position and type. #PACKED_POSITION_VALUE_NULL if no selection
    773      *         or if selection is on a header or a footer item.
    774      */
    775     public long getSelectedPosition() {
    776         final int selectedPos = getSelectedItemPosition();
    777 
    778         // The case where there is no selection (selectedPos == -1) is also handled here.
    779         return getExpandableListPosition(selectedPos);
    780     }
    781 
    782     /**
    783      * Gets the ID of the currently selected group or child. Can return -1 if no
    784      * selection.
    785      *
    786      * @return The ID of the currently selected group or child. -1 if no
    787      *         selection.
    788      */
    789     public long getSelectedId() {
    790         long packedPos = getSelectedPosition();
    791         if (packedPos == PACKED_POSITION_VALUE_NULL) return -1;
    792 
    793         int groupPos = getPackedPositionGroup(packedPos);
    794 
    795         if (getPackedPositionType(packedPos) == PACKED_POSITION_TYPE_GROUP) {
    796             // It's a group
    797             return mAdapter.getGroupId(groupPos);
    798         } else {
    799             // It's a child
    800             return mAdapter.getChildId(groupPos, getPackedPositionChild(packedPos));
    801         }
    802     }
    803 
    804     /**
    805      * Sets the selection to the specified group.
    806      * @param groupPosition The position of the group that should be selected.
    807      */
    808     public void setSelectedGroup(int groupPosition) {
    809         ExpandableListPosition elGroupPos = ExpandableListPosition
    810                 .obtainGroupPosition(groupPosition);
    811         PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
    812         elGroupPos.recycle();
    813         final int absoluteFlatPosition = getAbsoluteFlatPosition(pm.position.flatListPos);
    814         super.setSelection(absoluteFlatPosition);
    815         pm.recycle();
    816     }
    817 
    818     /**
    819      * Sets the selection to the specified child. If the child is in a collapsed
    820      * group, the group will only be expanded and child subsequently selected if
    821      * shouldExpandGroup is set to true, otherwise the method will return false.
    822      *
    823      * @param groupPosition The position of the group that contains the child.
    824      * @param childPosition The position of the child within the group.
    825      * @param shouldExpandGroup Whether the child's group should be expanded if
    826      *            it is collapsed.
    827      * @return Whether the selection was successfully set on the child.
    828      */
    829     public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) {
    830         ExpandableListPosition elChildPos = ExpandableListPosition.obtainChildPosition(
    831                 groupPosition, childPosition);
    832         PositionMetadata flatChildPos = mConnector.getFlattenedPos(elChildPos);
    833 
    834         if (flatChildPos == null) {
    835             // The child's group isn't expanded
    836 
    837             // Shouldn't expand the group, so return false for we didn't set the selection
    838             if (!shouldExpandGroup) return false;
    839 
    840             expandGroup(groupPosition);
    841 
    842             flatChildPos = mConnector.getFlattenedPos(elChildPos);
    843 
    844             // Sanity check
    845             if (flatChildPos == null) {
    846                 throw new IllegalStateException("Could not find child");
    847             }
    848         }
    849 
    850         int absoluteFlatPosition = getAbsoluteFlatPosition(flatChildPos.position.flatListPos);
    851         super.setSelection(absoluteFlatPosition);
    852 
    853         elChildPos.recycle();
    854         flatChildPos.recycle();
    855 
    856         return true;
    857     }
    858 
    859     /**
    860      * Whether the given group is currently expanded.
    861      *
    862      * @param groupPosition The group to check.
    863      * @return Whether the group is currently expanded.
    864      */
    865     public boolean isGroupExpanded(int groupPosition) {
    866         return mConnector.isGroupExpanded(groupPosition);
    867     }
    868 
    869     /**
    870      * Gets the type of a packed position. See
    871      * {@link #getPackedPositionForChild(int, int)}.
    872      *
    873      * @param packedPosition The packed position for which to return the type.
    874      * @return The type of the position contained within the packed position,
    875      *         either {@link #PACKED_POSITION_TYPE_CHILD}, {@link #PACKED_POSITION_TYPE_GROUP}, or
    876      *         {@link #PACKED_POSITION_TYPE_NULL}.
    877      */
    878     public static int getPackedPositionType(long packedPosition) {
    879         if (packedPosition == PACKED_POSITION_VALUE_NULL) {
    880             return PACKED_POSITION_TYPE_NULL;
    881         }
    882 
    883         return (packedPosition & PACKED_POSITION_MASK_TYPE) == PACKED_POSITION_MASK_TYPE
    884                 ? PACKED_POSITION_TYPE_CHILD
    885                 : PACKED_POSITION_TYPE_GROUP;
    886     }
    887 
    888     /**
    889      * Gets the group position from a packed position. See
    890      * {@link #getPackedPositionForChild(int, int)}.
    891      *
    892      * @param packedPosition The packed position from which the group position
    893      *            will be returned.
    894      * @return The group position portion of the packed position. If this does
    895      *         not contain a group, returns -1.
    896      */
    897     public static int getPackedPositionGroup(long packedPosition) {
    898         // Null
    899         if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
    900 
    901         return (int) ((packedPosition & PACKED_POSITION_MASK_GROUP) >> PACKED_POSITION_SHIFT_GROUP);
    902     }
    903 
    904     /**
    905      * Gets the child position from a packed position that is of
    906      * {@link #PACKED_POSITION_TYPE_CHILD} type (use {@link #getPackedPositionType(long)}).
    907      * To get the group that this child belongs to, use
    908      * {@link #getPackedPositionGroup(long)}. See
    909      * {@link #getPackedPositionForChild(int, int)}.
    910      *
    911      * @param packedPosition The packed position from which the child position
    912      *            will be returned.
    913      * @return The child position portion of the packed position. If this does
    914      *         not contain a child, returns -1.
    915      */
    916     public static int getPackedPositionChild(long packedPosition) {
    917         // Null
    918         if (packedPosition == PACKED_POSITION_VALUE_NULL) return -1;
    919 
    920         // Group since a group type clears this bit
    921         if ((packedPosition & PACKED_POSITION_MASK_TYPE) != PACKED_POSITION_MASK_TYPE) return -1;
    922 
    923         return (int) (packedPosition & PACKED_POSITION_MASK_CHILD);
    924     }
    925 
    926     /**
    927      * Returns the packed position representation of a child's position.
    928      * <p>
    929      * In general, a packed position should be used in
    930      * situations where the position given to/returned from an
    931      * {@link ExpandableListAdapter} or {@link ExpandableListView} method can
    932      * either be a child or group. The two positions are packed into a single
    933      * long which can be unpacked using
    934      * {@link #getPackedPositionChild(long)},
    935      * {@link #getPackedPositionGroup(long)}, and
    936      * {@link #getPackedPositionType(long)}.
    937      *
    938      * @param groupPosition The child's parent group's position.
    939      * @param childPosition The child position within the group.
    940      * @return The packed position representation of the child (and parent group).
    941      */
    942     public static long getPackedPositionForChild(int groupPosition, int childPosition) {
    943         return (((long)PACKED_POSITION_TYPE_CHILD) << PACKED_POSITION_SHIFT_TYPE)
    944                 | ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
    945                         << PACKED_POSITION_SHIFT_GROUP)
    946                 | (childPosition & PACKED_POSITION_INT_MASK_CHILD);
    947     }
    948 
    949     /**
    950      * Returns the packed position representation of a group's position. See
    951      * {@link #getPackedPositionForChild(int, int)}.
    952      *
    953      * @param groupPosition The child's parent group's position.
    954      * @return The packed position representation of the group.
    955      */
    956     public static long getPackedPositionForGroup(int groupPosition) {
    957         // No need to OR a type in because PACKED_POSITION_GROUP == 0
    958         return ((((long)groupPosition) & PACKED_POSITION_INT_MASK_GROUP)
    959                         << PACKED_POSITION_SHIFT_GROUP);
    960     }
    961 
    962     @Override
    963     ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) {
    964         if (isHeaderOrFooterPosition(flatListPosition)) {
    965             // Return normal info for header/footer view context menus
    966             return new AdapterContextMenuInfo(view, flatListPosition, id);
    967         }
    968 
    969         final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
    970         PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
    971         ExpandableListPosition pos = pm.position;
    972         pm.recycle();
    973 
    974         id = getChildOrGroupId(pos);
    975         long packedPosition = pos.getPackedPosition();
    976         pos.recycle();
    977 
    978         return new ExpandableListContextMenuInfo(view, packedPosition, id);
    979     }
    980 
    981     /**
    982      * Gets the ID of the group or child at the given <code>position</code>.
    983      * This is useful since there is no ListAdapter ID -> ExpandableListAdapter
    984      * ID conversion mechanism (in some cases, it isn't possible).
    985      *
    986      * @param position The position of the child or group whose ID should be
    987      *            returned.
    988      */
    989     private long getChildOrGroupId(ExpandableListPosition position) {
    990         if (position.type == ExpandableListPosition.CHILD) {
    991             return mAdapter.getChildId(position.groupPos, position.childPos);
    992         } else {
    993             return mAdapter.getGroupId(position.groupPos);
    994         }
    995     }
    996 
    997     /**
    998      * Sets the indicator to be drawn next to a child.
    999      *
   1000      * @param childIndicator The drawable to be used as an indicator. If the
   1001      *            child is the last child for a group, the state
   1002      *            {@link android.R.attr#state_last} will be set.
   1003      */
   1004     public void setChildIndicator(Drawable childIndicator) {
   1005         mChildIndicator = childIndicator;
   1006     }
   1007 
   1008     /**
   1009      * Sets the drawing bounds for the child indicator. For either, you can
   1010      * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
   1011      * indicator's bounds.
   1012      *
   1013      * @see #setIndicatorBounds(int, int)
   1014      * @param left The left position (relative to the left bounds of this View)
   1015      *            to start drawing the indicator.
   1016      * @param right The right position (relative to the left bounds of this
   1017      *            View) to end the drawing of the indicator.
   1018      */
   1019     public void setChildIndicatorBounds(int left, int right) {
   1020         mChildIndicatorLeft = left;
   1021         mChildIndicatorRight = right;
   1022     }
   1023 
   1024     /**
   1025      * Sets the indicator to be drawn next to a group.
   1026      *
   1027      * @param groupIndicator The drawable to be used as an indicator. If the
   1028      *            group is empty, the state {@link android.R.attr#state_empty} will be
   1029      *            set. If the group is expanded, the state
   1030      *            {@link android.R.attr#state_expanded} will be set.
   1031      */
   1032     public void setGroupIndicator(Drawable groupIndicator) {
   1033         mGroupIndicator = groupIndicator;
   1034     }
   1035 
   1036     /**
   1037      * Sets the drawing bounds for the indicators (at minimum, the group indicator
   1038      * is affected by this; the child indicator is affected by this if the
   1039      * child indicator bounds are set to inherit).
   1040      *
   1041      * @see #setChildIndicatorBounds(int, int)
   1042      * @param left The left position (relative to the left bounds of this View)
   1043      *            to start drawing the indicator.
   1044      * @param right The right position (relative to the left bounds of this
   1045      *            View) to end the drawing of the indicator.
   1046      */
   1047     public void setIndicatorBounds(int left, int right) {
   1048         mIndicatorLeft = left;
   1049         mIndicatorRight = right;
   1050     }
   1051 
   1052     /**
   1053      * Extra menu information specific to an {@link ExpandableListView} provided
   1054      * to the
   1055      * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
   1056      * callback when a context menu is brought up for this AdapterView.
   1057      */
   1058     public static class ExpandableListContextMenuInfo implements ContextMenu.ContextMenuInfo {
   1059 
   1060         public ExpandableListContextMenuInfo(View targetView, long packedPosition, long id) {
   1061             this.targetView = targetView;
   1062             this.packedPosition = packedPosition;
   1063             this.id = id;
   1064         }
   1065 
   1066         /**
   1067          * The view for which the context menu is being displayed. This
   1068          * will be one of the children Views of this {@link ExpandableListView}.
   1069          */
   1070         public View targetView;
   1071 
   1072         /**
   1073          * The packed position in the list represented by the adapter for which
   1074          * the context menu is being displayed. Use the methods
   1075          * {@link ExpandableListView#getPackedPositionType},
   1076          * {@link ExpandableListView#getPackedPositionChild}, and
   1077          * {@link ExpandableListView#getPackedPositionGroup} to unpack this.
   1078          */
   1079         public long packedPosition;
   1080 
   1081         /**
   1082          * The ID of the item (group or child) for which the context menu is
   1083          * being displayed.
   1084          */
   1085         public long id;
   1086     }
   1087 
   1088     static class SavedState extends BaseSavedState {
   1089         ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList;
   1090 
   1091         /**
   1092          * Constructor called from {@link ExpandableListView#onSaveInstanceState()}
   1093          */
   1094         SavedState(
   1095                 Parcelable superState,
   1096                 ArrayList<ExpandableListConnector.GroupMetadata> expandedGroupMetadataList) {
   1097             super(superState);
   1098             this.expandedGroupMetadataList = expandedGroupMetadataList;
   1099         }
   1100 
   1101         /**
   1102          * Constructor called from {@link #CREATOR}
   1103          */
   1104         private SavedState(Parcel in) {
   1105             super(in);
   1106             expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>();
   1107             in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader());
   1108         }
   1109 
   1110         @Override
   1111         public void writeToParcel(Parcel out, int flags) {
   1112             super.writeToParcel(out, flags);
   1113             out.writeList(expandedGroupMetadataList);
   1114         }
   1115 
   1116         public static final Parcelable.Creator<SavedState> CREATOR
   1117                 = new Parcelable.Creator<SavedState>() {
   1118             public SavedState createFromParcel(Parcel in) {
   1119                 return new SavedState(in);
   1120             }
   1121 
   1122             public SavedState[] newArray(int size) {
   1123                 return new SavedState[size];
   1124             }
   1125         };
   1126     }
   1127 
   1128     @Override
   1129     public Parcelable onSaveInstanceState() {
   1130         Parcelable superState = super.onSaveInstanceState();
   1131         return new SavedState(superState,
   1132                 mConnector != null ? mConnector.getExpandedGroupMetadataList() : null);
   1133     }
   1134 
   1135     @Override
   1136     public void onRestoreInstanceState(Parcelable state) {
   1137         if (!(state instanceof SavedState)) {
   1138             super.onRestoreInstanceState(state);
   1139             return;
   1140         }
   1141 
   1142         SavedState ss = (SavedState) state;
   1143         super.onRestoreInstanceState(ss.getSuperState());
   1144 
   1145         if (mConnector != null && ss.expandedGroupMetadataList != null) {
   1146             mConnector.setExpandedGroupMetadataList(ss.expandedGroupMetadataList);
   1147         }
   1148     }
   1149 
   1150 }
   1151