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 * Returns the position within the adapter's data set for the view, where 604 * view is a an adapter item or a descendant of an adapter item. 605 * <p> 606 * <strong>Note:</strong> The result of this method only reflects the 607 * position of the data bound to <var>view</var> during the most recent 608 * layout pass. If the adapter's data set has changed without a subsequent 609 * layout pass, the position returned by this method may not match the 610 * current position of the data within the adapter. 611 * 612 * @param view an adapter item, or a descendant of an adapter item. This 613 * must be visible in this AdapterView at the time of the call. 614 * @return the position within the adapter's data set of the view, or 615 * {@link #INVALID_POSITION} if the view does not correspond to a 616 * list item (or it is not currently visible) 617 */ getPositionForView(View view)618 public int getPositionForView(View view) { 619 View listItem = view; 620 try { 621 View v; 622 while ((v = (View) listItem.getParent()) != null && !v.equals(this)) { 623 listItem = v; 624 } 625 } catch (ClassCastException e) { 626 // We made it up to the window without find this list view 627 return INVALID_POSITION; 628 } 629 630 if (listItem != null) { 631 // Search the children for the list item 632 final int childCount = getChildCount(); 633 for (int i = 0; i < childCount; i++) { 634 if (getChildAt(i).equals(listItem)) { 635 return mFirstPosition + i; 636 } 637 } 638 } 639 640 // Child not found! 641 return INVALID_POSITION; 642 } 643 644 /** 645 * Returns the position within the adapter's data set for the first item 646 * displayed on screen. 647 * 648 * @return The position within the adapter's data set 649 */ getFirstVisiblePosition()650 public int getFirstVisiblePosition() { 651 return mFirstPosition; 652 } 653 654 /** 655 * Returns the position within the adapter's data set for the last item 656 * displayed on screen. 657 * 658 * @return The position within the adapter's data set 659 */ getLastVisiblePosition()660 public int getLastVisiblePosition() { 661 return mFirstPosition + getChildCount() - 1; 662 } 663 664 /** 665 * Sets the currently selected item. To support accessibility subclasses that 666 * override this method must invoke the overriden super method first. 667 * 668 * @param position Index (starting at 0) of the data item to be selected. 669 */ setSelection(int position)670 public abstract void setSelection(int position); 671 672 /** 673 * Sets the view to show if the adapter is empty 674 */ 675 @android.view.RemotableViewMethod setEmptyView(View emptyView)676 public void setEmptyView(View emptyView) { 677 mEmptyView = emptyView; 678 679 // If not explicitly specified this view is important for accessibility. 680 if (emptyView != null 681 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 682 emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 683 } 684 685 final T adapter = getAdapter(); 686 final boolean empty = ((adapter == null) || adapter.isEmpty()); 687 updateEmptyStatus(empty); 688 } 689 690 /** 691 * When the current adapter is empty, the AdapterView can display a special view 692 * called the empty view. The empty view is used to provide feedback to the user 693 * that no data is available in this AdapterView. 694 * 695 * @return The view to show if the adapter is empty. 696 */ getEmptyView()697 public View getEmptyView() { 698 return mEmptyView; 699 } 700 701 /** 702 * Indicates whether this view is in filter mode. Filter mode can for instance 703 * be enabled by a user when typing on the keyboard. 704 * 705 * @return True if the view is in filter mode, false otherwise. 706 */ isInFilterMode()707 boolean isInFilterMode() { 708 return false; 709 } 710 711 @Override setFocusable(boolean focusable)712 public void setFocusable(boolean focusable) { 713 final T adapter = getAdapter(); 714 final boolean empty = adapter == null || adapter.getCount() == 0; 715 716 mDesiredFocusableState = focusable; 717 if (!focusable) { 718 mDesiredFocusableInTouchModeState = false; 719 } 720 721 super.setFocusable(focusable && (!empty || isInFilterMode())); 722 } 723 724 @Override setFocusableInTouchMode(boolean focusable)725 public void setFocusableInTouchMode(boolean focusable) { 726 final T adapter = getAdapter(); 727 final boolean empty = adapter == null || adapter.getCount() == 0; 728 729 mDesiredFocusableInTouchModeState = focusable; 730 if (focusable) { 731 mDesiredFocusableState = true; 732 } 733 734 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode())); 735 } 736 checkFocus()737 void checkFocus() { 738 final T adapter = getAdapter(); 739 final boolean empty = adapter == null || adapter.getCount() == 0; 740 final boolean focusable = !empty || isInFilterMode(); 741 // The order in which we set focusable in touch mode/focusable may matter 742 // for the client, see View.setFocusableInTouchMode() comments for more 743 // details 744 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); 745 super.setFocusable(focusable && mDesiredFocusableState); 746 if (mEmptyView != null) { 747 updateEmptyStatus((adapter == null) || adapter.isEmpty()); 748 } 749 } 750 751 /** 752 * Update the status of the list based on the empty parameter. If empty is true and 753 * we have an empty view, display it. In all the other cases, make sure that the listview 754 * is VISIBLE and that the empty view is GONE (if it's not null). 755 */ updateEmptyStatus(boolean empty)756 private void updateEmptyStatus(boolean empty) { 757 if (isInFilterMode()) { 758 empty = false; 759 } 760 761 if (empty) { 762 if (mEmptyView != null) { 763 mEmptyView.setVisibility(View.VISIBLE); 764 setVisibility(View.GONE); 765 } else { 766 // If the caller just removed our empty view, make sure the list view is visible 767 setVisibility(View.VISIBLE); 768 } 769 770 // We are now GONE, so pending layouts will not be dispatched. 771 // Force one here to make sure that the state of the list matches 772 // the state of the adapter. 773 if (mDataChanged) { 774 this.onLayout(false, mLeft, mTop, mRight, mBottom); 775 } 776 } else { 777 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE); 778 setVisibility(View.VISIBLE); 779 } 780 } 781 782 /** 783 * Gets the data associated with the specified position in the list. 784 * 785 * @param position Which data to get 786 * @return The data associated with the specified position in the list 787 */ getItemAtPosition(int position)788 public Object getItemAtPosition(int position) { 789 T adapter = getAdapter(); 790 return (adapter == null || position < 0) ? null : adapter.getItem(position); 791 } 792 getItemIdAtPosition(int position)793 public long getItemIdAtPosition(int position) { 794 T adapter = getAdapter(); 795 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position); 796 } 797 798 @Override setOnClickListener(OnClickListener l)799 public void setOnClickListener(OnClickListener l) { 800 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. " 801 + "You probably want setOnItemClickListener instead"); 802 } 803 804 /** 805 * Override to prevent freezing of any views created by the adapter. 806 */ 807 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)808 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 809 dispatchFreezeSelfOnly(container); 810 } 811 812 /** 813 * Override to prevent thawing of any views created by the adapter. 814 */ 815 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)816 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 817 dispatchThawSelfOnly(container); 818 } 819 820 class AdapterDataSetObserver extends DataSetObserver { 821 822 private Parcelable mInstanceState = null; 823 824 @Override onChanged()825 public void onChanged() { 826 mDataChanged = true; 827 mOldItemCount = mItemCount; 828 mItemCount = getAdapter().getCount(); 829 830 // Detect the case where a cursor that was previously invalidated has 831 // been repopulated with new data. 832 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null 833 && mOldItemCount == 0 && mItemCount > 0) { 834 AdapterView.this.onRestoreInstanceState(mInstanceState); 835 mInstanceState = null; 836 } else { 837 rememberSyncState(); 838 } 839 checkFocus(); 840 requestLayout(); 841 } 842 843 @Override onInvalidated()844 public void onInvalidated() { 845 mDataChanged = true; 846 847 if (AdapterView.this.getAdapter().hasStableIds()) { 848 // Remember the current state for the case where our hosting activity is being 849 // stopped and later restarted 850 mInstanceState = AdapterView.this.onSaveInstanceState(); 851 } 852 853 // Data is invalid so we should reset our state 854 mOldItemCount = mItemCount; 855 mItemCount = 0; 856 mSelectedPosition = INVALID_POSITION; 857 mSelectedRowId = INVALID_ROW_ID; 858 mNextSelectedPosition = INVALID_POSITION; 859 mNextSelectedRowId = INVALID_ROW_ID; 860 mNeedSync = false; 861 862 checkFocus(); 863 requestLayout(); 864 } 865 clearSavedState()866 public void clearSavedState() { 867 mInstanceState = null; 868 } 869 } 870 871 @Override onDetachedFromWindow()872 protected void onDetachedFromWindow() { 873 super.onDetachedFromWindow(); 874 removeCallbacks(mSelectionNotifier); 875 } 876 877 private class SelectionNotifier implements Runnable { run()878 public void run() { 879 mPendingSelectionNotifier = null; 880 881 if (mDataChanged && getViewRootImpl() != null 882 && getViewRootImpl().isLayoutRequested()) { 883 // Data has changed between when this SelectionNotifier was 884 // posted and now. Postpone the notification until the next 885 // layout is complete and we run checkSelectionChanged(). 886 if (getAdapter() != null) { 887 mPendingSelectionNotifier = this; 888 } 889 } else { 890 dispatchOnItemSelected(); 891 } 892 } 893 } 894 selectionChanged()895 void selectionChanged() { 896 // We're about to post or run the selection notifier, so we don't need 897 // a pending notifier. 898 mPendingSelectionNotifier = null; 899 900 if (mOnItemSelectedListener != null 901 || AccessibilityManager.getInstance(mContext).isEnabled()) { 902 if (mInLayout || mBlockLayoutRequests) { 903 // If we are in a layout traversal, defer notification 904 // by posting. This ensures that the view tree is 905 // in a consistent state and is able to accommodate 906 // new layout or invalidate requests. 907 if (mSelectionNotifier == null) { 908 mSelectionNotifier = new SelectionNotifier(); 909 } else { 910 removeCallbacks(mSelectionNotifier); 911 } 912 post(mSelectionNotifier); 913 } else { 914 dispatchOnItemSelected(); 915 } 916 } 917 } 918 dispatchOnItemSelected()919 private void dispatchOnItemSelected() { 920 fireOnSelected(); 921 performAccessibilityActionsOnSelected(); 922 } 923 fireOnSelected()924 private void fireOnSelected() { 925 if (mOnItemSelectedListener == null) { 926 return; 927 } 928 final int selection = getSelectedItemPosition(); 929 if (selection >= 0) { 930 View v = getSelectedView(); 931 mOnItemSelectedListener.onItemSelected(this, v, selection, 932 getAdapter().getItemId(selection)); 933 } else { 934 mOnItemSelectedListener.onNothingSelected(this); 935 } 936 } 937 performAccessibilityActionsOnSelected()938 private void performAccessibilityActionsOnSelected() { 939 if (!AccessibilityManager.getInstance(mContext).isEnabled()) { 940 return; 941 } 942 final int position = getSelectedItemPosition(); 943 if (position >= 0) { 944 // we fire selection events here not in View 945 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 946 } 947 } 948 949 /** @hide */ 950 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)951 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 952 View selectedView = getSelectedView(); 953 if (selectedView != null && selectedView.getVisibility() == VISIBLE 954 && selectedView.dispatchPopulateAccessibilityEvent(event)) { 955 return true; 956 } 957 return false; 958 } 959 960 /** @hide */ 961 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)962 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 963 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 964 // Add a record for ourselves as well. 965 AccessibilityEvent record = AccessibilityEvent.obtain(); 966 onInitializeAccessibilityEvent(record); 967 // Populate with the text of the requesting child. 968 child.dispatchPopulateAccessibilityEvent(record); 969 event.appendRecord(record); 970 return true; 971 } 972 return false; 973 } 974 975 @Override getAccessibilityClassName()976 public CharSequence getAccessibilityClassName() { 977 return AdapterView.class.getName(); 978 } 979 980 /** @hide */ 981 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)982 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 983 super.onInitializeAccessibilityNodeInfoInternal(info); 984 info.setScrollable(isScrollableForAccessibility()); 985 View selectedView = getSelectedView(); 986 if (selectedView != null) { 987 info.setEnabled(selectedView.isEnabled()); 988 } 989 } 990 991 /** @hide */ 992 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)993 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 994 super.onInitializeAccessibilityEventInternal(event); 995 event.setScrollable(isScrollableForAccessibility()); 996 View selectedView = getSelectedView(); 997 if (selectedView != null) { 998 event.setEnabled(selectedView.isEnabled()); 999 } 1000 event.setCurrentItemIndex(getSelectedItemPosition()); 1001 event.setFromIndex(getFirstVisiblePosition()); 1002 event.setToIndex(getLastVisiblePosition()); 1003 event.setItemCount(getCount()); 1004 } 1005 isScrollableForAccessibility()1006 private boolean isScrollableForAccessibility() { 1007 T adapter = getAdapter(); 1008 if (adapter != null) { 1009 final int itemCount = adapter.getCount(); 1010 return itemCount > 0 1011 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1); 1012 } 1013 return false; 1014 } 1015 1016 @Override canAnimate()1017 protected boolean canAnimate() { 1018 return super.canAnimate() && mItemCount > 0; 1019 } 1020 handleDataChanged()1021 void handleDataChanged() { 1022 final int count = mItemCount; 1023 boolean found = false; 1024 1025 if (count > 0) { 1026 1027 int newPos; 1028 1029 // Find the row we are supposed to sync to 1030 if (mNeedSync) { 1031 // Update this first, since setNextSelectedPositionInt inspects 1032 // it 1033 mNeedSync = false; 1034 1035 // See if we can find a position in the new data with the same 1036 // id as the old selection 1037 newPos = findSyncPosition(); 1038 if (newPos >= 0) { 1039 // Verify that new selection is selectable 1040 int selectablePos = lookForSelectablePosition(newPos, true); 1041 if (selectablePos == newPos) { 1042 // Same row id is selected 1043 setNextSelectedPositionInt(newPos); 1044 found = true; 1045 } 1046 } 1047 } 1048 if (!found) { 1049 // Try to use the same position if we can't find matching data 1050 newPos = getSelectedItemPosition(); 1051 1052 // Pin position to the available range 1053 if (newPos >= count) { 1054 newPos = count - 1; 1055 } 1056 if (newPos < 0) { 1057 newPos = 0; 1058 } 1059 1060 // Make sure we select something selectable -- first look down 1061 int selectablePos = lookForSelectablePosition(newPos, true); 1062 if (selectablePos < 0) { 1063 // Looking down didn't work -- try looking up 1064 selectablePos = lookForSelectablePosition(newPos, false); 1065 } 1066 if (selectablePos >= 0) { 1067 setNextSelectedPositionInt(selectablePos); 1068 checkSelectionChanged(); 1069 found = true; 1070 } 1071 } 1072 } 1073 if (!found) { 1074 // Nothing is selected 1075 mSelectedPosition = INVALID_POSITION; 1076 mSelectedRowId = INVALID_ROW_ID; 1077 mNextSelectedPosition = INVALID_POSITION; 1078 mNextSelectedRowId = INVALID_ROW_ID; 1079 mNeedSync = false; 1080 checkSelectionChanged(); 1081 } 1082 1083 notifySubtreeAccessibilityStateChangedIfNeeded(); 1084 } 1085 1086 /** 1087 * Called after layout to determine whether the selection position needs to 1088 * be updated. Also used to fire any pending selection events. 1089 */ checkSelectionChanged()1090 void checkSelectionChanged() { 1091 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) { 1092 selectionChanged(); 1093 mOldSelectedPosition = mSelectedPosition; 1094 mOldSelectedRowId = mSelectedRowId; 1095 } 1096 1097 // If we have a pending selection notification -- and we won't if we 1098 // just fired one in selectionChanged() -- run it now. 1099 if (mPendingSelectionNotifier != null) { 1100 mPendingSelectionNotifier.run(); 1101 } 1102 } 1103 1104 /** 1105 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition 1106 * and then alternates between moving up and moving down until 1) we find the right position, or 1107 * 2) we run out of time, or 3) we have looked at every position 1108 * 1109 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't 1110 * be found 1111 */ findSyncPosition()1112 int findSyncPosition() { 1113 int count = mItemCount; 1114 1115 if (count == 0) { 1116 return INVALID_POSITION; 1117 } 1118 1119 long idToMatch = mSyncRowId; 1120 int seed = mSyncPosition; 1121 1122 // If there isn't a selection don't hunt for it 1123 if (idToMatch == INVALID_ROW_ID) { 1124 return INVALID_POSITION; 1125 } 1126 1127 // Pin seed to reasonable values 1128 seed = Math.max(0, seed); 1129 seed = Math.min(count - 1, seed); 1130 1131 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; 1132 1133 long rowId; 1134 1135 // first position scanned so far 1136 int first = seed; 1137 1138 // last position scanned so far 1139 int last = seed; 1140 1141 // True if we should move down on the next iteration 1142 boolean next = false; 1143 1144 // True when we have looked at the first item in the data 1145 boolean hitFirst; 1146 1147 // True when we have looked at the last item in the data 1148 boolean hitLast; 1149 1150 // Get the item ID locally (instead of getItemIdAtPosition), so 1151 // we need the adapter 1152 T adapter = getAdapter(); 1153 if (adapter == null) { 1154 return INVALID_POSITION; 1155 } 1156 1157 while (SystemClock.uptimeMillis() <= endTime) { 1158 rowId = adapter.getItemId(seed); 1159 if (rowId == idToMatch) { 1160 // Found it! 1161 return seed; 1162 } 1163 1164 hitLast = last == count - 1; 1165 hitFirst = first == 0; 1166 1167 if (hitLast && hitFirst) { 1168 // Looked at everything 1169 break; 1170 } 1171 1172 if (hitFirst || (next && !hitLast)) { 1173 // Either we hit the top, or we are trying to move down 1174 last++; 1175 seed = last; 1176 // Try going up next time 1177 next = false; 1178 } else if (hitLast || (!next && !hitFirst)) { 1179 // Either we hit the bottom, or we are trying to move up 1180 first--; 1181 seed = first; 1182 // Try going down next time 1183 next = true; 1184 } 1185 1186 } 1187 1188 return INVALID_POSITION; 1189 } 1190 1191 /** 1192 * Find a position that can be selected (i.e., is not a separator). 1193 * 1194 * @param position The starting position to look at. 1195 * @param lookDown Whether to look down for other positions. 1196 * @return The next selectable position starting at position and then searching either up or 1197 * down. Returns {@link #INVALID_POSITION} if nothing can be found. 1198 */ lookForSelectablePosition(int position, boolean lookDown)1199 int lookForSelectablePosition(int position, boolean lookDown) { 1200 return position; 1201 } 1202 1203 /** 1204 * Utility to keep mSelectedPosition and mSelectedRowId in sync 1205 * @param position Our current position 1206 */ setSelectedPositionInt(int position)1207 void setSelectedPositionInt(int position) { 1208 mSelectedPosition = position; 1209 mSelectedRowId = getItemIdAtPosition(position); 1210 } 1211 1212 /** 1213 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync 1214 * @param position Intended value for mSelectedPosition the next time we go 1215 * through layout 1216 */ setNextSelectedPositionInt(int position)1217 void setNextSelectedPositionInt(int position) { 1218 mNextSelectedPosition = position; 1219 mNextSelectedRowId = getItemIdAtPosition(position); 1220 // If we are trying to sync to the selection, update that too 1221 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { 1222 mSyncPosition = position; 1223 mSyncRowId = mNextSelectedRowId; 1224 } 1225 } 1226 1227 /** 1228 * Remember enough information to restore the screen state when the data has 1229 * changed. 1230 * 1231 */ rememberSyncState()1232 void rememberSyncState() { 1233 if (getChildCount() > 0) { 1234 mNeedSync = true; 1235 mSyncHeight = mLayoutHeight; 1236 if (mSelectedPosition >= 0) { 1237 // Sync the selection state 1238 View v = getChildAt(mSelectedPosition - mFirstPosition); 1239 mSyncRowId = mNextSelectedRowId; 1240 mSyncPosition = mNextSelectedPosition; 1241 if (v != null) { 1242 mSpecificTop = v.getTop(); 1243 } 1244 mSyncMode = SYNC_SELECTED_POSITION; 1245 } else { 1246 // Sync the based on the offset of the first view 1247 View v = getChildAt(0); 1248 T adapter = getAdapter(); 1249 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { 1250 mSyncRowId = adapter.getItemId(mFirstPosition); 1251 } else { 1252 mSyncRowId = NO_ID; 1253 } 1254 mSyncPosition = mFirstPosition; 1255 if (v != null) { 1256 mSpecificTop = v.getTop(); 1257 } 1258 mSyncMode = SYNC_FIRST_POSITION; 1259 } 1260 } 1261 } 1262 1263 /** @hide */ 1264 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)1265 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 1266 super.encodeProperties(encoder); 1267 1268 encoder.addProperty("scrolling:firstPosition", mFirstPosition); 1269 encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition); 1270 encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId); 1271 encoder.addProperty("list:selectedPosition", mSelectedPosition); 1272 encoder.addProperty("list:itemCount", mItemCount); 1273 } 1274 } 1275