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