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