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 if (lp != vlp) { 2399 child.setLayoutParams(lp); 2400 } 2401 } 2402 2403 class ListItemAccessibilityDelegate extends AccessibilityDelegate { 2404 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)2405 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 2406 super.onInitializeAccessibilityNodeInfo(host, info); 2407 2408 final int position = getPositionForView(host); 2409 onInitializeAccessibilityNodeInfoForItem(host, position, info); 2410 } 2411 2412 @Override performAccessibilityAction(View host, int action, Bundle arguments)2413 public boolean performAccessibilityAction(View host, int action, Bundle arguments) { 2414 if (super.performAccessibilityAction(host, action, arguments)) { 2415 return true; 2416 } 2417 2418 final int position = getPositionForView(host); 2419 final ListAdapter adapter = getAdapter(); 2420 2421 if ((position == INVALID_POSITION) || (adapter == null)) { 2422 // Cannot perform actions on invalid items. 2423 return false; 2424 } 2425 2426 if (!isEnabled() || !adapter.isEnabled(position)) { 2427 // Cannot perform actions on disabled items. 2428 return false; 2429 } 2430 2431 final long id = getItemIdAtPosition(position); 2432 2433 switch (action) { 2434 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: { 2435 if (getSelectedItemPosition() == position) { 2436 setSelection(INVALID_POSITION); 2437 return true; 2438 } 2439 } return false; 2440 case AccessibilityNodeInfo.ACTION_SELECT: { 2441 if (getSelectedItemPosition() != position) { 2442 setSelection(position); 2443 return true; 2444 } 2445 } return false; 2446 case AccessibilityNodeInfo.ACTION_CLICK: { 2447 if (isClickable()) { 2448 return performItemClick(host, position, id); 2449 } 2450 } return false; 2451 case AccessibilityNodeInfo.ACTION_LONG_CLICK: { 2452 if (isLongClickable()) { 2453 return performLongPress(host, position, id); 2454 } 2455 } return false; 2456 } 2457 2458 return false; 2459 } 2460 } 2461 2462 /** 2463 * Initializes an {@link AccessibilityNodeInfo} with information about a 2464 * particular item in the list. 2465 * 2466 * @param view View representing the list item. 2467 * @param position Position of the list item within the adapter. 2468 * @param info Node info to populate. 2469 */ onInitializeAccessibilityNodeInfoForItem( View view, int position, AccessibilityNodeInfo info)2470 public void onInitializeAccessibilityNodeInfoForItem( 2471 View view, int position, AccessibilityNodeInfo info) { 2472 final ListAdapter adapter = getAdapter(); 2473 if (position == INVALID_POSITION || adapter == null) { 2474 // The item doesn't exist, so there's not much we can do here. 2475 return; 2476 } 2477 2478 if (!isEnabled() || !adapter.isEnabled(position)) { 2479 info.setEnabled(false); 2480 return; 2481 } 2482 2483 if (position == getSelectedItemPosition()) { 2484 info.setSelected(true); 2485 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION); 2486 } else { 2487 info.addAction(AccessibilityAction.ACTION_SELECT); 2488 } 2489 2490 if (isClickable()) { 2491 info.addAction(AccessibilityAction.ACTION_CLICK); 2492 info.setClickable(true); 2493 } 2494 2495 if (isLongClickable()) { 2496 info.addAction(AccessibilityAction.ACTION_LONG_CLICK); 2497 info.setLongClickable(true); 2498 } 2499 } 2500 2501 /** 2502 * Positions the selector in a way that mimics touch. 2503 */ positionSelectorLikeTouch(int position, View sel, float x, float y)2504 void positionSelectorLikeTouch(int position, View sel, float x, float y) { 2505 positionSelector(position, sel, true, x, y); 2506 } 2507 2508 /** 2509 * Positions the selector in a way that mimics keyboard focus. 2510 */ positionSelectorLikeFocus(int position, View sel)2511 void positionSelectorLikeFocus(int position, View sel) { 2512 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) { 2513 final Rect bounds = mSelectorRect; 2514 final float x = bounds.exactCenterX(); 2515 final float y = bounds.exactCenterY(); 2516 positionSelector(position, sel, true, x, y); 2517 } else { 2518 positionSelector(position, sel); 2519 } 2520 } 2521 positionSelector(int position, View sel)2522 void positionSelector(int position, View sel) { 2523 positionSelector(position, sel, false, -1, -1); 2524 } 2525 positionSelector(int position, View sel, boolean manageHotspot, float x, float y)2526 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) { 2527 final boolean positionChanged = position != mSelectorPosition; 2528 if (position != INVALID_POSITION) { 2529 mSelectorPosition = position; 2530 } 2531 2532 final Rect selectorRect = mSelectorRect; 2533 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); 2534 if (sel instanceof SelectionBoundsAdjuster) { 2535 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect); 2536 } 2537 2538 // Adjust for selection padding. 2539 selectorRect.left -= mSelectionLeftPadding; 2540 selectorRect.top -= mSelectionTopPadding; 2541 selectorRect.right += mSelectionRightPadding; 2542 selectorRect.bottom += mSelectionBottomPadding; 2543 2544 // Update the child enabled state prior to updating the selector. 2545 final boolean isChildViewEnabled = sel.isEnabled(); 2546 if (mIsChildViewEnabled != isChildViewEnabled) { 2547 mIsChildViewEnabled = isChildViewEnabled; 2548 } 2549 2550 // Update the selector drawable's state and position. 2551 final Drawable selector = mSelector; 2552 if (selector != null) { 2553 if (positionChanged) { 2554 // Wipe out the current selector state so that we can start 2555 // over in the new position with a fresh state. 2556 selector.setVisible(false, false); 2557 selector.setState(StateSet.NOTHING); 2558 } 2559 selector.setBounds(selectorRect); 2560 if (positionChanged) { 2561 if (getVisibility() == VISIBLE) { 2562 selector.setVisible(true, false); 2563 } 2564 updateSelectorState(); 2565 } 2566 if (manageHotspot) { 2567 selector.setHotspot(x, y); 2568 } 2569 } 2570 } 2571 2572 @Override dispatchDraw(Canvas canvas)2573 protected void dispatchDraw(Canvas canvas) { 2574 int saveCount = 0; 2575 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; 2576 if (clipToPadding) { 2577 saveCount = canvas.save(); 2578 final int scrollX = mScrollX; 2579 final int scrollY = mScrollY; 2580 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, 2581 scrollX + mRight - mLeft - mPaddingRight, 2582 scrollY + mBottom - mTop - mPaddingBottom); 2583 mGroupFlags &= ~CLIP_TO_PADDING_MASK; 2584 } 2585 2586 final boolean drawSelectorOnTop = mDrawSelectorOnTop; 2587 if (!drawSelectorOnTop) { 2588 drawSelector(canvas); 2589 } 2590 2591 super.dispatchDraw(canvas); 2592 2593 if (drawSelectorOnTop) { 2594 drawSelector(canvas); 2595 } 2596 2597 if (clipToPadding) { 2598 canvas.restoreToCount(saveCount); 2599 mGroupFlags |= CLIP_TO_PADDING_MASK; 2600 } 2601 } 2602 2603 @Override isPaddingOffsetRequired()2604 protected boolean isPaddingOffsetRequired() { 2605 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK; 2606 } 2607 2608 @Override getLeftPaddingOffset()2609 protected int getLeftPaddingOffset() { 2610 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft; 2611 } 2612 2613 @Override getTopPaddingOffset()2614 protected int getTopPaddingOffset() { 2615 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop; 2616 } 2617 2618 @Override getRightPaddingOffset()2619 protected int getRightPaddingOffset() { 2620 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight; 2621 } 2622 2623 @Override getBottomPaddingOffset()2624 protected int getBottomPaddingOffset() { 2625 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom; 2626 } 2627 2628 @Override onSizeChanged(int w, int h, int oldw, int oldh)2629 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 2630 if (getChildCount() > 0) { 2631 mDataChanged = true; 2632 rememberSyncState(); 2633 } 2634 2635 if (mFastScroll != null) { 2636 mFastScroll.onSizeChanged(w, h, oldw, oldh); 2637 } 2638 } 2639 2640 /** 2641 * @return True if the current touch mode requires that we draw the selector in the pressed 2642 * state. 2643 */ touchModeDrawsInPressedState()2644 boolean touchModeDrawsInPressedState() { 2645 // FIXME use isPressed for this 2646 switch (mTouchMode) { 2647 case TOUCH_MODE_TAP: 2648 case TOUCH_MODE_DONE_WAITING: 2649 return true; 2650 default: 2651 return false; 2652 } 2653 } 2654 2655 /** 2656 * Indicates whether this view is in a state where the selector should be drawn. This will 2657 * happen if we have focus but are not in touch mode, or we are in the middle of displaying 2658 * the pressed state for an item. 2659 * 2660 * @return True if the selector should be shown 2661 */ shouldShowSelector()2662 boolean shouldShowSelector() { 2663 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed()); 2664 } 2665 drawSelector(Canvas canvas)2666 private void drawSelector(Canvas canvas) { 2667 if (!mSelectorRect.isEmpty()) { 2668 final Drawable selector = mSelector; 2669 selector.setBounds(mSelectorRect); 2670 selector.draw(canvas); 2671 } 2672 } 2673 2674 /** 2675 * Controls whether the selection highlight drawable should be drawn on top of the item or 2676 * behind it. 2677 * 2678 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default 2679 * is false. 2680 * 2681 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop 2682 */ setDrawSelectorOnTop(boolean onTop)2683 public void setDrawSelectorOnTop(boolean onTop) { 2684 mDrawSelectorOnTop = onTop; 2685 } 2686 2687 /** 2688 * Set a Drawable that should be used to highlight the currently selected item. 2689 * 2690 * @param resID A Drawable resource to use as the selection highlight. 2691 * 2692 * @attr ref android.R.styleable#AbsListView_listSelector 2693 */ setSelector(@rawableRes int resID)2694 public void setSelector(@DrawableRes int resID) { 2695 setSelector(getContext().getDrawable(resID)); 2696 } 2697 setSelector(Drawable sel)2698 public void setSelector(Drawable sel) { 2699 if (mSelector != null) { 2700 mSelector.setCallback(null); 2701 unscheduleDrawable(mSelector); 2702 } 2703 mSelector = sel; 2704 Rect padding = new Rect(); 2705 sel.getPadding(padding); 2706 mSelectionLeftPadding = padding.left; 2707 mSelectionTopPadding = padding.top; 2708 mSelectionRightPadding = padding.right; 2709 mSelectionBottomPadding = padding.bottom; 2710 sel.setCallback(this); 2711 updateSelectorState(); 2712 } 2713 2714 /** 2715 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the 2716 * selection in the list. 2717 * 2718 * @return the drawable used to display the selector 2719 */ getSelector()2720 public Drawable getSelector() { 2721 return mSelector; 2722 } 2723 2724 /** 2725 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if 2726 * this is a long press. 2727 */ keyPressed()2728 void keyPressed() { 2729 if (!isEnabled() || !isClickable()) { 2730 return; 2731 } 2732 2733 Drawable selector = mSelector; 2734 Rect selectorRect = mSelectorRect; 2735 if (selector != null && (isFocused() || touchModeDrawsInPressedState()) 2736 && !selectorRect.isEmpty()) { 2737 2738 final View v = getChildAt(mSelectedPosition - mFirstPosition); 2739 2740 if (v != null) { 2741 if (v.hasFocusable()) return; 2742 v.setPressed(true); 2743 } 2744 setPressed(true); 2745 2746 final boolean longClickable = isLongClickable(); 2747 Drawable d = selector.getCurrent(); 2748 if (d != null && d instanceof TransitionDrawable) { 2749 if (longClickable) { 2750 ((TransitionDrawable) d).startTransition( 2751 ViewConfiguration.getLongPressTimeout()); 2752 } else { 2753 ((TransitionDrawable) d).resetTransition(); 2754 } 2755 } 2756 if (longClickable && !mDataChanged) { 2757 if (mPendingCheckForKeyLongPress == null) { 2758 mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); 2759 } 2760 mPendingCheckForKeyLongPress.rememberWindowAttachCount(); 2761 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); 2762 } 2763 } 2764 } 2765 setScrollIndicators(View up, View down)2766 public void setScrollIndicators(View up, View down) { 2767 mScrollUp = up; 2768 mScrollDown = down; 2769 } 2770 updateSelectorState()2771 void updateSelectorState() { 2772 if (mSelector != null) { 2773 if (shouldShowSelector()) { 2774 mSelector.setState(getDrawableStateForSelector()); 2775 } else { 2776 mSelector.setState(StateSet.NOTHING); 2777 } 2778 } 2779 } 2780 2781 @Override drawableStateChanged()2782 protected void drawableStateChanged() { 2783 super.drawableStateChanged(); 2784 updateSelectorState(); 2785 } 2786 getDrawableStateForSelector()2787 private int[] getDrawableStateForSelector() { 2788 // If the child view is enabled then do the default behavior. 2789 if (mIsChildViewEnabled) { 2790 // Common case 2791 return super.getDrawableState(); 2792 } 2793 2794 // The selector uses this View's drawable state. The selected child view 2795 // is disabled, so we need to remove the enabled state from the drawable 2796 // states. 2797 final int enabledState = ENABLED_STATE_SET[0]; 2798 2799 // If we don't have any extra space, it will return one of the static 2800 // state arrays, and clearing the enabled state on those arrays is a 2801 // bad thing! If we specify we need extra space, it will create+copy 2802 // into a new array that is safely mutable. 2803 final int[] state = onCreateDrawableState(1); 2804 2805 int enabledPos = -1; 2806 for (int i = state.length - 1; i >= 0; i--) { 2807 if (state[i] == enabledState) { 2808 enabledPos = i; 2809 break; 2810 } 2811 } 2812 2813 // Remove the enabled state 2814 if (enabledPos >= 0) { 2815 System.arraycopy(state, enabledPos + 1, state, enabledPos, 2816 state.length - enabledPos - 1); 2817 } 2818 2819 return state; 2820 } 2821 2822 @Override verifyDrawable(Drawable dr)2823 public boolean verifyDrawable(Drawable dr) { 2824 return mSelector == dr || super.verifyDrawable(dr); 2825 } 2826 2827 @Override jumpDrawablesToCurrentState()2828 public void jumpDrawablesToCurrentState() { 2829 super.jumpDrawablesToCurrentState(); 2830 if (mSelector != null) mSelector.jumpToCurrentState(); 2831 } 2832 2833 @Override onAttachedToWindow()2834 protected void onAttachedToWindow() { 2835 super.onAttachedToWindow(); 2836 2837 final ViewTreeObserver treeObserver = getViewTreeObserver(); 2838 treeObserver.addOnTouchModeChangeListener(this); 2839 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) { 2840 treeObserver.addOnGlobalLayoutListener(this); 2841 } 2842 2843 if (mAdapter != null && mDataSetObserver == null) { 2844 mDataSetObserver = new AdapterDataSetObserver(); 2845 mAdapter.registerDataSetObserver(mDataSetObserver); 2846 2847 // Data may have changed while we were detached. Refresh. 2848 mDataChanged = true; 2849 mOldItemCount = mItemCount; 2850 mItemCount = mAdapter.getCount(); 2851 } 2852 } 2853 2854 @Override onDetachedFromWindow()2855 protected void onDetachedFromWindow() { 2856 super.onDetachedFromWindow(); 2857 2858 mIsDetaching = true; 2859 2860 // Dismiss the popup in case onSaveInstanceState() was not invoked 2861 dismissPopup(); 2862 2863 // Detach any view left in the scrap heap 2864 mRecycler.clear(); 2865 2866 final ViewTreeObserver treeObserver = getViewTreeObserver(); 2867 treeObserver.removeOnTouchModeChangeListener(this); 2868 if (mTextFilterEnabled && mPopup != null) { 2869 treeObserver.removeOnGlobalLayoutListener(this); 2870 mGlobalLayoutListenerAddedFilter = false; 2871 } 2872 2873 if (mAdapter != null && mDataSetObserver != null) { 2874 mAdapter.unregisterDataSetObserver(mDataSetObserver); 2875 mDataSetObserver = null; 2876 } 2877 2878 if (mScrollStrictSpan != null) { 2879 mScrollStrictSpan.finish(); 2880 mScrollStrictSpan = null; 2881 } 2882 2883 if (mFlingStrictSpan != null) { 2884 mFlingStrictSpan.finish(); 2885 mFlingStrictSpan = null; 2886 } 2887 2888 if (mFlingRunnable != null) { 2889 removeCallbacks(mFlingRunnable); 2890 } 2891 2892 if (mPositionScroller != null) { 2893 mPositionScroller.stop(); 2894 } 2895 2896 if (mClearScrollingCache != null) { 2897 removeCallbacks(mClearScrollingCache); 2898 } 2899 2900 if (mPerformClick != null) { 2901 removeCallbacks(mPerformClick); 2902 } 2903 2904 if (mTouchModeReset != null) { 2905 removeCallbacks(mTouchModeReset); 2906 mTouchModeReset.run(); 2907 } 2908 2909 mIsDetaching = false; 2910 } 2911 2912 @Override onWindowFocusChanged(boolean hasWindowFocus)2913 public void onWindowFocusChanged(boolean hasWindowFocus) { 2914 super.onWindowFocusChanged(hasWindowFocus); 2915 2916 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF; 2917 2918 if (!hasWindowFocus) { 2919 setChildrenDrawingCacheEnabled(false); 2920 if (mFlingRunnable != null) { 2921 removeCallbacks(mFlingRunnable); 2922 // let the fling runnable report it's new state which 2923 // should be idle 2924 mFlingRunnable.endFling(); 2925 if (mPositionScroller != null) { 2926 mPositionScroller.stop(); 2927 } 2928 if (mScrollY != 0) { 2929 mScrollY = 0; 2930 invalidateParentCaches(); 2931 finishGlows(); 2932 invalidate(); 2933 } 2934 } 2935 // Always hide the type filter 2936 dismissPopup(); 2937 2938 if (touchMode == TOUCH_MODE_OFF) { 2939 // Remember the last selected element 2940 mResurrectToPosition = mSelectedPosition; 2941 } 2942 } else { 2943 if (mFiltered && !mPopupHidden) { 2944 // Show the type filter only if a filter is in effect 2945 showPopup(); 2946 } 2947 2948 // If we changed touch mode since the last time we had focus 2949 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) { 2950 // If we come back in trackball mode, we bring the selection back 2951 if (touchMode == TOUCH_MODE_OFF) { 2952 // This will trigger a layout 2953 resurrectSelection(); 2954 2955 // If we come back in touch mode, then we want to hide the selector 2956 } else { 2957 hideSelector(); 2958 mLayoutMode = LAYOUT_NORMAL; 2959 layoutChildren(); 2960 } 2961 } 2962 } 2963 2964 mLastTouchMode = touchMode; 2965 } 2966 2967 @Override onRtlPropertiesChanged(int layoutDirection)2968 public void onRtlPropertiesChanged(int layoutDirection) { 2969 super.onRtlPropertiesChanged(layoutDirection); 2970 if (mFastScroll != null) { 2971 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition()); 2972 } 2973 } 2974 2975 /** 2976 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This 2977 * methods knows the view, position and ID of the item that received the 2978 * long press. 2979 * 2980 * @param view The view that received the long press. 2981 * @param position The position of the item that received the long press. 2982 * @param id The ID of the item that received the long press. 2983 * @return The extra information that should be returned by 2984 * {@link #getContextMenuInfo()}. 2985 */ createContextMenuInfo(View view, int position, long id)2986 ContextMenuInfo createContextMenuInfo(View view, int position, long id) { 2987 return new AdapterContextMenuInfo(view, position, id); 2988 } 2989 2990 @Override onCancelPendingInputEvents()2991 public void onCancelPendingInputEvents() { 2992 super.onCancelPendingInputEvents(); 2993 if (mPerformClick != null) { 2994 removeCallbacks(mPerformClick); 2995 } 2996 if (mPendingCheckForTap != null) { 2997 removeCallbacks(mPendingCheckForTap); 2998 } 2999 if (mPendingCheckForLongPress != null) { 3000 removeCallbacks(mPendingCheckForLongPress); 3001 } 3002 if (mPendingCheckForKeyLongPress != null) { 3003 removeCallbacks(mPendingCheckForKeyLongPress); 3004 } 3005 } 3006 3007 /** 3008 * A base class for Runnables that will check that their view is still attached to 3009 * the original window as when the Runnable was created. 3010 * 3011 */ 3012 private class WindowRunnnable { 3013 private int mOriginalAttachCount; 3014 rememberWindowAttachCount()3015 public void rememberWindowAttachCount() { 3016 mOriginalAttachCount = getWindowAttachCount(); 3017 } 3018 sameWindow()3019 public boolean sameWindow() { 3020 return getWindowAttachCount() == mOriginalAttachCount; 3021 } 3022 } 3023 3024 private class PerformClick extends WindowRunnnable implements Runnable { 3025 int mClickMotionPosition; 3026 3027 @Override run()3028 public void run() { 3029 // The data has changed since we posted this action in the event queue, 3030 // bail out before bad things happen 3031 if (mDataChanged) return; 3032 3033 final ListAdapter adapter = mAdapter; 3034 final int motionPosition = mClickMotionPosition; 3035 if (adapter != null && mItemCount > 0 && 3036 motionPosition != INVALID_POSITION && 3037 motionPosition < adapter.getCount() && sameWindow()) { 3038 final View view = getChildAt(motionPosition - mFirstPosition); 3039 // If there is no view, something bad happened (the view scrolled off the 3040 // screen, etc.) and we should cancel the click 3041 if (view != null) { 3042 performItemClick(view, motionPosition, adapter.getItemId(motionPosition)); 3043 } 3044 } 3045 } 3046 } 3047 3048 private class CheckForLongPress extends WindowRunnnable implements Runnable { 3049 @Override run()3050 public void run() { 3051 final int motionPosition = mMotionPosition; 3052 final View child = getChildAt(motionPosition - mFirstPosition); 3053 if (child != null) { 3054 final int longPressPosition = mMotionPosition; 3055 final long longPressId = mAdapter.getItemId(mMotionPosition); 3056 3057 boolean handled = false; 3058 if (sameWindow() && !mDataChanged) { 3059 handled = performLongPress(child, longPressPosition, longPressId); 3060 } 3061 if (handled) { 3062 mTouchMode = TOUCH_MODE_REST; 3063 setPressed(false); 3064 child.setPressed(false); 3065 } else { 3066 mTouchMode = TOUCH_MODE_DONE_WAITING; 3067 } 3068 } 3069 } 3070 } 3071 3072 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable { 3073 @Override run()3074 public void run() { 3075 if (isPressed() && mSelectedPosition >= 0) { 3076 int index = mSelectedPosition - mFirstPosition; 3077 View v = getChildAt(index); 3078 3079 if (!mDataChanged) { 3080 boolean handled = false; 3081 if (sameWindow()) { 3082 handled = performLongPress(v, mSelectedPosition, mSelectedRowId); 3083 } 3084 if (handled) { 3085 setPressed(false); 3086 v.setPressed(false); 3087 } 3088 } else { 3089 setPressed(false); 3090 if (v != null) v.setPressed(false); 3091 } 3092 } 3093 } 3094 } 3095 performStylusButtonPressAction(MotionEvent ev)3096 private boolean performStylusButtonPressAction(MotionEvent ev) { 3097 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { 3098 final View child = getChildAt(mMotionPosition - mFirstPosition); 3099 if (child != null) { 3100 final int longPressPosition = mMotionPosition; 3101 final long longPressId = mAdapter.getItemId(mMotionPosition); 3102 if (performLongPress(child, longPressPosition, longPressId)) { 3103 mTouchMode = TOUCH_MODE_REST; 3104 setPressed(false); 3105 child.setPressed(false); 3106 return true; 3107 } 3108 } 3109 } 3110 return false; 3111 } 3112 performLongPress(final View child, final int longPressPosition, final long longPressId)3113 boolean performLongPress(final View child, 3114 final int longPressPosition, final long longPressId) { 3115 // CHOICE_MODE_MULTIPLE_MODAL takes over long press. 3116 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { 3117 if (mChoiceActionMode == null && 3118 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) { 3119 setItemChecked(longPressPosition, true); 3120 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 3121 } 3122 return true; 3123 } 3124 3125 boolean handled = false; 3126 if (mOnItemLongClickListener != null) { 3127 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child, 3128 longPressPosition, longPressId); 3129 } 3130 if (!handled) { 3131 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); 3132 handled = super.showContextMenuForChild(AbsListView.this); 3133 } 3134 if (handled) { 3135 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 3136 } 3137 return handled; 3138 } 3139 3140 @Override getContextMenuInfo()3141 protected ContextMenuInfo getContextMenuInfo() { 3142 return mContextMenuInfo; 3143 } 3144 3145 /** @hide */ 3146 @Override showContextMenu(float x, float y, int metaState)3147 public boolean showContextMenu(float x, float y, int metaState) { 3148 final int position = pointToPosition((int)x, (int)y); 3149 if (position != INVALID_POSITION) { 3150 final long id = mAdapter.getItemId(position); 3151 View child = getChildAt(position - mFirstPosition); 3152 if (child != null) { 3153 mContextMenuInfo = createContextMenuInfo(child, position, id); 3154 return super.showContextMenuForChild(AbsListView.this); 3155 } 3156 } 3157 return super.showContextMenu(x, y, metaState); 3158 } 3159 3160 @Override showContextMenuForChild(View originalView)3161 public boolean showContextMenuForChild(View originalView) { 3162 final int longPressPosition = getPositionForView(originalView); 3163 if (longPressPosition >= 0) { 3164 final long longPressId = mAdapter.getItemId(longPressPosition); 3165 boolean handled = false; 3166 3167 if (mOnItemLongClickListener != null) { 3168 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView, 3169 longPressPosition, longPressId); 3170 } 3171 if (!handled) { 3172 mContextMenuInfo = createContextMenuInfo( 3173 getChildAt(longPressPosition - mFirstPosition), 3174 longPressPosition, longPressId); 3175 handled = super.showContextMenuForChild(originalView); 3176 } 3177 3178 return handled; 3179 } 3180 return false; 3181 } 3182 3183 @Override onKeyDown(int keyCode, KeyEvent event)3184 public boolean onKeyDown(int keyCode, KeyEvent event) { 3185 return false; 3186 } 3187 3188 @Override onKeyUp(int keyCode, KeyEvent event)3189 public boolean onKeyUp(int keyCode, KeyEvent event) { 3190 if (KeyEvent.isConfirmKey(keyCode)) { 3191 if (!isEnabled()) { 3192 return true; 3193 } 3194 if (isClickable() && isPressed() && 3195 mSelectedPosition >= 0 && mAdapter != null && 3196 mSelectedPosition < mAdapter.getCount()) { 3197 3198 final View view = getChildAt(mSelectedPosition - mFirstPosition); 3199 if (view != null) { 3200 performItemClick(view, mSelectedPosition, mSelectedRowId); 3201 view.setPressed(false); 3202 } 3203 setPressed(false); 3204 return true; 3205 } 3206 } 3207 return super.onKeyUp(keyCode, event); 3208 } 3209 3210 @Override dispatchSetPressed(boolean pressed)3211 protected void dispatchSetPressed(boolean pressed) { 3212 // Don't dispatch setPressed to our children. We call setPressed on ourselves to 3213 // get the selector in the right state, but we don't want to press each child. 3214 } 3215 3216 @Override dispatchDrawableHotspotChanged(float x, float y)3217 public void dispatchDrawableHotspotChanged(float x, float y) { 3218 // Don't dispatch hotspot changes to children. We'll manually handle 3219 // calling drawableHotspotChanged on the correct child. 3220 } 3221 3222 /** 3223 * Maps a point to a position in the list. 3224 * 3225 * @param x X in local coordinate 3226 * @param y Y in local coordinate 3227 * @return The position of the item which contains the specified point, or 3228 * {@link #INVALID_POSITION} if the point does not intersect an item. 3229 */ pointToPosition(int x, int y)3230 public int pointToPosition(int x, int y) { 3231 Rect frame = mTouchFrame; 3232 if (frame == null) { 3233 mTouchFrame = new Rect(); 3234 frame = mTouchFrame; 3235 } 3236 3237 final int count = getChildCount(); 3238 for (int i = count - 1; i >= 0; i--) { 3239 final View child = getChildAt(i); 3240 if (child.getVisibility() == View.VISIBLE) { 3241 child.getHitRect(frame); 3242 if (frame.contains(x, y)) { 3243 return mFirstPosition + i; 3244 } 3245 } 3246 } 3247 return INVALID_POSITION; 3248 } 3249 3250 3251 /** 3252 * Maps a point to a the rowId of the item which intersects that point. 3253 * 3254 * @param x X in local coordinate 3255 * @param y Y in local coordinate 3256 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID} 3257 * if the point does not intersect an item. 3258 */ pointToRowId(int x, int y)3259 public long pointToRowId(int x, int y) { 3260 int position = pointToPosition(x, y); 3261 if (position >= 0) { 3262 return mAdapter.getItemId(position); 3263 } 3264 return INVALID_ROW_ID; 3265 } 3266 3267 private final class CheckForTap implements Runnable { 3268 float x; 3269 float y; 3270 3271 @Override run()3272 public void run() { 3273 if (mTouchMode == TOUCH_MODE_DOWN) { 3274 mTouchMode = TOUCH_MODE_TAP; 3275 final View child = getChildAt(mMotionPosition - mFirstPosition); 3276 if (child != null && !child.hasFocusable()) { 3277 mLayoutMode = LAYOUT_NORMAL; 3278 3279 if (!mDataChanged) { 3280 final float[] point = mTmpPoint; 3281 point[0] = x; 3282 point[1] = y; 3283 transformPointToViewLocal(point, child); 3284 child.drawableHotspotChanged(point[0], point[1]); 3285 child.setPressed(true); 3286 setPressed(true); 3287 layoutChildren(); 3288 positionSelector(mMotionPosition, child); 3289 refreshDrawableState(); 3290 3291 final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); 3292 final boolean longClickable = isLongClickable(); 3293 3294 if (mSelector != null) { 3295 final Drawable d = mSelector.getCurrent(); 3296 if (d != null && d instanceof TransitionDrawable) { 3297 if (longClickable) { 3298 ((TransitionDrawable) d).startTransition(longPressTimeout); 3299 } else { 3300 ((TransitionDrawable) d).resetTransition(); 3301 } 3302 } 3303 mSelector.setHotspot(x, y); 3304 } 3305 3306 if (longClickable) { 3307 if (mPendingCheckForLongPress == null) { 3308 mPendingCheckForLongPress = new CheckForLongPress(); 3309 } 3310 mPendingCheckForLongPress.rememberWindowAttachCount(); 3311 postDelayed(mPendingCheckForLongPress, longPressTimeout); 3312 } else { 3313 mTouchMode = TOUCH_MODE_DONE_WAITING; 3314 } 3315 } else { 3316 mTouchMode = TOUCH_MODE_DONE_WAITING; 3317 } 3318 } 3319 } 3320 } 3321 } 3322 startScrollIfNeeded(int x, int y, MotionEvent vtev)3323 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) { 3324 // Check if we have moved far enough that it looks more like a 3325 // scroll than a tap 3326 final int deltaY = y - mMotionY; 3327 final int distance = Math.abs(deltaY); 3328 final boolean overscroll = mScrollY != 0; 3329 if ((overscroll || distance > mTouchSlop) && 3330 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { 3331 createScrollingCache(); 3332 if (overscroll) { 3333 mTouchMode = TOUCH_MODE_OVERSCROLL; 3334 mMotionCorrection = 0; 3335 } else { 3336 mTouchMode = TOUCH_MODE_SCROLL; 3337 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop; 3338 } 3339 removeCallbacks(mPendingCheckForLongPress); 3340 setPressed(false); 3341 final View motionView = getChildAt(mMotionPosition - mFirstPosition); 3342 if (motionView != null) { 3343 motionView.setPressed(false); 3344 } 3345 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 3346 // Time to start stealing events! Once we've stolen them, don't let anyone 3347 // steal from us 3348 final ViewParent parent = getParent(); 3349 if (parent != null) { 3350 parent.requestDisallowInterceptTouchEvent(true); 3351 } 3352 scrollIfNeeded(x, y, vtev); 3353 return true; 3354 } 3355 3356 return false; 3357 } 3358 scrollIfNeeded(int x, int y, MotionEvent vtev)3359 private void scrollIfNeeded(int x, int y, MotionEvent vtev) { 3360 int rawDeltaY = y - mMotionY; 3361 int scrollOffsetCorrection = 0; 3362 int scrollConsumedCorrection = 0; 3363 if (mLastY == Integer.MIN_VALUE) { 3364 rawDeltaY -= mMotionCorrection; 3365 } 3366 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY, 3367 mScrollConsumed, mScrollOffset)) { 3368 rawDeltaY += mScrollConsumed[1]; 3369 scrollOffsetCorrection = -mScrollOffset[1]; 3370 scrollConsumedCorrection = mScrollConsumed[1]; 3371 if (vtev != null) { 3372 vtev.offsetLocation(0, mScrollOffset[1]); 3373 mNestedYOffset += mScrollOffset[1]; 3374 } 3375 } 3376 final int deltaY = rawDeltaY; 3377 int incrementalDeltaY = 3378 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY; 3379 int lastYCorrection = 0; 3380 3381 if (mTouchMode == TOUCH_MODE_SCROLL) { 3382 if (PROFILE_SCROLLING) { 3383 if (!mScrollProfilingStarted) { 3384 Debug.startMethodTracing("AbsListViewScroll"); 3385 mScrollProfilingStarted = true; 3386 } 3387 } 3388 3389 if (mScrollStrictSpan == null) { 3390 // If it's non-null, we're already in a scroll. 3391 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll"); 3392 } 3393 3394 if (y != mLastY) { 3395 // We may be here after stopping a fling and continuing to scroll. 3396 // If so, we haven't disallowed intercepting touch events yet. 3397 // Make sure that we do so in case we're in a parent that can intercept. 3398 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 && 3399 Math.abs(rawDeltaY) > mTouchSlop) { 3400 final ViewParent parent = getParent(); 3401 if (parent != null) { 3402 parent.requestDisallowInterceptTouchEvent(true); 3403 } 3404 } 3405 3406 final int motionIndex; 3407 if (mMotionPosition >= 0) { 3408 motionIndex = mMotionPosition - mFirstPosition; 3409 } else { 3410 // If we don't have a motion position that we can reliably track, 3411 // pick something in the middle to make a best guess at things below. 3412 motionIndex = getChildCount() / 2; 3413 } 3414 3415 int motionViewPrevTop = 0; 3416 View motionView = this.getChildAt(motionIndex); 3417 if (motionView != null) { 3418 motionViewPrevTop = motionView.getTop(); 3419 } 3420 3421 // No need to do all this work if we're not going to move anyway 3422 boolean atEdge = false; 3423 if (incrementalDeltaY != 0) { 3424 atEdge = trackMotionScroll(deltaY, incrementalDeltaY); 3425 } 3426 3427 // Check to see if we have bumped into the scroll limit 3428 motionView = this.getChildAt(motionIndex); 3429 if (motionView != null) { 3430 // Check if the top of the motion view is where it is 3431 // supposed to be 3432 final int motionViewRealTop = motionView.getTop(); 3433 if (atEdge) { 3434 // Apply overscroll 3435 3436 int overscroll = -incrementalDeltaY - 3437 (motionViewRealTop - motionViewPrevTop); 3438 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll, 3439 mScrollOffset)) { 3440 lastYCorrection -= mScrollOffset[1]; 3441 if (vtev != null) { 3442 vtev.offsetLocation(0, mScrollOffset[1]); 3443 mNestedYOffset += mScrollOffset[1]; 3444 } 3445 } else { 3446 final boolean atOverscrollEdge = overScrollBy(0, overscroll, 3447 0, mScrollY, 0, 0, 0, mOverscrollDistance, true); 3448 3449 if (atOverscrollEdge && mVelocityTracker != null) { 3450 // Don't allow overfling if we're at the edge 3451 mVelocityTracker.clear(); 3452 } 3453 3454 final int overscrollMode = getOverScrollMode(); 3455 if (overscrollMode == OVER_SCROLL_ALWAYS || 3456 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && 3457 !contentFits())) { 3458 if (!atOverscrollEdge) { 3459 mDirection = 0; // Reset when entering overscroll. 3460 mTouchMode = TOUCH_MODE_OVERSCROLL; 3461 } 3462 if (incrementalDeltaY > 0) { 3463 mEdgeGlowTop.onPull((float) -overscroll / getHeight(), 3464 (float) x / getWidth()); 3465 if (!mEdgeGlowBottom.isFinished()) { 3466 mEdgeGlowBottom.onRelease(); 3467 } 3468 invalidateTopGlow(); 3469 } else if (incrementalDeltaY < 0) { 3470 mEdgeGlowBottom.onPull((float) overscroll / getHeight(), 3471 1.f - (float) x / getWidth()); 3472 if (!mEdgeGlowTop.isFinished()) { 3473 mEdgeGlowTop.onRelease(); 3474 } 3475 invalidateBottomGlow(); 3476 } 3477 } 3478 } 3479 } 3480 mMotionY = y + lastYCorrection + scrollOffsetCorrection; 3481 } 3482 mLastY = y + lastYCorrection + scrollOffsetCorrection; 3483 } 3484 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) { 3485 if (y != mLastY) { 3486 final int oldScroll = mScrollY; 3487 final int newScroll = oldScroll - incrementalDeltaY; 3488 int newDirection = y > mLastY ? 1 : -1; 3489 3490 if (mDirection == 0) { 3491 mDirection = newDirection; 3492 } 3493 3494 int overScrollDistance = -incrementalDeltaY; 3495 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) { 3496 overScrollDistance = -oldScroll; 3497 incrementalDeltaY += overScrollDistance; 3498 } else { 3499 incrementalDeltaY = 0; 3500 } 3501 3502 if (overScrollDistance != 0) { 3503 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0, 3504 0, mOverscrollDistance, true); 3505 final int overscrollMode = getOverScrollMode(); 3506 if (overscrollMode == OVER_SCROLL_ALWAYS || 3507 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && 3508 !contentFits())) { 3509 if (rawDeltaY > 0) { 3510 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(), 3511 (float) x / getWidth()); 3512 if (!mEdgeGlowBottom.isFinished()) { 3513 mEdgeGlowBottom.onRelease(); 3514 } 3515 invalidateTopGlow(); 3516 } else if (rawDeltaY < 0) { 3517 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(), 3518 1.f - (float) x / getWidth()); 3519 if (!mEdgeGlowTop.isFinished()) { 3520 mEdgeGlowTop.onRelease(); 3521 } 3522 invalidateBottomGlow(); 3523 } 3524 } 3525 } 3526 3527 if (incrementalDeltaY != 0) { 3528 // Coming back to 'real' list scrolling 3529 if (mScrollY != 0) { 3530 mScrollY = 0; 3531 invalidateParentIfNeeded(); 3532 } 3533 3534 trackMotionScroll(incrementalDeltaY, incrementalDeltaY); 3535 3536 mTouchMode = TOUCH_MODE_SCROLL; 3537 3538 // We did not scroll the full amount. Treat this essentially like the 3539 // start of a new touch scroll 3540 final int motionPosition = findClosestMotionRow(y); 3541 3542 mMotionCorrection = 0; 3543 View motionView = getChildAt(motionPosition - mFirstPosition); 3544 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0; 3545 mMotionY = y + scrollOffsetCorrection; 3546 mMotionPosition = motionPosition; 3547 } 3548 mLastY = y + lastYCorrection + scrollOffsetCorrection; 3549 mDirection = newDirection; 3550 } 3551 } 3552 } 3553 invalidateTopGlow()3554 private void invalidateTopGlow() { 3555 if (mEdgeGlowTop == null) { 3556 return; 3557 } 3558 final boolean clipToPadding = getClipToPadding(); 3559 final int top = clipToPadding ? mPaddingTop : 0; 3560 final int left = clipToPadding ? mPaddingLeft : 0; 3561 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); 3562 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight()); 3563 } 3564 invalidateBottomGlow()3565 private void invalidateBottomGlow() { 3566 if (mEdgeGlowBottom == null) { 3567 return; 3568 } 3569 final boolean clipToPadding = getClipToPadding(); 3570 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight(); 3571 final int left = clipToPadding ? mPaddingLeft : 0; 3572 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); 3573 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom); 3574 } 3575 3576 @Override onTouchModeChanged(boolean isInTouchMode)3577 public void onTouchModeChanged(boolean isInTouchMode) { 3578 if (isInTouchMode) { 3579 // Get rid of the selection when we enter touch mode 3580 hideSelector(); 3581 // Layout, but only if we already have done so previously. 3582 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore 3583 // state.) 3584 if (getHeight() > 0 && getChildCount() > 0) { 3585 // We do not lose focus initiating a touch (since AbsListView is focusable in 3586 // touch mode). Force an initial layout to get rid of the selection. 3587 layoutChildren(); 3588 } 3589 updateSelectorState(); 3590 } else { 3591 int touchMode = mTouchMode; 3592 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { 3593 if (mFlingRunnable != null) { 3594 mFlingRunnable.endFling(); 3595 } 3596 if (mPositionScroller != null) { 3597 mPositionScroller.stop(); 3598 } 3599 3600 if (mScrollY != 0) { 3601 mScrollY = 0; 3602 invalidateParentCaches(); 3603 finishGlows(); 3604 invalidate(); 3605 } 3606 } 3607 } 3608 } 3609 3610 @Override onTouchEvent(MotionEvent ev)3611 public boolean onTouchEvent(MotionEvent ev) { 3612 if (!isEnabled()) { 3613 // A disabled view that is clickable still consumes the touch 3614 // events, it just doesn't respond to them. 3615 return isClickable() || isLongClickable(); 3616 } 3617 3618 if (mPositionScroller != null) { 3619 mPositionScroller.stop(); 3620 } 3621 3622 if (mIsDetaching || !isAttachedToWindow()) { 3623 // Something isn't right. 3624 // Since we rely on being attached to get data set change notifications, 3625 // don't risk doing anything where we might try to resync and find things 3626 // in a bogus state. 3627 return false; 3628 } 3629 3630 startNestedScroll(SCROLL_AXIS_VERTICAL); 3631 3632 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) { 3633 return true; 3634 } 3635 3636 initVelocityTrackerIfNotExists(); 3637 final MotionEvent vtev = MotionEvent.obtain(ev); 3638 3639 final int actionMasked = ev.getActionMasked(); 3640 if (actionMasked == MotionEvent.ACTION_DOWN) { 3641 mNestedYOffset = 0; 3642 } 3643 vtev.offsetLocation(0, mNestedYOffset); 3644 switch (actionMasked) { 3645 case MotionEvent.ACTION_DOWN: { 3646 onTouchDown(ev); 3647 break; 3648 } 3649 3650 case MotionEvent.ACTION_MOVE: { 3651 onTouchMove(ev, vtev); 3652 break; 3653 } 3654 3655 case MotionEvent.ACTION_UP: { 3656 onTouchUp(ev); 3657 break; 3658 } 3659 3660 case MotionEvent.ACTION_CANCEL: { 3661 onTouchCancel(); 3662 break; 3663 } 3664 3665 case MotionEvent.ACTION_POINTER_UP: { 3666 onSecondaryPointerUp(ev); 3667 final int x = mMotionX; 3668 final int y = mMotionY; 3669 final int motionPosition = pointToPosition(x, y); 3670 if (motionPosition >= 0) { 3671 // Remember where the motion event started 3672 final View child = getChildAt(motionPosition - mFirstPosition); 3673 mMotionViewOriginalTop = child.getTop(); 3674 mMotionPosition = motionPosition; 3675 } 3676 mLastY = y; 3677 break; 3678 } 3679 3680 case MotionEvent.ACTION_POINTER_DOWN: { 3681 // New pointers take over dragging duties 3682 final int index = ev.getActionIndex(); 3683 final int id = ev.getPointerId(index); 3684 final int x = (int) ev.getX(index); 3685 final int y = (int) ev.getY(index); 3686 mMotionCorrection = 0; 3687 mActivePointerId = id; 3688 mMotionX = x; 3689 mMotionY = y; 3690 final int motionPosition = pointToPosition(x, y); 3691 if (motionPosition >= 0) { 3692 // Remember where the motion event started 3693 final View child = getChildAt(motionPosition - mFirstPosition); 3694 mMotionViewOriginalTop = child.getTop(); 3695 mMotionPosition = motionPosition; 3696 } 3697 mLastY = y; 3698 break; 3699 } 3700 } 3701 3702 if (mVelocityTracker != null) { 3703 mVelocityTracker.addMovement(vtev); 3704 } 3705 vtev.recycle(); 3706 return true; 3707 } 3708 onTouchDown(MotionEvent ev)3709 private void onTouchDown(MotionEvent ev) { 3710 mActivePointerId = ev.getPointerId(0); 3711 3712 if (mTouchMode == TOUCH_MODE_OVERFLING) { 3713 // Stopped the fling. It is a scroll. 3714 mFlingRunnable.endFling(); 3715 if (mPositionScroller != null) { 3716 mPositionScroller.stop(); 3717 } 3718 mTouchMode = TOUCH_MODE_OVERSCROLL; 3719 mMotionX = (int) ev.getX(); 3720 mMotionY = (int) ev.getY(); 3721 mLastY = mMotionY; 3722 mMotionCorrection = 0; 3723 mDirection = 0; 3724 } else { 3725 final int x = (int) ev.getX(); 3726 final int y = (int) ev.getY(); 3727 int motionPosition = pointToPosition(x, y); 3728 3729 if (!mDataChanged) { 3730 if (mTouchMode == TOUCH_MODE_FLING) { 3731 // Stopped a fling. It is a scroll. 3732 createScrollingCache(); 3733 mTouchMode = TOUCH_MODE_SCROLL; 3734 mMotionCorrection = 0; 3735 motionPosition = findMotionRow(y); 3736 mFlingRunnable.flywheelTouch(); 3737 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) { 3738 // User clicked on an actual view (and was not stopping a 3739 // fling). It might be a click or a scroll. Assume it is a 3740 // click until proven otherwise. 3741 mTouchMode = TOUCH_MODE_DOWN; 3742 3743 // FIXME Debounce 3744 if (mPendingCheckForTap == null) { 3745 mPendingCheckForTap = new CheckForTap(); 3746 } 3747 3748 mPendingCheckForTap.x = ev.getX(); 3749 mPendingCheckForTap.y = ev.getY(); 3750 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 3751 } 3752 } 3753 3754 if (motionPosition >= 0) { 3755 // Remember where the motion event started 3756 final View v = getChildAt(motionPosition - mFirstPosition); 3757 mMotionViewOriginalTop = v.getTop(); 3758 } 3759 3760 mMotionX = x; 3761 mMotionY = y; 3762 mMotionPosition = motionPosition; 3763 mLastY = Integer.MIN_VALUE; 3764 } 3765 3766 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION 3767 && performButtonActionOnTouchDown(ev)) { 3768 removeCallbacks(mPendingCheckForTap); 3769 } 3770 } 3771 onTouchMove(MotionEvent ev, MotionEvent vtev)3772 private void onTouchMove(MotionEvent ev, MotionEvent vtev) { 3773 int pointerIndex = ev.findPointerIndex(mActivePointerId); 3774 if (pointerIndex == -1) { 3775 pointerIndex = 0; 3776 mActivePointerId = ev.getPointerId(pointerIndex); 3777 } 3778 3779 if (mDataChanged) { 3780 // Re-sync everything if data has been changed 3781 // since the scroll operation can query the adapter. 3782 layoutChildren(); 3783 } 3784 3785 final int y = (int) ev.getY(pointerIndex); 3786 3787 switch (mTouchMode) { 3788 case TOUCH_MODE_DOWN: 3789 case TOUCH_MODE_TAP: 3790 case TOUCH_MODE_DONE_WAITING: 3791 // Check if we have moved far enough that it looks more like a 3792 // scroll than a tap. If so, we'll enter scrolling mode. 3793 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) { 3794 break; 3795 } 3796 // Otherwise, check containment within list bounds. If we're 3797 // outside bounds, cancel any active presses. 3798 final View motionView = getChildAt(mMotionPosition - mFirstPosition); 3799 final float x = ev.getX(pointerIndex); 3800 if (!pointInView(x, y, mTouchSlop)) { 3801 setPressed(false); 3802 if (motionView != null) { 3803 motionView.setPressed(false); 3804 } 3805 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? 3806 mPendingCheckForTap : mPendingCheckForLongPress); 3807 mTouchMode = TOUCH_MODE_DONE_WAITING; 3808 updateSelectorState(); 3809 } else if (motionView != null) { 3810 // Still within bounds, update the hotspot. 3811 final float[] point = mTmpPoint; 3812 point[0] = x; 3813 point[1] = y; 3814 transformPointToViewLocal(point, motionView); 3815 motionView.drawableHotspotChanged(point[0], point[1]); 3816 } 3817 break; 3818 case TOUCH_MODE_SCROLL: 3819 case TOUCH_MODE_OVERSCROLL: 3820 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev); 3821 break; 3822 } 3823 } 3824 onTouchUp(MotionEvent ev)3825 private void onTouchUp(MotionEvent ev) { 3826 switch (mTouchMode) { 3827 case TOUCH_MODE_DOWN: 3828 case TOUCH_MODE_TAP: 3829 case TOUCH_MODE_DONE_WAITING: 3830 final int motionPosition = mMotionPosition; 3831 final View child = getChildAt(motionPosition - mFirstPosition); 3832 if (child != null) { 3833 if (mTouchMode != TOUCH_MODE_DOWN) { 3834 child.setPressed(false); 3835 } 3836 3837 final float x = ev.getX(); 3838 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right; 3839 if (inList && !child.hasFocusable()) { 3840 if (mPerformClick == null) { 3841 mPerformClick = new PerformClick(); 3842 } 3843 3844 final AbsListView.PerformClick performClick = mPerformClick; 3845 performClick.mClickMotionPosition = motionPosition; 3846 performClick.rememberWindowAttachCount(); 3847 3848 mResurrectToPosition = motionPosition; 3849 3850 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { 3851 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? 3852 mPendingCheckForTap : mPendingCheckForLongPress); 3853 mLayoutMode = LAYOUT_NORMAL; 3854 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 3855 mTouchMode = TOUCH_MODE_TAP; 3856 setSelectedPositionInt(mMotionPosition); 3857 layoutChildren(); 3858 child.setPressed(true); 3859 positionSelector(mMotionPosition, child); 3860 setPressed(true); 3861 if (mSelector != null) { 3862 Drawable d = mSelector.getCurrent(); 3863 if (d != null && d instanceof TransitionDrawable) { 3864 ((TransitionDrawable) d).resetTransition(); 3865 } 3866 mSelector.setHotspot(x, ev.getY()); 3867 } 3868 if (mTouchModeReset != null) { 3869 removeCallbacks(mTouchModeReset); 3870 } 3871 mTouchModeReset = new Runnable() { 3872 @Override 3873 public void run() { 3874 mTouchModeReset = null; 3875 mTouchMode = TOUCH_MODE_REST; 3876 child.setPressed(false); 3877 setPressed(false); 3878 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) { 3879 performClick.run(); 3880 } 3881 } 3882 }; 3883 postDelayed(mTouchModeReset, 3884 ViewConfiguration.getPressedStateDuration()); 3885 } else { 3886 mTouchMode = TOUCH_MODE_REST; 3887 updateSelectorState(); 3888 } 3889 return; 3890 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 3891 performClick.run(); 3892 } 3893 } 3894 } 3895 mTouchMode = TOUCH_MODE_REST; 3896 updateSelectorState(); 3897 break; 3898 case TOUCH_MODE_SCROLL: 3899 final int childCount = getChildCount(); 3900 if (childCount > 0) { 3901 final int firstChildTop = getChildAt(0).getTop(); 3902 final int lastChildBottom = getChildAt(childCount - 1).getBottom(); 3903 final int contentTop = mListPadding.top; 3904 final int contentBottom = getHeight() - mListPadding.bottom; 3905 if (mFirstPosition == 0 && firstChildTop >= contentTop && 3906 mFirstPosition + childCount < mItemCount && 3907 lastChildBottom <= getHeight() - contentBottom) { 3908 mTouchMode = TOUCH_MODE_REST; 3909 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 3910 } else { 3911 final VelocityTracker velocityTracker = mVelocityTracker; 3912 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 3913 3914 final int initialVelocity = (int) 3915 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale); 3916 // Fling if we have enough velocity and we aren't at a boundary. 3917 // Since we can potentially overfling more than we can overscroll, don't 3918 // allow the weird behavior where you can scroll to a boundary then 3919 // fling further. 3920 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity; 3921 if (flingVelocity && 3922 !((mFirstPosition == 0 && 3923 firstChildTop == contentTop - mOverscrollDistance) || 3924 (mFirstPosition + childCount == mItemCount && 3925 lastChildBottom == contentBottom + mOverscrollDistance))) { 3926 if (!dispatchNestedPreFling(0, -initialVelocity)) { 3927 if (mFlingRunnable == null) { 3928 mFlingRunnable = new FlingRunnable(); 3929 } 3930 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 3931 mFlingRunnable.start(-initialVelocity); 3932 dispatchNestedFling(0, -initialVelocity, true); 3933 } else { 3934 mTouchMode = TOUCH_MODE_REST; 3935 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 3936 } 3937 } else { 3938 mTouchMode = TOUCH_MODE_REST; 3939 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 3940 if (mFlingRunnable != null) { 3941 mFlingRunnable.endFling(); 3942 } 3943 if (mPositionScroller != null) { 3944 mPositionScroller.stop(); 3945 } 3946 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) { 3947 dispatchNestedFling(0, -initialVelocity, false); 3948 } 3949 } 3950 } 3951 } else { 3952 mTouchMode = TOUCH_MODE_REST; 3953 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 3954 } 3955 break; 3956 3957 case TOUCH_MODE_OVERSCROLL: 3958 if (mFlingRunnable == null) { 3959 mFlingRunnable = new FlingRunnable(); 3960 } 3961 final VelocityTracker velocityTracker = mVelocityTracker; 3962 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 3963 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 3964 3965 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 3966 if (Math.abs(initialVelocity) > mMinimumVelocity) { 3967 mFlingRunnable.startOverfling(-initialVelocity); 3968 } else { 3969 mFlingRunnable.startSpringback(); 3970 } 3971 3972 break; 3973 } 3974 3975 setPressed(false); 3976 3977 if (mEdgeGlowTop != null) { 3978 mEdgeGlowTop.onRelease(); 3979 mEdgeGlowBottom.onRelease(); 3980 } 3981 3982 // Need to redraw since we probably aren't drawing the selector anymore 3983 invalidate(); 3984 removeCallbacks(mPendingCheckForLongPress); 3985 recycleVelocityTracker(); 3986 3987 mActivePointerId = INVALID_POINTER; 3988 3989 if (PROFILE_SCROLLING) { 3990 if (mScrollProfilingStarted) { 3991 Debug.stopMethodTracing(); 3992 mScrollProfilingStarted = false; 3993 } 3994 } 3995 3996 if (mScrollStrictSpan != null) { 3997 mScrollStrictSpan.finish(); 3998 mScrollStrictSpan = null; 3999 } 4000 } 4001 onTouchCancel()4002 private void onTouchCancel() { 4003 switch (mTouchMode) { 4004 case TOUCH_MODE_OVERSCROLL: 4005 if (mFlingRunnable == null) { 4006 mFlingRunnable = new FlingRunnable(); 4007 } 4008 mFlingRunnable.startSpringback(); 4009 break; 4010 4011 case TOUCH_MODE_OVERFLING: 4012 // Do nothing - let it play out. 4013 break; 4014 4015 default: 4016 mTouchMode = TOUCH_MODE_REST; 4017 setPressed(false); 4018 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition); 4019 if (motionView != null) { 4020 motionView.setPressed(false); 4021 } 4022 clearScrollingCache(); 4023 removeCallbacks(mPendingCheckForLongPress); 4024 recycleVelocityTracker(); 4025 } 4026 4027 if (mEdgeGlowTop != null) { 4028 mEdgeGlowTop.onRelease(); 4029 mEdgeGlowBottom.onRelease(); 4030 } 4031 mActivePointerId = INVALID_POINTER; 4032 } 4033 4034 @Override onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)4035 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 4036 if (mScrollY != scrollY) { 4037 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY); 4038 mScrollY = scrollY; 4039 invalidateParentIfNeeded(); 4040 4041 awakenScrollBars(); 4042 } 4043 } 4044 4045 @Override onGenericMotionEvent(MotionEvent event)4046 public boolean onGenericMotionEvent(MotionEvent event) { 4047 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 4048 switch (event.getAction()) { 4049 case MotionEvent.ACTION_SCROLL: 4050 if (mTouchMode == TOUCH_MODE_REST) { 4051 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 4052 if (vscroll != 0) { 4053 final int delta = (int) (vscroll * getVerticalScrollFactor()); 4054 if (!trackMotionScroll(delta, delta)) { 4055 return true; 4056 } 4057 } 4058 } 4059 break; 4060 4061 case MotionEvent.ACTION_BUTTON_PRESS: 4062 int actionButton = event.getActionButton(); 4063 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY 4064 || actionButton == MotionEvent.BUTTON_SECONDARY) 4065 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) { 4066 if (performStylusButtonPressAction(event)) { 4067 removeCallbacks(mPendingCheckForLongPress); 4068 removeCallbacks(mPendingCheckForTap); 4069 } 4070 } 4071 break; 4072 } 4073 } 4074 4075 return super.onGenericMotionEvent(event); 4076 } 4077 4078 /** 4079 * Initiate a fling with the given velocity. 4080 * 4081 * <p>Applications can use this method to manually initiate a fling as if the user 4082 * initiated it via touch interaction.</p> 4083 * 4084 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of 4085 * content, not velocity of a touch that initiated the fling. 4086 */ fling(int velocityY)4087 public void fling(int velocityY) { 4088 if (mFlingRunnable == null) { 4089 mFlingRunnable = new FlingRunnable(); 4090 } 4091 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4092 mFlingRunnable.start(velocityY); 4093 } 4094 4095 @Override onStartNestedScroll(View child, View target, int nestedScrollAxes)4096 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 4097 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0); 4098 } 4099 4100 @Override onNestedScrollAccepted(View child, View target, int axes)4101 public void onNestedScrollAccepted(View child, View target, int axes) { 4102 super.onNestedScrollAccepted(child, target, axes); 4103 startNestedScroll(SCROLL_AXIS_VERTICAL); 4104 } 4105 4106 @Override onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)4107 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 4108 int dxUnconsumed, int dyUnconsumed) { 4109 final int motionIndex = getChildCount() / 2; 4110 final View motionView = getChildAt(motionIndex); 4111 final int oldTop = motionView != null ? motionView.getTop() : 0; 4112 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) { 4113 int myUnconsumed = dyUnconsumed; 4114 int myConsumed = 0; 4115 if (motionView != null) { 4116 myConsumed = motionView.getTop() - oldTop; 4117 myUnconsumed -= myConsumed; 4118 } 4119 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); 4120 } 4121 } 4122 4123 @Override onNestedFling(View target, float velocityX, float velocityY, boolean consumed)4124 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 4125 final int childCount = getChildCount(); 4126 if (!consumed && childCount > 0 && canScrollList((int) velocityY) && 4127 Math.abs(velocityY) > mMinimumVelocity) { 4128 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4129 if (mFlingRunnable == null) { 4130 mFlingRunnable = new FlingRunnable(); 4131 } 4132 if (!dispatchNestedPreFling(0, velocityY)) { 4133 mFlingRunnable.start((int) velocityY); 4134 } 4135 return true; 4136 } 4137 return dispatchNestedFling(velocityX, velocityY, consumed); 4138 } 4139 4140 @Override draw(Canvas canvas)4141 public void draw(Canvas canvas) { 4142 super.draw(canvas); 4143 if (mEdgeGlowTop != null) { 4144 final int scrollY = mScrollY; 4145 final boolean clipToPadding = getClipToPadding(); 4146 final int width; 4147 final int height; 4148 final int translateX; 4149 final int translateY; 4150 4151 if (clipToPadding) { 4152 width = getWidth() - mPaddingLeft - mPaddingRight; 4153 height = getHeight() - mPaddingTop - mPaddingBottom; 4154 translateX = mPaddingLeft; 4155 translateY = mPaddingTop; 4156 } else { 4157 width = getWidth(); 4158 height = getHeight(); 4159 translateX = 0; 4160 translateY = 0; 4161 } 4162 if (!mEdgeGlowTop.isFinished()) { 4163 final int restoreCount = canvas.save(); 4164 canvas.clipRect(translateX, translateY, 4165 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight()); 4166 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY; 4167 canvas.translate(translateX, edgeY); 4168 mEdgeGlowTop.setSize(width, height); 4169 if (mEdgeGlowTop.draw(canvas)) { 4170 invalidateTopGlow(); 4171 } 4172 canvas.restoreToCount(restoreCount); 4173 } 4174 if (!mEdgeGlowBottom.isFinished()) { 4175 final int restoreCount = canvas.save(); 4176 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(), 4177 translateX + width, translateY + height); 4178 final int edgeX = -width + translateX; 4179 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess) 4180 - (clipToPadding ? mPaddingBottom : 0); 4181 canvas.translate(edgeX, edgeY); 4182 canvas.rotate(180, width, 0); 4183 mEdgeGlowBottom.setSize(width, height); 4184 if (mEdgeGlowBottom.draw(canvas)) { 4185 invalidateBottomGlow(); 4186 } 4187 canvas.restoreToCount(restoreCount); 4188 } 4189 } 4190 } 4191 initOrResetVelocityTracker()4192 private void initOrResetVelocityTracker() { 4193 if (mVelocityTracker == null) { 4194 mVelocityTracker = VelocityTracker.obtain(); 4195 } else { 4196 mVelocityTracker.clear(); 4197 } 4198 } 4199 initVelocityTrackerIfNotExists()4200 private void initVelocityTrackerIfNotExists() { 4201 if (mVelocityTracker == null) { 4202 mVelocityTracker = VelocityTracker.obtain(); 4203 } 4204 } 4205 recycleVelocityTracker()4206 private void recycleVelocityTracker() { 4207 if (mVelocityTracker != null) { 4208 mVelocityTracker.recycle(); 4209 mVelocityTracker = null; 4210 } 4211 } 4212 4213 @Override requestDisallowInterceptTouchEvent(boolean disallowIntercept)4214 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 4215 if (disallowIntercept) { 4216 recycleVelocityTracker(); 4217 } 4218 super.requestDisallowInterceptTouchEvent(disallowIntercept); 4219 } 4220 4221 @Override onInterceptHoverEvent(MotionEvent event)4222 public boolean onInterceptHoverEvent(MotionEvent event) { 4223 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) { 4224 return true; 4225 } 4226 4227 return super.onInterceptHoverEvent(event); 4228 } 4229 4230 @Override onInterceptTouchEvent(MotionEvent ev)4231 public boolean onInterceptTouchEvent(MotionEvent ev) { 4232 final int actionMasked = ev.getActionMasked(); 4233 View v; 4234 4235 if (mPositionScroller != null) { 4236 mPositionScroller.stop(); 4237 } 4238 4239 if (mIsDetaching || !isAttachedToWindow()) { 4240 // Something isn't right. 4241 // Since we rely on being attached to get data set change notifications, 4242 // don't risk doing anything where we might try to resync and find things 4243 // in a bogus state. 4244 return false; 4245 } 4246 4247 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) { 4248 return true; 4249 } 4250 4251 switch (actionMasked) { 4252 case MotionEvent.ACTION_DOWN: { 4253 int touchMode = mTouchMode; 4254 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) { 4255 mMotionCorrection = 0; 4256 return true; 4257 } 4258 4259 final int x = (int) ev.getX(); 4260 final int y = (int) ev.getY(); 4261 mActivePointerId = ev.getPointerId(0); 4262 4263 int motionPosition = findMotionRow(y); 4264 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) { 4265 // User clicked on an actual view (and was not stopping a fling). 4266 // Remember where the motion event started 4267 v = getChildAt(motionPosition - mFirstPosition); 4268 mMotionViewOriginalTop = v.getTop(); 4269 mMotionX = x; 4270 mMotionY = y; 4271 mMotionPosition = motionPosition; 4272 mTouchMode = TOUCH_MODE_DOWN; 4273 clearScrollingCache(); 4274 } 4275 mLastY = Integer.MIN_VALUE; 4276 initOrResetVelocityTracker(); 4277 mVelocityTracker.addMovement(ev); 4278 mNestedYOffset = 0; 4279 startNestedScroll(SCROLL_AXIS_VERTICAL); 4280 if (touchMode == TOUCH_MODE_FLING) { 4281 return true; 4282 } 4283 break; 4284 } 4285 4286 case MotionEvent.ACTION_MOVE: { 4287 switch (mTouchMode) { 4288 case TOUCH_MODE_DOWN: 4289 int pointerIndex = ev.findPointerIndex(mActivePointerId); 4290 if (pointerIndex == -1) { 4291 pointerIndex = 0; 4292 mActivePointerId = ev.getPointerId(pointerIndex); 4293 } 4294 final int y = (int) ev.getY(pointerIndex); 4295 initVelocityTrackerIfNotExists(); 4296 mVelocityTracker.addMovement(ev); 4297 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) { 4298 return true; 4299 } 4300 break; 4301 } 4302 break; 4303 } 4304 4305 case MotionEvent.ACTION_CANCEL: 4306 case MotionEvent.ACTION_UP: { 4307 mTouchMode = TOUCH_MODE_REST; 4308 mActivePointerId = INVALID_POINTER; 4309 recycleVelocityTracker(); 4310 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4311 stopNestedScroll(); 4312 break; 4313 } 4314 4315 case MotionEvent.ACTION_POINTER_UP: { 4316 onSecondaryPointerUp(ev); 4317 break; 4318 } 4319 } 4320 4321 return false; 4322 } 4323 onSecondaryPointerUp(MotionEvent ev)4324 private void onSecondaryPointerUp(MotionEvent ev) { 4325 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 4326 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 4327 final int pointerId = ev.getPointerId(pointerIndex); 4328 if (pointerId == mActivePointerId) { 4329 // This was our active pointer going up. Choose a new 4330 // active pointer and adjust accordingly. 4331 // TODO: Make this decision more intelligent. 4332 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 4333 mMotionX = (int) ev.getX(newPointerIndex); 4334 mMotionY = (int) ev.getY(newPointerIndex); 4335 mMotionCorrection = 0; 4336 mActivePointerId = ev.getPointerId(newPointerIndex); 4337 } 4338 } 4339 4340 /** 4341 * {@inheritDoc} 4342 */ 4343 @Override addTouchables(ArrayList<View> views)4344 public void addTouchables(ArrayList<View> views) { 4345 final int count = getChildCount(); 4346 final int firstPosition = mFirstPosition; 4347 final ListAdapter adapter = mAdapter; 4348 4349 if (adapter == null) { 4350 return; 4351 } 4352 4353 for (int i = 0; i < count; i++) { 4354 final View child = getChildAt(i); 4355 if (adapter.isEnabled(firstPosition + i)) { 4356 views.add(child); 4357 } 4358 child.addTouchables(views); 4359 } 4360 } 4361 4362 /** 4363 * Fires an "on scroll state changed" event to the registered 4364 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change 4365 * is fired only if the specified state is different from the previously known state. 4366 * 4367 * @param newState The new scroll state. 4368 */ reportScrollStateChange(int newState)4369 void reportScrollStateChange(int newState) { 4370 if (newState != mLastScrollState) { 4371 if (mOnScrollListener != null) { 4372 mLastScrollState = newState; 4373 mOnScrollListener.onScrollStateChanged(this, newState); 4374 } 4375 } 4376 } 4377 4378 /** 4379 * Responsible for fling behavior. Use {@link #start(int)} to 4380 * initiate a fling. Each frame of the fling is handled in {@link #run()}. 4381 * A FlingRunnable will keep re-posting itself until the fling is done. 4382 * 4383 */ 4384 private class FlingRunnable implements Runnable { 4385 /** 4386 * Tracks the decay of a fling scroll 4387 */ 4388 private final OverScroller mScroller; 4389 4390 /** 4391 * Y value reported by mScroller on the previous fling 4392 */ 4393 private int mLastFlingY; 4394 4395 private final Runnable mCheckFlywheel = new Runnable() { 4396 @Override 4397 public void run() { 4398 final int activeId = mActivePointerId; 4399 final VelocityTracker vt = mVelocityTracker; 4400 final OverScroller scroller = mScroller; 4401 if (vt == null || activeId == INVALID_POINTER) { 4402 return; 4403 } 4404 4405 vt.computeCurrentVelocity(1000, mMaximumVelocity); 4406 final float yvel = -vt.getYVelocity(activeId); 4407 4408 if (Math.abs(yvel) >= mMinimumVelocity 4409 && scroller.isScrollingInDirection(0, yvel)) { 4410 // Keep the fling alive a little longer 4411 postDelayed(this, FLYWHEEL_TIMEOUT); 4412 } else { 4413 endFling(); 4414 mTouchMode = TOUCH_MODE_SCROLL; 4415 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 4416 } 4417 } 4418 }; 4419 4420 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds 4421 FlingRunnable()4422 FlingRunnable() { 4423 mScroller = new OverScroller(getContext()); 4424 } 4425 start(int initialVelocity)4426 void start(int initialVelocity) { 4427 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0; 4428 mLastFlingY = initialY; 4429 mScroller.setInterpolator(null); 4430 mScroller.fling(0, initialY, 0, initialVelocity, 4431 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); 4432 mTouchMode = TOUCH_MODE_FLING; 4433 postOnAnimation(this); 4434 4435 if (PROFILE_FLINGING) { 4436 if (!mFlingProfilingStarted) { 4437 Debug.startMethodTracing("AbsListViewFling"); 4438 mFlingProfilingStarted = true; 4439 } 4440 } 4441 4442 if (mFlingStrictSpan == null) { 4443 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling"); 4444 } 4445 } 4446 4447 void startSpringback() { 4448 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) { 4449 mTouchMode = TOUCH_MODE_OVERFLING; 4450 invalidate(); 4451 postOnAnimation(this); 4452 } else { 4453 mTouchMode = TOUCH_MODE_REST; 4454 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4455 } 4456 } 4457 4458 void startOverfling(int initialVelocity) { 4459 mScroller.setInterpolator(null); 4460 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 4461 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight()); 4462 mTouchMode = TOUCH_MODE_OVERFLING; 4463 invalidate(); 4464 postOnAnimation(this); 4465 } 4466 4467 void edgeReached(int delta) { 4468 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance); 4469 final int overscrollMode = getOverScrollMode(); 4470 if (overscrollMode == OVER_SCROLL_ALWAYS || 4471 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { 4472 mTouchMode = TOUCH_MODE_OVERFLING; 4473 final int vel = (int) mScroller.getCurrVelocity(); 4474 if (delta > 0) { 4475 mEdgeGlowTop.onAbsorb(vel); 4476 } else { 4477 mEdgeGlowBottom.onAbsorb(vel); 4478 } 4479 } else { 4480 mTouchMode = TOUCH_MODE_REST; 4481 if (mPositionScroller != null) { 4482 mPositionScroller.stop(); 4483 } 4484 } 4485 invalidate(); 4486 postOnAnimation(this); 4487 } 4488 startScroll(int distance, int duration, boolean linear)4489 void startScroll(int distance, int duration, boolean linear) { 4490 int initialY = distance < 0 ? Integer.MAX_VALUE : 0; 4491 mLastFlingY = initialY; 4492 mScroller.setInterpolator(linear ? sLinearInterpolator : null); 4493 mScroller.startScroll(0, initialY, 0, distance, duration); 4494 mTouchMode = TOUCH_MODE_FLING; 4495 postOnAnimation(this); 4496 } 4497 4498 void endFling() { 4499 mTouchMode = TOUCH_MODE_REST; 4500 4501 removeCallbacks(this); 4502 removeCallbacks(mCheckFlywheel); 4503 4504 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 4505 clearScrollingCache(); 4506 mScroller.abortAnimation(); 4507 4508 if (mFlingStrictSpan != null) { 4509 mFlingStrictSpan.finish(); 4510 mFlingStrictSpan = null; 4511 } 4512 } 4513 4514 void flywheelTouch() { 4515 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT); 4516 } 4517 4518 @Override 4519 public void run() { 4520 switch (mTouchMode) { 4521 default: 4522 endFling(); 4523 return; 4524 4525 case TOUCH_MODE_SCROLL: 4526 if (mScroller.isFinished()) { 4527 return; 4528 } 4529 // Fall through 4530 case TOUCH_MODE_FLING: { 4531 if (mDataChanged) { 4532 layoutChildren(); 4533 } 4534 4535 if (mItemCount == 0 || getChildCount() == 0) { 4536 endFling(); 4537 return; 4538 } 4539 4540 final OverScroller scroller = mScroller; 4541 boolean more = scroller.computeScrollOffset(); 4542 final int y = scroller.getCurrY(); 4543 4544 // Flip sign to convert finger direction to list items direction 4545 // (e.g. finger moving down means list is moving towards the top) 4546 int delta = mLastFlingY - y; 4547 4548 // Pretend that each frame of a fling scroll is a touch scroll 4549 if (delta > 0) { 4550 // List is moving towards the top. Use first view as mMotionPosition 4551 mMotionPosition = mFirstPosition; 4552 final View firstView = getChildAt(0); 4553 mMotionViewOriginalTop = firstView.getTop(); 4554 4555 // Don't fling more than 1 screen 4556 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta); 4557 } else { 4558 // List is moving towards the bottom. Use last view as mMotionPosition 4559 int offsetToLast = getChildCount() - 1; 4560 mMotionPosition = mFirstPosition + offsetToLast; 4561 4562 final View lastView = getChildAt(offsetToLast); 4563 mMotionViewOriginalTop = lastView.getTop(); 4564 4565 // Don't fling more than 1 screen 4566 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); 4567 } 4568 4569 // Check to see if we have bumped into the scroll limit 4570 View motionView = getChildAt(mMotionPosition - mFirstPosition); 4571 int oldTop = 0; 4572 if (motionView != null) { 4573 oldTop = motionView.getTop(); 4574 } 4575 4576 // Don't stop just because delta is zero (it could have been rounded) 4577 final boolean atEdge = trackMotionScroll(delta, delta); 4578 final boolean atEnd = atEdge && (delta != 0); 4579 if (atEnd) { 4580 if (motionView != null) { 4581 // Tweak the scroll for how far we overshot 4582 int overshoot = -(delta - (motionView.getTop() - oldTop)); 4583 overScrollBy(0, overshoot, 0, mScrollY, 0, 0, 4584 0, mOverflingDistance, false); 4585 } 4586 if (more) { 4587 edgeReached(delta); 4588 } 4589 break; 4590 } 4591 4592 if (more && !atEnd) { 4593 if (atEdge) invalidate(); 4594 mLastFlingY = y; 4595 postOnAnimation(this); 4596 } else { 4597 endFling(); 4598 4599 if (PROFILE_FLINGING) { 4600 if (mFlingProfilingStarted) { 4601 Debug.stopMethodTracing(); 4602 mFlingProfilingStarted = false; 4603 } 4604 4605 if (mFlingStrictSpan != null) { 4606 mFlingStrictSpan.finish(); 4607 mFlingStrictSpan = null; 4608 } 4609 } 4610 } 4611 break; 4612 } 4613 4614 case TOUCH_MODE_OVERFLING: { 4615 final OverScroller scroller = mScroller; 4616 if (scroller.computeScrollOffset()) { 4617 final int scrollY = mScrollY; 4618 final int currY = scroller.getCurrY(); 4619 final int deltaY = currY - scrollY; 4620 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0, 4621 0, mOverflingDistance, false)) { 4622 final boolean crossDown = scrollY <= 0 && currY > 0; 4623 final boolean crossUp = scrollY >= 0 && currY < 0; 4624 if (crossDown || crossUp) { 4625 int velocity = (int) scroller.getCurrVelocity(); 4626 if (crossUp) velocity = -velocity; 4627 4628 // Don't flywheel from this; we're just continuing things. 4629 scroller.abortAnimation(); 4630 start(velocity); 4631 } else { 4632 startSpringback(); 4633 } 4634 } else { 4635 invalidate(); 4636 postOnAnimation(this); 4637 } 4638 } else { 4639 endFling(); 4640 } 4641 break; 4642 } 4643 } 4644 } 4645 } 4646 4647 /** 4648 * The amount of friction applied to flings. The default value 4649 * is {@link ViewConfiguration#getScrollFriction}. 4650 */ 4651 public void setFriction(float friction) { 4652 if (mFlingRunnable == null) { 4653 mFlingRunnable = new FlingRunnable(); 4654 } 4655 mFlingRunnable.mScroller.setFriction(friction); 4656 } 4657 4658 /** 4659 * Sets a scale factor for the fling velocity. The initial scale 4660 * factor is 1.0. 4661 * 4662 * @param scale The scale factor to multiply the velocity by. 4663 */ 4664 public void setVelocityScale(float scale) { 4665 mVelocityScale = scale; 4666 } 4667 4668 /** 4669 * Override this for better control over position scrolling. 4670 */ 4671 AbsPositionScroller createPositionScroller() { 4672 return new PositionScroller(); 4673 } 4674 4675 /** 4676 * Smoothly scroll to the specified adapter position. The view will 4677 * scroll such that the indicated position is displayed. 4678 * @param position Scroll to this adapter position. 4679 */ 4680 public void smoothScrollToPosition(int position) { 4681 if (mPositionScroller == null) { 4682 mPositionScroller = createPositionScroller(); 4683 } 4684 mPositionScroller.start(position); 4685 } 4686 4687 /** 4688 * Smoothly scroll to the specified adapter position. The view will scroll 4689 * such that the indicated position is displayed <code>offset</code> pixels below 4690 * the top edge of the view. If this is impossible, (e.g. the offset would scroll 4691 * the first or last item beyond the boundaries of the list) it will get as close 4692 * as possible. The scroll will take <code>duration</code> milliseconds to complete. 4693 * 4694 * @param position Position to scroll to 4695 * @param offset Desired distance in pixels of <code>position</code> from the top 4696 * of the view when scrolling is finished 4697 * @param duration Number of milliseconds to use for the scroll 4698 */ 4699 public void smoothScrollToPositionFromTop(int position, int offset, int duration) { 4700 if (mPositionScroller == null) { 4701 mPositionScroller = createPositionScroller(); 4702 } 4703 mPositionScroller.startWithOffset(position, offset, duration); 4704 } 4705 4706 /** 4707 * Smoothly scroll to the specified adapter position. The view will scroll 4708 * such that the indicated position is displayed <code>offset</code> pixels below 4709 * the top edge of the view. If this is impossible, (e.g. the offset would scroll 4710 * the first or last item beyond the boundaries of the list) it will get as close 4711 * as possible. 4712 * 4713 * @param position Position to scroll to 4714 * @param offset Desired distance in pixels of <code>position</code> from the top 4715 * of the view when scrolling is finished 4716 */ 4717 public void smoothScrollToPositionFromTop(int position, int offset) { 4718 if (mPositionScroller == null) { 4719 mPositionScroller = createPositionScroller(); 4720 } 4721 mPositionScroller.startWithOffset(position, offset); 4722 } 4723 4724 /** 4725 * Smoothly scroll to the specified adapter position. The view will 4726 * scroll such that the indicated position is displayed, but it will 4727 * stop early if scrolling further would scroll boundPosition out of 4728 * view. 4729 * 4730 * @param position Scroll to this adapter position. 4731 * @param boundPosition Do not scroll if it would move this adapter 4732 * position out of view. 4733 */ 4734 public void smoothScrollToPosition(int position, int boundPosition) { 4735 if (mPositionScroller == null) { 4736 mPositionScroller = createPositionScroller(); 4737 } 4738 mPositionScroller.start(position, boundPosition); 4739 } 4740 4741 /** 4742 * Smoothly scroll by distance pixels over duration milliseconds. 4743 * @param distance Distance to scroll in pixels. 4744 * @param duration Duration of the scroll animation in milliseconds. 4745 */ 4746 public void smoothScrollBy(int distance, int duration) { 4747 smoothScrollBy(distance, duration, false); 4748 } 4749 4750 void smoothScrollBy(int distance, int duration, boolean linear) { 4751 if (mFlingRunnable == null) { 4752 mFlingRunnable = new FlingRunnable(); 4753 } 4754 4755 // No sense starting to scroll if we're not going anywhere 4756 final int firstPos = mFirstPosition; 4757 final int childCount = getChildCount(); 4758 final int lastPos = firstPos + childCount; 4759 final int topLimit = getPaddingTop(); 4760 final int bottomLimit = getHeight() - getPaddingBottom(); 4761 4762 if (distance == 0 || mItemCount == 0 || childCount == 0 || 4763 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) || 4764 (lastPos == mItemCount && 4765 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) { 4766 mFlingRunnable.endFling(); 4767 if (mPositionScroller != null) { 4768 mPositionScroller.stop(); 4769 } 4770 } else { 4771 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 4772 mFlingRunnable.startScroll(distance, duration, linear); 4773 } 4774 } 4775 4776 /** 4777 * Allows RemoteViews to scroll relatively to a position. 4778 */ 4779 void smoothScrollByOffset(int position) { 4780 int index = -1; 4781 if (position < 0) { 4782 index = getFirstVisiblePosition(); 4783 } else if (position > 0) { 4784 index = getLastVisiblePosition(); 4785 } 4786 4787 if (index > -1) { 4788 View child = getChildAt(index - getFirstVisiblePosition()); 4789 if (child != null) { 4790 Rect visibleRect = new Rect(); 4791 if (child.getGlobalVisibleRect(visibleRect)) { 4792 // the child is partially visible 4793 int childRectArea = child.getWidth() * child.getHeight(); 4794 int visibleRectArea = visibleRect.width() * visibleRect.height(); 4795 float visibleArea = (visibleRectArea / (float) childRectArea); 4796 final float visibleThreshold = 0.75f; 4797 if ((position < 0) && (visibleArea < visibleThreshold)) { 4798 // the top index is not perceivably visible so offset 4799 // to account for showing that top index as well 4800 ++index; 4801 } else if ((position > 0) && (visibleArea < visibleThreshold)) { 4802 // the bottom index is not perceivably visible so offset 4803 // to account for showing that bottom index as well 4804 --index; 4805 } 4806 } 4807 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position))); 4808 } 4809 } 4810 } 4811 4812 private void createScrollingCache() { 4813 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) { 4814 setChildrenDrawnWithCacheEnabled(true); 4815 setChildrenDrawingCacheEnabled(true); 4816 mCachingStarted = mCachingActive = true; 4817 } 4818 } 4819 4820 private void clearScrollingCache() { 4821 if (!isHardwareAccelerated()) { 4822 if (mClearScrollingCache == null) { 4823 mClearScrollingCache = new Runnable() { 4824 @Override 4825 public void run() { 4826 if (mCachingStarted) { 4827 mCachingStarted = mCachingActive = false; 4828 setChildrenDrawnWithCacheEnabled(false); 4829 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { 4830 setChildrenDrawingCacheEnabled(false); 4831 } 4832 if (!isAlwaysDrawnWithCacheEnabled()) { 4833 invalidate(); 4834 } 4835 } 4836 } 4837 }; 4838 } 4839 post(mClearScrollingCache); 4840 } 4841 } 4842 4843 /** 4844 * Scrolls the list items within the view by a specified number of pixels. 4845 * 4846 * @param y the amount of pixels to scroll by vertically 4847 * @see #canScrollList(int) 4848 */ 4849 public void scrollListBy(int y) { 4850 trackMotionScroll(-y, -y); 4851 } 4852 4853 /** 4854 * Check if the items in the list can be scrolled in a certain direction. 4855 * 4856 * @param direction Negative to check scrolling up, positive to check 4857 * scrolling down. 4858 * @return true if the list can be scrolled in the specified direction, 4859 * false otherwise. 4860 * @see #scrollListBy(int) 4861 */ 4862 public boolean canScrollList(int direction) { 4863 final int childCount = getChildCount(); 4864 if (childCount == 0) { 4865 return false; 4866 } 4867 4868 final int firstPosition = mFirstPosition; 4869 final Rect listPadding = mListPadding; 4870 if (direction > 0) { 4871 final int lastBottom = getChildAt(childCount - 1).getBottom(); 4872 final int lastPosition = firstPosition + childCount; 4873 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom; 4874 } else { 4875 final int firstTop = getChildAt(0).getTop(); 4876 return firstPosition > 0 || firstTop < listPadding.top; 4877 } 4878 } 4879 4880 /** 4881 * Track a motion scroll 4882 * 4883 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion 4884 * began. Positive numbers mean the user's finger is moving down the screen. 4885 * @param incrementalDeltaY Change in deltaY from the previous event. 4886 * @return true if we're already at the beginning/end of the list and have nothing to do. 4887 */ 4888 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { 4889 final int childCount = getChildCount(); 4890 if (childCount == 0) { 4891 return true; 4892 } 4893 4894 final int firstTop = getChildAt(0).getTop(); 4895 final int lastBottom = getChildAt(childCount - 1).getBottom(); 4896 4897 final Rect listPadding = mListPadding; 4898 4899 // "effective padding" In this case is the amount of padding that affects 4900 // how much space should not be filled by items. If we don't clip to padding 4901 // there is no effective padding. 4902 int effectivePaddingTop = 0; 4903 int effectivePaddingBottom = 0; 4904 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 4905 effectivePaddingTop = listPadding.top; 4906 effectivePaddingBottom = listPadding.bottom; 4907 } 4908 4909 // FIXME account for grid vertical spacing too? 4910 final int spaceAbove = effectivePaddingTop - firstTop; 4911 final int end = getHeight() - effectivePaddingBottom; 4912 final int spaceBelow = lastBottom - end; 4913 4914 final int height = getHeight() - mPaddingBottom - mPaddingTop; 4915 if (deltaY < 0) { 4916 deltaY = Math.max(-(height - 1), deltaY); 4917 } else { 4918 deltaY = Math.min(height - 1, deltaY); 4919 } 4920 4921 if (incrementalDeltaY < 0) { 4922 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); 4923 } else { 4924 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); 4925 } 4926 4927 final int firstPosition = mFirstPosition; 4928 4929 // Update our guesses for where the first and last views are 4930 if (firstPosition == 0) { 4931 mFirstPositionDistanceGuess = firstTop - listPadding.top; 4932 } else { 4933 mFirstPositionDistanceGuess += incrementalDeltaY; 4934 } 4935 if (firstPosition + childCount == mItemCount) { 4936 mLastPositionDistanceGuess = lastBottom + listPadding.bottom; 4937 } else { 4938 mLastPositionDistanceGuess += incrementalDeltaY; 4939 } 4940 4941 final boolean cannotScrollDown = (firstPosition == 0 && 4942 firstTop >= listPadding.top && incrementalDeltaY >= 0); 4943 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && 4944 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); 4945 4946 if (cannotScrollDown || cannotScrollUp) { 4947 return incrementalDeltaY != 0; 4948 } 4949 4950 final boolean down = incrementalDeltaY < 0; 4951 4952 final boolean inTouchMode = isInTouchMode(); 4953 if (inTouchMode) { 4954 hideSelector(); 4955 } 4956 4957 final int headerViewsCount = getHeaderViewsCount(); 4958 final int footerViewsStart = mItemCount - getFooterViewsCount(); 4959 4960 int start = 0; 4961 int count = 0; 4962 4963 if (down) { 4964 int top = -incrementalDeltaY; 4965 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 4966 top += listPadding.top; 4967 } 4968 for (int i = 0; i < childCount; i++) { 4969 final View child = getChildAt(i); 4970 if (child.getBottom() >= top) { 4971 break; 4972 } else { 4973 count++; 4974 int position = firstPosition + i; 4975 if (position >= headerViewsCount && position < footerViewsStart) { 4976 // The view will be rebound to new data, clear any 4977 // system-managed transient state. 4978 child.clearAccessibilityFocus(); 4979 mRecycler.addScrapView(child, position); 4980 } 4981 } 4982 } 4983 } else { 4984 int bottom = getHeight() - incrementalDeltaY; 4985 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 4986 bottom -= listPadding.bottom; 4987 } 4988 for (int i = childCount - 1; i >= 0; i--) { 4989 final View child = getChildAt(i); 4990 if (child.getTop() <= bottom) { 4991 break; 4992 } else { 4993 start = i; 4994 count++; 4995 int position = firstPosition + i; 4996 if (position >= headerViewsCount && position < footerViewsStart) { 4997 // The view will be rebound to new data, clear any 4998 // system-managed transient state. 4999 child.clearAccessibilityFocus(); 5000 mRecycler.addScrapView(child, position); 5001 } 5002 } 5003 } 5004 } 5005 5006 mMotionViewNewTop = mMotionViewOriginalTop + deltaY; 5007 5008 mBlockLayoutRequests = true; 5009 5010 if (count > 0) { 5011 detachViewsFromParent(start, count); 5012 mRecycler.removeSkippedScrap(); 5013 } 5014 5015 // invalidate before moving the children to avoid unnecessary invalidate 5016 // calls to bubble up from the children all the way to the top 5017 if (!awakenScrollBars()) { 5018 invalidate(); 5019 } 5020 5021 offsetChildrenTopAndBottom(incrementalDeltaY); 5022 5023 if (down) { 5024 mFirstPosition += count; 5025 } 5026 5027 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); 5028 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { 5029 fillGap(down); 5030 } 5031 5032 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { 5033 final int childIndex = mSelectedPosition - mFirstPosition; 5034 if (childIndex >= 0 && childIndex < getChildCount()) { 5035 positionSelector(mSelectedPosition, getChildAt(childIndex)); 5036 } 5037 } else if (mSelectorPosition != INVALID_POSITION) { 5038 final int childIndex = mSelectorPosition - mFirstPosition; 5039 if (childIndex >= 0 && childIndex < getChildCount()) { 5040 positionSelector(INVALID_POSITION, getChildAt(childIndex)); 5041 } 5042 } else { 5043 mSelectorRect.setEmpty(); 5044 } 5045 5046 mBlockLayoutRequests = false; 5047 5048 invokeOnItemScrollListener(); 5049 5050 return false; 5051 } 5052 5053 /** 5054 * Returns the number of header views in the list. Header views are special views 5055 * at the top of the list that should not be recycled during a layout. 5056 * 5057 * @return The number of header views, 0 in the default implementation. 5058 */ 5059 int getHeaderViewsCount() { 5060 return 0; 5061 } 5062 5063 /** 5064 * Returns the number of footer views in the list. Footer views are special views 5065 * at the bottom of the list that should not be recycled during a layout. 5066 * 5067 * @return The number of footer views, 0 in the default implementation. 5068 */ 5069 int getFooterViewsCount() { 5070 return 0; 5071 } 5072 5073 /** 5074 * Fills the gap left open by a touch-scroll. During a touch scroll, children that 5075 * remain on screen are shifted and the other ones are discarded. The role of this 5076 * method is to fill the gap thus created by performing a partial layout in the 5077 * empty space. 5078 * 5079 * @param down true if the scroll is going down, false if it is going up 5080 */ 5081 abstract void fillGap(boolean down); 5082 hideSelector()5083 void hideSelector() { 5084 if (mSelectedPosition != INVALID_POSITION) { 5085 if (mLayoutMode != LAYOUT_SPECIFIC) { 5086 mResurrectToPosition = mSelectedPosition; 5087 } 5088 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) { 5089 mResurrectToPosition = mNextSelectedPosition; 5090 } 5091 setSelectedPositionInt(INVALID_POSITION); 5092 setNextSelectedPositionInt(INVALID_POSITION); 5093 mSelectedTop = 0; 5094 } 5095 } 5096 5097 /** 5098 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by 5099 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range 5100 * of items available in the adapter 5101 */ reconcileSelectedPosition()5102 int reconcileSelectedPosition() { 5103 int position = mSelectedPosition; 5104 if (position < 0) { 5105 position = mResurrectToPosition; 5106 } 5107 position = Math.max(0, position); 5108 position = Math.min(position, mItemCount - 1); 5109 return position; 5110 } 5111 5112 /** 5113 * Find the row closest to y. This row will be used as the motion row when scrolling 5114 * 5115 * @param y Where the user touched 5116 * @return The position of the first (or only) item in the row containing y 5117 */ 5118 abstract int findMotionRow(int y); 5119 5120 /** 5121 * Find the row closest to y. This row will be used as the motion row when scrolling. 5122 * 5123 * @param y Where the user touched 5124 * @return The position of the first (or only) item in the row closest to y 5125 */ findClosestMotionRow(int y)5126 int findClosestMotionRow(int y) { 5127 final int childCount = getChildCount(); 5128 if (childCount == 0) { 5129 return INVALID_POSITION; 5130 } 5131 5132 final int motionRow = findMotionRow(y); 5133 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1; 5134 } 5135 5136 /** 5137 * Causes all the views to be rebuilt and redrawn. 5138 */ invalidateViews()5139 public void invalidateViews() { 5140 mDataChanged = true; 5141 rememberSyncState(); 5142 requestLayout(); 5143 invalidate(); 5144 } 5145 5146 /** 5147 * If there is a selection returns false. 5148 * Otherwise resurrects the selection and returns true if resurrected. 5149 */ resurrectSelectionIfNeeded()5150 boolean resurrectSelectionIfNeeded() { 5151 if (mSelectedPosition < 0 && resurrectSelection()) { 5152 updateSelectorState(); 5153 return true; 5154 } 5155 return false; 5156 } 5157 5158 /** 5159 * Makes the item at the supplied position selected. 5160 * 5161 * @param position the position of the new selection 5162 */ 5163 abstract void setSelectionInt(int position); 5164 5165 /** 5166 * Attempt to bring the selection back if the user is switching from touch 5167 * to trackball mode 5168 * @return Whether selection was set to something. 5169 */ resurrectSelection()5170 boolean resurrectSelection() { 5171 final int childCount = getChildCount(); 5172 5173 if (childCount <= 0) { 5174 return false; 5175 } 5176 5177 int selectedTop = 0; 5178 int selectedPos; 5179 int childrenTop = mListPadding.top; 5180 int childrenBottom = mBottom - mTop - mListPadding.bottom; 5181 final int firstPosition = mFirstPosition; 5182 final int toPosition = mResurrectToPosition; 5183 boolean down = true; 5184 5185 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) { 5186 selectedPos = toPosition; 5187 5188 final View selected = getChildAt(selectedPos - mFirstPosition); 5189 selectedTop = selected.getTop(); 5190 int selectedBottom = selected.getBottom(); 5191 5192 // We are scrolled, don't get in the fade 5193 if (selectedTop < childrenTop) { 5194 selectedTop = childrenTop + getVerticalFadingEdgeLength(); 5195 } else if (selectedBottom > childrenBottom) { 5196 selectedTop = childrenBottom - selected.getMeasuredHeight() 5197 - getVerticalFadingEdgeLength(); 5198 } 5199 } else { 5200 if (toPosition < firstPosition) { 5201 // Default to selecting whatever is first 5202 selectedPos = firstPosition; 5203 for (int i = 0; i < childCount; i++) { 5204 final View v = getChildAt(i); 5205 final int top = v.getTop(); 5206 5207 if (i == 0) { 5208 // Remember the position of the first item 5209 selectedTop = top; 5210 // See if we are scrolled at all 5211 if (firstPosition > 0 || top < childrenTop) { 5212 // If we are scrolled, don't select anything that is 5213 // in the fade region 5214 childrenTop += getVerticalFadingEdgeLength(); 5215 } 5216 } 5217 if (top >= childrenTop) { 5218 // Found a view whose top is fully visisble 5219 selectedPos = firstPosition + i; 5220 selectedTop = top; 5221 break; 5222 } 5223 } 5224 } else { 5225 final int itemCount = mItemCount; 5226 down = false; 5227 selectedPos = firstPosition + childCount - 1; 5228 5229 for (int i = childCount - 1; i >= 0; i--) { 5230 final View v = getChildAt(i); 5231 final int top = v.getTop(); 5232 final int bottom = v.getBottom(); 5233 5234 if (i == childCount - 1) { 5235 selectedTop = top; 5236 if (firstPosition + childCount < itemCount || bottom > childrenBottom) { 5237 childrenBottom -= getVerticalFadingEdgeLength(); 5238 } 5239 } 5240 5241 if (bottom <= childrenBottom) { 5242 selectedPos = firstPosition + i; 5243 selectedTop = top; 5244 break; 5245 } 5246 } 5247 } 5248 } 5249 5250 mResurrectToPosition = INVALID_POSITION; 5251 removeCallbacks(mFlingRunnable); 5252 if (mPositionScroller != null) { 5253 mPositionScroller.stop(); 5254 } 5255 mTouchMode = TOUCH_MODE_REST; 5256 clearScrollingCache(); 5257 mSpecificTop = selectedTop; 5258 selectedPos = lookForSelectablePosition(selectedPos, down); 5259 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) { 5260 mLayoutMode = LAYOUT_SPECIFIC; 5261 updateSelectorState(); 5262 setSelectionInt(selectedPos); 5263 invokeOnItemScrollListener(); 5264 } else { 5265 selectedPos = INVALID_POSITION; 5266 } 5267 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 5268 5269 return selectedPos >= 0; 5270 } 5271 confirmCheckedPositionsById()5272 void confirmCheckedPositionsById() { 5273 // Clear out the positional check states, we'll rebuild it below from IDs. 5274 mCheckStates.clear(); 5275 5276 boolean checkedCountChanged = false; 5277 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) { 5278 final long id = mCheckedIdStates.keyAt(checkedIndex); 5279 final int lastPos = mCheckedIdStates.valueAt(checkedIndex); 5280 5281 final long lastPosId = mAdapter.getItemId(lastPos); 5282 if (id != lastPosId) { 5283 // Look around to see if the ID is nearby. If not, uncheck it. 5284 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE); 5285 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount); 5286 boolean found = false; 5287 for (int searchPos = start; searchPos < end; searchPos++) { 5288 final long searchId = mAdapter.getItemId(searchPos); 5289 if (id == searchId) { 5290 found = true; 5291 mCheckStates.put(searchPos, true); 5292 mCheckedIdStates.setValueAt(checkedIndex, searchPos); 5293 break; 5294 } 5295 } 5296 5297 if (!found) { 5298 mCheckedIdStates.delete(id); 5299 checkedIndex--; 5300 mCheckedItemCount--; 5301 checkedCountChanged = true; 5302 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) { 5303 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, 5304 lastPos, id, false); 5305 } 5306 } 5307 } else { 5308 mCheckStates.put(lastPos, true); 5309 } 5310 } 5311 5312 if (checkedCountChanged && mChoiceActionMode != null) { 5313 mChoiceActionMode.invalidate(); 5314 } 5315 } 5316 5317 @Override handleDataChanged()5318 protected void handleDataChanged() { 5319 int count = mItemCount; 5320 int lastHandledItemCount = mLastHandledItemCount; 5321 mLastHandledItemCount = mItemCount; 5322 5323 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) { 5324 confirmCheckedPositionsById(); 5325 } 5326 5327 // TODO: In the future we can recycle these views based on stable ID instead. 5328 mRecycler.clearTransientStateViews(); 5329 5330 if (count > 0) { 5331 int newPos; 5332 int selectablePos; 5333 5334 // Find the row we are supposed to sync to 5335 if (mNeedSync) { 5336 // Update this first, since setNextSelectedPositionInt inspects it 5337 mNeedSync = false; 5338 mPendingSync = null; 5339 5340 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) { 5341 mLayoutMode = LAYOUT_FORCE_BOTTOM; 5342 return; 5343 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) { 5344 if (mForceTranscriptScroll) { 5345 mForceTranscriptScroll = false; 5346 mLayoutMode = LAYOUT_FORCE_BOTTOM; 5347 return; 5348 } 5349 final int childCount = getChildCount(); 5350 final int listBottom = getHeight() - getPaddingBottom(); 5351 final View lastChild = getChildAt(childCount - 1); 5352 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom; 5353 if (mFirstPosition + childCount >= lastHandledItemCount && 5354 lastBottom <= listBottom) { 5355 mLayoutMode = LAYOUT_FORCE_BOTTOM; 5356 return; 5357 } 5358 // Something new came in and we didn't scroll; give the user a clue that 5359 // there's something new. 5360 awakenScrollBars(); 5361 } 5362 5363 switch (mSyncMode) { 5364 case SYNC_SELECTED_POSITION: 5365 if (isInTouchMode()) { 5366 // We saved our state when not in touch mode. (We know this because 5367 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to 5368 // restore in touch mode. Just leave mSyncPosition as it is (possibly 5369 // adjusting if the available range changed) and return. 5370 mLayoutMode = LAYOUT_SYNC; 5371 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); 5372 5373 return; 5374 } else { 5375 // See if we can find a position in the new data with the same 5376 // id as the old selection. This will change mSyncPosition. 5377 newPos = findSyncPosition(); 5378 if (newPos >= 0) { 5379 // Found it. Now verify that new selection is still selectable 5380 selectablePos = lookForSelectablePosition(newPos, true); 5381 if (selectablePos == newPos) { 5382 // Same row id is selected 5383 mSyncPosition = newPos; 5384 5385 if (mSyncHeight == getHeight()) { 5386 // If we are at the same height as when we saved state, try 5387 // to restore the scroll position too. 5388 mLayoutMode = LAYOUT_SYNC; 5389 } else { 5390 // We are not the same height as when the selection was saved, so 5391 // don't try to restore the exact position 5392 mLayoutMode = LAYOUT_SET_SELECTION; 5393 } 5394 5395 // Restore selection 5396 setNextSelectedPositionInt(newPos); 5397 return; 5398 } 5399 } 5400 } 5401 break; 5402 case SYNC_FIRST_POSITION: 5403 // Leave mSyncPosition as it is -- just pin to available range 5404 mLayoutMode = LAYOUT_SYNC; 5405 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); 5406 5407 return; 5408 } 5409 } 5410 5411 if (!isInTouchMode()) { 5412 // We couldn't find matching data -- try to use the same position 5413 newPos = getSelectedItemPosition(); 5414 5415 // Pin position to the available range 5416 if (newPos >= count) { 5417 newPos = count - 1; 5418 } 5419 if (newPos < 0) { 5420 newPos = 0; 5421 } 5422 5423 // Make sure we select something selectable -- first look down 5424 selectablePos = lookForSelectablePosition(newPos, true); 5425 5426 if (selectablePos >= 0) { 5427 setNextSelectedPositionInt(selectablePos); 5428 return; 5429 } else { 5430 // Looking down didn't work -- try looking up 5431 selectablePos = lookForSelectablePosition(newPos, false); 5432 if (selectablePos >= 0) { 5433 setNextSelectedPositionInt(selectablePos); 5434 return; 5435 } 5436 } 5437 } else { 5438 5439 // We already know where we want to resurrect the selection 5440 if (mResurrectToPosition >= 0) { 5441 return; 5442 } 5443 } 5444 5445 } 5446 5447 // Nothing is selected. Give up and reset everything. 5448 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP; 5449 mSelectedPosition = INVALID_POSITION; 5450 mSelectedRowId = INVALID_ROW_ID; 5451 mNextSelectedPosition = INVALID_POSITION; 5452 mNextSelectedRowId = INVALID_ROW_ID; 5453 mNeedSync = false; 5454 mPendingSync = null; 5455 mSelectorPosition = INVALID_POSITION; 5456 checkSelectionChanged(); 5457 } 5458 5459 @Override onDisplayHint(int hint)5460 protected void onDisplayHint(int hint) { 5461 super.onDisplayHint(hint); 5462 switch (hint) { 5463 case INVISIBLE: 5464 if (mPopup != null && mPopup.isShowing()) { 5465 dismissPopup(); 5466 } 5467 break; 5468 case VISIBLE: 5469 if (mFiltered && mPopup != null && !mPopup.isShowing()) { 5470 showPopup(); 5471 } 5472 break; 5473 } 5474 mPopupHidden = hint == INVISIBLE; 5475 } 5476 5477 /** 5478 * Removes the filter window 5479 */ dismissPopup()5480 private void dismissPopup() { 5481 if (mPopup != null) { 5482 mPopup.dismiss(); 5483 } 5484 } 5485 5486 /** 5487 * Shows the filter window 5488 */ showPopup()5489 private void showPopup() { 5490 // Make sure we have a window before showing the popup 5491 if (getWindowVisibility() == View.VISIBLE) { 5492 createTextFilter(true); 5493 positionPopup(); 5494 // Make sure we get focus if we are showing the popup 5495 checkFocus(); 5496 } 5497 } 5498 positionPopup()5499 private void positionPopup() { 5500 int screenHeight = getResources().getDisplayMetrics().heightPixels; 5501 final int[] xy = new int[2]; 5502 getLocationOnScreen(xy); 5503 // TODO: The 20 below should come from the theme 5504 // TODO: And the gravity should be defined in the theme as well 5505 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20); 5506 if (!mPopup.isShowing()) { 5507 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 5508 xy[0], bottomGap); 5509 } else { 5510 mPopup.update(xy[0], bottomGap, -1, -1); 5511 } 5512 } 5513 5514 /** 5515 * What is the distance between the source and destination rectangles given the direction of 5516 * focus navigation between them? The direction basically helps figure out more quickly what is 5517 * self evident by the relationship between the rects... 5518 * 5519 * @param source the source rectangle 5520 * @param dest the destination rectangle 5521 * @param direction the direction 5522 * @return the distance between the rectangles 5523 */ getDistance(Rect source, Rect dest, int direction)5524 static int getDistance(Rect source, Rect dest, int direction) { 5525 int sX, sY; // source x, y 5526 int dX, dY; // dest x, y 5527 switch (direction) { 5528 case View.FOCUS_RIGHT: 5529 sX = source.right; 5530 sY = source.top + source.height() / 2; 5531 dX = dest.left; 5532 dY = dest.top + dest.height() / 2; 5533 break; 5534 case View.FOCUS_DOWN: 5535 sX = source.left + source.width() / 2; 5536 sY = source.bottom; 5537 dX = dest.left + dest.width() / 2; 5538 dY = dest.top; 5539 break; 5540 case View.FOCUS_LEFT: 5541 sX = source.left; 5542 sY = source.top + source.height() / 2; 5543 dX = dest.right; 5544 dY = dest.top + dest.height() / 2; 5545 break; 5546 case View.FOCUS_UP: 5547 sX = source.left + source.width() / 2; 5548 sY = source.top; 5549 dX = dest.left + dest.width() / 2; 5550 dY = dest.bottom; 5551 break; 5552 case View.FOCUS_FORWARD: 5553 case View.FOCUS_BACKWARD: 5554 sX = source.right + source.width() / 2; 5555 sY = source.top + source.height() / 2; 5556 dX = dest.left + dest.width() / 2; 5557 dY = dest.top + dest.height() / 2; 5558 break; 5559 default: 5560 throw new IllegalArgumentException("direction must be one of " 5561 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, " 5562 + "FOCUS_FORWARD, FOCUS_BACKWARD}."); 5563 } 5564 int deltaX = dX - sX; 5565 int deltaY = dY - sY; 5566 return deltaY * deltaY + deltaX * deltaX; 5567 } 5568 5569 @Override isInFilterMode()5570 protected boolean isInFilterMode() { 5571 return mFiltered; 5572 } 5573 5574 /** 5575 * Sends a key to the text filter window 5576 * 5577 * @param keyCode The keycode for the event 5578 * @param event The actual key event 5579 * 5580 * @return True if the text filter handled the event, false otherwise. 5581 */ sendToTextFilter(int keyCode, int count, KeyEvent event)5582 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) { 5583 if (!acceptFilter()) { 5584 return false; 5585 } 5586 5587 boolean handled = false; 5588 boolean okToSend = true; 5589 switch (keyCode) { 5590 case KeyEvent.KEYCODE_DPAD_UP: 5591 case KeyEvent.KEYCODE_DPAD_DOWN: 5592 case KeyEvent.KEYCODE_DPAD_LEFT: 5593 case KeyEvent.KEYCODE_DPAD_RIGHT: 5594 case KeyEvent.KEYCODE_DPAD_CENTER: 5595 case KeyEvent.KEYCODE_ENTER: 5596 okToSend = false; 5597 break; 5598 case KeyEvent.KEYCODE_BACK: 5599 if (mFiltered && mPopup != null && mPopup.isShowing()) { 5600 if (event.getAction() == KeyEvent.ACTION_DOWN 5601 && event.getRepeatCount() == 0) { 5602 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5603 if (state != null) { 5604 state.startTracking(event, this); 5605 } 5606 handled = true; 5607 } else if (event.getAction() == KeyEvent.ACTION_UP 5608 && event.isTracking() && !event.isCanceled()) { 5609 handled = true; 5610 mTextFilter.setText(""); 5611 } 5612 } 5613 okToSend = false; 5614 break; 5615 case KeyEvent.KEYCODE_SPACE: 5616 // Only send spaces once we are filtered 5617 okToSend = mFiltered; 5618 break; 5619 } 5620 5621 if (okToSend) { 5622 createTextFilter(true); 5623 5624 KeyEvent forwardEvent = event; 5625 if (forwardEvent.getRepeatCount() > 0) { 5626 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0); 5627 } 5628 5629 int action = event.getAction(); 5630 switch (action) { 5631 case KeyEvent.ACTION_DOWN: 5632 handled = mTextFilter.onKeyDown(keyCode, forwardEvent); 5633 break; 5634 5635 case KeyEvent.ACTION_UP: 5636 handled = mTextFilter.onKeyUp(keyCode, forwardEvent); 5637 break; 5638 5639 case KeyEvent.ACTION_MULTIPLE: 5640 handled = mTextFilter.onKeyMultiple(keyCode, count, event); 5641 break; 5642 } 5643 } 5644 return handled; 5645 } 5646 5647 /** 5648 * Return an InputConnection for editing of the filter text. 5649 */ 5650 @Override onCreateInputConnection(EditorInfo outAttrs)5651 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 5652 if (isTextFilterEnabled()) { 5653 if (mPublicInputConnection == null) { 5654 mDefInputConnection = new BaseInputConnection(this, false); 5655 mPublicInputConnection = new InputConnectionWrapper(outAttrs); 5656 } 5657 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER; 5658 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; 5659 return mPublicInputConnection; 5660 } 5661 return null; 5662 } 5663 5664 private class InputConnectionWrapper implements InputConnection { 5665 private final EditorInfo mOutAttrs; 5666 private InputConnection mTarget; 5667 InputConnectionWrapper(EditorInfo outAttrs)5668 public InputConnectionWrapper(EditorInfo outAttrs) { 5669 mOutAttrs = outAttrs; 5670 } 5671 getTarget()5672 private InputConnection getTarget() { 5673 if (mTarget == null) { 5674 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs); 5675 } 5676 return mTarget; 5677 } 5678 5679 @Override reportFullscreenMode(boolean enabled)5680 public boolean reportFullscreenMode(boolean enabled) { 5681 // Use our own input connection, since it is 5682 // the "real" one the IME is talking with. 5683 return mDefInputConnection.reportFullscreenMode(enabled); 5684 } 5685 5686 @Override performEditorAction(int editorAction)5687 public boolean performEditorAction(int editorAction) { 5688 // The editor is off in its own window; we need to be 5689 // the one that does this. 5690 if (editorAction == EditorInfo.IME_ACTION_DONE) { 5691 InputMethodManager imm = (InputMethodManager) 5692 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 5693 if (imm != null) { 5694 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5695 } 5696 return true; 5697 } 5698 return false; 5699 } 5700 5701 @Override sendKeyEvent(KeyEvent event)5702 public boolean sendKeyEvent(KeyEvent event) { 5703 // Use our own input connection, since the filter 5704 // text view may not be shown in a window so has 5705 // no ViewAncestor to dispatch events with. 5706 return mDefInputConnection.sendKeyEvent(event); 5707 } 5708 5709 @Override getTextBeforeCursor(int n, int flags)5710 public CharSequence getTextBeforeCursor(int n, int flags) { 5711 if (mTarget == null) return ""; 5712 return mTarget.getTextBeforeCursor(n, flags); 5713 } 5714 5715 @Override getTextAfterCursor(int n, int flags)5716 public CharSequence getTextAfterCursor(int n, int flags) { 5717 if (mTarget == null) return ""; 5718 return mTarget.getTextAfterCursor(n, flags); 5719 } 5720 5721 @Override getSelectedText(int flags)5722 public CharSequence getSelectedText(int flags) { 5723 if (mTarget == null) return ""; 5724 return mTarget.getSelectedText(flags); 5725 } 5726 5727 @Override getCursorCapsMode(int reqModes)5728 public int getCursorCapsMode(int reqModes) { 5729 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; 5730 return mTarget.getCursorCapsMode(reqModes); 5731 } 5732 5733 @Override getExtractedText(ExtractedTextRequest request, int flags)5734 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 5735 return getTarget().getExtractedText(request, flags); 5736 } 5737 5738 @Override deleteSurroundingText(int beforeLength, int afterLength)5739 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 5740 return getTarget().deleteSurroundingText(beforeLength, afterLength); 5741 } 5742 5743 @Override setComposingText(CharSequence text, int newCursorPosition)5744 public boolean setComposingText(CharSequence text, int newCursorPosition) { 5745 return getTarget().setComposingText(text, newCursorPosition); 5746 } 5747 5748 @Override setComposingRegion(int start, int end)5749 public boolean setComposingRegion(int start, int end) { 5750 return getTarget().setComposingRegion(start, end); 5751 } 5752 5753 @Override finishComposingText()5754 public boolean finishComposingText() { 5755 return mTarget == null || mTarget.finishComposingText(); 5756 } 5757 5758 @Override commitText(CharSequence text, int newCursorPosition)5759 public boolean commitText(CharSequence text, int newCursorPosition) { 5760 return getTarget().commitText(text, newCursorPosition); 5761 } 5762 5763 @Override commitCompletion(CompletionInfo text)5764 public boolean commitCompletion(CompletionInfo text) { 5765 return getTarget().commitCompletion(text); 5766 } 5767 5768 @Override commitCorrection(CorrectionInfo correctionInfo)5769 public boolean commitCorrection(CorrectionInfo correctionInfo) { 5770 return getTarget().commitCorrection(correctionInfo); 5771 } 5772 5773 @Override setSelection(int start, int end)5774 public boolean setSelection(int start, int end) { 5775 return getTarget().setSelection(start, end); 5776 } 5777 5778 @Override performContextMenuAction(int id)5779 public boolean performContextMenuAction(int id) { 5780 return getTarget().performContextMenuAction(id); 5781 } 5782 5783 @Override beginBatchEdit()5784 public boolean beginBatchEdit() { 5785 return getTarget().beginBatchEdit(); 5786 } 5787 5788 @Override endBatchEdit()5789 public boolean endBatchEdit() { 5790 return getTarget().endBatchEdit(); 5791 } 5792 5793 @Override clearMetaKeyStates(int states)5794 public boolean clearMetaKeyStates(int states) { 5795 return getTarget().clearMetaKeyStates(states); 5796 } 5797 5798 @Override performPrivateCommand(String action, Bundle data)5799 public boolean performPrivateCommand(String action, Bundle data) { 5800 return getTarget().performPrivateCommand(action, data); 5801 } 5802 5803 @Override requestCursorUpdates(int cursorUpdateMode)5804 public boolean requestCursorUpdates(int cursorUpdateMode) { 5805 return getTarget().requestCursorUpdates(cursorUpdateMode); 5806 } 5807 } 5808 5809 /** 5810 * For filtering we proxy an input connection to an internal text editor, 5811 * and this allows the proxying to happen. 5812 */ 5813 @Override checkInputConnectionProxy(View view)5814 public boolean checkInputConnectionProxy(View view) { 5815 return view == mTextFilter; 5816 } 5817 5818 /** 5819 * Creates the window for the text filter and populates it with an EditText field; 5820 * 5821 * @param animateEntrance true if the window should appear with an animation 5822 */ createTextFilter(boolean animateEntrance)5823 private void createTextFilter(boolean animateEntrance) { 5824 if (mPopup == null) { 5825 PopupWindow p = new PopupWindow(getContext()); 5826 p.setFocusable(false); 5827 p.setTouchable(false); 5828 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 5829 p.setContentView(getTextFilterInput()); 5830 p.setWidth(LayoutParams.WRAP_CONTENT); 5831 p.setHeight(LayoutParams.WRAP_CONTENT); 5832 p.setBackgroundDrawable(null); 5833 mPopup = p; 5834 getViewTreeObserver().addOnGlobalLayoutListener(this); 5835 mGlobalLayoutListenerAddedFilter = true; 5836 } 5837 if (animateEntrance) { 5838 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter); 5839 } else { 5840 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore); 5841 } 5842 } 5843 getTextFilterInput()5844 private EditText getTextFilterInput() { 5845 if (mTextFilter == null) { 5846 final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); 5847 mTextFilter = (EditText) layoutInflater.inflate( 5848 com.android.internal.R.layout.typing_filter, null); 5849 // For some reason setting this as the "real" input type changes 5850 // the text view in some way that it doesn't work, and I don't 5851 // want to figure out why this is. 5852 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT 5853 | EditorInfo.TYPE_TEXT_VARIATION_FILTER); 5854 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); 5855 mTextFilter.addTextChangedListener(this); 5856 } 5857 return mTextFilter; 5858 } 5859 5860 /** 5861 * Clear the text filter. 5862 */ clearTextFilter()5863 public void clearTextFilter() { 5864 if (mFiltered) { 5865 getTextFilterInput().setText(""); 5866 mFiltered = false; 5867 if (mPopup != null && mPopup.isShowing()) { 5868 dismissPopup(); 5869 } 5870 } 5871 } 5872 5873 /** 5874 * Returns if the ListView currently has a text filter. 5875 */ hasTextFilter()5876 public boolean hasTextFilter() { 5877 return mFiltered; 5878 } 5879 5880 @Override onGlobalLayout()5881 public void onGlobalLayout() { 5882 if (isShown()) { 5883 // Show the popup if we are filtered 5884 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) { 5885 showPopup(); 5886 } 5887 } else { 5888 // Hide the popup when we are no longer visible 5889 if (mPopup != null && mPopup.isShowing()) { 5890 dismissPopup(); 5891 } 5892 } 5893 5894 } 5895 5896 /** 5897 * For our text watcher that is associated with the text filter. Does 5898 * nothing. 5899 */ 5900 @Override beforeTextChanged(CharSequence s, int start, int count, int after)5901 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 5902 } 5903 5904 /** 5905 * For our text watcher that is associated with the text filter. Performs 5906 * the actual filtering as the text changes, and takes care of hiding and 5907 * showing the popup displaying the currently entered filter text. 5908 */ 5909 @Override onTextChanged(CharSequence s, int start, int before, int count)5910 public void onTextChanged(CharSequence s, int start, int before, int count) { 5911 if (isTextFilterEnabled()) { 5912 createTextFilter(true); 5913 int length = s.length(); 5914 boolean showing = mPopup.isShowing(); 5915 if (!showing && length > 0) { 5916 // Show the filter popup if necessary 5917 showPopup(); 5918 mFiltered = true; 5919 } else if (showing && length == 0) { 5920 // Remove the filter popup if the user has cleared all text 5921 dismissPopup(); 5922 mFiltered = false; 5923 } 5924 if (mAdapter instanceof Filterable) { 5925 Filter f = ((Filterable) mAdapter).getFilter(); 5926 // Filter should not be null when we reach this part 5927 if (f != null) { 5928 f.filter(s, this); 5929 } else { 5930 throw new IllegalStateException("You cannot call onTextChanged with a non " 5931 + "filterable adapter"); 5932 } 5933 } 5934 } 5935 } 5936 5937 /** 5938 * For our text watcher that is associated with the text filter. Does 5939 * nothing. 5940 */ 5941 @Override afterTextChanged(Editable s)5942 public void afterTextChanged(Editable s) { 5943 } 5944 5945 @Override onFilterComplete(int count)5946 public void onFilterComplete(int count) { 5947 if (mSelectedPosition < 0 && count > 0) { 5948 mResurrectToPosition = INVALID_POSITION; 5949 resurrectSelection(); 5950 } 5951 } 5952 5953 @Override generateDefaultLayoutParams()5954 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 5955 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5956 ViewGroup.LayoutParams.WRAP_CONTENT, 0); 5957 } 5958 5959 @Override generateLayoutParams(ViewGroup.LayoutParams p)5960 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 5961 return new LayoutParams(p); 5962 } 5963 5964 @Override generateLayoutParams(AttributeSet attrs)5965 public LayoutParams generateLayoutParams(AttributeSet attrs) { 5966 return new AbsListView.LayoutParams(getContext(), attrs); 5967 } 5968 5969 @Override checkLayoutParams(ViewGroup.LayoutParams p)5970 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 5971 return p instanceof AbsListView.LayoutParams; 5972 } 5973 5974 /** 5975 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll 5976 * to the bottom to show new items. 5977 * 5978 * @param mode the transcript mode to set 5979 * 5980 * @see #TRANSCRIPT_MODE_DISABLED 5981 * @see #TRANSCRIPT_MODE_NORMAL 5982 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL 5983 */ setTranscriptMode(int mode)5984 public void setTranscriptMode(int mode) { 5985 mTranscriptMode = mode; 5986 } 5987 5988 /** 5989 * Returns the current transcript mode. 5990 * 5991 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or 5992 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL} 5993 */ getTranscriptMode()5994 public int getTranscriptMode() { 5995 return mTranscriptMode; 5996 } 5997 5998 @Override getSolidColor()5999 public int getSolidColor() { 6000 return mCacheColorHint; 6001 } 6002 6003 /** 6004 * When set to a non-zero value, the cache color hint indicates that this list is always drawn 6005 * on top of a solid, single-color, opaque background. 6006 * 6007 * Zero means that what's behind this object is translucent (non solid) or is not made of a 6008 * single color. This hint will not affect any existing background drawable set on this view ( 6009 * typically set via {@link #setBackgroundDrawable(Drawable)}). 6010 * 6011 * @param color The background color 6012 */ setCacheColorHint(@olorInt int color)6013 public void setCacheColorHint(@ColorInt int color) { 6014 if (color != mCacheColorHint) { 6015 mCacheColorHint = color; 6016 int count = getChildCount(); 6017 for (int i = 0; i < count; i++) { 6018 getChildAt(i).setDrawingCacheBackgroundColor(color); 6019 } 6020 mRecycler.setCacheColorHint(color); 6021 } 6022 } 6023 6024 /** 6025 * When set to a non-zero value, the cache color hint indicates that this list is always drawn 6026 * on top of a solid, single-color, opaque background 6027 * 6028 * @return The cache color hint 6029 */ 6030 @ViewDebug.ExportedProperty(category = "drawing") 6031 @ColorInt getCacheColorHint()6032 public int getCacheColorHint() { 6033 return mCacheColorHint; 6034 } 6035 6036 /** 6037 * Move all views (excluding headers and footers) held by this AbsListView into the supplied 6038 * List. This includes views displayed on the screen as well as views stored in AbsListView's 6039 * internal view recycler. 6040 * 6041 * @param views A list into which to put the reclaimed views 6042 */ reclaimViews(List<View> views)6043 public void reclaimViews(List<View> views) { 6044 int childCount = getChildCount(); 6045 RecyclerListener listener = mRecycler.mRecyclerListener; 6046 6047 // Reclaim views on screen 6048 for (int i = 0; i < childCount; i++) { 6049 View child = getChildAt(i); 6050 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); 6051 // Don't reclaim header or footer views, or views that should be ignored 6052 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { 6053 views.add(child); 6054 child.setAccessibilityDelegate(null); 6055 if (listener != null) { 6056 // Pretend they went through the scrap heap 6057 listener.onMovedToScrapHeap(child); 6058 } 6059 } 6060 } 6061 mRecycler.reclaimScrapViews(views); 6062 removeAllViewsInLayout(); 6063 } 6064 finishGlows()6065 private void finishGlows() { 6066 if (mEdgeGlowTop != null) { 6067 mEdgeGlowTop.finish(); 6068 mEdgeGlowBottom.finish(); 6069 } 6070 } 6071 6072 /** 6073 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService 6074 * through the specified intent. 6075 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to. 6076 */ setRemoteViewsAdapter(Intent intent)6077 public void setRemoteViewsAdapter(Intent intent) { 6078 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 6079 // service handling the specified intent. 6080 if (mRemoteAdapter != null) { 6081 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); 6082 Intent.FilterComparison fcOld = new Intent.FilterComparison( 6083 mRemoteAdapter.getRemoteViewsServiceIntent()); 6084 if (fcNew.equals(fcOld)) { 6085 return; 6086 } 6087 } 6088 mDeferNotifyDataSetChanged = false; 6089 // Otherwise, create a new RemoteViewsAdapter for binding 6090 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this); 6091 if (mRemoteAdapter.isDataReady()) { 6092 setAdapter(mRemoteAdapter); 6093 } 6094 } 6095 6096 /** 6097 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews 6098 * 6099 * @param handler The OnClickHandler to use when inflating RemoteViews. 6100 * 6101 * @hide 6102 */ setRemoteViewsOnClickHandler(OnClickHandler handler)6103 public void setRemoteViewsOnClickHandler(OnClickHandler handler) { 6104 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing 6105 // service handling the specified intent. 6106 if (mRemoteAdapter != null) { 6107 mRemoteAdapter.setRemoteViewsOnClickHandler(handler); 6108 } 6109 } 6110 6111 /** 6112 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not 6113 * connected yet. 6114 */ 6115 @Override deferNotifyDataSetChanged()6116 public void deferNotifyDataSetChanged() { 6117 mDeferNotifyDataSetChanged = true; 6118 } 6119 6120 /** 6121 * Called back when the adapter connects to the RemoteViewsService. 6122 */ 6123 @Override onRemoteAdapterConnected()6124 public boolean onRemoteAdapterConnected() { 6125 if (mRemoteAdapter != mAdapter) { 6126 setAdapter(mRemoteAdapter); 6127 if (mDeferNotifyDataSetChanged) { 6128 mRemoteAdapter.notifyDataSetChanged(); 6129 mDeferNotifyDataSetChanged = false; 6130 } 6131 return false; 6132 } else if (mRemoteAdapter != null) { 6133 mRemoteAdapter.superNotifyDataSetChanged(); 6134 return true; 6135 } 6136 return false; 6137 } 6138 6139 /** 6140 * Called back when the adapter disconnects from the RemoteViewsService. 6141 */ 6142 @Override onRemoteAdapterDisconnected()6143 public void onRemoteAdapterDisconnected() { 6144 // If the remote adapter disconnects, we keep it around 6145 // since the currently displayed items are still cached. 6146 // Further, we want the service to eventually reconnect 6147 // when necessary, as triggered by this view requesting 6148 // items from the Adapter. 6149 } 6150 6151 /** 6152 * Hints the RemoteViewsAdapter, if it exists, about which views are currently 6153 * being displayed by the AbsListView. 6154 */ setVisibleRangeHint(int start, int end)6155 void setVisibleRangeHint(int start, int end) { 6156 if (mRemoteAdapter != null) { 6157 mRemoteAdapter.setVisibleRangeHint(start, end); 6158 } 6159 } 6160 6161 /** 6162 * Sets the recycler listener to be notified whenever a View is set aside in 6163 * the recycler for later reuse. This listener can be used to free resources 6164 * associated to the View. 6165 * 6166 * @param listener The recycler listener to be notified of views set aside 6167 * in the recycler. 6168 * 6169 * @see android.widget.AbsListView.RecycleBin 6170 * @see android.widget.AbsListView.RecyclerListener 6171 */ setRecyclerListener(RecyclerListener listener)6172 public void setRecyclerListener(RecyclerListener listener) { 6173 mRecycler.mRecyclerListener = listener; 6174 } 6175 6176 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver { 6177 @Override onChanged()6178 public void onChanged() { 6179 super.onChanged(); 6180 if (mFastScroll != null) { 6181 mFastScroll.onSectionsChanged(); 6182 } 6183 } 6184 6185 @Override onInvalidated()6186 public void onInvalidated() { 6187 super.onInvalidated(); 6188 if (mFastScroll != null) { 6189 mFastScroll.onSectionsChanged(); 6190 } 6191 } 6192 } 6193 6194 /** 6195 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. 6196 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives 6197 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user 6198 * selects and deselects list items. 6199 */ 6200 public interface MultiChoiceModeListener extends ActionMode.Callback { 6201 /** 6202 * Called when an item is checked or unchecked during selection mode. 6203 * 6204 * @param mode The {@link ActionMode} providing the selection mode 6205 * @param position Adapter position of the item that was checked or unchecked 6206 * @param id Adapter ID of the item that was checked or unchecked 6207 * @param checked <code>true</code> if the item is now checked, <code>false</code> 6208 * if the item is now unchecked. 6209 */ 6210 public void onItemCheckedStateChanged(ActionMode mode, 6211 int position, long id, boolean checked); 6212 } 6213 6214 class MultiChoiceModeWrapper implements MultiChoiceModeListener { 6215 private MultiChoiceModeListener mWrapped; 6216 setWrapped(MultiChoiceModeListener wrapped)6217 public void setWrapped(MultiChoiceModeListener wrapped) { 6218 mWrapped = wrapped; 6219 } 6220 hasWrappedCallback()6221 public boolean hasWrappedCallback() { 6222 return mWrapped != null; 6223 } 6224 6225 @Override onCreateActionMode(ActionMode mode, Menu menu)6226 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 6227 if (mWrapped.onCreateActionMode(mode, menu)) { 6228 // Initialize checked graphic state? 6229 setLongClickable(false); 6230 return true; 6231 } 6232 return false; 6233 } 6234 6235 @Override onPrepareActionMode(ActionMode mode, Menu menu)6236 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 6237 return mWrapped.onPrepareActionMode(mode, menu); 6238 } 6239 6240 @Override onActionItemClicked(ActionMode mode, MenuItem item)6241 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 6242 return mWrapped.onActionItemClicked(mode, item); 6243 } 6244 6245 @Override onDestroyActionMode(ActionMode mode)6246 public void onDestroyActionMode(ActionMode mode) { 6247 mWrapped.onDestroyActionMode(mode); 6248 mChoiceActionMode = null; 6249 6250 // Ending selection mode means deselecting everything. 6251 clearChoices(); 6252 6253 mDataChanged = true; 6254 rememberSyncState(); 6255 requestLayout(); 6256 6257 setLongClickable(true); 6258 } 6259 6260 @Override onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked)6261 public void onItemCheckedStateChanged(ActionMode mode, 6262 int position, long id, boolean checked) { 6263 mWrapped.onItemCheckedStateChanged(mode, position, id, checked); 6264 6265 // If there are no items selected we no longer need the selection mode. 6266 if (getCheckedItemCount() == 0) { 6267 mode.finish(); 6268 } 6269 } 6270 } 6271 6272 /** 6273 * AbsListView extends LayoutParams to provide a place to hold the view type. 6274 */ 6275 public static class LayoutParams extends ViewGroup.LayoutParams { 6276 /** 6277 * View type for this view, as returned by 6278 * {@link android.widget.Adapter#getItemViewType(int) } 6279 */ 6280 @ViewDebug.ExportedProperty(category = "list", mapping = { 6281 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"), 6282 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER") 6283 }) 6284 int viewType; 6285 6286 /** 6287 * When this boolean is set, the view has been added to the AbsListView 6288 * at least once. It is used to know whether headers/footers have already 6289 * been added to the list view and whether they should be treated as 6290 * recycled views or not. 6291 */ 6292 @ViewDebug.ExportedProperty(category = "list") 6293 boolean recycledHeaderFooter; 6294 6295 /** 6296 * When an AbsListView is measured with an AT_MOST measure spec, it needs 6297 * to obtain children views to measure itself. When doing so, the children 6298 * are not attached to the window, but put in the recycler which assumes 6299 * they've been attached before. Setting this flag will force the reused 6300 * view to be attached to the window rather than just attached to the 6301 * parent. 6302 */ 6303 @ViewDebug.ExportedProperty(category = "list") 6304 boolean forceAdd; 6305 6306 /** 6307 * The position the view was removed from when pulled out of the 6308 * scrap heap. 6309 * @hide 6310 */ 6311 int scrappedFromPosition; 6312 6313 /** 6314 * The ID the view represents 6315 */ 6316 long itemId = -1; 6317 LayoutParams(Context c, AttributeSet attrs)6318 public LayoutParams(Context c, AttributeSet attrs) { 6319 super(c, attrs); 6320 } 6321 LayoutParams(int w, int h)6322 public LayoutParams(int w, int h) { 6323 super(w, h); 6324 } 6325 LayoutParams(int w, int h, int viewType)6326 public LayoutParams(int w, int h, int viewType) { 6327 super(w, h); 6328 this.viewType = viewType; 6329 } 6330 LayoutParams(ViewGroup.LayoutParams source)6331 public LayoutParams(ViewGroup.LayoutParams source) { 6332 super(source); 6333 } 6334 6335 /** @hide */ 6336 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)6337 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 6338 super.encodeProperties(encoder); 6339 6340 encoder.addProperty("list:viewType", viewType); 6341 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter); 6342 encoder.addProperty("list:forceAdd", forceAdd); 6343 } 6344 } 6345 6346 /** 6347 * A RecyclerListener is used to receive a notification whenever a View is placed 6348 * inside the RecycleBin's scrap heap. This listener is used to free resources 6349 * associated to Views placed in the RecycleBin. 6350 * 6351 * @see android.widget.AbsListView.RecycleBin 6352 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 6353 */ 6354 public static interface RecyclerListener { 6355 /** 6356 * Indicates that the specified View was moved into the recycler's scrap heap. 6357 * The view is not displayed on screen any more and any expensive resource 6358 * associated with the view should be discarded. 6359 * 6360 * @param view 6361 */ 6362 void onMovedToScrapHeap(View view); 6363 } 6364 6365 /** 6366 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of 6367 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the 6368 * start of a layout. By construction, they are displaying current information. At the end of 6369 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that 6370 * could potentially be used by the adapter to avoid allocating views unnecessarily. 6371 * 6372 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 6373 * @see android.widget.AbsListView.RecyclerListener 6374 */ 6375 class RecycleBin { 6376 private RecyclerListener mRecyclerListener; 6377 6378 /** 6379 * The position of the first view stored in mActiveViews. 6380 */ 6381 private int mFirstActivePosition; 6382 6383 /** 6384 * Views that were on screen at the start of layout. This array is populated at the start of 6385 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. 6386 * Views in mActiveViews represent a contiguous range of Views, with position of the first 6387 * view store in mFirstActivePosition. 6388 */ 6389 private View[] mActiveViews = new View[0]; 6390 6391 /** 6392 * Unsorted views that can be used by the adapter as a convert view. 6393 */ 6394 private ArrayList<View>[] mScrapViews; 6395 6396 private int mViewTypeCount; 6397 6398 private ArrayList<View> mCurrentScrap; 6399 6400 private ArrayList<View> mSkippedScrap; 6401 6402 private SparseArray<View> mTransientStateViews; 6403 private LongSparseArray<View> mTransientStateViewsById; 6404 setViewTypeCount(int viewTypeCount)6405 public void setViewTypeCount(int viewTypeCount) { 6406 if (viewTypeCount < 1) { 6407 throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); 6408 } 6409 //noinspection unchecked 6410 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; 6411 for (int i = 0; i < viewTypeCount; i++) { 6412 scrapViews[i] = new ArrayList<View>(); 6413 } 6414 mViewTypeCount = viewTypeCount; 6415 mCurrentScrap = scrapViews[0]; 6416 mScrapViews = scrapViews; 6417 } 6418 markChildrenDirty()6419 public void markChildrenDirty() { 6420 if (mViewTypeCount == 1) { 6421 final ArrayList<View> scrap = mCurrentScrap; 6422 final int scrapCount = scrap.size(); 6423 for (int i = 0; i < scrapCount; i++) { 6424 scrap.get(i).forceLayout(); 6425 } 6426 } else { 6427 final int typeCount = mViewTypeCount; 6428 for (int i = 0; i < typeCount; i++) { 6429 final ArrayList<View> scrap = mScrapViews[i]; 6430 final int scrapCount = scrap.size(); 6431 for (int j = 0; j < scrapCount; j++) { 6432 scrap.get(j).forceLayout(); 6433 } 6434 } 6435 } 6436 if (mTransientStateViews != null) { 6437 final int count = mTransientStateViews.size(); 6438 for (int i = 0; i < count; i++) { 6439 mTransientStateViews.valueAt(i).forceLayout(); 6440 } 6441 } 6442 if (mTransientStateViewsById != null) { 6443 final int count = mTransientStateViewsById.size(); 6444 for (int i = 0; i < count; i++) { 6445 mTransientStateViewsById.valueAt(i).forceLayout(); 6446 } 6447 } 6448 } 6449 shouldRecycleViewType(int viewType)6450 public boolean shouldRecycleViewType(int viewType) { 6451 return viewType >= 0; 6452 } 6453 6454 /** 6455 * Clears the scrap heap. 6456 */ clear()6457 void clear() { 6458 if (mViewTypeCount == 1) { 6459 final ArrayList<View> scrap = mCurrentScrap; 6460 clearScrap(scrap); 6461 } else { 6462 final int typeCount = mViewTypeCount; 6463 for (int i = 0; i < typeCount; i++) { 6464 final ArrayList<View> scrap = mScrapViews[i]; 6465 clearScrap(scrap); 6466 } 6467 } 6468 6469 clearTransientStateViews(); 6470 } 6471 6472 /** 6473 * Fill ActiveViews with all of the children of the AbsListView. 6474 * 6475 * @param childCount The minimum number of views mActiveViews should hold 6476 * @param firstActivePosition The position of the first view that will be stored in 6477 * mActiveViews 6478 */ fillActiveViews(int childCount, int firstActivePosition)6479 void fillActiveViews(int childCount, int firstActivePosition) { 6480 if (mActiveViews.length < childCount) { 6481 mActiveViews = new View[childCount]; 6482 } 6483 mFirstActivePosition = firstActivePosition; 6484 6485 //noinspection MismatchedReadAndWriteOfArray 6486 final View[] activeViews = mActiveViews; 6487 for (int i = 0; i < childCount; i++) { 6488 View child = getChildAt(i); 6489 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); 6490 // Don't put header or footer views into the scrap heap 6491 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6492 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. 6493 // However, we will NOT place them into scrap views. 6494 activeViews[i] = child; 6495 // Remember the position so that setupChild() doesn't reset state. 6496 lp.scrappedFromPosition = firstActivePosition + i; 6497 } 6498 } 6499 } 6500 6501 /** 6502 * Get the view corresponding to the specified position. The view will be removed from 6503 * mActiveViews if it is found. 6504 * 6505 * @param position The position to look up in mActiveViews 6506 * @return The view if it is found, null otherwise 6507 */ getActiveView(int position)6508 View getActiveView(int position) { 6509 int index = position - mFirstActivePosition; 6510 final View[] activeViews = mActiveViews; 6511 if (index >=0 && index < activeViews.length) { 6512 final View match = activeViews[index]; 6513 activeViews[index] = null; 6514 return match; 6515 } 6516 return null; 6517 } 6518 getTransientStateView(int position)6519 View getTransientStateView(int position) { 6520 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) { 6521 long id = mAdapter.getItemId(position); 6522 View result = mTransientStateViewsById.get(id); 6523 mTransientStateViewsById.remove(id); 6524 return result; 6525 } 6526 if (mTransientStateViews != null) { 6527 final int index = mTransientStateViews.indexOfKey(position); 6528 if (index >= 0) { 6529 View result = mTransientStateViews.valueAt(index); 6530 mTransientStateViews.removeAt(index); 6531 return result; 6532 } 6533 } 6534 return null; 6535 } 6536 6537 /** 6538 * Dumps and fully detaches any currently saved views with transient 6539 * state. 6540 */ clearTransientStateViews()6541 void clearTransientStateViews() { 6542 final SparseArray<View> viewsByPos = mTransientStateViews; 6543 if (viewsByPos != null) { 6544 final int N = viewsByPos.size(); 6545 for (int i = 0; i < N; i++) { 6546 removeDetachedView(viewsByPos.valueAt(i), false); 6547 } 6548 viewsByPos.clear(); 6549 } 6550 6551 final LongSparseArray<View> viewsById = mTransientStateViewsById; 6552 if (viewsById != null) { 6553 final int N = viewsById.size(); 6554 for (int i = 0; i < N; i++) { 6555 removeDetachedView(viewsById.valueAt(i), false); 6556 } 6557 viewsById.clear(); 6558 } 6559 } 6560 6561 /** 6562 * @return A view from the ScrapViews collection. These are unordered. 6563 */ getScrapView(int position)6564 View getScrapView(int position) { 6565 final int whichScrap = mAdapter.getItemViewType(position); 6566 if (whichScrap < 0) { 6567 return null; 6568 } 6569 if (mViewTypeCount == 1) { 6570 return retrieveFromScrap(mCurrentScrap, position); 6571 } else if (whichScrap < mScrapViews.length) { 6572 return retrieveFromScrap(mScrapViews[whichScrap], position); 6573 } 6574 return null; 6575 } 6576 6577 /** 6578 * Puts a view into the list of scrap views. 6579 * <p> 6580 * If the list data hasn't changed or the adapter has stable IDs, views 6581 * with transient state will be preserved for later retrieval. 6582 * 6583 * @param scrap The view to add 6584 * @param position The view's position within its parent 6585 */ addScrapView(View scrap, int position)6586 void addScrapView(View scrap, int position) { 6587 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); 6588 if (lp == null) { 6589 // Can't recycle, but we don't know anything about the view. 6590 // Ignore it completely. 6591 return; 6592 } 6593 6594 lp.scrappedFromPosition = position; 6595 6596 // Remove but don't scrap header or footer views, or views that 6597 // should otherwise not be recycled. 6598 final int viewType = lp.viewType; 6599 if (!shouldRecycleViewType(viewType)) { 6600 // Can't recycle. If it's not a header or footer, which have 6601 // special handling and should be ignored, then skip the scrap 6602 // heap and we'll fully detach the view later. 6603 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6604 getSkippedScrap().add(scrap); 6605 } 6606 return; 6607 } 6608 6609 scrap.dispatchStartTemporaryDetach(); 6610 6611 // The the accessibility state of the view may change while temporary 6612 // detached and we do not allow detached views to fire accessibility 6613 // events. So we are announcing that the subtree changed giving a chance 6614 // to clients holding on to a view in this subtree to refresh it. 6615 notifyViewAccessibilityStateChangedIfNeeded( 6616 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); 6617 6618 // Don't scrap views that have transient state. 6619 final boolean scrapHasTransientState = scrap.hasTransientState(); 6620 if (scrapHasTransientState) { 6621 if (mAdapter != null && mAdapterHasStableIds) { 6622 // If the adapter has stable IDs, we can reuse the view for 6623 // the same data. 6624 if (mTransientStateViewsById == null) { 6625 mTransientStateViewsById = new LongSparseArray<>(); 6626 } 6627 mTransientStateViewsById.put(lp.itemId, scrap); 6628 } else if (!mDataChanged) { 6629 // If the data hasn't changed, we can reuse the views at 6630 // their old positions. 6631 if (mTransientStateViews == null) { 6632 mTransientStateViews = new SparseArray<>(); 6633 } 6634 mTransientStateViews.put(position, scrap); 6635 } else { 6636 // Otherwise, we'll have to remove the view and start over. 6637 getSkippedScrap().add(scrap); 6638 } 6639 } else { 6640 if (mViewTypeCount == 1) { 6641 mCurrentScrap.add(scrap); 6642 } else { 6643 mScrapViews[viewType].add(scrap); 6644 } 6645 6646 if (mRecyclerListener != null) { 6647 mRecyclerListener.onMovedToScrapHeap(scrap); 6648 } 6649 } 6650 } 6651 getSkippedScrap()6652 private ArrayList<View> getSkippedScrap() { 6653 if (mSkippedScrap == null) { 6654 mSkippedScrap = new ArrayList<>(); 6655 } 6656 return mSkippedScrap; 6657 } 6658 6659 /** 6660 * Finish the removal of any views that skipped the scrap heap. 6661 */ removeSkippedScrap()6662 void removeSkippedScrap() { 6663 if (mSkippedScrap == null) { 6664 return; 6665 } 6666 final int count = mSkippedScrap.size(); 6667 for (int i = 0; i < count; i++) { 6668 removeDetachedView(mSkippedScrap.get(i), false); 6669 } 6670 mSkippedScrap.clear(); 6671 } 6672 6673 /** 6674 * Move all views remaining in mActiveViews to mScrapViews. 6675 */ scrapActiveViews()6676 void scrapActiveViews() { 6677 final View[] activeViews = mActiveViews; 6678 final boolean hasListener = mRecyclerListener != null; 6679 final boolean multipleScraps = mViewTypeCount > 1; 6680 6681 ArrayList<View> scrapViews = mCurrentScrap; 6682 final int count = activeViews.length; 6683 for (int i = count - 1; i >= 0; i--) { 6684 final View victim = activeViews[i]; 6685 if (victim != null) { 6686 final AbsListView.LayoutParams lp 6687 = (AbsListView.LayoutParams) victim.getLayoutParams(); 6688 final int whichScrap = lp.viewType; 6689 6690 activeViews[i] = null; 6691 6692 if (victim.hasTransientState()) { 6693 // Store views with transient state for later use. 6694 victim.dispatchStartTemporaryDetach(); 6695 6696 if (mAdapter != null && mAdapterHasStableIds) { 6697 if (mTransientStateViewsById == null) { 6698 mTransientStateViewsById = new LongSparseArray<View>(); 6699 } 6700 long id = mAdapter.getItemId(mFirstActivePosition + i); 6701 mTransientStateViewsById.put(id, victim); 6702 } else if (!mDataChanged) { 6703 if (mTransientStateViews == null) { 6704 mTransientStateViews = new SparseArray<View>(); 6705 } 6706 mTransientStateViews.put(mFirstActivePosition + i, victim); 6707 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6708 // The data has changed, we can't keep this view. 6709 removeDetachedView(victim, false); 6710 } 6711 } else if (!shouldRecycleViewType(whichScrap)) { 6712 // Discard non-recyclable views except headers/footers. 6713 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { 6714 removeDetachedView(victim, false); 6715 } 6716 } else { 6717 // Store everything else on the appropriate scrap heap. 6718 if (multipleScraps) { 6719 scrapViews = mScrapViews[whichScrap]; 6720 } 6721 6722 victim.dispatchStartTemporaryDetach(); 6723 lp.scrappedFromPosition = mFirstActivePosition + i; 6724 scrapViews.add(victim); 6725 6726 if (hasListener) { 6727 mRecyclerListener.onMovedToScrapHeap(victim); 6728 } 6729 } 6730 } 6731 } 6732 6733 pruneScrapViews(); 6734 } 6735 6736 /** 6737 * Makes sure that the size of mScrapViews does not exceed the size of 6738 * mActiveViews, which can happen if an adapter does not recycle its 6739 * views. Removes cached transient state views that no longer have 6740 * transient state. 6741 */ pruneScrapViews()6742 private void pruneScrapViews() { 6743 final int maxViews = mActiveViews.length; 6744 final int viewTypeCount = mViewTypeCount; 6745 final ArrayList<View>[] scrapViews = mScrapViews; 6746 for (int i = 0; i < viewTypeCount; ++i) { 6747 final ArrayList<View> scrapPile = scrapViews[i]; 6748 int size = scrapPile.size(); 6749 final int extras = size - maxViews; 6750 size--; 6751 for (int j = 0; j < extras; j++) { 6752 removeDetachedView(scrapPile.remove(size--), false); 6753 } 6754 } 6755 6756 final SparseArray<View> transViewsByPos = mTransientStateViews; 6757 if (transViewsByPos != null) { 6758 for (int i = 0; i < transViewsByPos.size(); i++) { 6759 final View v = transViewsByPos.valueAt(i); 6760 if (!v.hasTransientState()) { 6761 removeDetachedView(v, false); 6762 transViewsByPos.removeAt(i); 6763 i--; 6764 } 6765 } 6766 } 6767 6768 final LongSparseArray<View> transViewsById = mTransientStateViewsById; 6769 if (transViewsById != null) { 6770 for (int i = 0; i < transViewsById.size(); i++) { 6771 final View v = transViewsById.valueAt(i); 6772 if (!v.hasTransientState()) { 6773 removeDetachedView(v, false); 6774 transViewsById.removeAt(i); 6775 i--; 6776 } 6777 } 6778 } 6779 } 6780 6781 /** 6782 * Puts all views in the scrap heap into the supplied list. 6783 */ reclaimScrapViews(List<View> views)6784 void reclaimScrapViews(List<View> views) { 6785 if (mViewTypeCount == 1) { 6786 views.addAll(mCurrentScrap); 6787 } else { 6788 final int viewTypeCount = mViewTypeCount; 6789 final ArrayList<View>[] scrapViews = mScrapViews; 6790 for (int i = 0; i < viewTypeCount; ++i) { 6791 final ArrayList<View> scrapPile = scrapViews[i]; 6792 views.addAll(scrapPile); 6793 } 6794 } 6795 } 6796 6797 /** 6798 * Updates the cache color hint of all known views. 6799 * 6800 * @param color The new cache color hint. 6801 */ setCacheColorHint(int color)6802 void setCacheColorHint(int color) { 6803 if (mViewTypeCount == 1) { 6804 final ArrayList<View> scrap = mCurrentScrap; 6805 final int scrapCount = scrap.size(); 6806 for (int i = 0; i < scrapCount; i++) { 6807 scrap.get(i).setDrawingCacheBackgroundColor(color); 6808 } 6809 } else { 6810 final int typeCount = mViewTypeCount; 6811 for (int i = 0; i < typeCount; i++) { 6812 final ArrayList<View> scrap = mScrapViews[i]; 6813 final int scrapCount = scrap.size(); 6814 for (int j = 0; j < scrapCount; j++) { 6815 scrap.get(j).setDrawingCacheBackgroundColor(color); 6816 } 6817 } 6818 } 6819 // Just in case this is called during a layout pass 6820 final View[] activeViews = mActiveViews; 6821 final int count = activeViews.length; 6822 for (int i = 0; i < count; ++i) { 6823 final View victim = activeViews[i]; 6824 if (victim != null) { 6825 victim.setDrawingCacheBackgroundColor(color); 6826 } 6827 } 6828 } 6829 retrieveFromScrap(ArrayList<View> scrapViews, int position)6830 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) { 6831 final int size = scrapViews.size(); 6832 if (size > 0) { 6833 // See if we still have a view for this position or ID. 6834 for (int i = 0; i < size; i++) { 6835 final View view = scrapViews.get(i); 6836 final AbsListView.LayoutParams params = 6837 (AbsListView.LayoutParams) view.getLayoutParams(); 6838 6839 if (mAdapterHasStableIds) { 6840 final long id = mAdapter.getItemId(position); 6841 if (id == params.itemId) { 6842 return scrapViews.remove(i); 6843 } 6844 } else if (params.scrappedFromPosition == position) { 6845 final View scrap = scrapViews.remove(i); 6846 clearAccessibilityFromScrap(scrap); 6847 return scrap; 6848 } 6849 } 6850 final View scrap = scrapViews.remove(size - 1); 6851 clearAccessibilityFromScrap(scrap); 6852 return scrap; 6853 } else { 6854 return null; 6855 } 6856 } 6857 clearScrap(final ArrayList<View> scrap)6858 private void clearScrap(final ArrayList<View> scrap) { 6859 final int scrapCount = scrap.size(); 6860 for (int j = 0; j < scrapCount; j++) { 6861 removeDetachedView(scrap.remove(scrapCount - 1 - j), false); 6862 } 6863 } 6864 clearAccessibilityFromScrap(View view)6865 private void clearAccessibilityFromScrap(View view) { 6866 view.clearAccessibilityFocus(); 6867 view.setAccessibilityDelegate(null); 6868 } 6869 removeDetachedView(View child, boolean animate)6870 private void removeDetachedView(View child, boolean animate) { 6871 child.setAccessibilityDelegate(null); 6872 AbsListView.this.removeDetachedView(child, animate); 6873 } 6874 } 6875 6876 /** 6877 * Returns the height of the view for the specified position. 6878 * 6879 * @param position the item position 6880 * @return view height in pixels 6881 */ getHeightForPosition(int position)6882 int getHeightForPosition(int position) { 6883 final int firstVisiblePosition = getFirstVisiblePosition(); 6884 final int childCount = getChildCount(); 6885 final int index = position - firstVisiblePosition; 6886 if (index >= 0 && index < childCount) { 6887 // Position is on-screen, use existing view. 6888 final View view = getChildAt(index); 6889 return view.getHeight(); 6890 } else { 6891 // Position is off-screen, obtain & recycle view. 6892 final View view = obtainView(position, mIsScrap); 6893 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED); 6894 final int height = view.getMeasuredHeight(); 6895 mRecycler.addScrapView(view, position); 6896 return height; 6897 } 6898 } 6899 6900 /** 6901 * Sets the selected item and positions the selection y pixels from the top edge 6902 * of the ListView. (If in touch mode, the item will not be selected but it will 6903 * still be positioned appropriately.) 6904 * 6905 * @param position Index (starting at 0) of the data item to be selected. 6906 * @param y The distance from the top edge of the ListView (plus padding) that the 6907 * item will be positioned. 6908 */ setSelectionFromTop(int position, int y)6909 public void setSelectionFromTop(int position, int y) { 6910 if (mAdapter == null) { 6911 return; 6912 } 6913 6914 if (!isInTouchMode()) { 6915 position = lookForSelectablePosition(position, true); 6916 if (position >= 0) { 6917 setNextSelectedPositionInt(position); 6918 } 6919 } else { 6920 mResurrectToPosition = position; 6921 } 6922 6923 if (position >= 0) { 6924 mLayoutMode = LAYOUT_SPECIFIC; 6925 mSpecificTop = mListPadding.top + y; 6926 6927 if (mNeedSync) { 6928 mSyncPosition = position; 6929 mSyncRowId = mAdapter.getItemId(position); 6930 } 6931 6932 if (mPositionScroller != null) { 6933 mPositionScroller.stop(); 6934 } 6935 requestLayout(); 6936 } 6937 } 6938 6939 /** @hide */ 6940 @Override encodeProperties(@onNull ViewHierarchyEncoder encoder)6941 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { 6942 super.encodeProperties(encoder); 6943 6944 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint()); 6945 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled()); 6946 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled()); 6947 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled()); 6948 encoder.addProperty("list:stackFromBottom", isStackFromBottom()); 6949 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled()); 6950 6951 View selectedView = getSelectedView(); 6952 if (selectedView != null) { 6953 encoder.addPropertyKey("selectedView"); 6954 selectedView.encode(encoder); 6955 } 6956 } 6957 6958 /** 6959 * Abstract positon scroller used to handle smooth scrolling. 6960 */ 6961 static abstract class AbsPositionScroller { 6962 public abstract void start(int position); 6963 public abstract void start(int position, int boundPosition); 6964 public abstract void startWithOffset(int position, int offset); 6965 public abstract void startWithOffset(int position, int offset, int duration); 6966 public abstract void stop(); 6967 } 6968 6969 /** 6970 * Default position scroller that simulates a fling. 6971 */ 6972 class PositionScroller extends AbsPositionScroller implements Runnable { 6973 private static final int SCROLL_DURATION = 200; 6974 6975 private static final int MOVE_DOWN_POS = 1; 6976 private static final int MOVE_UP_POS = 2; 6977 private static final int MOVE_DOWN_BOUND = 3; 6978 private static final int MOVE_UP_BOUND = 4; 6979 private static final int MOVE_OFFSET = 5; 6980 6981 private int mMode; 6982 private int mTargetPos; 6983 private int mBoundPos; 6984 private int mLastSeenPos; 6985 private int mScrollDuration; 6986 private final int mExtraScroll; 6987 6988 private int mOffsetFromTop; 6989 PositionScroller()6990 PositionScroller() { 6991 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); 6992 } 6993 6994 @Override start(final int position)6995 public void start(final int position) { 6996 stop(); 6997 6998 if (mDataChanged) { 6999 // Wait until we're back in a stable state to try this. 7000 mPositionScrollAfterLayout = new Runnable() { 7001 @Override public void run() { 7002 start(position); 7003 } 7004 }; 7005 return; 7006 } 7007 7008 final int childCount = getChildCount(); 7009 if (childCount == 0) { 7010 // Can't scroll without children. 7011 return; 7012 } 7013 7014 final int firstPos = mFirstPosition; 7015 final int lastPos = firstPos + childCount - 1; 7016 7017 int viewTravelCount; 7018 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); 7019 if (clampedPosition < firstPos) { 7020 viewTravelCount = firstPos - clampedPosition + 1; 7021 mMode = MOVE_UP_POS; 7022 } else if (clampedPosition > lastPos) { 7023 viewTravelCount = clampedPosition - lastPos + 1; 7024 mMode = MOVE_DOWN_POS; 7025 } else { 7026 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION); 7027 return; 7028 } 7029 7030 if (viewTravelCount > 0) { 7031 mScrollDuration = SCROLL_DURATION / viewTravelCount; 7032 } else { 7033 mScrollDuration = SCROLL_DURATION; 7034 } 7035 mTargetPos = clampedPosition; 7036 mBoundPos = INVALID_POSITION; 7037 mLastSeenPos = INVALID_POSITION; 7038 7039 postOnAnimation(this); 7040 } 7041 7042 @Override start(final int position, final int boundPosition)7043 public void start(final int position, final int boundPosition) { 7044 stop(); 7045 7046 if (boundPosition == INVALID_POSITION) { 7047 start(position); 7048 return; 7049 } 7050 7051 if (mDataChanged) { 7052 // Wait until we're back in a stable state to try this. 7053 mPositionScrollAfterLayout = new Runnable() { 7054 @Override public void run() { 7055 start(position, boundPosition); 7056 } 7057 }; 7058 return; 7059 } 7060 7061 final int childCount = getChildCount(); 7062 if (childCount == 0) { 7063 // Can't scroll without children. 7064 return; 7065 } 7066 7067 final int firstPos = mFirstPosition; 7068 final int lastPos = firstPos + childCount - 1; 7069 7070 int viewTravelCount; 7071 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); 7072 if (clampedPosition < firstPos) { 7073 final int boundPosFromLast = lastPos - boundPosition; 7074 if (boundPosFromLast < 1) { 7075 // Moving would shift our bound position off the screen. Abort. 7076 return; 7077 } 7078 7079 final int posTravel = firstPos - clampedPosition + 1; 7080 final int boundTravel = boundPosFromLast - 1; 7081 if (boundTravel < posTravel) { 7082 viewTravelCount = boundTravel; 7083 mMode = MOVE_UP_BOUND; 7084 } else { 7085 viewTravelCount = posTravel; 7086 mMode = MOVE_UP_POS; 7087 } 7088 } else if (clampedPosition > lastPos) { 7089 final int boundPosFromFirst = boundPosition - firstPos; 7090 if (boundPosFromFirst < 1) { 7091 // Moving would shift our bound position off the screen. Abort. 7092 return; 7093 } 7094 7095 final int posTravel = clampedPosition - lastPos + 1; 7096 final int boundTravel = boundPosFromFirst - 1; 7097 if (boundTravel < posTravel) { 7098 viewTravelCount = boundTravel; 7099 mMode = MOVE_DOWN_BOUND; 7100 } else { 7101 viewTravelCount = posTravel; 7102 mMode = MOVE_DOWN_POS; 7103 } 7104 } else { 7105 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION); 7106 return; 7107 } 7108 7109 if (viewTravelCount > 0) { 7110 mScrollDuration = SCROLL_DURATION / viewTravelCount; 7111 } else { 7112 mScrollDuration = SCROLL_DURATION; 7113 } 7114 mTargetPos = clampedPosition; 7115 mBoundPos = boundPosition; 7116 mLastSeenPos = INVALID_POSITION; 7117 7118 postOnAnimation(this); 7119 } 7120 7121 @Override startWithOffset(int position, int offset)7122 public void startWithOffset(int position, int offset) { 7123 startWithOffset(position, offset, SCROLL_DURATION); 7124 } 7125 7126 @Override startWithOffset(final int position, int offset, final int duration)7127 public void startWithOffset(final int position, int offset, final int duration) { 7128 stop(); 7129 7130 if (mDataChanged) { 7131 // Wait until we're back in a stable state to try this. 7132 final int postOffset = offset; 7133 mPositionScrollAfterLayout = new Runnable() { 7134 @Override public void run() { 7135 startWithOffset(position, postOffset, duration); 7136 } 7137 }; 7138 return; 7139 } 7140 7141 final int childCount = getChildCount(); 7142 if (childCount == 0) { 7143 // Can't scroll without children. 7144 return; 7145 } 7146 7147 offset += getPaddingTop(); 7148 7149 mTargetPos = Math.max(0, Math.min(getCount() - 1, position)); 7150 mOffsetFromTop = offset; 7151 mBoundPos = INVALID_POSITION; 7152 mLastSeenPos = INVALID_POSITION; 7153 mMode = MOVE_OFFSET; 7154 7155 final int firstPos = mFirstPosition; 7156 final int lastPos = firstPos + childCount - 1; 7157 7158 int viewTravelCount; 7159 if (mTargetPos < firstPos) { 7160 viewTravelCount = firstPos - mTargetPos; 7161 } else if (mTargetPos > lastPos) { 7162 viewTravelCount = mTargetPos - lastPos; 7163 } else { 7164 // On-screen, just scroll. 7165 final int targetTop = getChildAt(mTargetPos - firstPos).getTop(); 7166 smoothScrollBy(targetTop - offset, duration, true); 7167 return; 7168 } 7169 7170 // Estimate how many screens we should travel 7171 final float screenTravelCount = (float) viewTravelCount / childCount; 7172 mScrollDuration = screenTravelCount < 1 ? 7173 duration : (int) (duration / screenTravelCount); 7174 mLastSeenPos = INVALID_POSITION; 7175 7176 postOnAnimation(this); 7177 } 7178 7179 /** 7180 * Scroll such that targetPos is in the visible padded region without scrolling 7181 * boundPos out of view. Assumes targetPos is onscreen. 7182 */ 7183 private void scrollToVisible(int targetPos, int boundPos, int duration) { 7184 final int firstPos = mFirstPosition; 7185 final int childCount = getChildCount(); 7186 final int lastPos = firstPos + childCount - 1; 7187 final int paddedTop = mListPadding.top; 7188 final int paddedBottom = getHeight() - mListPadding.bottom; 7189 7190 if (targetPos < firstPos || targetPos > lastPos) { 7191 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + 7192 " not visible [" + firstPos + ", " + lastPos + "]"); 7193 } 7194 if (boundPos < firstPos || boundPos > lastPos) { 7195 // boundPos doesn't matter, it's already offscreen. 7196 boundPos = INVALID_POSITION; 7197 } 7198 7199 final View targetChild = getChildAt(targetPos - firstPos); 7200 final int targetTop = targetChild.getTop(); 7201 final int targetBottom = targetChild.getBottom(); 7202 int scrollBy = 0; 7203 7204 if (targetBottom > paddedBottom) { 7205 scrollBy = targetBottom - paddedBottom; 7206 } 7207 if (targetTop < paddedTop) { 7208 scrollBy = targetTop - paddedTop; 7209 } 7210 7211 if (scrollBy == 0) { 7212 return; 7213 } 7214 7215 if (boundPos >= 0) { 7216 final View boundChild = getChildAt(boundPos - firstPos); 7217 final int boundTop = boundChild.getTop(); 7218 final int boundBottom = boundChild.getBottom(); 7219 final int absScroll = Math.abs(scrollBy); 7220 7221 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { 7222 // Don't scroll the bound view off the bottom of the screen. 7223 scrollBy = Math.max(0, boundBottom - paddedBottom); 7224 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { 7225 // Don't scroll the bound view off the top of the screen. 7226 scrollBy = Math.min(0, boundTop - paddedTop); 7227 } 7228 } 7229 7230 smoothScrollBy(scrollBy, duration); 7231 } 7232 7233 @Override stop()7234 public void stop() { 7235 removeCallbacks(this); 7236 } 7237 7238 @Override run()7239 public void run() { 7240 final int listHeight = getHeight(); 7241 final int firstPos = mFirstPosition; 7242 7243 switch (mMode) { 7244 case MOVE_DOWN_POS: { 7245 final int lastViewIndex = getChildCount() - 1; 7246 final int lastPos = firstPos + lastViewIndex; 7247 7248 if (lastViewIndex < 0) { 7249 return; 7250 } 7251 7252 if (lastPos == mLastSeenPos) { 7253 // No new views, let things keep going. 7254 postOnAnimation(this); 7255 return; 7256 } 7257 7258 final View lastView = getChildAt(lastViewIndex); 7259 final int lastViewHeight = lastView.getHeight(); 7260 final int lastViewTop = lastView.getTop(); 7261 final int lastViewPixelsShowing = listHeight - lastViewTop; 7262 final int extraScroll = lastPos < mItemCount - 1 ? 7263 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom; 7264 7265 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll; 7266 smoothScrollBy(scrollBy, mScrollDuration, true); 7267 7268 mLastSeenPos = lastPos; 7269 if (lastPos < mTargetPos) { 7270 postOnAnimation(this); 7271 } 7272 break; 7273 } 7274 7275 case MOVE_DOWN_BOUND: { 7276 final int nextViewIndex = 1; 7277 final int childCount = getChildCount(); 7278 7279 if (firstPos == mBoundPos || childCount <= nextViewIndex 7280 || firstPos + childCount >= mItemCount) { 7281 return; 7282 } 7283 final int nextPos = firstPos + nextViewIndex; 7284 7285 if (nextPos == mLastSeenPos) { 7286 // No new views, let things keep going. 7287 postOnAnimation(this); 7288 return; 7289 } 7290 7291 final View nextView = getChildAt(nextViewIndex); 7292 final int nextViewHeight = nextView.getHeight(); 7293 final int nextViewTop = nextView.getTop(); 7294 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll); 7295 if (nextPos < mBoundPos) { 7296 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), 7297 mScrollDuration, true); 7298 7299 mLastSeenPos = nextPos; 7300 7301 postOnAnimation(this); 7302 } else { 7303 if (nextViewTop > extraScroll) { 7304 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true); 7305 } 7306 } 7307 break; 7308 } 7309 7310 case MOVE_UP_POS: { 7311 if (firstPos == mLastSeenPos) { 7312 // No new views, let things keep going. 7313 postOnAnimation(this); 7314 return; 7315 } 7316 7317 final View firstView = getChildAt(0); 7318 if (firstView == null) { 7319 return; 7320 } 7321 final int firstViewTop = firstView.getTop(); 7322 final int extraScroll = firstPos > 0 ? 7323 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top; 7324 7325 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true); 7326 7327 mLastSeenPos = firstPos; 7328 7329 if (firstPos > mTargetPos) { 7330 postOnAnimation(this); 7331 } 7332 break; 7333 } 7334 7335 case MOVE_UP_BOUND: { 7336 final int lastViewIndex = getChildCount() - 2; 7337 if (lastViewIndex < 0) { 7338 return; 7339 } 7340 final int lastPos = firstPos + lastViewIndex; 7341 7342 if (lastPos == mLastSeenPos) { 7343 // No new views, let things keep going. 7344 postOnAnimation(this); 7345 return; 7346 } 7347 7348 final View lastView = getChildAt(lastViewIndex); 7349 final int lastViewHeight = lastView.getHeight(); 7350 final int lastViewTop = lastView.getTop(); 7351 final int lastViewPixelsShowing = listHeight - lastViewTop; 7352 final int extraScroll = Math.max(mListPadding.top, mExtraScroll); 7353 mLastSeenPos = lastPos; 7354 if (lastPos > mBoundPos) { 7355 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true); 7356 postOnAnimation(this); 7357 } else { 7358 final int bottom = listHeight - extraScroll; 7359 final int lastViewBottom = lastViewTop + lastViewHeight; 7360 if (bottom > lastViewBottom) { 7361 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true); 7362 } 7363 } 7364 break; 7365 } 7366 7367 case MOVE_OFFSET: { 7368 if (mLastSeenPos == firstPos) { 7369 // No new views, let things keep going. 7370 postOnAnimation(this); 7371 return; 7372 } 7373 7374 mLastSeenPos = firstPos; 7375 7376 final int childCount = getChildCount(); 7377 final int position = mTargetPos; 7378 final int lastPos = firstPos + childCount - 1; 7379 7380 int viewTravelCount = 0; 7381 if (position < firstPos) { 7382 viewTravelCount = firstPos - position + 1; 7383 } else if (position > lastPos) { 7384 viewTravelCount = position - lastPos; 7385 } 7386 7387 // Estimate how many screens we should travel 7388 final float screenTravelCount = (float) viewTravelCount / childCount; 7389 7390 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f); 7391 if (position < firstPos) { 7392 final int distance = (int) (-getHeight() * modifier); 7393 final int duration = (int) (mScrollDuration * modifier); 7394 smoothScrollBy(distance, duration, true); 7395 postOnAnimation(this); 7396 } else if (position > lastPos) { 7397 final int distance = (int) (getHeight() * modifier); 7398 final int duration = (int) (mScrollDuration * modifier); 7399 smoothScrollBy(distance, duration, true); 7400 postOnAnimation(this); 7401 } else { 7402 // On-screen, just scroll. 7403 final int targetTop = getChildAt(position - firstPos).getTop(); 7404 final int distance = targetTop - mOffsetFromTop; 7405 final int duration = (int) (mScrollDuration * 7406 ((float) Math.abs(distance) / getHeight())); 7407 smoothScrollBy(distance, duration, true); 7408 } 7409 break; 7410 } 7411 7412 default: 7413 break; 7414 } 7415 } 7416 } 7417 } 7418