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