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