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