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