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.ColorInt; 20 import android.annotation.DrawableRes; 21 import android.annotation.NonNull; 22 import android.annotation.TestApi; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Configuration; 26 import android.content.res.TypedArray; 27 import android.graphics.Canvas; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.TransitionDrawable; 31 import android.os.Bundle; 32 import android.os.Debug; 33 import android.os.Handler; 34 import android.os.Parcel; 35 import android.os.Parcelable; 36 import android.os.StrictMode; 37 import android.os.Trace; 38 import android.text.Editable; 39 import android.text.InputType; 40 import android.text.TextUtils; 41 import android.text.TextWatcher; 42 import android.util.AttributeSet; 43 import android.util.Log; 44 import android.util.LongSparseArray; 45 import android.util.SparseArray; 46 import android.util.SparseBooleanArray; 47 import android.util.StateSet; 48 import android.view.ActionMode; 49 import android.view.ContextMenu.ContextMenuInfo; 50 import android.view.Gravity; 51 import android.view.HapticFeedbackConstants; 52 import android.view.InputDevice; 53 import android.view.KeyEvent; 54 import android.view.LayoutInflater; 55 import android.view.Menu; 56 import android.view.MenuItem; 57 import android.view.MotionEvent; 58 import android.view.PointerIcon; 59 import android.view.VelocityTracker; 60 import android.view.View; 61 import android.view.ViewConfiguration; 62 import android.view.ViewDebug; 63 import android.view.ViewGroup; 64 import android.view.ViewHierarchyEncoder; 65 import android.view.ViewParent; 66 import android.view.ViewTreeObserver; 67 import android.view.accessibility.AccessibilityEvent; 68 import android.view.accessibility.AccessibilityManager; 69 import android.view.accessibility.AccessibilityNodeInfo; 70 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 71 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo; 72 import android.view.animation.Interpolator; 73 import android.view.animation.LinearInterpolator; 74 import android.view.inputmethod.BaseInputConnection; 75 import android.view.inputmethod.CompletionInfo; 76 import android.view.inputmethod.CorrectionInfo; 77 import android.view.inputmethod.EditorInfo; 78 import android.view.inputmethod.ExtractedText; 79 import android.view.inputmethod.ExtractedTextRequest; 80 import android.view.inputmethod.InputConnection; 81 import android.view.inputmethod.InputContentInfo; 82 import android.view.inputmethod.InputMethodManager; 83 import android.widget.RemoteViews.OnClickHandler; 84 85 import com.android.internal.R; 86 87 import java.util.ArrayList; 88 import java.util.List; 89 90 /** 91 * Base class that can be used to implement virtualized lists of items. A list does 92 * not have a spatial definition here. For instance, subclasses of this class can 93 * display the content of the list in a grid, in a carousel, as stack, etc. 94 * 95 * @attr ref android.R.styleable#AbsListView_listSelector 96 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop 97 * @attr ref android.R.styleable#AbsListView_stackFromBottom 98 * @attr ref android.R.styleable#AbsListView_scrollingCache 99 * @attr ref android.R.styleable#AbsListView_textFilterEnabled 100 * @attr ref android.R.styleable#AbsListView_transcriptMode 101 * @attr ref android.R.styleable#AbsListView_cacheColorHint 102 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled 103 * @attr ref android.R.styleable#AbsListView_smoothScrollbar 104 * @attr ref android.R.styleable#AbsListView_choiceMode 105 */ 106 public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, 107 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, 108 ViewTreeObserver.OnTouchModeChangeListener, 109 RemoteViewsAdapter.RemoteAdapterConnectionCallback { 110 111 @SuppressWarnings("UnusedDeclaration") 112 private static final String TAG = "AbsListView"; 113 114 /** 115 * Disables the transcript mode. 116 * 117 * @see #setTranscriptMode(int) 118 */ 119 public static final int TRANSCRIPT_MODE_DISABLED = 0; 120 121 /** 122 * The list will automatically scroll to the bottom when a data set change 123 * notification is received and only if the last item is already visible 124 * on screen. 125 * 126 * @see #setTranscriptMode(int) 127 */ 128 public static final int TRANSCRIPT_MODE_NORMAL = 1; 129 130 /** 131 * The list will automatically scroll to the bottom, no matter what items 132 * are currently visible. 133 * 134 * @see #setTranscriptMode(int) 135 */ 136 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2; 137 138 /** 139 * Indicates that we are not in the middle of a touch gesture 140 */ 141 static final int TOUCH_MODE_REST = -1; 142 143 /** 144 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a 145 * scroll gesture. 146 */ 147 static final int TOUCH_MODE_DOWN = 0; 148 149 /** 150 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch 151 * is a longpress 152 */ 153 static final int TOUCH_MODE_TAP = 1; 154 155 /** 156 * Indicates we have waited for everything we can wait for, but the user's finger is still down 157 */ 158 static final int TOUCH_MODE_DONE_WAITING = 2; 159 160 /** 161 * Indicates the touch gesture is a scroll 162 */ 163 static final int TOUCH_MODE_SCROLL = 3; 164 165 /** 166 * Indicates the view is in the process of being flung 167 */ 168 static final int TOUCH_MODE_FLING = 4; 169 170 /** 171 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end. 172 */ 173 static final int TOUCH_MODE_OVERSCROLL = 5; 174 175 /** 176 * Indicates the view is being flung outside of normal content bounds 177 * and will spring back. 178 */ 179 static final int TOUCH_MODE_OVERFLING = 6; 180 181 /** 182 * Regular layout - usually an unsolicited layout from the view system 183 */ 184 static final int LAYOUT_NORMAL = 0; 185 186 /** 187 * Show the first item 188 */ 189 static final int LAYOUT_FORCE_TOP = 1; 190 191 /** 192 * Force the selected item to be on somewhere on the screen 193 */ 194 static final int LAYOUT_SET_SELECTION = 2; 195 196 /** 197 * Show the last item 198 */ 199 static final int LAYOUT_FORCE_BOTTOM = 3; 200 201 /** 202 * Make a mSelectedItem appear in a specific location and build the rest of 203 * the views from there. The top is specified by mSpecificTop. 204 */ 205 static final int LAYOUT_SPECIFIC = 4; 206 207 /** 208 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top 209 * at mSpecificTop 210 */ 211 static final int LAYOUT_SYNC = 5; 212 213 /** 214 * Layout as a result of using the navigation keys 215 */ 216 static final int LAYOUT_MOVE_SELECTION = 6; 217 218 /** 219 * Normal list that does not indicate choices 220 */ 221 public static final int CHOICE_MODE_NONE = 0; 222 223 /** 224 * The list allows up to one choice 225 */ 226 public static final int CHOICE_MODE_SINGLE = 1; 227 228 /** 229 * The list allows multiple choices 230 */ 231 public static final int CHOICE_MODE_MULTIPLE = 2; 232 233 /** 234 * The list allows multiple choices in a modal selection mode 235 */ 236 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3; 237 238 /** 239 * The thread that created this view. 240 */ 241 private final Thread mOwnerThread; 242 243 /** 244 * Controls if/how the user may choose/check items in the list 245 */ 246 int mChoiceMode = CHOICE_MODE_NONE; 247 248 /** 249 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive. 250 */ 251 ActionMode mChoiceActionMode; 252 253 /** 254 * Wrapper for the multiple choice mode callback; AbsListView needs to perform 255 * a few extra actions around what application code does. 256 */ 257 MultiChoiceModeWrapper mMultiChoiceModeCallback; 258 259 /** 260 * Running count of how many items are currently checked 261 */ 262 int mCheckedItemCount; 263 264 /** 265 * Running state of which positions are currently checked 266 */ 267 SparseBooleanArray mCheckStates; 268 269 /** 270 * Running state of which IDs are currently checked. 271 * If there is a value for a given key, the checked state for that ID is true 272 * and the value holds the last known position in the adapter for that id. 273 */ 274 LongSparseArray<Integer> mCheckedIdStates; 275 276 /** 277 * Controls how the next layout will happen 278 */ 279 int mLayoutMode = LAYOUT_NORMAL; 280 281 /** 282 * Should be used by subclasses to listen to changes in the dataset 283 */ 284 AdapterDataSetObserver mDataSetObserver; 285 286 /** 287 * The adapter containing the data to be displayed by this view 288 */ 289 ListAdapter mAdapter; 290 291 /** 292 * The remote adapter containing the data to be displayed by this view to be set 293 */ 294 private RemoteViewsAdapter mRemoteAdapter; 295 296 /** 297 * If mAdapter != null, whenever this is true the adapter has stable IDs. 298 */ 299 boolean mAdapterHasStableIds; 300 301 /** 302 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects 303 */ 304 private boolean mDeferNotifyDataSetChanged = false; 305 306 /** 307 * Indicates whether the list selector should be drawn on top of the children or behind 308 */ 309 boolean mDrawSelectorOnTop = false; 310 311 /** 312 * The drawable used to draw the selector 313 */ 314 Drawable mSelector; 315 316 /** 317 * The current position of the selector in the list. 318 */ 319 int mSelectorPosition = INVALID_POSITION; 320 321 /** 322 * Defines the selector's location and dimension at drawing time 323 */ 324 Rect mSelectorRect = new Rect(); 325 326 /** 327 * The data set used to store unused views that should be reused during the next layout 328 * to avoid creating new ones 329 */ 330 final RecycleBin mRecycler = new RecycleBin(); 331 332 /** 333 * The selection's left padding 334 */ 335 int mSelectionLeftPadding = 0; 336 337 /** 338 * The selection's top padding 339 */ 340 int mSelectionTopPadding = 0; 341 342 /** 343 * The selection's right padding 344 */ 345 int mSelectionRightPadding = 0; 346 347 /** 348 * The selection's bottom padding 349 */ 350 int mSelectionBottomPadding = 0; 351 352 /** 353 * This view's padding 354 */ 355 Rect mListPadding = new Rect(); 356 357 /** 358 * Subclasses must retain their measure spec from onMeasure() into this member 359 */ 360 int mWidthMeasureSpec = 0; 361 362 /** 363 * The top scroll indicator 364 */ 365 View mScrollUp; 366 367 /** 368 * The down scroll indicator 369 */ 370 View mScrollDown; 371 372 /** 373 * When the view is scrolling, this flag is set to true to indicate subclasses that 374 * the drawing cache was enabled on the children 375 */ 376 boolean mCachingStarted; 377 boolean mCachingActive; 378 379 /** 380 * The position of the view that received the down motion event 381 */ 382 int mMotionPosition; 383 384 /** 385 * The offset to the top of the mMotionPosition view when the down motion event was received 386 */ 387 int mMotionViewOriginalTop; 388 389 /** 390 * The desired offset to the top of the mMotionPosition view after a scroll 391 */ 392 int mMotionViewNewTop; 393 394 /** 395 * The X value associated with the the down motion event 396 */ 397 int mMotionX; 398 399 /** 400 * The Y value associated with the the down motion event 401 */ 402 int mMotionY; 403 404 /** 405 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or 406 * TOUCH_MODE_DONE_WAITING 407 */ 408 int mTouchMode = TOUCH_MODE_REST; 409 410 /** 411 * Y value from on the previous motion event (if any) 412 */ 413 int mLastY; 414 415 /** 416 * How far the finger moved before we started scrolling 417 */ 418 int mMotionCorrection; 419 420 /** 421 * Determines speed during touch scrolling 422 */ 423 private VelocityTracker mVelocityTracker; 424 425 /** 426 * Handles one frame of a fling 427 */ 428 private FlingRunnable mFlingRunnable; 429 430 /** 431 * Handles scrolling between positions within the list. 432 */ 433 AbsPositionScroller mPositionScroller; 434 435 /** 436 * The offset in pixels form the top of the AdapterView to the top 437 * of the currently selected view. Used to save and restore state. 438 */ 439 int mSelectedTop = 0; 440 441 /** 442 * Indicates whether the list is stacked from the bottom edge or 443 * the top edge. 444 */ 445 boolean mStackFromBottom; 446 447 /** 448 * When set to true, the list automatically discards the children's 449 * bitmap cache after scrolling. 450 */ 451 boolean mScrollingCacheEnabled; 452 453 /** 454 * Whether or not to enable the fast scroll feature on this list 455 */ 456 boolean mFastScrollEnabled; 457 458 /** 459 * Whether or not to always show the fast scroll feature on this list 460 */ 461 boolean mFastScrollAlwaysVisible; 462 463 /** 464 * Optional callback to notify client when scroll position has changed 465 */ 466 private OnScrollListener mOnScrollListener; 467 468 /** 469 * Keeps track of our accessory window 470 */ 471 PopupWindow mPopup; 472 473 /** 474 * Used with type filter window 475 */ 476 EditText mTextFilter; 477 478 /** 479 * Indicates whether to use pixels-based or position-based scrollbar 480 * properties. 481 */ 482 private boolean mSmoothScrollbarEnabled = true; 483 484 /** 485 * Indicates that this view supports filtering 486 */ 487 private boolean mTextFilterEnabled; 488 489 /** 490 * Indicates that this view is currently displaying a filtered view of the data 491 */ 492 private boolean mFiltered; 493 494 /** 495 * Rectangle used for hit testing children 496 */ 497 private Rect mTouchFrame; 498 499 /** 500 * The position to resurrect the selected position to. 501 */ 502 int mResurrectToPosition = INVALID_POSITION; 503 504 private ContextMenuInfo mContextMenuInfo = null; 505 506 /** 507 * Maximum distance to record overscroll 508 */ 509 int mOverscrollMax; 510 511 /** 512 * Content height divided by this is the overscroll limit. 513 */ 514 static final int OVERSCROLL_LIMIT_DIVISOR = 3; 515 516 /** 517 * How many positions in either direction we will search to try to 518 * find a checked item with a stable ID that moved position across 519 * a data set change. If the item isn't found it will be unselected. 520 */ 521 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20; 522 523 /** 524 * Used to request a layout when we changed touch mode 525 */ 526 private static final int TOUCH_MODE_UNKNOWN = -1; 527 private static final int TOUCH_MODE_ON = 0; 528 private static final int TOUCH_MODE_OFF = 1; 529 530 private int mLastTouchMode = TOUCH_MODE_UNKNOWN; 531 532 private static final boolean PROFILE_SCROLLING = false; 533 private boolean mScrollProfilingStarted = false; 534 535 private static final boolean PROFILE_FLINGING = false; 536 private boolean mFlingProfilingStarted = false; 537 538 /** 539 * The StrictMode "critical time span" objects to catch animation 540 * stutters. Non-null when a time-sensitive animation is 541 * in-flight. Must call finish() on them when done animating. 542 * These are no-ops on user builds. 543 */ 544 private StrictMode.Span mScrollStrictSpan = null; 545 private StrictMode.Span mFlingStrictSpan = null; 546 547 /** 548 * The last CheckForLongPress runnable we posted, if any 549 */ 550 private CheckForLongPress mPendingCheckForLongPress; 551 552 /** 553 * The last CheckForTap runnable we posted, if any 554 */ 555 private CheckForTap mPendingCheckForTap; 556 557 /** 558 * The last CheckForKeyLongPress runnable we posted, if any 559 */ 560 private CheckForKeyLongPress mPendingCheckForKeyLongPress; 561 562 /** 563 * Acts upon click 564 */ 565 private AbsListView.PerformClick mPerformClick; 566 567 /** 568 * Delayed action for touch mode. 569 */ 570 private Runnable mTouchModeReset; 571 572 /** 573 * Whether the most recent touch event stream resulted in a successful 574 * long-press action. This is reset on TOUCH_DOWN. 575 */ 576 private boolean mHasPerformedLongPress; 577 578 /** 579 * This view is in transcript mode -- it shows the bottom of the list when the data 580 * changes 581 */ 582 private int mTranscriptMode; 583 584 /** 585 * Indicates that this list is always drawn on top of a solid, single-color, opaque 586 * background 587 */ 588 private int mCacheColorHint; 589 590 /** 591 * The select child's view (from the adapter's getView) is enabled. 592 */ 593 private boolean mIsChildViewEnabled; 594 595 /** 596 * The cached drawable state for the selector. Accounts for child enabled 597 * state, but otherwise identical to the view's own drawable state. 598 */ 599 private int[] mSelectorState; 600 601 /** 602 * The last scroll state reported to clients through {@link OnScrollListener}. 603 */ 604 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; 605 606 /** 607 * Helper object that renders and controls the fast scroll thumb. 608 */ 609 private FastScroller mFastScroll; 610 611 /** 612 * Temporary holder for fast scroller style until a FastScroller object 613 * is created. 614 */ 615 private int mFastScrollStyle; 616 617 private boolean mGlobalLayoutListenerAddedFilter; 618 619 private int mTouchSlop; 620 private float mDensityScale; 621 622 private float mVerticalScrollFactor; 623 624 private InputConnection mDefInputConnection; 625 private InputConnectionWrapper mPublicInputConnection; 626 627 private Runnable mClearScrollingCache; 628 Runnable mPositionScrollAfterLayout; 629 private int mMinimumVelocity; 630 private int mMaximumVelocity; 631 private float mVelocityScale = 1.0f; 632 633 final boolean[] mIsScrap = new boolean[1]; 634 635 private final int[] mScrollOffset = new int[2]; 636 private final int[] mScrollConsumed = new int[2]; 637 638 private final float[] mTmpPoint = new float[2]; 639 640 // Used for offsetting MotionEvents that we feed to the VelocityTracker. 641 // In the future it would be nice to be able to give this to the VelocityTracker 642 // directly, or alternatively put a VT into absolute-positioning mode that only 643 // reads the raw screen-coordinate x/y values. 644 private int mNestedYOffset = 0; 645 646 // True when the popup should be hidden because of a call to 647 // dispatchDisplayHint() 648 private boolean mPopupHidden; 649 650 /** 651 * ID of the active pointer. This is used to retain consistency during 652 * drags/flings if multiple pointers are used. 653 */ 654 private int mActivePointerId = INVALID_POINTER; 655 656 /** 657 * Sentinel value for no current active pointer. 658 * Used by {@link #mActivePointerId}. 659 */ 660 private static final int INVALID_POINTER = -1; 661 662 /** 663 * Maximum distance to overscroll by during edge effects 664 */ 665 int mOverscrollDistance; 666 667 /** 668 * Maximum distance to overfling during edge effects 669 */ 670 int mOverflingDistance; 671 672 // These two EdgeGlows are always set and used together. 673 // Checking one for null is as good as checking both. 674 675 /** 676 * Tracks the state of the top edge glow. 677 */ 678 private EdgeEffect mEdgeGlowTop; 679 680 /** 681 * Tracks the state of the bottom edge glow. 682 */ 683 private EdgeEffect mEdgeGlowBottom; 684 685 /** 686 * An estimate of how many pixels are between the top of the list and 687 * the top of the first position in the adapter, based on the last time 688 * we saw it. Used to hint where to draw edge glows. 689 */ 690 private int mFirstPositionDistanceGuess; 691 692 /** 693 * An estimate of how many pixels are between the bottom of the list and 694 * the bottom of the last position in the adapter, based on the last time 695 * we saw it. Used to hint where to draw edge glows. 696 */ 697 private int mLastPositionDistanceGuess; 698 699 /** 700 * Used for determining when to cancel out of overscroll. 701 */ 702 private int mDirection = 0; 703 704 /** 705 * Tracked on measurement in transcript mode. Makes sure that we can still pin to 706 * the bottom correctly on resizes. 707 */ 708 private boolean mForceTranscriptScroll; 709 710 /** 711 * Used for interacting with list items from an accessibility service. 712 */ 713 private ListItemAccessibilityDelegate mAccessibilityDelegate; 714 715 private int mLastAccessibilityScrollEventFromIndex; 716 private int mLastAccessibilityScrollEventToIndex; 717 718 /** 719 * Track the item count from the last time we handled a data change. 720 */ 721 private int mLastHandledItemCount; 722 723 /** 724 * Used for smooth scrolling at a consistent rate 725 */ 726 static final Interpolator sLinearInterpolator = new LinearInterpolator(); 727 728 /** 729 * The saved state that we will be restoring from when we next sync. 730 * Kept here so that if we happen to be asked to save our state before 731 * the sync happens, we can return this existing data rather than losing 732 * it. 733 */ 734 private SavedState mPendingSync; 735 736 /** 737 * Whether the view is in the process of detaching from its window. 738 */ 739 private boolean mIsDetaching; 740 741 /** 742 * Interface definition for a callback to be invoked when the list or grid 743 * has been scrolled. 744 */ 745 public interface OnScrollListener { 746 747 /** 748 * The view is not scrolling. Note navigating the list using the trackball counts as 749 * being in the idle state since these transitions are not animated. 750 */ 751 public static int SCROLL_STATE_IDLE = 0; 752 753 /** 754 * The user is scrolling using touch, and their finger is still on the screen 755 */ 756 public static int SCROLL_STATE_TOUCH_SCROLL = 1; 757 758 /** 759 * The user had previously been scrolling using touch and had performed a fling. The 760 * animation is now coasting to a stop 761 */ 762 public static int SCROLL_STATE_FLING = 2; 763 764 /** 765 * Callback method to be invoked while the list view or grid view is being scrolled. If the 766 * view is being scrolled, this method will be called before the next frame of the scroll is 767 * rendered. In particular, it will be called before any calls to 768 * {@link Adapter#getView(int, View, ViewGroup)}. 769 * 770 * @param view The view whose scroll state is being reported 771 * 772 * @param scrollState The current scroll state. One of 773 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}. 774 */ onScrollStateChanged(AbsListView view, int scrollState)775 public void onScrollStateChanged(AbsListView view, int scrollState); 776 777 /** 778 * Callback method to be invoked when the list or grid has been scrolled. This will be 779 * called after the scroll has completed 780 * @param view The view whose scroll state is being reported 781 * @param firstVisibleItem the index of the first visible cell (ignore if 782 * visibleItemCount == 0) 783 * @param visibleItemCount the number of visible cells 784 * @param totalItemCount the number of items in the list adaptor 785 */ onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)786 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 787 int totalItemCount); 788 } 789 790 /** 791 * The top-level view of a list item can implement this interface to allow 792 * itself to modify the bounds of the selection shown for that item. 793 */ 794 public interface SelectionBoundsAdjuster { 795 /** 796 * Called to allow the list item to adjust the bounds shown for 797 * its selection. 798 * 799 * @param bounds On call, this contains the bounds the list has 800 * selected for the item (that is the bounds of the entire view). The 801 * values can be modified as desired. 802 */ adjustListItemSelectionBounds(Rect bounds)803 public void adjustListItemSelectionBounds(Rect bounds); 804 } 805 AbsListView(Context context)806 public AbsListView(Context context) { 807 super(context); 808 initAbsListView(); 809 810 mOwnerThread = Thread.currentThread(); 811 812 setVerticalScrollBarEnabled(true); 813 TypedArray a = context.obtainStyledAttributes(R.styleable.View); 814 initializeScrollbarsInternal(a); 815 a.recycle(); 816 } 817 AbsListView(Context context, AttributeSet attrs)818 public AbsListView(Context context, AttributeSet attrs) { 819 this(context, attrs, com.android.internal.R.attr.absListViewStyle); 820 } 821 AbsListView(Context context, AttributeSet attrs, int defStyleAttr)822 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) { 823 this(context, attrs, defStyleAttr, 0); 824 } 825 AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)826 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 827 super(context, attrs, defStyleAttr, defStyleRes); 828 initAbsListView(); 829 830 mOwnerThread = Thread.currentThread(); 831 832 final TypedArray a = context.obtainStyledAttributes( 833 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes); 834 835 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector); 836 if (selector != null) { 837 setSelector(selector); 838 } 839 840 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false); 841 842 setStackFromBottom(a.getBoolean( 843 R.styleable.AbsListView_stackFromBottom, false)); 844 setScrollingCacheEnabled(a.getBoolean( 845 R.styleable.AbsListView_scrollingCache, true)); 846 setTextFilterEnabled(a.getBoolean( 847 R.styleable.AbsListView_textFilterEnabled, false)); 848 setTranscriptMode(a.getInt( 849 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED)); 850 setCacheColorHint(a.getColor( 851 R.styleable.AbsListView_cacheColorHint, 0)); 852 setSmoothScrollbarEnabled(a.getBoolean( 853 R.styleable.AbsListView_smoothScrollbar, true)); 854 setChoiceMode(a.getInt( 855 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE)); 856 857 setFastScrollEnabled(a.getBoolean( 858 R.styleable.AbsListView_fastScrollEnabled, false)); 859 setFastScrollStyle(a.getResourceId( 860 R.styleable.AbsListView_fastScrollStyle, 0)); 861 setFastScrollAlwaysVisible(a.getBoolean( 862 R.styleable.AbsListView_fastScrollAlwaysVisible, false)); 863 864 a.recycle(); 865 866 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) { 867 setRevealOnFocusHint(false); 868 } 869 } 870 initAbsListView()871 private void initAbsListView() { 872 // Setting focusable in touch mode will set the focusable property to true 873 setClickable(true); 874 setFocusableInTouchMode(true); 875 setWillNotDraw(false); 876 setAlwaysDrawnWithCacheEnabled(false); 877 setScrollingCacheEnabled(true); 878 879 final ViewConfiguration configuration = ViewConfiguration.get(mContext); 880 mTouchSlop = configuration.getScaledTouchSlop(); 881 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor(); 882 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 883 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 884 mOverscrollDistance = configuration.getScaledOverscrollDistance(); 885 mOverflingDistance = configuration.getScaledOverflingDistance(); 886 887 mDensityScale = getContext().getResources().getDisplayMetrics().density; 888 } 889 890 @Override setOverScrollMode(int mode)891 public void setOverScrollMode(int mode) { 892 if (mode != OVER_SCROLL_NEVER) { 893 if (mEdgeGlowTop == null) { 894 Context context = getContext(); 895 mEdgeGlowTop = new EdgeEffect(context); 896 mEdgeGlowBottom = new EdgeEffect(context); 897 } 898 } else { 899 mEdgeGlowTop = null; 900 mEdgeGlowBottom = null; 901 } 902 super.setOverScrollMode(mode); 903 } 904 905 /** 906 * {@inheritDoc} 907 */ 908 @Override setAdapter(ListAdapter adapter)909 public void setAdapter(ListAdapter adapter) { 910 if (adapter != null) { 911 mAdapterHasStableIds = mAdapter.hasStableIds(); 912 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds && 913 mCheckedIdStates == null) { 914 mCheckedIdStates = new LongSparseArray<Integer>(); 915 } 916 } 917 clearChoices(); 918 } 919 920 /** 921 * Returns the number of items currently selected. This will only be valid 922 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). 923 * 924 * <p>To determine the specific items that are currently selected, use one of 925 * the <code>getChecked*</code> methods. 926 * 927 * @return The number of items currently selected 928 * 929 * @see #getCheckedItemPosition() 930 * @see #getCheckedItemPositions() 931 * @see #getCheckedItemIds() 932 */ getCheckedItemCount()933 public int getCheckedItemCount() { 934 return mCheckedItemCount; 935 } 936 937 /** 938 * Returns the checked state of the specified position. The result is only 939 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} 940 * or {@link #CHOICE_MODE_MULTIPLE}. 941 * 942 * @param position The item whose checked state to return 943 * @return The item's checked state or <code>false</code> if choice mode 944 * is invalid 945 * 946 * @see #setChoiceMode(int) 947 */ isItemChecked(int position)948 public boolean isItemChecked(int position) { 949 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { 950 return mCheckStates.get(position); 951 } 952 953 return false; 954 } 955 956 /** 957 * Returns the currently checked item. The result is only valid if the choice 958 * mode has been set to {@link #CHOICE_MODE_SINGLE}. 959 * 960 * @return The position of the currently checked item or 961 * {@link #INVALID_POSITION} if nothing is selected 962 * 963 * @see #setChoiceMode(int) 964 */ getCheckedItemPosition()965 public int getCheckedItemPosition() { 966 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) { 967 return mCheckStates.keyAt(0); 968 } 969 970 return INVALID_POSITION; 971 } 972 973 /** 974 * Returns the set of checked items in the list. The result is only valid if 975 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. 976 * 977 * @return A SparseBooleanArray which will return true for each call to 978 * get(int position) where position is a checked position in the 979 * list and false otherwise, or <code>null</code> if the choice 980 * mode is set to {@link #CHOICE_MODE_NONE}. 981 */ getCheckedItemPositions()982 public SparseBooleanArray getCheckedItemPositions() { 983 if (mChoiceMode != CHOICE_MODE_NONE) { 984 return mCheckStates; 985 } 986 return null; 987 } 988 989 /** 990 * Returns the set of checked items ids. The result is only valid if the 991 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter 992 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) 993 * 994 * @return A new array which contains the id of each checked item in the 995 * list. 996 */ getCheckedItemIds()997 public long[] getCheckedItemIds() { 998 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) { 999 return new long[0]; 1000 } 1001 1002 final LongSparseArray<Integer> idStates = mCheckedIdStates; 1003 final int count = idStates.size(); 1004 final long[] ids = new long[count]; 1005 1006 for (int i = 0; i < count; i++) { 1007 ids[i] = idStates.keyAt(i); 1008 } 1009 1010 return ids; 1011 } 1012 1013 /** 1014 * Clear any choices previously set 1015 */ clearChoices()1016 public void clearChoices() { 1017 if (mCheckStates != null) { 1018 mCheckStates.clear(); 1019 } 1020 if (mCheckedIdStates != null) { 1021 mCheckedIdStates.clear(); 1022 } 1023 mCheckedItemCount = 0; 1024 } 1025 1026 /** 1027 * Sets the checked state of the specified position. The is only valid if 1028 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or 1029 * {@link #CHOICE_MODE_MULTIPLE}. 1030 * 1031 * @param position The item whose checked state is to be checked 1032 * @param value The new checked state for the item 1033 */ setItemChecked(int position, boolean value)1034 public void setItemChecked(int position, boolean value) { 1035 if (mChoiceMode == CHOICE_MODE_NONE) { 1036 return; 1037 } 1038 1039 // Start selection mode if needed. We don't need to if we're unchecking something. 1040 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { 1041 if (mMultiChoiceModeCallback == null || 1042 !mMultiChoiceModeCallback.hasWrappedCallback()) { 1043 throw new IllegalStateException("AbsListView: attempted to start selection mode " + 1044 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " + 1045 "supplied. Call setMultiChoiceModeListener to set a callback."); 1046 } 1047 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); 1048 } 1049 1050 final boolean itemCheckChanged; 1051 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { 1052 boolean oldValue = mCheckStates.get(position); 1053 mCheckStates.put(position, value); 1054 if (mCheckedIdStates != null && mAdapter.hasStableIds()) { 1055 if (value) { 1056 mCheckedIdStates.put(mAdapter.getItemId(position), position); 1057 } else { 1058 mCheckedIdStates.delete(mAdapter.getItemId(position)); 1059 } 1060 } 1061 itemCheckChanged = oldValue != value; 1062 if (itemCheckChanged) { 1063 if (value) { 1064 mCheckedItemCount++; 1065 } else { 1066 mCheckedItemCount--; 1067 } 1068 } 1069 if (mChoiceActionMode != null) { 1070 final long id = mAdapter.getItemId(position); 1071 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, 1072 position, id, value); 1073 } 1074 } else { 1075 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds(); 1076 // Clear all values if we're checking something, or unchecking the currently 1077 // selected item 1078 itemCheckChanged = isItemChecked(position) != value; 1079 if (value || isItemChecked(position)) { 1080 mCheckStates.clear(); 1081 if (updateIds) { 1082 mCheckedIdStates.clear(); 1083 } 1084 } 1085 // this may end up selecting the value we just cleared but this way 1086 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on 1087 if (value) { 1088 mCheckStates.put(position, true); 1089 if (updateIds) { 1090 mCheckedIdStates.put(mAdapter.getItemId(position), position); 1091 } 1092 mCheckedItemCount = 1; 1093 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { 1094 mCheckedItemCount = 0; 1095 } 1096 } 1097 1098 // Do not generate a data change while we are in the layout phase or data has not changed 1099 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) { 1100 mDataChanged = true; 1101 rememberSyncState(); 1102 requestLayout(); 1103 } 1104 } 1105 1106 @Override performItemClick(View view, int position, long id)1107 public boolean performItemClick(View view, int position, long id) { 1108 boolean handled = false; 1109 boolean dispatchItemClick = true; 1110 1111 if (mChoiceMode != CHOICE_MODE_NONE) { 1112 handled = true; 1113 boolean checkedStateChanged = false; 1114 1115 if (mChoiceMode == CHOICE_MODE_MULTIPLE || 1116 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) { 1117 boolean checked = !mCheckStates.get(position, false); 1118 mCheckStates.put(position, checked); 1119 if (mCheckedIdStates != null && mAdapter.hasStableIds()) { 1120 if (checked) { 1121 mCheckedIdStates.put(mAdapter.getItemId(position), position); 1122 } else { 1123 mCheckedIdStates.delete(mAdapter.getItemId(position)); 1124 } 1125 } 1126 if (checked) { 1127 mCheckedItemCount++; 1128 } else { 1129 mCheckedItemCount--; 1130 } 1131 if (mChoiceActionMode != null) { 1132 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, 1133 position, id, checked); 1134 dispatchItemClick = false; 1135 } 1136 checkedStateChanged = true; 1137 } else if (mChoiceMode == CHOICE_MODE_SINGLE) { 1138 boolean checked = !mCheckStates.get(position, false); 1139 if (checked) { 1140 mCheckStates.clear(); 1141 mCheckStates.put(position, true); 1142 if (mCheckedIdStates != null && mAdapter.hasStableIds()) { 1143 mCheckedIdStates.clear(); 1144 mCheckedIdStates.put(mAdapter.getItemId(position), position); 1145 } 1146 mCheckedItemCount = 1; 1147 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { 1148 mCheckedItemCount = 0; 1149 } 1150 checkedStateChanged = true; 1151 } 1152 1153 if (checkedStateChanged) { 1154 updateOnScreenCheckedViews(); 1155 } 1156 } 1157 1158 if (dispatchItemClick) { 1159 handled |= super.performItemClick(view, position, id); 1160 } 1161 1162 return handled; 1163 } 1164 1165 /** 1166 * Perform a quick, in-place update of the checked or activated state 1167 * on all visible item views. This should only be called when a valid 1168 * choice mode is active. 1169 */ updateOnScreenCheckedViews()1170 private void updateOnScreenCheckedViews() { 1171 final int firstPos = mFirstPosition; 1172 final int count = getChildCount(); 1173 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion 1174 >= android.os.Build.VERSION_CODES.HONEYCOMB; 1175 for (int i = 0; i < count; i++) { 1176 final View child = getChildAt(i); 1177 final int position = firstPos + i; 1178 1179 if (child instanceof Checkable) { 1180 ((Checkable) child).setChecked(mCheckStates.get(position)); 1181 } else if (useActivated) { 1182 child.setActivated(mCheckStates.get(position)); 1183 } 1184 } 1185 } 1186 1187 /** 1188 * @see #setChoiceMode(int) 1189 * 1190 * @return The current choice mode 1191 */ getChoiceMode()1192 public int getChoiceMode() { 1193 return mChoiceMode; 1194 } 1195 1196 /** 1197 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior 1198 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the 1199 * List allows up to one item to be in a chosen state. By setting the choiceMode to 1200 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. 1201 * 1202 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or 1203 * {@link #CHOICE_MODE_MULTIPLE} 1204 */ setChoiceMode(int choiceMode)1205 public void setChoiceMode(int choiceMode) { 1206 mChoiceMode = choiceMode; 1207 if (mChoiceActionMode != null) { 1208 mChoiceActionMode.finish(); 1209 mChoiceActionMode = null; 1210 } 1211 if (mChoiceMode != CHOICE_MODE_NONE) { 1212 if (mCheckStates == null) { 1213 mCheckStates = new SparseBooleanArray(0); 1214 } 1215 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { 1216 mCheckedIdStates = new LongSparseArray<Integer>(0); 1217 } 1218 // Modal multi-choice mode only has choices when the mode is active. Clear them. 1219 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { 1220 clearChoices(); 1221 setLongClickable(true); 1222 } 1223 } 1224 } 1225 1226 /** 1227 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the 1228 * selection {@link ActionMode}. Only used when the choice mode is set to 1229 * {@link #CHOICE_MODE_MULTIPLE_MODAL}. 1230 * 1231 * @param listener Listener that will manage the selection mode 1232 * 1233 * @see #setChoiceMode(int) 1234 */ setMultiChoiceModeListener(MultiChoiceModeListener listener)1235 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) { 1236 if (mMultiChoiceModeCallback == null) { 1237 mMultiChoiceModeCallback = new MultiChoiceModeWrapper(); 1238 } 1239 mMultiChoiceModeCallback.setWrapped(listener); 1240 } 1241 1242 /** 1243 * @return true if all list content currently fits within the view boundaries 1244 */ contentFits()1245 private boolean contentFits() { 1246 final int childCount = getChildCount(); 1247 if (childCount == 0) return true; 1248 if (childCount != mItemCount) return false; 1249 1250 return getChildAt(0).getTop() >= mListPadding.top && 1251 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom; 1252 } 1253 1254 /** 1255 * Specifies whether fast scrolling is enabled or disabled. 1256 * <p> 1257 * When fast scrolling is enabled, the user can quickly scroll through lists 1258 * by dragging the fast scroll thumb. 1259 * <p> 1260 * If the adapter backing this list implements {@link SectionIndexer}, the 1261 * fast scroller will display section header previews as the user scrolls. 1262 * Additionally, the user will be able to quickly jump between sections by 1263 * tapping along the length of the scroll bar. 1264 * 1265 * @see SectionIndexer 1266 * @see #isFastScrollEnabled() 1267 * @param enabled true to enable fast scrolling, false otherwise 1268 */ setFastScrollEnabled(final boolean enabled)1269 public void setFastScrollEnabled(final boolean enabled) { 1270 if (mFastScrollEnabled != enabled) { 1271 mFastScrollEnabled = enabled; 1272 1273 if (isOwnerThread()) { 1274 setFastScrollerEnabledUiThread(enabled); 1275 } else { 1276 post(new Runnable() { 1277 @Override 1278 public void run() { 1279 setFastScrollerEnabledUiThread(enabled); 1280 } 1281 }); 1282 } 1283 } 1284 } 1285 setFastScrollerEnabledUiThread(boolean enabled)1286 private void setFastScrollerEnabledUiThread(boolean enabled) { 1287 if (mFastScroll != null) { 1288 mFastScroll.setEnabled(enabled); 1289 } else if (enabled) { 1290 mFastScroll = new FastScroller(this, mFastScrollStyle); 1291 mFastScroll.setEnabled(true); 1292 } 1293 1294 resolvePadding(); 1295 1296 if (mFastScroll != null) { 1297 mFastScroll.updateLayout(); 1298 } 1299 } 1300 1301 /** 1302 * Specifies the style of the fast scroller decorations. 1303 * 1304 * @param styleResId style resource containing fast scroller properties 1305 * @see android.R.styleable#FastScroll 1306 */ setFastScrollStyle(int styleResId)1307 public void setFastScrollStyle(int styleResId) { 1308 if (mFastScroll == null) { 1309 mFastScrollStyle = styleResId; 1310 } else { 1311 mFastScroll.setStyle(styleResId); 1312 } 1313 } 1314 1315 /** 1316 * Set whether or not the fast scroller should always be shown in place of 1317 * the standard scroll bars. This will enable fast scrolling if it is not 1318 * already enabled. 1319 * <p> 1320 * Fast scrollers shown in this way will not fade out and will be a 1321 * permanent fixture within the list. This is best combined with an inset 1322 * scroll bar style to ensure the scroll bar does not overlap content. 1323 * 1324 * @param alwaysShow true if the fast scroller should always be displayed, 1325 * false otherwise 1326 * @see #setScrollBarStyle(int) 1327 * @see #setFastScrollEnabled(boolean) 1328 */ setFastScrollAlwaysVisible(final boolean alwaysShow)1329 public void setFastScrollAlwaysVisible(final boolean alwaysShow) { 1330 if (mFastScrollAlwaysVisible != alwaysShow) { 1331 if (alwaysShow && !mFastScrollEnabled) { 1332 setFastScrollEnabled(true); 1333 } 1334 1335 mFastScrollAlwaysVisible = alwaysShow; 1336 1337 if (isOwnerThread()) { 1338 setFastScrollerAlwaysVisibleUiThread(alwaysShow); 1339 } else { 1340 post(new Runnable() { 1341 @Override 1342 public void run() { 1343 setFastScrollerAlwaysVisibleUiThread(alwaysShow); 1344 } 1345 }); 1346 } 1347 } 1348 } 1349 setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow)1350 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) { 1351 if (mFastScroll != null) { 1352 mFastScroll.setAlwaysShow(alwaysShow); 1353 } 1354 } 1355 1356 /** 1357 * @return whether the current thread is the one that created the view 1358 */ isOwnerThread()1359 private boolean isOwnerThread() { 1360 return mOwnerThread == Thread.currentThread(); 1361 } 1362 1363 /** 1364 * Returns true if the fast scroller is set to always show on this view. 1365 * 1366 * @return true if the fast scroller will always show 1367 * @see #setFastScrollAlwaysVisible(boolean) 1368 */ isFastScrollAlwaysVisible()1369 public boolean isFastScrollAlwaysVisible() { 1370 if (mFastScroll == null) { 1371 return mFastScrollEnabled && mFastScrollAlwaysVisible; 1372 } else { 1373 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled(); 1374 } 1375 } 1376 1377 @Override getVerticalScrollbarWidth()1378 public int getVerticalScrollbarWidth() { 1379 if (mFastScroll != null && mFastScroll.isEnabled()) { 1380 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth()); 1381 } 1382 return super.getVerticalScrollbarWidth(); 1383 } 1384 1385 /** 1386 * Returns true if the fast scroller is enabled. 1387 * 1388 * @see #setFastScrollEnabled(boolean) 1389 * @return true if fast scroll is enabled, false otherwise 1390 */ 1391 @ViewDebug.ExportedProperty isFastScrollEnabled()1392 public boolean isFastScrollEnabled() { 1393 if (mFastScroll == null) { 1394 return mFastScrollEnabled; 1395 } else { 1396 return mFastScroll.isEnabled(); 1397 } 1398 } 1399 1400 @Override setVerticalScrollbarPosition(int position)1401 public void setVerticalScrollbarPosition(int position) { 1402 super.setVerticalScrollbarPosition(position); 1403 if (mFastScroll != null) { 1404 mFastScroll.setScrollbarPosition(position); 1405 } 1406 } 1407 1408 @Override setScrollBarStyle(int style)1409 public void setScrollBarStyle(int style) { 1410 super.setScrollBarStyle(style); 1411 if (mFastScroll != null) { 1412 mFastScroll.setScrollBarStyle(style); 1413 } 1414 } 1415 1416 /** 1417 * If fast scroll is enabled, then don't draw the vertical scrollbar. 1418 * @hide 1419 */ 1420 @Override isVerticalScrollBarHidden()1421 protected boolean isVerticalScrollBarHidden() { 1422 return isFastScrollEnabled(); 1423 } 1424 1425 /** 1426 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb 1427 * is computed based on the number of visible pixels in the visible items. This 1428 * however assumes that all list items have the same height. If you use a list in 1429 * which items have different heights, the scrollbar will change appearance as the 1430 * user scrolls through the list. To avoid this issue, you need to disable this 1431 * property. 1432 * 1433 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb 1434 * is based solely on the number of items in the adapter and the position of the 1435 * visible items inside the adapter. This provides a stable scrollbar as the user 1436 * navigates through a list of items with varying heights. 1437 * 1438 * @param enabled Whether or not to enable smooth scrollbar. 1439 * 1440 * @see #setSmoothScrollbarEnabled(boolean) 1441 * @attr ref android.R.styleable#AbsListView_smoothScrollbar 1442 */ setSmoothScrollbarEnabled(boolean enabled)1443 public void setSmoothScrollbarEnabled(boolean enabled) { 1444 mSmoothScrollbarEnabled = enabled; 1445 } 1446 1447 /** 1448 * Returns the current state of the fast scroll feature. 1449 * 1450 * @return True if smooth scrollbar is enabled is enabled, false otherwise. 1451 * 1452 * @see #setSmoothScrollbarEnabled(boolean) 1453 */ 1454 @ViewDebug.ExportedProperty isSmoothScrollbarEnabled()1455 public boolean isSmoothScrollbarEnabled() { 1456 return mSmoothScrollbarEnabled; 1457 } 1458 1459 /** 1460 * Set the listener that will receive notifications every time the list scrolls. 1461 * 1462 * @param l the scroll listener 1463 */ setOnScrollListener(OnScrollListener l)1464 public void setOnScrollListener(OnScrollListener l) { 1465 mOnScrollListener = l; 1466 invokeOnItemScrollListener(); 1467 } 1468 1469 /** 1470 * Notify our scroll listener (if there is one) of a change in scroll state 1471 */ invokeOnItemScrollListener()1472 void invokeOnItemScrollListener() { 1473 if (mFastScroll != null) { 1474 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount); 1475 } 1476 if (mOnScrollListener != null) { 1477 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount); 1478 } 1479 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. 1480 } 1481 1482 /** @hide */ 1483 @Override sendAccessibilityEventUnchecked(AccessibilityEvent event)1484 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { 1485 // Since this class calls onScrollChanged even if the mFirstPosition and the 1486 // child count have not changed we will avoid sending duplicate accessibility 1487 // events. 1488 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 1489 final int firstVisiblePosition = getFirstVisiblePosition(); 1490 final int lastVisiblePosition = getLastVisiblePosition(); 1491 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition 1492 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { 1493 return; 1494 } else { 1495 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; 1496 mLastAccessibilityScrollEventToIndex = lastVisiblePosition; 1497 } 1498 } 1499 super.sendAccessibilityEventUnchecked(event); 1500 } 1501 1502 @Override getAccessibilityClassName()1503 public CharSequence getAccessibilityClassName() { 1504 return AbsListView.class.getName(); 1505 } 1506 1507 /** @hide */ 1508 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)1509 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1510 super.onInitializeAccessibilityNodeInfoInternal(info); 1511 if (isEnabled()) { 1512 if (canScrollUp()) { 1513 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD); 1514 info.addAction(AccessibilityAction.ACTION_SCROLL_UP); 1515 info.setScrollable(true); 1516 } 1517 if (canScrollDown()) { 1518 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD); 1519 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN); 1520 info.setScrollable(true); 1521 } 1522 } 1523 1524 info.removeAction(AccessibilityAction.ACTION_CLICK); 1525 info.setClickable(false); 1526 } 1527 getSelectionModeForAccessibility()1528 int getSelectionModeForAccessibility() { 1529 final int choiceMode = getChoiceMode(); 1530 switch (choiceMode) { 1531 case CHOICE_MODE_NONE: 1532 return CollectionInfo.SELECTION_MODE_NONE; 1533 case CHOICE_MODE_SINGLE: 1534 return CollectionInfo.SELECTION_MODE_SINGLE; 1535 case CHOICE_MODE_MULTIPLE: 1536 case CHOICE_MODE_MULTIPLE_MODAL: 1537 return CollectionInfo.SELECTION_MODE_MULTIPLE; 1538 default: 1539 return CollectionInfo.SELECTION_MODE_NONE; 1540 } 1541 } 1542 1543 /** @hide */ 1544 @Override performAccessibilityActionInternal(int action, Bundle arguments)1545 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 1546 if (super.performAccessibilityActionInternal(action, arguments)) { 1547 return true; 1548 } 1549 switch (action) { 1550 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 1551 case R.id.accessibilityActionScrollDown: { 1552 if (isEnabled() && canScrollDown()) { 1553 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; 1554 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION); 1555 return true; 1556 } 1557 } return false; 1558 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 1559 case R.id.accessibilityActionScrollUp: { 1560 if (isEnabled() && canScrollUp()) { 1561 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; 1562 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION); 1563 return true; 1564 } 1565 } return false; 1566 } 1567 return false; 1568 } 1569 1570 /** @hide */ 1571 @Override findViewByAccessibilityIdTraversal(int accessibilityId)1572 public View findViewByAccessibilityIdTraversal(int accessibilityId) { 1573 if (accessibilityId == getAccessibilityViewId()) { 1574 return this; 1575 } 1576 return super.findViewByAccessibilityIdTraversal(accessibilityId); 1577 } 1578 1579 /** 1580 * Indicates whether the children's drawing cache is used during a scroll. 1581 * By default, the drawing cache is enabled but this will consume more memory. 1582 * 1583 * @return true if the scrolling cache is enabled, false otherwise 1584 * 1585 * @see #setScrollingCacheEnabled(boolean) 1586 * @see View#setDrawingCacheEnabled(boolean) 1587 */ 1588 @ViewDebug.ExportedProperty isScrollingCacheEnabled()1589 public boolean isScrollingCacheEnabled() { 1590 return mScrollingCacheEnabled; 1591 } 1592 1593 /** 1594 * Enables or disables the children's drawing cache during a scroll. 1595 * By default, the drawing cache is enabled but this will use more memory. 1596 * 1597 * When the scrolling cache is enabled, the caches are kept after the 1598 * first scrolling. You can manually clear the cache by calling 1599 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}. 1600 * 1601 * @param enabled true to enable the scroll cache, false otherwise 1602 * 1603 * @see #isScrollingCacheEnabled() 1604 * @see View#setDrawingCacheEnabled(boolean) 1605 */ setScrollingCacheEnabled(boolean enabled)1606 public void setScrollingCacheEnabled(boolean enabled) { 1607 if (mScrollingCacheEnabled && !enabled) { 1608 clearScrollingCache(); 1609 } 1610 mScrollingCacheEnabled = enabled; 1611 } 1612 1613 /** 1614 * Enables or disables the type filter window. If enabled, typing when 1615 * this view has focus will filter the children to match the users input. 1616 * Note that the {@link Adapter} used by this view must implement the 1617 * {@link Filterable} interface. 1618 * 1619 * @param textFilterEnabled true to enable type filtering, false otherwise 1620 * 1621 * @see Filterable 1622 */ setTextFilterEnabled(boolean textFilterEnabled)1623 public void setTextFilterEnabled(boolean textFilterEnabled) { 1624 mTextFilterEnabled = textFilterEnabled; 1625 } 1626 1627 /** 1628 * Indicates whether type filtering is enabled for this view 1629 * 1630 * @return true if type filtering is enabled, false otherwise 1631 * 1632 * @see #setTextFilterEnabled(boolean) 1633 * @see Filterable 1634 */ 1635 @ViewDebug.ExportedProperty isTextFilterEnabled()1636 public boolean isTextFilterEnabled() { 1637 return mTextFilterEnabled; 1638 } 1639 1640 @Override getFocusedRect(Rect r)1641 public void getFocusedRect(Rect r) { 1642 View view = getSelectedView(); 1643 if (view != null && view.getParent() == this) { 1644 // the focused rectangle of the selected view offset into the 1645 // coordinate space of this view. 1646 view.getFocusedRect(r); 1647 offsetDescendantRectToMyCoords(view, r); 1648 } else { 1649 // otherwise, just the norm 1650 super.getFocusedRect(r); 1651 } 1652 } 1653 useDefaultSelector()1654 private void useDefaultSelector() { 1655 setSelector(getContext().getDrawable( 1656 com.android.internal.R.drawable.list_selector_background)); 1657 } 1658 1659 /** 1660 * Indicates whether the content of this view is pinned to, or stacked from, 1661 * the bottom edge. 1662 * 1663 * @return true if the content is stacked from the bottom edge, false otherwise 1664 */ 1665 @ViewDebug.ExportedProperty isStackFromBottom()1666 public boolean isStackFromBottom() { 1667 return mStackFromBottom; 1668 } 1669 1670 /** 1671 * When stack from bottom is set to true, the list fills its content starting from 1672 * the bottom of the view. 1673 * 1674 * @param stackFromBottom true to pin the view's content to the bottom edge, 1675 * false to pin the view's content to the top edge 1676 */ setStackFromBottom(boolean stackFromBottom)1677 public void setStackFromBottom(boolean stackFromBottom) { 1678 if (mStackFromBottom != stackFromBottom) { 1679 mStackFromBottom = stackFromBottom; 1680 requestLayoutIfNecessary(); 1681 } 1682 } 1683 requestLayoutIfNecessary()1684 void requestLayoutIfNecessary() { 1685 if (getChildCount() > 0) { 1686 resetList(); 1687 requestLayout(); 1688 invalidate(); 1689 } 1690 } 1691 1692 static class SavedState extends BaseSavedState { 1693 long selectedId; 1694 long firstId; 1695 int viewTop; 1696 int position; 1697 int height; 1698 String filter; 1699 boolean inActionMode; 1700 int checkedItemCount; 1701 SparseBooleanArray checkState; 1702 LongSparseArray<Integer> checkIdState; 1703 1704 /** 1705 * Constructor called from {@link AbsListView#onSaveInstanceState()} 1706 */ SavedState(Parcelable superState)1707 SavedState(Parcelable superState) { 1708 super(superState); 1709 } 1710 1711 /** 1712 * Constructor called from {@link #CREATOR} 1713 */ SavedState(Parcel in)1714 private SavedState(Parcel in) { 1715 super(in); 1716 selectedId = in.readLong(); 1717 firstId = in.readLong(); 1718 viewTop = in.readInt(); 1719 position = in.readInt(); 1720 height = in.readInt(); 1721 filter = in.readString(); 1722 inActionMode = in.readByte() != 0; 1723 checkedItemCount = in.readInt(); 1724 checkState = in.readSparseBooleanArray(); 1725 final int N = in.readInt(); 1726 if (N > 0) { 1727 checkIdState = new LongSparseArray<Integer>(); 1728 for (int i=0; i<N; i++) { 1729 final long key = in.readLong(); 1730 final int value = in.readInt(); 1731 checkIdState.put(key, value); 1732 } 1733 } 1734 } 1735 1736 @Override writeToParcel(Parcel out, int flags)1737 public void writeToParcel(Parcel out, int flags) { 1738 super.writeToParcel(out, flags); 1739 out.writeLong(selectedId); 1740 out.writeLong(firstId); 1741 out.writeInt(viewTop); 1742 out.writeInt(position); 1743 out.writeInt(height); 1744 out.writeString(filter); 1745 out.writeByte((byte) (inActionMode ? 1 : 0)); 1746 out.writeInt(checkedItemCount); 1747 out.writeSparseBooleanArray(checkState); 1748 final int N = checkIdState != null ? checkIdState.size() : 0; 1749 out.writeInt(N); 1750 for (int i=0; i<N; i++) { 1751 out.writeLong(checkIdState.keyAt(i)); 1752 out.writeInt(checkIdState.valueAt(i)); 1753 } 1754 } 1755 1756 @Override toString()1757 public String toString() { 1758 return "AbsListView.SavedState{" 1759 + Integer.toHexString(System.identityHashCode(this)) 1760 + " selectedId=" + selectedId 1761 + " firstId=" + firstId 1762 + " viewTop=" + viewTop 1763 + " position=" + position 1764 + " height=" + height 1765 + " filter=" + filter 1766 + " checkState=" + checkState + "}"; 1767 } 1768 1769 public static final Parcelable.Creator<SavedState> CREATOR 1770 = new Parcelable.Creator<SavedState>() { 1771 @Override 1772 public SavedState createFromParcel(Parcel in) { 1773 return new SavedState(in); 1774 } 1775 1776 @Override 1777 public SavedState[] newArray(int size) { 1778 return new SavedState[size]; 1779 } 1780 }; 1781 } 1782 1783 @Override onSaveInstanceState()1784 public Parcelable onSaveInstanceState() { 1785 /* 1786 * This doesn't really make sense as the place to dismiss the 1787 * popups, but there don't seem to be any other useful hooks 1788 * that happen early enough to keep from getting complaints 1789 * about having leaked the window. 1790 */ 1791 dismissPopup(); 1792 1793 Parcelable superState = super.onSaveInstanceState(); 1794 1795 SavedState ss = new SavedState(superState); 1796 1797 if (mPendingSync != null) { 1798 // Just keep what we last restored. 1799 ss.selectedId = mPendingSync.selectedId; 1800 ss.firstId = mPendingSync.firstId; 1801 ss.viewTop = mPendingSync.viewTop; 1802 ss.position = mPendingSync.position; 1803 ss.height = mPendingSync.height; 1804 ss.filter = mPendingSync.filter; 1805 ss.inActionMode = mPendingSync.inActionMode; 1806 ss.checkedItemCount = mPendingSync.checkedItemCount; 1807 ss.checkState = mPendingSync.checkState; 1808 ss.checkIdState = mPendingSync.checkIdState; 1809 return ss; 1810 } 1811 1812 boolean haveChildren = getChildCount() > 0 && mItemCount > 0; 1813 long selectedId = getSelectedItemId(); 1814 ss.selectedId = selectedId; 1815 ss.height = getHeight(); 1816 1817 if (selectedId >= 0) { 1818 // Remember the selection 1819 ss.viewTop = mSelectedTop; 1820 ss.position = getSelectedItemPosition(); 1821 ss.firstId = INVALID_POSITION; 1822 } else { 1823 if (haveChildren && mFirstPosition > 0) { 1824 // Remember the position of the first child. 1825 // We only do this if we are not currently at the top of 1826 // the list, for two reasons: 1827 // (1) The list may be in the process of becoming empty, in 1828 // which case mItemCount may not be 0, but if we try to 1829 // ask for any information about position 0 we will crash. 1830 // (2) Being "at the top" seems like a special case, anyway, 1831 // and the user wouldn't expect to end up somewhere else when 1832 // they revisit the list even if its content has changed. 1833 View v = getChildAt(0); 1834 ss.viewTop = v.getTop(); 1835 int firstPos = mFirstPosition; 1836 if (firstPos >= mItemCount) { 1837 firstPos = mItemCount - 1; 1838 } 1839 ss.position = firstPos; 1840 ss.firstId = mAdapter.getItemId(firstPos); 1841 } else { 1842 ss.viewTop = 0; 1843 ss.firstId = INVALID_POSITION; 1844 ss.position = 0; 1845 } 1846 } 1847 1848 ss.filter = null; 1849 if (mFiltered) { 1850 final EditText textFilter = mTextFilter; 1851 if (textFilter != null) { 1852 Editable filterText = textFilter.getText(); 1853 if (filterText != null) { 1854 ss.filter = filterText.toString(); 1855 } 1856 } 1857 } 1858 1859 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null; 1860 1861 if (mCheckStates != null) { 1862 ss.checkState = mCheckStates.clone(); 1863 } 1864 if (mCheckedIdStates != null) { 1865 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>(); 1866 final int count = mCheckedIdStates.size(); 1867 for (int i = 0; i < count; i++) { 1868 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i)); 1869 } 1870 ss.checkIdState = idState; 1871 } 1872 ss.checkedItemCount = mCheckedItemCount; 1873 1874 if (mRemoteAdapter != null) { 1875 mRemoteAdapter.saveRemoteViewsCache(); 1876 } 1877 1878 return ss; 1879 } 1880 1881 @Override onRestoreInstanceState(Parcelable state)1882 public void onRestoreInstanceState(Parcelable state) { 1883 SavedState ss = (SavedState) state; 1884 1885 super.onRestoreInstanceState(ss.getSuperState()); 1886 mDataChanged = true; 1887 1888 mSyncHeight = ss.height; 1889 1890 if (ss.selectedId >= 0) { 1891 mNeedSync = true; 1892 mPendingSync = ss; 1893 mSyncRowId = ss.selectedId; 1894 mSyncPosition = ss.position; 1895 mSpecificTop = ss.viewTop; 1896 mSyncMode = SYNC_SELECTED_POSITION; 1897 } else if (ss.firstId >= 0) { 1898 setSelectedPositionInt(INVALID_POSITION); 1899 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync 1900 setNextSelectedPositionInt(INVALID_POSITION); 1901 mSelectorPosition = INVALID_POSITION; 1902 mNeedSync = true; 1903 mPendingSync = ss; 1904 mSyncRowId = ss.firstId; 1905 mSyncPosition = ss.position; 1906 mSpecificTop = ss.viewTop; 1907 mSyncMode = SYNC_FIRST_POSITION; 1908 } 1909 1910 setFilterText(ss.filter); 1911 1912 if (ss.checkState != null) { 1913 mCheckStates = ss.checkState; 1914 } 1915 1916 if (ss.checkIdState != null) { 1917 mCheckedIdStates = ss.checkIdState; 1918 } 1919 1920 mCheckedItemCount = ss.checkedItemCount; 1921 1922 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && 1923 mMultiChoiceModeCallback != null) { 1924 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); 1925 } 1926 1927 requestLayout(); 1928 } 1929 acceptFilter()1930 private boolean acceptFilter() { 1931 return mTextFilterEnabled && getAdapter() instanceof Filterable && 1932 ((Filterable) getAdapter()).getFilter() != null; 1933 } 1934 1935 /** 1936 * Sets the initial value for the text filter. 1937 * @param filterText The text to use for the filter. 1938 * 1939 * @see #setTextFilterEnabled 1940 */ setFilterText(String filterText)1941 public void setFilterText(String filterText) { 1942 // TODO: Should we check for acceptFilter()? 1943 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) { 1944 createTextFilter(false); 1945 // This is going to call our listener onTextChanged, but we might not 1946 // be ready to bring up a window yet 1947 mTextFilter.setText(filterText); 1948 mTextFilter.setSelection(filterText.length()); 1949 if (mAdapter instanceof Filterable) { 1950 // if mPopup is non-null, then onTextChanged will do the filtering 1951 if (mPopup == null) { 1952 Filter f = ((Filterable) mAdapter).getFilter(); 1953 f.filter(filterText); 1954 } 1955 // Set filtered to true so we will display the filter window when our main 1956 // window is ready 1957 mFiltered = true; 1958 mDataSetObserver.clearSavedState(); 1959 } 1960 } 1961 } 1962 1963 /** 1964 * Returns the list's text filter, if available. 1965 * @return the list's text filter or null if filtering isn't enabled 1966 */ getTextFilter()1967 public CharSequence getTextFilter() { 1968 if (mTextFilterEnabled && mTextFilter != null) { 1969 return mTextFilter.getText(); 1970 } 1971 return null; 1972 } 1973 1974 @Override onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)1975 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 1976 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 1977 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) { 1978 if (!isAttachedToWindow() && mAdapter != null) { 1979 // Data may have changed while we were detached and it's valid 1980 // to change focus while detached. Refresh so we don't die. 1981 mDataChanged = true; 1982 mOldItemCount = mItemCount; 1983 mItemCount = mAdapter.getCount(); 1984 } 1985 resurrectSelection(); 1986 } 1987 } 1988 1989 @Override requestLayout()1990 public void requestLayout() { 1991 if (!mBlockLayoutRequests && !mInLayout) { 1992 super.requestLayout(); 1993 } 1994 } 1995 1996 /** 1997 * The list is empty. Clear everything out. 1998 */ resetList()1999 void resetList() { 2000 removeAllViewsInLayout(); 2001 mFirstPosition = 0; 2002 mDataChanged = false; 2003 mPositionScrollAfterLayout = null; 2004 mNeedSync = false; 2005 mPendingSync = null; 2006 mOldSelectedPosition = INVALID_POSITION; 2007 mOldSelectedRowId = INVALID_ROW_ID; 2008 setSelectedPositionInt(INVALID_POSITION); 2009 setNextSelectedPositionInt(INVALID_POSITION); 2010 mSelectedTop = 0; 2011 mSelectorPosition = INVALID_POSITION; 2012 mSelectorRect.setEmpty(); 2013 invalidate(); 2014 } 2015 2016 @Override computeVerticalScrollExtent()2017 protected int computeVerticalScrollExtent() { 2018 final int count = getChildCount(); 2019 if (count > 0) { 2020 if (mSmoothScrollbarEnabled) { 2021 int extent = count * 100; 2022 2023 View view = getChildAt(0); 2024 final int top = view.getTop(); 2025 int height = view.getHeight(); 2026 if (height > 0) { 2027 extent += (top * 100) / height; 2028 } 2029 2030 view = getChildAt(count - 1); 2031 final int bottom = view.getBottom(); 2032 height = view.getHeight(); 2033 if (height > 0) { 2034 extent -= ((bottom - getHeight()) * 100) / height; 2035 } 2036 2037 return extent; 2038 } else { 2039 return 1; 2040 } 2041 } 2042 return 0; 2043 } 2044 2045 @Override computeVerticalScrollOffset()2046 protected int computeVerticalScrollOffset() { 2047 final int firstPosition = mFirstPosition; 2048 final int childCount = getChildCount(); 2049 if (firstPosition >= 0 && childCount > 0) { 2050 if (mSmoothScrollbarEnabled) { 2051 final View view = getChildAt(0); 2052 final int top = view.getTop(); 2053 int height = view.getHeight(); 2054 if (height > 0) { 2055 return Math.max(firstPosition * 100 - (top * 100) / height + 2056 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0); 2057 } 2058 } else { 2059 int index; 2060 final int count = mItemCount; 2061 if (firstPosition == 0) { 2062 index = 0; 2063 } else if (firstPosition + childCount == count) { 2064 index = count; 2065 } else { 2066 index = firstPosition + childCount / 2; 2067 } 2068 return (int) (firstPosition + childCount * (index / (float) count)); 2069 } 2070 } 2071 return 0; 2072 } 2073 2074 @Override computeVerticalScrollRange()2075 protected int computeVerticalScrollRange() { 2076 int result; 2077 if (mSmoothScrollbarEnabled) { 2078 result = Math.max(mItemCount * 100, 0); 2079 if (mScrollY != 0) { 2080 // Compensate for overscroll 2081 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100)); 2082 } 2083 } else { 2084 result = mItemCount; 2085 } 2086 return result; 2087 } 2088 2089 @Override getTopFadingEdgeStrength()2090 protected float getTopFadingEdgeStrength() { 2091 final int count = getChildCount(); 2092 final float fadeEdge = super.getTopFadingEdgeStrength(); 2093 if (count == 0) { 2094 return fadeEdge; 2095 } else { 2096 if (mFirstPosition > 0) { 2097 return 1.0f; 2098 } 2099 2100 final int top = getChildAt(0).getTop(); 2101 final float fadeLength = getVerticalFadingEdgeLength(); 2102 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge; 2103 } 2104 } 2105 2106 @Override getBottomFadingEdgeStrength()2107 protected float getBottomFadingEdgeStrength() { 2108 final int count = getChildCount(); 2109 final float fadeEdge = super.getBottomFadingEdgeStrength(); 2110 if (count == 0) { 2111 return fadeEdge; 2112 } else { 2113 if (mFirstPosition + count - 1 < mItemCount - 1) { 2114 return 1.0f; 2115 } 2116 2117 final int bottom = getChildAt(count - 1).getBottom(); 2118 final int height = getHeight(); 2119 final float fadeLength = getVerticalFadingEdgeLength(); 2120 return bottom > height - mPaddingBottom ? 2121 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge; 2122 } 2123 } 2124 2125 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)2126 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2127 if (mSelector == null) { 2128 useDefaultSelector(); 2129 } 2130 final Rect listPadding = mListPadding; 2131 listPadding.left = mSelectionLeftPadding + mPaddingLeft; 2132 listPadding.top = mSelectionTopPadding + mPaddingTop; 2133 listPadding.right = mSelectionRightPadding + mPaddingRight; 2134 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom; 2135 2136 // Check if our previous measured size was at a point where we should scroll later. 2137 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) { 2138 final int childCount = getChildCount(); 2139 final int listBottom = getHeight() - getPaddingBottom(); 2140 final View lastChild = getChildAt(childCount - 1); 2141 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom; 2142 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount && 2143 lastBottom <= listBottom; 2144 } 2145 } 2146 2147 /** 2148 * Subclasses should NOT override this method but 2149 * {@link #layoutChildren()} instead. 2150 */ 2151 @Override onLayout(boolean changed, int l, int t, int r, int b)2152 protected void onLayout(boolean changed, int l, int t, int r, int b) { 2153 super.onLayout(changed, l, t, r, b); 2154 2155 mInLayout = true; 2156 2157 final int childCount = getChildCount(); 2158 if (changed) { 2159 for (int i = 0; i < childCount; i++) { 2160 getChildAt(i).forceLayout(); 2161 } 2162 mRecycler.markChildrenDirty(); 2163 } 2164 2165 layoutChildren(); 2166 2167 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; 2168 2169 // TODO: Move somewhere sane. This doesn't belong in onLayout(). 2170 if (mFastScroll != null) { 2171 mFastScroll.onItemCountChanged(getChildCount(), mItemCount); 2172 } 2173 mInLayout = false; 2174 } 2175 2176 /** 2177 * @hide 2178 */ 2179 @Override setFrame(int left, int top, int right, int bottom)2180 protected boolean setFrame(int left, int top, int right, int bottom) { 2181 final boolean changed = super.setFrame(left, top, right, bottom); 2182 2183 if (changed) { 2184 // Reposition the popup when the frame has changed. This includes 2185 // translating the widget, not just changing its dimension. The 2186 // filter popup needs to follow the widget. 2187 final boolean visible = getWindowVisibility() == View.VISIBLE; 2188 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) { 2189 positionPopup(); 2190 } 2191 } 2192 2193 return changed; 2194 } 2195 2196 /** 2197 * Subclasses must override this method to layout their children. 2198 */ layoutChildren()2199 protected void layoutChildren() { 2200 } 2201 2202 /** 2203 * @param focusedView view that holds accessibility focus 2204 * @return direct child that contains accessibility focus, or null if no 2205 * child contains accessibility focus 2206 */ getAccessibilityFocusedChild(View focusedView)2207 View getAccessibilityFocusedChild(View focusedView) { 2208 ViewParent viewParent = focusedView.getParent(); 2209 while ((viewParent instanceof View) && (viewParent != this)) { 2210 focusedView = (View) viewParent; 2211 viewParent = viewParent.getParent(); 2212 } 2213 2214 if (!(viewParent instanceof View)) { 2215 return null; 2216 } 2217 2218 return focusedView; 2219 } 2220 updateScrollIndicators()2221 void updateScrollIndicators() { 2222 if (mScrollUp != null) { 2223 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE); 2224 } 2225 2226 if (mScrollDown != null) { 2227 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE); 2228 } 2229 } 2230 canScrollUp()2231 private boolean canScrollUp() { 2232 boolean canScrollUp; 2233 // 0th element is not visible 2234 canScrollUp = mFirstPosition > 0; 2235 2236 // ... Or top of 0th element is not visible 2237 if (!canScrollUp) { 2238 if (getChildCount() > 0) { 2239 View child = getChildAt(0); 2240 canScrollUp = child.getTop() < mListPadding.top; 2241 } 2242 } 2243 2244 return canScrollUp; 2245 } 2246 2247 private boolean canScrollDown() { 2248 boolean canScrollDown; 2249 int count = getChildCount(); 2250 2251 // Last item is not visible 2252 canScrollDown = (mFirstPosition + count) < mItemCount; 2253 2254 // ... Or bottom of the last element is not visible 2255 if (!canScrollDown && count > 0) { 2256 View child = getChildAt(count - 1); 2257 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom; 2258 } 2259 2260 return canScrollDown; 2261 } 2262 2263 @Override 2264 @ViewDebug.ExportedProperty getSelectedView()2265 public View getSelectedView() { 2266 if (mItemCount > 0 && mSelectedPosition >= 0) { 2267 return getChildAt(mSelectedPosition - mFirstPosition); 2268 } else { 2269 return null; 2270 } 2271 } 2272 2273 /** 2274 * List padding is the maximum of the normal view's padding and the padding of the selector. 2275 * 2276 * @see android.view.View#getPaddingTop() 2277 * @see #getSelector() 2278 * 2279 * @return The top list padding. 2280 */ getListPaddingTop()2281 public int getListPaddingTop() { 2282 return mListPadding.top; 2283 } 2284 2285 /** 2286 * List padding is the maximum of the normal view's padding and the padding of the selector. 2287 * 2288 * @see android.view.View#getPaddingBottom() 2289 * @see #getSelector() 2290 * 2291 * @return The bottom list padding. 2292 */ getListPaddingBottom()2293 public int getListPaddingBottom() { 2294 return mListPadding.bottom; 2295 } 2296 2297 /** 2298 * List padding is the maximum of the normal view's padding and the padding of the selector. 2299 * 2300 * @see android.view.View#getPaddingLeft() 2301 * @see #getSelector() 2302 * 2303 * @return The left list padding. 2304 */ getListPaddingLeft()2305 public int getListPaddingLeft() { 2306 return mListPadding.left; 2307 } 2308 2309 /** 2310 * List padding is the maximum of the normal view's padding and the padding of the selector. 2311 * 2312 * @see android.view.View#getPaddingRight() 2313 * @see #getSelector() 2314 * 2315 * @return The right list padding. 2316 */ getListPaddingRight()2317 public int getListPaddingRight() { 2318 return mListPadding.right; 2319 } 2320 2321 /** 2322 * Gets a view and have it show the data associated with the specified 2323 * position. This is called when we have already discovered that the view 2324 * is not available for reuse in the recycle bin. The only choices left are 2325 * converting an old view or making a new one. 2326 * 2327 * @param position the position to display 2328 * @param outMetadata an array of at least 1 boolean where the first entry 2329 * will be set {@code true} if the view is currently 2330 * attached to the window, {@code false} otherwise (e.g. 2331 * newly-inflated or remained scrap for multiple layout 2332 * passes) 2333 * 2334 * @return A view displaying the data associated with the specified position 2335 */ obtainView(int position, boolean[] outMetadata)2336 View obtainView(int position, boolean[] outMetadata) { 2337 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); 2338 2339 outMetadata[0] = false; 2340 2341 // Check whether we have a transient state view. Attempt to re-bind the 2342 // data and discard the view if we fail. 2343 final View transientView = mRecycler.getTransientStateView(position); 2344 if (transientView != null) { 2345 final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); 2346 2347 // If the view type hasn't changed, attempt to re-bind the data. 2348 if (params.viewType == mAdapter.getItemViewType(position)) { 2349 final View updatedView = mAdapter.getView(position, transientView, this); 2350 2351 // If we failed to re-bind the data, scrap the obtained view. 2352 if (updatedView != transientView) { 2353 setItemViewLayoutParams(updatedView, position); 2354 mRecycler.addScrapView(updatedView, position); 2355 } 2356 } 2357 2358 outMetadata[0] = true; 2359 2360 // Finish the temporary detach started in addScrapView(). 2361 transientView.dispatchFinishTemporaryDetach(); 2362 return transientView; 2363 } 2364 2365 final View scrapView = mRecycler.getScrapView(position); 2366 final View child = mAdapter.getView(position, scrapView, this); 2367 if (scrapView != null) { 2368 if (child != scrapView) { 2369 // Failed to re-bind the data, return scrap to the heap. 2370 mRecycler.addScrapView(scrapView, position); 2371 } else if (child.isTemporarilyDetached()) { 2372 outMetadata[0] = true; 2373 2374 // Finish the temporary detach started in addScrapView(). 2375 child.dispatchFinishTemporaryDetach(); 2376 } 2377 } 2378 2379 if (mCacheColorHint != 0) { 2380 child.setDrawingCacheBackgroundColor(mCacheColorHint); 2381 } 2382 2383 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 2384 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 2385 } 2386 2387 setItemViewLayoutParams(child, position); 2388 2389 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 2390 if (mAccessibilityDelegate == null) { 2391 mAccessibilityDelegate = new ListItemAccessibilityDelegate(); 2392 } 2393 if (child.getAccessibilityDelegate() == null) { 2394 child.setAccessibilityDelegate(mAccessibilityDelegate); 2395 } 2396 } 2397 2398 Trace.traceEnd(Trace.TRACE_TAG_VIEW); 2399 2400 return child; 2401 } 2402 setItemViewLayoutParams(View child, int position)2403 private void setItemViewLayoutParams(View child, int position) { 2404 final ViewGroup.LayoutParams vlp = child.getLayoutParams(); 2405 LayoutParams lp; 2406 if (vlp == null) { 2407 lp = (LayoutParams) generateDefaultLayoutParams(); 2408 } else if (!checkLayoutParams(vlp)) { 2409 lp = (LayoutParams) generateLayoutParams(vlp); 2410 } else { 2411 lp = (LayoutParams) vlp; 2412 } 2413 2414 if (mAdapterHasStableIds) { 2415 lp.itemId = mAdapter.getItemId(position); 2416 } 2417 lp.viewType = mAdapter.getItemViewType(position); 2418 lp.isEnabled = mAdapter.isEnabled(position); 2419 if (lp != vlp) { 2420 child.setLayoutParams(lp); 2421 } 2422 } 2423 2424 class ListItemAccessibilityDelegate extends AccessibilityDelegate { 2425 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)2426 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 2427 super.onInitializeAccessibilityNodeInfo(host, info); 2428 2429 final int position = getPositionForView(host); 2430 onInitializeAccessibilityNodeInfoForItem(host, position, info); 2431 } 2432 2433 @Override performAccessibilityAction(View host, int action, Bundle arguments)2434 public boolean performAccessibilityAction(View host, int action, Bundle arguments) { 2435 if (super.performAccessibilityAction(host, action, arguments)) { 2436 return true; 2437 } 2438 2439 final int position = getPositionForView(host); 2440 if (position == INVALID_POSITION || mAdapter == null) { 2441 // Cannot perform actions on invalid items. 2442 return false; 2443 } 2444 2445 if (position >= mAdapter.getCount()) { 2446 // The position is no longer valid, likely due to a data set 2447 // change. We could fail here for all data set changes, since 2448 // there is a chance that the data bound to the view may no 2449 // longer exist at the same position within the adapter, but 2450 // it's more consistent with the standard touch interaction to 2451 // click at whatever may have moved into that position. 2452 return false; 2453 } 2454 2455 final boolean isItemEnabled; 2456 final ViewGroup.LayoutParams lp = host.getLayoutParams(); 2457 if (lp instanceof AbsListView.LayoutParams) { 2458 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled; 2459 } else { 2460 isItemEnabled = false; 2461 } 2462 2463 if (!isEnabled() || !isItemEnabled) { 2464 // Cannot perform actions on disabled items. 2465 return false; 2466 } 2467 2468 switch (action) { 2469 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: { 2470 if (getSelectedItemPosition() == position) { 2471 setSelection(INVALID_POSITION); 2472 return true; 2473 } 2474 } return false; 2475 case AccessibilityNodeInfo.ACTION_SELECT: { 2476 if (getSelectedItemPosition() != position) { 2477 setSelection(position); 2478 return true; 2479 } 2480 } return false; 2481 case AccessibilityNodeInfo.ACTION_CLICK: { 2482 if (isItemClickable(host)) { 2483 final long id = getItemIdAtPosition(position); 2484 return performItemClick(host, position, id); 2485 } 2486 } return false; 2487 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 2488 if (isLongClickable()) { 2489 final long id = getItemIdAtPosition(position); 2490 return performLongPress(host, position, id); 2491 } 2492 } return false; 2493 } 2494 2495 return false; 2496 } 2497 } 2498 2499 /** 2500 * Initializes an {@link AccessibilityNodeInfo} with information about a 2501 * particular item in the list. 2502 * 2503 * @param view View representing the list item. 2504 * @param position Position of the list item within the adapter. 2505 * @param info Node info to populate. 2506 */ onInitializeAccessibilityNodeInfoForItem( View view, int position, AccessibilityNodeInfo info)2507 public void onInitializeAccessibilityNodeInfoForItem( 2508 View view, int position, AccessibilityNodeInfo info) { 2509 if (position == INVALID_POSITION) { 2510 // The item doesn't exist, so there's not much we can do here. 2511 return; 2512 } 2513 2514 final boolean isItemEnabled; 2515 final ViewGroup.LayoutParams lp = view.getLayoutParams(); 2516 if (lp instanceof AbsListView.LayoutParams) { 2517 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled; 2518 } else { 2519 isItemEnabled = false; 2520 } 2521 2522 if (!isEnabled() || !isItemEnabled) { 2523 info.setEnabled(false); 2524 return; 2525 } 2526 2527 if (position == getSelectedItemPosition()) { 2528 info.setSelected(true); 2529 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION); 2530 } else { 2531 info.addAction(AccessibilityAction.ACTION_SELECT); 2532 } 2533 2534 if (isItemClickable(view)) { 2535 info.addAction(AccessibilityAction.ACTION_CLICK); 2536 info.setClickable(true); 2537 } 2538 2539 if (isLongClickable()) { 2540 info.addAction(AccessibilityAction.ACTION_LONG_CLICK); 2541 info.setLongClickable(true); 2542 } 2543 } 2544 isItemClickable(View view)2545 private boolean isItemClickable(View view) { 2546 return !view.hasExplicitFocusable(); 2547 } 2548 2549 /** 2550 * Positions the selector in a way that mimics touch. 2551 */ positionSelectorLikeTouch(int position, View sel, float x, float y)2552 void positionSelectorLikeTouch(int position, View sel, float x, float y) { 2553 positionSelector(position, sel, true, x, y); 2554 } 2555 2556 /** 2557 * Positions the selector in a way that mimics keyboard focus. 2558 */ positionSelectorLikeFocus(int position, View sel)2559 void positionSelectorLikeFocus(int position, View sel) { 2560 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) { 2561 final Rect bounds = mSelectorRect; 2562 final float x = bounds.exactCenterX(); 2563 final float y = bounds.exactCenterY(); 2564 positionSelector(position, sel, true, x, y); 2565 } else { 2566 positionSelector(position, sel); 2567 } 2568 } 2569 positionSelector(int position, View sel)2570 void positionSelector(int position, View sel) { 2571 positionSelector(position, sel, false, -1, -1); 2572 } 2573 positionSelector(int position, View sel, boolean manageHotspot, float x, float y)2574 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) { 2575 final boolean positionChanged = position != mSelectorPosition; 2576 if (position != INVALID_POSITION) { 2577 mSelectorPosition = position; 2578 } 2579 2580 final Rect selectorRect = mSelectorRect; 2581 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); 2582 if (sel instanceof SelectionBoundsAdjuster) { 2583 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect); 2584 } 2585 2586 // Adjust for selection padding. 2587 selectorRect.left -= mSelectionLeftPadding; 2588 selectorRect.top -= mSelectionTopPadding; 2589 selectorRect.right += mSelectionRightPadding; 2590 selectorRect.bottom += mSelectionBottomPadding; 2591 2592 // Update the child enabled state prior to updating the selector. 2593 final boolean isChildViewEnabled = sel.isEnabled(); 2594 if (mIsChildViewEnabled != isChildViewEnabled) { 2595 mIsChildViewEnabled = isChildViewEnabled; 2596 } 2597 2598 // Update the selector drawable's state and position. 2599 final Drawable selector = mSelector; 2600 if (selector != null) { 2601 if (positionChanged) { 2602 // Wipe out the current selector state so that we can start 2603 // over in the new position with a fresh state. 2604 selector.setVisible(false, false); 2605 selector.setState(StateSet.NOTHING); 2606 } 2607 selector.setBounds(selectorRect); 2608 if (positionChanged) { 2609 if (getVisibility() == VISIBLE) { 2610 selector.setVisible(true, false); 2611 } 2612 updateSelectorState(); 2613 } 2614 if (manageHotspot) { 2615 selector.setHotspot(x, y); 2616 } 2617 } 2618 } 2619 2620 @Override dispatchDraw(Canvas canvas)2621 protected void dispatchDraw(Canvas canvas) { 2622 int saveCount = 0; 2623 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; 2624 if (clipToPadding) { 2625 saveCount = canvas.save(); 2626 final int scrollX = mScrollX; 2627 final int scrollY = mScrollY; 2628 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, 2629 scrollX + mRight - mLeft - mPaddingRight, 2630 scrollY + mBottom - mTop - mPaddingBottom); 2631 mGroupFlags &= ~CLIP_TO_PADDING_MASK; 2632 } 2633 2634 final boolean drawSelectorOnTop = mDrawSelectorOnTop; 2635 if (!drawSelectorOnTop) { 2636 drawSelector(canvas); 2637 } 2638 2639 super.dispatchDraw(canvas); 2640 2641 if (drawSelectorOnTop) { 2642 drawSelector(canvas); 2643 } 2644 2645 if (clipToPadding) { 2646 canvas.restoreToCount(saveCount); 2647 mGroupFlags |= CLIP_TO_PADDING_MASK; 2648 } 2649 } 2650 2651 @Override isPaddingOffsetRequired()2652 protected boolean isPaddingOffsetRequired() { 2653 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK; 2654 } 2655 2656 @Override getLeftPaddingOffset()2657 protected int getLeftPaddingOffset() { 2658 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft; 2659 } 2660 2661 @Override getTopPaddingOffset()2662 protected int getTopPaddingOffset() { 2663 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop; 2664 } 2665 2666 @Override getRightPaddingOffset()2667 protected int getRightPaddingOffset() { 2668 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight; 2669 } 2670 2671 @Override getBottomPaddingOffset()2672 protected int getBottomPaddingOffset() { 2673 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom; 2674 } 2675 2676 /** 2677 * @hide 2678 */ 2679 @Override internalSetPadding(int left, int top, int right, int bottom)2680 protected void internalSetPadding(int left, int top, int right, int bottom) { 2681 super.internalSetPadding(left, top, right, bottom); 2682 if (isLayoutRequested()) { 2683 handleBoundsChange(); 2684 } 2685 } 2686 2687 @Override onSizeChanged(int w, int h, int oldw, int oldh)2688 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 2689 handleBoundsChange(); 2690 if (mFastScroll != null) { 2691 mFastScroll.onSizeChanged(w, h, oldw, oldh); 2692 } 2693 } 2694 2695 /** 2696 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed 2697 * and force layouts all children that don't have exact measure specs. 2698 * <p> 2699 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and 2700 * fail to relayout them properly to accommodate for new bounds. 2701 */ handleBoundsChange()2702 void handleBoundsChange() { 2703 if (mInLayout) { 2704 return; 2705 } 2706 final int childCount = getChildCount(); 2707 if (childCount > 0) { 2708 mDataChanged = true; 2709 rememberSyncState(); 2710 for (int i = 0; i < childCount; i++) { 2711 final View child = getChildAt(i); 2712 final ViewGroup.LayoutParams lp = child.getLayoutParams(); 2713 // force layout child unless it has exact specs 2714 if (lp == null || lp.width < 1 || lp.height < 1) { 2715 child.forceLayout(); 2716 } 2717 } 2718 } 2719 } 2720 2721 /** 2722 * @return True if the current touch mode requires that we draw the selector in the pressed 2723 * state. 2724 */ touchModeDrawsInPressedState()2725 boolean touchModeDrawsInPressedState() { 2726 // FIXME use isPressed for this 2727 switch (mTouchMode) { 2728 case TOUCH_MODE_TAP: 2729 case TOUCH_MODE_DONE_WAITING: 2730 return true; 2731 default: 2732 return false; 2733 } 2734 } 2735 2736 /** 2737 * Indicates whether this view is in a state where the selector should be drawn. This will 2738 * happen if we have focus but are not in touch mode, or we are in the middle of displaying 2739 * the pressed state for an item. 2740 * 2741 * @return True if the selector should be shown 2742 */ shouldShowSelector()2743 boolean shouldShowSelector() { 2744 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed()); 2745 } 2746 drawSelector(Canvas canvas)2747 private void drawSelector(Canvas canvas) { 2748 if (shouldDrawSelector()) { 2749 final Drawable selector = mSelector; 2750 selector.setBounds(mSelectorRect); 2751 selector.draw(canvas); 2752 } 2753 } 2754 2755 /** 2756 * @hide 2757 */ 2758 @TestApi shouldDrawSelector()2759 public final boolean shouldDrawSelector() { 2760 return !mSelectorRect.isEmpty(); 2761 } 2762 2763 /** 2764 * Controls whether the selection highlight drawable should be drawn on top of the item or 2765 * behind it. 2766 * 2767 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default 2768 * is false. 2769 * 2770 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop 2771 */ setDrawSelectorOnTop(boolean onTop)2772 public void setDrawSelectorOnTop(boolean onTop) { 2773 mDrawSelectorOnTop = onTop; 2774 } 2775 2776 /** 2777 * Set a Drawable that should be used to highlight the currently selected item. 2778 * 2779 * @param resID A Drawable resource to use as the selection highlight. 2780 * 2781 * @attr ref android.R.styleable#AbsListView_listSelector 2782 */ setSelector(@rawableRes int resID)2783 public void setSelector(@DrawableRes int resID) { 2784 setSelector(getContext().getDrawable(resID)); 2785 } 2786 setSelector(Drawable sel)2787 public void setSelector(Drawable sel) { 2788 if (mSelector != null) { 2789 mSelector.setCallback(null); 2790 unscheduleDrawable(mSelector); 2791 } 2792 mSelector = sel; 2793 Rect padding = new Rect(); 2794 sel.getPadding(padding); 2795 mSelectionLeftPadding = padding.left; 2796 mSelectionTopPadding = padding.top; 2797 mSelectionRightPadding = padding.right; 2798 mSelectionBottomPadding = padding.bottom; 2799 sel.setCallback(this); 2800 updateSelectorState(); 2801 } 2802 2803 /** 2804 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the 2805 * selection in the list. 2806 * 2807 * @return the drawable used to display the selector 2808 */ getSelector()2809 public Drawable getSelector() { 2810 return mSelector; 2811 } 2812 2813 /** 2814 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if 2815 * this is a long press. 2816 */ keyPressed()2817 void keyPressed() { 2818 if (!isEnabled() || !isClickable()) { 2819 return; 2820 } 2821 2822 Drawable selector = mSelector; 2823 Rect selectorRect = mSelectorRect; 2824 if (selector != null && (isFocused() || touchModeDrawsInPressedState()) 2825 && !selectorRect.isEmpty()) { 2826 2827 final View v = getChildAt(mSelectedPosition - mFirstPosition); 2828 2829 if (v != null) { 2830 if (v.hasExplicitFocusable()) return; 2831 v.setPressed(true); 2832 } 2833 setPressed(true); 2834 2835 final boolean longClickable = isLongClickable(); 2836 Drawable d = selector.getCurrent(); 2837 if (d != null && d instanceof TransitionDrawable) { 2838 if (longClickable) { 2839 ((TransitionDrawable) d).startTransition( 2840 ViewConfiguration.getLongPressTimeout()); 2841 } else { 2842 ((TransitionDrawable) d).resetTransition(); 2843 } 2844 } 2845 if (longClickable && !mDataChanged) { 2846 if (mPendingCheckForKeyLongPress == null) { 2847 mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); 2848 } 2849 mPendingCheckForKeyLongPress.rememberWindowAttachCount(); 2850 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); 2851 } 2852 } 2853 } 2854 setScrollIndicators(View up, View down)2855 public void setScrollIndicators(View up, View down) { 2856 mScrollUp = up; 2857 mScrollDown = down; 2858 } 2859 updateSelectorState()2860 void updateSelectorState() { 2861 final Drawable selector = mSelector; 2862 if (selector != null && selector.isStateful()) { 2863 if (shouldShowSelector()) { 2864 if (selector.setState(getDrawableStateForSelector())) { 2865 invalidateDrawable(selector); 2866 } 2867 } else { 2868 selector.setState(StateSet.NOTHING); 2869 } 2870 } 2871 } 2872 2873 @Override drawableStateChanged()2874 protected void drawableStateChanged() { 2875 super.drawableStateChanged(); 2876 updateSelectorState(); 2877 } 2878 getDrawableStateForSelector()2879 private int[] getDrawableStateForSelector() { 2880 // If the child view is enabled then do the default behavior. 2881 if (mIsChildViewEnabled) { 2882 // Common case 2883 return super.getDrawableState(); 2884 } 2885 2886 // The selector uses this View's drawable state. The selected child view 2887 // is disabled, so we need to remove the enabled state from the drawable 2888 // states. 2889 final int enabledState = ENABLED_STATE_SET[0]; 2890 2891 // If we don't have any extra space, it will return one of the static 2892 // state arrays, and clearing the enabled state on those arrays is a 2893 // bad thing! If we specify we need extra space, it will create+copy 2894 // into a new array that is safely mutable. 2895 final int[] state = onCreateDrawableState(1); 2896 2897 int enabledPos = -1; 2898 for (int i = state.length - 1; i >= 0; i--) { 2899 if (state[i] == enabledState) { 2900 enabledPos = i; 2901 break; 2902 } 2903 } 2904 2905 // Remove the enabled state 2906 if (enabledPos >= 0) { 2907 System.arraycopy(state, enabledPos + 1, state, enabledPos, 2908 state.length - enabledPos - 1); 2909 } 2910 2911 return state; 2912 } 2913 2914 @Override verifyDrawable(@onNull Drawable dr)2915 public boolean verifyDrawable(@NonNull Drawable dr) { 2916 return mSelector == dr || super.verifyDrawable(dr); 2917 } 2918 2919 @Override jumpDrawablesToCurrentState()2920 public void jumpDrawablesToCurrentState() { 2921 super.jumpDrawablesToCurrentState(); 2922 if (mSelector != null) mSelector.jumpToCurrentState(); 2923 } 2924 2925 @Override onAttachedToWindow()2926 protected void onAttachedToWindow() { 2927 super.onAttachedToWindow(); 2928 2929 final ViewTreeObserver treeObserver = getViewTreeObserver(); 2930 treeObserver.addOnTouchModeChangeListener(this); 2931 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) { 2932 treeObserver.addOnGlobalLayoutListener(this); 2933 } 2934 2935 if (mAdapter != null && mDataSetObserver == null) { 2936 mDataSetObserver = new AdapterDataSetObserver(); 2937 mAdapter.registerDataSetObserver(mDataSetObserver); 2938 2939 // Data may have changed while we were detached. Refresh. 2940 mDataChanged = true; 2941 mOldItemCount = mItemCount; 2942 mItemCount = mAdapter.getCount(); 2943 } 2944 } 2945 2946 @Override onDetachedFromWindow()2947 protected void onDetachedFromWindow() { 2948 super.onDetachedFromWindow(); 2949 2950 mIsDetaching = true; 2951 2952 // Dismiss the popup in case onSaveInstanceState() was not invoked 2953 dismissPopup(); 2954 2955 // Detach any view left in the scrap heap 2956 mRecycler.clear(); 2957 2958 final ViewTreeObserver treeObserver = getViewTreeObserver(); 2959 treeObserver.removeOnTouchModeChangeListener(this); 2960 if (mTextFilterEnabled && mPopup != null) { 2961 treeObserver.removeOnGlobalLayoutListener(this); 2962 mGlobalLayoutListenerAddedFilter = false; 2963 } 2964 2965 if (mAdapter != null && mDataSetObserver != null) { 2966 mAdapter.unregisterDataSetObserver(mDataSetObserver); 2967 mDataSetObserver = null; 2968 } 2969 2970 if (mScrollStrictSpan != null) { 2971 mScrollStrictSpan.finish(); 2972 mScrollStrictSpan = null; 2973 } 2974 2975 if (mFlingStrictSpan != null) { 2976 mFlingStrictSpan.finish(); 2977 mFlingStrictSpan = null; 2978 } 2979 2980 if (mFlingRunnable != null) { 2981 removeCallbacks(mFlingRunnable); 2982 } 2983 2984 if (mPositionScroller != null) { 2985 mPositionScroller.stop(); 2986 } 2987 2988 if (mClearScrollingCache != null) { 2989 removeCallbacks(mClearScrollingCache); 2990 } 2991 2992 if (mPerformClick != null) { 2993 removeCallbacks(mPerformClick); 2994 } 2995 2996 if (mTouchModeReset != null) { 2997 removeCallbacks(mTouchModeReset); 2998 mTouchModeReset.run(); 2999 } 3000 3001 mIsDetaching = false; 3002 } 3003 3004 @Override onWindowFocusChanged(boolean hasWindowFocus)3005 public void onWindowFocusChanged(boolean hasWindowFocus) { 3006 super.onWindowFocusChanged(hasWindowFocus); 3007 3008 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF; 3009 3010 if (!hasWindowFocus) { 3011 setChildrenDrawingCacheEnabled(false); 3012 if (mFlingRunnable != null) { 3013 removeCallbacks(mFlingRunnable); 3014 // let the fling runnable report its new state which 3015 // should be idle 3016 mFlingRunnable.mSuppressIdleStateChangeCall = false; 3017 mFlingRunnable.endFling(); 3018 if (mPositionScroller != null) { 3019 mPositionScroller.stop(); 3020 } 3021 if (mScrollY != 0) { 3022 mScrollY = 0; 3023 invalidateParentCaches(); 3024 finishGlows(); 3025 invalidate(); 3026 } 3027 } 3028 // Always hide the type filter 3029 dismissPopup(); 3030 3031 if (touchMode == TOUCH_MODE_OFF) { 3032 // Remember the last selected element 3033 mResurrectToPosition = mSelectedPosition; 3034 } 3035 } else { 3036 if (mFiltered && !mPopupHidden) { 3037 // Show the type filter only if a filter is in effect 3038 showPopup(); 3039 } 3040 3041 // If we changed touch mode since the last time we had focus 3042 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) { 3043 // If we come back in trackball mode, we bring the selection back 3044 if (touchMode == TOUCH_MODE_OFF) { 3045 // This will trigger a layout 3046 resurrectSelection(); 3047 3048 // If we come back in touch mode, then we want to hide the selector 3049 } else { 3050 hideSelector(); 3051 mLayoutMode = LAYOUT_NORMAL; 3052 layoutChildren(); 3053 } 3054 } 3055 } 3056 3057 mLastTouchMode = touchMode; 3058 } 3059 3060 @Override onRtlPropertiesChanged(int layoutDirection)3061 public void onRtlPropertiesChanged(int layoutDirection) { 3062 super.onRtlPropertiesChanged(layoutDirection); 3063 if (mFastScroll != null) { 3064 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition()); 3065 } 3066 } 3067 3068 /** 3069 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This 3070 * methods knows the view, position and ID of the item that received the 3071 * long press. 3072 * 3073 * @param view The view that received the long press. 3074 * @param position The position of the item that received the long press. 3075 * @param id The ID of the item that received the long press. 3076 * @return The extra information that should be returned by 3077 * {@link #getContextMenuInfo()}. 3078 */ createContextMenuInfo(View view, int position, long id)3079 ContextMenuInfo createContextMenuInfo(View view, int position, long id) { 3080 return new AdapterContextMenuInfo(view, position, id); 3081 } 3082 3083 @Override onCancelPendingInputEvents()3084 public void onCancelPendingInputEvents() { 3085 super.onCancelPendingInputEvents(); 3086 if (mPerformClick != null) { 3087 removeCallbacks(mPerformClick); 3088 } 3089 if (mPendingCheckForTap != null) { 3090 removeCallbacks(mPendingCheckForTap); 3091 } 3092 if (mPendingCheckForLongPress != null) { 3093 removeCallbacks(mPendingCheckForLongPress); 3094 } 3095 if (mPendingCheckForKeyLongPress != null) { 3096 removeCallbacks(mPendingCheckForKeyLongPress); 3097 } 3098 } 3099 3100 /** 3101 * A base class for Runnables that will check that their view is still attached to 3102 * the original window as when the Runnable was created. 3103 * 3104 */ 3105 private class WindowRunnnable { 3106 private int mOriginalAttachCount; 3107 rememberWindowAttachCount()3108 public void rememberWindowAttachCount() { 3109 mOriginalAttachCount = getWindowAttachCount(); 3110 } 3111 sameWindow()3112 public boolean sameWindow() { 3113 return getWindowAttachCount() == mOriginalAttachCount; 3114 } 3115 } 3116 3117 private class PerformClick extends WindowRunnnable implements Runnable { 3118 int mClickMotionPosition; 3119 3120 @Override run()3121 public void run() { 3122 // The data has changed since we posted this action in the event queue, 3123 // bail out before bad things happen 3124 if (mDataChanged) return; 3125 3126 final ListAdapter adapter = mAdapter; 3127 final int motionPosition = mClickMotionPosition; 3128 if (adapter != null && mItemCount > 0 && 3129 motionPosition != INVALID_POSITION && 3130 motionPosition < adapter.getCount() && sameWindow() && 3131 adapter.isEnabled(motionPosition)) { 3132 final View view = getChildAt(motionPosition - mFirstPosition); 3133 // If there is no view, something bad happened (the view scrolled off the 3134 // screen, etc.) and we should cancel the click 3135 if (view != null) { 3136 performItemClick(view, motionPosition, adapter.getItemId(motionPosition)); 3137 } 3138 } 3139 } 3140 } 3141 3142 private class CheckForLongPress extends WindowRunnnable implements Runnable { 3143 private static final int INVALID_COORD = -1; 3144 private float mX = INVALID_COORD; 3145 private float mY = INVALID_COORD; 3146 setCoords(float x, float y)3147 private void setCoords(float x, float y) { 3148 mX = x; 3149 mY = y; 3150 } 3151 3152 @Override run()3153 public void run() { 3154 final int motionPosition = mMotionPosition; 3155 final View child = getChildAt(motionPosition - mFirstPosition); 3156 if (child != null) { 3157 final int longPressPosition = mMotionPosition; 3158 final long longPressId = mAdapter.getItemId(mMotionPosition); 3159 3160 boolean handled = false; 3161 if (sameWindow() && !mDataChanged) { 3162 if (mX != INVALID_COORD && mY != INVALID_COORD) { 3163 handled = performLongPress(child, longPressPosition, longPressId, mX, mY); 3164 } else { 3165 handled = performLongPress(child, longPressPosition, longPressId); 3166 } 3167 } 3168 3169 if (handled) { 3170 mHasPerformedLongPress = true; 3171 mTouchMode = TOUCH_MODE_REST; 3172 setPressed(false); 3173 child.setPressed(false); 3174 } else { 3175 mTouchMode = TOUCH_MODE_DONE_WAITING; 3176 } 3177 } 3178 } 3179 } 3180 3181 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable { 3182 @Override run()3183 public void run() { 3184 if (isPressed() && mSelectedPosition >= 0) { 3185 int index = mSelectedPosition - mFirstPosition; 3186 View v = getChildAt(index); 3187 3188 if (!mDataChanged) { 3189 boolean handled = false; 3190 if (sameWindow()) { 3191 handled = performLongPress(v, mSelectedPosition, mSelectedRowId); 3192 } 3193 if (handled) { 3194 setPressed(false); 3195 v.setPressed(false); 3196 } 3197 } else { 3198 setPressed(false); 3199 if (v != null) v.setPressed(false); 3200 } 3201 } 3202 } 3203 } 3204 performStylusButtonPressAction(MotionEvent ev)3205 private boolean performStylusButtonPressAction(MotionEvent ev) { 3206 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { 3207 final View child = getChildAt(mMotionPosition - mFirstPosition); 3208 if (child != null) { 3209 final int longPressPosition = mMotionPosition; 3210 final long longPressId = mAdapter.getItemId(mMotionPosition); 3211 if (performLongPress(child, longPressPosition, longPressId)) { 3212 mTouchMode = TOUCH_MODE_REST; 3213 setPressed(false); 3214 child.setPressed(false); 3215 return true; 3216 } 3217 } 3218 } 3219 return false; 3220 } 3221 performLongPress(final View child, final int longPressPosition, final long longPressId)3222 boolean performLongPress(final View child, 3223 final int longPressPosition, final long longPressId) { 3224 return performLongPress( 3225 child, 3226 longPressPosition, 3227 longPressId, 3228 CheckForLongPress.INVALID_COORD, 3229 CheckForLongPress.INVALID_COORD); 3230 } 3231 performLongPress(final View child, final int longPressPosition, final long longPressId, float x, float y)3232 boolean performLongPress(final View child, 3233 final int longPressPosition, final long longPressId, float x, float y) { 3234 // CHOICE_MODE_MULTIPLE_MODAL takes over long press. 3235 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { 3236 if (mChoiceActionMode == null && 3237 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) { 3238 setItemChecked(longPressPosition, true); 3239 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 3240 } 3241 return true; 3242 } 3243 3244 boolean handled = false; 3245 if (mOnItemLongClickListener != null) { 3246 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child, 3247 longPressPosition, longPressId); 3248 } 3249 if (!handled) { 3250 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); 3251 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) { 3252 handled = super.showContextMenuForChild(AbsListView.this, x, y); 3253 } else { 3254 handled = super.showContextMenuForChild(AbsListView.this); 3255 } 3256 } 3257 if (handled) { 3258 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 3259 } 3260 return handled; 3261 } 3262 3263 @Override getContextMenuInfo()3264 protected ContextMenuInfo getContextMenuInfo() { 3265 return mContextMenuInfo; 3266 } 3267 3268 @Override showContextMenu()3269 public boolean showContextMenu() { 3270 return showContextMenuInternal(0, 0, false); 3271 } 3272 3273 @Override showContextMenu(float x, float y)3274 public boolean showContextMenu(float x, float y) { 3275 return showContextMenuInternal(x, y, true); 3276 } 3277 showContextMenuInternal(float x, float y, boolean useOffsets)3278 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) { 3279 final int position = pointToPosition((int)x, (int)y); 3280 if (position != INVALID_POSITION) { 3281 final long id = mAdapter.getItemId(position); 3282 View child = getChildAt(position - mFirstPosition); 3283 if (child != null) { 3284 mContextMenuInfo = createContextMenuInfo(child, position, id); 3285 if (useOffsets) { 3286 return super.showContextMenuForChild(this, x, y); 3287 } else { 3288 return super.showContextMenuForChild(this); 3289 } 3290 } 3291 } 3292 if (useOffsets) { 3293 return super.showContextMenu(x, y); 3294 } else { 3295 return super.showContextMenu(); 3296 } 3297 } 3298 3299 @Override showContextMenuForChild(View originalView)3300 public boolean showContextMenuForChild(View originalView) { 3301 if (isShowingContextMenuWithCoords()) { 3302 return false; 3303 } 3304 return showContextMenuForChildInternal(originalView, 0, 0, false); 3305 } 3306 3307 @Override showContextMenuForChild(View originalView, float x, float y)3308 public boolean showContextMenuForChild(View originalView, float x, float y) { 3309 return showContextMenuForChildInternal(originalView,x, y, true); 3310 } 3311 showContextMenuForChildInternal(View originalView, float x, float y, boolean useOffsets)3312 private boolean showContextMenuForChildInternal(View originalView, float x, float y, 3313 boolean useOffsets) { 3314 final int longPressPosition = getPositionForView(originalView); 3315 if (longPressPosition < 0) { 3316 return false; 3317 } 3318 3319 final long longPressId = mAdapter.getItemId(longPressPosition); 3320 boolean handled = false; 3321 3322 if (mOnItemLongClickListener != null) { 3323 handled = mOnItemLongClickListener.onItemLongClick(this, originalView, 3324 longPressPosition, longPressId); 3325 } 3326 3327 if (!handled) { 3328 final View child = getChildAt(longPressPosition - mFirstPosition); 3329 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); 3330 3331 if (useOffsets) { 3332 handled = super.showContextMenuForChild(originalView, x, y); 3333 } else { 3334 handled = super.showContextMenuForChild(originalView); 3335 } 3336 } 3337 3338 return handled; 3339 } 3340 3341 @Override onKeyDown(int keyCode, KeyEvent event)3342 public boolean onKeyDown(int keyCode, KeyEvent event) { 3343 return false; 3344 } 3345 3346 @Override onKeyUp(int keyCode, KeyEvent event)3347 public boolean onKeyUp(int keyCode, KeyEvent event) { 3348 if (KeyEvent.isConfirmKey(keyCode)) { 3349 if (!isEnabled()) { 3350 return true; 3351 } 3352 if (isClickable() && isPressed() && 3353 mSelectedPosition >= 0 && mAdapter != null && 3354 mSelectedPosition < mAdapter.getCount()) { 3355 3356 final View view = getChildAt(mSelectedPosition - mFirstPosition); 3357 if (view != null) { 3358 performItemClick(view, mSelectedPosition, mSelectedRowId); 3359 view.setPressed(false); 3360 } 3361 setPressed(false); 3362 return true; 3363 } 3364 } 3365 return super.onKeyUp(keyCode, event); 3366 } 3367 3368 @Override dispatchSetPressed(boolean pressed)3369 protected void dispatchSetPressed(boolean pressed) { 3370 // Don't dispatch setPressed to our children. We call setPressed on ourselves to 3371 // get the selector in the right state, but we don't want to press each child. 3372 } 3373 3374 @Override dispatchDrawableHotspotChanged(float x, float y)3375 public void dispatchDrawableHotspotChanged(float x, float y) { 3376 // Don't dispatch hotspot changes to children. We'll manually handle 3377 // calling drawableHotspotChanged on the correct child. 3378 } 3379 3380 /** 3381 * Maps a point to a position in the list. 3382 * 3383 * @param x X in local coordinate 3384 * @param y Y in local coordinate 3385 * @return The position of the item which contains the specified point, or 3386 * {@link #INVALID_POSITION} if the point does not intersect an item. 3387 */ pointToPosition(int x, int y)3388 public int pointToPosition(int x, int y) { 3389 Rect frame = mTouchFrame; 3390 if (frame == null) { 3391 mTouchFrame = new Rect(); 3392 frame = mTouchFrame; 3393 } 3394 3395 final int count = getChildCount(); 3396 for (int i = count - 1; i >= 0; i--) { 3397 final View child = getChildAt(i); 3398 if (child.getVisibility() == View.VISIBLE) { 3399 child.getHitRect(frame); 3400 if (frame.contains(x, y)) { 3401 return mFirstPosition + i; 3402 } 3403 } 3404 } 3405 return INVALID_POSITION; 3406 } 3407 3408 3409 /** 3410 * Maps a point to a the rowId of the item which intersects that point. 3411 * 3412 * @param x X in local coordinate 3413 * @param y Y in local coordinate 3414 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID} 3415 * if the point does not intersect an item. 3416 */ pointToRowId(int x, int y)3417 public long pointToRowId(int x, int y) { 3418 int position = pointToPosition(x, y); 3419 if (position >= 0) { 3420 return mAdapter.getItemId(position); 3421 } 3422 return INVALID_ROW_ID; 3423 } 3424 3425 private final class CheckForTap implements Runnable { 3426 float x; 3427 float y; 3428 3429 @Override run()3430 public void run() { 3431 if (mTouchMode == TOUCH_MODE_DOWN) { 3432 mTouchMode = TOUCH_MODE_TAP; 3433 final View child = getChildAt(mMotionPosition - mFirstPosition); 3434 if (child != null && !child.hasExplicitFocusable()) { 3435 mLayoutMode = LAYOUT_NORMAL; 3436 3437 if (!mDataChanged) { 3438 final float[] point = mTmpPoint; 3439 point[0] = x; 3440 point[1] = y; 3441 transformPointToViewLocal(point, child); 3442 child.drawableHotspotChanged(point[0], point[1]); 3443 child.setPressed(true); 3444 setPressed(true); 3445 layoutChildren(); 3446 positionSelector(mMotionPosition, child); 3447 refreshDrawableState(); 3448 3449 final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); 3450 final boolean longClickable = isLongClickable(); 3451 3452 if (mSelector != null) { 3453 final Drawable d = mSelector.getCurrent(); 3454 if (d != null && d instanceof TransitionDrawable) { 3455 if (longClickable) { 3456 ((TransitionDrawable) d).startTransition(longPressTimeout); 3457 } else { 3458 ((TransitionDrawable) d).resetTransition(); 3459 } 3460 } 3461 mSelector.setHotspot(x, y); 3462 } 3463 3464 if (longClickable) { 3465 if (mPendingCheckForLongPress == null) { 3466 mPendingCheckForLongPress = new CheckForLongPress(); 3467 } 3468 mPendingCheckForLongPress.setCoords(x, y); 3469 mPendingCheckForLongPress.rememberWindowAttachCount(); 3470 postDelayed(mPendingCheckForLongPress, longPressTimeout); 3471 } else { 3472 mTouchMode = TOUCH_MODE_DONE_WAITING; 3473 } 3474 } else { 3475 mTouchMode = TOUCH_MODE_DONE_WAITING; 3476 } 3477 } 3478 } 3479 } 3480 } 3481 startScrollIfNeeded(int x, int y, MotionEvent vtev)3482 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) { 3483 // Check if we have moved far enough that it looks more like a 3484 // scroll than a tap 3485 final int deltaY = y - mMotionY; 3486 final int distance = Math.abs(deltaY); 3487 final boolean overscroll = mScrollY != 0; 3488 if ((overscroll || distance > mTouchSlop) && 3489 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { 3490 createScrollingCache(); 3491 if (overscroll) { 3492 mTouchMode = TOUCH_MODE_OVERSCROLL; 3493 mMotionCorrection = 0; 3494 } else { 3495 mTouchMode = TOUCH_MODE_SCROLL; 3496 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop; 3497 } 3498 removeCallbacks(mPendingCheckForLongPress); 3499 setPressed(false); 3500 final View motionView = getChildAt(mMotionPosition - mFirstPosition); 3501 if (motionView != null) { 3502 motionView.setPressed(false); 3503 } 3504 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 3505 // Time to start stealing events! Once we've stolen them, don't let anyone 3506 // steal from us 3507 final ViewParent parent = getParent(); 3508 if (parent != null) { 3509 parent.requestDisallowInterceptTouchEvent(true); 3510 } 3511 scrollIfNeeded(x, y, vtev); 3512 return true; 3513 } 3514 3515 return false; 3516 } 3517 scrollIfNeeded(int x, int y, MotionEvent vtev)3518 private void scrollIfNeeded(int x, int y, MotionEvent vtev) { 3519 int rawDeltaY = y - mMotionY; 3520 int scrollOffsetCorrection = 0; 3521 int scrollConsumedCorrection = 0; 3522 if (mLastY == Integer.MIN_VALUE) { 3523 rawDeltaY -= mMotionCorrection; 3524 } 3525 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY, 3526 mScrollConsumed, mScrollOffset)) { 3527 rawDeltaY += mScrollConsumed[1]; 3528 scrollOffsetCorrection = -mScrollOffset[1]; 3529 scrollConsumedCorrection = mScrollConsumed[1]; 3530 if (vtev != null) { 3531 vtev.offsetLocation(0, mScrollOffset[1]); 3532 mNestedYOffset += mScrollOffset[1]; 3533 } 3534 } 3535 final int deltaY = rawDeltaY; 3536 int incrementalDeltaY = 3537 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY; 3538 int lastYCorrection = 0; 3539 3540 if (mTouchMode == TOUCH_MODE_SCROLL) { 3541 if (PROFILE_SCROLLING) { 3542 if (!mScrollProfilingStarted) { 3543 Debug.startMethodTracing("AbsListViewScroll"); 3544 mScrollProfilingStarted = true; 3545 } 3546 } 3547 3548 if (mScrollStrictSpan == null) { 3549 // If it's non-null, we're already in a scroll. 3550 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll"); 3551 } 3552 3553 if (y != mLastY) { 3554 // We may be here after stopping a fling and continuing to scroll. 3555 // If so, we haven't disallowed intercepting touch events yet. 3556 // Make sure that we do so in case we're in a parent that can intercept. 3557 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 && 3558 Math.abs(rawDeltaY) > mTouchSlop) { 3559 final ViewParent parent = getParent(); 3560 if (parent != null) { 3561 parent.requestDisallowInterceptTouchEvent(true); 3562 } 3563 } 3564 3565 final int motionIndex; 3566 if (mMotionPosition >= 0) { 3567 motionIndex = mMotionPosition - mFirstPosition; 3568 } else { 3569 // If we don't have a motion position that we can reliably track, 3570 // pick something in the middle to make a best guess at things below. 3571 motionIndex = getChildCount() / 2; 3572 } 3573 3574 int motionViewPrevTop = 0; 3575 View motionView = this.getChildAt(motionIndex); 3576 if (motionView != null) { 3577 motionViewPrevTop = motionView.getTop(); 3578 } 3579 3580 // No need to do all this work if we're not going to move anyway 3581 boolean atEdge = false; 3582 if (incrementalDeltaY != 0) { 3583 atEdge = trackMotionScroll(deltaY, incrementalDeltaY); 3584 } 3585 3586 // Check to see if we have bumped into the scroll limit 3587 motionView = this.getChildAt(motionIndex); 3588 if (motionView != null) { 3589 // Check if the top of the motion view is where it is 3590 // supposed to be 3591 final int motionViewRealTop = motionView.getTop(); 3592 if (atEdge) { 3593 // Apply overscroll 3594 3595 int overscroll = -incrementalDeltaY - 3596 (motionViewRealTop - motionViewPrevTop); 3597 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll, 3598 mScrollOffset)) { 3599 lastYCorrection -= mScrollOffset[1]; 3600 if (vtev != null) { 3601 vtev.offsetLocation(0, mScrollOffset[1]); 3602 mNestedYOffset += mScrollOffset[1]; 3603 } 3604 } else { 3605 final boolean atOverscrollEdge = overScrollBy(0, overscroll, 3606 0, mScrollY, 0, 0, 0, mOverscrollDistance, true); 3607 3608 if (atOverscrollEdge && mVelocityTracker != null) { 3609 // Don't allow overfling if we're at the edge 3610 mVelocityTracker.clear(); 3611 } 3612 3613 final int overscrollMode = getOverScrollMode(); 3614 if (overscrollMode == OVER_SCROLL_ALWAYS || 3615 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && 3616 !contentFits())) { 3617 if (!atOverscrollEdge) { 3618 mDirection = 0; // Reset when entering overscroll. 3619 mTouchMode = TOUCH_MODE_OVERSCROLL; 3620 } 3621 if (incrementalDeltaY > 0) { 3622 mEdgeGlowTop.onPull((float) -overscroll / getHeight(), 3623 (float) x / getWidth()); 3624 if (!mEdgeGlowBottom.isFinished()) { 3625 mEdgeGlowBottom.onRelease(); 3626 } 3627 invalidateTopGlow(); 3628 } else if (incrementalDeltaY < 0) { 3629 mEdgeGlowBottom.onPull((float) overscroll / getHeight(), 3630 1.f - (float) x / getWidth()); 3631 if (!mEdgeGlowTop.isFinished()) { 3632 mEdgeGlowTop.onRelease(); 3633 } 3634 invalidateBottomGlow(); 3635 } 3636 } 3637 } 3638 } 3639 mMotionY = y + lastYCorrection + scrollOffsetCorrection; 3640 } 3641 mLastY = y + lastYCorrection + scrollOffsetCorrection; 3642 } 3643 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) { 3644 if (y != mLastY) { 3645 final int oldScroll = mScrollY; 3646 final int newScroll = oldScroll - incrementalDeltaY; 3647 int newDirection = y > mLastY ? 1 : -1; 3648 3649 if (mDirection == 0) { 3650 mDirection = newDirection; 3651 } 3652 3653 int overScrollDistance = -incrementalDeltaY; 3654 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) { 3655 overScrollDistance = -oldScroll; 3656 incrementalDeltaY += overScrollDistance; 3657 } else { 3658 incrementalDeltaY = 0; 3659 } 3660 3661 if (overScrollDistance != 0) { 3662 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0, 3663 0, mOverscrollDistance, true); 3664 final int overscrollMode = getOverScrollMode(); 3665 if (overscrollMode == OVER_SCROLL_ALWAYS || 3666 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && 3667 !contentFits())) { 3668 if (rawDeltaY > 0) { 3669 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(), 3670 (float) x / getWidth()); 3671 if (!mEdgeGlowBottom.isFinished()) { 3672 mEdgeGlowBottom.onRelease(); 3673 } 3674 invalidateTopGlow(); 3675 } else if (rawDeltaY < 0) { 3676 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(), 3677 1.f - (float) x / getWidth()); 3678 if (!mEdgeGlowTop.isFinished()) { 3679 mEdgeGlowTop.onRelease(); 3680 } 3681 invalidateBottomGlow(); 3682 } 3683 } 3684 } 3685 3686 if (incrementalDeltaY != 0) { 3687 // Coming back to 'real' list scrolling 3688 if (mScrollY != 0) { 3689 mScrollY = 0; 3690 invalidateParentIfNeeded(); 3691 } 3692 3693 trackMotionScroll(incrementalDeltaY, incrementalDeltaY); 3694 3695 mTouchMode = TOUCH_MODE_SCROLL; 3696 3697 // We did not scroll the full amount. Treat this essentially like the 3698 // start of a new touch scroll 3699 final int motionPosition = findClosestMotionRow(y); 3700 3701 mMotionCorrection = 0; 3702 View motionView = getChildAt(motionPosition - mFirstPosition); 3703 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0; 3704 mMotionY = y + scrollOffsetCorrection; 3705 mMotionPosition = motionPosition; 3706 } 3707 mLastY = y + lastYCorrection + scrollOffsetCorrection; 3708 mDirection = newDirection; 3709 } 3710 } 3711 } 3712 invalidateTopGlow()3713 private void invalidateTopGlow() { 3714 if (mEdgeGlowTop == null) { 3715 return; 3716 } 3717 final boolean clipToPadding = getClipToPadding(); 3718 final int top = clipToPadding ? mPaddingTop : 0; 3719 final int left = clipToPadding ? mPaddingLeft : 0; 3720 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); 3721 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight()); 3722 } 3723 invalidateBottomGlow()3724 private void invalidateBottomGlow() { 3725 if (mEdgeGlowBottom == null) { 3726 return; 3727 } 3728 final boolean clipToPadding = getClipToPadding(); 3729 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight(); 3730 final int left = clipToPadding ? mPaddingLeft : 0; 3731 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); 3732 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom); 3733 } 3734 3735 @Override onTouchModeChanged(boolean isInTouchMode)3736 public void onTouchModeChanged(boolean isInTouchMode) { 3737 if (isInTouchMode) { 3738 // Get rid of the selection when we enter touch mode 3739 hideSelector(); 3740 // Layout, but only if we already have done so previously. 3741 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore 3742 // state.) 3743 if (getHeight() > 0 && getChildCount() > 0) { 3744 // We do not lose focus initiating a touch (since AbsListView is focusable in 3745 // touch mode). Force an initial layout to get rid of the selection. 3746 layoutChildren(); 3747 } 3748 updateSelectorState(); 3749 } else { 3750 int touchMode = mTouchMode; 3751 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { 3752 if (mFlingRunnable != null) { 3753 mFlingRunnable.endFling(); 3754 } 3755 if (mPositionScroller != null) { 3756 mPositionScroller.stop(); 3757 } 3758 3759 if (mScrollY != 0) { 3760 mScrollY = 0; 3761 invalidateParentCaches(); 3762 finishGlows(); 3763 invalidate(); 3764 } 3765 } 3766 } 3767 } 3768 3769 /** @hide */ 3770 @Override handleScrollBarDragging(MotionEvent event)3771 protected boolean handleScrollBarDragging(MotionEvent event) { 3772 // Doesn't support normal scroll bar dragging. Use FastScroller. 3773 return false; 3774 } 3775 3776 @Override onTouchEvent(MotionEvent ev)3777 public boolean onTouchEvent(MotionEvent ev) { 3778 if (!isEnabled()) { 3779 // A disabled view that is clickable still consumes the touch 3780 // events, it just doesn't respond to them. 3781 return isClickable() || isLongClickable(); 3782 } 3783 3784 if (mPositionScroller != null) { 3785 mPositionScroller.stop(); 3786 } 3787 3788 if (mIsDetaching || !isAttachedToWindow()) { 3789 // Something isn't right. 3790 // Since we rely on being attached to get data set change notifications, 3791 // don't risk doing anything where we might try to resync and find things 3792 // in a bogus state. 3793 return false; 3794 } 3795 3796 startNestedScroll(SCROLL_AXIS_VERTICAL); 3797 3798 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) { 3799 return true; 3800 } 3801 3802 initVelocityTrackerIfNotExists(); 3803 final MotionEvent vtev = MotionEvent.obtain(ev); 3804 3805 final int actionMasked = ev.getActionMasked(); 3806 if (actionMasked == MotionEvent.ACTION_DOWN) { 3807 mNestedYOffset = 0; 3808 } 3809 vtev.offsetLocation(0, mNestedYOffset); 3810 switch (actionMasked) { 3811 case MotionEvent.ACTION_DOWN: { 3812 onTouchDown(ev); 3813 break; 3814 } 3815 3816 case MotionEvent.ACTION_MOVE: { 3817 onTouchMove(ev, vtev); 3818 break; 3819 } 3820 3821 case MotionEvent.ACTION_UP: { 3822 onTouchUp(ev); 3823 break; 3824 } 3825 3826 case MotionEvent.ACTION_CANCEL: { 3827 onTouchCancel(); 3828 break; 3829 } 3830 3831 case MotionEvent.ACTION_POINTER_UP: { 3832 onSecondaryPointerUp(ev); 3833 final int x = mMotionX; 3834 final int y = mMotionY; 3835 final int motionPosition = pointToPosition(x, y); 3836 if (motionPosition >= 0) { 3837 // Remember where the motion event started 3838 final View child = getChildAt(motionPosition - mFirstPosition); 3839 mMotionViewOriginalTop = child.getTop(); 3840 mMotionPosition = motionPosition; 3841 } 3842 mLastY = y; 3843 break; 3844 } 3845 3846 case MotionEvent.ACTION_POINTER_DOWN: { 3847 // New pointers take over dragging duties 3848 final int index = ev.getActionIndex(); 3849 final int id = ev.getPointerId(index); 3850 final int x = (int) ev.getX(index); 3851 final int y = (int) ev.getY(index); 3852 mMotionCorrection = 0; 3853 mActivePointerId = id; 3854 mMotionX = x; 3855 mMotionY = y; 3856 final int motionPosition = pointToPosition(x, y); 3857 if (motionPosition >= 0) { 3858 // Remember where the motion event started 3859 final View child = getChildAt(motionPosition - mFirstPosition); 3860 mMotionViewOriginalTop = child.getTop(); 3861 mMotionPosition = motionPosition; 3862 } 3863 mLastY = y; 3864 break; 3865 } 3866 } 3867 3868 if (mVelocityTracker != null) { 3869 mVelocityTracker.addMovement(vtev); 3870 } 3871 vtev.recycle(); 3872 return true; 3873 } 3874 onTouchDown(MotionEvent ev)3875 private void onTouchDown(MotionEvent ev) { 3876 mHasPerformedLongPress = false; 3877 mActivePointerId = ev.getPointerId(0); 3878 hideSelector(); 3879 3880 if (mTouchMode == TOUCH_MODE_OVERFLING) { 3881 // Stopped the fling. It is a scroll. 3882 mFlingRunnable.endFling(); 3883 if (mPositionScroller != null) { 3884 mPositionScroller.stop(); 3885 } 3886 mTouchMode = TOUCH_MODE_OVERSCROLL; 3887 mMotionX = (int) ev.getX(); 3888 mMotionY = (int) ev.getY(); 3889 mLastY = mMotionY; 3890 mMotionCorrection = 0; 3891 mDirection = 0; 3892 } else { 3893 final int x = (int) ev.getX(); 3894 final int y = (int) ev.getY(); 3895 int motionPosition = pointToPosition(x, y); 3896 3897 if (!mDataChanged) { 3898 if (mTouchMode == TOUCH_MODE_FLING) { 3899 // Stopped a fling. It is a scroll. 3900 createScrollingCache(); 3901 mTouchMode = TOUCH_MODE_SCROLL; 3902 mMotionCorrection = 0; 3903 motionPosition = findMotionRow(y); 3904 mFlingRunnable.flywheelTouch(); 3905 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) { 3906 // User clicked on an actual view (and was not stopping a 3907 // fling). It might be a click or a scroll. Assume it is a 3908 // click until proven otherwise. 3909 mTouchMode = TOUCH_MODE_DOWN; 3910 3911 // FIXME Debounce 3912 if (mPendingCheckForTap == null) { 3913 mPendingCheckForTap = new CheckForTap(); 3914 } 3915 3916 mPendingCheckForTap.x = ev.getX(); 3917 mPendingCheckForTap.y = ev.getY(); 3918 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 3919 } 3920 } 3921 3922 if (motionPosition >= 0) { 3923 // Remember where the motion event started 3924 final View v = getChildAt(motionPosition - mFirstPosition); 3925 mMotionViewOriginalTop = v.getTop(); 3926 } 3927 3928 mMotionX = x; 3929 mMotionY = y; 3930 mMotionPosition = motionPosition; 3931 mLastY = Integer.MIN_VALUE; 3932 } 3933 3934 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION 3935 && performButtonActionOnTouchDown(ev)) { 3936 removeCallbacks(mPendingCheckForTap); 3937 } 3938 } 3939 onTouchMove(MotionEvent ev, MotionEvent vtev)3940 private void onTouchMove(MotionEvent ev, MotionEvent vtev) { 3941 if (mHasPerformedLongPress) { 3942 // Consume all move events following a successful long press. 3943 return; 3944 } 3945 3946 int pointerIndex = ev.findPointerIndex(mActivePointerId); 3947 if (pointerIndex == -1) { 3948 pointerIndex = 0; 3949 mActivePointerId = ev.getPointerId(pointerIndex); 3950 } 3951 3952 if (mDataChanged) { 3953 // Re-sync everything if data has been changed 3954 // since the scroll operation can query the adapter. 3955 layoutChildren(); 3956 } 3957 3958 final int y = (int) ev.getY(pointerIndex); 3959 3960 switch (mTouchMode) { 3961 case TOUCH_MODE_DOWN: 3962 case TOUCH_MODE_TAP: 3963 case TOUCH_MODE_DONE_WAITING: 3964 // Check if we have moved far enough that it looks more like a 3965 // scroll than a tap. If so, we'll enter scrolling mode. 3966 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) { 3967 break; 3968 } 3969 // Otherwise, check containment within list bounds. If we're 3970 // outside bounds, cancel any active presses. 3971 final View motionView = getChildAt(mMotionPosition - mFirstPosition); 3972 final float x = ev.getX(pointerIndex); 3973 if (!pointInView(x, y, mTouchSlop)) { 3974 setPressed(false); 3975 if (motionView != null) { 3976 motionView.setPressed(false); 3977 } 3978 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? 3979 mPendingCheckForTap : mPendingCheckForLongPress); 3980 mTouchMode = TOUCH_MODE_DONE_WAITING; 3981 updateSelectorState(); 3982 } else if (motionView != null) { 3983 // Still within bounds, update the hotspot. 3984 final float[] point = mTmpPoint; 3985 point[0] = x; 3986 point[1] = y; 3987 transformPointToViewLocal(point, motionView); 3988 motionView.drawableHotspotChanged(point[0], point[1]); 3989 } 3990 break; 3991 case TOUCH_MODE_SCROLL: 3992 case TOUCH_MODE_OVERSCROLL: 3993 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev); 3994 break; 3995 } 3996 } 3997 onTouchUp(MotionEvent ev)3998 private void onTouchUp(MotionEvent ev) { 3999 switch (mTouchMode) { 4000 case TOUCH_MODE_DOWN: 4001 case TOUCH_MODE_TAP: 4002 case TOUCH_MODE_DONE_WAITING: 4003 final int motionPosition = mMotionPosition; 4004 final View child = getChildAt(motionPosition - mFirstPosition); 4005 if (child != null) { 4006 if (mTouchMode != TOUCH_MODE_DOWN) { 4007 child.setPressed(false); 4008 } 4009 4010 final float x = ev.getX(); 4011 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right; 4012 if (inList && !child.hasExplicitFocusable()) { 4013 if (mPerformClick == null) { 4014 mPerformClick = new PerformClick(); 4015 } 4016 4017 final AbsListView.PerformClick performClick = mPerformClick; 4018 performClick.mClickMotionPosition = motionPosition; 4019 performClick.rememberWindowAttachCount(); 4020 4021 mResurrectToPosition = motionPosition; 4022 4023 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { 4024 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? 4025 mPendingCheckForTap : mPendingCheckForLongPress); 4026 mLayoutMode = LAYOUT_NORMAL; 4027 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 4028 mTouchMode = TOUCH_MODE_TAP; 4029 setSelectedPositionInt(mMotionPosition); 4030 layoutChildren(); 4031 child.setPressed(true); 4032 positionSelector(mMotionPosition, child); 4033 setPressed(true); 4034 if (mSelector != null) { 4035 Drawable d = mSelector.getCurrent(); 4036 if (d != null && d instanceof TransitionDrawable) { 4037 ((TransitionDrawable) d).resetTransition(); 4038 } 4039 mSelector.setHotspot(x, ev.getY()); 4040 } 4041 if (mTouchModeReset != null) { 4042 removeCallbacks(mTouchModeReset); 4043 } 4044 mTouchModeReset = new Runnable() { 4045 @Override 4046 public void run() { 4047 mTouchModeReset = null; 4048 mTouchMode = TOUCH_MODE_REST; 4049 child.setPressed(false); 4050 setPressed(false); 4051 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) { 4052 performClick.run(); 4053 } 4054 } 4055 }; 4056 postDelayed(mTouchModeReset, 4057 ViewConfiguration.getPressedStateDuration()); 4058 } else { 4059 mTouchMode = TOUCH_MODE_REST; 4060 updateSelectorState(); 4061 } 4062 return; 4063 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 4064 performClick.run(); 4065 } 4066 } 4067 } 4068 mTouchMode = TOUCH_MODE_REST; 4069 updateSelectorState(); 4070 break; 4071 case TOUCH_MODE_SCROLL: 4072 final int childCount = getChildCount(); 4073 if (childCount > 0) { 4074 final int firstChildTop = getChildAt(0).getTop(); 4075 final int lastChildBottom = getChildAt(childCount - 1).getBottom(); 4076 final int contentTop = mListPadding.top; 4077 final int contentBottom = getHeight() - mListPadding.bottom; 4078 if (mFirstPosition == 0 && firstChildTop >= contentTop && 4079 mFirstPosition + childCount < mItemCount && 4080 lastChildBottom <= getHeight() - contentBottom) { 4081 mTouchMode = TOUCH_MODE_REST; 4082 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4083 } else { 4084 final VelocityTracker velocityTracker = mVelocityTracker; 4085 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 4086 4087 final int initialVelocity = (int) 4088 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale); 4089 // Fling if we have enough velocity and we aren't at a boundary. 4090 // Since we can potentially overfling more than we can overscroll, don't 4091 // allow the weird behavior where you can scroll to a boundary then 4092 // fling further. 4093 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity; 4094 if (flingVelocity && 4095 !((mFirstPosition == 0 && 4096 firstChildTop == contentTop - mOverscrollDistance) || 4097 (mFirstPosition + childCount == mItemCount && 4098 lastChildBottom == contentBottom + mOverscrollDistance))) { 4099 if (!dispatchNestedPreFling(0, -initialVelocity)) { 4100 if (mFlingRunnable == null) { 4101 mFlingRunnable = new FlingRunnable(); 4102 } 4103 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4104 mFlingRunnable.start(-initialVelocity); 4105 dispatchNestedFling(0, -initialVelocity, true); 4106 } else { 4107 mTouchMode = TOUCH_MODE_REST; 4108 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4109 } 4110 } else { 4111 mTouchMode = TOUCH_MODE_REST; 4112 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4113 if (mFlingRunnable != null) { 4114 mFlingRunnable.endFling(); 4115 } 4116 if (mPositionScroller != null) { 4117 mPositionScroller.stop(); 4118 } 4119 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) { 4120 dispatchNestedFling(0, -initialVelocity, false); 4121 } 4122 } 4123 } 4124 } else { 4125 mTouchMode = TOUCH_MODE_REST; 4126 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4127 } 4128 break; 4129 4130 case TOUCH_MODE_OVERSCROLL: 4131 if (mFlingRunnable == null) { 4132 mFlingRunnable = new FlingRunnable(); 4133 } 4134 final VelocityTracker velocityTracker = mVelocityTracker; 4135 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 4136 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 4137 4138 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4139 if (Math.abs(initialVelocity) > mMinimumVelocity) { 4140 mFlingRunnable.startOverfling(-initialVelocity); 4141 } else { 4142 mFlingRunnable.startSpringback(); 4143 } 4144 4145 break; 4146 } 4147 4148 setPressed(false); 4149 4150 if (mEdgeGlowTop != null) { 4151 mEdgeGlowTop.onRelease(); 4152 mEdgeGlowBottom.onRelease(); 4153 } 4154 4155 // Need to redraw since we probably aren't drawing the selector anymore 4156 invalidate(); 4157 removeCallbacks(mPendingCheckForLongPress); 4158 recycleVelocityTracker(); 4159 4160 mActivePointerId = INVALID_POINTER; 4161 4162 if (PROFILE_SCROLLING) { 4163 if (mScrollProfilingStarted) { 4164 Debug.stopMethodTracing(); 4165 mScrollProfilingStarted = false; 4166 } 4167 } 4168 4169 if (mScrollStrictSpan != null) { 4170 mScrollStrictSpan.finish(); 4171 mScrollStrictSpan = null; 4172 } 4173 } 4174 onTouchCancel()4175 private void onTouchCancel() { 4176 switch (mTouchMode) { 4177 case TOUCH_MODE_OVERSCROLL: 4178 if (mFlingRunnable == null) { 4179 mFlingRunnable = new FlingRunnable(); 4180 } 4181 mFlingRunnable.startSpringback(); 4182 break; 4183 4184 case TOUCH_MODE_OVERFLING: 4185 // Do nothing - let it play out. 4186 break; 4187 4188 default: 4189 mTouchMode = TOUCH_MODE_REST; 4190 setPressed(false); 4191 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition); 4192 if (motionView != null) { 4193 motionView.setPressed(false); 4194 } 4195 clearScrollingCache(); 4196 removeCallbacks(mPendingCheckForLongPress); 4197 recycleVelocityTracker(); 4198 } 4199 4200 if (mEdgeGlowTop != null) { 4201 mEdgeGlowTop.onRelease(); 4202 mEdgeGlowBottom.onRelease(); 4203 } 4204 mActivePointerId = INVALID_POINTER; 4205 } 4206 4207 @Override onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)4208 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 4209 if (mScrollY != scrollY) { 4210 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY); 4211 mScrollY = scrollY; 4212 invalidateParentIfNeeded(); 4213 4214 awakenScrollBars(); 4215 } 4216 } 4217 4218 @Override onGenericMotionEvent(MotionEvent event)4219 public boolean onGenericMotionEvent(MotionEvent event) { 4220 switch (event.getAction()) { 4221 case MotionEvent.ACTION_SCROLL: 4222 final float axisValue; 4223 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { 4224 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 4225 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) { 4226 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL); 4227 } else { 4228 axisValue = 0; 4229 } 4230 4231 final int delta = Math.round(axisValue * mVerticalScrollFactor); 4232 if (delta != 0) { 4233 if (!trackMotionScroll(delta, delta)) { 4234 return true; 4235 } 4236 } 4237 break; 4238 case MotionEvent.ACTION_BUTTON_PRESS: 4239 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { 4240 int actionButton = event.getActionButton(); 4241 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY 4242 || actionButton == MotionEvent.BUTTON_SECONDARY) 4243 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) { 4244 if (performStylusButtonPressAction(event)) { 4245 removeCallbacks(mPendingCheckForLongPress); 4246 removeCallbacks(mPendingCheckForTap); 4247 } 4248 } 4249 } 4250 break; 4251 } 4252 4253 return super.onGenericMotionEvent(event); 4254 } 4255 4256 /** 4257 * Initiate a fling with the given velocity. 4258 * 4259 * <p>Applications can use this method to manually initiate a fling as if the user 4260 * initiated it via touch interaction.</p> 4261 * 4262 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of 4263 * content, not velocity of a touch that initiated the fling. 4264 */ fling(int velocityY)4265 public void fling(int velocityY) { 4266 if (mFlingRunnable == null) { 4267 mFlingRunnable = new FlingRunnable(); 4268 } 4269 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4270 mFlingRunnable.start(velocityY); 4271 } 4272 4273 @Override onStartNestedScroll(View child, View target, int nestedScrollAxes)4274 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 4275 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0); 4276 } 4277 4278 @Override onNestedScrollAccepted(View child, View target, int axes)4279 public void onNestedScrollAccepted(View child, View target, int axes) { 4280 super.onNestedScrollAccepted(child, target, axes); 4281 startNestedScroll(SCROLL_AXIS_VERTICAL); 4282 } 4283 4284 @Override onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)4285 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 4286 int dxUnconsumed, int dyUnconsumed) { 4287 final int motionIndex = getChildCount() / 2; 4288 final View motionView = getChildAt(motionIndex); 4289 final int oldTop = motionView != null ? motionView.getTop() : 0; 4290 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) { 4291 int myUnconsumed = dyUnconsumed; 4292 int myConsumed = 0; 4293 if (motionView != null) { 4294 myConsumed = motionView.getTop() - oldTop; 4295 myUnconsumed -= myConsumed; 4296 } 4297 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); 4298 } 4299 } 4300 4301 @Override onNestedFling(View target, float velocityX, float velocityY, boolean consumed)4302 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 4303 final int childCount = getChildCount(); 4304 if (!consumed && childCount > 0 && canScrollList((int) velocityY) && 4305 Math.abs(velocityY) > mMinimumVelocity) { 4306 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4307 if (mFlingRunnable == null) { 4308 mFlingRunnable = new FlingRunnable(); 4309 } 4310 if (!dispatchNestedPreFling(0, velocityY)) { 4311 mFlingRunnable.start((int) velocityY); 4312 } 4313 return true; 4314 } 4315 return dispatchNestedFling(velocityX, velocityY, consumed); 4316 } 4317 4318 @Override draw(Canvas canvas)4319 public void draw(Canvas canvas) { 4320 super.draw(canvas); 4321 if (mEdgeGlowTop != null) { 4322 final int scrollY = mScrollY; 4323 final boolean clipToPadding = getClipToPadding(); 4324 final int width; 4325 final int height; 4326 final int translateX; 4327 final int translateY; 4328 4329 if (clipToPadding) { 4330 width = getWidth() - mPaddingLeft - mPaddingRight; 4331 height = getHeight() - mPaddingTop - mPaddingBottom; 4332 translateX = mPaddingLeft; 4333 translateY = mPaddingTop; 4334 } else { 4335 width = getWidth(); 4336 height = getHeight(); 4337 translateX = 0; 4338 translateY = 0; 4339 } 4340 if (!mEdgeGlowTop.isFinished()) { 4341 final int restoreCount = canvas.save(); 4342 canvas.clipRect(translateX, translateY, 4343 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight()); 4344 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY; 4345 canvas.translate(translateX, edgeY); 4346 mEdgeGlowTop.setSize(width, height); 4347 if (mEdgeGlowTop.draw(canvas)) { 4348 invalidateTopGlow(); 4349 } 4350 canvas.restoreToCount(restoreCount); 4351 } 4352 if (!mEdgeGlowBottom.isFinished()) { 4353 final int restoreCount = canvas.save(); 4354 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(), 4355 translateX + width, translateY + height); 4356 final int edgeX = -width + translateX; 4357 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess) 4358 - (clipToPadding ? mPaddingBottom : 0); 4359 canvas.translate(edgeX, edgeY); 4360 canvas.rotate(180, width, 0); 4361 mEdgeGlowBottom.setSize(width, height); 4362 if (mEdgeGlowBottom.draw(canvas)) { 4363 invalidateBottomGlow(); 4364 } 4365 canvas.restoreToCount(restoreCount); 4366 } 4367 } 4368 } 4369 initOrResetVelocityTracker()4370 private void initOrResetVelocityTracker() { 4371 if (mVelocityTracker == null) { 4372 mVelocityTracker = VelocityTracker.obtain(); 4373 } else { 4374 mVelocityTracker.clear(); 4375 } 4376 } 4377 initVelocityTrackerIfNotExists()4378 private void initVelocityTrackerIfNotExists() { 4379 if (mVelocityTracker == null) { 4380 mVelocityTracker = VelocityTracker.obtain(); 4381 } 4382 } 4383 recycleVelocityTracker()4384 private void recycleVelocityTracker() { 4385 if (mVelocityTracker != null) { 4386 mVelocityTracker.recycle(); 4387 mVelocityTracker = null; 4388 } 4389 } 4390 4391 @Override requestDisallowInterceptTouchEvent(boolean disallowIntercept)4392 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 4393 if (disallowIntercept) { 4394 recycleVelocityTracker(); 4395 } 4396 super.requestDisallowInterceptTouchEvent(disallowIntercept); 4397 } 4398 4399 @Override onInterceptHoverEvent(MotionEvent event)4400 public boolean onInterceptHoverEvent(MotionEvent event) { 4401 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) { 4402 return true; 4403 } 4404 4405 return super.onInterceptHoverEvent(event); 4406 } 4407 4408 @Override onResolvePointerIcon(MotionEvent event, int pointerIndex)4409 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 4410 if (mFastScroll != null) { 4411 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex); 4412 if (pointerIcon != null) { 4413 return pointerIcon; 4414 } 4415 } 4416 return super.onResolvePointerIcon(event, pointerIndex); 4417 } 4418 4419 @Override onInterceptTouchEvent(MotionEvent ev)4420 public boolean onInterceptTouchEvent(MotionEvent ev) { 4421 final int actionMasked = ev.getActionMasked(); 4422 View v; 4423 4424 if (mPositionScroller != null) { 4425 mPositionScroller.stop(); 4426 } 4427 4428 if (mIsDetaching || !isAttachedToWindow()) { 4429 // Something isn't right. 4430 // Since we rely on being attached to get data set change notifications, 4431 // don't risk doing anything where we might try to resync and find things 4432 // in a bogus state. 4433 return false; 4434 } 4435 4436 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) { 4437 return true; 4438 } 4439 4440 switch (actionMasked) { 4441 case MotionEvent.ACTION_DOWN: { 4442 int touchMode = mTouchMode; 4443 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) { 4444 mMotionCorrection = 0; 4445 return true; 4446 } 4447 4448 final int x = (int) ev.getX(); 4449 final int y = (int) ev.getY(); 4450 mActivePointerId = ev.getPointerId(0); 4451 4452 int motionPosition = findMotionRow(y); 4453 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) { 4454 // User clicked on an actual view (and was not stopping a fling). 4455 // Remember where the motion event started 4456 v = getChildAt(motionPosition - mFirstPosition); 4457 mMotionViewOriginalTop = v.getTop(); 4458 mMotionX = x; 4459 mMotionY = y; 4460 mMotionPosition = motionPosition; 4461 mTouchMode = TOUCH_MODE_DOWN; 4462 clearScrollingCache(); 4463 } 4464 mLastY = Integer.MIN_VALUE; 4465 initOrResetVelocityTracker(); 4466 mVelocityTracker.addMovement(ev); 4467 mNestedYOffset = 0; 4468 startNestedScroll(SCROLL_AXIS_VERTICAL); 4469 if (touchMode == TOUCH_MODE_FLING) { 4470 return true; 4471 } 4472 break; 4473 } 4474 4475 case MotionEvent.ACTION_MOVE: { 4476 switch (mTouchMode) { 4477 case TOUCH_MODE_DOWN: 4478 int pointerIndex = ev.findPointerIndex(mActivePointerId); 4479 if (pointerIndex == -1) { 4480 pointerIndex = 0; 4481 mActivePointerId = ev.getPointerId(pointerIndex); 4482 } 4483 final int y = (int) ev.getY(pointerIndex); 4484 initVelocityTrackerIfNotExists(); 4485 mVelocityTracker.addMovement(ev); 4486 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) { 4487 return true; 4488 } 4489 break; 4490 } 4491 break; 4492 } 4493 4494 case MotionEvent.ACTION_CANCEL: 4495 case MotionEvent.ACTION_UP: { 4496 mTouchMode = TOUCH_MODE_REST; 4497 mActivePointerId = INVALID_POINTER; 4498 recycleVelocityTracker(); 4499 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4500 stopNestedScroll(); 4501 break; 4502 } 4503 4504 case MotionEvent.ACTION_POINTER_UP: { 4505 onSecondaryPointerUp(ev); 4506 break; 4507 } 4508 } 4509 4510 return false; 4511 } 4512 onSecondaryPointerUp(MotionEvent ev)4513 private void onSecondaryPointerUp(MotionEvent ev) { 4514 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 4515 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 4516 final int pointerId = ev.getPointerId(pointerIndex); 4517 if (pointerId == mActivePointerId) { 4518 // This was our active pointer going up. Choose a new 4519 // active pointer and adjust accordingly. 4520 // TODO: Make this decision more intelligent. 4521 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 4522 mMotionX = (int) ev.getX(newPointerIndex); 4523 mMotionY = (int) ev.getY(newPointerIndex); 4524 mMotionCorrection = 0; 4525 mActivePointerId = ev.getPointerId(newPointerIndex); 4526 } 4527 } 4528 4529 /** 4530 * {@inheritDoc} 4531 */ 4532 @Override addTouchables(ArrayList<View> views)4533 public void addTouchables(ArrayList<View> views) { 4534 final int count = getChildCount(); 4535 final int firstPosition = mFirstPosition; 4536 final ListAdapter adapter = mAdapter; 4537 4538 if (adapter == null) { 4539 return; 4540 } 4541 4542 for (int i = 0; i < count; i++) { 4543 final View child = getChildAt(i); 4544 if (adapter.isEnabled(firstPosition + i)) { 4545 views.add(child); 4546 } 4547 child.addTouchables(views); 4548 } 4549 } 4550 4551 /** 4552 * Fires an "on scroll state changed" event to the registered 4553 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change 4554 * is fired only if the specified state is different from the previously known state. 4555 * 4556 * @param newState The new scroll state. 4557 */ reportScrollStateChange(int newState)4558 void reportScrollStateChange(int newState) { 4559 if (newState != mLastScrollState) { 4560 if (mOnScrollListener != null) { 4561 mLastScrollState = newState; 4562 mOnScrollListener.onScrollStateChanged(this, newState); 4563 } 4564 } 4565 } 4566 4567 /** 4568 * Responsible for fling behavior. Use {@link #start(int)} to 4569 * initiate a fling. Each frame of the fling is handled in {@link #run()}. 4570 * A FlingRunnable will keep re-posting itself until the fling is done. 4571 * 4572 */ 4573 private class FlingRunnable implements Runnable { 4574 /** 4575 * Tracks the decay of a fling scroll 4576 */ 4577 private final OverScroller mScroller; 4578 4579 /** 4580 * Y value reported by mScroller on the previous fling 4581 */ 4582 private int mLastFlingY; 4583 4584 /** 4585 * If true, {@link #endFling()} will not report scroll state change to 4586 * {@link OnScrollListener#SCROLL_STATE_IDLE}. 4587 */ 4588 private boolean mSuppressIdleStateChangeCall; 4589 4590 private final Runnable mCheckFlywheel = new Runnable() { 4591 @Override 4592 public void run() { 4593 final int activeId = mActivePointerId; 4594 final VelocityTracker vt = mVelocityTracker; 4595 final OverScroller scroller = mScroller; 4596 if (vt == null || activeId == INVALID_POINTER) { 4597 return; 4598 } 4599 4600 vt.computeCurrentVelocity(1000, mMaximumVelocity); 4601 final float yvel = -vt.getYVelocity(activeId); 4602 4603 if (Math.abs(yvel) >= mMinimumVelocity 4604 && scroller.isScrollingInDirection(0, yvel)) { 4605 // Keep the fling alive a little longer 4606 postDelayed(this, FLYWHEEL_TIMEOUT); 4607 } else { 4608 endFling(); 4609 mTouchMode = TOUCH_MODE_SCROLL; 4610 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 4611 } 4612 } 4613 }; 4614 4615 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds 4616 FlingRunnable()4617 FlingRunnable() { 4618 mScroller = new OverScroller(getContext()); 4619 } 4620 start(int initialVelocity)4621 void start(int initialVelocity) { 4622 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0; 4623 mLastFlingY = initialY; 4624 mScroller.setInterpolator(null); 4625 mScroller.fling(0, initialY, 0, initialVelocity, 4626 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); 4627 mTouchMode = TOUCH_MODE_FLING; 4628 mSuppressIdleStateChangeCall = false; 4629 postOnAnimation(this); 4630 4631 if (PROFILE_FLINGING) { 4632 if (!mFlingProfilingStarted) { 4633 Debug.startMethodTracing("AbsListViewFling"); 4634 mFlingProfilingStarted = true; 4635 } 4636 } 4637 4638 if (mFlingStrictSpan == null) { 4639 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling"); 4640 } 4641 } 4642 4643 void startSpringback() { 4644 mSuppressIdleStateChangeCall = false; 4645 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) { 4646 mTouchMode = TOUCH_MODE_OVERFLING; 4647 invalidate(); 4648 postOnAnimation(this); 4649 } else { 4650 mTouchMode = TOUCH_MODE_REST; 4651 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4652 } 4653 } 4654 4655 void startOverfling(int initialVelocity) { 4656 mScroller.setInterpolator(null); 4657 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 4658 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight()); 4659 mTouchMode = TOUCH_MODE_OVERFLING; 4660 mSuppressIdleStateChangeCall = false; 4661 invalidate(); 4662 postOnAnimation(this); 4663 } 4664 4665 void edgeReached(int delta) { 4666 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance); 4667 final int overscrollMode = getOverScrollMode(); 4668 if (overscrollMode == OVER_SCROLL_ALWAYS || 4669 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { 4670 mTouchMode = TOUCH_MODE_OVERFLING; 4671 final int vel = (int) mScroller.getCurrVelocity(); 4672 if (delta > 0) { 4673 mEdgeGlowTop.onAbsorb(vel); 4674 } else { 4675 mEdgeGlowBottom.onAbsorb(vel); 4676 } 4677 } else { 4678 mTouchMode = TOUCH_MODE_REST; 4679 if (mPositionScroller != null) { 4680 mPositionScroller.stop(); 4681 } 4682 } 4683 invalidate(); 4684 postOnAnimation(this); 4685 } 4686 startScroll(int distance, int duration, boolean linear, boolean suppressEndFlingStateChangeCall)4687 void startScroll(int distance, int duration, boolean linear, 4688 boolean suppressEndFlingStateChangeCall) { 4689 int initialY = distance < 0 ? Integer.MAX_VALUE : 0; 4690 mLastFlingY = initialY; 4691 mScroller.setInterpolator(linear ? sLinearInterpolator : null); 4692 mScroller.startScroll(0, initialY, 0, distance, duration); 4693 mTouchMode = TOUCH_MODE_FLING; 4694 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall; 4695 postOnAnimation(this); 4696 } 4697 4698 void endFling() { 4699 mTouchMode = TOUCH_MODE_REST; 4700 4701 removeCallbacks(this); 4702 removeCallbacks(mCheckFlywheel); 4703 4704 if (!mSuppressIdleStateChangeCall) { 4705 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4706 } 4707 clearScrollingCache(); 4708 mScroller.abortAnimation(); 4709 4710 if (mFlingStrictSpan != null) { 4711 mFlingStrictSpan.finish(); 4712 mFlingStrictSpan = null; 4713 } 4714 } 4715 4716 void flywheelTouch() { 4717 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT); 4718 } 4719 4720 @Override 4721 public void run() { 4722 switch (mTouchMode) { 4723 default: 4724 endFling(); 4725 return; 4726 4727 case TOUCH_MODE_SCROLL: 4728 if (mScroller.isFinished()) { 4729 return; 4730 } 4731 // Fall through 4732 case TOUCH_MODE_FLING: { 4733 if (mDataChanged) { 4734 layoutChildren(); 4735 } 4736 4737 if (mItemCount == 0 || getChildCount() == 0) { 4738 endFling(); 4739 return; 4740 } 4741 4742 final OverScroller scroller = mScroller; 4743 boolean more = scroller.computeScrollOffset(); 4744 final int y = scroller.getCurrY(); 4745 4746 // Flip sign to convert finger direction to list items direction 4747 // (e.g. finger moving down means list is moving towards the top) 4748 int delta = mLastFlingY - y; 4749 4750 // Pretend that each frame of a fling scroll is a touch scroll 4751 if (delta > 0) { 4752 // List is moving towards the top. Use first view as mMotionPosition 4753 mMotionPosition = mFirstPosition; 4754 final View firstView = getChildAt(0); 4755 mMotionViewOriginalTop = firstView.getTop(); 4756 4757 // Don't fling more than 1 screen 4758 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta); 4759 } else { 4760 // List is moving towards the bottom. Use last view as mMotionPosition 4761 int offsetToLast = getChildCount() - 1; 4762 mMotionPosition = mFirstPosition + offsetToLast; 4763 4764 final View lastView = getChildAt(offsetToLast); 4765 mMotionViewOriginalTop = lastView.getTop(); 4766 4767 // Don't fling more than 1 screen 4768 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); 4769 } 4770 4771 // Check to see if we have bumped into the scroll limit 4772 View motionView = getChildAt(mMotionPosition - mFirstPosition); 4773 int oldTop = 0; 4774 if (motionView != null) { 4775 oldTop = motionView.getTop(); 4776 } 4777 4778 // Don't stop just because delta is zero (it could have been rounded) 4779 final boolean atEdge = trackMotionScroll(delta, delta); 4780 final boolean atEnd = atEdge && (delta != 0); 4781 if (atEnd) { 4782 if (motionView != null) { 4783 // Tweak the scroll for how far we overshot 4784 int overshoot = -(delta - (motionView.getTop() - oldTop)); 4785 overScrollBy(0, overshoot, 0, mScrollY, 0, 0, 4786 0, mOverflingDistance, false); 4787 } 4788 if (more) { 4789 edgeReached(delta); 4790 } 4791 break; 4792 } 4793 4794 if (more && !atEnd) { 4795 if (atEdge) invalidate(); 4796 mLastFlingY = y; 4797 postOnAnimation(this); 4798 } else { 4799 endFling(); 4800 4801 if (PROFILE_FLINGING) { 4802 if (mFlingProfilingStarted) { 4803 Debug.stopMethodTracing(); 4804 mFlingProfilingStarted = false; 4805 } 4806 4807 if (mFlingStrictSpan != null) { 4808 mFlingStrictSpan.finish(); 4809 mFlingStrictSpan = null; 4810 } 4811 } 4812 } 4813 break; 4814 } 4815 4816 case TOUCH_MODE_OVERFLING: { 4817 final OverScroller scroller = mScroller; 4818 if (scroller.computeScrollOffset()) { 4819 final int scrollY = mScrollY; 4820 final int currY = scroller.getCurrY(); 4821 final int deltaY = currY - scrollY; 4822 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0, 4823 0, mOverflingDistance, false)) { 4824 final boolean crossDown = scrollY <= 0 && currY > 0; 4825 final boolean crossUp = scrollY >= 0 && currY < 0; 4826 if (crossDown || crossUp) { 4827 int velocity = (int) scroller.getCurrVelocity(); 4828 if (crossUp) velocity = -velocity; 4829 4830 // Don't flywheel from this; we're just continuing things. 4831 scroller.abortAnimation(); 4832 start(velocity); 4833 } else { 4834 startSpringback(); 4835 } 4836 } else { 4837 invalidate(); 4838 postOnAnimation(this); 4839 } 4840 } else { 4841 endFling(); 4842 } 4843 break; 4844 } 4845 } 4846 } 4847 } 4848 4849 /** 4850 * The amount of friction applied to flings. The default value 4851 * is {@link ViewConfiguration#getScrollFriction}. 4852 */ 4853 public void setFriction(float friction) { 4854 if (mFlingRunnable == null) { 4855 mFlingRunnable = new FlingRunnable(); 4856 } 4857 mFlingRunnable.mScroller.setFriction(friction); 4858 } 4859 4860 /** 4861 * Sets a scale factor for the fling velocity. The initial scale 4862 * factor is 1.0. 4863 * 4864 * @param scale The scale factor to multiply the velocity by. 4865 */ 4866 public void setVelocityScale(float scale) { 4867 mVelocityScale = scale; 4868 } 4869 4870 /** 4871 * Override this for better control over position scrolling. 4872 */ 4873 AbsPositionScroller createPositionScroller() { 4874 return new PositionScroller(); 4875 } 4876 4877 /** 4878 * Smoothly scroll to the specified adapter position. The view will 4879 * scroll such that the indicated position is displayed. 4880 * @param position Scroll to this adapter position. 4881 */ 4882 public void smoothScrollToPosition(int position) { 4883 if (mPositionScroller == null) { 4884 mPositionScroller = createPositionScroller(); 4885 } 4886 mPositionScroller.start(position); 4887 } 4888 4889 /** 4890 * Smoothly scroll to the specified adapter position. The view will scroll 4891 * such that the indicated position is displayed <code>offset</code> pixels below 4892 * the top edge of the view. If this is impossible, (e.g. the offset would scroll 4893 * the first or last item beyond the boundaries of the list) it will get as close 4894 * as possible. The scroll will take <code>duration</code> milliseconds to complete. 4895 * 4896 * @param position Position to scroll to 4897 * @param offset Desired distance in pixels of <code>position</code> from the top 4898 * of the view when scrolling is finished 4899 * @param duration Number of milliseconds to use for the scroll 4900 */ 4901 public void smoothScrollToPositionFromTop(int position, int offset, int duration) { 4902 if (mPositionScroller == null) { 4903 mPositionScroller = createPositionScroller(); 4904 } 4905 mPositionScroller.startWithOffset(position, offset, duration); 4906 } 4907 4908 /** 4909 * Smoothly scroll to the specified adapter position. The view will scroll 4910 * such that the indicated position is displayed <code>offset</code> pixels below 4911 * the top edge of the view. If this is impossible, (e.g. the offset would scroll 4912 * the first or last item beyond the boundaries of the list) it will get as close 4913 * as possible. 4914 * 4915 * @param position Position to scroll to 4916 * @param offset Desired distance in pixels of <code>position</code> from the top 4917 * of the view when scrolling is finished 4918 */ 4919 public void smoothScrollToPositionFromTop(int position, int offset) { 4920 if (mPositionScroller == null) { 4921 mPositionScroller = createPositionScroller(); 4922 } 4923 mPositionScroller.startWithOffset(position, offset); 4924 } 4925 4926 /** 4927 * Smoothly scroll to the specified adapter position. The view will 4928 * scroll such that the indicated position is displayed, but it will 4929 * stop early if scrolling further would scroll boundPosition out of 4930 * view. 4931 * 4932 * @param position Scroll to this adapter position. 4933 * @param boundPosition Do not scroll if it would move this adapter 4934 * position out of view. 4935 */ 4936 public void smoothScrollToPosition(int position, int boundPosition) { 4937 if (mPositionScroller == null) { 4938 mPositionScroller = createPositionScroller(); 4939 } 4940 mPositionScroller.start(position, boundPosition); 4941 } 4942 4943 /** 4944 * Smoothly scroll by distance pixels over duration milliseconds. 4945 * @param distance Distance to scroll in pixels. 4946 * @param duration Duration of the scroll animation in milliseconds. 4947 */ 4948 public void smoothScrollBy(int distance, int duration) { 4949 smoothScrollBy(distance, duration, false, false); 4950 } 4951 4952 void smoothScrollBy(int distance, int duration, boolean linear, 4953 boolean suppressEndFlingStateChangeCall) { 4954 if (mFlingRunnable == null) { 4955 mFlingRunnable = new FlingRunnable(); 4956 } 4957 4958 // No sense starting to scroll if we're not going anywhere 4959 final int firstPos = mFirstPosition; 4960 final int childCount = getChildCount(); 4961 final int lastPos = firstPos + childCount; 4962 final int topLimit = getPaddingTop(); 4963 final int bottomLimit = getHeight() - getPaddingBottom(); 4964 4965 if (distance == 0 || mItemCount == 0 || childCount == 0 || 4966 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) || 4967 (lastPos == mItemCount && 4968 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) { 4969 mFlingRunnable.endFling(); 4970 if (mPositionScroller != null) { 4971 mPositionScroller.stop(); 4972 } 4973 } else { 4974 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4975 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall); 4976 } 4977 } 4978 4979 /** 4980 * Allows RemoteViews to scroll relatively to a position. 4981 */ 4982 void smoothScrollByOffset(int position) { 4983 int index = -1; 4984 if (position < 0) { 4985 index = getFirstVisiblePosition(); 4986 } else if (position > 0) { 4987 index = getLastVisiblePosition(); 4988 } 4989 4990 if (index > -1) { 4991 View child = getChildAt(index - getFirstVisiblePosition()); 4992 if (child != null) { 4993 Rect visibleRect = new Rect(); 4994 if (child.getGlobalVisibleRect(visibleRect)) { 4995 // the child is partially visible 4996 int childRectArea = child.getWidth() * child.getHeight(); 4997 int visibleRectArea = visibleRect.width() * visibleRect.height(); 4998 float visibleArea = (visibleRectArea / (float) childRectArea); 4999 final float visibleThreshold = 0.75f; 5000 if ((position < 0) && (visibleArea < visibleThreshold)) { 5001 // the top index is not perceivably visible so offset 5002 // to account for showing that top index as well 5003 ++index; 5004 } else if ((position > 0) && (visibleArea < visibleThreshold)) { 5005 // the bottom index is not perceivably visible so offset 5006 // to account for showing that bottom index as well 5007 --index; 5008 } 5009 } 5010 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position))); 5011 } 5012 } 5013 } 5014 5015 private void createScrollingCache() { 5016 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) { 5017 setChildrenDrawnWithCacheEnabled(true); 5018 setChildrenDrawingCacheEnabled(true); 5019 mCachingStarted = mCachingActive = true; 5020 } 5021 } 5022 5023 private void clearScrollingCache() { 5024 if (!isHardwareAccelerated()) { 5025 if (mClearScrollingCache == null) { 5026 mClearScrollingCache = new Runnable() { 5027 @Override 5028 public void run() { 5029 if (mCachingStarted) { 5030 mCachingStarted = mCachingActive = false; 5031 setChildrenDrawnWithCacheEnabled(false); 5032 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { 5033 setChildrenDrawingCacheEnabled(false); 5034 } 5035 if (!isAlwaysDrawnWithCacheEnabled()) { 5036 invalidate(); 5037 } 5038 } 5039 } 5040 }; 5041 } 5042 post(mClearScrollingCache); 5043 } 5044 } 5045 5046 /** 5047 * Scrolls the list items within the view by a specified number of pixels. 5048 * 5049 * <p>The actual amount of scroll is capped by the list content viewport height 5050 * which is the list height minus top and bottom paddings minus one pixel.</p> 5051 * 5052 * @param y the amount of pixels to scroll by vertically 5053 * @see #canScrollList(int) 5054 */ 5055 public void scrollListBy(int y) { 5056 trackMotionScroll(-y, -y); 5057 } 5058 5059 /** 5060 * Check if the items in the list can be scrolled in a certain direction. 5061 * 5062 * @param direction Negative to check scrolling up, positive to check 5063 * scrolling down. 5064 * @return true if the list can be scrolled in the specified direction, 5065 * false otherwise. 5066 * @see #scrollListBy(int) 5067 */ 5068 public boolean canScrollList(int direction) { 5069 final int childCount = getChildCount(); 5070 if (childCount == 0) { 5071 return false; 5072 } 5073 5074 final int firstPosition = mFirstPosition; 5075 final Rect listPadding = mListPadding; 5076 if (direction > 0) { 5077 final int lastBottom = getChildAt(childCount - 1).getBottom(); 5078 final int lastPosition = firstPosition + childCount; 5079 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom; 5080 } else { 5081 final int firstTop = getChildAt(0).getTop(); 5082 return firstPosition > 0 || firstTop < listPadding.top; 5083 } 5084 } 5085 5086 /** 5087 * Track a motion scroll 5088 * 5089 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion 5090 * began. Positive numbers mean the user's finger is moving down the screen. 5091 * @param incrementalDeltaY Change in deltaY from the previous event. 5092 * @return true if we're already at the beginning/end of the list and have nothing to do. 5093 */ 5094 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { 5095 final int childCount = getChildCount(); 5096 if (childCount == 0) { 5097 return true; 5098 } 5099 5100 final int firstTop = getChildAt(0).getTop(); 5101 final int lastBottom = getChildAt(childCount - 1).getBottom(); 5102 5103 final Rect listPadding = mListPadding; 5104 5105 // "effective padding" In this case is the amount of padding that affects 5106 // how much space should not be filled by items. If we don't clip to padding 5107 // there is no effective padding. 5108 int effectivePaddingTop = 0; 5109 int effectivePaddingBottom = 0; 5110 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 5111 effectivePaddingTop = listPadding.top; 5112 effectivePaddingBottom = listPadding.bottom; 5113 } 5114 5115 // FIXME account for grid vertical spacing too? 5116 final int spaceAbove = effectivePaddingTop - firstTop; 5117 final int end = getHeight() - effectivePaddingBottom; 5118 final int spaceBelow = lastBottom - end; 5119 5120 final int height = getHeight() - mPaddingBottom - mPaddingTop; 5121 if (deltaY < 0) { 5122 deltaY = Math.max(-(height - 1), deltaY); 5123 } else { 5124 deltaY = Math.min(height - 1, deltaY); 5125 } 5126 5127 if (incrementalDeltaY < 0) { 5128 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); 5129 } else { 5130 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); 5131 } 5132 5133 final int firstPosition = mFirstPosition; 5134 5135 // Update our guesses for where the first and last views are 5136 if (firstPosition == 0) { 5137 mFirstPositionDistanceGuess = firstTop - listPadding.top; 5138 } else { 5139 mFirstPositionDistanceGuess += incrementalDeltaY; 5140 } 5141 if (firstPosition + childCount == mItemCount) { 5142 mLastPositionDistanceGuess = lastBottom + listPadding.bottom; 5143 } else { 5144 mLastPositionDistanceGuess += incrementalDeltaY; 5145 } 5146 5147 final boolean cannotScrollDown = (firstPosition == 0 && 5148 firstTop >= listPadding.top && incrementalDeltaY >= 0); 5149 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && 5150 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); 5151 5152 if (cannotScrollDown || cannotScrollUp) { 5153 return incrementalDeltaY != 0; 5154 } 5155 5156 final boolean down = incrementalDeltaY < 0; 5157 5158 final boolean inTouchMode = isInTouchMode(); 5159 if (inTouchMode) { 5160 hideSelector(); 5161 } 5162 5163 final int headerViewsCount = getHeaderViewsCount(); 5164 final int footerViewsStart = mItemCount - getFooterViewsCount(); 5165 5166 int start = 0; 5167 int count = 0; 5168 5169 if (down) { 5170 int top = -incrementalDeltaY; 5171 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 5172 top += listPadding.top; 5173 } 5174 for (int i = 0; i < childCount; i++) { 5175 final View child = getChildAt(i); 5176 if (child.getBottom() >= top) { 5177 break; 5178 } else { 5179 count++; 5180 int position = firstPosition + i; 5181 if (position >= headerViewsCount && position < footerViewsStart) { 5182 // The view will be rebound to new data, clear any 5183 // system-managed transient state. 5184 child.clearAccessibilityFocus(); 5185 mRecycler.addScrapView(child, position); 5186 } 5187 } 5188 } 5189 } else { 5190 int bottom = getHeight() - incrementalDeltaY; 5191 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 5192 bottom -= listPadding.bottom; 5193 } 5194 for (int i = childCount - 1; i >= 0; i--) { 5195 final View child = getChildAt(i); 5196 if (child.getTop() <= bottom) { 5197 break; 5198 } else { 5199 start = i; 5200 count++; 5201 int position = firstPosition + i; 5202 if (position >= headerViewsCount && position < footerViewsStart) { 5203 // The view will be rebound to new data, clear any 5204 // system-managed transient state. 5205 child.clearAccessibilityFocus(); 5206 mRecycler.addScrapView(child, position); 5207 } 5208 } 5209 } 5210 } 5211 5212 mMotionViewNewTop = mMotionViewOriginalTop + deltaY; 5213 5214 mBlockLayoutRequests = true; 5215 5216 if (count > 0) { 5217 detachViewsFromParent(start, count); 5218 mRecycler.removeSkippedScrap(); 5219 } 5220 5221 // invalidate before moving the children to avoid unnecessary invalidate 5222 // calls to bubble up from the children all the way to the top 5223 if (!awakenScrollBars()) { 5224 invalidate(); 5225 } 5226 5227 offsetChildrenTopAndBottom(incrementalDeltaY); 5228 5229 if (down) { 5230 mFirstPosition += count; 5231 } 5232 5233 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); 5234 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { 5235 fillGap(down); 5236 } 5237 5238 mRecycler.fullyDetachScrapViews(); 5239 boolean selectorOnScreen = false; 5240 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { 5241 final int childIndex = mSelectedPosition - mFirstPosition; 5242 if (childIndex >= 0 && childIndex < getChildCount()) { 5243 positionSelector(mSelectedPosition, getChildAt(childIndex)); 5244 selectorOnScreen = true; 5245 } 5246 } else if (mSelectorPosition != INVALID_POSITION) { 5247 final int childIndex = mSelectorPosition - mFirstPosition; 5248 if (childIndex >= 0 && childIndex < getChildCount()) { 5249 positionSelector(mSelectorPosition, getChildAt(childIndex)); 5250 selectorOnScreen = true; 5251 } 5252 } 5253 if (!selectorOnScreen) { 5254 mSelectorRect.setEmpty(); 5255 } 5256 5257 mBlockLayoutRequests = false; 5258 5259 invokeOnItemScrollListener(); 5260 5261 return false; 5262 } 5263 5264 /** 5265 * Returns the number of header views in the list. Header views are special views 5266 * at the top of the list that should not be recycled during a layout. 5267 * 5268 * @return The number of header views, 0 in the default implementation. 5269 */ 5270 int getHeaderViewsCount() { 5271 return 0; 5272 } 5273 5274 /** 5275 * Returns the number of footer views in the list. Footer views are special views 5276 * at the bottom of the list that should not be recycled during a layout. 5277 * 5278 * @return The number of footer views, 0 in the default implementation. 5279 */ 5280 int getFooterViewsCount() { 5281 return 0; 5282 } 5283 5284 /** 5285 * Fills the gap left open by a touch-scroll. During a touch scroll, children that 5286 * remain on screen are shifted and the other ones are discarded. The role of this 5287 * method is to fill the gap thus created by performing a partial layout in the 5288 * empty space. 5289 * 5290 * @param down true if the scroll is going down, false if it is going up 5291 */ 5292 abstract void fillGap(boolean down); 5293 hideSelector()5294 void hideSelector() { 5295 if (mSelectedPosition != INVALID_POSITION) { 5296 if (mLayoutMode != LAYOUT_SPECIFIC) { 5297 mResurrectToPosition = mSelectedPosition; 5298 } 5299 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) { 5300 mResurrectToPosition = mNextSelectedPosition; 5301 } 5302 setSelectedPositionInt(INVALID_POSITION); 5303 setNextSelectedPositionInt(INVALID_POSITION); 5304 mSelectedTop = 0; 5305 } 5306 } 5307 5308 /** 5309 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by 5310 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range 5311 * of items available in the adapter 5312 */ reconcileSelectedPosition()5313 int reconcileSelectedPosition() { 5314 int position = mSelectedPosition; 5315 if (position < 0) { 5316 position = mResurrectToPosition; 5317 } 5318 position = Math.max(0, position); 5319 position = Math.min(position, mItemCount - 1); 5320 return position; 5321 } 5322 5323 /** 5324 * Find the row closest to y. This row will be used as the motion row when scrolling 5325 * 5326 * @param y Where the user touched 5327 * @return The position of the first (or only) item in the row containing y 5328 */ 5329 abstract int findMotionRow(int y); 5330 5331 /** 5332 * Find the row closest to y. This row will be used as the motion row when scrolling. 5333 * 5334 * @param y Where the user touched 5335 * @return The position of the first (or only) item in the row closest to y 5336 */ findClosestMotionRow(int y)5337 int findClosestMotionRow(int y) { 5338 final int childCount = getChildCount(); 5339 if (childCount == 0) { 5340 return INVALID_POSITION; 5341 } 5342 5343 final int motionRow = findMotionRow(y); 5344 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1; 5345 } 5346 5347 /** 5348 * Causes all the views to be rebuilt and redrawn. 5349 */ invalidateViews()5350 public void invalidateViews() { 5351 mDataChanged = true; 5352 rememberSyncState(); 5353 requestLayout(); 5354 invalidate(); 5355 } 5356 5357 /** 5358 * If there is a selection returns false. 5359 * Otherwise resurrects the selection and returns true if resurrected. 5360 */ resurrectSelectionIfNeeded()5361 boolean resurrectSelectionIfNeeded() { 5362 if (mSelectedPosition < 0 && resurrectSelection()) { 5363 updateSelectorState(); 5364 return true; 5365 } 5366 return false; 5367 } 5368 5369 /** 5370 * Makes the item at the supplied position selected. 5371 * 5372 * @param position the position of the new selection 5373 */ 5374 abstract void setSelectionInt(int position); 5375 5376 /** 5377 * Attempt to bring the selection back if the user is switching from touch 5378 * to trackball mode 5379 * @return Whether selection was set to something. 5380 */ resurrectSelection()5381 boolean resurrectSelection() { 5382 final int childCount = getChildCount(); 5383 5384 if (childCount <= 0) { 5385 return false; 5386 } 5387 5388 int selectedTop = 0; 5389 int selectedPos; 5390 int childrenTop = mListPadding.top; 5391 int childrenBottom = mBottom - mTop - mListPadding.bottom; 5392 final int firstPosition = mFirstPosition; 5393 final int toPosition = mResurrectToPosition; 5394 boolean down = true; 5395 5396 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) { 5397 selectedPos = toPosition; 5398 5399 final View selected = getChildAt(selectedPos - mFirstPosition); 5400 selectedTop = selected.getTop(); 5401 int selectedBottom = selected.getBottom(); 5402 5403 // We are scrolled, don't get in the fade 5404 if (selectedTop < childrenTop) { 5405 selectedTop = childrenTop + getVerticalFadingEdgeLength(); 5406 } else if (selectedBottom > childrenBottom) { 5407 selectedTop = childrenBottom - selected.getMeasuredHeight() 5408 - getVerticalFadingEdgeLength(); 5409 } 5410 } else { 5411 if (toPosition < firstPosition) { 5412 // Default to selecting whatever is first 5413 selectedPos = firstPosition; 5414 for (int i = 0; i < childCount; i++) { 5415 final View v = getChildAt(i); 5416 final int top = v.getTop(); 5417 5418 if (i == 0) { 5419 // Remember the position of the first item 5420 selectedTop = top; 5421 // See if we are scrolled at all 5422 if (firstPosition > 0 || top < childrenTop) { 5423 // If we are scrolled, don't select anything that is 5424 // in the fade region 5425 childrenTop += getVerticalFadingEdgeLength(); 5426 } 5427 } 5428 if (top >= childrenTop) { 5429 // Found a view whose top is fully visisble 5430 selectedPos = firstPosition + i; 5431 selectedTop = top; 5432 break; 5433 } 5434 } 5435 } else { 5436 final int itemCount = mItemCount; 5437 down = false; 5438 selectedPos = firstPosition + childCount - 1; 5439 5440 for (int i = childCount - 1; i >= 0; i--) { 5441 final View v = getChildAt(i); 5442 final int top = v.getTop(); 5443 final int bottom = v.getBottom(); 5444 5445 if (i == childCount - 1) { 5446 selectedTop = top; 5447 if (firstPosition + childCount < itemCount || bottom > childrenBottom) { 5448 childrenBottom -= getVerticalFadingEdgeLength(); 5449 } 5450 } 5451 5452 if (bottom <= childrenBottom) { 5453 selectedPos = firstPosition + i; 5454 selectedTop = top; 5455 break; 5456 } 5457 } 5458 } 5459 } 5460 5461 mResurrectToPosition = INVALID_POSITION; 5462 removeCallbacks(mFlingRunnable); 5463 if (mPositionScroller != null) { 5464 mPositionScroller.stop(); 5465 } 5466 mTouchMode = TOUCH_MODE_REST; 5467 clearScrollingCache(); 5468 mSpecificTop = selectedTop; 5469 selectedPos = lookForSelectablePosition(selectedPos, down); 5470 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) { 5471 mLayoutMode = LAYOUT_SPECIFIC; 5472 updateSelectorState(); 5473 setSelectionInt(selectedPos); 5474 invokeOnItemScrollListener(); 5475 } else { 5476 selectedPos = INVALID_POSITION; 5477 } 5478 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 5479 5480 return selectedPos >= 0; 5481 } 5482 confirmCheckedPositionsById()5483 void confirmCheckedPositionsById() { 5484 // Clear out the positional check states, we'll rebuild it below from IDs. 5485 mCheckStates.clear(); 5486 5487 boolean checkedCountChanged = false; 5488 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) { 5489 final long id = mCheckedIdStates.keyAt(checkedIndex); 5490 final int lastPos = mCheckedIdStates.valueAt(checkedIndex); 5491 5492 final long lastPosId = mAdapter.getItemId(lastPos); 5493 if (id != lastPosId) { 5494 // Look around to see if the ID is nearby. If not, uncheck it. 5495 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE); 5496 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount); 5497 boolean found = false; 5498 for (int searchPos = start; searchPos < end; searchPos++) { 5499 final long searchId = mAdapter.getItemId(searchPos); 5500 if (id == searchId) { 5501 found = true; 5502 mCheckStates.put(searchPos, true); 5503 mCheckedIdStates.setValueAt(checkedIndex, searchPos); 5504 break; 5505 } 5506 } 5507 5508 if (!found) { 5509 mCheckedIdStates.delete(id); 5510 checkedIndex--; 5511 mCheckedItemCount--; 5512 checkedCountChanged = true; 5513 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) { 5514 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, 5515 lastPos, id, false); 5516 } 5517 } 5518 } else { 5519 mCheckStates.put(lastPos, true); 5520 } 5521 } 5522 5523 if (checkedCountChanged && mChoiceActionMode != null) { 5524 mChoiceActionMode.invalidate(); 5525 } 5526 } 5527 5528 @Override handleDataChanged()5529 protected void handleDataChanged() { 5530 int count = mItemCount; 5531 int lastHandledItemCount = mLastHandledItemCount; 5532 mLastHandledItemCount = mItemCount; 5533 5534 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) { 5535 confirmCheckedPositionsById(); 5536 } 5537 5538 // TODO: In the future we can recycle these views based on stable ID instead. 5539 mRecycler.clearTransientStateViews(); 5540 5541 if (count > 0) { 5542 int newPos; 5543 int selectablePos; 5544 5545 // Find the row we are supposed to sync to 5546 if (mNeedSync) { 5547 // Update this first, since setNextSelectedPositionInt inspects it 5548 mNeedSync = false; 5549 mPendingSync = null; 5550 5551 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) { 5552 mLayoutMode = LAYOUT_FORCE_BOTTOM; 5553 return; 5554 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) { 5555 if (mForceTranscriptScroll) { 5556 mForceTranscriptScroll = false; 5557 mLayoutMode = LAYOUT_FORCE_BOTTOM; 5558 return; 5559 } 5560 final int childCount = getChildCount(); 5561 final int listBottom = getHeight() - getPaddingBottom(); 5562 final View lastChild = getChildAt(childCount - 1); 5563 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom; 5564 if (mFirstPosition + childCount >= lastHandledItemCount && 5565 lastBottom <= listBottom) { 5566 mLayoutMode = LAYOUT_FORCE_BOTTOM; 5567 return; 5568 } 5569 // Something new came in and we didn't scroll; give the user a clue that 5570 // there's something new. 5571 awakenScrollBars(); 5572 } 5573 5574 switch (mSyncMode) { 5575 case SYNC_SELECTED_POSITION: 5576 if (isInTouchMode()) { 5577 // We saved our state when not in touch mode. (We know this because 5578 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to 5579 // restore in touch mode. Just leave mSyncPosition as it is (possibly 5580 // adjusting if the available range changed) and return. 5581 mLayoutMode = LAYOUT_SYNC; 5582 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); 5583 5584 return; 5585 } else { 5586 // See if we can find a position in the new data with the same 5587 // id as the old selection. This will change mSyncPosition. 5588 newPos = findSyncPosition(); 5589 if (newPos >= 0) { 5590 // Found it. Now verify that new selection is still selectable 5591 selectablePos = lookForSelectablePosition(newPos, true); 5592 if (selectablePos == newPos) { 5593 // Same row id is selected 5594 mSyncPosition = newPos; 5595 5596 if (mSyncHeight == getHeight()) { 5597 // If we are at the same height as when we saved state, try 5598 // to restore the scroll position too. 5599 mLayoutMode = LAYOUT_SYNC; 5600 } else { 5601 // We are not the same height as when the selection was saved, so 5602 // don't try to restore the exact position 5603 mLayoutMode = LAYOUT_SET_SELECTION; 5604 } 5605 5606 // Restore selection 5607 setNextSelectedPositionInt(newPos); 5608 return; 5609 } 5610 } 5611 } 5612 break; 5613 case SYNC_FIRST_POSITION: 5614 // Leave mSyncPosition as it is -- just pin to available range 5615 mLayoutMode = LAYOUT_SYNC; 5616 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); 5617 5618 return; 5619 } 5620 } 5621 5622 if (!isInTouchMode()) { 5623 // We couldn't find matching data -- try to use the same position 5624 newPos = getSelectedItemPosition(); 5625 5626 // Pin position to the available range 5627 if (newPos >= count) { 5628 newPos = count - 1; 5629 } 5630 if (newPos < 0) { 5631 newPos = 0; 5632 } 5633 5634 // Make sure we select something selectable -- first look down 5635 selectablePos = lookForSelectablePosition(newPos, true); 5636 5637 if (selectablePos >= 0) { 5638 setNextSelectedPositionInt(selectablePos); 5639 return; 5640 } else { 5641 // Looking down didn't work -- try looking up 5642 selectablePos = lookForSelectablePosition(newPos, false); 5643 if (selectablePos >= 0) { 5644 setNextSelectedPositionInt(selectablePos); 5645 return; 5646 } 5647 } 5648 } else { 5649 5650 // We already know where we want to resurrect the selection 5651 if (mResurrectToPosition >= 0) { 5652 return; 5653 } 5654 } 5655 5656 } 5657 5658 // Nothing is selected. Give up and reset everything. 5659 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP; 5660 mSelectedPosition = INVALID_POSITION; 5661 mSelectedRowId = INVALID_ROW_ID; 5662 mNextSelectedPosition = INVALID_POSITION; 5663 mNextSelectedRowId = INVALID_ROW_ID; 5664 mNeedSync = false; 5665 mPendingSync = null; 5666 mSelectorPosition = INVALID_POSITION; 5667 checkSelectionChanged(); 5668 } 5669 5670 @Override onDisplayHint(int hint)5671 protected void onDisplayHint(int hint) { 5672 super.onDisplayHint(hint); 5673 switch (hint) { 5674 case INVISIBLE: 5675 if (mPopup != null && mPopup.isShowing()) { 5676 dismissPopup(); 5677 } 5678 break; 5679 case VISIBLE: 5680 if (mFiltered && mPopup != null && !mPopup.isShowing()) { 5681 showPopup(); 5682 } 5683 break; 5684 } 5685 mPopupHidden = hint == INVISIBLE; 5686 } 5687 5688 /** 5689 * Removes the filter window 5690 */ dismissPopup()5691 private void dismissPopup() { 5692 if (mPopup != null) { 5693 mPopup.dismiss(); 5694 } 5695 } 5696 5697 /** 5698 * Shows the filter window 5699 */ showPopup()5700 private void showPopup() { 5701 // Make sure we have a window before showing the popup 5702 if (getWindowVisibility() == View.VISIBLE) { 5703 createTextFilter(true); 5704 positionPopup(); 5705 // Make sure we get focus if we are showing the popup 5706 checkFocus(); 5707 } 5708 } 5709 positionPopup()5710 private void positionPopup() { 5711 int screenHeight = getResources().getDisplayMetrics().heightPixels; 5712 final int[] xy = new int[2]; 5713 getLocationOnScreen(xy); 5714 // TODO: The 20 below should come from the theme 5715 // TODO: And the gravity should be defined in the theme as well 5716 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20); 5717 if (!mPopup.isShowing()) { 5718 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 5719 xy[0], bottomGap); 5720 } else { 5721 mPopup.update(xy[0], bottomGap, -1, -1); 5722 } 5723 } 5724 5725 /** 5726 * What is the distance between the source and destination rectangles given the direction of 5727 * focus navigation between them? The direction basically helps figure out more quickly what is 5728 * self evident by the relationship between the rects... 5729 * 5730 * @param source the source rectangle 5731 * @param dest the destination rectangle 5732 * @param direction the direction 5733 * @return the distance between the rectangles 5734 */ getDistance(Rect source, Rect dest, int direction)5735 static int getDistance(Rect source, Rect dest, int direction) { 5736 int sX, sY; // source x, y 5737 int dX, dY; // dest x, y 5738 switch (direction) { 5739 case View.FOCUS_RIGHT: 5740 sX = source.right; 5741 sY = source.top + source.height() / 2; 5742 dX = dest.left; 5743 dY = dest.top + dest.height() / 2; 5744 break; 5745 case View.FOCUS_DOWN: 5746 sX = source.left + source.width() / 2; 5747 sY = source.bottom; 5748 dX = dest.left + dest.width() / 2; 5749 dY = dest.top; 5750 break; 5751 case View.FOCUS_LEFT: 5752 sX = source.left; 5753 sY = source.top + source.height() / 2; 5754 dX = dest.right; 5755 dY = dest.top + dest.height() / 2; 5756 break; 5757 case View.FOCUS_UP: 5758 sX = source.left + source.width() / 2; 5759 sY = source.top; 5760 dX = dest.left + dest.width() / 2; 5761 dY = dest.bottom; 5762 break; 5763 case View.FOCUS_FORWARD: 5764 case View.FOCUS_BACKWARD: 5765 sX = source.right + source.width() / 2; 5766 sY = source.top + source.height() / 2; 5767 dX = dest.left + dest.width() / 2; 5768 dY = dest.top + dest.height() / 2; 5769 break; 5770 default: 5771 throw new IllegalArgumentException("direction must be one of " 5772 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, " 5773 + "FOCUS_FORWARD, FOCUS_BACKWARD}."); 5774 } 5775 int deltaX = dX - sX; 5776 int deltaY = dY - sY; 5777 return deltaY * deltaY + deltaX * deltaX; 5778 } 5779 5780 @Override isInFilterMode()5781 protected boolean isInFilterMode() { 5782 return mFiltered; 5783 } 5784 5785 /** 5786 * Sends a key to the text filter window 5787 * 5788 * @param keyCode The keycode for the event 5789 * @param event The actual key event 5790 * 5791 * @return True if the text filter handled the event, false otherwise. 5792 */ sendToTextFilter(int keyCode, int count, KeyEvent event)5793 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) { 5794 if (!acceptFilter()) { 5795 return false; 5796 } 5797 5798 boolean handled = false; 5799 boolean okToSend = true; 5800 switch (keyCode) { 5801 case KeyEvent.KEYCODE_DPAD_UP: 5802 case KeyEvent.KEYCODE_DPAD_DOWN: 5803 case KeyEvent.KEYCODE_DPAD_LEFT: 5804 case KeyEvent.KEYCODE_DPAD_RIGHT: 5805 case KeyEvent.KEYCODE_DPAD_CENTER: 5806 case KeyEvent.KEYCODE_ENTER: 5807 okToSend = false; 5808 break; 5809 case KeyEvent.KEYCODE_BACK: 5810 if (mFiltered && mPopup != null && mPopup.isShowing()) { 5811 if (event.getAction() == KeyEvent.ACTION_DOWN 5812 && event.getRepeatCount() == 0) { 5813 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5814 if (state != null) { 5815 state.startTracking(event, this); 5816 } 5817 handled = true; 5818 } else if (event.getAction() == KeyEvent.ACTION_UP 5819 && event.isTracking() && !event.isCanceled()) { 5820 handled = true; 5821 mTextFilter.setText(""); 5822 } 5823 } 5824 okToSend = false; 5825 break; 5826 case KeyEvent.KEYCODE_SPACE: 5827 // Only send spaces once we are filtered 5828 okToSend = mFiltered; 5829 break; 5830 } 5831 5832 if (okToSend) { 5833 createTextFilter(true); 5834 5835 KeyEvent forwardEvent = event; 5836 if (forwardEvent.getRepeatCount() > 0) { 5837 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0); 5838 } 5839 5840 int action = event.getAction(); 5841 switch (action) { 5842 case KeyEvent.ACTION_DOWN: 5843 handled = mTextFilter.onKeyDown(keyCode, forwardEvent); 5844 break; 5845 5846 case KeyEvent.ACTION_UP: 5847 handled = mTextFilter.onKeyUp(keyCode, forwardEvent); 5848 break; 5849 5850 case KeyEvent.ACTION_MULTIPLE: 5851 handled = mTextFilter.onKeyMultiple(keyCode, count, event); 5852 break; 5853 } 5854 } 5855 return handled; 5856 } 5857 5858 /** 5859 * Return an InputConnection for editing of the filter text. 5860 */ 5861 @Override onCreateInputConnection(EditorInfo outAttrs)5862 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 5863 if (isTextFilterEnabled()) { 5864 if (mPublicInputConnection == null) { 5865 mDefInputConnection = new BaseInputConnection(this, false); 5866 mPublicInputConnection = new InputConnectionWrapper(outAttrs); 5867 } 5868 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER; 5869 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; 5870 return mPublicInputConnection; 5871 } 5872 return null; 5873 } 5874 5875 private class InputConnectionWrapper implements InputConnection { 5876 private final EditorInfo mOutAttrs; 5877 private InputConnection mTarget; 5878 InputConnectionWrapper(EditorInfo outAttrs)5879 public InputConnectionWrapper(EditorInfo outAttrs) { 5880 mOutAttrs = outAttrs; 5881 } 5882 getTarget()5883 private InputConnection getTarget() { 5884 if (mTarget == null) { 5885 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs); 5886 } 5887 return mTarget; 5888 } 5889 5890 @Override reportFullscreenMode(boolean enabled)5891 public boolean reportFullscreenMode(boolean enabled) { 5892 // Use our own input connection, since it is 5893 // the "real" one the IME is talking with. 5894 return mDefInputConnection.reportFullscreenMode(enabled); 5895 } 5896 5897 @Override performEditorAction(int editorAction)5898 public boolean performEditorAction(int editorAction) { 5899 // The editor is off in its own window; we need to be 5900 // the one that does this. 5901 if (editorAction == EditorInfo.IME_ACTION_DONE) { 5902 InputMethodManager imm = 5903 getContext().getSystemService(InputMethodManager.class); 5904 if (imm != null) { 5905 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5906 } 5907 return true; 5908 } 5909 return false; 5910 } 5911 5912 @Override sendKeyEvent(KeyEvent event)5913 public boolean sendKeyEvent(KeyEvent event) { 5914 // Use our own input connection, since the filter 5915 // text view may not be shown in a window so has 5916 // no ViewAncestor to dispatch events with. 5917 return mDefInputConnection.sendKeyEvent(event); 5918 } 5919 5920 @Override getTextBeforeCursor(int n, int flags)5921 public CharSequence getTextBeforeCursor(int n, int flags) { 5922 if (mTarget == null) return ""; 5923 return mTarget.getTextBeforeCursor(n, flags); 5924 } 5925 5926 @Override getTextAfterCursor(int n, int flags)5927 public CharSequence getTextAfterCursor(int n, int flags) { 5928 if (mTarget == null) return ""; 5929 return mTarget.getTextAfterCursor(n, flags); 5930 } 5931 5932 @Override getSelectedText(int flags)5933 public CharSequence getSelectedText(int flags) { 5934 if (mTarget == null) return ""; 5935 return mTarget.getSelectedText(flags); 5936 } 5937 5938 @Override getCursorCapsMode(int reqModes)5939 public int getCursorCapsMode(int reqModes) { 5940 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; 5941 return mTarget.getCursorCapsMode(reqModes); 5942 } 5943 5944 @Override getExtractedText(ExtractedTextRequest request, int flags)5945 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 5946 return getTarget().getExtractedText(request, flags); 5947 } 5948 5949 @Override deleteSurroundingText(int beforeLength, int afterLength)5950 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 5951 return getTarget().deleteSurroundingText(beforeLength, afterLength); 5952 } 5953 5954 @Override deleteSurroundingTextInCodePoints(int beforeLength, int afterLength)5955 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 5956 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength); 5957 } 5958 5959 @Override setComposingText(CharSequence text, int newCursorPosition)5960 public boolean setComposingText(CharSequence text, int newCursorPosition) { 5961 return getTarget().setComposingText(text, newCursorPosition); 5962 } 5963 5964 @Override setComposingRegion(int start, int end)5965 public boolean setComposingRegion(int start, int end) { 5966 return getTarget().setComposingRegion(start, end); 5967 } 5968 5969 @Override finishComposingText()5970 public boolean finishComposingText() { 5971 return mTarget == null || mTarget.finishComposingText(); 5972 } 5973 5974 @Override commitText(CharSequence text, int newCursorPosition)5975 public boolean commitText(CharSequence text, int newCursorPosition) { 5976 return getTarget().commitText(text, newCursorPosition); 5977 } 5978 5979 @Override commitCompletion(CompletionInfo text)5980 public boolean commitCompletion(CompletionInfo text) { 5981 return getTarget().commitCompletion(text); 5982 } 5983 5984 @Override commitCorrection(CorrectionInfo correctionInfo)5985 public boolean commitCorrection(CorrectionInfo correctionInfo) { 5986 return getTarget().commitCorrection(correctionInfo); 5987 } 5988 5989 @Override setSelection(int start, int end)5990 public boolean setSelection(int start, int end) { 5991 return getTarget().setSelection(start, end); 5992 } 5993 5994 @Override performContextMenuAction(int id)5995 public boolean performContextMenuAction(int id) { 5996 return getTarget().performContextMenuAction(id); 5997 } 5998 5999 @Override beginBatchEdit()6000 public boolean beginBatchEdit() { 6001 return getTarget().beginBatchEdit(); 6002 } 6003 6004 @Override endBatchEdit()6005 public boolean endBatchEdit() { 6006 return getTarget().endBatchEdit(); 6007 } 6008 6009 @Override clearMetaKeyStates(int states)6010 public boolean clearMetaKeyStates(int states) { 6011 return getTarget().clearMetaKeyStates(states); 6012 } 6013 6014 @Override performPrivateCommand(String action, Bundle data)6015 public boolean performPrivateCommand(String action, Bundle data) { 6016 return getTarget().performPrivateCommand(action, data); 6017 } 6018 6019 @Override requestCursorUpdates(int cursorUpdateMode)6020 public boolean requestCursorUpdates(int cursorUpdateMode) { 6021 return getTarget().requestCursorUpdates(cursorUpdateMode); 6022 } 6023 6024 @Override getHandler()6025 public Handler getHandler() { 6026 return getTarget().getHandler(); 6027 } 6028 6029 @Override closeConnection()6030 public void closeConnection() { 6031 getTarget().closeConnection(); 6032 } 6033 6034 @Override commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)6035 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { 6036 return getTarget().commitContent(inputContentInfo, flags, opts); 6037 } 6038 } 6039 6040 /** 6041 * For filtering we proxy an input connection to an internal text editor, 6042 * and this allows the proxying to happen. 6043 */ 6044 @Override checkInputConnectionProxy(View view)6045 public boolean checkInputConnectionProxy(View view) { 6046 return view == mTextFilter; 6047 } 6048 6049 /** 6050 * Creates the window for the text filter and populates it with an EditText field; 6051 * 6052 * @param animateEntrance true if the window should appear with an animation 6053 */ createTextFilter(boolean animateEntrance)6054 private void createTextFilter(boolean animateEntrance) { 6055 if (mPopup == null) { 6056 PopupWindow p = new PopupWindow(getContext()); 6057 p.setFocusable(false); 6058 p.setTouchable(false); 6059 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 6060 p.setContentView(getTextFilterInput()); 6061 p.setWidth(LayoutParams.WRAP_CONTENT); 6062 p.setHeight(LayoutParams.WRAP_CONTENT); 6063 p.setBackgroundDrawable(null); 6064 mPopup = p; 6065 getViewTreeObserver().addOnGlobalLayoutListener(this); 6066 mGlobalLayoutListenerAddedFilter = true; 6067 } 6068 if (animateEntrance) { 6069 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter); 6070 } else { 6071 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore); 6072 } 6073 } 6074 getTextFilterInput()6075 private EditText getTextFilterInput() { 6076 if (mTextFilter == null) { 6077 final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); 6078 mTextFilter = (EditText) layoutInflater.inflate( 6079 com.android.internal.R.layout.typing_filter, null); 6080 // For some reason setting this as the "real" input type changes 6081 // the text view in some way that it doesn't work, and I don't 6082 // want to figure out why this is. 6083 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT 6084 | EditorInfo.TYPE_TEXT_VARIATION_FILTER); 6085 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); 6086 mTextFilter.addTextChangedListener(this); 6087 } 6088 return mTextFilter; 6089 } 6090 6091 /** 6092 * Clear the text filter. 6093 */ clearTextFilter()6094 public void clearTextFilter() { 6095 if (mFiltered) { 6096 getTextFilterInput().setText(""); 6097 mFiltered = false; 6098 if (mPopup != null && mPopup.isShowing()) { 6099 dismissPopup(); 6100 } 6101 } 6102 } 6103 6104 /** 6105 * Returns if the ListView currently has a text filter. 6106 */ hasTextFilter()6107 public boolean hasTextFilter() { 6108 return mFiltered; 6109 } 6110 6111 @Override onGlobalLayout()6112 public void onGlobalLayout() { 6113 if (isShown()) { 6114 // Show the popup if we are filtered 6115 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) { 6116 showPopup(); 6117 } 6118 } else { 6119 // Hide the popup when we are no longer visible 6120 if (mPopup != null && mPopup.isShowing()) { 6121 dismissPopup(); 6122 } 6123 } 6124 6125 } 6126 6127 /** 6128 * For our text watcher that is associated with the text filter. Does 6129 * nothing. 6130 */ 6131 @Override beforeTextChanged(CharSequence s, int start, int count, int after)6132 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 6133 } 6134 6135 /** 6136 * For our text watcher that is associated with the text filter. Performs 6137 * the actual filtering as the text changes, and takes care of hiding and 6138 * showing the popup displaying the currently entered filter text. 6139 */ 6140 @Override onTextChanged(CharSequence s, int start, int before, int count)6141 public void onTextChanged(CharSequence s, int start, int before, int count) { 6142 if (isTextFilterEnabled()) { 6143 createTextFilter(true); 6144 int length = s.length(); 6145 boolean showing = mPopup.isShowing(); 6146 if (!showing && length > 0) { 6147 // Show the filter popup if necessary 6148 showPopup(); 6149 mFiltered = true; 6150 } else if (showing && length == 0) { 6151 // Remove the filter popup if the user has cleared all text 6152 dismissPopup(); 6153 mFiltered = false; 6154 } 6155 if (mAdapter instanceof Filterable) { 6156 Filter f = ((Filterable) mAdapter).getFilter(); 6157 // Filter should not be null when we reach this part 6158 if (f != null) { 6159 f.filter(s, this); 6160 } else { 6161 throw new IllegalStateException("You cannot call onTextChanged with a non " 6162 + "filterable adapter"); 6163 } 6164 } 6165 } 6166 } 6167 6168 /** 6169 * For our text watcher that is associated with the text filter. Does 6170 * nothing. 6171 */ 6172 @Override afterTextChanged(Editable s)6173 public void afterTextChanged(Editable s) { 6174 } 6175 6176 @Override onFilterComplete(int count)6177 public void onFilterComplete(int count) { 6178 if (mSelectedPosition < 0 && count > 0) { 6179 mResurrectToPosition = INVALID_POSITION; 6180 resurrectSelection(); 6181 } 6182 } 6183 6184 @Override generateDefaultLayoutParams()6185 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 6186 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 6187 ViewGroup.LayoutParams.WRAP_CONTENT, 0); 6188 } 6189 6190 @Override generateLayoutParams(ViewGroup.LayoutParams p)6191 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 6192 return new LayoutParams(p); 6193 } 6194 6195 @Override generateLayoutParams(AttributeSet attrs)6196 public LayoutParams generateLayoutParams(AttributeSet attrs) { 6197 return new AbsListView.LayoutParams(getContext(), attrs); 6198 } 6199 6200 @Override checkLayoutParams(ViewGroup.LayoutParams p)6201 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 6202 return p instanceof AbsListView.LayoutParams; 6203 } 6204 6205 /** 6206 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll 6207 * to the bottom to show new items. 6208 * 6209 * @param mode the transcript mode to set 6210 * 6211 * @see #TRANSCRIPT_MODE_DISABLED 6212 * @see #TRANSCRIPT_MODE_NORMAL 6213 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL 6214 */ setTranscriptMode(int mode)6215 public void setTranscriptMode(int mode) { 6216 mTranscriptMode = mode; 6217 } 6218 6219 /** 6220 * Returns the current transcript mode. 6221 * 6222 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or 6223 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL} 6224 */ getTranscriptMode()6225 public int getTranscriptMode() { 6226 return mTranscriptMode; 6227 } 6228 6229 @Override getSolidColor()6230 public int getSolidColor() { 6231 return mCacheColorHint; 6232 } 6233 6234 /** 6235 * When set to a non-zero value, the cache color hint indicates that this list is always drawn 6236 * on top of a solid, single-color, opaque background. 6237 * 6238 * Zero means that what's behind this object is translucent (non solid) or is not made of a 6239 * single color. This hint will not affect any existing background drawable set on this view ( 6240 * typically set via {@link #setBackgroundDrawable(Drawable)}). 6241 * 6242 * @param color The background color 6243 */ setCacheColorHint(@olorInt int color)6244 public void setCacheColorHint(@ColorInt int color) { 6245 if (color != mCacheColorHint) { 6246 mCacheColorHint = color; 6247 int count = getChildCount(); 6248 for (int i = 0; i < count; i++) { 6249 getChildAt(i).setDrawingCacheBackgroundColor(color); 6250 } 6251 mRecycler.setCacheColorHint(color); 6252 } 6253 } 6254 6255 /** 6256 * When set to a non-zero value, the cache color hint indicates that this list is always drawn 6257 * on top of a solid, single-color, opaque background 6258 * 6259 * @return The cache color hint 6260 */ 6261 @ViewDebug.ExportedProperty(category = "drawing") 6262 @ColorInt getCacheColorHint()6263 public int getCacheColorHint() { 6264 return mCacheColorHint; 6265 } 6266 6267 /** 6268 * Move all views (excluding headers and footers) held by this AbsListView into the supplied 6269 * List. This includes views displayed on the screen as well as views stored in AbsListView's 6270 * internal view recycler. 6271 * 6272 * @param views A list into which to put the reclaimed views 6273 */ reclaimViews(List<View> views)6274 public void reclaimViews(List<View> views) { 6275 int childCount = getChildCount(); 6276 RecyclerListener listener = mRecycler.mRecyclerListener; 6277 6278 // Reclaim views on screen 6279 for (int i = 0; i < childCount; i++) { 6280 View child = getChildAt(i); 6281 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); 6282 // Don't reclaim header or footer views, or views that should be ignored 6283 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { 6284 views.add(child); 6285 child.setAccessibilityDelegate(null); 6286 if (listener != null) { 6287 // Pretend they went through the scrap heap 6288 listener.onMovedToScrapHeap(child); 6289 } 6290 } 6291 } 6292 mRecycler.reclaimScrapViews(views); 6293 removeAllViewsInLayout(); 6294 } 6295 finishGlows()6296 private void finishGlows() { 6297 if (mEdgeGlowTop != null) { 6298 mEdgeGlowTop.finish(); 6299 mEdgeGlowBottom.finish(); 6300 } 6301 } 6302 6303 /** 6304 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService 6305 * through the specified intent. 6306 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to. 6307 */ setRemoteViewsAdapter(Intent intent)6308 public void setRemoteViewsAdapter(Intent intent) { 6309 setRemoteViewsAdapter(intent, false); 6310 } 6311 6312 /** @hide **/ setRemoteViewsAdapterAsync(final Intent intent)6313 public Runnable setRemoteViewsAdapterAsync(final Intent intent) { 6314 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent); 6315 } 6316 6317 /** @hide **/ 6318 @Override setRemoteViewsAdapter(Intent intent, boolean isAsync)6319 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) { 6320 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 6321 // service handling the specified intent. 6322 if (mRemoteAdapter != null) { 6323 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); 6324 Intent.FilterComparison fcOld = new Intent.FilterComparison( 6325 mRemoteAdapter.getRemoteViewsServiceIntent()); 6326 if (fcNew.equals(fcOld)) { 6327 return; 6328 } 6329 } 6330 mDeferNotifyDataSetChanged = false; 6331 // Otherwise, create a new RemoteViewsAdapter for binding 6332 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync); 6333 if (mRemoteAdapter.isDataReady()) { 6334 setAdapter(mRemoteAdapter); 6335 } 6336 } 6337 6338 /** 6339 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews 6340 * 6341 * @param handler The OnClickHandler to use when inflating RemoteViews. 6342 * 6343 * @hide 6344 */ setRemoteViewsOnClickHandler(OnClickHandler handler)6345 public void setRemoteViewsOnClickHandler(OnClickHandler handler) { 6346 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 6347 // service handling the specified intent. 6348 if (mRemoteAdapter != null) { 6349 mRemoteAdapter.setRemoteViewsOnClickHandler(handler); 6350 } 6351 } 6352 6353 /** 6354 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not 6355 * connected yet. 6356 */ 6357 @Override deferNotifyDataSetChanged()6358 public void deferNotifyDataSetChanged() { 6359 mDeferNotifyDataSetChanged = true; 6360 } 6361 6362 /** 6363 * Called back when the adapter connects to the RemoteViewsService. 6364 */ 6365 @Override onRemoteAdapterConnected()6366 public boolean onRemoteAdapterConnected() { 6367 if (mRemoteAdapter != mAdapter) { 6368 setAdapter(mRemoteAdapter); 6369 if (mDeferNotifyDataSetChanged) { 6370 mRemoteAdapter.notifyDataSetChanged(); 6371 mDeferNotifyDataSetChanged = false; 6372 } 6373 return false; 6374 } else if (mRemoteAdapter != null) { 6375 mRemoteAdapter.superNotifyDataSetChanged(); 6376 return true; 6377 } 6378 return false; 6379 } 6380 6381 /** 6382 * Called back when the adapter disconnects from the RemoteViewsService. 6383 */ 6384 @Override onRemoteAdapterDisconnected()6385 public void onRemoteAdapterDisconnected() { 6386 // If the remote adapter disconnects, we keep it around 6387 // since the currently displayed items are still cached. 6388 // Further, we want the service to eventually reconnect 6389 // when necessary, as triggered by this view requesting 6390 // items from the Adapter. 6391 } 6392 6393 /** 6394 * Hints the RemoteViewsAdapter, if it exists, about which views are currently 6395 * being displayed by the AbsListView. 6396 */ setVisibleRangeHint(int start, int end)6397 void setVisibleRangeHint(int start, int end) { 6398 if (mRemoteAdapter != null) { 6399 mRemoteAdapter.setVisibleRangeHint(start, end); 6400 } 6401 } 6402 6403 /** 6404 * Sets the recycler listener to be notified whenever a View is set aside in 6405 * the recycler for later reuse. This listener can be used to free resources 6406 * associated to the View. 6407 * 6408 * @param listener The recycler listener to be notified of views set aside 6409 * in the recycler. 6410 * 6411 * @see android.widget.AbsListView.RecycleBin 6412 * @see android.widget.AbsListView.RecyclerListener 6413 */ setRecyclerListener(RecyclerListener listener)6414 public void setRecyclerListener(RecyclerListener listener) { 6415 mRecycler.mRecyclerListener = listener; 6416 } 6417 6418 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver { 6419 @Override onChanged()6420 public void onChanged() { 6421 super.onChanged(); 6422 if (mFastScroll != null) { 6423 mFastScroll.onSectionsChanged(); 6424 } 6425 } 6426 6427 @Override onInvalidated()6428 public void onInvalidated() { 6429 super.onInvalidated(); 6430 if (mFastScroll != null) { 6431 mFastScroll.onSectionsChanged(); 6432 } 6433 } 6434 } 6435 6436 /** 6437 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. 6438 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives 6439 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user 6440 * selects and deselects list items. 6441 */ 6442 public interface MultiChoiceModeListener extends ActionMode.Callback { 6443 /** 6444 * Called when an item is checked or unchecked during selection mode. 6445 * 6446 * @param mode The {@link ActionMode} providing the selection mode 6447 * @param position Adapter position of the item that was checked or unchecked 6448 * @param id Adapter ID of the item that was checked or unchecked 6449 * @param checked <code>true</code> if the item is now checked, <code>false</code> 6450 * if the item is now unchecked. 6451 */ 6452 public void onItemCheckedStateChanged(ActionMode mode, 6453 int position, long id, boolean checked); 6454 } 6455 6456 class MultiChoiceModeWrapper implements MultiChoiceModeListener { 6457 private MultiChoiceModeListener mWrapped; 6458 setWrapped(MultiChoiceModeListener wrapped)6459 public void setWrapped(MultiChoiceModeListener wrapped) { 6460 mWrapped = wrapped; 6461 } 6462 hasWrappedCallback()6463 public boolean hasWrappedCallback() { 6464 return mWrapped != null; 6465 } 6466 6467 @Override onCreateActionMode(ActionMode mode, Menu menu)6468 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 6469 if (mWrapped.onCreateActionMode(mode, menu)) { 6470 // Initialize checked graphic state? 6471 setLongClickable(false); 6472 return true; 6473 } 6474 return false; 6475 } 6476 6477 @Override onPrepareActionMode(ActionMode mode, Menu menu)6478 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 6479 return mWrapped.onPrepareActionMode(mode, menu); 6480 } 6481 6482 @Override onActionItemClicked(ActionMode mode, MenuItem item)6483 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 6484 return mWrapped.onActionItemClicked(mode, item); 6485 } 6486 6487 @Override onDestroyActionMode(ActionMode mode)6488 public void onDestroyActionMode(ActionMode mode) { 6489 mWrapped.onDestroyActionMode(mode); 6490 mChoiceActionMode = null; 6491 6492 // Ending selection mode means deselecting everything. 6493 clearChoices(); 6494 6495 mDataChanged = true; 6496 rememberSyncState(); 6497 requestLayout(); 6498 6499 setLongClickable(true); 6500 } 6501 6502 @Override onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked)6503 public void onItemCheckedStateChanged(ActionMode mode, 6504 int position, long id, boolean checked) { 6505 mWrapped.onItemCheckedStateChanged(mode, position, id, checked); 6506 6507 // If there are no items selected we no longer need the selection mode. 6508 if (getCheckedItemCount() == 0) { 6509 mode.finish(); 6510 } 6511 } 6512 } 6513 6514 /** 6515 * AbsListView extends LayoutParams to provide a place to hold the view type. 6516 */ 6517 public static class LayoutParams extends ViewGroup.LayoutParams { 6518 /** 6519 * View type for this view, as returned by 6520 * {@link android.widget.Adapter#getItemViewType(int) } 6521 */ 6522 @ViewDebug.ExportedProperty(category = "list", mapping = { 6523 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"), 6524 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER") 6525 }) 6526 int viewType; 6527 6528 /** 6529 * When this boolean is set, the view has been added to the AbsListView 6530 * at least once. It is used to know whether headers/footers have already 6531 * been added to the list view and whether they should be treated as 6532 * recycled views or not. 6533 */ 6534 @ViewDebug.ExportedProperty(category = "list") 6535 boolean recycledHeaderFooter; 6536 6537 /** 6538 * When an AbsListView is measured with an AT_MOST measure spec, it needs 6539 * to obtain children views to measure itself. When doing so, the children 6540 * are not attached to the window, but put in the recycler which assumes 6541 * they've been attached before. Setting this flag will force the reused 6542 * view to be attached to the window rather than just attached to the 6543 * parent. 6544 */ 6545 @ViewDebug.ExportedProperty(category = "list") 6546 boolean forceAdd; 6547 6548 /** 6549 * The position the view was removed from when pulled out of the 6550 * scrap heap. 6551 * @hide 6552 */ 6553 int scrappedFromPosition; 6554 6555 /** 6556 * The ID the view represents 6557 */ 6558 long itemId = -1; 6559 6560 /** Whether the adapter considers the item enabled. */ 6561 boolean isEnabled; 6562 LayoutParams(Context c, AttributeSet attrs)6563 public LayoutParams(Context c, AttributeSet attrs) { 6564 super(c, attrs); 6565 } 6566 LayoutParams(int w, int h)6567 public LayoutParams(int w, int h) { 6568 super(w, h); 6569 } 6570 LayoutParams(int w, int h, int viewType)6571 public LayoutParams(int w, int h, int viewType) { 6572 super(w, h); 6573 this.viewType = viewType; 6574 } 6575 LayoutParams(ViewGroup.LayoutParams source)6576 public LayoutParams(ViewGroup.LayoutParams source) { 6577 super(source); 6578 } 6579 6580 /** @hide */ 6581 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)6582 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 6583 super.encodeProperties(encoder); 6584 6585 encoder.addProperty("list:viewType", viewType); 6586 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter); 6587 encoder.addProperty("list:forceAdd", forceAdd); 6588 encoder.addProperty("list:isEnabled", isEnabled); 6589 } 6590 } 6591 6592 /** 6593 * A RecyclerListener is used to receive a notification whenever a View is placed 6594 * inside the RecycleBin's scrap heap. This listener is used to free resources 6595 * associated to Views placed in the RecycleBin. 6596 * 6597 * @see android.widget.AbsListView.RecycleBin 6598 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 6599 */ 6600 public static interface RecyclerListener { 6601 /** 6602 * Indicates that the specified View was moved into the recycler's scrap heap. 6603 * The view is not displayed on screen any more and any expensive resource 6604 * associated with the view should be discarded. 6605 * 6606 * @param view 6607 */ 6608 void onMovedToScrapHeap(View view); 6609 } 6610 6611 /** 6612 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of 6613 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the 6614 * start of a layout. By construction, they are displaying current information. At the end of 6615 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that 6616 * could potentially be used by the adapter to avoid allocating views unnecessarily. 6617 * 6618 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 6619 * @see android.widget.AbsListView.RecyclerListener 6620 */ 6621 class RecycleBin { 6622 private RecyclerListener mRecyclerListener; 6623 6624 /** 6625 * The position of the first view stored in mActiveViews. 6626 */ 6627 private int mFirstActivePosition; 6628 6629 /** 6630 * Views that were on screen at the start of layout. This array is populated at the start of 6631 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. 6632 * Views in mActiveViews represent a contiguous range of Views, with position of the first 6633 * view store in mFirstActivePosition. 6634 */ 6635 private View[] mActiveViews = new View[0]; 6636 6637 /** 6638 * Unsorted views that can be used by the adapter as a convert view. 6639 */ 6640 private ArrayList<View>[] mScrapViews; 6641 6642 private int mViewTypeCount; 6643 6644 private ArrayList<View> mCurrentScrap; 6645 6646 private ArrayList<View> mSkippedScrap; 6647 6648 private SparseArray<View> mTransientStateViews; 6649 private LongSparseArray<View> mTransientStateViewsById; 6650 setViewTypeCount(int viewTypeCount)6651 public void setViewTypeCount(int viewTypeCount) { 6652 if (viewTypeCount < 1) { 6653 throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); 6654 } 6655 //noinspection unchecked 6656 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; 6657 for (int i = 0; i < viewTypeCount; i++) { 6658 scrapViews[i] = new ArrayList<View>(); 6659 } 6660 mViewTypeCount = viewTypeCount; 6661 mCurrentScrap = scrapViews[0]; 6662 mScrapViews = scrapViews; 6663 } 6664 markChildrenDirty()6665 public void markChildrenDirty() { 6666 if (mViewTypeCount == 1) { 6667 final ArrayList<View> scrap = mCurrentScrap; 6668 final int scrapCount = scrap.size(); 6669 for (int i = 0; i < scrapCount; i++) { 6670 scrap.get(i).forceLayout(); 6671 } 6672 } else { 6673 final int typeCount = mViewTypeCount; 6674 for (int i = 0; i < typeCount; i++) { 6675 final ArrayList<View> scrap = mScrapViews[i]; 6676 final int scrapCount = scrap.size(); 6677 for (int j = 0; j < scrapCount; j++) { 6678 scrap.get(j).forceLayout(); 6679 } 6680 } 6681 } 6682 if (mTransientStateViews != null) { 6683 final int count = mTransientStateViews.size(); 6684 for (int i = 0; i < count; i++) { 6685 mTransientStateViews.valueAt(i).forceLayout(); 6686 } 6687 } 6688 if (mTransientStateViewsById != null) { 6689 final int count = mTransientStateViewsById.size(); 6690 for (int i = 0; i < count; i++) { 6691 mTransientStateViewsById.valueAt(i).forceLayout(); 6692 } 6693 } 6694 } 6695 shouldRecycleViewType(int viewType)6696 public boolean shouldRecycleViewType(int viewType) { 6697 return viewType >= 0; 6698 } 6699 6700 /** 6701 * Clears the scrap heap. 6702 */ clear()6703 void clear() { 6704 if (mViewTypeCount == 1) { 6705 final ArrayList<View> scrap = mCurrentScrap; 6706 clearScrap(scrap); 6707 } else { 6708 final int typeCount = mViewTypeCount; 6709 for (int i = 0; i < typeCount; i++) { 6710 final ArrayList<View> scrap = mScrapViews[i]; 6711 clearScrap(scrap); 6712 } 6713 } 6714 6715 clearTransientStateViews(); 6716 } 6717 6718 /** 6719 * Fill ActiveViews with all of the children of the AbsListView. 6720 * 6721 * @param childCount The minimum number of views mActiveViews should hold 6722 * @param firstActivePosition The position of the first view that will be stored in 6723 * mActiveViews 6724 */ fillActiveViews(int childCount, int firstActivePosition)6725 void fillActiveViews(int childCount, int firstActivePosition) { 6726 if (mActiveViews.length < childCount) { 6727 mActiveViews = new View[childCount]; 6728 } 6729 mFirstActivePosition = firstActivePosition; 6730 6731 //noinspection MismatchedReadAndWriteOfArray 6732 final View[] activeViews = mActiveViews; 6733 for (int i = 0; i < childCount; i++) { 6734 View child = getChildAt(i); 6735 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); 6736 // Don't put header or footer views into the scrap heap 6737 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6738 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. 6739 // However, we will NOT place them into scrap views. 6740 activeViews[i] = child; 6741 // Remember the position so that setupChild() doesn't reset state. 6742 lp.scrappedFromPosition = firstActivePosition + i; 6743 } 6744 } 6745 } 6746 6747 /** 6748 * Get the view corresponding to the specified position. The view will be removed from 6749 * mActiveViews if it is found. 6750 * 6751 * @param position The position to look up in mActiveViews 6752 * @return The view if it is found, null otherwise 6753 */ getActiveView(int position)6754 View getActiveView(int position) { 6755 int index = position - mFirstActivePosition; 6756 final View[] activeViews = mActiveViews; 6757 if (index >=0 && index < activeViews.length) { 6758 final View match = activeViews[index]; 6759 activeViews[index] = null; 6760 return match; 6761 } 6762 return null; 6763 } 6764 getTransientStateView(int position)6765 View getTransientStateView(int position) { 6766 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) { 6767 long id = mAdapter.getItemId(position); 6768 View result = mTransientStateViewsById.get(id); 6769 mTransientStateViewsById.remove(id); 6770 return result; 6771 } 6772 if (mTransientStateViews != null) { 6773 final int index = mTransientStateViews.indexOfKey(position); 6774 if (index >= 0) { 6775 View result = mTransientStateViews.valueAt(index); 6776 mTransientStateViews.removeAt(index); 6777 return result; 6778 } 6779 } 6780 return null; 6781 } 6782 6783 /** 6784 * Dumps and fully detaches any currently saved views with transient 6785 * state. 6786 */ clearTransientStateViews()6787 void clearTransientStateViews() { 6788 final SparseArray<View> viewsByPos = mTransientStateViews; 6789 if (viewsByPos != null) { 6790 final int N = viewsByPos.size(); 6791 for (int i = 0; i < N; i++) { 6792 removeDetachedView(viewsByPos.valueAt(i), false); 6793 } 6794 viewsByPos.clear(); 6795 } 6796 6797 final LongSparseArray<View> viewsById = mTransientStateViewsById; 6798 if (viewsById != null) { 6799 final int N = viewsById.size(); 6800 for (int i = 0; i < N; i++) { 6801 removeDetachedView(viewsById.valueAt(i), false); 6802 } 6803 viewsById.clear(); 6804 } 6805 } 6806 6807 /** 6808 * @return A view from the ScrapViews collection. These are unordered. 6809 */ getScrapView(int position)6810 View getScrapView(int position) { 6811 final int whichScrap = mAdapter.getItemViewType(position); 6812 if (whichScrap < 0) { 6813 return null; 6814 } 6815 if (mViewTypeCount == 1) { 6816 return retrieveFromScrap(mCurrentScrap, position); 6817 } else if (whichScrap < mScrapViews.length) { 6818 return retrieveFromScrap(mScrapViews[whichScrap], position); 6819 } 6820 return null; 6821 } 6822 6823 /** 6824 * Puts a view into the list of scrap views. 6825 * <p> 6826 * If the list data hasn't changed or the adapter has stable IDs, views 6827 * with transient state will be preserved for later retrieval. 6828 * 6829 * @param scrap The view to add 6830 * @param position The view's position within its parent 6831 */ addScrapView(View scrap, int position)6832 void addScrapView(View scrap, int position) { 6833 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); 6834 if (lp == null) { 6835 // Can't recycle, but we don't know anything about the view. 6836 // Ignore it completely. 6837 return; 6838 } 6839 6840 lp.scrappedFromPosition = position; 6841 6842 // Remove but don't scrap header or footer views, or views that 6843 // should otherwise not be recycled. 6844 final int viewType = lp.viewType; 6845 if (!shouldRecycleViewType(viewType)) { 6846 // Can't recycle. If it's not a header or footer, which have 6847 // special handling and should be ignored, then skip the scrap 6848 // heap and we'll fully detach the view later. 6849 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6850 getSkippedScrap().add(scrap); 6851 } 6852 return; 6853 } 6854 6855 scrap.dispatchStartTemporaryDetach(); 6856 6857 // The the accessibility state of the view may change while temporary 6858 // detached and we do not allow detached views to fire accessibility 6859 // events. So we are announcing that the subtree changed giving a chance 6860 // to clients holding on to a view in this subtree to refresh it. 6861 notifyViewAccessibilityStateChangedIfNeeded( 6862 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); 6863 6864 // Don't scrap views that have transient state. 6865 final boolean scrapHasTransientState = scrap.hasTransientState(); 6866 if (scrapHasTransientState) { 6867 if (mAdapter != null && mAdapterHasStableIds) { 6868 // If the adapter has stable IDs, we can reuse the view for 6869 // the same data. 6870 if (mTransientStateViewsById == null) { 6871 mTransientStateViewsById = new LongSparseArray<>(); 6872 } 6873 mTransientStateViewsById.put(lp.itemId, scrap); 6874 } else if (!mDataChanged) { 6875 // If the data hasn't changed, we can reuse the views at 6876 // their old positions. 6877 if (mTransientStateViews == null) { 6878 mTransientStateViews = new SparseArray<>(); 6879 } 6880 mTransientStateViews.put(position, scrap); 6881 } else { 6882 // Otherwise, we'll have to remove the view and start over. 6883 clearScrapForRebind(scrap); 6884 getSkippedScrap().add(scrap); 6885 } 6886 } else { 6887 clearScrapForRebind(scrap); 6888 if (mViewTypeCount == 1) { 6889 mCurrentScrap.add(scrap); 6890 } else { 6891 mScrapViews[viewType].add(scrap); 6892 } 6893 6894 if (mRecyclerListener != null) { 6895 mRecyclerListener.onMovedToScrapHeap(scrap); 6896 } 6897 } 6898 } 6899 getSkippedScrap()6900 private ArrayList<View> getSkippedScrap() { 6901 if (mSkippedScrap == null) { 6902 mSkippedScrap = new ArrayList<>(); 6903 } 6904 return mSkippedScrap; 6905 } 6906 6907 /** 6908 * Finish the removal of any views that skipped the scrap heap. 6909 */ removeSkippedScrap()6910 void removeSkippedScrap() { 6911 if (mSkippedScrap == null) { 6912 return; 6913 } 6914 final int count = mSkippedScrap.size(); 6915 for (int i = 0; i < count; i++) { 6916 removeDetachedView(mSkippedScrap.get(i), false); 6917 } 6918 mSkippedScrap.clear(); 6919 } 6920 6921 /** 6922 * Move all views remaining in mActiveViews to mScrapViews. 6923 */ scrapActiveViews()6924 void scrapActiveViews() { 6925 final View[] activeViews = mActiveViews; 6926 final boolean hasListener = mRecyclerListener != null; 6927 final boolean multipleScraps = mViewTypeCount > 1; 6928 6929 ArrayList<View> scrapViews = mCurrentScrap; 6930 final int count = activeViews.length; 6931 for (int i = count - 1; i >= 0; i--) { 6932 final View victim = activeViews[i]; 6933 if (victim != null) { 6934 final AbsListView.LayoutParams lp 6935 = (AbsListView.LayoutParams) victim.getLayoutParams(); 6936 final int whichScrap = lp.viewType; 6937 6938 activeViews[i] = null; 6939 6940 if (victim.hasTransientState()) { 6941 // Store views with transient state for later use. 6942 victim.dispatchStartTemporaryDetach(); 6943 6944 if (mAdapter != null && mAdapterHasStableIds) { 6945 if (mTransientStateViewsById == null) { 6946 mTransientStateViewsById = new LongSparseArray<View>(); 6947 } 6948 long id = mAdapter.getItemId(mFirstActivePosition + i); 6949 mTransientStateViewsById.put(id, victim); 6950 } else if (!mDataChanged) { 6951 if (mTransientStateViews == null) { 6952 mTransientStateViews = new SparseArray<View>(); 6953 } 6954 mTransientStateViews.put(mFirstActivePosition + i, victim); 6955 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6956 // The data has changed, we can't keep this view. 6957 removeDetachedView(victim, false); 6958 } 6959 } else if (!shouldRecycleViewType(whichScrap)) { 6960 // Discard non-recyclable views except headers/footers. 6961 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6962 removeDetachedView(victim, false); 6963 } 6964 } else { 6965 // Store everything else on the appropriate scrap heap. 6966 if (multipleScraps) { 6967 scrapViews = mScrapViews[whichScrap]; 6968 } 6969 6970 lp.scrappedFromPosition = mFirstActivePosition + i; 6971 removeDetachedView(victim, false); 6972 scrapViews.add(victim); 6973 6974 if (hasListener) { 6975 mRecyclerListener.onMovedToScrapHeap(victim); 6976 } 6977 } 6978 } 6979 } 6980 pruneScrapViews(); 6981 } 6982 6983 /** 6984 * At the end of a layout pass, all temp detached views should either be re-attached or 6985 * completely detached. This method ensures that any remaining view in the scrap list is 6986 * fully detached. 6987 */ fullyDetachScrapViews()6988 void fullyDetachScrapViews() { 6989 final int viewTypeCount = mViewTypeCount; 6990 final ArrayList<View>[] scrapViews = mScrapViews; 6991 for (int i = 0; i < viewTypeCount; ++i) { 6992 final ArrayList<View> scrapPile = scrapViews[i]; 6993 for (int j = scrapPile.size() - 1; j >= 0; j--) { 6994 final View view = scrapPile.get(j); 6995 if (view.isTemporarilyDetached()) { 6996 removeDetachedView(view, false); 6997 } 6998 } 6999 } 7000 } 7001 7002 /** 7003 * Makes sure that the size of mScrapViews does not exceed the size of 7004 * mActiveViews, which can happen if an adapter does not recycle its 7005 * views. Removes cached transient state views that no longer have 7006 * transient state. 7007 */ pruneScrapViews()7008 private void pruneScrapViews() { 7009 final int maxViews = mActiveViews.length; 7010 final int viewTypeCount = mViewTypeCount; 7011 final ArrayList<View>[] scrapViews = mScrapViews; 7012 for (int i = 0; i < viewTypeCount; ++i) { 7013 final ArrayList<View> scrapPile = scrapViews[i]; 7014 int size = scrapPile.size(); 7015 while (size > maxViews) { 7016 scrapPile.remove(--size); 7017 } 7018 } 7019 7020 final SparseArray<View> transViewsByPos = mTransientStateViews; 7021 if (transViewsByPos != null) { 7022 for (int i = 0; i < transViewsByPos.size(); i++) { 7023 final View v = transViewsByPos.valueAt(i); 7024 if (!v.hasTransientState()) { 7025 removeDetachedView(v, false); 7026 transViewsByPos.removeAt(i); 7027 i--; 7028 } 7029 } 7030 } 7031 7032 final LongSparseArray<View> transViewsById = mTransientStateViewsById; 7033 if (transViewsById != null) { 7034 for (int i = 0; i < transViewsById.size(); i++) { 7035 final View v = transViewsById.valueAt(i); 7036 if (!v.hasTransientState()) { 7037 removeDetachedView(v, false); 7038 transViewsById.removeAt(i); 7039 i--; 7040 } 7041 } 7042 } 7043 } 7044 7045 /** 7046 * Puts all views in the scrap heap into the supplied list. 7047 */ reclaimScrapViews(List<View> views)7048 void reclaimScrapViews(List<View> views) { 7049 if (mViewTypeCount == 1) { 7050 views.addAll(mCurrentScrap); 7051 } else { 7052 final int viewTypeCount = mViewTypeCount; 7053 final ArrayList<View>[] scrapViews = mScrapViews; 7054 for (int i = 0; i < viewTypeCount; ++i) { 7055 final ArrayList<View> scrapPile = scrapViews[i]; 7056 views.addAll(scrapPile); 7057 } 7058 } 7059 } 7060 7061 /** 7062 * Updates the cache color hint of all known views. 7063 * 7064 * @param color The new cache color hint. 7065 */ setCacheColorHint(int color)7066 void setCacheColorHint(int color) { 7067 if (mViewTypeCount == 1) { 7068 final ArrayList<View> scrap = mCurrentScrap; 7069 final int scrapCount = scrap.size(); 7070 for (int i = 0; i < scrapCount; i++) { 7071 scrap.get(i).setDrawingCacheBackgroundColor(color); 7072 } 7073 } else { 7074 final int typeCount = mViewTypeCount; 7075 for (int i = 0; i < typeCount; i++) { 7076 final ArrayList<View> scrap = mScrapViews[i]; 7077 final int scrapCount = scrap.size(); 7078 for (int j = 0; j < scrapCount; j++) { 7079 scrap.get(j).setDrawingCacheBackgroundColor(color); 7080 } 7081 } 7082 } 7083 // Just in case this is called during a layout pass 7084 final View[] activeViews = mActiveViews; 7085 final int count = activeViews.length; 7086 for (int i = 0; i < count; ++i) { 7087 final View victim = activeViews[i]; 7088 if (victim != null) { 7089 victim.setDrawingCacheBackgroundColor(color); 7090 } 7091 } 7092 } 7093 retrieveFromScrap(ArrayList<View> scrapViews, int position)7094 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) { 7095 final int size = scrapViews.size(); 7096 if (size > 0) { 7097 // See if we still have a view for this position or ID. 7098 // Traverse backwards to find the most recently used scrap view 7099 for (int i = size - 1; i >= 0; i--) { 7100 final View view = scrapViews.get(i); 7101 final AbsListView.LayoutParams params = 7102 (AbsListView.LayoutParams) view.getLayoutParams(); 7103 7104 if (mAdapterHasStableIds) { 7105 final long id = mAdapter.getItemId(position); 7106 if (id == params.itemId) { 7107 return scrapViews.remove(i); 7108 } 7109 } else if (params.scrappedFromPosition == position) { 7110 final View scrap = scrapViews.remove(i); 7111 clearScrapForRebind(scrap); 7112 return scrap; 7113 } 7114 } 7115 final View scrap = scrapViews.remove(size - 1); 7116 clearScrapForRebind(scrap); 7117 return scrap; 7118 } else { 7119 return null; 7120 } 7121 } 7122 clearScrap(final ArrayList<View> scrap)7123 private void clearScrap(final ArrayList<View> scrap) { 7124 final int scrapCount = scrap.size(); 7125 for (int j = 0; j < scrapCount; j++) { 7126 removeDetachedView(scrap.remove(scrapCount - 1 - j), false); 7127 } 7128 } 7129 clearScrapForRebind(View view)7130 private void clearScrapForRebind(View view) { 7131 view.clearAccessibilityFocus(); 7132 view.setAccessibilityDelegate(null); 7133 } 7134 removeDetachedView(View child, boolean animate)7135 private void removeDetachedView(View child, boolean animate) { 7136 child.setAccessibilityDelegate(null); 7137 AbsListView.this.removeDetachedView(child, animate); 7138 } 7139 } 7140 7141 /** 7142 * Returns the height of the view for the specified position. 7143 * 7144 * @param position the item position 7145 * @return view height in pixels 7146 */ getHeightForPosition(int position)7147 int getHeightForPosition(int position) { 7148 final int firstVisiblePosition = getFirstVisiblePosition(); 7149 final int childCount = getChildCount(); 7150 final int index = position - firstVisiblePosition; 7151 if (index >= 0 && index < childCount) { 7152 // Position is on-screen, use existing view. 7153 final View view = getChildAt(index); 7154 return view.getHeight(); 7155 } else { 7156 // Position is off-screen, obtain & recycle view. 7157 final View view = obtainView(position, mIsScrap); 7158 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED); 7159 final int height = view.getMeasuredHeight(); 7160 mRecycler.addScrapView(view, position); 7161 return height; 7162 } 7163 } 7164 7165 /** 7166 * Sets the selected item and positions the selection y pixels from the top edge 7167 * of the ListView. (If in touch mode, the item will not be selected but it will 7168 * still be positioned appropriately.) 7169 * 7170 * @param position Index (starting at 0) of the data item to be selected. 7171 * @param y The distance from the top edge of the ListView (plus padding) that the 7172 * item will be positioned. 7173 */ setSelectionFromTop(int position, int y)7174 public void setSelectionFromTop(int position, int y) { 7175 if (mAdapter == null) { 7176 return; 7177 } 7178 7179 if (!isInTouchMode()) { 7180 position = lookForSelectablePosition(position, true); 7181 if (position >= 0) { 7182 setNextSelectedPositionInt(position); 7183 } 7184 } else { 7185 mResurrectToPosition = position; 7186 } 7187 7188 if (position >= 0) { 7189 mLayoutMode = LAYOUT_SPECIFIC; 7190 mSpecificTop = mListPadding.top + y; 7191 7192 if (mNeedSync) { 7193 mSyncPosition = position; 7194 mSyncRowId = mAdapter.getItemId(position); 7195 } 7196 7197 if (mPositionScroller != null) { 7198 mPositionScroller.stop(); 7199 } 7200 requestLayout(); 7201 } 7202 } 7203 7204 /** @hide */ 7205 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)7206 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 7207 super.encodeProperties(encoder); 7208 7209 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint()); 7210 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled()); 7211 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled()); 7212 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled()); 7213 encoder.addProperty("list:stackFromBottom", isStackFromBottom()); 7214 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled()); 7215 7216 View selectedView = getSelectedView(); 7217 if (selectedView != null) { 7218 encoder.addPropertyKey("selectedView"); 7219 selectedView.encode(encoder); 7220 } 7221 } 7222 7223 /** 7224 * Abstract positon scroller used to handle smooth scrolling. 7225 */ 7226 static abstract class AbsPositionScroller { 7227 public abstract void start(int position); 7228 public abstract void start(int position, int boundPosition); 7229 public abstract void startWithOffset(int position, int offset); 7230 public abstract void startWithOffset(int position, int offset, int duration); 7231 public abstract void stop(); 7232 } 7233 7234 /** 7235 * Default position scroller that simulates a fling. 7236 */ 7237 class PositionScroller extends AbsPositionScroller implements Runnable { 7238 private static final int SCROLL_DURATION = 200; 7239 7240 private static final int MOVE_DOWN_POS = 1; 7241 private static final int MOVE_UP_POS = 2; 7242 private static final int MOVE_DOWN_BOUND = 3; 7243 private static final int MOVE_UP_BOUND = 4; 7244 private static final int MOVE_OFFSET = 5; 7245 7246 private int mMode; 7247 private int mTargetPos; 7248 private int mBoundPos; 7249 private int mLastSeenPos; 7250 private int mScrollDuration; 7251 private final int mExtraScroll; 7252 7253 private int mOffsetFromTop; 7254 PositionScroller()7255 PositionScroller() { 7256 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); 7257 } 7258 7259 @Override start(final int position)7260 public void start(final int position) { 7261 stop(); 7262 7263 if (mDataChanged) { 7264 // Wait until we're back in a stable state to try this. 7265 mPositionScrollAfterLayout = new Runnable() { 7266 @Override public void run() { 7267 start(position); 7268 } 7269 }; 7270 return; 7271 } 7272 7273 final int childCount = getChildCount(); 7274 if (childCount == 0) { 7275 // Can't scroll without children. 7276 return; 7277 } 7278 7279 final int firstPos = mFirstPosition; 7280 final int lastPos = firstPos + childCount - 1; 7281 7282 int viewTravelCount; 7283 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); 7284 if (clampedPosition < firstPos) { 7285 viewTravelCount = firstPos - clampedPosition + 1; 7286 mMode = MOVE_UP_POS; 7287 } else if (clampedPosition > lastPos) { 7288 viewTravelCount = clampedPosition - lastPos + 1; 7289 mMode = MOVE_DOWN_POS; 7290 } else { 7291 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION); 7292 return; 7293 } 7294 7295 if (viewTravelCount > 0) { 7296 mScrollDuration = SCROLL_DURATION / viewTravelCount; 7297 } else { 7298 mScrollDuration = SCROLL_DURATION; 7299 } 7300 mTargetPos = clampedPosition; 7301 mBoundPos = INVALID_POSITION; 7302 mLastSeenPos = INVALID_POSITION; 7303 7304 postOnAnimation(this); 7305 } 7306 7307 @Override start(final int position, final int boundPosition)7308 public void start(final int position, final int boundPosition) { 7309 stop(); 7310 7311 if (boundPosition == INVALID_POSITION) { 7312 start(position); 7313 return; 7314 } 7315 7316 if (mDataChanged) { 7317 // Wait until we're back in a stable state to try this. 7318 mPositionScrollAfterLayout = new Runnable() { 7319 @Override public void run() { 7320 start(position, boundPosition); 7321 } 7322 }; 7323 return; 7324 } 7325 7326 final int childCount = getChildCount(); 7327 if (childCount == 0) { 7328 // Can't scroll without children. 7329 return; 7330 } 7331 7332 final int firstPos = mFirstPosition; 7333 final int lastPos = firstPos + childCount - 1; 7334 7335 int viewTravelCount; 7336 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); 7337 if (clampedPosition < firstPos) { 7338 final int boundPosFromLast = lastPos - boundPosition; 7339 if (boundPosFromLast < 1) { 7340 // Moving would shift our bound position off the screen. Abort. 7341 return; 7342 } 7343 7344 final int posTravel = firstPos - clampedPosition + 1; 7345 final int boundTravel = boundPosFromLast - 1; 7346 if (boundTravel < posTravel) { 7347 viewTravelCount = boundTravel; 7348 mMode = MOVE_UP_BOUND; 7349 } else { 7350 viewTravelCount = posTravel; 7351 mMode = MOVE_UP_POS; 7352 } 7353 } else if (clampedPosition > lastPos) { 7354 final int boundPosFromFirst = boundPosition - firstPos; 7355 if (boundPosFromFirst < 1) { 7356 // Moving would shift our bound position off the screen. Abort. 7357 return; 7358 } 7359 7360 final int posTravel = clampedPosition - lastPos + 1; 7361 final int boundTravel = boundPosFromFirst - 1; 7362 if (boundTravel < posTravel) { 7363 viewTravelCount = boundTravel; 7364 mMode = MOVE_DOWN_BOUND; 7365 } else { 7366 viewTravelCount = posTravel; 7367 mMode = MOVE_DOWN_POS; 7368 } 7369 } else { 7370 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION); 7371 return; 7372 } 7373 7374 if (viewTravelCount > 0) { 7375 mScrollDuration = SCROLL_DURATION / viewTravelCount; 7376 } else { 7377 mScrollDuration = SCROLL_DURATION; 7378 } 7379 mTargetPos = clampedPosition; 7380 mBoundPos = boundPosition; 7381 mLastSeenPos = INVALID_POSITION; 7382 7383 postOnAnimation(this); 7384 } 7385 7386 @Override startWithOffset(int position, int offset)7387 public void startWithOffset(int position, int offset) { 7388 startWithOffset(position, offset, SCROLL_DURATION); 7389 } 7390 7391 @Override startWithOffset(final int position, int offset, final int duration)7392 public void startWithOffset(final int position, int offset, final int duration) { 7393 stop(); 7394 7395 if (mDataChanged) { 7396 // Wait until we're back in a stable state to try this. 7397 final int postOffset = offset; 7398 mPositionScrollAfterLayout = new Runnable() { 7399 @Override public void run() { 7400 startWithOffset(position, postOffset, duration); 7401 } 7402 }; 7403 return; 7404 } 7405 7406 final int childCount = getChildCount(); 7407 if (childCount == 0) { 7408 // Can't scroll without children. 7409 return; 7410 } 7411 7412 offset += getPaddingTop(); 7413 7414 mTargetPos = Math.max(0, Math.min(getCount() - 1, position)); 7415 mOffsetFromTop = offset; 7416 mBoundPos = INVALID_POSITION; 7417 mLastSeenPos = INVALID_POSITION; 7418 mMode = MOVE_OFFSET; 7419 7420 final int firstPos = mFirstPosition; 7421 final int lastPos = firstPos + childCount - 1; 7422 7423 int viewTravelCount; 7424 if (mTargetPos < firstPos) { 7425 viewTravelCount = firstPos - mTargetPos; 7426 } else if (mTargetPos > lastPos) { 7427 viewTravelCount = mTargetPos - lastPos; 7428 } else { 7429 // On-screen, just scroll. 7430 final int targetTop = getChildAt(mTargetPos - firstPos).getTop(); 7431 smoothScrollBy(targetTop - offset, duration, true, false); 7432 return; 7433 } 7434 7435 // Estimate how many screens we should travel 7436 final float screenTravelCount = (float) viewTravelCount / childCount; 7437 mScrollDuration = screenTravelCount < 1 ? 7438 duration : (int) (duration / screenTravelCount); 7439 mLastSeenPos = INVALID_POSITION; 7440 7441 postOnAnimation(this); 7442 } 7443 7444 /** 7445 * Scroll such that targetPos is in the visible padded region without scrolling 7446 * boundPos out of view. Assumes targetPos is onscreen. 7447 */ 7448 private void scrollToVisible(int targetPos, int boundPos, int duration) { 7449 final int firstPos = mFirstPosition; 7450 final int childCount = getChildCount(); 7451 final int lastPos = firstPos + childCount - 1; 7452 final int paddedTop = mListPadding.top; 7453 final int paddedBottom = getHeight() - mListPadding.bottom; 7454 7455 if (targetPos < firstPos || targetPos > lastPos) { 7456 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + 7457 " not visible [" + firstPos + ", " + lastPos + "]"); 7458 } 7459 if (boundPos < firstPos || boundPos > lastPos) { 7460 // boundPos doesn't matter, it's already offscreen. 7461 boundPos = INVALID_POSITION; 7462 } 7463 7464 final View targetChild = getChildAt(targetPos - firstPos); 7465 final int targetTop = targetChild.getTop(); 7466 final int targetBottom = targetChild.getBottom(); 7467 int scrollBy = 0; 7468 7469 if (targetBottom > paddedBottom) { 7470 scrollBy = targetBottom - paddedBottom; 7471 } 7472 if (targetTop < paddedTop) { 7473 scrollBy = targetTop - paddedTop; 7474 } 7475 7476 if (scrollBy == 0) { 7477 return; 7478 } 7479 7480 if (boundPos >= 0) { 7481 final View boundChild = getChildAt(boundPos - firstPos); 7482 final int boundTop = boundChild.getTop(); 7483 final int boundBottom = boundChild.getBottom(); 7484 final int absScroll = Math.abs(scrollBy); 7485 7486 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { 7487 // Don't scroll the bound view off the bottom of the screen. 7488 scrollBy = Math.max(0, boundBottom - paddedBottom); 7489 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { 7490 // Don't scroll the bound view off the top of the screen. 7491 scrollBy = Math.min(0, boundTop - paddedTop); 7492 } 7493 } 7494 7495 smoothScrollBy(scrollBy, duration); 7496 } 7497 7498 @Override stop()7499 public void stop() { 7500 removeCallbacks(this); 7501 } 7502 7503 @Override run()7504 public void run() { 7505 final int listHeight = getHeight(); 7506 final int firstPos = mFirstPosition; 7507 7508 switch (mMode) { 7509 case MOVE_DOWN_POS: { 7510 final int lastViewIndex = getChildCount() - 1; 7511 final int lastPos = firstPos + lastViewIndex; 7512 7513 if (lastViewIndex < 0) { 7514 return; 7515 } 7516 7517 if (lastPos == mLastSeenPos) { 7518 // No new views, let things keep going. 7519 postOnAnimation(this); 7520 return; 7521 } 7522 7523 final View lastView = getChildAt(lastViewIndex); 7524 final int lastViewHeight = lastView.getHeight(); 7525 final int lastViewTop = lastView.getTop(); 7526 final int lastViewPixelsShowing = listHeight - lastViewTop; 7527 final int extraScroll = lastPos < mItemCount - 1 ? 7528 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom; 7529 7530 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll; 7531 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos); 7532 7533 mLastSeenPos = lastPos; 7534 if (lastPos < mTargetPos) { 7535 postOnAnimation(this); 7536 } 7537 break; 7538 } 7539 7540 case MOVE_DOWN_BOUND: { 7541 final int nextViewIndex = 1; 7542 final int childCount = getChildCount(); 7543 7544 if (firstPos == mBoundPos || childCount <= nextViewIndex 7545 || firstPos + childCount >= mItemCount) { 7546 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 7547 return; 7548 } 7549 final int nextPos = firstPos + nextViewIndex; 7550 7551 if (nextPos == mLastSeenPos) { 7552 // No new views, let things keep going. 7553 postOnAnimation(this); 7554 return; 7555 } 7556 7557 final View nextView = getChildAt(nextViewIndex); 7558 final int nextViewHeight = nextView.getHeight(); 7559 final int nextViewTop = nextView.getTop(); 7560 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll); 7561 if (nextPos < mBoundPos) { 7562 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), 7563 mScrollDuration, true, true); 7564 7565 mLastSeenPos = nextPos; 7566 7567 postOnAnimation(this); 7568 } else { 7569 if (nextViewTop > extraScroll) { 7570 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false); 7571 } else { 7572 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 7573 } 7574 } 7575 break; 7576 } 7577 7578 case MOVE_UP_POS: { 7579 if (firstPos == mLastSeenPos) { 7580 // No new views, let things keep going. 7581 postOnAnimation(this); 7582 return; 7583 } 7584 7585 final View firstView = getChildAt(0); 7586 if (firstView == null) { 7587 return; 7588 } 7589 final int firstViewTop = firstView.getTop(); 7590 final int extraScroll = firstPos > 0 ? 7591 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top; 7592 7593 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true, 7594 firstPos > mTargetPos); 7595 7596 mLastSeenPos = firstPos; 7597 7598 if (firstPos > mTargetPos) { 7599 postOnAnimation(this); 7600 } 7601 break; 7602 } 7603 7604 case MOVE_UP_BOUND: { 7605 final int lastViewIndex = getChildCount() - 2; 7606 if (lastViewIndex < 0) { 7607 return; 7608 } 7609 final int lastPos = firstPos + lastViewIndex; 7610 7611 if (lastPos == mLastSeenPos) { 7612 // No new views, let things keep going. 7613 postOnAnimation(this); 7614 return; 7615 } 7616 7617 final View lastView = getChildAt(lastViewIndex); 7618 final int lastViewHeight = lastView.getHeight(); 7619 final int lastViewTop = lastView.getTop(); 7620 final int lastViewPixelsShowing = listHeight - lastViewTop; 7621 final int extraScroll = Math.max(mListPadding.top, mExtraScroll); 7622 mLastSeenPos = lastPos; 7623 if (lastPos > mBoundPos) { 7624 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true, 7625 true); 7626 postOnAnimation(this); 7627 } else { 7628 final int bottom = listHeight - extraScroll; 7629 final int lastViewBottom = lastViewTop + lastViewHeight; 7630 if (bottom > lastViewBottom) { 7631 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false); 7632 } else { 7633 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 7634 } 7635 } 7636 break; 7637 } 7638 7639 case MOVE_OFFSET: { 7640 if (mLastSeenPos == firstPos) { 7641 // No new views, let things keep going. 7642 postOnAnimation(this); 7643 return; 7644 } 7645 7646 mLastSeenPos = firstPos; 7647 7648 final int childCount = getChildCount(); 7649 final int position = mTargetPos; 7650 final int lastPos = firstPos + childCount - 1; 7651 7652 // Account for the visible "portion" of the first / last child when we estimate 7653 // how many screens we should travel to reach our target 7654 final View firstChild = getChildAt(0); 7655 final int firstChildHeight = firstChild.getHeight(); 7656 final View lastChild = getChildAt(childCount - 1); 7657 final int lastChildHeight = lastChild.getHeight(); 7658 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f 7659 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight; 7660 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f 7661 : (float) (lastChildHeight + getHeight() - lastChild.getBottom()) 7662 / lastChildHeight; 7663 7664 float viewTravelCount = 0; 7665 if (position < firstPos) { 7666 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1; 7667 } else if (position > lastPos) { 7668 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart); 7669 } 7670 7671 // Estimate how many screens we should travel 7672 final float screenTravelCount = viewTravelCount / childCount; 7673 7674 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f); 7675 if (position < firstPos) { 7676 final int distance = (int) (-getHeight() * modifier); 7677 final int duration = (int) (mScrollDuration * modifier); 7678 smoothScrollBy(distance, duration, true, true); 7679 postOnAnimation(this); 7680 } else if (position > lastPos) { 7681 final int distance = (int) (getHeight() * modifier); 7682 final int duration = (int) (mScrollDuration * modifier); 7683 smoothScrollBy(distance, duration, true, true); 7684 postOnAnimation(this); 7685 } else { 7686 // On-screen, just scroll. 7687 final int targetTop = getChildAt(position - firstPos).getTop(); 7688 final int distance = targetTop - mOffsetFromTop; 7689 final int duration = (int) (mScrollDuration * 7690 ((float) Math.abs(distance) / getHeight())); 7691 smoothScrollBy(distance, duration, true, false); 7692 } 7693 break; 7694 } 7695 7696 default: 7697 break; 7698 } 7699 } 7700 } 7701 } 7702