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