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