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