• 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 android.content.Context;
20 import android.database.DataSetObserver;
21 import android.os.Parcelable;
22 import android.os.SystemClock;
23 import android.util.AttributeSet;
24 import android.util.SparseArray;
25 import android.view.ContextMenu;
26 import android.view.ContextMenu.ContextMenuInfo;
27 import android.view.SoundEffectConstants;
28 import android.view.View;
29 import android.view.ViewDebug;
30 import android.view.ViewGroup;
31 import android.view.accessibility.AccessibilityEvent;
32 import android.view.accessibility.AccessibilityManager;
33 import android.view.accessibility.AccessibilityNodeInfo;
34 import android.view.accessibility.AccessibilityNodeProvider;
35 
36 /**
37  * An AdapterView is a view whose children are determined by an {@link Adapter}.
38  *
39  * <p>
40  * See {@link ListView}, {@link GridView}, {@link Spinner} and
41  *      {@link Gallery} for commonly used subclasses of AdapterView.
42  *
43  * <div class="special reference">
44  * <h3>Developer Guides</h3>
45  * <p>For more information about using AdapterView, read the
46  * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
47  * developer guide.</p></div>
48  */
49 public abstract class AdapterView<T extends Adapter> extends ViewGroup {
50 
51     /**
52      * The item view type returned by {@link Adapter#getItemViewType(int)} when
53      * the adapter does not want the item's view recycled.
54      */
55     public static final int ITEM_VIEW_TYPE_IGNORE = -1;
56 
57     /**
58      * The item view type returned by {@link Adapter#getItemViewType(int)} when
59      * the item is a header or footer.
60      */
61     public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
62 
63     /**
64      * The position of the first child displayed
65      */
66     @ViewDebug.ExportedProperty(category = "scrolling")
67     int mFirstPosition = 0;
68 
69     /**
70      * The offset in pixels from the top of the AdapterView to the top
71      * of the view to select during the next layout.
72      */
73     int mSpecificTop;
74 
75     /**
76      * Position from which to start looking for mSyncRowId
77      */
78     int mSyncPosition;
79 
80     /**
81      * Row id to look for when data has changed
82      */
83     long mSyncRowId = INVALID_ROW_ID;
84 
85     /**
86      * Height of the view when mSyncPosition and mSyncRowId where set
87      */
88     long mSyncHeight;
89 
90     /**
91      * True if we need to sync to mSyncRowId
92      */
93     boolean mNeedSync = false;
94 
95     /**
96      * Indicates whether to sync based on the selection or position. Possible
97      * values are {@link #SYNC_SELECTED_POSITION} or
98      * {@link #SYNC_FIRST_POSITION}.
99      */
100     int mSyncMode;
101 
102     /**
103      * Our height after the last layout
104      */
105     private int mLayoutHeight;
106 
107     /**
108      * Sync based on the selected child
109      */
110     static final int SYNC_SELECTED_POSITION = 0;
111 
112     /**
113      * Sync based on the first child displayed
114      */
115     static final int SYNC_FIRST_POSITION = 1;
116 
117     /**
118      * Maximum amount of time to spend in {@link #findSyncPosition()}
119      */
120     static final int SYNC_MAX_DURATION_MILLIS = 100;
121 
122     /**
123      * Indicates that this view is currently being laid out.
124      */
125     boolean mInLayout = false;
126 
127     /**
128      * The listener that receives notifications when an item is selected.
129      */
130     OnItemSelectedListener mOnItemSelectedListener;
131 
132     /**
133      * The listener that receives notifications when an item is clicked.
134      */
135     OnItemClickListener mOnItemClickListener;
136 
137     /**
138      * The listener that receives notifications when an item is long clicked.
139      */
140     OnItemLongClickListener mOnItemLongClickListener;
141 
142     /**
143      * True if the data has changed since the last layout
144      */
145     boolean mDataChanged;
146 
147     /**
148      * The position within the adapter's data set of the item to select
149      * during the next layout.
150      */
151     @ViewDebug.ExportedProperty(category = "list")
152     int mNextSelectedPosition = INVALID_POSITION;
153 
154     /**
155      * The item id of the item to select during the next layout.
156      */
157     long mNextSelectedRowId = INVALID_ROW_ID;
158 
159     /**
160      * The position within the adapter's data set of the currently selected item.
161      */
162     @ViewDebug.ExportedProperty(category = "list")
163     int mSelectedPosition = INVALID_POSITION;
164 
165     /**
166      * The item id of the currently selected item.
167      */
168     long mSelectedRowId = INVALID_ROW_ID;
169 
170     /**
171      * View to show if there are no items to show.
172      */
173     private View mEmptyView;
174 
175     /**
176      * The number of items in the current adapter.
177      */
178     @ViewDebug.ExportedProperty(category = "list")
179     int mItemCount;
180 
181     /**
182      * The number of items in the adapter before a data changed event occurred.
183      */
184     int mOldItemCount;
185 
186     /**
187      * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
188      * number of items in the current adapter.
189      */
190     public static final int INVALID_POSITION = -1;
191 
192     /**
193      * Represents an empty or invalid row id
194      */
195     public static final long INVALID_ROW_ID = Long.MIN_VALUE;
196 
197     /**
198      * The last selected position we used when notifying
199      */
200     int mOldSelectedPosition = INVALID_POSITION;
201 
202     /**
203      * The id of the last selected position we used when notifying
204      */
205     long mOldSelectedRowId = INVALID_ROW_ID;
206 
207     /**
208      * Indicates what focusable state is requested when calling setFocusable().
209      * In addition to this, this view has other criteria for actually
210      * determining the focusable state (such as whether its empty or the text
211      * filter is shown).
212      *
213      * @see #setFocusable(boolean)
214      * @see #checkFocus()
215      */
216     private boolean mDesiredFocusableState;
217     private boolean mDesiredFocusableInTouchModeState;
218 
219     private SelectionNotifier mSelectionNotifier;
220     /**
221      * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
222      * This is used to layout the children during a layout pass.
223      */
224     boolean mBlockLayoutRequests = false;
225 
AdapterView(Context context)226     public AdapterView(Context context) {
227         super(context);
228     }
229 
AdapterView(Context context, AttributeSet attrs)230     public AdapterView(Context context, AttributeSet attrs) {
231         super(context, attrs);
232     }
233 
AdapterView(Context context, AttributeSet attrs, int defStyle)234     public AdapterView(Context context, AttributeSet attrs, int defStyle) {
235         super(context, attrs, defStyle);
236 
237         // If not explicitly specified this view is important for accessibility.
238         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
239             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
240         }
241     }
242 
243     /**
244      * Interface definition for a callback to be invoked when an item in this
245      * AdapterView has been clicked.
246      */
247     public interface OnItemClickListener {
248 
249         /**
250          * Callback method to be invoked when an item in this AdapterView has
251          * been clicked.
252          * <p>
253          * Implementers can call getItemAtPosition(position) if they need
254          * to access the data associated with the selected item.
255          *
256          * @param parent The AdapterView where the click happened.
257          * @param view The view within the AdapterView that was clicked (this
258          *            will be a view provided by the adapter)
259          * @param position The position of the view in the adapter.
260          * @param id The row id of the item that was clicked.
261          */
onItemClick(AdapterView<?> parent, View view, int position, long id)262         void onItemClick(AdapterView<?> parent, View view, int position, long id);
263     }
264 
265     /**
266      * Register a callback to be invoked when an item in this AdapterView has
267      * been clicked.
268      *
269      * @param listener The callback that will be invoked.
270      */
setOnItemClickListener(OnItemClickListener listener)271     public void setOnItemClickListener(OnItemClickListener listener) {
272         mOnItemClickListener = listener;
273     }
274 
275     /**
276      * @return The callback to be invoked with an item in this AdapterView has
277      *         been clicked, or null id no callback has been set.
278      */
getOnItemClickListener()279     public final OnItemClickListener getOnItemClickListener() {
280         return mOnItemClickListener;
281     }
282 
283     /**
284      * Call the OnItemClickListener, if it is defined.
285      *
286      * @param view The view within the AdapterView that was clicked.
287      * @param position The position of the view in the adapter.
288      * @param id The row id of the item that was clicked.
289      * @return True if there was an assigned OnItemClickListener that was
290      *         called, false otherwise is returned.
291      */
performItemClick(View view, int position, long id)292     public boolean performItemClick(View view, int position, long id) {
293         if (mOnItemClickListener != null) {
294             playSoundEffect(SoundEffectConstants.CLICK);
295             if (view != null) {
296                 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
297             }
298             mOnItemClickListener.onItemClick(this, view, position, id);
299             return true;
300         }
301 
302         return false;
303     }
304 
305     /**
306      * Interface definition for a callback to be invoked when an item in this
307      * view has been clicked and held.
308      */
309     public interface OnItemLongClickListener {
310         /**
311          * Callback method to be invoked when an item in this view has been
312          * clicked and held.
313          *
314          * Implementers can call getItemAtPosition(position) if they need to access
315          * the data associated with the selected item.
316          *
317          * @param parent The AbsListView where the click happened
318          * @param view The view within the AbsListView that was clicked
319          * @param position The position of the view in the list
320          * @param id The row id of the item that was clicked
321          *
322          * @return true if the callback consumed the long click, false otherwise
323          */
onItemLongClick(AdapterView<?> parent, View view, int position, long id)324         boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
325     }
326 
327 
328     /**
329      * Register a callback to be invoked when an item in this AdapterView has
330      * been clicked and held
331      *
332      * @param listener The callback that will run
333      */
setOnItemLongClickListener(OnItemLongClickListener listener)334     public void setOnItemLongClickListener(OnItemLongClickListener listener) {
335         if (!isLongClickable()) {
336             setLongClickable(true);
337         }
338         mOnItemLongClickListener = listener;
339     }
340 
341     /**
342      * @return The callback to be invoked with an item in this AdapterView has
343      *         been clicked and held, or null id no callback as been set.
344      */
getOnItemLongClickListener()345     public final OnItemLongClickListener getOnItemLongClickListener() {
346         return mOnItemLongClickListener;
347     }
348 
349     /**
350      * Interface definition for a callback to be invoked when
351      * an item in this view has been selected.
352      */
353     public interface OnItemSelectedListener {
354         /**
355          * <p>Callback method to be invoked when an item in this view has been
356          * selected. This callback is invoked only when the newly selected
357          * position is different from the previously selected position or if
358          * there was no selected item.</p>
359          *
360          * Impelmenters can call getItemAtPosition(position) if they need to access the
361          * data associated with the selected item.
362          *
363          * @param parent The AdapterView where the selection happened
364          * @param view The view within the AdapterView that was clicked
365          * @param position The position of the view in the adapter
366          * @param id The row id of the item that is selected
367          */
onItemSelected(AdapterView<?> parent, View view, int position, long id)368         void onItemSelected(AdapterView<?> parent, View view, int position, long id);
369 
370         /**
371          * Callback method to be invoked when the selection disappears from this
372          * view. The selection can disappear for instance when touch is activated
373          * or when the adapter becomes empty.
374          *
375          * @param parent The AdapterView that now contains no selected item.
376          */
onNothingSelected(AdapterView<?> parent)377         void onNothingSelected(AdapterView<?> parent);
378     }
379 
380 
381     /**
382      * Register a callback to be invoked when an item in this AdapterView has
383      * been selected.
384      *
385      * @param listener The callback that will run
386      */
setOnItemSelectedListener(OnItemSelectedListener listener)387     public void setOnItemSelectedListener(OnItemSelectedListener listener) {
388         mOnItemSelectedListener = listener;
389     }
390 
getOnItemSelectedListener()391     public final OnItemSelectedListener getOnItemSelectedListener() {
392         return mOnItemSelectedListener;
393     }
394 
395     /**
396      * Extra menu information provided to the
397      * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
398      * callback when a context menu is brought up for this AdapterView.
399      *
400      */
401     public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
402 
AdapterContextMenuInfo(View targetView, int position, long id)403         public AdapterContextMenuInfo(View targetView, int position, long id) {
404             this.targetView = targetView;
405             this.position = position;
406             this.id = id;
407         }
408 
409         /**
410          * The child view for which the context menu is being displayed. This
411          * will be one of the children of this AdapterView.
412          */
413         public View targetView;
414 
415         /**
416          * The position in the adapter for which the context menu is being
417          * displayed.
418          */
419         public int position;
420 
421         /**
422          * The row id of the item for which the context menu is being displayed.
423          */
424         public long id;
425     }
426 
427     /**
428      * Returns the adapter currently associated with this widget.
429      *
430      * @return The adapter used to provide this view's content.
431      */
getAdapter()432     public abstract T getAdapter();
433 
434     /**
435      * Sets the adapter that provides the data and the views to represent the data
436      * in this widget.
437      *
438      * @param adapter The adapter to use to create this view's content.
439      */
setAdapter(T adapter)440     public abstract void setAdapter(T adapter);
441 
442     /**
443      * This method is not supported and throws an UnsupportedOperationException when called.
444      *
445      * @param child Ignored.
446      *
447      * @throws UnsupportedOperationException Every time this method is invoked.
448      */
449     @Override
addView(View child)450     public void addView(View child) {
451         throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
452     }
453 
454     /**
455      * This method is not supported and throws an UnsupportedOperationException when called.
456      *
457      * @param child Ignored.
458      * @param index Ignored.
459      *
460      * @throws UnsupportedOperationException Every time this method is invoked.
461      */
462     @Override
addView(View child, int index)463     public void addView(View child, int index) {
464         throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
465     }
466 
467     /**
468      * This method is not supported and throws an UnsupportedOperationException when called.
469      *
470      * @param child Ignored.
471      * @param params Ignored.
472      *
473      * @throws UnsupportedOperationException Every time this method is invoked.
474      */
475     @Override
addView(View child, LayoutParams params)476     public void addView(View child, LayoutParams params) {
477         throw new UnsupportedOperationException("addView(View, LayoutParams) "
478                 + "is not supported in AdapterView");
479     }
480 
481     /**
482      * This method is not supported and throws an UnsupportedOperationException when called.
483      *
484      * @param child Ignored.
485      * @param index Ignored.
486      * @param params Ignored.
487      *
488      * @throws UnsupportedOperationException Every time this method is invoked.
489      */
490     @Override
addView(View child, int index, LayoutParams params)491     public void addView(View child, int index, LayoutParams params) {
492         throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
493                 + "is not supported in AdapterView");
494     }
495 
496     /**
497      * This method is not supported and throws an UnsupportedOperationException when called.
498      *
499      * @param child Ignored.
500      *
501      * @throws UnsupportedOperationException Every time this method is invoked.
502      */
503     @Override
removeView(View child)504     public void removeView(View child) {
505         throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
506     }
507 
508     /**
509      * This method is not supported and throws an UnsupportedOperationException when called.
510      *
511      * @param index Ignored.
512      *
513      * @throws UnsupportedOperationException Every time this method is invoked.
514      */
515     @Override
removeViewAt(int index)516     public void removeViewAt(int index) {
517         throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
518     }
519 
520     /**
521      * This method is not supported and throws an UnsupportedOperationException when called.
522      *
523      * @throws UnsupportedOperationException Every time this method is invoked.
524      */
525     @Override
removeAllViews()526     public void removeAllViews() {
527         throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
528     }
529 
530     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)531     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
532         mLayoutHeight = getHeight();
533     }
534 
535     /**
536      * Return the position of the currently selected item within the adapter's data set
537      *
538      * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
539      */
540     @ViewDebug.CapturedViewProperty
getSelectedItemPosition()541     public int getSelectedItemPosition() {
542         return mNextSelectedPosition;
543     }
544 
545     /**
546      * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
547      * if nothing is selected.
548      */
549     @ViewDebug.CapturedViewProperty
getSelectedItemId()550     public long getSelectedItemId() {
551         return mNextSelectedRowId;
552     }
553 
554     /**
555      * @return The view corresponding to the currently selected item, or null
556      * if nothing is selected
557      */
getSelectedView()558     public abstract View getSelectedView();
559 
560     /**
561      * @return The data corresponding to the currently selected item, or
562      * null if there is nothing selected.
563      */
getSelectedItem()564     public Object getSelectedItem() {
565         T adapter = getAdapter();
566         int selection = getSelectedItemPosition();
567         if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
568             return adapter.getItem(selection);
569         } else {
570             return null;
571         }
572     }
573 
574     /**
575      * @return The number of items owned by the Adapter associated with this
576      *         AdapterView. (This is the number of data items, which may be
577      *         larger than the number of visible views.)
578      */
579     @ViewDebug.CapturedViewProperty
getCount()580     public int getCount() {
581         return mItemCount;
582     }
583 
584     /**
585      * Get the position within the adapter's data set for the view, where view is a an adapter item
586      * or a descendant of an adapter item.
587      *
588      * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
589      *        AdapterView at the time of the call.
590      * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
591      *         if the view does not correspond to a list item (or it is not currently visible).
592      */
getPositionForView(View view)593     public int getPositionForView(View view) {
594         View listItem = view;
595         try {
596             View v;
597             while (!(v = (View) listItem.getParent()).equals(this)) {
598                 listItem = v;
599             }
600         } catch (ClassCastException e) {
601             // We made it up to the window without find this list view
602             return INVALID_POSITION;
603         }
604 
605         // Search the children for the list item
606         final int childCount = getChildCount();
607         for (int i = 0; i < childCount; i++) {
608             if (getChildAt(i).equals(listItem)) {
609                 return mFirstPosition + i;
610             }
611         }
612 
613         // Child not found!
614         return INVALID_POSITION;
615     }
616 
617     /**
618      * Returns the position within the adapter's data set for the first item
619      * displayed on screen.
620      *
621      * @return The position within the adapter's data set
622      */
getFirstVisiblePosition()623     public int getFirstVisiblePosition() {
624         return mFirstPosition;
625     }
626 
627     /**
628      * Returns the position within the adapter's data set for the last item
629      * displayed on screen.
630      *
631      * @return The position within the adapter's data set
632      */
getLastVisiblePosition()633     public int getLastVisiblePosition() {
634         return mFirstPosition + getChildCount() - 1;
635     }
636 
637     /**
638      * Sets the currently selected item. To support accessibility subclasses that
639      * override this method must invoke the overriden super method first.
640      *
641      * @param position Index (starting at 0) of the data item to be selected.
642      */
setSelection(int position)643     public abstract void setSelection(int position);
644 
645     /**
646      * Sets the view to show if the adapter is empty
647      */
648     @android.view.RemotableViewMethod
setEmptyView(View emptyView)649     public void setEmptyView(View emptyView) {
650         mEmptyView = emptyView;
651 
652         // If not explicitly specified this view is important for accessibility.
653         if (emptyView != null
654                 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
655             emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
656         }
657 
658         final T adapter = getAdapter();
659         final boolean empty = ((adapter == null) || adapter.isEmpty());
660         updateEmptyStatus(empty);
661     }
662 
663     /**
664      * When the current adapter is empty, the AdapterView can display a special view
665      * call the empty view. The empty view is used to provide feedback to the user
666      * that no data is available in this AdapterView.
667      *
668      * @return The view to show if the adapter is empty.
669      */
getEmptyView()670     public View getEmptyView() {
671         return mEmptyView;
672     }
673 
674     /**
675      * Indicates whether this view is in filter mode. Filter mode can for instance
676      * be enabled by a user when typing on the keyboard.
677      *
678      * @return True if the view is in filter mode, false otherwise.
679      */
isInFilterMode()680     boolean isInFilterMode() {
681         return false;
682     }
683 
684     @Override
setFocusable(boolean focusable)685     public void setFocusable(boolean focusable) {
686         final T adapter = getAdapter();
687         final boolean empty = adapter == null || adapter.getCount() == 0;
688 
689         mDesiredFocusableState = focusable;
690         if (!focusable) {
691             mDesiredFocusableInTouchModeState = false;
692         }
693 
694         super.setFocusable(focusable && (!empty || isInFilterMode()));
695     }
696 
697     @Override
setFocusableInTouchMode(boolean focusable)698     public void setFocusableInTouchMode(boolean focusable) {
699         final T adapter = getAdapter();
700         final boolean empty = adapter == null || adapter.getCount() == 0;
701 
702         mDesiredFocusableInTouchModeState = focusable;
703         if (focusable) {
704             mDesiredFocusableState = true;
705         }
706 
707         super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
708     }
709 
checkFocus()710     void checkFocus() {
711         final T adapter = getAdapter();
712         final boolean empty = adapter == null || adapter.getCount() == 0;
713         final boolean focusable = !empty || isInFilterMode();
714         // The order in which we set focusable in touch mode/focusable may matter
715         // for the client, see View.setFocusableInTouchMode() comments for more
716         // details
717         super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
718         super.setFocusable(focusable && mDesiredFocusableState);
719         if (mEmptyView != null) {
720             updateEmptyStatus((adapter == null) || adapter.isEmpty());
721         }
722     }
723 
724     /**
725      * Update the status of the list based on the empty parameter.  If empty is true and
726      * we have an empty view, display it.  In all the other cases, make sure that the listview
727      * is VISIBLE and that the empty view is GONE (if it's not null).
728      */
updateEmptyStatus(boolean empty)729     private void updateEmptyStatus(boolean empty) {
730         if (isInFilterMode()) {
731             empty = false;
732         }
733 
734         if (empty) {
735             if (mEmptyView != null) {
736                 mEmptyView.setVisibility(View.VISIBLE);
737                 setVisibility(View.GONE);
738             } else {
739                 // If the caller just removed our empty view, make sure the list view is visible
740                 setVisibility(View.VISIBLE);
741             }
742 
743             // We are now GONE, so pending layouts will not be dispatched.
744             // Force one here to make sure that the state of the list matches
745             // the state of the adapter.
746             if (mDataChanged) {
747                 this.onLayout(false, mLeft, mTop, mRight, mBottom);
748             }
749         } else {
750             if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
751             setVisibility(View.VISIBLE);
752         }
753     }
754 
755     /**
756      * Gets the data associated with the specified position in the list.
757      *
758      * @param position Which data to get
759      * @return The data associated with the specified position in the list
760      */
getItemAtPosition(int position)761     public Object getItemAtPosition(int position) {
762         T adapter = getAdapter();
763         return (adapter == null || position < 0) ? null : adapter.getItem(position);
764     }
765 
getItemIdAtPosition(int position)766     public long getItemIdAtPosition(int position) {
767         T adapter = getAdapter();
768         return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
769     }
770 
771     @Override
setOnClickListener(OnClickListener l)772     public void setOnClickListener(OnClickListener l) {
773         throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
774                 + "You probably want setOnItemClickListener instead");
775     }
776 
777     /**
778      * Override to prevent freezing of any views created by the adapter.
779      */
780     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)781     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
782         dispatchFreezeSelfOnly(container);
783     }
784 
785     /**
786      * Override to prevent thawing of any views created by the adapter.
787      */
788     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)789     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
790         dispatchThawSelfOnly(container);
791     }
792 
793     class AdapterDataSetObserver extends DataSetObserver {
794 
795         private Parcelable mInstanceState = null;
796 
797         @Override
onChanged()798         public void onChanged() {
799             mDataChanged = true;
800             mOldItemCount = mItemCount;
801             mItemCount = getAdapter().getCount();
802 
803             // Detect the case where a cursor that was previously invalidated has
804             // been repopulated with new data.
805             if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
806                     && mOldItemCount == 0 && mItemCount > 0) {
807                 AdapterView.this.onRestoreInstanceState(mInstanceState);
808                 mInstanceState = null;
809             } else {
810                 rememberSyncState();
811             }
812             checkFocus();
813             requestLayout();
814         }
815 
816         @Override
onInvalidated()817         public void onInvalidated() {
818             mDataChanged = true;
819 
820             if (AdapterView.this.getAdapter().hasStableIds()) {
821                 // Remember the current state for the case where our hosting activity is being
822                 // stopped and later restarted
823                 mInstanceState = AdapterView.this.onSaveInstanceState();
824             }
825 
826             // Data is invalid so we should reset our state
827             mOldItemCount = mItemCount;
828             mItemCount = 0;
829             mSelectedPosition = INVALID_POSITION;
830             mSelectedRowId = INVALID_ROW_ID;
831             mNextSelectedPosition = INVALID_POSITION;
832             mNextSelectedRowId = INVALID_ROW_ID;
833             mNeedSync = false;
834 
835             checkFocus();
836             requestLayout();
837         }
838 
clearSavedState()839         public void clearSavedState() {
840             mInstanceState = null;
841         }
842     }
843 
844     @Override
onDetachedFromWindow()845     protected void onDetachedFromWindow() {
846         super.onDetachedFromWindow();
847         removeCallbacks(mSelectionNotifier);
848     }
849 
850     private class SelectionNotifier implements Runnable {
run()851         public void run() {
852             if (mDataChanged) {
853                 // Data has changed between when this SelectionNotifier
854                 // was posted and now. We need to wait until the AdapterView
855                 // has been synched to the new data.
856                 if (getAdapter() != null) {
857                     post(this);
858                 }
859             } else {
860                 fireOnSelected();
861                 performAccessibilityActionsOnSelected();
862             }
863         }
864     }
865 
selectionChanged()866     void selectionChanged() {
867         if (mOnItemSelectedListener != null
868                 || AccessibilityManager.getInstance(mContext).isEnabled()) {
869             if (mInLayout || mBlockLayoutRequests) {
870                 // If we are in a layout traversal, defer notification
871                 // by posting. This ensures that the view tree is
872                 // in a consistent state and is able to accomodate
873                 // new layout or invalidate requests.
874                 if (mSelectionNotifier == null) {
875                     mSelectionNotifier = new SelectionNotifier();
876                 }
877                 post(mSelectionNotifier);
878             } else {
879                 fireOnSelected();
880                 performAccessibilityActionsOnSelected();
881             }
882         }
883     }
884 
fireOnSelected()885     private void fireOnSelected() {
886         if (mOnItemSelectedListener == null) {
887             return;
888         }
889         final int selection = getSelectedItemPosition();
890         if (selection >= 0) {
891             View v = getSelectedView();
892             mOnItemSelectedListener.onItemSelected(this, v, selection,
893                     getAdapter().getItemId(selection));
894         } else {
895             mOnItemSelectedListener.onNothingSelected(this);
896         }
897     }
898 
performAccessibilityActionsOnSelected()899     private void performAccessibilityActionsOnSelected() {
900         if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
901             return;
902         }
903         final int position = getSelectedItemPosition();
904         if (position >= 0) {
905             // we fire selection events here not in View
906             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
907         }
908     }
909 
910     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)911     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
912         View selectedView = getSelectedView();
913         if (selectedView != null && selectedView.getVisibility() == VISIBLE
914                 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
915             return true;
916         }
917         return false;
918     }
919 
920     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)921     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
922         if (super.onRequestSendAccessibilityEvent(child, event)) {
923             // Add a record for ourselves as well.
924             AccessibilityEvent record = AccessibilityEvent.obtain();
925             onInitializeAccessibilityEvent(record);
926             // Populate with the text of the requesting child.
927             child.dispatchPopulateAccessibilityEvent(record);
928             event.appendRecord(record);
929             return true;
930         }
931         return false;
932     }
933 
934     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)935     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
936         super.onInitializeAccessibilityNodeInfo(info);
937         info.setClassName(AdapterView.class.getName());
938         info.setScrollable(isScrollableForAccessibility());
939         View selectedView = getSelectedView();
940         if (selectedView != null) {
941             info.setEnabled(selectedView.isEnabled());
942         }
943     }
944 
945     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)946     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
947         super.onInitializeAccessibilityEvent(event);
948         event.setClassName(AdapterView.class.getName());
949         event.setScrollable(isScrollableForAccessibility());
950         View selectedView = getSelectedView();
951         if (selectedView != null) {
952             event.setEnabled(selectedView.isEnabled());
953         }
954         event.setCurrentItemIndex(getSelectedItemPosition());
955         event.setFromIndex(getFirstVisiblePosition());
956         event.setToIndex(getLastVisiblePosition());
957         event.setItemCount(getCount());
958     }
959 
isScrollableForAccessibility()960     private boolean isScrollableForAccessibility() {
961         T adapter = getAdapter();
962         if (adapter != null) {
963             final int itemCount = adapter.getCount();
964             return itemCount > 0
965                 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
966         }
967         return false;
968     }
969 
970     @Override
canAnimate()971     protected boolean canAnimate() {
972         return super.canAnimate() && mItemCount > 0;
973     }
974 
handleDataChanged()975     void handleDataChanged() {
976         final int count = mItemCount;
977         boolean found = false;
978 
979         if (count > 0) {
980 
981             int newPos;
982 
983             // Find the row we are supposed to sync to
984             if (mNeedSync) {
985                 // Update this first, since setNextSelectedPositionInt inspects
986                 // it
987                 mNeedSync = false;
988 
989                 // See if we can find a position in the new data with the same
990                 // id as the old selection
991                 newPos = findSyncPosition();
992                 if (newPos >= 0) {
993                     // Verify that new selection is selectable
994                     int selectablePos = lookForSelectablePosition(newPos, true);
995                     if (selectablePos == newPos) {
996                         // Same row id is selected
997                         setNextSelectedPositionInt(newPos);
998                         found = true;
999                     }
1000                 }
1001             }
1002             if (!found) {
1003                 // Try to use the same position if we can't find matching data
1004                 newPos = getSelectedItemPosition();
1005 
1006                 // Pin position to the available range
1007                 if (newPos >= count) {
1008                     newPos = count - 1;
1009                 }
1010                 if (newPos < 0) {
1011                     newPos = 0;
1012                 }
1013 
1014                 // Make sure we select something selectable -- first look down
1015                 int selectablePos = lookForSelectablePosition(newPos, true);
1016                 if (selectablePos < 0) {
1017                     // Looking down didn't work -- try looking up
1018                     selectablePos = lookForSelectablePosition(newPos, false);
1019                 }
1020                 if (selectablePos >= 0) {
1021                     setNextSelectedPositionInt(selectablePos);
1022                     checkSelectionChanged();
1023                     found = true;
1024                 }
1025             }
1026         }
1027         if (!found) {
1028             // Nothing is selected
1029             mSelectedPosition = INVALID_POSITION;
1030             mSelectedRowId = INVALID_ROW_ID;
1031             mNextSelectedPosition = INVALID_POSITION;
1032             mNextSelectedRowId = INVALID_ROW_ID;
1033             mNeedSync = false;
1034             checkSelectionChanged();
1035         }
1036 
1037         //TODO: Hmm, we do not know the old state so this is sub-optimal
1038         notifyAccessibilityStateChanged();
1039     }
1040 
checkSelectionChanged()1041     void checkSelectionChanged() {
1042         if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1043             selectionChanged();
1044             mOldSelectedPosition = mSelectedPosition;
1045             mOldSelectedRowId = mSelectedRowId;
1046         }
1047     }
1048 
1049     /**
1050      * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1051      * and then alternates between moving up and moving down until 1) we find the right position, or
1052      * 2) we run out of time, or 3) we have looked at every position
1053      *
1054      * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1055      *         be found
1056      */
findSyncPosition()1057     int findSyncPosition() {
1058         int count = mItemCount;
1059 
1060         if (count == 0) {
1061             return INVALID_POSITION;
1062         }
1063 
1064         long idToMatch = mSyncRowId;
1065         int seed = mSyncPosition;
1066 
1067         // If there isn't a selection don't hunt for it
1068         if (idToMatch == INVALID_ROW_ID) {
1069             return INVALID_POSITION;
1070         }
1071 
1072         // Pin seed to reasonable values
1073         seed = Math.max(0, seed);
1074         seed = Math.min(count - 1, seed);
1075 
1076         long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1077 
1078         long rowId;
1079 
1080         // first position scanned so far
1081         int first = seed;
1082 
1083         // last position scanned so far
1084         int last = seed;
1085 
1086         // True if we should move down on the next iteration
1087         boolean next = false;
1088 
1089         // True when we have looked at the first item in the data
1090         boolean hitFirst;
1091 
1092         // True when we have looked at the last item in the data
1093         boolean hitLast;
1094 
1095         // Get the item ID locally (instead of getItemIdAtPosition), so
1096         // we need the adapter
1097         T adapter = getAdapter();
1098         if (adapter == null) {
1099             return INVALID_POSITION;
1100         }
1101 
1102         while (SystemClock.uptimeMillis() <= endTime) {
1103             rowId = adapter.getItemId(seed);
1104             if (rowId == idToMatch) {
1105                 // Found it!
1106                 return seed;
1107             }
1108 
1109             hitLast = last == count - 1;
1110             hitFirst = first == 0;
1111 
1112             if (hitLast && hitFirst) {
1113                 // Looked at everything
1114                 break;
1115             }
1116 
1117             if (hitFirst || (next && !hitLast)) {
1118                 // Either we hit the top, or we are trying to move down
1119                 last++;
1120                 seed = last;
1121                 // Try going up next time
1122                 next = false;
1123             } else if (hitLast || (!next && !hitFirst)) {
1124                 // Either we hit the bottom, or we are trying to move up
1125                 first--;
1126                 seed = first;
1127                 // Try going down next time
1128                 next = true;
1129             }
1130 
1131         }
1132 
1133         return INVALID_POSITION;
1134     }
1135 
1136     /**
1137      * Find a position that can be selected (i.e., is not a separator).
1138      *
1139      * @param position The starting position to look at.
1140      * @param lookDown Whether to look down for other positions.
1141      * @return The next selectable position starting at position and then searching either up or
1142      *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
1143      */
lookForSelectablePosition(int position, boolean lookDown)1144     int lookForSelectablePosition(int position, boolean lookDown) {
1145         return position;
1146     }
1147 
1148     /**
1149      * Utility to keep mSelectedPosition and mSelectedRowId in sync
1150      * @param position Our current position
1151      */
setSelectedPositionInt(int position)1152     void setSelectedPositionInt(int position) {
1153         mSelectedPosition = position;
1154         mSelectedRowId = getItemIdAtPosition(position);
1155     }
1156 
1157     /**
1158      * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1159      * @param position Intended value for mSelectedPosition the next time we go
1160      * through layout
1161      */
setNextSelectedPositionInt(int position)1162     void setNextSelectedPositionInt(int position) {
1163         mNextSelectedPosition = position;
1164         mNextSelectedRowId = getItemIdAtPosition(position);
1165         // If we are trying to sync to the selection, update that too
1166         if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1167             mSyncPosition = position;
1168             mSyncRowId = mNextSelectedRowId;
1169         }
1170     }
1171 
1172     /**
1173      * Remember enough information to restore the screen state when the data has
1174      * changed.
1175      *
1176      */
rememberSyncState()1177     void rememberSyncState() {
1178         if (getChildCount() > 0) {
1179             mNeedSync = true;
1180             mSyncHeight = mLayoutHeight;
1181             if (mSelectedPosition >= 0) {
1182                 // Sync the selection state
1183                 View v = getChildAt(mSelectedPosition - mFirstPosition);
1184                 mSyncRowId = mNextSelectedRowId;
1185                 mSyncPosition = mNextSelectedPosition;
1186                 if (v != null) {
1187                     mSpecificTop = v.getTop();
1188                 }
1189                 mSyncMode = SYNC_SELECTED_POSITION;
1190             } else {
1191                 // Sync the based on the offset of the first view
1192                 View v = getChildAt(0);
1193                 T adapter = getAdapter();
1194                 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1195                     mSyncRowId = adapter.getItemId(mFirstPosition);
1196                 } else {
1197                     mSyncRowId = NO_ID;
1198                 }
1199                 mSyncPosition = mFirstPosition;
1200                 if (v != null) {
1201                     mSpecificTop = v.getTop();
1202                 }
1203                 mSyncMode = SYNC_FIRST_POSITION;
1204             }
1205         }
1206     }
1207 }
1208