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