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