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