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