blob: d2aef0ac362c4b67895bbdbcbbd1f9303d5e0e80 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Tor Norbye80756e32015-03-02 09:39:27 -080019import android.annotation.ColorInt;
Tor Norbye7b9c9122013-05-30 16:48:33 -070020import android.annotation.DrawableRes;
Siva Velusamy94a6d152015-05-05 15:07:00 -070021import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070023import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070029import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.os.Debug;
Yohei Yukawa612cce92016-02-11 17:47:33 -080031import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.os.Parcel;
33import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080034import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070035import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070037import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.text.TextUtils;
39import android.text.TextWatcher;
40import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080041import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070042import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080043import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070044import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070045import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070046import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070047import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import android.view.Gravity;
49import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080050import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.view.KeyEvent;
52import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070053import android.view.Menu;
54import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import android.view.MotionEvent;
56import android.view.VelocityTracker;
57import android.view.View;
58import android.view.ViewConfiguration;
59import android.view.ViewDebug;
60import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070061import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070062import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070064import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070065import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070066import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070067import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080068import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070069import android.view.animation.Interpolator;
70import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070071import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070072import android.view.inputmethod.CompletionInfo;
73import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070074import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070075import android.view.inputmethod.ExtractedText;
76import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070077import android.view.inputmethod.InputConnection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070079import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080
Adam Cohen335c3b62012-07-24 17:18:16 -070081import com.android.internal.R;
82
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083import java.util.ArrayList;
84import java.util.List;
85
86/**
Romain Guyd6a463a2009-05-21 23:10:10 -070087 * Base class that can be used to implement virtualized lists of items. A list does
88 * not have a spatial definition here. For instance, subclases of this class can
89 * display the content of the list in a grid, in a carousel, as stack, etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 *
91 * @attr ref android.R.styleable#AbsListView_listSelector
92 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
93 * @attr ref android.R.styleable#AbsListView_stackFromBottom
94 * @attr ref android.R.styleable#AbsListView_scrollingCache
95 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
96 * @attr ref android.R.styleable#AbsListView_transcriptMode
97 * @attr ref android.R.styleable#AbsListView_cacheColorHint
98 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
99 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -0700100 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 */
102public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
103 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700104 ViewTreeObserver.OnTouchModeChangeListener,
105 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106
Romain Guy9d849a22012-03-14 16:41:42 -0700107 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800108 private static final String TAG = "AbsListView";
109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 /**
111 * Disables the transcript mode.
112 *
113 * @see #setTranscriptMode(int)
114 */
115 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 /**
118 * The list will automatically scroll to the bottom when a data set change
119 * notification is received and only if the last item is already visible
120 * on screen.
121 *
122 * @see #setTranscriptMode(int)
123 */
124 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700125
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 /**
127 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700128 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 *
130 * @see #setTranscriptMode(int)
131 */
132 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
133
134 /**
135 * Indicates that we are not in the middle of a touch gesture
136 */
137 static final int TOUCH_MODE_REST = -1;
138
139 /**
140 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
141 * scroll gesture.
142 */
143 static final int TOUCH_MODE_DOWN = 0;
144
145 /**
146 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
147 * is a longpress
148 */
149 static final int TOUCH_MODE_TAP = 1;
150
151 /**
152 * Indicates we have waited for everything we can wait for, but the user's finger is still down
153 */
154 static final int TOUCH_MODE_DONE_WAITING = 2;
155
156 /**
157 * Indicates the touch gesture is a scroll
158 */
159 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800160
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 /**
162 * Indicates the view is in the process of being flung
163 */
164 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 /**
Adam Powell637d3372010-08-25 14:37:03 -0700167 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
168 */
169 static final int TOUCH_MODE_OVERSCROLL = 5;
170
171 /**
172 * Indicates the view is being flung outside of normal content bounds
173 * and will spring back.
174 */
175 static final int TOUCH_MODE_OVERFLING = 6;
176
177 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 * Regular layout - usually an unsolicited layout from the view system
179 */
180 static final int LAYOUT_NORMAL = 0;
181
182 /**
183 * Show the first item
184 */
185 static final int LAYOUT_FORCE_TOP = 1;
186
187 /**
188 * Force the selected item to be on somewhere on the screen
189 */
190 static final int LAYOUT_SET_SELECTION = 2;
191
192 /**
193 * Show the last item
194 */
195 static final int LAYOUT_FORCE_BOTTOM = 3;
196
197 /**
198 * Make a mSelectedItem appear in a specific location and build the rest of
199 * the views from there. The top is specified by mSpecificTop.
200 */
201 static final int LAYOUT_SPECIFIC = 4;
202
203 /**
204 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
205 * at mSpecificTop
206 */
207 static final int LAYOUT_SYNC = 5;
208
209 /**
210 * Layout as a result of using the navigation keys
211 */
212 static final int LAYOUT_MOVE_SELECTION = 6;
213
214 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700215 * Normal list that does not indicate choices
216 */
217 public static final int CHOICE_MODE_NONE = 0;
218
219 /**
220 * The list allows up to one choice
221 */
222 public static final int CHOICE_MODE_SINGLE = 1;
223
224 /**
225 * The list allows multiple choices
226 */
227 public static final int CHOICE_MODE_MULTIPLE = 2;
228
229 /**
230 * The list allows multiple choices in a modal selection mode
231 */
232 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
233
234 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700235 * The thread that created this view.
236 */
237 private final Thread mOwnerThread;
238
239 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700240 * Controls if/how the user may choose/check items in the list
241 */
242 int mChoiceMode = CHOICE_MODE_NONE;
243
244 /**
245 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
246 */
247 ActionMode mChoiceActionMode;
248
249 /**
250 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
251 * a few extra actions around what application code does.
252 */
253 MultiChoiceModeWrapper mMultiChoiceModeCallback;
254
255 /**
256 * Running count of how many items are currently checked
257 */
258 int mCheckedItemCount;
259
260 /**
261 * Running state of which positions are currently checked
262 */
263 SparseBooleanArray mCheckStates;
264
265 /**
Adam Powell14c08042011-10-06 19:46:18 -0700266 * Running state of which IDs are currently checked.
267 * If there is a value for a given key, the checked state for that ID is true
268 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700269 */
Adam Powell14c08042011-10-06 19:46:18 -0700270 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700271
272 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 * Controls how the next layout will happen
274 */
275 int mLayoutMode = LAYOUT_NORMAL;
276
277 /**
278 * Should be used by subclasses to listen to changes in the dataset
279 */
280 AdapterDataSetObserver mDataSetObserver;
281
282 /**
283 * The adapter containing the data to be displayed by this view
284 */
285 ListAdapter mAdapter;
286
287 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700288 * The remote adapter containing the data to be displayed by this view to be set
289 */
290 private RemoteViewsAdapter mRemoteAdapter;
291
292 /**
Adam Powell539ee872012-02-03 19:00:49 -0800293 * If mAdapter != null, whenever this is true the adapter has stable IDs.
294 */
295 boolean mAdapterHasStableIds;
296
297 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700298 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
299 */
300 private boolean mDeferNotifyDataSetChanged = false;
301
302 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 * Indicates whether the list selector should be drawn on top of the children or behind
304 */
305 boolean mDrawSelectorOnTop = false;
306
307 /**
308 * The drawable used to draw the selector
309 */
310 Drawable mSelector;
311
312 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700313 * The current position of the selector in the list.
314 */
315 int mSelectorPosition = INVALID_POSITION;
316
317 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 * Defines the selector's location and dimension at drawing time
319 */
320 Rect mSelectorRect = new Rect();
321
322 /**
323 * The data set used to store unused views that should be reused during the next layout
324 * to avoid creating new ones
325 */
326 final RecycleBin mRecycler = new RecycleBin();
327
328 /**
329 * The selection's left padding
330 */
331 int mSelectionLeftPadding = 0;
332
333 /**
334 * The selection's top padding
335 */
336 int mSelectionTopPadding = 0;
337
338 /**
339 * The selection's right padding
340 */
341 int mSelectionRightPadding = 0;
342
343 /**
344 * The selection's bottom padding
345 */
346 int mSelectionBottomPadding = 0;
347
348 /**
349 * This view's padding
350 */
351 Rect mListPadding = new Rect();
352
353 /**
354 * Subclasses must retain their measure spec from onMeasure() into this member
355 */
356 int mWidthMeasureSpec = 0;
357
358 /**
359 * The top scroll indicator
360 */
361 View mScrollUp;
362
363 /**
364 * The down scroll indicator
365 */
366 View mScrollDown;
367
368 /**
369 * When the view is scrolling, this flag is set to true to indicate subclasses that
370 * the drawing cache was enabled on the children
371 */
372 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800373 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374
375 /**
376 * The position of the view that received the down motion event
377 */
378 int mMotionPosition;
379
380 /**
381 * The offset to the top of the mMotionPosition view when the down motion event was received
382 */
383 int mMotionViewOriginalTop;
384
385 /**
386 * The desired offset to the top of the mMotionPosition view after a scroll
387 */
388 int mMotionViewNewTop;
389
390 /**
391 * The X value associated with the the down motion event
392 */
393 int mMotionX;
394
395 /**
396 * The Y value associated with the the down motion event
397 */
398 int mMotionY;
399
400 /**
401 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
402 * TOUCH_MODE_DONE_WAITING
403 */
404 int mTouchMode = TOUCH_MODE_REST;
405
406 /**
407 * Y value from on the previous motion event (if any)
408 */
409 int mLastY;
410
411 /**
412 * How far the finger moved before we started scrolling
413 */
414 int mMotionCorrection;
415
416 /**
417 * Determines speed during touch scrolling
418 */
419 private VelocityTracker mVelocityTracker;
420
421 /**
422 * Handles one frame of a fling
423 */
424 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800425
Adam Powell45803472010-01-25 15:10:44 -0800426 /**
427 * Handles scrolling between positions within the list.
428 */
Alan Viveretted22db212014-02-13 17:47:38 -0800429 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430
431 /**
432 * The offset in pixels form the top of the AdapterView to the top
433 * of the currently selected view. Used to save and restore state.
434 */
435 int mSelectedTop = 0;
436
437 /**
438 * Indicates whether the list is stacked from the bottom edge or
439 * the top edge.
440 */
441 boolean mStackFromBottom;
442
443 /**
444 * When set to true, the list automatically discards the children's
445 * bitmap cache after scrolling.
446 */
447 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 /**
450 * Whether or not to enable the fast scroll feature on this list
451 */
452 boolean mFastScrollEnabled;
453
454 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700455 * Whether or not to always show the fast scroll feature on this list
456 */
457 boolean mFastScrollAlwaysVisible;
458
459 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 * Optional callback to notify client when scroll position has changed
461 */
462 private OnScrollListener mOnScrollListener;
463
464 /**
465 * Keeps track of our accessory window
466 */
467 PopupWindow mPopup;
468
469 /**
470 * Used with type filter window
471 */
472 EditText mTextFilter;
473
474 /**
475 * Indicates whether to use pixels-based or position-based scrollbar
476 * properties.
477 */
478 private boolean mSmoothScrollbarEnabled = true;
479
480 /**
481 * Indicates that this view supports filtering
482 */
483 private boolean mTextFilterEnabled;
484
485 /**
486 * Indicates that this view is currently displaying a filtered view of the data
487 */
488 private boolean mFiltered;
489
490 /**
491 * Rectangle used for hit testing children
492 */
493 private Rect mTouchFrame;
494
495 /**
496 * The position to resurrect the selected position to.
497 */
498 int mResurrectToPosition = INVALID_POSITION;
499
500 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800501
Adam Powell0b8bb422010-02-08 14:30:45 -0800502 /**
Adam Powell637d3372010-08-25 14:37:03 -0700503 * Maximum distance to record overscroll
504 */
505 int mOverscrollMax;
506
507 /**
508 * Content height divided by this is the overscroll limit.
509 */
510 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
511
512 /**
Adam Powell14c08042011-10-06 19:46:18 -0700513 * How many positions in either direction we will search to try to
514 * find a checked item with a stable ID that moved position across
515 * a data set change. If the item isn't found it will be unselected.
516 */
517 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
518
519 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 * Used to request a layout when we changed touch mode
521 */
522 private static final int TOUCH_MODE_UNKNOWN = -1;
523 private static final int TOUCH_MODE_ON = 0;
524 private static final int TOUCH_MODE_OFF = 1;
525
526 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
527
528 private static final boolean PROFILE_SCROLLING = false;
529 private boolean mScrollProfilingStarted = false;
530
531 private static final boolean PROFILE_FLINGING = false;
532 private boolean mFlingProfilingStarted = false;
533
534 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800535 * The StrictMode "critical time span" objects to catch animation
536 * stutters. Non-null when a time-sensitive animation is
537 * in-flight. Must call finish() on them when done animating.
538 * These are no-ops on user builds.
539 */
540 private StrictMode.Span mScrollStrictSpan = null;
541 private StrictMode.Span mFlingStrictSpan = null;
542
543 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 * The last CheckForLongPress runnable we posted, if any
545 */
546 private CheckForLongPress mPendingCheckForLongPress;
547
548 /**
549 * The last CheckForTap runnable we posted, if any
550 */
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700551 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700552
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 /**
554 * The last CheckForKeyLongPress runnable we posted, if any
555 */
556 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
557
558 /**
559 * Acts upon click
560 */
561 private AbsListView.PerformClick mPerformClick;
562
563 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800564 * Delayed action for touch mode.
565 */
566 private Runnable mTouchModeReset;
567
568 /**
Alan Viverette66df60f2016-01-28 14:56:07 -0500569 * Whether the most recent touch event stream resulted in a successful
570 * long-press action. This is reset on TOUCH_DOWN.
571 */
572 private boolean mHasPerformedLongPress;
573
574 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 * This view is in transcript mode -- it shows the bottom of the list when the data
576 * changes
577 */
578 private int mTranscriptMode;
579
580 /**
581 * Indicates that this list is always drawn on top of a solid, single-color, opaque
582 * background
583 */
584 private int mCacheColorHint;
585
586 /**
587 * The select child's view (from the adapter's getView) is enabled.
588 */
589 private boolean mIsChildViewEnabled;
590
591 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800592 * The cached drawable state for the selector. Accounts for child enabled
593 * state, but otherwise identical to the view's own drawable state.
594 */
595 private int[] mSelectorState;
596
597 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800598 * The last scroll state reported to clients through {@link OnScrollListener}.
599 */
600 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
601
602 /**
603 * Helper object that renders and controls the fast scroll thumb.
604 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700605 private FastScroller mFastScroll;
606
607 /**
608 * Temporary holder for fast scroller style until a FastScroller object
609 * is created.
610 */
611 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612
Romain Guyd6a463a2009-05-21 23:10:10 -0700613 private boolean mGlobalLayoutListenerAddedFilter;
614
615 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 private float mDensityScale;
617
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700618 private InputConnection mDefInputConnection;
619 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700620
621 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700622 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700623 private int mMinimumVelocity;
624 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700625 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800626
Romain Guy21875052010-01-06 18:48:08 -0800627 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800628
Adam Powell96d62af2014-05-02 10:04:38 -0700629 private final int[] mScrollOffset = new int[2];
630 private final int[] mScrollConsumed = new int[2];
631
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800632 private final float[] mTmpPoint = new float[2];
633
Adam Powell744beff2014-09-22 09:47:48 -0700634 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
635 // In the future it would be nice to be able to give this to the VelocityTracker
636 // directly, or alternatively put a VT into absolute-positioning mode that only
637 // reads the raw screen-coordinate x/y values.
638 private int mNestedYOffset = 0;
639
Romain Guy24562482010-02-01 14:56:19 -0800640 // True when the popup should be hidden because of a call to
641 // dispatchDisplayHint()
642 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800643
Adam Powell4cd47702010-02-25 11:21:14 -0800644 /**
645 * ID of the active pointer. This is used to retain consistency during
646 * drags/flings if multiple pointers are used.
647 */
648 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800649
Adam Powell4cd47702010-02-25 11:21:14 -0800650 /**
651 * Sentinel value for no current active pointer.
652 * Used by {@link #mActivePointerId}.
653 */
654 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700655
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 /**
Adam Powell637d3372010-08-25 14:37:03 -0700657 * Maximum distance to overscroll by during edge effects
658 */
659 int mOverscrollDistance;
660
661 /**
662 * Maximum distance to overfling during edge effects
663 */
664 int mOverflingDistance;
665
666 // These two EdgeGlows are always set and used together.
667 // Checking one for null is as good as checking both.
668
669 /**
670 * Tracks the state of the top edge glow.
671 */
Adam Powell89935e42011-08-31 14:26:12 -0700672 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700673
674 /**
675 * Tracks the state of the bottom edge glow.
676 */
Adam Powell89935e42011-08-31 14:26:12 -0700677 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700678
679 /**
680 * An estimate of how many pixels are between the top of the list and
681 * the top of the first position in the adapter, based on the last time
682 * we saw it. Used to hint where to draw edge glows.
683 */
684 private int mFirstPositionDistanceGuess;
685
686 /**
687 * An estimate of how many pixels are between the bottom of the list and
688 * the bottom of the last position in the adapter, based on the last time
689 * we saw it. Used to hint where to draw edge glows.
690 */
691 private int mLastPositionDistanceGuess;
692
693 /**
694 * Used for determining when to cancel out of overscroll.
695 */
696 private int mDirection = 0;
697
698 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800699 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
700 * the bottom correctly on resizes.
701 */
702 private boolean mForceTranscriptScroll;
703
alanvc1d7e772012-05-08 14:47:24 -0700704 /**
705 * Used for interacting with list items from an accessibility service.
706 */
707 private ListItemAccessibilityDelegate mAccessibilityDelegate;
708
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700709 private int mLastAccessibilityScrollEventFromIndex;
710 private int mLastAccessibilityScrollEventToIndex;
711
Adam Powellda13dba2010-12-05 13:47:23 -0800712 /**
Adam Powellee78b172011-08-16 16:39:20 -0700713 * Track the item count from the last time we handled a data change.
714 */
715 private int mLastHandledItemCount;
716
717 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700718 * Used for smooth scrolling at a consistent rate
719 */
720 static final Interpolator sLinearInterpolator = new LinearInterpolator();
721
722 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700723 * The saved state that we will be restoring from when we next sync.
724 * Kept here so that if we happen to be asked to save our state before
725 * the sync happens, we can return this existing data rather than losing
726 * it.
727 */
728 private SavedState mPendingSync;
729
730 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800731 * Whether the view is in the process of detaching from its window.
732 */
733 private boolean mIsDetaching;
734
735 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 * Interface definition for a callback to be invoked when the list or grid
737 * has been scrolled.
738 */
739 public interface OnScrollListener {
740
741 /**
742 * The view is not scrolling. Note navigating the list using the trackball counts as
743 * being in the idle state since these transitions are not animated.
744 */
745 public static int SCROLL_STATE_IDLE = 0;
746
747 /**
748 * The user is scrolling using touch, and their finger is still on the screen
749 */
750 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
751
752 /**
753 * The user had previously been scrolling using touch and had performed a fling. The
754 * animation is now coasting to a stop
755 */
756 public static int SCROLL_STATE_FLING = 2;
757
758 /**
759 * Callback method to be invoked while the list view or grid view is being scrolled. If the
760 * view is being scrolled, this method will be called before the next frame of the scroll is
761 * rendered. In particular, it will be called before any calls to
762 * {@link Adapter#getView(int, View, ViewGroup)}.
763 *
764 * @param view The view whose scroll state is being reported
765 *
Yorke Lee43943d82014-05-08 10:15:20 -0700766 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
768 */
769 public void onScrollStateChanged(AbsListView view, int scrollState);
770
771 /**
772 * Callback method to be invoked when the list or grid has been scrolled. This will be
773 * called after the scroll has completed
774 * @param view The view whose scroll state is being reported
775 * @param firstVisibleItem the index of the first visible cell (ignore if
776 * visibleItemCount == 0)
777 * @param visibleItemCount the number of visible cells
778 * @param totalItemCount the number of items in the list adaptor
779 */
780 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
781 int totalItemCount);
782 }
783
Dianne Hackborne2136772010-11-04 15:08:59 -0700784 /**
785 * The top-level view of a list item can implement this interface to allow
786 * itself to modify the bounds of the selection shown for that item.
787 */
788 public interface SelectionBoundsAdjuster {
789 /**
790 * Called to allow the list item to adjust the bounds shown for
791 * its selection.
792 *
793 * @param bounds On call, this contains the bounds the list has
794 * selected for the item (that is the bounds of the entire view). The
795 * values can be modified as desired.
796 */
797 public void adjustListItemSelectionBounds(Rect bounds);
798 }
799
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 public AbsListView(Context context) {
801 super(context);
802 initAbsListView();
803
Alan Viverette39bed692013-08-07 15:47:04 -0700804 mOwnerThread = Thread.currentThread();
805
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 setVerticalScrollBarEnabled(true);
807 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700808 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 a.recycle();
810 }
811
812 public AbsListView(Context context, AttributeSet attrs) {
813 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
814 }
815
Alan Viverette617feb92013-09-09 18:09:13 -0700816 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
817 this(context, attrs, defStyleAttr, 0);
818 }
819
820 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
821 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 initAbsListView();
823
Alan Viverette39bed692013-08-07 15:47:04 -0700824 mOwnerThread = Thread.currentThread();
825
Alan Viverette617feb92013-09-09 18:09:13 -0700826 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700827 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828
Alan Viverette7eceda32015-06-01 10:47:29 -0700829 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
830 if (selector != null) {
831 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 }
833
Alan Viverette7eceda32015-06-01 10:47:29 -0700834 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835
Alan Viverette7eceda32015-06-01 10:47:29 -0700836 setStackFromBottom(a.getBoolean(
837 R.styleable.AbsListView_stackFromBottom, false));
838 setScrollingCacheEnabled(a.getBoolean(
839 R.styleable.AbsListView_scrollingCache, true));
840 setTextFilterEnabled(a.getBoolean(
841 R.styleable.AbsListView_textFilterEnabled, false));
842 setTranscriptMode(a.getInt(
843 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
844 setCacheColorHint(a.getColor(
845 R.styleable.AbsListView_cacheColorHint, 0));
846 setSmoothScrollbarEnabled(a.getBoolean(
847 R.styleable.AbsListView_smoothScrollbar, true));
848 setChoiceMode(a.getInt(
849 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850
Alan Viverette7eceda32015-06-01 10:47:29 -0700851 setFastScrollEnabled(a.getBoolean(
852 R.styleable.AbsListView_fastScrollEnabled, false));
853 setFastScrollStyle(a.getResourceId(
854 R.styleable.AbsListView_fastScrollStyle, 0));
855 setFastScrollAlwaysVisible(a.getBoolean(
856 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700857
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 a.recycle();
859 }
860
Romain Guyd6a463a2009-05-21 23:10:10 -0700861 private void initAbsListView() {
862 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700863 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700864 setFocusableInTouchMode(true);
865 setWillNotDraw(false);
866 setAlwaysDrawnWithCacheEnabled(false);
867 setScrollingCacheEnabled(true);
868
Romain Guy4296fc42009-07-06 11:48:52 -0700869 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
870 mTouchSlop = configuration.getScaledTouchSlop();
871 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
872 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700873 mOverscrollDistance = configuration.getScaledOverscrollDistance();
874 mOverflingDistance = configuration.getScaledOverflingDistance();
875
Romain Guyd6a463a2009-05-21 23:10:10 -0700876 mDensityScale = getContext().getResources().getDisplayMetrics().density;
877 }
Romain Guy0a637162009-05-29 14:43:54 -0700878
Adam Powell637d3372010-08-25 14:37:03 -0700879 @Override
880 public void setOverScrollMode(int mode) {
881 if (mode != OVER_SCROLL_NEVER) {
882 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800883 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700884 mEdgeGlowTop = new EdgeEffect(context);
885 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700886 }
887 } else {
888 mEdgeGlowTop = null;
889 mEdgeGlowBottom = null;
890 }
891 super.setOverScrollMode(mode);
892 }
893
Romain Guyd6a463a2009-05-21 23:10:10 -0700894 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700895 * {@inheritDoc}
896 */
897 @Override
898 public void setAdapter(ListAdapter adapter) {
899 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800900 mAdapterHasStableIds = mAdapter.hasStableIds();
901 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700902 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700903 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700904 }
905 }
906
907 if (mCheckStates != null) {
908 mCheckStates.clear();
909 }
910
911 if (mCheckedIdStates != null) {
912 mCheckedIdStates.clear();
913 }
914 }
915
916 /**
917 * Returns the number of items currently selected. This will only be valid
918 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
919 *
920 * <p>To determine the specific items that are currently selected, use one of
921 * the <code>getChecked*</code> methods.
922 *
923 * @return The number of items currently selected
924 *
925 * @see #getCheckedItemPosition()
926 * @see #getCheckedItemPositions()
927 * @see #getCheckedItemIds()
928 */
929 public int getCheckedItemCount() {
930 return mCheckedItemCount;
931 }
932
933 /**
934 * Returns the checked state of the specified position. The result is only
935 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
936 * or {@link #CHOICE_MODE_MULTIPLE}.
937 *
938 * @param position The item whose checked state to return
939 * @return The item's checked state or <code>false</code> if choice mode
940 * is invalid
941 *
942 * @see #setChoiceMode(int)
943 */
944 public boolean isItemChecked(int position) {
945 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
946 return mCheckStates.get(position);
947 }
948
949 return false;
950 }
951
952 /**
953 * Returns the currently checked item. The result is only valid if the choice
954 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
955 *
956 * @return The position of the currently checked item or
957 * {@link #INVALID_POSITION} if nothing is selected
958 *
959 * @see #setChoiceMode(int)
960 */
961 public int getCheckedItemPosition() {
962 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
963 return mCheckStates.keyAt(0);
964 }
965
966 return INVALID_POSITION;
967 }
968
969 /**
970 * Returns the set of checked items in the list. The result is only valid if
971 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
972 *
973 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200974 * get(int position) where position is a checked position in the
975 * list and false otherwise, or <code>null</code> if the choice
976 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700977 */
978 public SparseBooleanArray getCheckedItemPositions() {
979 if (mChoiceMode != CHOICE_MODE_NONE) {
980 return mCheckStates;
981 }
982 return null;
983 }
984
985 /**
986 * Returns the set of checked items ids. The result is only valid if the
987 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
988 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
989 *
990 * @return A new array which contains the id of each checked item in the
991 * list.
992 */
993 public long[] getCheckedItemIds() {
994 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
995 return new long[0];
996 }
997
Adam Powell14c08042011-10-06 19:46:18 -0700998 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700999 final int count = idStates.size();
1000 final long[] ids = new long[count];
1001
1002 for (int i = 0; i < count; i++) {
1003 ids[i] = idStates.keyAt(i);
1004 }
1005
1006 return ids;
1007 }
1008
1009 /**
1010 * Clear any choices previously set
1011 */
1012 public void clearChoices() {
1013 if (mCheckStates != null) {
1014 mCheckStates.clear();
1015 }
1016 if (mCheckedIdStates != null) {
1017 mCheckedIdStates.clear();
1018 }
1019 mCheckedItemCount = 0;
1020 }
1021
1022 /**
1023 * Sets the checked state of the specified position. The is only valid if
1024 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1025 * {@link #CHOICE_MODE_MULTIPLE}.
1026 *
1027 * @param position The item whose checked state is to be checked
1028 * @param value The new checked state for the item
1029 */
1030 public void setItemChecked(int position, boolean value) {
1031 if (mChoiceMode == CHOICE_MODE_NONE) {
1032 return;
1033 }
1034
1035 // Start selection mode if needed. We don't need to if we're unchecking something.
1036 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001037 if (mMultiChoiceModeCallback == null ||
1038 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1039 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1040 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1041 "supplied. Call setMultiChoiceModeListener to set a callback.");
1042 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001043 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1044 }
1045
Siyamed Sinir135554e2016-01-22 18:40:42 -08001046 final boolean itemCheckChanged;
Adam Powellf343e1b2010-08-13 18:27:04 -07001047 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1048 boolean oldValue = mCheckStates.get(position);
1049 mCheckStates.put(position, value);
1050 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1051 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001052 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001053 } else {
1054 mCheckedIdStates.delete(mAdapter.getItemId(position));
1055 }
1056 }
Siyamed Sinir135554e2016-01-22 18:40:42 -08001057 itemCheckChanged = oldValue != value;
1058 if (itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001059 if (value) {
1060 mCheckedItemCount++;
1061 } else {
1062 mCheckedItemCount--;
1063 }
1064 }
1065 if (mChoiceActionMode != null) {
1066 final long id = mAdapter.getItemId(position);
1067 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1068 position, id, value);
1069 }
1070 } else {
1071 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1072 // Clear all values if we're checking something, or unchecking the currently
1073 // selected item
Siyamed Sinir135554e2016-01-22 18:40:42 -08001074 itemCheckChanged = isItemChecked(position) != value;
Adam Powellf343e1b2010-08-13 18:27:04 -07001075 if (value || isItemChecked(position)) {
1076 mCheckStates.clear();
1077 if (updateIds) {
1078 mCheckedIdStates.clear();
1079 }
1080 }
1081 // this may end up selecting the value we just cleared but this way
1082 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1083 if (value) {
1084 mCheckStates.put(position, true);
1085 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001086 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001087 }
1088 mCheckedItemCount = 1;
1089 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1090 mCheckedItemCount = 0;
1091 }
1092 }
1093
Siyamed Sinir135554e2016-01-22 18:40:42 -08001094 // Do not generate a data change while we are in the layout phase or data has not changed
1095 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001096 mDataChanged = true;
1097 rememberSyncState();
1098 requestLayout();
1099 }
1100 }
1101
1102 @Override
1103 public boolean performItemClick(View view, int position, long id) {
1104 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001105 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001106
1107 if (mChoiceMode != CHOICE_MODE_NONE) {
1108 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001109 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001110
1111 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1112 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001113 boolean checked = !mCheckStates.get(position, false);
1114 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001115 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001116 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001117 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001118 } else {
1119 mCheckedIdStates.delete(mAdapter.getItemId(position));
1120 }
1121 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001122 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001123 mCheckedItemCount++;
1124 } else {
1125 mCheckedItemCount--;
1126 }
1127 if (mChoiceActionMode != null) {
1128 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001129 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001130 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001131 }
Adam Powell29382d92012-02-23 11:03:22 -08001132 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001133 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001134 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001135 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001136 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001137 mCheckStates.put(position, true);
1138 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1139 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001140 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001141 }
1142 mCheckedItemCount = 1;
1143 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1144 mCheckedItemCount = 0;
1145 }
Adam Powell29382d92012-02-23 11:03:22 -08001146 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001147 }
1148
Adam Powell29382d92012-02-23 11:03:22 -08001149 if (checkedStateChanged) {
1150 updateOnScreenCheckedViews();
1151 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001152 }
1153
Adam Powellbf5f2b32010-10-24 16:45:44 -07001154 if (dispatchItemClick) {
1155 handled |= super.performItemClick(view, position, id);
1156 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001157
1158 return handled;
1159 }
1160
1161 /**
Adam Powell29382d92012-02-23 11:03:22 -08001162 * Perform a quick, in-place update of the checked or activated state
1163 * on all visible item views. This should only be called when a valid
1164 * choice mode is active.
1165 */
1166 private void updateOnScreenCheckedViews() {
1167 final int firstPos = mFirstPosition;
1168 final int count = getChildCount();
1169 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1170 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1171 for (int i = 0; i < count; i++) {
1172 final View child = getChildAt(i);
1173 final int position = firstPos + i;
1174
1175 if (child instanceof Checkable) {
1176 ((Checkable) child).setChecked(mCheckStates.get(position));
1177 } else if (useActivated) {
1178 child.setActivated(mCheckStates.get(position));
1179 }
1180 }
1181 }
1182
1183 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001184 * @see #setChoiceMode(int)
1185 *
1186 * @return The current choice mode
1187 */
1188 public int getChoiceMode() {
1189 return mChoiceMode;
1190 }
1191
1192 /**
1193 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1194 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1195 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1196 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1197 *
1198 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1199 * {@link #CHOICE_MODE_MULTIPLE}
1200 */
1201 public void setChoiceMode(int choiceMode) {
1202 mChoiceMode = choiceMode;
1203 if (mChoiceActionMode != null) {
1204 mChoiceActionMode.finish();
1205 mChoiceActionMode = null;
1206 }
1207 if (mChoiceMode != CHOICE_MODE_NONE) {
1208 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001209 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001210 }
1211 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001212 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001213 }
1214 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1215 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1216 clearChoices();
1217 setLongClickable(true);
1218 }
1219 }
1220 }
1221
1222 /**
1223 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1224 * selection {@link ActionMode}. Only used when the choice mode is set to
1225 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1226 *
1227 * @param listener Listener that will manage the selection mode
1228 *
1229 * @see #setChoiceMode(int)
1230 */
1231 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1232 if (mMultiChoiceModeCallback == null) {
1233 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1234 }
1235 mMultiChoiceModeCallback.setWrapped(listener);
1236 }
1237
1238 /**
Adam Powell637d3372010-08-25 14:37:03 -07001239 * @return true if all list content currently fits within the view boundaries
1240 */
1241 private boolean contentFits() {
1242 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001243 if (childCount == 0) return true;
1244 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001245
Adam Powell4ce35412011-01-24 14:55:00 -08001246 return getChildAt(0).getTop() >= mListPadding.top &&
1247 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001248 }
1249
1250 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001251 * Specifies whether fast scrolling is enabled or disabled.
1252 * <p>
1253 * When fast scrolling is enabled, the user can quickly scroll through lists
1254 * by dragging the fast scroll thumb.
1255 * <p>
1256 * If the adapter backing this list implements {@link SectionIndexer}, the
1257 * fast scroller will display section header previews as the user scrolls.
1258 * Additionally, the user will be able to quickly jump between sections by
1259 * tapping along the length of the scroll bar.
1260 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001261 * @see SectionIndexer
1262 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001263 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 */
Alan Viverette39bed692013-08-07 15:47:04 -07001265 public void setFastScrollEnabled(final boolean enabled) {
1266 if (mFastScrollEnabled != enabled) {
1267 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001268
Alan Viverette39bed692013-08-07 15:47:04 -07001269 if (isOwnerThread()) {
1270 setFastScrollerEnabledUiThread(enabled);
1271 } else {
1272 post(new Runnable() {
1273 @Override
1274 public void run() {
1275 setFastScrollerEnabledUiThread(enabled);
1276 }
1277 });
1278 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001279 }
Alan Viverette39bed692013-08-07 15:47:04 -07001280 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001281
Alan Viverette39bed692013-08-07 15:47:04 -07001282 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001283 if (mFastScroll != null) {
1284 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001285 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001286 mFastScroll = new FastScroller(this, mFastScrollStyle);
1287 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001289
Alan Viveretteb9f27222013-09-06 19:39:47 -07001290 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001291
Alan Viverette8636ace2013-10-31 15:41:31 -07001292 if (mFastScroll != null) {
1293 mFastScroll.updateLayout();
1294 }
1295 }
1296
1297 /**
1298 * Specifies the style of the fast scroller decorations.
1299 *
1300 * @param styleResId style resource containing fast scroller properties
1301 * @see android.R.styleable#FastScroll
1302 */
1303 public void setFastScrollStyle(int styleResId) {
1304 if (mFastScroll == null) {
1305 mFastScrollStyle = styleResId;
1306 } else {
1307 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001308 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 }
Romain Guy0a637162009-05-29 14:43:54 -07001310
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001311 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001312 * Set whether or not the fast scroller should always be shown in place of
1313 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001314 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001315 * <p>
1316 * Fast scrollers shown in this way will not fade out and will be a
1317 * permanent fixture within the list. This is best combined with an inset
1318 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001319 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001320 * @param alwaysShow true if the fast scroller should always be displayed,
1321 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001322 * @see #setScrollBarStyle(int)
1323 * @see #setFastScrollEnabled(boolean)
1324 */
Alan Viverette39bed692013-08-07 15:47:04 -07001325 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1326 if (mFastScrollAlwaysVisible != alwaysShow) {
1327 if (alwaysShow && !mFastScrollEnabled) {
1328 setFastScrollEnabled(true);
1329 }
Adam Powell20232d02010-12-08 21:08:53 -08001330
Alan Viverette39bed692013-08-07 15:47:04 -07001331 mFastScrollAlwaysVisible = alwaysShow;
1332
1333 if (isOwnerThread()) {
1334 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1335 } else {
1336 post(new Runnable() {
1337 @Override
1338 public void run() {
1339 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1340 }
1341 });
1342 }
1343 }
1344 }
1345
1346 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001347 if (mFastScroll != null) {
1348 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001349 }
Alan Viverette39bed692013-08-07 15:47:04 -07001350 }
Adam Powell20232d02010-12-08 21:08:53 -08001351
Alan Viverette39bed692013-08-07 15:47:04 -07001352 /**
1353 * @return whether the current thread is the one that created the view
1354 */
1355 private boolean isOwnerThread() {
1356 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001357 }
1358
1359 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001360 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001361 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001362 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001363 * @see #setFastScrollAlwaysVisible(boolean)
1364 */
1365 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001366 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001367 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1368 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001369 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001370 }
Adam Powell20232d02010-12-08 21:08:53 -08001371 }
1372
1373 @Override
1374 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001375 if (mFastScroll != null && mFastScroll.isEnabled()) {
1376 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001377 }
1378 return super.getVerticalScrollbarWidth();
1379 }
1380
1381 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001382 * Returns true if the fast scroller is enabled.
1383 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 * @see #setFastScrollEnabled(boolean)
1385 * @return true if fast scroll is enabled, false otherwise
1386 */
1387 @ViewDebug.ExportedProperty
1388 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001389 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001390 return mFastScrollEnabled;
1391 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001392 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001393 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001394 }
Romain Guy0a637162009-05-29 14:43:54 -07001395
Adam Powell20232d02010-12-08 21:08:53 -08001396 @Override
1397 public void setVerticalScrollbarPosition(int position) {
1398 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001399 if (mFastScroll != null) {
1400 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001401 }
1402 }
1403
Alan Viverette26bb2532013-08-09 10:40:50 -07001404 @Override
1405 public void setScrollBarStyle(int style) {
1406 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001407 if (mFastScroll != null) {
1408 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001409 }
1410 }
1411
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001412 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001413 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001414 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 */
1416 @Override
1417 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001418 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419 }
1420
1421 /**
1422 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1423 * is computed based on the number of visible pixels in the visible items. This
1424 * however assumes that all list items have the same height. If you use a list in
1425 * which items have different heights, the scrollbar will change appearance as the
1426 * user scrolls through the list. To avoid this issue, you need to disable this
1427 * property.
1428 *
1429 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1430 * is based solely on the number of items in the adapter and the position of the
1431 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001432 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001433 *
1434 * @param enabled Whether or not to enable smooth scrollbar.
1435 *
Romain Guy0a637162009-05-29 14:43:54 -07001436 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1438 */
1439 public void setSmoothScrollbarEnabled(boolean enabled) {
1440 mSmoothScrollbarEnabled = enabled;
1441 }
1442
1443 /**
1444 * Returns the current state of the fast scroll feature.
1445 *
1446 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1447 *
1448 * @see #setSmoothScrollbarEnabled(boolean)
1449 */
1450 @ViewDebug.ExportedProperty
1451 public boolean isSmoothScrollbarEnabled() {
1452 return mSmoothScrollbarEnabled;
1453 }
1454
1455 /**
1456 * Set the listener that will receive notifications every time the list scrolls.
1457 *
1458 * @param l the scroll listener
1459 */
1460 public void setOnScrollListener(OnScrollListener l) {
1461 mOnScrollListener = l;
1462 invokeOnItemScrollListener();
1463 }
1464
1465 /**
1466 * Notify our scroll listener (if there is one) of a change in scroll state
1467 */
1468 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001469 if (mFastScroll != null) {
1470 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 }
1472 if (mOnScrollListener != null) {
1473 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1474 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001475 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 }
1477
Alan Viverettea54956a2015-01-07 16:05:02 -08001478 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001479 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001480 public void sendAccessibilityEventInternal(int eventType) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001481 // Since this class calls onScrollChanged even if the mFirstPosition and the
1482 // child count have not changed we will avoid sending duplicate accessibility
1483 // events.
1484 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001485 final int firstVisiblePosition = getFirstVisiblePosition();
1486 final int lastVisiblePosition = getLastVisiblePosition();
1487 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1488 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001489 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001490 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001491 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1492 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001493 }
1494 }
Alan Viverettea54956a2015-01-07 16:05:02 -08001495 super.sendAccessibilityEventInternal(eventType);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001496 }
1497
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001498 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001499 public CharSequence getAccessibilityClassName() {
1500 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001501 }
1502
Alan Viverettea54956a2015-01-07 16:05:02 -08001503 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001504 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001505 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1506 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001507 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001508 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001509 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001510 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001511 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001512 }
Alan Viverette947a9692014-09-25 12:43:47 -07001513 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001514 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001515 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001516 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001517 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001518 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001519
1520 info.removeAction(AccessibilityAction.ACTION_CLICK);
1521 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001522 }
1523
Alan Viverette76769ae2014-02-12 16:38:10 -08001524 int getSelectionModeForAccessibility() {
1525 final int choiceMode = getChoiceMode();
1526 switch (choiceMode) {
1527 case CHOICE_MODE_NONE:
1528 return CollectionInfo.SELECTION_MODE_NONE;
1529 case CHOICE_MODE_SINGLE:
1530 return CollectionInfo.SELECTION_MODE_SINGLE;
1531 case CHOICE_MODE_MULTIPLE:
1532 case CHOICE_MODE_MULTIPLE_MODAL:
1533 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1534 default:
1535 return CollectionInfo.SELECTION_MODE_NONE;
1536 }
1537 }
1538
Alan Viverettea54956a2015-01-07 16:05:02 -08001539 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001540 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001541 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1542 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001543 return true;
1544 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001545 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001546 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1547 case R.id.accessibilityActionScrollDown: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001548 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001549 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1550 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1551 return true;
1552 }
1553 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001554 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1555 case R.id.accessibilityActionScrollUp: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001556 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001557 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1558 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1559 return true;
1560 }
1561 } return false;
1562 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001563 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001564 }
1565
Svetoslav5b578da2013-05-08 14:23:32 -07001566 /** @hide */
1567 @Override
1568 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1569 if (accessibilityId == getAccessibilityViewId()) {
1570 return this;
1571 }
Svetoslav5b578da2013-05-08 14:23:32 -07001572 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1573 }
1574
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001575 /**
1576 * Indicates whether the children's drawing cache is used during a scroll.
1577 * By default, the drawing cache is enabled but this will consume more memory.
1578 *
1579 * @return true if the scrolling cache is enabled, false otherwise
1580 *
1581 * @see #setScrollingCacheEnabled(boolean)
1582 * @see View#setDrawingCacheEnabled(boolean)
1583 */
1584 @ViewDebug.ExportedProperty
1585 public boolean isScrollingCacheEnabled() {
1586 return mScrollingCacheEnabled;
1587 }
1588
1589 /**
1590 * Enables or disables the children's drawing cache during a scroll.
1591 * By default, the drawing cache is enabled but this will use more memory.
1592 *
1593 * When the scrolling cache is enabled, the caches are kept after the
1594 * first scrolling. You can manually clear the cache by calling
1595 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1596 *
1597 * @param enabled true to enable the scroll cache, false otherwise
1598 *
1599 * @see #isScrollingCacheEnabled()
1600 * @see View#setDrawingCacheEnabled(boolean)
1601 */
1602 public void setScrollingCacheEnabled(boolean enabled) {
1603 if (mScrollingCacheEnabled && !enabled) {
1604 clearScrollingCache();
1605 }
1606 mScrollingCacheEnabled = enabled;
1607 }
1608
1609 /**
1610 * Enables or disables the type filter window. If enabled, typing when
1611 * this view has focus will filter the children to match the users input.
1612 * Note that the {@link Adapter} used by this view must implement the
1613 * {@link Filterable} interface.
1614 *
1615 * @param textFilterEnabled true to enable type filtering, false otherwise
1616 *
1617 * @see Filterable
1618 */
1619 public void setTextFilterEnabled(boolean textFilterEnabled) {
1620 mTextFilterEnabled = textFilterEnabled;
1621 }
1622
1623 /**
1624 * Indicates whether type filtering is enabled for this view
1625 *
1626 * @return true if type filtering is enabled, false otherwise
1627 *
1628 * @see #setTextFilterEnabled(boolean)
1629 * @see Filterable
1630 */
1631 @ViewDebug.ExportedProperty
1632 public boolean isTextFilterEnabled() {
1633 return mTextFilterEnabled;
1634 }
1635
1636 @Override
1637 public void getFocusedRect(Rect r) {
1638 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001639 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001640 // the focused rectangle of the selected view offset into the
1641 // coordinate space of this view.
1642 view.getFocusedRect(r);
1643 offsetDescendantRectToMyCoords(view, r);
1644 } else {
1645 // otherwise, just the norm
1646 super.getFocusedRect(r);
1647 }
1648 }
1649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001650 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001651 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001652 com.android.internal.R.drawable.list_selector_background));
1653 }
1654
1655 /**
1656 * Indicates whether the content of this view is pinned to, or stacked from,
1657 * the bottom edge.
1658 *
1659 * @return true if the content is stacked from the bottom edge, false otherwise
1660 */
1661 @ViewDebug.ExportedProperty
1662 public boolean isStackFromBottom() {
1663 return mStackFromBottom;
1664 }
1665
1666 /**
1667 * When stack from bottom is set to true, the list fills its content starting from
1668 * the bottom of the view.
1669 *
1670 * @param stackFromBottom true to pin the view's content to the bottom edge,
1671 * false to pin the view's content to the top edge
1672 */
1673 public void setStackFromBottom(boolean stackFromBottom) {
1674 if (mStackFromBottom != stackFromBottom) {
1675 mStackFromBottom = stackFromBottom;
1676 requestLayoutIfNecessary();
1677 }
1678 }
1679
1680 void requestLayoutIfNecessary() {
1681 if (getChildCount() > 0) {
1682 resetList();
1683 requestLayout();
1684 invalidate();
1685 }
1686 }
1687
1688 static class SavedState extends BaseSavedState {
1689 long selectedId;
1690 long firstId;
1691 int viewTop;
1692 int position;
1693 int height;
1694 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001695 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001696 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001697 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001698 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001699
1700 /**
1701 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1702 */
1703 SavedState(Parcelable superState) {
1704 super(superState);
1705 }
1706
1707 /**
1708 * Constructor called from {@link #CREATOR}
1709 */
1710 private SavedState(Parcel in) {
1711 super(in);
1712 selectedId = in.readLong();
1713 firstId = in.readLong();
1714 viewTop = in.readInt();
1715 position = in.readInt();
1716 height = in.readInt();
1717 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001718 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001719 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001720 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001721 final int N = in.readInt();
1722 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001723 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001724 for (int i=0; i<N; i++) {
1725 final long key = in.readLong();
1726 final int value = in.readInt();
1727 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001728 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001729 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001730 }
1731
1732 @Override
1733 public void writeToParcel(Parcel out, int flags) {
1734 super.writeToParcel(out, flags);
1735 out.writeLong(selectedId);
1736 out.writeLong(firstId);
1737 out.writeInt(viewTop);
1738 out.writeInt(position);
1739 out.writeInt(height);
1740 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001741 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001742 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001743 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001744 final int N = checkIdState != null ? checkIdState.size() : 0;
1745 out.writeInt(N);
1746 for (int i=0; i<N; i++) {
1747 out.writeLong(checkIdState.keyAt(i));
1748 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001749 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001750 }
1751
1752 @Override
1753 public String toString() {
1754 return "AbsListView.SavedState{"
1755 + Integer.toHexString(System.identityHashCode(this))
1756 + " selectedId=" + selectedId
1757 + " firstId=" + firstId
1758 + " viewTop=" + viewTop
1759 + " position=" + position
1760 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001761 + " filter=" + filter
1762 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001763 }
1764
1765 public static final Parcelable.Creator<SavedState> CREATOR
1766 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001767 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001768 public SavedState createFromParcel(Parcel in) {
1769 return new SavedState(in);
1770 }
1771
Alan Viverette8fa327a2013-05-31 14:53:13 -07001772 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001773 public SavedState[] newArray(int size) {
1774 return new SavedState[size];
1775 }
1776 };
1777 }
1778
1779 @Override
1780 public Parcelable onSaveInstanceState() {
1781 /*
1782 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001783 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001784 * that happen early enough to keep from getting complaints
1785 * about having leaked the window.
1786 */
1787 dismissPopup();
1788
1789 Parcelable superState = super.onSaveInstanceState();
1790
1791 SavedState ss = new SavedState(superState);
1792
Dianne Hackborne181bd92012-09-25 14:15:15 -07001793 if (mPendingSync != null) {
1794 // Just keep what we last restored.
1795 ss.selectedId = mPendingSync.selectedId;
1796 ss.firstId = mPendingSync.firstId;
1797 ss.viewTop = mPendingSync.viewTop;
1798 ss.position = mPendingSync.position;
1799 ss.height = mPendingSync.height;
1800 ss.filter = mPendingSync.filter;
1801 ss.inActionMode = mPendingSync.inActionMode;
1802 ss.checkedItemCount = mPendingSync.checkedItemCount;
1803 ss.checkState = mPendingSync.checkState;
1804 ss.checkIdState = mPendingSync.checkIdState;
1805 return ss;
1806 }
1807
Dianne Hackborn99441c42010-12-15 11:02:55 -08001808 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001809 long selectedId = getSelectedItemId();
1810 ss.selectedId = selectedId;
1811 ss.height = getHeight();
1812
1813 if (selectedId >= 0) {
1814 // Remember the selection
1815 ss.viewTop = mSelectedTop;
1816 ss.position = getSelectedItemPosition();
1817 ss.firstId = INVALID_POSITION;
1818 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001819 if (haveChildren && mFirstPosition > 0) {
1820 // Remember the position of the first child.
1821 // We only do this if we are not currently at the top of
1822 // the list, for two reasons:
1823 // (1) The list may be in the process of becoming empty, in
1824 // which case mItemCount may not be 0, but if we try to
1825 // ask for any information about position 0 we will crash.
1826 // (2) Being "at the top" seems like a special case, anyway,
1827 // and the user wouldn't expect to end up somewhere else when
1828 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001829 View v = getChildAt(0);
1830 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001831 int firstPos = mFirstPosition;
1832 if (firstPos >= mItemCount) {
1833 firstPos = mItemCount - 1;
1834 }
1835 ss.position = firstPos;
1836 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001837 } else {
1838 ss.viewTop = 0;
1839 ss.firstId = INVALID_POSITION;
1840 ss.position = 0;
1841 }
1842 }
1843
1844 ss.filter = null;
1845 if (mFiltered) {
1846 final EditText textFilter = mTextFilter;
1847 if (textFilter != null) {
1848 Editable filterText = textFilter.getText();
1849 if (filterText != null) {
1850 ss.filter = filterText.toString();
1851 }
1852 }
1853 }
1854
Adam Powella0eeeac2010-11-05 11:55:05 -07001855 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1856
Adam Powell9a5cc282011-08-28 16:18:16 -07001857 if (mCheckStates != null) {
1858 ss.checkState = mCheckStates.clone();
1859 }
1860 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001861 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001862 final int count = mCheckedIdStates.size();
1863 for (int i = 0; i < count; i++) {
1864 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1865 }
1866 ss.checkIdState = idState;
1867 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001868 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001869
Adam Cohen335c3b62012-07-24 17:18:16 -07001870 if (mRemoteAdapter != null) {
1871 mRemoteAdapter.saveRemoteViewsCache();
1872 }
1873
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001874 return ss;
1875 }
1876
1877 @Override
1878 public void onRestoreInstanceState(Parcelable state) {
1879 SavedState ss = (SavedState) state;
1880
1881 super.onRestoreInstanceState(ss.getSuperState());
1882 mDataChanged = true;
1883
1884 mSyncHeight = ss.height;
1885
1886 if (ss.selectedId >= 0) {
1887 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001888 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001889 mSyncRowId = ss.selectedId;
1890 mSyncPosition = ss.position;
1891 mSpecificTop = ss.viewTop;
1892 mSyncMode = SYNC_SELECTED_POSITION;
1893 } else if (ss.firstId >= 0) {
1894 setSelectedPositionInt(INVALID_POSITION);
1895 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1896 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001897 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001898 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001899 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001900 mSyncRowId = ss.firstId;
1901 mSyncPosition = ss.position;
1902 mSpecificTop = ss.viewTop;
1903 mSyncMode = SYNC_FIRST_POSITION;
1904 }
1905
1906 setFilterText(ss.filter);
1907
Adam Powellf343e1b2010-08-13 18:27:04 -07001908 if (ss.checkState != null) {
1909 mCheckStates = ss.checkState;
1910 }
1911
1912 if (ss.checkIdState != null) {
1913 mCheckedIdStates = ss.checkIdState;
1914 }
1915
Adam Powell2614c6c2010-11-04 17:54:45 -07001916 mCheckedItemCount = ss.checkedItemCount;
1917
Adam Powella0eeeac2010-11-05 11:55:05 -07001918 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1919 mMultiChoiceModeCallback != null) {
1920 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1921 }
1922
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001923 requestLayout();
1924 }
1925
1926 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001927 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1928 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001929 }
1930
1931 /**
1932 * Sets the initial value for the text filter.
1933 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001934 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001935 * @see #setTextFilterEnabled
1936 */
1937 public void setFilterText(String filterText) {
1938 // TODO: Should we check for acceptFilter()?
1939 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1940 createTextFilter(false);
1941 // This is going to call our listener onTextChanged, but we might not
1942 // be ready to bring up a window yet
1943 mTextFilter.setText(filterText);
1944 mTextFilter.setSelection(filterText.length());
1945 if (mAdapter instanceof Filterable) {
1946 // if mPopup is non-null, then onTextChanged will do the filtering
1947 if (mPopup == null) {
1948 Filter f = ((Filterable) mAdapter).getFilter();
1949 f.filter(filterText);
1950 }
1951 // Set filtered to true so we will display the filter window when our main
1952 // window is ready
1953 mFiltered = true;
1954 mDataSetObserver.clearSavedState();
1955 }
1956 }
1957 }
1958
1959 /**
Romain Guy0a637162009-05-29 14:43:54 -07001960 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001961 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001962 */
1963 public CharSequence getTextFilter() {
1964 if (mTextFilterEnabled && mTextFilter != null) {
1965 return mTextFilter.getText();
1966 }
1967 return null;
1968 }
Romain Guy0a637162009-05-29 14:43:54 -07001969
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001970 @Override
1971 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1972 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1973 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001974 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001975 // Data may have changed while we were detached and it's valid
1976 // to change focus while detached. Refresh so we don't die.
1977 mDataChanged = true;
1978 mOldItemCount = mItemCount;
1979 mItemCount = mAdapter.getCount();
1980 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001981 resurrectSelection();
1982 }
1983 }
1984
1985 @Override
1986 public void requestLayout() {
1987 if (!mBlockLayoutRequests && !mInLayout) {
1988 super.requestLayout();
1989 }
1990 }
1991
1992 /**
1993 * The list is empty. Clear everything out.
1994 */
1995 void resetList() {
1996 removeAllViewsInLayout();
1997 mFirstPosition = 0;
1998 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001999 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002000 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07002001 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002002 mOldSelectedPosition = INVALID_POSITION;
2003 mOldSelectedRowId = INVALID_ROW_ID;
2004 setSelectedPositionInt(INVALID_POSITION);
2005 setNextSelectedPositionInt(INVALID_POSITION);
2006 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002007 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002008 mSelectorRect.setEmpty();
2009 invalidate();
2010 }
2011
2012 @Override
2013 protected int computeVerticalScrollExtent() {
2014 final int count = getChildCount();
2015 if (count > 0) {
2016 if (mSmoothScrollbarEnabled) {
2017 int extent = count * 100;
2018
2019 View view = getChildAt(0);
2020 final int top = view.getTop();
2021 int height = view.getHeight();
2022 if (height > 0) {
2023 extent += (top * 100) / height;
2024 }
2025
2026 view = getChildAt(count - 1);
2027 final int bottom = view.getBottom();
2028 height = view.getHeight();
2029 if (height > 0) {
2030 extent -= ((bottom - getHeight()) * 100) / height;
2031 }
2032
2033 return extent;
2034 } else {
2035 return 1;
2036 }
2037 }
2038 return 0;
2039 }
2040
2041 @Override
2042 protected int computeVerticalScrollOffset() {
2043 final int firstPosition = mFirstPosition;
2044 final int childCount = getChildCount();
2045 if (firstPosition >= 0 && childCount > 0) {
2046 if (mSmoothScrollbarEnabled) {
2047 final View view = getChildAt(0);
2048 final int top = view.getTop();
2049 int height = view.getHeight();
2050 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002051 return Math.max(firstPosition * 100 - (top * 100) / height +
2052 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002053 }
2054 } else {
2055 int index;
2056 final int count = mItemCount;
2057 if (firstPosition == 0) {
2058 index = 0;
2059 } else if (firstPosition + childCount == count) {
2060 index = count;
2061 } else {
2062 index = firstPosition + childCount / 2;
2063 }
2064 return (int) (firstPosition + childCount * (index / (float) count));
2065 }
2066 }
2067 return 0;
2068 }
2069
2070 @Override
2071 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002072 int result;
2073 if (mSmoothScrollbarEnabled) {
2074 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002075 if (mScrollY != 0) {
2076 // Compensate for overscroll
2077 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2078 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002079 } else {
2080 result = mItemCount;
2081 }
2082 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002083 }
2084
2085 @Override
2086 protected float getTopFadingEdgeStrength() {
2087 final int count = getChildCount();
2088 final float fadeEdge = super.getTopFadingEdgeStrength();
2089 if (count == 0) {
2090 return fadeEdge;
2091 } else {
2092 if (mFirstPosition > 0) {
2093 return 1.0f;
2094 }
2095
2096 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002097 final float fadeLength = getVerticalFadingEdgeLength();
2098 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002099 }
2100 }
2101
2102 @Override
2103 protected float getBottomFadingEdgeStrength() {
2104 final int count = getChildCount();
2105 final float fadeEdge = super.getBottomFadingEdgeStrength();
2106 if (count == 0) {
2107 return fadeEdge;
2108 } else {
2109 if (mFirstPosition + count - 1 < mItemCount - 1) {
2110 return 1.0f;
2111 }
2112
2113 final int bottom = getChildAt(count - 1).getBottom();
2114 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002115 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002116 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002117 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002118 }
2119 }
2120
2121 @Override
2122 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2123 if (mSelector == null) {
2124 useDefaultSelector();
2125 }
2126 final Rect listPadding = mListPadding;
2127 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2128 listPadding.top = mSelectionTopPadding + mPaddingTop;
2129 listPadding.right = mSelectionRightPadding + mPaddingRight;
2130 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002131
2132 // Check if our previous measured size was at a point where we should scroll later.
2133 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2134 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002135 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002136 final View lastChild = getChildAt(childCount - 1);
2137 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002138 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002139 lastBottom <= listBottom;
2140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002141 }
2142
Romain Guyd6a463a2009-05-21 23:10:10 -07002143 /**
2144 * Subclasses should NOT override this method but
2145 * {@link #layoutChildren()} instead.
2146 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002147 @Override
2148 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2149 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002150
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002151 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002152
Alan Viverette4b95cc72014-01-14 16:54:02 -08002153 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002154 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002155 for (int i = 0; i < childCount; i++) {
2156 getChildAt(i).forceLayout();
2157 }
2158 mRecycler.markChildrenDirty();
2159 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002160
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002161 layoutChildren();
2162 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002163
2164 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002165
2166 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2167 if (mFastScroll != null) {
2168 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2169 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002170 }
2171
2172 /**
2173 * @hide
2174 */
2175 @Override
2176 protected boolean setFrame(int left, int top, int right, int bottom) {
2177 final boolean changed = super.setFrame(left, top, right, bottom);
2178
Romain Guyd6a463a2009-05-21 23:10:10 -07002179 if (changed) {
2180 // Reposition the popup when the frame has changed. This includes
2181 // translating the widget, not just changing its dimension. The
2182 // filter popup needs to follow the widget.
2183 final boolean visible = getWindowVisibility() == View.VISIBLE;
2184 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2185 positionPopup();
2186 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002187 }
2188
2189 return changed;
2190 }
2191
Romain Guyd6a463a2009-05-21 23:10:10 -07002192 /**
2193 * Subclasses must override this method to layout their children.
2194 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002195 protected void layoutChildren() {
2196 }
2197
Alan Viverette5d565fa2013-10-30 11:09:03 -07002198 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002199 * @param focusedView view that holds accessibility focus
2200 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002201 * child contains accessibility focus
2202 */
Alan Viverette3e141622014-02-18 17:05:13 -08002203 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002204 ViewParent viewParent = focusedView.getParent();
2205 while ((viewParent instanceof View) && (viewParent != this)) {
2206 focusedView = (View) viewParent;
2207 viewParent = viewParent.getParent();
2208 }
2209
2210 if (!(viewParent instanceof View)) {
2211 return null;
2212 }
2213
2214 return focusedView;
2215 }
2216
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002217 void updateScrollIndicators() {
2218 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002219 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002220 }
2221
2222 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002223 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002224 }
2225 }
2226
Alan Viverette947a9692014-09-25 12:43:47 -07002227 private boolean canScrollUp() {
2228 boolean canScrollUp;
2229 // 0th element is not visible
2230 canScrollUp = mFirstPosition > 0;
2231
2232 // ... Or top of 0th element is not visible
2233 if (!canScrollUp) {
2234 if (getChildCount() > 0) {
2235 View child = getChildAt(0);
2236 canScrollUp = child.getTop() < mListPadding.top;
2237 }
2238 }
2239
2240 return canScrollUp;
2241 }
2242
2243 private boolean canScrollDown() {
2244 boolean canScrollDown;
2245 int count = getChildCount();
2246
2247 // Last item is not visible
2248 canScrollDown = (mFirstPosition + count) < mItemCount;
2249
2250 // ... Or bottom of the last element is not visible
2251 if (!canScrollDown && count > 0) {
2252 View child = getChildAt(count - 1);
2253 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2254 }
2255
2256 return canScrollDown;
2257 }
2258
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002259 @Override
2260 @ViewDebug.ExportedProperty
2261 public View getSelectedView() {
2262 if (mItemCount > 0 && mSelectedPosition >= 0) {
2263 return getChildAt(mSelectedPosition - mFirstPosition);
2264 } else {
2265 return null;
2266 }
2267 }
2268
2269 /**
2270 * List padding is the maximum of the normal view's padding and the padding of the selector.
2271 *
2272 * @see android.view.View#getPaddingTop()
2273 * @see #getSelector()
2274 *
2275 * @return The top list padding.
2276 */
2277 public int getListPaddingTop() {
2278 return mListPadding.top;
2279 }
2280
2281 /**
2282 * List padding is the maximum of the normal view's padding and the padding of the selector.
2283 *
2284 * @see android.view.View#getPaddingBottom()
2285 * @see #getSelector()
2286 *
2287 * @return The bottom list padding.
2288 */
2289 public int getListPaddingBottom() {
2290 return mListPadding.bottom;
2291 }
2292
2293 /**
2294 * List padding is the maximum of the normal view's padding and the padding of the selector.
2295 *
2296 * @see android.view.View#getPaddingLeft()
2297 * @see #getSelector()
2298 *
2299 * @return The left list padding.
2300 */
2301 public int getListPaddingLeft() {
2302 return mListPadding.left;
2303 }
2304
2305 /**
2306 * List padding is the maximum of the normal view's padding and the padding of the selector.
2307 *
2308 * @see android.view.View#getPaddingRight()
2309 * @see #getSelector()
2310 *
2311 * @return The right list padding.
2312 */
2313 public int getListPaddingRight() {
2314 return mListPadding.right;
2315 }
2316
2317 /**
2318 * Get a view and have it show the data associated with the specified
2319 * position. This is called when we have already discovered that the view is
2320 * not available for reuse in the recycle bin. The only choices left are
2321 * converting an old view or making a new one.
2322 *
2323 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002324 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2325 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002326 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002327 * @return A view displaying the data associated with the specified position
2328 */
Romain Guy21875052010-01-06 18:48:08 -08002329 View obtainView(int position, boolean[] isScrap) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002330 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2331
Romain Guy21875052010-01-06 18:48:08 -08002332 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002333
Alan Viverette59511502013-12-09 13:49:25 -08002334 // Check whether we have a transient state view. Attempt to re-bind the
2335 // data and discard the view if we fail.
2336 final View transientView = mRecycler.getTransientStateView(position);
2337 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002338 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2339
2340 // If the view type hasn't changed, attempt to re-bind the data.
2341 if (params.viewType == mAdapter.getItemViewType(position)) {
2342 final View updatedView = mAdapter.getView(position, transientView, this);
2343
2344 // If we failed to re-bind the data, scrap the obtained view.
2345 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002346 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002347 mRecycler.addScrapView(updatedView, position);
2348 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002349 }
2350
Alan Viverette59511502013-12-09 13:49:25 -08002351 isScrap[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002352
2353 // Finish the temporary detach started in addScrapView().
2354 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002355 return transientView;
2356 }
2357
2358 final View scrapView = mRecycler.getScrapView(position);
2359 final View child = mAdapter.getView(position, scrapView, this);
2360 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002361 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002362 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002363 mRecycler.addScrapView(scrapView, position);
Romain Guy21875052010-01-06 18:48:08 -08002364 } else {
Romain Guya440b002010-02-24 15:57:54 -08002365 isScrap[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002366
Alan Viverette6c413ce2015-06-03 10:35:44 -07002367 // Finish the temporary detach started in addScrapView().
Deepanshu Gupta5987e552015-05-13 21:25:57 -07002368 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002369 }
Alan Viverette59511502013-12-09 13:49:25 -08002370 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002371
Alan Viverette59511502013-12-09 13:49:25 -08002372 if (mCacheColorHint != 0) {
2373 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2374 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002375
Alan Viverette59511502013-12-09 13:49:25 -08002376 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2377 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002378 }
2379
Alan Viverettee6be9c782014-02-26 18:16:36 -08002380 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002381
alanvc1d7e772012-05-08 14:47:24 -07002382 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2383 if (mAccessibilityDelegate == null) {
2384 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2385 }
alanvb72fe7a2012-08-27 16:44:25 -07002386 if (child.getAccessibilityDelegate() == null) {
2387 child.setAccessibilityDelegate(mAccessibilityDelegate);
2388 }
alanvc1d7e772012-05-08 14:47:24 -07002389 }
2390
Romain Guy5fade8c2013-07-10 16:36:18 -07002391 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002393 return child;
2394 }
2395
Alan Viverettee6be9c782014-02-26 18:16:36 -08002396 private void setItemViewLayoutParams(View child, int position) {
2397 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2398 LayoutParams lp;
2399 if (vlp == null) {
2400 lp = (LayoutParams) generateDefaultLayoutParams();
2401 } else if (!checkLayoutParams(vlp)) {
2402 lp = (LayoutParams) generateLayoutParams(vlp);
2403 } else {
2404 lp = (LayoutParams) vlp;
2405 }
2406
2407 if (mAdapterHasStableIds) {
2408 lp.itemId = mAdapter.getItemId(position);
2409 }
2410 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002411 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002412 if (lp != vlp) {
2413 child.setLayoutParams(lp);
2414 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002415 }
2416
alanvc1d7e772012-05-08 14:47:24 -07002417 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2418 @Override
2419 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2420 super.onInitializeAccessibilityNodeInfo(host, info);
2421
2422 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002423 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002424 }
2425
2426 @Override
2427 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002428 if (super.performAccessibilityAction(host, action, arguments)) {
2429 return true;
2430 }
2431
alanvc1d7e772012-05-08 14:47:24 -07002432 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002433 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002434 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002435 return false;
2436 }
2437
Alan Viverette92539d52015-09-14 10:49:25 -04002438 if (position >= mAdapter.getCount()) {
2439 // The position is no longer valid, likely due to a data set
2440 // change. We could fail here for all data set changes, since
2441 // there is a chance that the data bound to the view may no
2442 // longer exist at the same position within the adapter, but
2443 // it's more consistent with the standard touch interaction to
2444 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002445 return false;
2446 }
2447
Alan Viverette92539d52015-09-14 10:49:25 -04002448 final boolean isItemEnabled;
2449 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2450 if (lp instanceof AbsListView.LayoutParams) {
2451 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2452 } else {
2453 isItemEnabled = false;
2454 }
2455
2456 if (!isEnabled() || !isItemEnabled) {
2457 // Cannot perform actions on disabled items.
2458 return false;
2459 }
alanvc1d7e772012-05-08 14:47:24 -07002460
2461 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002462 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2463 if (getSelectedItemPosition() == position) {
2464 setSelection(INVALID_POSITION);
2465 return true;
2466 }
2467 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002468 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002469 if (getSelectedItemPosition() != position) {
2470 setSelection(position);
2471 return true;
2472 }
2473 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002474 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002475 if (isItemClickable(host)) {
2476 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002477 return performItemClick(host, position, id);
2478 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002479 } return false;
2480 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2481 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002482 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002483 return performLongPress(host, position, id);
2484 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002485 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002486 }
2487
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002488 return false;
alanvc1d7e772012-05-08 14:47:24 -07002489 }
2490 }
2491
Alan Viverette5b2081d2013-08-28 10:43:07 -07002492 /**
2493 * Initializes an {@link AccessibilityNodeInfo} with information about a
2494 * particular item in the list.
2495 *
2496 * @param view View representing the list item.
2497 * @param position Position of the list item within the adapter.
2498 * @param info Node info to populate.
2499 */
2500 public void onInitializeAccessibilityNodeInfoForItem(
2501 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002502 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002503 // The item doesn't exist, so there's not much we can do here.
2504 return;
2505 }
2506
Alan Viverette92539d52015-09-14 10:49:25 -04002507 final boolean isItemEnabled;
2508 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2509 if (lp instanceof AbsListView.LayoutParams) {
2510 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2511 } else {
2512 isItemEnabled = false;
2513 }
2514
2515 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002516 info.setEnabled(false);
2517 return;
2518 }
2519
2520 if (position == getSelectedItemPosition()) {
2521 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002522 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002523 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002524 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002525 }
2526
Alan Viverette92539d52015-09-14 10:49:25 -04002527 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002528 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002529 info.setClickable(true);
2530 }
2531
2532 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002533 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002534 info.setLongClickable(true);
2535 }
2536 }
2537
Alan Viverette92539d52015-09-14 10:49:25 -04002538 private boolean isItemClickable(View view) {
2539 return !view.hasFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002540 }
2541
Alan Viverettede399392014-05-01 17:20:55 -07002542 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002543 * Positions the selector in a way that mimics touch.
2544 */
2545 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002546 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002547 }
2548
2549 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002550 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002551 */
2552 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002553 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002554 final Rect bounds = mSelectorRect;
2555 final float x = bounds.exactCenterX();
2556 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002557 positionSelector(position, sel, true, x, y);
2558 } else {
2559 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002560 }
2561 }
2562
Dianne Hackborn079e2352010-10-18 17:02:43 -07002563 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002564 positionSelector(position, sel, false, -1, -1);
2565 }
2566
2567 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2568 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002569 if (position != INVALID_POSITION) {
2570 mSelectorPosition = position;
2571 }
2572
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002573 final Rect selectorRect = mSelectorRect;
2574 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002575 if (sel instanceof SelectionBoundsAdjuster) {
2576 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2577 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002578
2579 // Adjust for selection padding.
2580 selectorRect.left -= mSelectionLeftPadding;
2581 selectorRect.top -= mSelectionTopPadding;
2582 selectorRect.right += mSelectionRightPadding;
2583 selectorRect.bottom += mSelectionBottomPadding;
2584
Alan Viverettea19ab342015-05-18 13:20:52 -07002585 // Update the child enabled state prior to updating the selector.
2586 final boolean isChildViewEnabled = sel.isEnabled();
2587 if (mIsChildViewEnabled != isChildViewEnabled) {
2588 mIsChildViewEnabled = isChildViewEnabled;
2589 }
2590
2591 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002592 final Drawable selector = mSelector;
2593 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002594 if (positionChanged) {
2595 // Wipe out the current selector state so that we can start
2596 // over in the new position with a fresh state.
2597 selector.setVisible(false, false);
2598 selector.setState(StateSet.NOTHING);
2599 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002600 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002601 if (positionChanged) {
2602 if (getVisibility() == VISIBLE) {
2603 selector.setVisible(true, false);
2604 }
Chet Haase2167b112014-12-19 16:37:18 -08002605 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002606 }
2607 if (manageHotspot) {
2608 selector.setHotspot(x, y);
2609 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002610 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002611 }
2612
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002613 @Override
2614 protected void dispatchDraw(Canvas canvas) {
2615 int saveCount = 0;
2616 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2617 if (clipToPadding) {
2618 saveCount = canvas.save();
2619 final int scrollX = mScrollX;
2620 final int scrollY = mScrollY;
2621 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2622 scrollX + mRight - mLeft - mPaddingRight,
2623 scrollY + mBottom - mTop - mPaddingBottom);
2624 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2625 }
2626
2627 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2628 if (!drawSelectorOnTop) {
2629 drawSelector(canvas);
2630 }
2631
2632 super.dispatchDraw(canvas);
2633
2634 if (drawSelectorOnTop) {
2635 drawSelector(canvas);
2636 }
2637
2638 if (clipToPadding) {
2639 canvas.restoreToCount(saveCount);
2640 mGroupFlags |= CLIP_TO_PADDING_MASK;
2641 }
2642 }
2643
2644 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002645 protected boolean isPaddingOffsetRequired() {
2646 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2647 }
2648
2649 @Override
2650 protected int getLeftPaddingOffset() {
2651 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2652 }
2653
2654 @Override
2655 protected int getTopPaddingOffset() {
2656 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2657 }
2658
2659 @Override
2660 protected int getRightPaddingOffset() {
2661 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2662 }
2663
2664 @Override
2665 protected int getBottomPaddingOffset() {
2666 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2667 }
2668
2669 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002670 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2671 if (getChildCount() > 0) {
2672 mDataChanged = true;
2673 rememberSyncState();
2674 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002675
Alan Viverette8636ace2013-10-31 15:41:31 -07002676 if (mFastScroll != null) {
2677 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002678 }
2679 }
2680
2681 /**
2682 * @return True if the current touch mode requires that we draw the selector in the pressed
2683 * state.
2684 */
2685 boolean touchModeDrawsInPressedState() {
2686 // FIXME use isPressed for this
2687 switch (mTouchMode) {
2688 case TOUCH_MODE_TAP:
2689 case TOUCH_MODE_DONE_WAITING:
2690 return true;
2691 default:
2692 return false;
2693 }
2694 }
2695
2696 /**
2697 * Indicates whether this view is in a state where the selector should be drawn. This will
2698 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2699 * the pressed state for an item.
2700 *
2701 * @return True if the selector should be shown
2702 */
2703 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002704 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002705 }
2706
2707 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002708 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002709 final Drawable selector = mSelector;
2710 selector.setBounds(mSelectorRect);
2711 selector.draw(canvas);
2712 }
2713 }
2714
2715 /**
2716 * Controls whether the selection highlight drawable should be drawn on top of the item or
2717 * behind it.
2718 *
2719 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2720 * is false.
2721 *
2722 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2723 */
2724 public void setDrawSelectorOnTop(boolean onTop) {
2725 mDrawSelectorOnTop = onTop;
2726 }
2727
2728 /**
2729 * Set a Drawable that should be used to highlight the currently selected item.
2730 *
2731 * @param resID A Drawable resource to use as the selection highlight.
2732 *
2733 * @attr ref android.R.styleable#AbsListView_listSelector
2734 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002735 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002736 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002737 }
2738
2739 public void setSelector(Drawable sel) {
2740 if (mSelector != null) {
2741 mSelector.setCallback(null);
2742 unscheduleDrawable(mSelector);
2743 }
2744 mSelector = sel;
2745 Rect padding = new Rect();
2746 sel.getPadding(padding);
2747 mSelectionLeftPadding = padding.left;
2748 mSelectionTopPadding = padding.top;
2749 mSelectionRightPadding = padding.right;
2750 mSelectionBottomPadding = padding.bottom;
2751 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002752 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002753 }
2754
2755 /**
2756 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2757 * selection in the list.
2758 *
2759 * @return the drawable used to display the selector
2760 */
2761 public Drawable getSelector() {
2762 return mSelector;
2763 }
2764
2765 /**
2766 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2767 * this is a long press.
2768 */
2769 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002770 if (!isEnabled() || !isClickable()) {
2771 return;
2772 }
2773
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002774 Drawable selector = mSelector;
2775 Rect selectorRect = mSelectorRect;
2776 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002777 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002778
2779 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2780
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002781 if (v != null) {
2782 if (v.hasFocusable()) return;
2783 v.setPressed(true);
2784 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002785 setPressed(true);
2786
2787 final boolean longClickable = isLongClickable();
2788 Drawable d = selector.getCurrent();
2789 if (d != null && d instanceof TransitionDrawable) {
2790 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002791 ((TransitionDrawable) d).startTransition(
2792 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002793 } else {
2794 ((TransitionDrawable) d).resetTransition();
2795 }
2796 }
2797 if (longClickable && !mDataChanged) {
2798 if (mPendingCheckForKeyLongPress == null) {
2799 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2800 }
2801 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2802 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2803 }
2804 }
2805 }
2806
2807 public void setScrollIndicators(View up, View down) {
2808 mScrollUp = up;
2809 mScrollDown = down;
2810 }
2811
Dianne Hackborn079e2352010-10-18 17:02:43 -07002812 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002813 final Drawable selector = mSelector;
2814 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002815 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002816 if (selector.setState(getDrawableStateForSelector())) {
2817 invalidateDrawable(selector);
2818 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002819 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002820 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002821 }
2822 }
2823 }
2824
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002825 @Override
2826 protected void drawableStateChanged() {
2827 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002828 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002829 }
2830
Alan Viverettef723c832015-02-03 16:31:46 -08002831 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002832 // If the child view is enabled then do the default behavior.
2833 if (mIsChildViewEnabled) {
2834 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002835 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002836 }
2837
2838 // The selector uses this View's drawable state. The selected child view
2839 // is disabled, so we need to remove the enabled state from the drawable
2840 // states.
2841 final int enabledState = ENABLED_STATE_SET[0];
2842
Alan Viverettef723c832015-02-03 16:31:46 -08002843 // If we don't have any extra space, it will return one of the static
2844 // state arrays, and clearing the enabled state on those arrays is a
2845 // bad thing! If we specify we need extra space, it will create+copy
2846 // into a new array that is safely mutable.
2847 final int[] state = onCreateDrawableState(1);
2848
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002849 int enabledPos = -1;
2850 for (int i = state.length - 1; i >= 0; i--) {
2851 if (state[i] == enabledState) {
2852 enabledPos = i;
2853 break;
2854 }
2855 }
2856
2857 // Remove the enabled state
2858 if (enabledPos >= 0) {
2859 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2860 state.length - enabledPos - 1);
2861 }
Romain Guy0a637162009-05-29 14:43:54 -07002862
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002863 return state;
2864 }
2865
2866 @Override
2867 public boolean verifyDrawable(Drawable dr) {
2868 return mSelector == dr || super.verifyDrawable(dr);
2869 }
2870
2871 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002872 public void jumpDrawablesToCurrentState() {
2873 super.jumpDrawablesToCurrentState();
2874 if (mSelector != null) mSelector.jumpToCurrentState();
2875 }
2876
2877 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002878 protected void onAttachedToWindow() {
2879 super.onAttachedToWindow();
2880
2881 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002882 treeObserver.addOnTouchModeChangeListener(this);
2883 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2884 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002885 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002886
Romain Guy82afc7b2010-05-13 11:52:37 -07002887 if (mAdapter != null && mDataSetObserver == null) {
2888 mDataSetObserver = new AdapterDataSetObserver();
2889 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002890
2891 // Data may have changed while we were detached. Refresh.
2892 mDataChanged = true;
2893 mOldItemCount = mItemCount;
2894 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002895 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002896 }
2897
2898 @Override
2899 protected void onDetachedFromWindow() {
2900 super.onDetachedFromWindow();
2901
Alan Viverette462c2172014-02-24 12:24:11 -08002902 mIsDetaching = true;
2903
Romain Guy1f7f3c32009-07-22 11:25:42 -07002904 // Dismiss the popup in case onSaveInstanceState() was not invoked
2905 dismissPopup();
2906
Romain Guy21875052010-01-06 18:48:08 -08002907 // Detach any view left in the scrap heap
2908 mRecycler.clear();
2909
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002910 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002911 treeObserver.removeOnTouchModeChangeListener(this);
2912 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002913 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002914 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002915 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002916
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002917 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002918 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2919 mDataSetObserver = null;
2920 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002921
2922 if (mScrollStrictSpan != null) {
2923 mScrollStrictSpan.finish();
2924 mScrollStrictSpan = null;
2925 }
2926
2927 if (mFlingStrictSpan != null) {
2928 mFlingStrictSpan.finish();
2929 mFlingStrictSpan = null;
2930 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002931
2932 if (mFlingRunnable != null) {
2933 removeCallbacks(mFlingRunnable);
2934 }
2935
2936 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002937 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002938 }
2939
2940 if (mClearScrollingCache != null) {
2941 removeCallbacks(mClearScrollingCache);
2942 }
2943
2944 if (mPerformClick != null) {
2945 removeCallbacks(mPerformClick);
2946 }
2947
2948 if (mTouchModeReset != null) {
2949 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002950 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002951 }
Alan Viverette462c2172014-02-24 12:24:11 -08002952
2953 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002954 }
2955
2956 @Override
2957 public void onWindowFocusChanged(boolean hasWindowFocus) {
2958 super.onWindowFocusChanged(hasWindowFocus);
2959
2960 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2961
2962 if (!hasWindowFocus) {
2963 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002964 if (mFlingRunnable != null) {
2965 removeCallbacks(mFlingRunnable);
2966 // let the fling runnable report it's new state which
2967 // should be idle
2968 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002969 if (mPositionScroller != null) {
2970 mPositionScroller.stop();
2971 }
Adam Powell45803472010-01-25 15:10:44 -08002972 if (mScrollY != 0) {
2973 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002974 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002975 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002976 invalidate();
2977 }
Mark Wagner670dd812010-01-13 16:17:47 -08002978 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002979 // Always hide the type filter
2980 dismissPopup();
2981
2982 if (touchMode == TOUCH_MODE_OFF) {
2983 // Remember the last selected element
2984 mResurrectToPosition = mSelectedPosition;
2985 }
2986 } else {
Adam Powell97566042010-03-09 15:34:09 -08002987 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002988 // Show the type filter only if a filter is in effect
2989 showPopup();
2990 }
2991
2992 // If we changed touch mode since the last time we had focus
2993 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2994 // If we come back in trackball mode, we bring the selection back
2995 if (touchMode == TOUCH_MODE_OFF) {
2996 // This will trigger a layout
2997 resurrectSelection();
2998
2999 // If we come back in touch mode, then we want to hide the selector
3000 } else {
3001 hideSelector();
3002 mLayoutMode = LAYOUT_NORMAL;
3003 layoutChildren();
3004 }
3005 }
3006 }
3007
3008 mLastTouchMode = touchMode;
3009 }
3010
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003011 @Override
3012 public void onRtlPropertiesChanged(int layoutDirection) {
3013 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003014 if (mFastScroll != null) {
3015 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003016 }
3017 }
3018
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003019 /**
3020 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3021 * methods knows the view, position and ID of the item that received the
3022 * long press.
3023 *
3024 * @param view The view that received the long press.
3025 * @param position The position of the item that received the long press.
3026 * @param id The ID of the item that received the long press.
3027 * @return The extra information that should be returned by
3028 * {@link #getContextMenuInfo()}.
3029 */
3030 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3031 return new AdapterContextMenuInfo(view, position, id);
3032 }
3033
Adam Powell14874662013-07-18 19:42:41 -07003034 @Override
3035 public void onCancelPendingInputEvents() {
3036 super.onCancelPendingInputEvents();
3037 if (mPerformClick != null) {
3038 removeCallbacks(mPerformClick);
3039 }
3040 if (mPendingCheckForTap != null) {
3041 removeCallbacks(mPendingCheckForTap);
3042 }
3043 if (mPendingCheckForLongPress != null) {
3044 removeCallbacks(mPendingCheckForLongPress);
3045 }
3046 if (mPendingCheckForKeyLongPress != null) {
3047 removeCallbacks(mPendingCheckForKeyLongPress);
3048 }
3049 }
3050
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003051 /**
3052 * A base class for Runnables that will check that their view is still attached to
3053 * the original window as when the Runnable was created.
3054 *
3055 */
3056 private class WindowRunnnable {
3057 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003058
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003059 public void rememberWindowAttachCount() {
3060 mOriginalAttachCount = getWindowAttachCount();
3061 }
Romain Guy0a637162009-05-29 14:43:54 -07003062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003063 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003064 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003065 }
3066 }
Romain Guy0a637162009-05-29 14:43:54 -07003067
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003068 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003069 int mClickMotionPosition;
3070
Alan Viverette8fa327a2013-05-31 14:53:13 -07003071 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003072 public void run() {
3073 // The data has changed since we posted this action in the event queue,
3074 // bail out before bad things happen
3075 if (mDataChanged) return;
3076
Adam Powell005c0a42010-03-30 16:26:36 -07003077 final ListAdapter adapter = mAdapter;
3078 final int motionPosition = mClickMotionPosition;
3079 if (adapter != null && mItemCount > 0 &&
3080 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003081 motionPosition < adapter.getCount() && sameWindow() &&
3082 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003083 final View view = getChildAt(motionPosition - mFirstPosition);
3084 // If there is no view, something bad happened (the view scrolled off the
3085 // screen, etc.) and we should cancel the click
3086 if (view != null) {
3087 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3088 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003089 }
3090 }
3091 }
3092
3093 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003094 private static final int INVALID_COORD = -1;
3095 private float mX = INVALID_COORD;
3096 private float mY = INVALID_COORD;
3097
3098 private void setCoords(float x, float y) {
3099 mX = x;
3100 mY = y;
3101 }
3102
Alan Viverette8fa327a2013-05-31 14:53:13 -07003103 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003104 public void run() {
3105 final int motionPosition = mMotionPosition;
3106 final View child = getChildAt(motionPosition - mFirstPosition);
3107 if (child != null) {
3108 final int longPressPosition = mMotionPosition;
3109 final long longPressId = mAdapter.getItemId(mMotionPosition);
3110
3111 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003112 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003113 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3114 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3115 } else {
3116 handled = performLongPress(child, longPressPosition, longPressId);
3117 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003118 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003119
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003120 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003121 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003122 mTouchMode = TOUCH_MODE_REST;
3123 setPressed(false);
3124 child.setPressed(false);
3125 } else {
3126 mTouchMode = TOUCH_MODE_DONE_WAITING;
3127 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003128 }
3129 }
3130 }
Romain Guy0a637162009-05-29 14:43:54 -07003131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003132 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003133 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003134 public void run() {
3135 if (isPressed() && mSelectedPosition >= 0) {
3136 int index = mSelectedPosition - mFirstPosition;
3137 View v = getChildAt(index);
3138
3139 if (!mDataChanged) {
3140 boolean handled = false;
3141 if (sameWindow()) {
3142 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3143 }
3144 if (handled) {
3145 setPressed(false);
3146 v.setPressed(false);
3147 }
3148 } else {
3149 setPressed(false);
3150 if (v != null) v.setPressed(false);
3151 }
3152 }
3153 }
3154 }
3155
Mady Mellore5561982015-04-14 15:06:40 -07003156 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003157 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003158 final View child = getChildAt(mMotionPosition - mFirstPosition);
3159 if (child != null) {
3160 final int longPressPosition = mMotionPosition;
3161 final long longPressId = mAdapter.getItemId(mMotionPosition);
3162 if (performLongPress(child, longPressPosition, longPressId)) {
3163 mTouchMode = TOUCH_MODE_REST;
3164 setPressed(false);
3165 child.setPressed(false);
3166 return true;
3167 }
3168 }
3169 }
3170 return false;
3171 }
3172
Adam Powell8350f7d2010-07-28 14:27:28 -07003173 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003174 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003175 return performLongPress(
3176 child,
3177 longPressPosition,
3178 longPressId,
3179 CheckForLongPress.INVALID_COORD,
3180 CheckForLongPress.INVALID_COORD);
3181 }
3182
3183 boolean performLongPress(final View child,
3184 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003185 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3186 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003187 if (mChoiceActionMode == null &&
3188 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003189 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003190 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003191 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003192 return true;
3193 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003194
Adam Powellf343e1b2010-08-13 18:27:04 -07003195 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003196 if (mOnItemLongClickListener != null) {
3197 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3198 longPressPosition, longPressId);
3199 }
3200 if (!handled) {
3201 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003202 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3203 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3204 } else {
3205 handled = super.showContextMenuForChild(AbsListView.this);
3206 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003207 }
3208 if (handled) {
3209 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3210 }
3211 return handled;
3212 }
3213
3214 @Override
3215 protected ContextMenuInfo getContextMenuInfo() {
3216 return mContextMenuInfo;
3217 }
3218
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003219 @Override
3220 public boolean showContextMenu() {
3221 return showContextMenuInternal(0, 0, false);
3222 }
3223
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003224 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003225 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003226 return showContextMenuInternal(x, y, true);
3227 }
3228
3229 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003230 final int position = pointToPosition((int)x, (int)y);
3231 if (position != INVALID_POSITION) {
3232 final long id = mAdapter.getItemId(position);
3233 View child = getChildAt(position - mFirstPosition);
3234 if (child != null) {
3235 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003236 if (useOffsets) {
3237 return super.showContextMenuForChild(this, x, y);
3238 } else {
3239 return super.showContextMenuForChild(this);
3240 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003241 }
3242 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003243 if (useOffsets) {
3244 return super.showContextMenu(x, y);
3245 } else {
3246 return super.showContextMenu();
3247 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003248 }
3249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003250 @Override
3251 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003252 if (isShowingContextMenuWithCoords()) {
3253 return false;
3254 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003255 return showContextMenuForChildInternal(originalView, 0, 0, false);
3256 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003257
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003258 @Override
3259 public boolean showContextMenuForChild(View originalView, float x, float y) {
3260 return showContextMenuForChildInternal(originalView,x, y, true);
3261 }
3262
3263 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3264 boolean useOffsets) {
3265 final int longPressPosition = getPositionForView(originalView);
3266 if (longPressPosition < 0) {
3267 return false;
3268 }
3269
3270 final long longPressId = mAdapter.getItemId(longPressPosition);
3271 boolean handled = false;
3272
3273 if (mOnItemLongClickListener != null) {
3274 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3275 longPressPosition, longPressId);
3276 }
3277
3278 if (!handled) {
3279 final View child = getChildAt(longPressPosition - mFirstPosition);
3280 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3281
3282 if (useOffsets) {
3283 handled = super.showContextMenuForChild(originalView, x, y);
3284 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003285 handled = super.showContextMenuForChild(originalView);
3286 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003287 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003288
3289 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003290 }
3291
3292 @Override
Romain Guydf016072009-08-17 12:51:30 -07003293 public boolean onKeyDown(int keyCode, KeyEvent event) {
3294 return false;
3295 }
3296
3297 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003298 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003299 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003300 if (!isEnabled()) {
3301 return true;
3302 }
Romain Guydf016072009-08-17 12:51:30 -07003303 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003304 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003305 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003306
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003307 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003308 if (view != null) {
3309 performItemClick(view, mSelectedPosition, mSelectedRowId);
3310 view.setPressed(false);
3311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003312 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003313 return true;
3314 }
3315 }
3316 return super.onKeyUp(keyCode, event);
3317 }
3318
3319 @Override
3320 protected void dispatchSetPressed(boolean pressed) {
3321 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3322 // get the selector in the right state, but we don't want to press each child.
3323 }
3324
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003325 @Override
3326 public void dispatchDrawableHotspotChanged(float x, float y) {
3327 // Don't dispatch hotspot changes to children. We'll manually handle
3328 // calling drawableHotspotChanged on the correct child.
3329 }
3330
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003331 /**
3332 * Maps a point to a position in the list.
3333 *
3334 * @param x X in local coordinate
3335 * @param y Y in local coordinate
3336 * @return The position of the item which contains the specified point, or
3337 * {@link #INVALID_POSITION} if the point does not intersect an item.
3338 */
3339 public int pointToPosition(int x, int y) {
3340 Rect frame = mTouchFrame;
3341 if (frame == null) {
3342 mTouchFrame = new Rect();
3343 frame = mTouchFrame;
3344 }
3345
3346 final int count = getChildCount();
3347 for (int i = count - 1; i >= 0; i--) {
3348 final View child = getChildAt(i);
3349 if (child.getVisibility() == View.VISIBLE) {
3350 child.getHitRect(frame);
3351 if (frame.contains(x, y)) {
3352 return mFirstPosition + i;
3353 }
3354 }
3355 }
3356 return INVALID_POSITION;
3357 }
3358
3359
3360 /**
3361 * Maps a point to a the rowId of the item which intersects that point.
3362 *
3363 * @param x X in local coordinate
3364 * @param y Y in local coordinate
3365 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3366 * if the point does not intersect an item.
3367 */
3368 public long pointToRowId(int x, int y) {
3369 int position = pointToPosition(x, y);
3370 if (position >= 0) {
3371 return mAdapter.getItemId(position);
3372 }
3373 return INVALID_ROW_ID;
3374 }
3375
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003376 private final class CheckForTap implements Runnable {
3377 float x;
3378 float y;
3379
Alan Viverette8fa327a2013-05-31 14:53:13 -07003380 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003381 public void run() {
3382 if (mTouchMode == TOUCH_MODE_DOWN) {
3383 mTouchMode = TOUCH_MODE_TAP;
3384 final View child = getChildAt(mMotionPosition - mFirstPosition);
3385 if (child != null && !child.hasFocusable()) {
3386 mLayoutMode = LAYOUT_NORMAL;
3387
3388 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003389 final float[] point = mTmpPoint;
3390 point[0] = x;
3391 point[1] = y;
3392 transformPointToViewLocal(point, child);
3393 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003394 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003395 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003396 layoutChildren();
3397 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003398 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003399
3400 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3401 final boolean longClickable = isLongClickable();
3402
3403 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003404 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003405 if (d != null && d instanceof TransitionDrawable) {
3406 if (longClickable) {
3407 ((TransitionDrawable) d).startTransition(longPressTimeout);
3408 } else {
3409 ((TransitionDrawable) d).resetTransition();
3410 }
3411 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003412 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003413 }
3414
3415 if (longClickable) {
3416 if (mPendingCheckForLongPress == null) {
3417 mPendingCheckForLongPress = new CheckForLongPress();
3418 }
Oren Blasberged391262015-09-01 12:12:51 -07003419 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003420 mPendingCheckForLongPress.rememberWindowAttachCount();
3421 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3422 } else {
3423 mTouchMode = TOUCH_MODE_DONE_WAITING;
3424 }
3425 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003426 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003427 }
3428 }
3429 }
3430 }
3431 }
3432
Adam Powellc501db9f2014-05-08 12:50:10 -07003433 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003434 // Check if we have moved far enough that it looks more like a
3435 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003436 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003437 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003438 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003439 if ((overscroll || distance > mTouchSlop) &&
3440 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003441 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003442 if (overscroll) {
3443 mTouchMode = TOUCH_MODE_OVERSCROLL;
3444 mMotionCorrection = 0;
3445 } else {
3446 mTouchMode = TOUCH_MODE_SCROLL;
3447 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3448 }
Alan Viverette74ded292013-06-03 15:34:11 -07003449 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003450 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003451 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003452 if (motionView != null) {
3453 motionView.setPressed(false);
3454 }
3455 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3456 // Time to start stealing events! Once we've stolen them, don't let anyone
3457 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003458 final ViewParent parent = getParent();
3459 if (parent != null) {
3460 parent.requestDisallowInterceptTouchEvent(true);
3461 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003462 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003463 return true;
3464 }
3465
3466 return false;
3467 }
3468
Adam Powellc501db9f2014-05-08 12:50:10 -07003469 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003470 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003471 int scrollOffsetCorrection = 0;
3472 int scrollConsumedCorrection = 0;
3473 if (mLastY == Integer.MIN_VALUE) {
3474 rawDeltaY -= mMotionCorrection;
3475 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003476 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3477 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003478 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003479 scrollOffsetCorrection = -mScrollOffset[1];
3480 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003481 if (vtev != null) {
3482 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003483 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003484 }
3485 }
Yorke Lee43943d82014-05-08 10:15:20 -07003486 final int deltaY = rawDeltaY;
3487 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003488 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003489 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003490
3491 if (mTouchMode == TOUCH_MODE_SCROLL) {
3492 if (PROFILE_SCROLLING) {
3493 if (!mScrollProfilingStarted) {
3494 Debug.startMethodTracing("AbsListViewScroll");
3495 mScrollProfilingStarted = true;
3496 }
3497 }
3498
3499 if (mScrollStrictSpan == null) {
3500 // If it's non-null, we're already in a scroll.
3501 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3502 }
3503
3504 if (y != mLastY) {
3505 // We may be here after stopping a fling and continuing to scroll.
3506 // If so, we haven't disallowed intercepting touch events yet.
3507 // Make sure that we do so in case we're in a parent that can intercept.
3508 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3509 Math.abs(rawDeltaY) > mTouchSlop) {
3510 final ViewParent parent = getParent();
3511 if (parent != null) {
3512 parent.requestDisallowInterceptTouchEvent(true);
3513 }
3514 }
3515
3516 final int motionIndex;
3517 if (mMotionPosition >= 0) {
3518 motionIndex = mMotionPosition - mFirstPosition;
3519 } else {
3520 // If we don't have a motion position that we can reliably track,
3521 // pick something in the middle to make a best guess at things below.
3522 motionIndex = getChildCount() / 2;
3523 }
3524
3525 int motionViewPrevTop = 0;
3526 View motionView = this.getChildAt(motionIndex);
3527 if (motionView != null) {
3528 motionViewPrevTop = motionView.getTop();
3529 }
3530
3531 // No need to do all this work if we're not going to move anyway
3532 boolean atEdge = false;
3533 if (incrementalDeltaY != 0) {
3534 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3535 }
3536
3537 // Check to see if we have bumped into the scroll limit
3538 motionView = this.getChildAt(motionIndex);
3539 if (motionView != null) {
3540 // Check if the top of the motion view is where it is
3541 // supposed to be
3542 final int motionViewRealTop = motionView.getTop();
3543 if (atEdge) {
3544 // Apply overscroll
3545
3546 int overscroll = -incrementalDeltaY -
3547 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003548 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3549 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003550 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003551 if (vtev != null) {
3552 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003553 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003554 }
Adam Powell96d62af2014-05-02 10:04:38 -07003555 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003556 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3557 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3558
3559 if (atOverscrollEdge && mVelocityTracker != null) {
3560 // Don't allow overfling if we're at the edge
3561 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003562 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003563
Adam Powell96d62af2014-05-02 10:04:38 -07003564 final int overscrollMode = getOverScrollMode();
3565 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3566 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3567 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003568 if (!atOverscrollEdge) {
3569 mDirection = 0; // Reset when entering overscroll.
3570 mTouchMode = TOUCH_MODE_OVERSCROLL;
3571 }
3572 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003573 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003574 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003575 if (!mEdgeGlowBottom.isFinished()) {
3576 mEdgeGlowBottom.onRelease();
3577 }
Doris Liuf36c0612015-06-04 11:11:14 -07003578 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003579 } else if (incrementalDeltaY < 0) {
3580 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3581 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003582 if (!mEdgeGlowTop.isFinished()) {
3583 mEdgeGlowTop.onRelease();
3584 }
Doris Liuf36c0612015-06-04 11:11:14 -07003585 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003586 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003587 }
3588 }
3589 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003590 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003591 }
Yorke Lee43943d82014-05-08 10:15:20 -07003592 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003593 }
3594 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3595 if (y != mLastY) {
3596 final int oldScroll = mScrollY;
3597 final int newScroll = oldScroll - incrementalDeltaY;
3598 int newDirection = y > mLastY ? 1 : -1;
3599
3600 if (mDirection == 0) {
3601 mDirection = newDirection;
3602 }
3603
3604 int overScrollDistance = -incrementalDeltaY;
3605 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3606 overScrollDistance = -oldScroll;
3607 incrementalDeltaY += overScrollDistance;
3608 } else {
3609 incrementalDeltaY = 0;
3610 }
3611
3612 if (overScrollDistance != 0) {
3613 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3614 0, mOverscrollDistance, true);
3615 final int overscrollMode = getOverScrollMode();
3616 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3617 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3618 !contentFits())) {
3619 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003620 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3621 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003622 if (!mEdgeGlowBottom.isFinished()) {
3623 mEdgeGlowBottom.onRelease();
3624 }
Doris Liuf36c0612015-06-04 11:11:14 -07003625 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003626 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003627 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3628 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003629 if (!mEdgeGlowTop.isFinished()) {
3630 mEdgeGlowTop.onRelease();
3631 }
Doris Liuf36c0612015-06-04 11:11:14 -07003632 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003633 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003634 }
3635 }
3636
3637 if (incrementalDeltaY != 0) {
3638 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003639 if (mScrollY != 0) {
3640 mScrollY = 0;
3641 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003642 }
3643
Romain Guy9d849a22012-03-14 16:41:42 -07003644 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3645
Jeff Brown78f6e632011-09-09 17:15:31 -07003646 mTouchMode = TOUCH_MODE_SCROLL;
3647
3648 // We did not scroll the full amount. Treat this essentially like the
3649 // start of a new touch scroll
3650 final int motionPosition = findClosestMotionRow(y);
3651
3652 mMotionCorrection = 0;
3653 View motionView = getChildAt(motionPosition - mFirstPosition);
3654 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003655 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003656 mMotionPosition = motionPosition;
3657 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003658 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003659 mDirection = newDirection;
3660 }
3661 }
3662 }
3663
Doris Liuf36c0612015-06-04 11:11:14 -07003664 private void invalidateTopGlow() {
3665 if (mEdgeGlowTop == null) {
3666 return;
3667 }
3668 final boolean clipToPadding = getClipToPadding();
3669 final int top = clipToPadding ? mPaddingTop : 0;
3670 final int left = clipToPadding ? mPaddingLeft : 0;
3671 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3672 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3673 }
3674
3675 private void invalidateBottomGlow() {
3676 if (mEdgeGlowBottom == null) {
3677 return;
3678 }
3679 final boolean clipToPadding = getClipToPadding();
3680 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3681 final int left = clipToPadding ? mPaddingLeft : 0;
3682 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3683 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3684 }
3685
Alan Viverette8fa327a2013-05-31 14:53:13 -07003686 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003687 public void onTouchModeChanged(boolean isInTouchMode) {
3688 if (isInTouchMode) {
3689 // Get rid of the selection when we enter touch mode
3690 hideSelector();
3691 // Layout, but only if we already have done so previously.
3692 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3693 // state.)
3694 if (getHeight() > 0 && getChildCount() > 0) {
3695 // We do not lose focus initiating a touch (since AbsListView is focusable in
3696 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003697 layoutChildren();
3698 }
Jeff Brown1e209462011-07-14 22:19:19 -07003699 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003700 } else {
3701 int touchMode = mTouchMode;
3702 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3703 if (mFlingRunnable != null) {
3704 mFlingRunnable.endFling();
3705 }
Adam Powell40322522011-01-12 21:58:20 -08003706 if (mPositionScroller != null) {
3707 mPositionScroller.stop();
3708 }
Adam Powell637d3372010-08-25 14:37:03 -07003709
3710 if (mScrollY != 0) {
3711 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003712 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003713 finishGlows();
3714 invalidate();
3715 }
3716 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003717 }
3718 }
3719
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003720 /** @hide */
3721 @Override
3722 protected boolean handleScrollBarDragging(MotionEvent event) {
3723 // Doesn't support normal scroll bar dragging. Use FastScroller.
3724 return false;
3725 }
3726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003727 @Override
3728 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003729 if (!isEnabled()) {
3730 // A disabled view that is clickable still consumes the touch
3731 // events, it just doesn't respond to them.
3732 return isClickable() || isLongClickable();
3733 }
3734
Adam Powell1fa179ef2012-04-12 15:01:40 -07003735 if (mPositionScroller != null) {
3736 mPositionScroller.stop();
3737 }
3738
Alan Viverette462c2172014-02-24 12:24:11 -08003739 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003740 // Something isn't right.
3741 // Since we rely on being attached to get data set change notifications,
3742 // don't risk doing anything where we might try to resync and find things
3743 // in a bogus state.
3744 return false;
3745 }
3746
Adam Powell96d62af2014-05-02 10:04:38 -07003747 startNestedScroll(SCROLL_AXIS_VERTICAL);
3748
Alan Viverettefb99ba82015-05-01 10:10:15 -07003749 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3750 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003751 }
Romain Guy82f34952009-05-24 18:40:45 -07003752
Michael Jurka13451a42011-08-22 15:54:21 -07003753 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003754 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003755
Alan Viverette8fa327a2013-05-31 14:53:13 -07003756 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003757 if (actionMasked == MotionEvent.ACTION_DOWN) {
3758 mNestedYOffset = 0;
3759 }
3760 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003761 switch (actionMasked) {
3762 case MotionEvent.ACTION_DOWN: {
3763 onTouchDown(ev);
3764 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003765 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003766
Alan Viverette8fa327a2013-05-31 14:53:13 -07003767 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003768 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003769 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003770 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003771
3772 case MotionEvent.ACTION_UP: {
3773 onTouchUp(ev);
3774 break;
3775 }
3776
3777 case MotionEvent.ACTION_CANCEL: {
3778 onTouchCancel();
3779 break;
3780 }
3781
3782 case MotionEvent.ACTION_POINTER_UP: {
3783 onSecondaryPointerUp(ev);
3784 final int x = mMotionX;
3785 final int y = mMotionY;
3786 final int motionPosition = pointToPosition(x, y);
3787 if (motionPosition >= 0) {
3788 // Remember where the motion event started
3789 final View child = getChildAt(motionPosition - mFirstPosition);
3790 mMotionViewOriginalTop = child.getTop();
3791 mMotionPosition = motionPosition;
3792 }
3793 mLastY = y;
3794 break;
3795 }
3796
3797 case MotionEvent.ACTION_POINTER_DOWN: {
3798 // New pointers take over dragging duties
3799 final int index = ev.getActionIndex();
3800 final int id = ev.getPointerId(index);
3801 final int x = (int) ev.getX(index);
3802 final int y = (int) ev.getY(index);
3803 mMotionCorrection = 0;
3804 mActivePointerId = id;
3805 mMotionX = x;
3806 mMotionY = y;
3807 final int motionPosition = pointToPosition(x, y);
3808 if (motionPosition >= 0) {
3809 // Remember where the motion event started
3810 final View child = getChildAt(motionPosition - mFirstPosition);
3811 mMotionViewOriginalTop = child.getTop();
3812 mMotionPosition = motionPosition;
3813 }
3814 mLastY = y;
3815 break;
3816 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003817 }
3818
Adam Powell96d62af2014-05-02 10:04:38 -07003819 if (mVelocityTracker != null) {
3820 mVelocityTracker.addMovement(vtev);
3821 }
3822 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003823 return true;
3824 }
Romain Guy0a637162009-05-29 14:43:54 -07003825
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003826 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003827 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003828 mActivePointerId = ev.getPointerId(0);
3829
3830 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3831 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003832 mFlingRunnable.endFling();
3833 if (mPositionScroller != null) {
3834 mPositionScroller.stop();
3835 }
3836 mTouchMode = TOUCH_MODE_OVERSCROLL;
3837 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003838 mMotionY = (int) ev.getY();
3839 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003840 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003841 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003842 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003843 final int x = (int) ev.getX();
3844 final int y = (int) ev.getY();
3845 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003846
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003847 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003848 if (mTouchMode == TOUCH_MODE_FLING) {
3849 // Stopped a fling. It is a scroll.
3850 createScrollingCache();
3851 mTouchMode = TOUCH_MODE_SCROLL;
3852 mMotionCorrection = 0;
3853 motionPosition = findMotionRow(y);
3854 mFlingRunnable.flywheelTouch();
3855 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3856 // User clicked on an actual view (and was not stopping a
3857 // fling). It might be a click or a scroll. Assume it is a
3858 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003859 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003860
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003861 // FIXME Debounce
3862 if (mPendingCheckForTap == null) {
3863 mPendingCheckForTap = new CheckForTap();
3864 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003865
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003866 mPendingCheckForTap.x = ev.getX();
3867 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003868 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003869 }
3870 }
3871
3872 if (motionPosition >= 0) {
3873 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003874 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003875 mMotionViewOriginalTop = v.getTop();
3876 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003877
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003878 mMotionX = x;
3879 mMotionY = y;
3880 mMotionPosition = motionPosition;
3881 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003882 }
3883
Alan Viveretteb339cc52013-08-12 13:29:15 -07003884 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003885 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003886 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003887 }
3888 }
3889
Adam Powell96d62af2014-05-02 10:04:38 -07003890 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003891 if (mHasPerformedLongPress) {
3892 // Consume all move events following a successful long press.
3893 return;
3894 }
3895
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003896 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3897 if (pointerIndex == -1) {
3898 pointerIndex = 0;
3899 mActivePointerId = ev.getPointerId(pointerIndex);
3900 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003901
3902 if (mDataChanged) {
3903 // Re-sync everything if data has been changed
3904 // since the scroll operation can query the adapter.
3905 layoutChildren();
3906 }
3907
Alan Viverette8fa327a2013-05-31 14:53:13 -07003908 final int y = (int) ev.getY(pointerIndex);
3909
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003910 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003911 case TOUCH_MODE_DOWN:
3912 case TOUCH_MODE_TAP:
3913 case TOUCH_MODE_DONE_WAITING:
3914 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003915 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003916 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003917 break;
3918 }
3919 // Otherwise, check containment within list bounds. If we're
3920 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003921 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003922 final float x = ev.getX(pointerIndex);
3923 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003924 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003925 if (motionView != null) {
3926 motionView.setPressed(false);
3927 }
3928 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3929 mPendingCheckForTap : mPendingCheckForLongPress);
3930 mTouchMode = TOUCH_MODE_DONE_WAITING;
3931 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003932 } else if (motionView != null) {
3933 // Still within bounds, update the hotspot.
3934 final float[] point = mTmpPoint;
3935 point[0] = x;
3936 point[1] = y;
3937 transformPointToViewLocal(point, motionView);
3938 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003939 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003940 break;
3941 case TOUCH_MODE_SCROLL:
3942 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003943 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003944 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003945 }
3946 }
3947
3948 private void onTouchUp(MotionEvent ev) {
3949 switch (mTouchMode) {
3950 case TOUCH_MODE_DOWN:
3951 case TOUCH_MODE_TAP:
3952 case TOUCH_MODE_DONE_WAITING:
3953 final int motionPosition = mMotionPosition;
3954 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003955 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003956 if (mTouchMode != TOUCH_MODE_DOWN) {
3957 child.setPressed(false);
3958 }
3959
Alan Viverette74ded292013-06-03 15:34:11 -07003960 final float x = ev.getX();
3961 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3962 if (inList && !child.hasFocusable()) {
3963 if (mPerformClick == null) {
3964 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003965 }
Alan Viverette74ded292013-06-03 15:34:11 -07003966
3967 final AbsListView.PerformClick performClick = mPerformClick;
3968 performClick.mClickMotionPosition = motionPosition;
3969 performClick.rememberWindowAttachCount();
3970
3971 mResurrectToPosition = motionPosition;
3972
3973 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3974 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3975 mPendingCheckForTap : mPendingCheckForLongPress);
3976 mLayoutMode = LAYOUT_NORMAL;
3977 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3978 mTouchMode = TOUCH_MODE_TAP;
3979 setSelectedPositionInt(mMotionPosition);
3980 layoutChildren();
3981 child.setPressed(true);
3982 positionSelector(mMotionPosition, child);
3983 setPressed(true);
3984 if (mSelector != null) {
3985 Drawable d = mSelector.getCurrent();
3986 if (d != null && d instanceof TransitionDrawable) {
3987 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003988 }
Alan Viverettec80ad992014-05-19 15:46:17 -07003989 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003990 }
Alan Viverette74ded292013-06-03 15:34:11 -07003991 if (mTouchModeReset != null) {
3992 removeCallbacks(mTouchModeReset);
3993 }
3994 mTouchModeReset = new Runnable() {
3995 @Override
3996 public void run() {
3997 mTouchModeReset = null;
3998 mTouchMode = TOUCH_MODE_REST;
3999 child.setPressed(false);
4000 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004001 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004002 performClick.run();
4003 }
4004 }
4005 };
4006 postDelayed(mTouchModeReset,
4007 ViewConfiguration.getPressedStateDuration());
4008 } else {
4009 mTouchMode = TOUCH_MODE_REST;
4010 updateSelectorState();
4011 }
4012 return;
4013 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4014 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004015 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004016 }
4017 }
4018 mTouchMode = TOUCH_MODE_REST;
4019 updateSelectorState();
4020 break;
4021 case TOUCH_MODE_SCROLL:
4022 final int childCount = getChildCount();
4023 if (childCount > 0) {
4024 final int firstChildTop = getChildAt(0).getTop();
4025 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4026 final int contentTop = mListPadding.top;
4027 final int contentBottom = getHeight() - mListPadding.bottom;
4028 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4029 mFirstPosition + childCount < mItemCount &&
4030 lastChildBottom <= getHeight() - contentBottom) {
4031 mTouchMode = TOUCH_MODE_REST;
4032 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4033 } else {
4034 final VelocityTracker velocityTracker = mVelocityTracker;
4035 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4036
4037 final int initialVelocity = (int)
4038 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4039 // Fling if we have enough velocity and we aren't at a boundary.
4040 // Since we can potentially overfling more than we can overscroll, don't
4041 // allow the weird behavior where you can scroll to a boundary then
4042 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004043 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4044 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004045 !((mFirstPosition == 0 &&
4046 firstChildTop == contentTop - mOverscrollDistance) ||
4047 (mFirstPosition + childCount == mItemCount &&
4048 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004049 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4050 if (mFlingRunnable == null) {
4051 mFlingRunnable = new FlingRunnable();
4052 }
4053 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4054 mFlingRunnable.start(-initialVelocity);
4055 dispatchNestedFling(0, -initialVelocity, true);
4056 } else {
4057 mTouchMode = TOUCH_MODE_REST;
4058 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004059 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004060 } else {
4061 mTouchMode = TOUCH_MODE_REST;
4062 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4063 if (mFlingRunnable != null) {
4064 mFlingRunnable.endFling();
4065 }
4066 if (mPositionScroller != null) {
4067 mPositionScroller.stop();
4068 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004069 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004070 dispatchNestedFling(0, -initialVelocity, false);
4071 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004072 }
4073 }
4074 } else {
4075 mTouchMode = TOUCH_MODE_REST;
4076 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4077 }
4078 break;
4079
4080 case TOUCH_MODE_OVERSCROLL:
4081 if (mFlingRunnable == null) {
4082 mFlingRunnable = new FlingRunnable();
4083 }
4084 final VelocityTracker velocityTracker = mVelocityTracker;
4085 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4086 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4087
4088 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4089 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4090 mFlingRunnable.startOverfling(-initialVelocity);
4091 } else {
4092 mFlingRunnable.startSpringback();
4093 }
4094
4095 break;
4096 }
4097
4098 setPressed(false);
4099
4100 if (mEdgeGlowTop != null) {
4101 mEdgeGlowTop.onRelease();
4102 mEdgeGlowBottom.onRelease();
4103 }
4104
4105 // Need to redraw since we probably aren't drawing the selector anymore
4106 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004107 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004108 recycleVelocityTracker();
4109
4110 mActivePointerId = INVALID_POINTER;
4111
4112 if (PROFILE_SCROLLING) {
4113 if (mScrollProfilingStarted) {
4114 Debug.stopMethodTracing();
4115 mScrollProfilingStarted = false;
4116 }
4117 }
4118
4119 if (mScrollStrictSpan != null) {
4120 mScrollStrictSpan.finish();
4121 mScrollStrictSpan = null;
4122 }
4123 }
4124
4125 private void onTouchCancel() {
4126 switch (mTouchMode) {
4127 case TOUCH_MODE_OVERSCROLL:
4128 if (mFlingRunnable == null) {
4129 mFlingRunnable = new FlingRunnable();
4130 }
4131 mFlingRunnable.startSpringback();
4132 break;
4133
4134 case TOUCH_MODE_OVERFLING:
4135 // Do nothing - let it play out.
4136 break;
4137
4138 default:
4139 mTouchMode = TOUCH_MODE_REST;
4140 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004141 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004142 if (motionView != null) {
4143 motionView.setPressed(false);
4144 }
4145 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004146 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004147 recycleVelocityTracker();
4148 }
4149
4150 if (mEdgeGlowTop != null) {
4151 mEdgeGlowTop.onRelease();
4152 mEdgeGlowBottom.onRelease();
4153 }
4154 mActivePointerId = INVALID_POINTER;
4155 }
4156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004157 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004158 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4159 if (mScrollY != scrollY) {
4160 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4161 mScrollY = scrollY;
4162 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004163
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004164 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004165 }
Adam Powell637d3372010-08-25 14:37:03 -07004166 }
4167
4168 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004169 public boolean onGenericMotionEvent(MotionEvent event) {
4170 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
4171 switch (event.getAction()) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004172 case MotionEvent.ACTION_SCROLL:
Jeff Brown33bbfd22011-02-24 20:55:35 -08004173 if (mTouchMode == TOUCH_MODE_REST) {
4174 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4175 if (vscroll != 0) {
4176 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08004177 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08004178 return true;
4179 }
4180 }
4181 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004182 break;
4183
4184 case MotionEvent.ACTION_BUTTON_PRESS:
4185 int actionButton = event.getActionButton();
4186 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4187 || actionButton == MotionEvent.BUTTON_SECONDARY)
4188 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4189 if (performStylusButtonPressAction(event)) {
4190 removeCallbacks(mPendingCheckForLongPress);
4191 removeCallbacks(mPendingCheckForTap);
4192 }
4193 }
4194 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004195 }
4196 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004197
Jeff Brown33bbfd22011-02-24 20:55:35 -08004198 return super.onGenericMotionEvent(event);
4199 }
4200
Adam Powell4884c642014-08-07 13:52:53 -07004201 /**
4202 * Initiate a fling with the given velocity.
4203 *
4204 * <p>Applications can use this method to manually initiate a fling as if the user
4205 * initiated it via touch interaction.</p>
4206 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004207 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4208 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004209 */
4210 public void fling(int velocityY) {
4211 if (mFlingRunnable == null) {
4212 mFlingRunnable = new FlingRunnable();
4213 }
4214 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004215 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004216 }
4217
Jeff Brown33bbfd22011-02-24 20:55:35 -08004218 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004219 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4220 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4221 }
4222
4223 @Override
4224 public void onNestedScrollAccepted(View child, View target, int axes) {
4225 super.onNestedScrollAccepted(child, target, axes);
4226 startNestedScroll(SCROLL_AXIS_VERTICAL);
4227 }
4228
4229 @Override
4230 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4231 int dxUnconsumed, int dyUnconsumed) {
4232 final int motionIndex = getChildCount() / 2;
4233 final View motionView = getChildAt(motionIndex);
4234 final int oldTop = motionView != null ? motionView.getTop() : 0;
4235 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4236 int myUnconsumed = dyUnconsumed;
4237 int myConsumed = 0;
4238 if (motionView != null) {
4239 myConsumed = motionView.getTop() - oldTop;
4240 myUnconsumed -= myConsumed;
4241 }
4242 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4243 }
4244 }
4245
4246 @Override
4247 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4248 final int childCount = getChildCount();
4249 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4250 Math.abs(velocityY) > mMinimumVelocity) {
4251 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4252 if (mFlingRunnable == null) {
4253 mFlingRunnable = new FlingRunnable();
4254 }
Adam Powell9413b242014-08-06 17:34:24 -07004255 if (!dispatchNestedPreFling(0, velocityY)) {
4256 mFlingRunnable.start((int) velocityY);
4257 }
Adam Powell96d62af2014-05-02 10:04:38 -07004258 return true;
4259 }
4260 return dispatchNestedFling(velocityX, velocityY, consumed);
4261 }
4262
4263 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004264 public void draw(Canvas canvas) {
4265 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004266 if (mEdgeGlowTop != null) {
4267 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004268 final boolean clipToPadding = getClipToPadding();
4269 final int width;
4270 final int height;
4271 final int translateX;
4272 final int translateY;
4273
4274 if (clipToPadding) {
4275 width = getWidth() - mPaddingLeft - mPaddingRight;
4276 height = getHeight() - mPaddingTop - mPaddingBottom;
4277 translateX = mPaddingLeft;
4278 translateY = mPaddingTop;
4279 } else {
4280 width = getWidth();
4281 height = getHeight();
4282 translateX = 0;
4283 translateY = 0;
4284 }
Adam Powell637d3372010-08-25 14:37:03 -07004285 if (!mEdgeGlowTop.isFinished()) {
4286 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004287 canvas.clipRect(translateX, translateY,
4288 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4289 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4290 canvas.translate(translateX, edgeY);
4291 mEdgeGlowTop.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004292 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004293 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004294 }
4295 canvas.restoreToCount(restoreCount);
4296 }
4297 if (!mEdgeGlowBottom.isFinished()) {
4298 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004299 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4300 translateX + width, translateY + height);
4301 final int edgeX = -width + translateX;
4302 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4303 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004304 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004305 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08004306 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004307 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004308 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004309 }
4310 canvas.restoreToCount(restoreCount);
4311 }
4312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004313 }
4314
Michael Jurka13451a42011-08-22 15:54:21 -07004315 private void initOrResetVelocityTracker() {
4316 if (mVelocityTracker == null) {
4317 mVelocityTracker = VelocityTracker.obtain();
4318 } else {
4319 mVelocityTracker.clear();
4320 }
4321 }
4322
4323 private void initVelocityTrackerIfNotExists() {
4324 if (mVelocityTracker == null) {
4325 mVelocityTracker = VelocityTracker.obtain();
4326 }
4327 }
4328
4329 private void recycleVelocityTracker() {
4330 if (mVelocityTracker != null) {
4331 mVelocityTracker.recycle();
4332 mVelocityTracker = null;
4333 }
4334 }
4335
4336 @Override
4337 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4338 if (disallowIntercept) {
4339 recycleVelocityTracker();
4340 }
4341 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4342 }
4343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004344 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004345 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004346 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004347 return true;
4348 }
4349
4350 return super.onInterceptHoverEvent(event);
4351 }
4352
4353 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004354 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004355 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004356 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004357
Adam Powell1fa179ef2012-04-12 15:01:40 -07004358 if (mPositionScroller != null) {
4359 mPositionScroller.stop();
4360 }
4361
Alan Viverette462c2172014-02-24 12:24:11 -08004362 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004363 // Something isn't right.
4364 // Since we rely on being attached to get data set change notifications,
4365 // don't risk doing anything where we might try to resync and find things
4366 // in a bogus state.
4367 return false;
4368 }
4369
Alan Viverette8636ace2013-10-31 15:41:31 -07004370 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004371 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004372 }
Romain Guy0a637162009-05-29 14:43:54 -07004373
Adam Powell744beff2014-09-22 09:47:48 -07004374 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004375 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004376 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004377 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4378 mMotionCorrection = 0;
4379 return true;
4380 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004381
Adam Powell4cd47702010-02-25 11:21:14 -08004382 final int x = (int) ev.getX();
4383 final int y = (int) ev.getY();
4384 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004385
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004386 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004387 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004388 // User clicked on an actual view (and was not stopping a fling).
4389 // Remember where the motion event started
4390 v = getChildAt(motionPosition - mFirstPosition);
4391 mMotionViewOriginalTop = v.getTop();
4392 mMotionX = x;
4393 mMotionY = y;
4394 mMotionPosition = motionPosition;
4395 mTouchMode = TOUCH_MODE_DOWN;
4396 clearScrollingCache();
4397 }
4398 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004399 initOrResetVelocityTracker();
4400 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004401 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004402 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004403 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004404 return true;
4405 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004406 break;
4407 }
4408
4409 case MotionEvent.ACTION_MOVE: {
4410 switch (mTouchMode) {
4411 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004412 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4413 if (pointerIndex == -1) {
4414 pointerIndex = 0;
4415 mActivePointerId = ev.getPointerId(pointerIndex);
4416 }
Adam Powell4cd47702010-02-25 11:21:14 -08004417 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004418 initVelocityTrackerIfNotExists();
4419 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004420 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004421 return true;
4422 }
4423 break;
4424 }
4425 break;
4426 }
4427
Michael Jurka13451a42011-08-22 15:54:21 -07004428 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004429 case MotionEvent.ACTION_UP: {
4430 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004431 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004432 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004433 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004434 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004435 break;
4436 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004437
Adam Powell4cd47702010-02-25 11:21:14 -08004438 case MotionEvent.ACTION_POINTER_UP: {
4439 onSecondaryPointerUp(ev);
4440 break;
4441 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004442 }
4443
4444 return false;
4445 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004446
Adam Powell4cd47702010-02-25 11:21:14 -08004447 private void onSecondaryPointerUp(MotionEvent ev) {
4448 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4449 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4450 final int pointerId = ev.getPointerId(pointerIndex);
4451 if (pointerId == mActivePointerId) {
4452 // This was our active pointer going up. Choose a new
4453 // active pointer and adjust accordingly.
4454 // TODO: Make this decision more intelligent.
4455 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4456 mMotionX = (int) ev.getX(newPointerIndex);
4457 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004458 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004459 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004460 }
4461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004462
4463 /**
4464 * {@inheritDoc}
4465 */
4466 @Override
4467 public void addTouchables(ArrayList<View> views) {
4468 final int count = getChildCount();
4469 final int firstPosition = mFirstPosition;
4470 final ListAdapter adapter = mAdapter;
4471
4472 if (adapter == null) {
4473 return;
4474 }
4475
4476 for (int i = 0; i < count; i++) {
4477 final View child = getChildAt(i);
4478 if (adapter.isEnabled(firstPosition + i)) {
4479 views.add(child);
4480 }
4481 child.addTouchables(views);
4482 }
4483 }
4484
4485 /**
4486 * Fires an "on scroll state changed" event to the registered
4487 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4488 * is fired only if the specified state is different from the previously known state.
4489 *
4490 * @param newState The new scroll state.
4491 */
4492 void reportScrollStateChange(int newState) {
4493 if (newState != mLastScrollState) {
4494 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004495 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004496 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004497 }
4498 }
4499 }
4500
4501 /**
4502 * Responsible for fling behavior. Use {@link #start(int)} to
4503 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4504 * A FlingRunnable will keep re-posting itself until the fling is done.
4505 *
4506 */
4507 private class FlingRunnable implements Runnable {
4508 /**
4509 * Tracks the decay of a fling scroll
4510 */
Adam Powell637d3372010-08-25 14:37:03 -07004511 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004512
4513 /**
4514 * Y value reported by mScroller on the previous fling
4515 */
4516 private int mLastFlingY;
4517
Gilles Debunned348bb42010-11-15 12:19:35 -08004518 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004519 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004520 public void run() {
4521 final int activeId = mActivePointerId;
4522 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004523 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004524 if (vt == null || activeId == INVALID_POINTER) {
4525 return;
4526 }
4527
4528 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4529 final float yvel = -vt.getYVelocity(activeId);
4530
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004531 if (Math.abs(yvel) >= mMinimumVelocity
4532 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004533 // Keep the fling alive a little longer
4534 postDelayed(this, FLYWHEEL_TIMEOUT);
4535 } else {
4536 endFling();
4537 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004538 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004539 }
4540 }
4541 };
4542
4543 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4544
Adam Powell79ac3392010-01-28 21:22:20 -08004545 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004546 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004547 }
4548
Adam Powell79ac3392010-01-28 21:22:20 -08004549 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004550 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4551 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004552 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004553 mScroller.fling(0, initialY, 0, initialVelocity,
4554 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4555 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004556 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004557
4558 if (PROFILE_FLINGING) {
4559 if (!mFlingProfilingStarted) {
4560 Debug.startMethodTracing("AbsListViewFling");
4561 mFlingProfilingStarted = true;
4562 }
4563 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004564
4565 if (mFlingStrictSpan == null) {
4566 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4567 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004568 }
Adam Powell45803472010-01-25 15:10:44 -08004569
Adam Powell637d3372010-08-25 14:37:03 -07004570 void startSpringback() {
4571 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4572 mTouchMode = TOUCH_MODE_OVERFLING;
4573 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004574 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004575 } else {
4576 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004577 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004578 }
4579 }
4580
4581 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004582 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004583 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4584 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004585 mTouchMode = TOUCH_MODE_OVERFLING;
4586 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004587 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004588 }
4589
4590 void edgeReached(int delta) {
4591 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4592 final int overscrollMode = getOverScrollMode();
4593 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4594 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4595 mTouchMode = TOUCH_MODE_OVERFLING;
4596 final int vel = (int) mScroller.getCurrVelocity();
4597 if (delta > 0) {
4598 mEdgeGlowTop.onAbsorb(vel);
4599 } else {
4600 mEdgeGlowBottom.onAbsorb(vel);
4601 }
Adam Powell40322522011-01-12 21:58:20 -08004602 } else {
4603 mTouchMode = TOUCH_MODE_REST;
4604 if (mPositionScroller != null) {
4605 mPositionScroller.stop();
4606 }
Adam Powell637d3372010-08-25 14:37:03 -07004607 }
4608 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004609 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004610 }
4611
Adam Powell0b8acd82012-04-25 20:29:23 -07004612 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004613 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4614 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004615 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004616 mScroller.startScroll(0, initialY, 0, distance, duration);
4617 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004618 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004619 }
4620
Gilles Debunned348bb42010-11-15 12:19:35 -08004621 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004622 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004623
Adam Powell79ac3392010-01-28 21:22:20 -08004624 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004625 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004626
4627 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4628 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004629 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004630
4631 if (mFlingStrictSpan != null) {
4632 mFlingStrictSpan.finish();
4633 mFlingStrictSpan = null;
4634 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004635 }
4636
4637 void flywheelTouch() {
4638 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004639 }
4640
Alan Viverette8fa327a2013-05-31 14:53:13 -07004641 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004642 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004643 switch (mTouchMode) {
4644 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004645 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004646 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004647
Gilles Debunned348bb42010-11-15 12:19:35 -08004648 case TOUCH_MODE_SCROLL:
4649 if (mScroller.isFinished()) {
4650 return;
4651 }
4652 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004653 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004654 if (mDataChanged) {
4655 layoutChildren();
4656 }
4657
Adam Powell79ac3392010-01-28 21:22:20 -08004658 if (mItemCount == 0 || getChildCount() == 0) {
4659 endFling();
4660 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004661 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004662
Adam Powell637d3372010-08-25 14:37:03 -07004663 final OverScroller scroller = mScroller;
4664 boolean more = scroller.computeScrollOffset();
4665 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004666
Adam Powell637d3372010-08-25 14:37:03 -07004667 // Flip sign to convert finger direction to list items direction
4668 // (e.g. finger moving down means list is moving towards the top)
4669 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004670
Adam Powell637d3372010-08-25 14:37:03 -07004671 // Pretend that each frame of a fling scroll is a touch scroll
4672 if (delta > 0) {
4673 // List is moving towards the top. Use first view as mMotionPosition
4674 mMotionPosition = mFirstPosition;
4675 final View firstView = getChildAt(0);
4676 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004677
Adam Powell637d3372010-08-25 14:37:03 -07004678 // Don't fling more than 1 screen
4679 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4680 } else {
4681 // List is moving towards the bottom. Use last view as mMotionPosition
4682 int offsetToLast = getChildCount() - 1;
4683 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004684
Adam Powell637d3372010-08-25 14:37:03 -07004685 final View lastView = getChildAt(offsetToLast);
4686 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004687
Adam Powell637d3372010-08-25 14:37:03 -07004688 // Don't fling more than 1 screen
4689 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4690 }
Adam Powell79ac3392010-01-28 21:22:20 -08004691
Adam Powell637d3372010-08-25 14:37:03 -07004692 // Check to see if we have bumped into the scroll limit
4693 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4694 int oldTop = 0;
4695 if (motionView != null) {
4696 oldTop = motionView.getTop();
4697 }
Adam Powell9d32d242010-03-29 16:02:07 -07004698
Adam Powell637d3372010-08-25 14:37:03 -07004699 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004700 final boolean atEdge = trackMotionScroll(delta, delta);
4701 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004702 if (atEnd) {
4703 if (motionView != null) {
4704 // Tweak the scroll for how far we overshot
4705 int overshoot = -(delta - (motionView.getTop() - oldTop));
4706 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4707 0, mOverflingDistance, false);
4708 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004709 if (more) {
4710 edgeReached(delta);
4711 }
Adam Powell637d3372010-08-25 14:37:03 -07004712 break;
4713 }
4714
4715 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004716 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004717 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004718 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004719 } else {
4720 endFling();
4721
4722 if (PROFILE_FLINGING) {
4723 if (mFlingProfilingStarted) {
4724 Debug.stopMethodTracing();
4725 mFlingProfilingStarted = false;
4726 }
4727
4728 if (mFlingStrictSpan != null) {
4729 mFlingStrictSpan.finish();
4730 mFlingStrictSpan = null;
4731 }
Adam Powell79ac3392010-01-28 21:22:20 -08004732 }
4733 }
Adam Powell637d3372010-08-25 14:37:03 -07004734 break;
4735 }
4736
4737 case TOUCH_MODE_OVERFLING: {
4738 final OverScroller scroller = mScroller;
4739 if (scroller.computeScrollOffset()) {
4740 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004741 final int currY = scroller.getCurrY();
4742 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004743 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4744 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004745 final boolean crossDown = scrollY <= 0 && currY > 0;
4746 final boolean crossUp = scrollY >= 0 && currY < 0;
4747 if (crossDown || crossUp) {
4748 int velocity = (int) scroller.getCurrVelocity();
4749 if (crossUp) velocity = -velocity;
4750
4751 // Don't flywheel from this; we're just continuing things.
4752 scroller.abortAnimation();
4753 start(velocity);
4754 } else {
4755 startSpringback();
4756 }
Adam Powell637d3372010-08-25 14:37:03 -07004757 } else {
4758 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004759 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004760 }
4761 } else {
4762 endFling();
4763 }
4764 break;
4765 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004766 }
4767 }
4768 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004769
Adam Powell45803472010-01-25 15:10:44 -08004770 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004771 * The amount of friction applied to flings. The default value
4772 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004773 */
4774 public void setFriction(float friction) {
4775 if (mFlingRunnable == null) {
4776 mFlingRunnable = new FlingRunnable();
4777 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004778 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004779 }
Romain Guy21317d12010-10-12 13:32:31 -07004780
4781 /**
4782 * Sets a scale factor for the fling velocity. The initial scale
4783 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004784 *
Romain Guy21317d12010-10-12 13:32:31 -07004785 * @param scale The scale factor to multiply the velocity by.
4786 */
4787 public void setVelocityScale(float scale) {
4788 mVelocityScale = scale;
4789 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004790
Romain Guy4bede9e2010-10-11 19:36:59 -07004791 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004792 * Override this for better control over position scrolling.
4793 */
4794 AbsPositionScroller createPositionScroller() {
4795 return new PositionScroller();
4796 }
4797
4798 /**
Adam Powell45803472010-01-25 15:10:44 -08004799 * Smoothly scroll to the specified adapter position. The view will
4800 * scroll such that the indicated position is displayed.
4801 * @param position Scroll to this adapter position.
4802 */
4803 public void smoothScrollToPosition(int position) {
4804 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004805 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004806 }
4807 mPositionScroller.start(position);
4808 }
Erik322171b2010-10-13 15:46:00 -07004809
4810 /**
4811 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004812 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004813 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4814 * the first or last item beyond the boundaries of the list) it will get as close
4815 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4816 *
4817 * @param position Position to scroll to
4818 * @param offset Desired distance in pixels of <code>position</code> from the top
4819 * of the view when scrolling is finished
4820 * @param duration Number of milliseconds to use for the scroll
4821 */
4822 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4823 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004824 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004825 }
4826 mPositionScroller.startWithOffset(position, offset, duration);
4827 }
4828
Adam Powell45803472010-01-25 15:10:44 -08004829 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004830 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004831 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004832 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4833 * the first or last item beyond the boundaries of the list) it will get as close
4834 * as possible.
4835 *
4836 * @param position Position to scroll to
4837 * @param offset Desired distance in pixels of <code>position</code> from the top
4838 * of the view when scrolling is finished
4839 */
4840 public void smoothScrollToPositionFromTop(int position, int offset) {
4841 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004842 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004843 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004844 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004845 }
4846
4847 /**
Adam Powell45803472010-01-25 15:10:44 -08004848 * Smoothly scroll to the specified adapter position. The view will
4849 * scroll such that the indicated position is displayed, but it will
4850 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004851 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004852 *
Adam Powell45803472010-01-25 15:10:44 -08004853 * @param position Scroll to this adapter position.
4854 * @param boundPosition Do not scroll if it would move this adapter
4855 * position out of view.
4856 */
4857 public void smoothScrollToPosition(int position, int boundPosition) {
4858 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004859 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004860 }
4861 mPositionScroller.start(position, boundPosition);
4862 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004863
Adam Powell45803472010-01-25 15:10:44 -08004864 /**
4865 * Smoothly scroll by distance pixels over duration milliseconds.
4866 * @param distance Distance to scroll in pixels.
4867 * @param duration Duration of the scroll animation in milliseconds.
4868 */
4869 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004870 smoothScrollBy(distance, duration, false);
4871 }
4872
4873 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004874 if (mFlingRunnable == null) {
4875 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004876 }
Adam Powell40322522011-01-12 21:58:20 -08004877
Marc Blank299acb52010-10-21 11:03:53 -07004878 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004879 final int firstPos = mFirstPosition;
4880 final int childCount = getChildCount();
4881 final int lastPos = firstPos + childCount;
4882 final int topLimit = getPaddingTop();
4883 final int bottomLimit = getHeight() - getPaddingBottom();
4884
Adam Powell79303752011-01-13 22:06:49 -08004885 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004886 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004887 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004888 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4889 mFlingRunnable.endFling();
4890 if (mPositionScroller != null) {
4891 mPositionScroller.stop();
4892 }
4893 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004894 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004895 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004896 }
Adam Powell45803472010-01-25 15:10:44 -08004897 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004898
Winson Chung499cb9f2010-07-16 11:18:17 -07004899 /**
4900 * Allows RemoteViews to scroll relatively to a position.
4901 */
4902 void smoothScrollByOffset(int position) {
4903 int index = -1;
4904 if (position < 0) {
4905 index = getFirstVisiblePosition();
4906 } else if (position > 0) {
4907 index = getLastVisiblePosition();
4908 }
4909
4910 if (index > -1) {
4911 View child = getChildAt(index - getFirstVisiblePosition());
4912 if (child != null) {
4913 Rect visibleRect = new Rect();
4914 if (child.getGlobalVisibleRect(visibleRect)) {
4915 // the child is partially visible
4916 int childRectArea = child.getWidth() * child.getHeight();
4917 int visibleRectArea = visibleRect.width() * visibleRect.height();
4918 float visibleArea = (visibleRectArea / (float) childRectArea);
4919 final float visibleThreshold = 0.75f;
4920 if ((position < 0) && (visibleArea < visibleThreshold)) {
4921 // the top index is not perceivably visible so offset
4922 // to account for showing that top index as well
4923 ++index;
4924 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4925 // the bottom index is not perceivably visible so offset
4926 // to account for showing that bottom index as well
4927 --index;
4928 }
4929 }
4930 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4931 }
4932 }
4933 }
4934
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004935 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004936 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004937 setChildrenDrawnWithCacheEnabled(true);
4938 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004939 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004940 }
4941 }
4942
4943 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004944 if (!isHardwareAccelerated()) {
4945 if (mClearScrollingCache == null) {
4946 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004947 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07004948 public void run() {
4949 if (mCachingStarted) {
4950 mCachingStarted = mCachingActive = false;
4951 setChildrenDrawnWithCacheEnabled(false);
4952 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4953 setChildrenDrawingCacheEnabled(false);
4954 }
4955 if (!isAlwaysDrawnWithCacheEnabled()) {
4956 invalidate();
4957 }
Romain Guy6dfed242009-05-11 18:25:05 -07004958 }
4959 }
Romain Guy9d849a22012-03-14 16:41:42 -07004960 };
4961 }
4962 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004963 }
4964 }
4965
4966 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07004967 * Scrolls the list items within the view by a specified number of pixels.
4968 *
4969 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07004970 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07004971 */
Alan Viveretteba299062013-09-03 16:01:51 -07004972 public void scrollListBy(int y) {
4973 trackMotionScroll(-y, -y);
4974 }
4975
4976 /**
4977 * Check if the items in the list can be scrolled in a certain direction.
4978 *
4979 * @param direction Negative to check scrolling up, positive to check
4980 * scrolling down.
4981 * @return true if the list can be scrolled in the specified direction,
4982 * false otherwise.
4983 * @see #scrollListBy(int)
4984 */
4985 public boolean canScrollList(int direction) {
4986 final int childCount = getChildCount();
4987 if (childCount == 0) {
4988 return false;
4989 }
4990
4991 final int firstPosition = mFirstPosition;
4992 final Rect listPadding = mListPadding;
4993 if (direction > 0) {
4994 final int lastBottom = getChildAt(childCount - 1).getBottom();
4995 final int lastPosition = firstPosition + childCount;
4996 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
4997 } else {
4998 final int firstTop = getChildAt(0).getTop();
4999 return firstPosition > 0 || firstTop < listPadding.top;
5000 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005001 }
5002
5003 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005004 * Track a motion scroll
5005 *
5006 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5007 * began. Positive numbers mean the user's finger is moving down the screen.
5008 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005009 * @return true if we're already at the beginning/end of the list and have nothing to do.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005010 */
Adam Powell45803472010-01-25 15:10:44 -08005011 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005012 final int childCount = getChildCount();
5013 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005014 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005015 }
5016
5017 final int firstTop = getChildAt(0).getTop();
5018 final int lastBottom = getChildAt(childCount - 1).getBottom();
5019
5020 final Rect listPadding = mListPadding;
5021
Adam Powellbdccc2d2010-12-14 17:34:27 -08005022 // "effective padding" In this case is the amount of padding that affects
5023 // how much space should not be filled by items. If we don't clip to padding
5024 // there is no effective padding.
5025 int effectivePaddingTop = 0;
5026 int effectivePaddingBottom = 0;
5027 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5028 effectivePaddingTop = listPadding.top;
5029 effectivePaddingBottom = listPadding.bottom;
5030 }
5031
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005032 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005033 final int spaceAbove = effectivePaddingTop - firstTop;
5034 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005035 final int spaceBelow = lastBottom - end;
5036
5037 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5038 if (deltaY < 0) {
5039 deltaY = Math.max(-(height - 1), deltaY);
5040 } else {
5041 deltaY = Math.min(height - 1, deltaY);
5042 }
5043
5044 if (incrementalDeltaY < 0) {
5045 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5046 } else {
5047 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5048 }
5049
Adam Powell45803472010-01-25 15:10:44 -08005050 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005051
Adam Powell637d3372010-08-25 14:37:03 -07005052 // Update our guesses for where the first and last views are
5053 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005054 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005055 } else {
5056 mFirstPositionDistanceGuess += incrementalDeltaY;
5057 }
5058 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005059 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005060 } else {
5061 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005062 }
Adam Powell45803472010-01-25 15:10:44 -08005063
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005064 final boolean cannotScrollDown = (firstPosition == 0 &&
5065 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5066 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5067 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005068
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005069 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005070 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005071 }
5072
5073 final boolean down = incrementalDeltaY < 0;
5074
Adam Powell029cfbd2010-03-08 19:03:54 -08005075 final boolean inTouchMode = isInTouchMode();
5076 if (inTouchMode) {
5077 hideSelector();
5078 }
Adam Powell45803472010-01-25 15:10:44 -08005079
5080 final int headerViewsCount = getHeaderViewsCount();
5081 final int footerViewsStart = mItemCount - getFooterViewsCount();
5082
5083 int start = 0;
5084 int count = 0;
5085
5086 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005087 int top = -incrementalDeltaY;
5088 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5089 top += listPadding.top;
5090 }
Adam Powell45803472010-01-25 15:10:44 -08005091 for (int i = 0; i < childCount; i++) {
5092 final View child = getChildAt(i);
5093 if (child.getBottom() >= top) {
5094 break;
5095 } else {
5096 count++;
5097 int position = firstPosition + i;
5098 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005099 // The view will be rebound to new data, clear any
5100 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005101 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005102 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005103 }
5104 }
5105 }
5106 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005107 int bottom = getHeight() - incrementalDeltaY;
5108 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5109 bottom -= listPadding.bottom;
5110 }
Adam Powell45803472010-01-25 15:10:44 -08005111 for (int i = childCount - 1; i >= 0; i--) {
5112 final View child = getChildAt(i);
5113 if (child.getTop() <= bottom) {
5114 break;
5115 } else {
5116 start = i;
5117 count++;
5118 int position = firstPosition + i;
5119 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005120 // The view will be rebound to new data, clear any
5121 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005122 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005123 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005124 }
5125 }
5126 }
5127 }
5128
5129 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5130
5131 mBlockLayoutRequests = true;
5132
5133 if (count > 0) {
5134 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005135 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005136 }
Adam Powell539ee872012-02-03 19:00:49 -08005137
Romain Guy9d849a22012-03-14 16:41:42 -07005138 // invalidate before moving the children to avoid unnecessary invalidate
5139 // calls to bubble up from the children all the way to the top
5140 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005141 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005142 }
5143
Adam Powell45803472010-01-25 15:10:44 -08005144 offsetChildrenTopAndBottom(incrementalDeltaY);
5145
5146 if (down) {
5147 mFirstPosition += count;
5148 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005149
Adam Powell45803472010-01-25 15:10:44 -08005150 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5151 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5152 fillGap(down);
5153 }
5154
Adam Powell029cfbd2010-03-08 19:03:54 -08005155 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005156 final int childIndex = mSelectedPosition - mFirstPosition;
5157 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005158 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08005159 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005160 } else if (mSelectorPosition != INVALID_POSITION) {
5161 final int childIndex = mSelectorPosition - mFirstPosition;
5162 if (childIndex >= 0 && childIndex < getChildCount()) {
5163 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5164 }
5165 } else {
5166 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005167 }
5168
Adam Powell45803472010-01-25 15:10:44 -08005169 mBlockLayoutRequests = false;
5170
5171 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005172
Adam Powell45803472010-01-25 15:10:44 -08005173 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005174 }
5175
5176 /**
5177 * Returns the number of header views in the list. Header views are special views
5178 * at the top of the list that should not be recycled during a layout.
5179 *
5180 * @return The number of header views, 0 in the default implementation.
5181 */
5182 int getHeaderViewsCount() {
5183 return 0;
5184 }
5185
5186 /**
5187 * Returns the number of footer views in the list. Footer views are special views
5188 * at the bottom of the list that should not be recycled during a layout.
5189 *
5190 * @return The number of footer views, 0 in the default implementation.
5191 */
5192 int getFooterViewsCount() {
5193 return 0;
5194 }
5195
5196 /**
5197 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5198 * remain on screen are shifted and the other ones are discarded. The role of this
5199 * method is to fill the gap thus created by performing a partial layout in the
5200 * empty space.
5201 *
5202 * @param down true if the scroll is going down, false if it is going up
5203 */
5204 abstract void fillGap(boolean down);
5205
5206 void hideSelector() {
5207 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005208 if (mLayoutMode != LAYOUT_SPECIFIC) {
5209 mResurrectToPosition = mSelectedPosition;
5210 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005211 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5212 mResurrectToPosition = mNextSelectedPosition;
5213 }
5214 setSelectedPositionInt(INVALID_POSITION);
5215 setNextSelectedPositionInt(INVALID_POSITION);
5216 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005217 }
5218 }
5219
5220 /**
5221 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5222 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5223 * of items available in the adapter
5224 */
5225 int reconcileSelectedPosition() {
5226 int position = mSelectedPosition;
5227 if (position < 0) {
5228 position = mResurrectToPosition;
5229 }
5230 position = Math.max(0, position);
5231 position = Math.min(position, mItemCount - 1);
5232 return position;
5233 }
5234
5235 /**
5236 * Find the row closest to y. This row will be used as the motion row when scrolling
5237 *
5238 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005239 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005240 */
5241 abstract int findMotionRow(int y);
5242
5243 /**
Adam Powell637d3372010-08-25 14:37:03 -07005244 * Find the row closest to y. This row will be used as the motion row when scrolling.
5245 *
5246 * @param y Where the user touched
5247 * @return The position of the first (or only) item in the row closest to y
5248 */
5249 int findClosestMotionRow(int y) {
5250 final int childCount = getChildCount();
5251 if (childCount == 0) {
5252 return INVALID_POSITION;
5253 }
5254
5255 final int motionRow = findMotionRow(y);
5256 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5257 }
5258
5259 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005260 * Causes all the views to be rebuilt and redrawn.
5261 */
5262 public void invalidateViews() {
5263 mDataChanged = true;
5264 rememberSyncState();
5265 requestLayout();
5266 invalidate();
5267 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005268
Jeff Brown4e6319b2010-12-13 10:36:51 -08005269 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005270 * If there is a selection returns false.
5271 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005272 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005273 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005274 if (mSelectedPosition < 0 && resurrectSelection()) {
5275 updateSelectorState();
5276 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005277 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005278 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005279 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005280
5281 /**
5282 * Makes the item at the supplied position selected.
5283 *
5284 * @param position the position of the new selection
5285 */
5286 abstract void setSelectionInt(int position);
5287
5288 /**
5289 * Attempt to bring the selection back if the user is switching from touch
5290 * to trackball mode
5291 * @return Whether selection was set to something.
5292 */
5293 boolean resurrectSelection() {
5294 final int childCount = getChildCount();
5295
5296 if (childCount <= 0) {
5297 return false;
5298 }
5299
5300 int selectedTop = 0;
5301 int selectedPos;
5302 int childrenTop = mListPadding.top;
5303 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5304 final int firstPosition = mFirstPosition;
5305 final int toPosition = mResurrectToPosition;
5306 boolean down = true;
5307
5308 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5309 selectedPos = toPosition;
5310
5311 final View selected = getChildAt(selectedPos - mFirstPosition);
5312 selectedTop = selected.getTop();
5313 int selectedBottom = selected.getBottom();
5314
5315 // We are scrolled, don't get in the fade
5316 if (selectedTop < childrenTop) {
5317 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5318 } else if (selectedBottom > childrenBottom) {
5319 selectedTop = childrenBottom - selected.getMeasuredHeight()
5320 - getVerticalFadingEdgeLength();
5321 }
5322 } else {
5323 if (toPosition < firstPosition) {
5324 // Default to selecting whatever is first
5325 selectedPos = firstPosition;
5326 for (int i = 0; i < childCount; i++) {
5327 final View v = getChildAt(i);
5328 final int top = v.getTop();
5329
5330 if (i == 0) {
5331 // Remember the position of the first item
5332 selectedTop = top;
5333 // See if we are scrolled at all
5334 if (firstPosition > 0 || top < childrenTop) {
5335 // If we are scrolled, don't select anything that is
5336 // in the fade region
5337 childrenTop += getVerticalFadingEdgeLength();
5338 }
5339 }
5340 if (top >= childrenTop) {
5341 // Found a view whose top is fully visisble
5342 selectedPos = firstPosition + i;
5343 selectedTop = top;
5344 break;
5345 }
5346 }
5347 } else {
5348 final int itemCount = mItemCount;
5349 down = false;
5350 selectedPos = firstPosition + childCount - 1;
5351
5352 for (int i = childCount - 1; i >= 0; i--) {
5353 final View v = getChildAt(i);
5354 final int top = v.getTop();
5355 final int bottom = v.getBottom();
5356
5357 if (i == childCount - 1) {
5358 selectedTop = top;
5359 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5360 childrenBottom -= getVerticalFadingEdgeLength();
5361 }
5362 }
5363
5364 if (bottom <= childrenBottom) {
5365 selectedPos = firstPosition + i;
5366 selectedTop = top;
5367 break;
5368 }
5369 }
5370 }
5371 }
5372
5373 mResurrectToPosition = INVALID_POSITION;
5374 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005375 if (mPositionScroller != null) {
5376 mPositionScroller.stop();
5377 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005378 mTouchMode = TOUCH_MODE_REST;
5379 clearScrollingCache();
5380 mSpecificTop = selectedTop;
5381 selectedPos = lookForSelectablePosition(selectedPos, down);
5382 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5383 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005384 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005385 setSelectionInt(selectedPos);
5386 invokeOnItemScrollListener();
5387 } else {
5388 selectedPos = INVALID_POSITION;
5389 }
5390 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5391
5392 return selectedPos >= 0;
5393 }
5394
Adam Powell14c08042011-10-06 19:46:18 -07005395 void confirmCheckedPositionsById() {
5396 // Clear out the positional check states, we'll rebuild it below from IDs.
5397 mCheckStates.clear();
5398
5399 boolean checkedCountChanged = false;
5400 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5401 final long id = mCheckedIdStates.keyAt(checkedIndex);
5402 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5403
5404 final long lastPosId = mAdapter.getItemId(lastPos);
5405 if (id != lastPosId) {
5406 // Look around to see if the ID is nearby. If not, uncheck it.
5407 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5408 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5409 boolean found = false;
5410 for (int searchPos = start; searchPos < end; searchPos++) {
5411 final long searchId = mAdapter.getItemId(searchPos);
5412 if (id == searchId) {
5413 found = true;
5414 mCheckStates.put(searchPos, true);
5415 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5416 break;
5417 }
5418 }
5419
5420 if (!found) {
5421 mCheckedIdStates.delete(id);
5422 checkedIndex--;
5423 mCheckedItemCount--;
5424 checkedCountChanged = true;
5425 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5426 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5427 lastPos, id, false);
5428 }
5429 }
5430 } else {
5431 mCheckStates.put(lastPos, true);
5432 }
5433 }
5434
5435 if (checkedCountChanged && mChoiceActionMode != null) {
5436 mChoiceActionMode.invalidate();
5437 }
5438 }
5439
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005440 @Override
5441 protected void handleDataChanged() {
5442 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005443 int lastHandledItemCount = mLastHandledItemCount;
5444 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005445
5446 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5447 confirmCheckedPositionsById();
5448 }
5449
Adam Powell539ee872012-02-03 19:00:49 -08005450 // TODO: In the future we can recycle these views based on stable ID instead.
5451 mRecycler.clearTransientStateViews();
5452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005453 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005454 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005455 int selectablePos;
5456
5457 // Find the row we are supposed to sync to
5458 if (mNeedSync) {
5459 // Update this first, since setNextSelectedPositionInt inspects it
5460 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005461 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005462
Adam Powell07852792010-11-10 16:57:05 -08005463 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005464 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5465 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005466 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5467 if (mForceTranscriptScroll) {
5468 mForceTranscriptScroll = false;
5469 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5470 return;
5471 }
Adam Powell07852792010-11-10 16:57:05 -08005472 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005473 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005474 final View lastChild = getChildAt(childCount - 1);
5475 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005476 if (mFirstPosition + childCount >= lastHandledItemCount &&
5477 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005478 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5479 return;
5480 }
5481 // Something new came in and we didn't scroll; give the user a clue that
5482 // there's something new.
5483 awakenScrollBars();
5484 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005485
5486 switch (mSyncMode) {
5487 case SYNC_SELECTED_POSITION:
5488 if (isInTouchMode()) {
5489 // We saved our state when not in touch mode. (We know this because
5490 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5491 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5492 // adjusting if the available range changed) and return.
5493 mLayoutMode = LAYOUT_SYNC;
5494 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5495
5496 return;
5497 } else {
5498 // See if we can find a position in the new data with the same
5499 // id as the old selection. This will change mSyncPosition.
5500 newPos = findSyncPosition();
5501 if (newPos >= 0) {
5502 // Found it. Now verify that new selection is still selectable
5503 selectablePos = lookForSelectablePosition(newPos, true);
5504 if (selectablePos == newPos) {
5505 // Same row id is selected
5506 mSyncPosition = newPos;
5507
5508 if (mSyncHeight == getHeight()) {
5509 // If we are at the same height as when we saved state, try
5510 // to restore the scroll position too.
5511 mLayoutMode = LAYOUT_SYNC;
5512 } else {
5513 // We are not the same height as when the selection was saved, so
5514 // don't try to restore the exact position
5515 mLayoutMode = LAYOUT_SET_SELECTION;
5516 }
5517
5518 // Restore selection
5519 setNextSelectedPositionInt(newPos);
5520 return;
5521 }
5522 }
5523 }
5524 break;
5525 case SYNC_FIRST_POSITION:
5526 // Leave mSyncPosition as it is -- just pin to available range
5527 mLayoutMode = LAYOUT_SYNC;
5528 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5529
5530 return;
5531 }
5532 }
5533
5534 if (!isInTouchMode()) {
5535 // We couldn't find matching data -- try to use the same position
5536 newPos = getSelectedItemPosition();
5537
5538 // Pin position to the available range
5539 if (newPos >= count) {
5540 newPos = count - 1;
5541 }
5542 if (newPos < 0) {
5543 newPos = 0;
5544 }
5545
5546 // Make sure we select something selectable -- first look down
5547 selectablePos = lookForSelectablePosition(newPos, true);
5548
5549 if (selectablePos >= 0) {
5550 setNextSelectedPositionInt(selectablePos);
5551 return;
5552 } else {
5553 // Looking down didn't work -- try looking up
5554 selectablePos = lookForSelectablePosition(newPos, false);
5555 if (selectablePos >= 0) {
5556 setNextSelectedPositionInt(selectablePos);
5557 return;
5558 }
5559 }
5560 } else {
5561
5562 // We already know where we want to resurrect the selection
5563 if (mResurrectToPosition >= 0) {
5564 return;
5565 }
5566 }
5567
5568 }
5569
5570 // Nothing is selected. Give up and reset everything.
5571 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5572 mSelectedPosition = INVALID_POSITION;
5573 mSelectedRowId = INVALID_ROW_ID;
5574 mNextSelectedPosition = INVALID_POSITION;
5575 mNextSelectedRowId = INVALID_ROW_ID;
5576 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005577 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005578 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005579 checkSelectionChanged();
5580 }
5581
Romain Guy43c9cdf2010-01-27 13:53:55 -08005582 @Override
5583 protected void onDisplayHint(int hint) {
5584 super.onDisplayHint(hint);
5585 switch (hint) {
5586 case INVISIBLE:
5587 if (mPopup != null && mPopup.isShowing()) {
5588 dismissPopup();
5589 }
5590 break;
5591 case VISIBLE:
5592 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5593 showPopup();
5594 }
5595 break;
5596 }
Romain Guy24562482010-02-01 14:56:19 -08005597 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005598 }
5599
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005600 /**
5601 * Removes the filter window
5602 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005603 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005604 if (mPopup != null) {
5605 mPopup.dismiss();
5606 }
5607 }
5608
5609 /**
5610 * Shows the filter window
5611 */
5612 private void showPopup() {
5613 // Make sure we have a window before showing the popup
5614 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005615 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005616 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005617 // Make sure we get focus if we are showing the popup
5618 checkFocus();
5619 }
5620 }
5621
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005622 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005623 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5624 final int[] xy = new int[2];
5625 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005626 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005627 // TODO: And the gravity should be defined in the theme as well
5628 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005629 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005630 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5631 xy[0], bottomGap);
5632 } else {
5633 mPopup.update(xy[0], bottomGap, -1, -1);
5634 }
5635 }
5636
5637 /**
5638 * What is the distance between the source and destination rectangles given the direction of
5639 * focus navigation between them? The direction basically helps figure out more quickly what is
5640 * self evident by the relationship between the rects...
5641 *
5642 * @param source the source rectangle
5643 * @param dest the destination rectangle
5644 * @param direction the direction
5645 * @return the distance between the rectangles
5646 */
5647 static int getDistance(Rect source, Rect dest, int direction) {
5648 int sX, sY; // source x, y
5649 int dX, dY; // dest x, y
5650 switch (direction) {
5651 case View.FOCUS_RIGHT:
5652 sX = source.right;
5653 sY = source.top + source.height() / 2;
5654 dX = dest.left;
5655 dY = dest.top + dest.height() / 2;
5656 break;
5657 case View.FOCUS_DOWN:
5658 sX = source.left + source.width() / 2;
5659 sY = source.bottom;
5660 dX = dest.left + dest.width() / 2;
5661 dY = dest.top;
5662 break;
5663 case View.FOCUS_LEFT:
5664 sX = source.left;
5665 sY = source.top + source.height() / 2;
5666 dX = dest.right;
5667 dY = dest.top + dest.height() / 2;
5668 break;
5669 case View.FOCUS_UP:
5670 sX = source.left + source.width() / 2;
5671 sY = source.top;
5672 dX = dest.left + dest.width() / 2;
5673 dY = dest.bottom;
5674 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005675 case View.FOCUS_FORWARD:
5676 case View.FOCUS_BACKWARD:
5677 sX = source.right + source.width() / 2;
5678 sY = source.top + source.height() / 2;
5679 dX = dest.left + dest.width() / 2;
5680 dY = dest.top + dest.height() / 2;
5681 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005682 default:
5683 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005684 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5685 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005686 }
5687 int deltaX = dX - sX;
5688 int deltaY = dY - sY;
5689 return deltaY * deltaY + deltaX * deltaX;
5690 }
5691
5692 @Override
5693 protected boolean isInFilterMode() {
5694 return mFiltered;
5695 }
5696
5697 /**
5698 * Sends a key to the text filter window
5699 *
5700 * @param keyCode The keycode for the event
5701 * @param event The actual key event
5702 *
5703 * @return True if the text filter handled the event, false otherwise.
5704 */
5705 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005706 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005707 return false;
5708 }
5709
5710 boolean handled = false;
5711 boolean okToSend = true;
5712 switch (keyCode) {
5713 case KeyEvent.KEYCODE_DPAD_UP:
5714 case KeyEvent.KEYCODE_DPAD_DOWN:
5715 case KeyEvent.KEYCODE_DPAD_LEFT:
5716 case KeyEvent.KEYCODE_DPAD_RIGHT:
5717 case KeyEvent.KEYCODE_DPAD_CENTER:
5718 case KeyEvent.KEYCODE_ENTER:
5719 okToSend = false;
5720 break;
5721 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005722 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005723 if (event.getAction() == KeyEvent.ACTION_DOWN
5724 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005725 KeyEvent.DispatcherState state = getKeyDispatcherState();
5726 if (state != null) {
5727 state.startTracking(event, this);
5728 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005729 handled = true;
5730 } else if (event.getAction() == KeyEvent.ACTION_UP
5731 && event.isTracking() && !event.isCanceled()) {
5732 handled = true;
5733 mTextFilter.setText("");
5734 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005735 }
5736 okToSend = false;
5737 break;
5738 case KeyEvent.KEYCODE_SPACE:
5739 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005740 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005741 break;
5742 }
5743
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005744 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005745 createTextFilter(true);
5746
5747 KeyEvent forwardEvent = event;
5748 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005749 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005750 }
5751
5752 int action = event.getAction();
5753 switch (action) {
5754 case KeyEvent.ACTION_DOWN:
5755 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5756 break;
5757
5758 case KeyEvent.ACTION_UP:
5759 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5760 break;
5761
5762 case KeyEvent.ACTION_MULTIPLE:
5763 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5764 break;
5765 }
5766 }
5767 return handled;
5768 }
5769
5770 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005771 * Return an InputConnection for editing of the filter text.
5772 */
5773 @Override
5774 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005775 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005776 if (mPublicInputConnection == null) {
5777 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005778 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005779 }
Romain Guyf6991302013-06-05 17:19:01 -07005780 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005781 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5782 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005783 }
5784 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005785 }
Romain Guy0a637162009-05-29 14:43:54 -07005786
Romain Guyf6991302013-06-05 17:19:01 -07005787 private class InputConnectionWrapper implements InputConnection {
5788 private final EditorInfo mOutAttrs;
5789 private InputConnection mTarget;
5790
5791 public InputConnectionWrapper(EditorInfo outAttrs) {
5792 mOutAttrs = outAttrs;
5793 }
5794
5795 private InputConnection getTarget() {
5796 if (mTarget == null) {
5797 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5798 }
5799 return mTarget;
5800 }
5801
5802 @Override
5803 public boolean reportFullscreenMode(boolean enabled) {
5804 // Use our own input connection, since it is
5805 // the "real" one the IME is talking with.
5806 return mDefInputConnection.reportFullscreenMode(enabled);
5807 }
5808
5809 @Override
5810 public boolean performEditorAction(int editorAction) {
5811 // The editor is off in its own window; we need to be
5812 // the one that does this.
5813 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005814 InputMethodManager imm =
5815 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005816 if (imm != null) {
5817 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5818 }
5819 return true;
5820 }
5821 return false;
5822 }
5823
5824 @Override
5825 public boolean sendKeyEvent(KeyEvent event) {
5826 // Use our own input connection, since the filter
5827 // text view may not be shown in a window so has
5828 // no ViewAncestor to dispatch events with.
5829 return mDefInputConnection.sendKeyEvent(event);
5830 }
5831
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005832 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005833 public CharSequence getTextBeforeCursor(int n, int flags) {
5834 if (mTarget == null) return "";
5835 return mTarget.getTextBeforeCursor(n, flags);
5836 }
5837
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005838 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005839 public CharSequence getTextAfterCursor(int n, int flags) {
5840 if (mTarget == null) return "";
5841 return mTarget.getTextAfterCursor(n, flags);
5842 }
5843
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005844 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005845 public CharSequence getSelectedText(int flags) {
5846 if (mTarget == null) return "";
5847 return mTarget.getSelectedText(flags);
5848 }
5849
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005850 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005851 public int getCursorCapsMode(int reqModes) {
5852 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5853 return mTarget.getCursorCapsMode(reqModes);
5854 }
5855
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005856 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005857 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5858 return getTarget().getExtractedText(request, flags);
5859 }
5860
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005861 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005862 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5863 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5864 }
5865
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005866 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08005867 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5868 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5869 }
5870
5871 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005872 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5873 return getTarget().setComposingText(text, newCursorPosition);
5874 }
5875
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005876 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005877 public boolean setComposingRegion(int start, int end) {
5878 return getTarget().setComposingRegion(start, end);
5879 }
5880
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005881 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005882 public boolean finishComposingText() {
5883 return mTarget == null || mTarget.finishComposingText();
5884 }
5885
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005886 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005887 public boolean commitText(CharSequence text, int newCursorPosition) {
5888 return getTarget().commitText(text, newCursorPosition);
5889 }
5890
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005891 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005892 public boolean commitCompletion(CompletionInfo text) {
5893 return getTarget().commitCompletion(text);
5894 }
5895
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005896 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005897 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5898 return getTarget().commitCorrection(correctionInfo);
5899 }
5900
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005901 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005902 public boolean setSelection(int start, int end) {
5903 return getTarget().setSelection(start, end);
5904 }
5905
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005906 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005907 public boolean performContextMenuAction(int id) {
5908 return getTarget().performContextMenuAction(id);
5909 }
5910
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005911 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005912 public boolean beginBatchEdit() {
5913 return getTarget().beginBatchEdit();
5914 }
5915
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005916 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005917 public boolean endBatchEdit() {
5918 return getTarget().endBatchEdit();
5919 }
5920
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005921 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005922 public boolean clearMetaKeyStates(int states) {
5923 return getTarget().clearMetaKeyStates(states);
5924 }
5925
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005926 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005927 public boolean performPrivateCommand(String action, Bundle data) {
5928 return getTarget().performPrivateCommand(action, data);
5929 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09005930
5931 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07005932 public boolean requestCursorUpdates(int cursorUpdateMode) {
5933 return getTarget().requestCursorUpdates(cursorUpdateMode);
5934 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08005935
5936 @Override
5937 public Handler getHandler() {
5938 return getTarget().getHandler();
5939 }
Romain Guyf6991302013-06-05 17:19:01 -07005940 }
5941
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005942 /**
5943 * For filtering we proxy an input connection to an internal text editor,
5944 * and this allows the proxying to happen.
5945 */
5946 @Override
5947 public boolean checkInputConnectionProxy(View view) {
5948 return view == mTextFilter;
5949 }
Romain Guy0a637162009-05-29 14:43:54 -07005950
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005951 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005952 * Creates the window for the text filter and populates it with an EditText field;
5953 *
5954 * @param animateEntrance true if the window should appear with an animation
5955 */
5956 private void createTextFilter(boolean animateEntrance) {
5957 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07005958 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005959 p.setFocusable(false);
5960 p.setTouchable(false);
5961 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07005962 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005963 p.setWidth(LayoutParams.WRAP_CONTENT);
5964 p.setHeight(LayoutParams.WRAP_CONTENT);
5965 p.setBackgroundDrawable(null);
5966 mPopup = p;
5967 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005968 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005969 }
5970 if (animateEntrance) {
5971 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5972 } else {
5973 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5974 }
5975 }
5976
Romain Guyf6991302013-06-05 17:19:01 -07005977 private EditText getTextFilterInput() {
5978 if (mTextFilter == null) {
5979 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
5980 mTextFilter = (EditText) layoutInflater.inflate(
5981 com.android.internal.R.layout.typing_filter, null);
5982 // For some reason setting this as the "real" input type changes
5983 // the text view in some way that it doesn't work, and I don't
5984 // want to figure out why this is.
5985 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5986 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5987 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5988 mTextFilter.addTextChangedListener(this);
5989 }
5990 return mTextFilter;
5991 }
5992
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005993 /**
5994 * Clear the text filter.
5995 */
5996 public void clearTextFilter() {
5997 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07005998 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005999 mFiltered = false;
6000 if (mPopup != null && mPopup.isShowing()) {
6001 dismissPopup();
6002 }
6003 }
6004 }
6005
6006 /**
6007 * Returns if the ListView currently has a text filter.
6008 */
6009 public boolean hasTextFilter() {
6010 return mFiltered;
6011 }
6012
Alan Viverette8fa327a2013-05-31 14:53:13 -07006013 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006014 public void onGlobalLayout() {
6015 if (isShown()) {
6016 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006017 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006018 showPopup();
6019 }
6020 } else {
6021 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006022 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006023 dismissPopup();
6024 }
6025 }
6026
6027 }
6028
6029 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006030 * For our text watcher that is associated with the text filter. Does
6031 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006032 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006033 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006034 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6035 }
6036
6037 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006038 * For our text watcher that is associated with the text filter. Performs
6039 * the actual filtering as the text changes, and takes care of hiding and
6040 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006041 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006042 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006043 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006044 if (isTextFilterEnabled()) {
6045 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006046 int length = s.length();
6047 boolean showing = mPopup.isShowing();
6048 if (!showing && length > 0) {
6049 // Show the filter popup if necessary
6050 showPopup();
6051 mFiltered = true;
6052 } else if (showing && length == 0) {
6053 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006054 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006055 mFiltered = false;
6056 }
6057 if (mAdapter instanceof Filterable) {
6058 Filter f = ((Filterable) mAdapter).getFilter();
6059 // Filter should not be null when we reach this part
6060 if (f != null) {
6061 f.filter(s, this);
6062 } else {
6063 throw new IllegalStateException("You cannot call onTextChanged with a non "
6064 + "filterable adapter");
6065 }
6066 }
6067 }
6068 }
6069
6070 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006071 * For our text watcher that is associated with the text filter. Does
6072 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006073 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006074 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006075 public void afterTextChanged(Editable s) {
6076 }
6077
Alan Viverette8fa327a2013-05-31 14:53:13 -07006078 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006079 public void onFilterComplete(int count) {
6080 if (mSelectedPosition < 0 && count > 0) {
6081 mResurrectToPosition = INVALID_POSITION;
6082 resurrectSelection();
6083 }
6084 }
6085
6086 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006087 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6088 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6089 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6090 }
6091
6092 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006093 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6094 return new LayoutParams(p);
6095 }
6096
6097 @Override
6098 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6099 return new AbsListView.LayoutParams(getContext(), attrs);
6100 }
6101
6102 @Override
6103 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6104 return p instanceof AbsListView.LayoutParams;
6105 }
6106
6107 /**
6108 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6109 * to the bottom to show new items.
6110 *
6111 * @param mode the transcript mode to set
6112 *
6113 * @see #TRANSCRIPT_MODE_DISABLED
6114 * @see #TRANSCRIPT_MODE_NORMAL
6115 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6116 */
6117 public void setTranscriptMode(int mode) {
6118 mTranscriptMode = mode;
6119 }
6120
6121 /**
6122 * Returns the current transcript mode.
6123 *
6124 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6125 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6126 */
6127 public int getTranscriptMode() {
6128 return mTranscriptMode;
6129 }
6130
6131 @Override
6132 public int getSolidColor() {
6133 return mCacheColorHint;
6134 }
6135
6136 /**
6137 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
Daisuke Miyakawa5be37c82010-08-31 16:37:33 -07006138 * on top of a solid, single-color, opaque background.
6139 *
6140 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6141 * single color. This hint will not affect any existing background drawable set on this view (
6142 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006143 *
6144 * @param color The background color
6145 */
Tor Norbye80756e32015-03-02 09:39:27 -08006146 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006147 if (color != mCacheColorHint) {
6148 mCacheColorHint = color;
6149 int count = getChildCount();
6150 for (int i = 0; i < count; i++) {
6151 getChildAt(i).setDrawingCacheBackgroundColor(color);
6152 }
6153 mRecycler.setCacheColorHint(color);
6154 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006155 }
6156
6157 /**
6158 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6159 * on top of a solid, single-color, opaque background
6160 *
6161 * @return The cache color hint
6162 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006163 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006164 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006165 public int getCacheColorHint() {
6166 return mCacheColorHint;
6167 }
6168
6169 /**
6170 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6171 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6172 * internal view recycler.
6173 *
6174 * @param views A list into which to put the reclaimed views
6175 */
6176 public void reclaimViews(List<View> views) {
6177 int childCount = getChildCount();
6178 RecyclerListener listener = mRecycler.mRecyclerListener;
6179
6180 // Reclaim views on screen
6181 for (int i = 0; i < childCount; i++) {
6182 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006183 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006184 // Don't reclaim header or footer views, or views that should be ignored
6185 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6186 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006187 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006188 if (listener != null) {
6189 // Pretend they went through the scrap heap
6190 listener.onMovedToScrapHeap(child);
6191 }
6192 }
6193 }
6194 mRecycler.reclaimScrapViews(views);
6195 removeAllViewsInLayout();
6196 }
6197
Adam Powell637d3372010-08-25 14:37:03 -07006198 private void finishGlows() {
6199 if (mEdgeGlowTop != null) {
6200 mEdgeGlowTop.finish();
6201 mEdgeGlowBottom.finish();
6202 }
6203 }
6204
Romain Guy13922e02009-05-12 17:56:14 -07006205 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006206 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6207 * through the specified intent.
6208 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6209 */
6210 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006211 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6212 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006213 if (mRemoteAdapter != null) {
6214 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6215 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6216 mRemoteAdapter.getRemoteViewsServiceIntent());
6217 if (fcNew.equals(fcOld)) {
6218 return;
6219 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006220 }
Adam Cohen2148d432011-07-28 14:59:54 -07006221 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006222 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07006223 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07006224 if (mRemoteAdapter.isDataReady()) {
6225 setAdapter(mRemoteAdapter);
6226 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006227 }
6228
6229 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006230 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006231 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006232 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006233 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006234 * @hide
6235 */
6236 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6237 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6238 // service handling the specified intent.
6239 if (mRemoteAdapter != null) {
6240 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6241 }
6242 }
6243
6244 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006245 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6246 * connected yet.
6247 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006248 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006249 public void deferNotifyDataSetChanged() {
6250 mDeferNotifyDataSetChanged = true;
6251 }
6252
6253 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006254 * Called back when the adapter connects to the RemoteViewsService.
6255 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006256 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006257 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006258 if (mRemoteAdapter != mAdapter) {
6259 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006260 if (mDeferNotifyDataSetChanged) {
6261 mRemoteAdapter.notifyDataSetChanged();
6262 mDeferNotifyDataSetChanged = false;
6263 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006264 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006265 } else if (mRemoteAdapter != null) {
6266 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006267 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006268 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006269 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006270 }
6271
6272 /**
6273 * Called back when the adapter disconnects from the RemoteViewsService.
6274 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006275 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006276 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006277 // If the remote adapter disconnects, we keep it around
6278 // since the currently displayed items are still cached.
6279 // Further, we want the service to eventually reconnect
6280 // when necessary, as triggered by this view requesting
6281 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006282 }
6283
6284 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006285 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6286 * being displayed by the AbsListView.
6287 */
6288 void setVisibleRangeHint(int start, int end) {
6289 if (mRemoteAdapter != null) {
6290 mRemoteAdapter.setVisibleRangeHint(start, end);
6291 }
6292 }
6293
6294 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006295 * Sets the recycler listener to be notified whenever a View is set aside in
6296 * the recycler for later reuse. This listener can be used to free resources
6297 * associated to the View.
6298 *
6299 * @param listener The recycler listener to be notified of views set aside
6300 * in the recycler.
6301 *
6302 * @see android.widget.AbsListView.RecycleBin
6303 * @see android.widget.AbsListView.RecyclerListener
6304 */
6305 public void setRecyclerListener(RecyclerListener listener) {
6306 mRecycler.mRecyclerListener = listener;
6307 }
6308
Adam Powellb1f498a2011-01-18 20:43:23 -08006309 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6310 @Override
6311 public void onChanged() {
6312 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006313 if (mFastScroll != null) {
6314 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006315 }
6316 }
6317
6318 @Override
6319 public void onInvalidated() {
6320 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006321 if (mFastScroll != null) {
6322 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006323 }
6324 }
6325 }
6326
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006327 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006328 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6329 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6330 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6331 * selects and deselects list items.
6332 */
6333 public interface MultiChoiceModeListener extends ActionMode.Callback {
6334 /**
6335 * Called when an item is checked or unchecked during selection mode.
6336 *
6337 * @param mode The {@link ActionMode} providing the selection mode
6338 * @param position Adapter position of the item that was checked or unchecked
6339 * @param id Adapter ID of the item that was checked or unchecked
6340 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6341 * if the item is now unchecked.
6342 */
6343 public void onItemCheckedStateChanged(ActionMode mode,
6344 int position, long id, boolean checked);
6345 }
6346
6347 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6348 private MultiChoiceModeListener mWrapped;
6349
6350 public void setWrapped(MultiChoiceModeListener wrapped) {
6351 mWrapped = wrapped;
6352 }
6353
Adam Powella7981702012-08-24 12:43:41 -07006354 public boolean hasWrappedCallback() {
6355 return mWrapped != null;
6356 }
6357
Alan Viverette8fa327a2013-05-31 14:53:13 -07006358 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006359 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6360 if (mWrapped.onCreateActionMode(mode, menu)) {
6361 // Initialize checked graphic state?
6362 setLongClickable(false);
6363 return true;
6364 }
6365 return false;
6366 }
6367
Alan Viverette8fa327a2013-05-31 14:53:13 -07006368 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006369 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6370 return mWrapped.onPrepareActionMode(mode, menu);
6371 }
6372
Alan Viverette8fa327a2013-05-31 14:53:13 -07006373 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006374 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6375 return mWrapped.onActionItemClicked(mode, item);
6376 }
6377
Alan Viverette8fa327a2013-05-31 14:53:13 -07006378 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006379 public void onDestroyActionMode(ActionMode mode) {
6380 mWrapped.onDestroyActionMode(mode);
6381 mChoiceActionMode = null;
6382
6383 // Ending selection mode means deselecting everything.
6384 clearChoices();
6385
6386 mDataChanged = true;
6387 rememberSyncState();
6388 requestLayout();
6389
6390 setLongClickable(true);
6391 }
6392
Alan Viverette8fa327a2013-05-31 14:53:13 -07006393 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006394 public void onItemCheckedStateChanged(ActionMode mode,
6395 int position, long id, boolean checked) {
6396 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6397
6398 // If there are no items selected we no longer need the selection mode.
6399 if (getCheckedItemCount() == 0) {
6400 mode.finish();
6401 }
6402 }
6403 }
6404
6405 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006406 * AbsListView extends LayoutParams to provide a place to hold the view type.
6407 */
6408 public static class LayoutParams extends ViewGroup.LayoutParams {
6409 /**
6410 * View type for this view, as returned by
6411 * {@link android.widget.Adapter#getItemViewType(int) }
6412 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006413 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006414 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6415 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6416 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006417 int viewType;
6418
The Android Open Source Project4df24232009-03-05 14:34:35 -08006419 /**
6420 * When this boolean is set, the view has been added to the AbsListView
6421 * at least once. It is used to know whether headers/footers have already
6422 * been added to the list view and whether they should be treated as
6423 * recycled views or not.
6424 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006425 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006426 boolean recycledHeaderFooter;
6427
Romain Guy0bf88592010-03-02 13:38:44 -08006428 /**
6429 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6430 * to obtain children views to measure itself. When doing so, the children
6431 * are not attached to the window, but put in the recycler which assumes
6432 * they've been attached before. Setting this flag will force the reused
6433 * view to be attached to the window rather than just attached to the
6434 * parent.
6435 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006436 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006437 boolean forceAdd;
6438
Dianne Hackborn079e2352010-10-18 17:02:43 -07006439 /**
6440 * The position the view was removed from when pulled out of the
6441 * scrap heap.
6442 * @hide
6443 */
6444 int scrappedFromPosition;
6445
Adam Powell539ee872012-02-03 19:00:49 -08006446 /**
6447 * The ID the view represents
6448 */
6449 long itemId = -1;
6450
Alan Viverette92539d52015-09-14 10:49:25 -04006451 /** Whether the adapter considers the item enabled. */
6452 boolean isEnabled;
6453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006454 public LayoutParams(Context c, AttributeSet attrs) {
6455 super(c, attrs);
6456 }
6457
6458 public LayoutParams(int w, int h) {
6459 super(w, h);
6460 }
6461
6462 public LayoutParams(int w, int h, int viewType) {
6463 super(w, h);
6464 this.viewType = viewType;
6465 }
6466
6467 public LayoutParams(ViewGroup.LayoutParams source) {
6468 super(source);
6469 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006470
6471 /** @hide */
6472 @Override
6473 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6474 super.encodeProperties(encoder);
6475
6476 encoder.addProperty("list:viewType", viewType);
6477 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6478 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006479 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006480 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006481 }
6482
6483 /**
6484 * A RecyclerListener is used to receive a notification whenever a View is placed
6485 * inside the RecycleBin's scrap heap. This listener is used to free resources
6486 * associated to Views placed in the RecycleBin.
6487 *
6488 * @see android.widget.AbsListView.RecycleBin
6489 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6490 */
6491 public static interface RecyclerListener {
6492 /**
6493 * Indicates that the specified View was moved into the recycler's scrap heap.
6494 * The view is not displayed on screen any more and any expensive resource
6495 * associated with the view should be discarded.
6496 *
6497 * @param view
6498 */
6499 void onMovedToScrapHeap(View view);
6500 }
6501
6502 /**
6503 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6504 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6505 * start of a layout. By construction, they are displaying current information. At the end of
6506 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6507 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6508 *
6509 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6510 * @see android.widget.AbsListView.RecyclerListener
6511 */
6512 class RecycleBin {
6513 private RecyclerListener mRecyclerListener;
6514
6515 /**
6516 * The position of the first view stored in mActiveViews.
6517 */
6518 private int mFirstActivePosition;
6519
6520 /**
6521 * Views that were on screen at the start of layout. This array is populated at the start of
6522 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6523 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6524 * view store in mFirstActivePosition.
6525 */
6526 private View[] mActiveViews = new View[0];
6527
6528 /**
6529 * Unsorted views that can be used by the adapter as a convert view.
6530 */
6531 private ArrayList<View>[] mScrapViews;
6532
6533 private int mViewTypeCount;
6534
6535 private ArrayList<View> mCurrentScrap;
6536
Adam Powell539ee872012-02-03 19:00:49 -08006537 private ArrayList<View> mSkippedScrap;
6538
6539 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006540 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006541
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006542 public void setViewTypeCount(int viewTypeCount) {
6543 if (viewTypeCount < 1) {
6544 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6545 }
6546 //noinspection unchecked
6547 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6548 for (int i = 0; i < viewTypeCount; i++) {
6549 scrapViews[i] = new ArrayList<View>();
6550 }
6551 mViewTypeCount = viewTypeCount;
6552 mCurrentScrap = scrapViews[0];
6553 mScrapViews = scrapViews;
6554 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006555
Adam Powellf3c2eda2010-03-16 17:31:01 -07006556 public void markChildrenDirty() {
6557 if (mViewTypeCount == 1) {
6558 final ArrayList<View> scrap = mCurrentScrap;
6559 final int scrapCount = scrap.size();
6560 for (int i = 0; i < scrapCount; i++) {
6561 scrap.get(i).forceLayout();
6562 }
6563 } else {
6564 final int typeCount = mViewTypeCount;
6565 for (int i = 0; i < typeCount; i++) {
6566 final ArrayList<View> scrap = mScrapViews[i];
6567 final int scrapCount = scrap.size();
6568 for (int j = 0; j < scrapCount; j++) {
6569 scrap.get(j).forceLayout();
6570 }
6571 }
6572 }
Adam Powell539ee872012-02-03 19:00:49 -08006573 if (mTransientStateViews != null) {
6574 final int count = mTransientStateViews.size();
6575 for (int i = 0; i < count; i++) {
6576 mTransientStateViews.valueAt(i).forceLayout();
6577 }
6578 }
Chet Haase72871322013-02-26 16:12:13 -07006579 if (mTransientStateViewsById != null) {
6580 final int count = mTransientStateViewsById.size();
6581 for (int i = 0; i < count; i++) {
6582 mTransientStateViewsById.valueAt(i).forceLayout();
6583 }
6584 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006585 }
Romain Guy0a637162009-05-29 14:43:54 -07006586
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006587 public boolean shouldRecycleViewType(int viewType) {
6588 return viewType >= 0;
6589 }
6590
6591 /**
6592 * Clears the scrap heap.
6593 */
6594 void clear() {
6595 if (mViewTypeCount == 1) {
6596 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006597 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006598 } else {
6599 final int typeCount = mViewTypeCount;
6600 for (int i = 0; i < typeCount; i++) {
6601 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006602 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006603 }
6604 }
Alan Viverette59511502013-12-09 13:49:25 -08006605
6606 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006607 }
6608
6609 /**
6610 * Fill ActiveViews with all of the children of the AbsListView.
6611 *
6612 * @param childCount The minimum number of views mActiveViews should hold
6613 * @param firstActivePosition The position of the first view that will be stored in
6614 * mActiveViews
6615 */
6616 void fillActiveViews(int childCount, int firstActivePosition) {
6617 if (mActiveViews.length < childCount) {
6618 mActiveViews = new View[childCount];
6619 }
6620 mFirstActivePosition = firstActivePosition;
6621
Romain Guyf6991302013-06-05 17:19:01 -07006622 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006623 final View[] activeViews = mActiveViews;
6624 for (int i = 0; i < childCount; i++) {
6625 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006626 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006627 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006628 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006629 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6630 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006631 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006632 // Remember the position so that setupChild() doesn't reset state.
6633 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006634 }
6635 }
6636 }
6637
6638 /**
6639 * Get the view corresponding to the specified position. The view will be removed from
6640 * mActiveViews if it is found.
6641 *
6642 * @param position The position to look up in mActiveViews
6643 * @return The view if it is found, null otherwise
6644 */
6645 View getActiveView(int position) {
6646 int index = position - mFirstActivePosition;
6647 final View[] activeViews = mActiveViews;
6648 if (index >=0 && index < activeViews.length) {
6649 final View match = activeViews[index];
6650 activeViews[index] = null;
6651 return match;
6652 }
6653 return null;
6654 }
6655
Adam Powell539ee872012-02-03 19:00:49 -08006656 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006657 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6658 long id = mAdapter.getItemId(position);
6659 View result = mTransientStateViewsById.get(id);
6660 mTransientStateViewsById.remove(id);
6661 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006662 }
Chet Haase72871322013-02-26 16:12:13 -07006663 if (mTransientStateViews != null) {
6664 final int index = mTransientStateViews.indexOfKey(position);
6665 if (index >= 0) {
6666 View result = mTransientStateViews.valueAt(index);
6667 mTransientStateViews.removeAt(index);
6668 return result;
6669 }
Adam Powell539ee872012-02-03 19:00:49 -08006670 }
Chet Haase72871322013-02-26 16:12:13 -07006671 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006672 }
6673
6674 /**
Alan Viverette59511502013-12-09 13:49:25 -08006675 * Dumps and fully detaches any currently saved views with transient
6676 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006677 */
6678 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006679 final SparseArray<View> viewsByPos = mTransientStateViews;
6680 if (viewsByPos != null) {
6681 final int N = viewsByPos.size();
6682 for (int i = 0; i < N; i++) {
6683 removeDetachedView(viewsByPos.valueAt(i), false);
6684 }
6685 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006686 }
Alan Viverette59511502013-12-09 13:49:25 -08006687
6688 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6689 if (viewsById != null) {
6690 final int N = viewsById.size();
6691 for (int i = 0; i < N; i++) {
6692 removeDetachedView(viewsById.valueAt(i), false);
6693 }
6694 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006695 }
Adam Powell539ee872012-02-03 19:00:49 -08006696 }
6697
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006698 /**
6699 * @return A view from the ScrapViews collection. These are unordered.
6700 */
6701 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006702 final int whichScrap = mAdapter.getItemViewType(position);
6703 if (whichScrap < 0) {
6704 return null;
6705 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006706 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006707 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006708 } else if (whichScrap < mScrapViews.length) {
6709 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006710 }
6711 return null;
6712 }
6713
6714 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006715 * Puts a view into the list of scrap views.
6716 * <p>
6717 * If the list data hasn't changed or the adapter has stable IDs, views
6718 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006719 *
6720 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006721 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006722 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006723 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006724 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006725 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006726 // Can't recycle, but we don't know anything about the view.
6727 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006728 return;
6729 }
6730
Adam Powell539ee872012-02-03 19:00:49 -08006731 lp.scrappedFromPosition = position;
6732
Alan Viverette1e51cc72013-09-27 14:32:20 -07006733 // Remove but don't scrap header or footer views, or views that
6734 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006735 final int viewType = lp.viewType;
6736 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006737 // Can't recycle. If it's not a header or footer, which have
6738 // special handling and should be ignored, then skip the scrap
6739 // heap and we'll fully detach the view later.
6740 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6741 getSkippedScrap().add(scrap);
6742 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006743 return;
6744 }
6745
6746 scrap.dispatchStartTemporaryDetach();
6747
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006748 // The the accessibility state of the view may change while temporary
6749 // detached and we do not allow detached views to fire accessibility
6750 // events. So we are announcing that the subtree changed giving a chance
6751 // to clients holding on to a view in this subtree to refresh it.
6752 notifyViewAccessibilityStateChangedIfNeeded(
6753 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6754
Alan Viveretted44696c2013-07-18 10:37:15 -07006755 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006756 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006757 if (scrapHasTransientState) {
6758 if (mAdapter != null && mAdapterHasStableIds) {
6759 // If the adapter has stable IDs, we can reuse the view for
6760 // the same data.
6761 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006762 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006763 }
6764 mTransientStateViewsById.put(lp.itemId, scrap);
6765 } else if (!mDataChanged) {
6766 // If the data hasn't changed, we can reuse the views at
6767 // their old positions.
6768 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006769 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006770 }
6771 mTransientStateViews.put(position, scrap);
6772 } else {
6773 // Otherwise, we'll have to remove the view and start over.
Alan Viverette8bbae342015-06-25 14:49:29 -07006774 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006775 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006776 } else {
Alan Viveretted44696c2013-07-18 10:37:15 -07006777 if (mViewTypeCount == 1) {
6778 mCurrentScrap.add(scrap);
6779 } else {
6780 mScrapViews[viewType].add(scrap);
6781 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006782
Alan Viveretted44696c2013-07-18 10:37:15 -07006783 if (mRecyclerListener != null) {
6784 mRecyclerListener.onMovedToScrapHeap(scrap);
6785 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006786 }
6787 }
6788
Alan Viverette8bbae342015-06-25 14:49:29 -07006789 private ArrayList<View> getSkippedScrap() {
6790 if (mSkippedScrap == null) {
6791 mSkippedScrap = new ArrayList<>();
6792 }
6793 return mSkippedScrap;
6794 }
6795
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006796 /**
Adam Powell539ee872012-02-03 19:00:49 -08006797 * Finish the removal of any views that skipped the scrap heap.
6798 */
6799 void removeSkippedScrap() {
6800 if (mSkippedScrap == null) {
6801 return;
6802 }
6803 final int count = mSkippedScrap.size();
6804 for (int i = 0; i < count; i++) {
6805 removeDetachedView(mSkippedScrap.get(i), false);
6806 }
6807 mSkippedScrap.clear();
6808 }
6809
6810 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006811 * Move all views remaining in mActiveViews to mScrapViews.
6812 */
6813 void scrapActiveViews() {
6814 final View[] activeViews = mActiveViews;
6815 final boolean hasListener = mRecyclerListener != null;
6816 final boolean multipleScraps = mViewTypeCount > 1;
6817
6818 ArrayList<View> scrapViews = mCurrentScrap;
6819 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006820 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006821 final View victim = activeViews[i];
6822 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006823 final AbsListView.LayoutParams lp
6824 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006825 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006826
6827 activeViews[i] = null;
6828
Alan Viverette59511502013-12-09 13:49:25 -08006829 if (victim.hasTransientState()) {
6830 // Store views with transient state for later use.
6831 victim.dispatchStartTemporaryDetach();
6832
6833 if (mAdapter != null && mAdapterHasStableIds) {
6834 if (mTransientStateViewsById == null) {
6835 mTransientStateViewsById = new LongSparseArray<View>();
6836 }
6837 long id = mAdapter.getItemId(mFirstActivePosition + i);
6838 mTransientStateViewsById.put(id, victim);
6839 } else if (!mDataChanged) {
6840 if (mTransientStateViews == null) {
6841 mTransientStateViews = new SparseArray<View>();
6842 }
6843 mTransientStateViews.put(mFirstActivePosition + i, victim);
6844 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6845 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006846 removeDetachedView(victim, false);
6847 }
Alan Viverette59511502013-12-09 13:49:25 -08006848 } else if (!shouldRecycleViewType(whichScrap)) {
6849 // Discard non-recyclable views except headers/footers.
6850 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6851 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006852 }
Alan Viverette59511502013-12-09 13:49:25 -08006853 } else {
6854 // Store everything else on the appropriate scrap heap.
6855 if (multipleScraps) {
6856 scrapViews = mScrapViews[whichScrap];
6857 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006858
Alan Viverette59511502013-12-09 13:49:25 -08006859 victim.dispatchStartTemporaryDetach();
6860 lp.scrappedFromPosition = mFirstActivePosition + i;
6861 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006862
Alan Viverette59511502013-12-09 13:49:25 -08006863 if (hasListener) {
6864 mRecyclerListener.onMovedToScrapHeap(victim);
6865 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006866 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006867 }
6868 }
6869
6870 pruneScrapViews();
6871 }
6872
6873 /**
Alan Viverette59511502013-12-09 13:49:25 -08006874 * Makes sure that the size of mScrapViews does not exceed the size of
6875 * mActiveViews, which can happen if an adapter does not recycle its
6876 * views. Removes cached transient state views that no longer have
6877 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006878 */
6879 private void pruneScrapViews() {
6880 final int maxViews = mActiveViews.length;
6881 final int viewTypeCount = mViewTypeCount;
6882 final ArrayList<View>[] scrapViews = mScrapViews;
6883 for (int i = 0; i < viewTypeCount; ++i) {
6884 final ArrayList<View> scrapPile = scrapViews[i];
6885 int size = scrapPile.size();
6886 final int extras = size - maxViews;
6887 size--;
6888 for (int j = 0; j < extras; j++) {
6889 removeDetachedView(scrapPile.remove(size--), false);
6890 }
6891 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006892
Alan Viverette59511502013-12-09 13:49:25 -08006893 final SparseArray<View> transViewsByPos = mTransientStateViews;
6894 if (transViewsByPos != null) {
6895 for (int i = 0; i < transViewsByPos.size(); i++) {
6896 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07006897 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08006898 removeDetachedView(v, false);
6899 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07006900 i--;
6901 }
6902 }
6903 }
Alan Viverette59511502013-12-09 13:49:25 -08006904
6905 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
6906 if (transViewsById != null) {
6907 for (int i = 0; i < transViewsById.size(); i++) {
6908 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07006909 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08006910 removeDetachedView(v, false);
6911 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07006912 i--;
6913 }
6914 }
6915 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006916 }
6917
6918 /**
6919 * Puts all views in the scrap heap into the supplied list.
6920 */
6921 void reclaimScrapViews(List<View> views) {
6922 if (mViewTypeCount == 1) {
6923 views.addAll(mCurrentScrap);
6924 } else {
6925 final int viewTypeCount = mViewTypeCount;
6926 final ArrayList<View>[] scrapViews = mScrapViews;
6927 for (int i = 0; i < viewTypeCount; ++i) {
6928 final ArrayList<View> scrapPile = scrapViews[i];
6929 views.addAll(scrapPile);
6930 }
6931 }
6932 }
Romain Guy52e2ef82010-01-14 12:11:48 -08006933
6934 /**
6935 * Updates the cache color hint of all known views.
6936 *
6937 * @param color The new cache color hint.
6938 */
6939 void setCacheColorHint(int color) {
6940 if (mViewTypeCount == 1) {
6941 final ArrayList<View> scrap = mCurrentScrap;
6942 final int scrapCount = scrap.size();
6943 for (int i = 0; i < scrapCount; i++) {
6944 scrap.get(i).setDrawingCacheBackgroundColor(color);
6945 }
6946 } else {
6947 final int typeCount = mViewTypeCount;
6948 for (int i = 0; i < typeCount; i++) {
6949 final ArrayList<View> scrap = mScrapViews[i];
6950 final int scrapCount = scrap.size();
6951 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07006952 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08006953 }
6954 }
6955 }
6956 // Just in case this is called during a layout pass
6957 final View[] activeViews = mActiveViews;
6958 final int count = activeViews.length;
6959 for (int i = 0; i < count; ++i) {
6960 final View victim = activeViews[i];
6961 if (victim != null) {
6962 victim.setDrawingCacheBackgroundColor(color);
6963 }
6964 }
6965 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006966
Alan Viverette3e141622014-02-18 17:05:13 -08006967 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6968 final int size = scrapViews.size();
6969 if (size > 0) {
6970 // See if we still have a view for this position or ID.
6971 for (int i = 0; i < size; i++) {
6972 final View view = scrapViews.get(i);
6973 final AbsListView.LayoutParams params =
6974 (AbsListView.LayoutParams) view.getLayoutParams();
6975
6976 if (mAdapterHasStableIds) {
6977 final long id = mAdapter.getItemId(position);
6978 if (id == params.itemId) {
6979 return scrapViews.remove(i);
6980 }
6981 } else if (params.scrappedFromPosition == position) {
6982 final View scrap = scrapViews.remove(i);
6983 clearAccessibilityFromScrap(scrap);
6984 return scrap;
6985 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006986 }
Alan Viverette3e141622014-02-18 17:05:13 -08006987 final View scrap = scrapViews.remove(size - 1);
6988 clearAccessibilityFromScrap(scrap);
6989 return scrap;
6990 } else {
6991 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07006992 }
Alan Viverette3e141622014-02-18 17:05:13 -08006993 }
6994
6995 private void clearScrap(final ArrayList<View> scrap) {
6996 final int scrapCount = scrap.size();
6997 for (int j = 0; j < scrapCount; j++) {
6998 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6999 }
7000 }
7001
7002 private void clearAccessibilityFromScrap(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007003 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007004 view.setAccessibilityDelegate(null);
7005 }
7006
7007 private void removeDetachedView(View child, boolean animate) {
7008 child.setAccessibilityDelegate(null);
7009 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007010 }
7011 }
Alan Viverette441b4372014-02-12 13:30:20 -08007012
7013 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007014 * Returns the height of the view for the specified position.
7015 *
7016 * @param position the item position
7017 * @return view height in pixels
7018 */
7019 int getHeightForPosition(int position) {
7020 final int firstVisiblePosition = getFirstVisiblePosition();
7021 final int childCount = getChildCount();
7022 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007023 if (index >= 0 && index < childCount) {
7024 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007025 final View view = getChildAt(index);
7026 return view.getHeight();
7027 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007028 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007029 final View view = obtainView(position, mIsScrap);
7030 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7031 final int height = view.getMeasuredHeight();
7032 mRecycler.addScrapView(view, position);
7033 return height;
7034 }
7035 }
7036
7037 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007038 * Sets the selected item and positions the selection y pixels from the top edge
7039 * of the ListView. (If in touch mode, the item will not be selected but it will
7040 * still be positioned appropriately.)
7041 *
7042 * @param position Index (starting at 0) of the data item to be selected.
7043 * @param y The distance from the top edge of the ListView (plus padding) that the
7044 * item will be positioned.
7045 */
7046 public void setSelectionFromTop(int position, int y) {
7047 if (mAdapter == null) {
7048 return;
7049 }
7050
7051 if (!isInTouchMode()) {
7052 position = lookForSelectablePosition(position, true);
7053 if (position >= 0) {
7054 setNextSelectedPositionInt(position);
7055 }
7056 } else {
7057 mResurrectToPosition = position;
7058 }
7059
7060 if (position >= 0) {
7061 mLayoutMode = LAYOUT_SPECIFIC;
7062 mSpecificTop = mListPadding.top + y;
7063
7064 if (mNeedSync) {
7065 mSyncPosition = position;
7066 mSyncRowId = mAdapter.getItemId(position);
7067 }
7068
7069 if (mPositionScroller != null) {
7070 mPositionScroller.stop();
7071 }
7072 requestLayout();
7073 }
7074 }
7075
Siva Velusamy94a6d152015-05-05 15:07:00 -07007076 /** @hide */
7077 @Override
7078 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7079 super.encodeProperties(encoder);
7080
7081 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7082 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7083 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7084 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7085 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7086 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7087
7088 View selectedView = getSelectedView();
7089 if (selectedView != null) {
7090 encoder.addPropertyKey("selectedView");
7091 selectedView.encode(encoder);
7092 }
7093 }
7094
Alan Viveretted22db212014-02-13 17:47:38 -08007095 /**
7096 * Abstract positon scroller used to handle smooth scrolling.
7097 */
7098 static abstract class AbsPositionScroller {
7099 public abstract void start(int position);
7100 public abstract void start(int position, int boundPosition);
7101 public abstract void startWithOffset(int position, int offset);
7102 public abstract void startWithOffset(int position, int offset, int duration);
7103 public abstract void stop();
7104 }
7105
7106 /**
7107 * Default position scroller that simulates a fling.
7108 */
7109 class PositionScroller extends AbsPositionScroller implements Runnable {
7110 private static final int SCROLL_DURATION = 200;
7111
7112 private static final int MOVE_DOWN_POS = 1;
7113 private static final int MOVE_UP_POS = 2;
7114 private static final int MOVE_DOWN_BOUND = 3;
7115 private static final int MOVE_UP_BOUND = 4;
7116 private static final int MOVE_OFFSET = 5;
7117
7118 private int mMode;
7119 private int mTargetPos;
7120 private int mBoundPos;
7121 private int mLastSeenPos;
7122 private int mScrollDuration;
7123 private final int mExtraScroll;
7124
7125 private int mOffsetFromTop;
7126
7127 PositionScroller() {
7128 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7129 }
7130
7131 @Override
7132 public void start(final int position) {
7133 stop();
7134
7135 if (mDataChanged) {
7136 // Wait until we're back in a stable state to try this.
7137 mPositionScrollAfterLayout = new Runnable() {
7138 @Override public void run() {
7139 start(position);
7140 }
7141 };
7142 return;
7143 }
7144
7145 final int childCount = getChildCount();
7146 if (childCount == 0) {
7147 // Can't scroll without children.
7148 return;
7149 }
7150
7151 final int firstPos = mFirstPosition;
7152 final int lastPos = firstPos + childCount - 1;
7153
7154 int viewTravelCount;
7155 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7156 if (clampedPosition < firstPos) {
7157 viewTravelCount = firstPos - clampedPosition + 1;
7158 mMode = MOVE_UP_POS;
7159 } else if (clampedPosition > lastPos) {
7160 viewTravelCount = clampedPosition - lastPos + 1;
7161 mMode = MOVE_DOWN_POS;
7162 } else {
7163 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7164 return;
7165 }
7166
7167 if (viewTravelCount > 0) {
7168 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7169 } else {
7170 mScrollDuration = SCROLL_DURATION;
7171 }
7172 mTargetPos = clampedPosition;
7173 mBoundPos = INVALID_POSITION;
7174 mLastSeenPos = INVALID_POSITION;
7175
7176 postOnAnimation(this);
7177 }
7178
7179 @Override
7180 public void start(final int position, final int boundPosition) {
7181 stop();
7182
7183 if (boundPosition == INVALID_POSITION) {
7184 start(position);
7185 return;
7186 }
7187
7188 if (mDataChanged) {
7189 // Wait until we're back in a stable state to try this.
7190 mPositionScrollAfterLayout = new Runnable() {
7191 @Override public void run() {
7192 start(position, boundPosition);
7193 }
7194 };
7195 return;
7196 }
7197
7198 final int childCount = getChildCount();
7199 if (childCount == 0) {
7200 // Can't scroll without children.
7201 return;
7202 }
7203
7204 final int firstPos = mFirstPosition;
7205 final int lastPos = firstPos + childCount - 1;
7206
7207 int viewTravelCount;
7208 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7209 if (clampedPosition < firstPos) {
7210 final int boundPosFromLast = lastPos - boundPosition;
7211 if (boundPosFromLast < 1) {
7212 // Moving would shift our bound position off the screen. Abort.
7213 return;
7214 }
7215
7216 final int posTravel = firstPos - clampedPosition + 1;
7217 final int boundTravel = boundPosFromLast - 1;
7218 if (boundTravel < posTravel) {
7219 viewTravelCount = boundTravel;
7220 mMode = MOVE_UP_BOUND;
7221 } else {
7222 viewTravelCount = posTravel;
7223 mMode = MOVE_UP_POS;
7224 }
7225 } else if (clampedPosition > lastPos) {
7226 final int boundPosFromFirst = boundPosition - firstPos;
7227 if (boundPosFromFirst < 1) {
7228 // Moving would shift our bound position off the screen. Abort.
7229 return;
7230 }
7231
7232 final int posTravel = clampedPosition - lastPos + 1;
7233 final int boundTravel = boundPosFromFirst - 1;
7234 if (boundTravel < posTravel) {
7235 viewTravelCount = boundTravel;
7236 mMode = MOVE_DOWN_BOUND;
7237 } else {
7238 viewTravelCount = posTravel;
7239 mMode = MOVE_DOWN_POS;
7240 }
7241 } else {
7242 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7243 return;
7244 }
7245
7246 if (viewTravelCount > 0) {
7247 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7248 } else {
7249 mScrollDuration = SCROLL_DURATION;
7250 }
7251 mTargetPos = clampedPosition;
7252 mBoundPos = boundPosition;
7253 mLastSeenPos = INVALID_POSITION;
7254
7255 postOnAnimation(this);
7256 }
7257
7258 @Override
7259 public void startWithOffset(int position, int offset) {
7260 startWithOffset(position, offset, SCROLL_DURATION);
7261 }
7262
7263 @Override
7264 public void startWithOffset(final int position, int offset, final int duration) {
7265 stop();
7266
7267 if (mDataChanged) {
7268 // Wait until we're back in a stable state to try this.
7269 final int postOffset = offset;
7270 mPositionScrollAfterLayout = new Runnable() {
7271 @Override public void run() {
7272 startWithOffset(position, postOffset, duration);
7273 }
7274 };
7275 return;
7276 }
7277
7278 final int childCount = getChildCount();
7279 if (childCount == 0) {
7280 // Can't scroll without children.
7281 return;
7282 }
7283
7284 offset += getPaddingTop();
7285
7286 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7287 mOffsetFromTop = offset;
7288 mBoundPos = INVALID_POSITION;
7289 mLastSeenPos = INVALID_POSITION;
7290 mMode = MOVE_OFFSET;
7291
7292 final int firstPos = mFirstPosition;
7293 final int lastPos = firstPos + childCount - 1;
7294
7295 int viewTravelCount;
7296 if (mTargetPos < firstPos) {
7297 viewTravelCount = firstPos - mTargetPos;
7298 } else if (mTargetPos > lastPos) {
7299 viewTravelCount = mTargetPos - lastPos;
7300 } else {
7301 // On-screen, just scroll.
7302 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
7303 smoothScrollBy(targetTop - offset, duration, true);
7304 return;
7305 }
7306
7307 // Estimate how many screens we should travel
7308 final float screenTravelCount = (float) viewTravelCount / childCount;
7309 mScrollDuration = screenTravelCount < 1 ?
7310 duration : (int) (duration / screenTravelCount);
7311 mLastSeenPos = INVALID_POSITION;
7312
7313 postOnAnimation(this);
7314 }
7315
7316 /**
7317 * Scroll such that targetPos is in the visible padded region without scrolling
7318 * boundPos out of view. Assumes targetPos is onscreen.
7319 */
7320 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7321 final int firstPos = mFirstPosition;
7322 final int childCount = getChildCount();
7323 final int lastPos = firstPos + childCount - 1;
7324 final int paddedTop = mListPadding.top;
7325 final int paddedBottom = getHeight() - mListPadding.bottom;
7326
7327 if (targetPos < firstPos || targetPos > lastPos) {
7328 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7329 " not visible [" + firstPos + ", " + lastPos + "]");
7330 }
7331 if (boundPos < firstPos || boundPos > lastPos) {
7332 // boundPos doesn't matter, it's already offscreen.
7333 boundPos = INVALID_POSITION;
7334 }
7335
7336 final View targetChild = getChildAt(targetPos - firstPos);
7337 final int targetTop = targetChild.getTop();
7338 final int targetBottom = targetChild.getBottom();
7339 int scrollBy = 0;
7340
7341 if (targetBottom > paddedBottom) {
7342 scrollBy = targetBottom - paddedBottom;
7343 }
7344 if (targetTop < paddedTop) {
7345 scrollBy = targetTop - paddedTop;
7346 }
7347
7348 if (scrollBy == 0) {
7349 return;
7350 }
7351
7352 if (boundPos >= 0) {
7353 final View boundChild = getChildAt(boundPos - firstPos);
7354 final int boundTop = boundChild.getTop();
7355 final int boundBottom = boundChild.getBottom();
7356 final int absScroll = Math.abs(scrollBy);
7357
7358 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7359 // Don't scroll the bound view off the bottom of the screen.
7360 scrollBy = Math.max(0, boundBottom - paddedBottom);
7361 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7362 // Don't scroll the bound view off the top of the screen.
7363 scrollBy = Math.min(0, boundTop - paddedTop);
7364 }
7365 }
7366
7367 smoothScrollBy(scrollBy, duration);
7368 }
7369
7370 @Override
7371 public void stop() {
7372 removeCallbacks(this);
7373 }
7374
7375 @Override
7376 public void run() {
7377 final int listHeight = getHeight();
7378 final int firstPos = mFirstPosition;
7379
7380 switch (mMode) {
7381 case MOVE_DOWN_POS: {
7382 final int lastViewIndex = getChildCount() - 1;
7383 final int lastPos = firstPos + lastViewIndex;
7384
7385 if (lastViewIndex < 0) {
7386 return;
7387 }
7388
7389 if (lastPos == mLastSeenPos) {
7390 // No new views, let things keep going.
7391 postOnAnimation(this);
7392 return;
7393 }
7394
7395 final View lastView = getChildAt(lastViewIndex);
7396 final int lastViewHeight = lastView.getHeight();
7397 final int lastViewTop = lastView.getTop();
7398 final int lastViewPixelsShowing = listHeight - lastViewTop;
7399 final int extraScroll = lastPos < mItemCount - 1 ?
7400 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7401
7402 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
7403 smoothScrollBy(scrollBy, mScrollDuration, true);
7404
7405 mLastSeenPos = lastPos;
7406 if (lastPos < mTargetPos) {
7407 postOnAnimation(this);
7408 }
7409 break;
7410 }
7411
7412 case MOVE_DOWN_BOUND: {
7413 final int nextViewIndex = 1;
7414 final int childCount = getChildCount();
7415
7416 if (firstPos == mBoundPos || childCount <= nextViewIndex
7417 || firstPos + childCount >= mItemCount) {
7418 return;
7419 }
7420 final int nextPos = firstPos + nextViewIndex;
7421
7422 if (nextPos == mLastSeenPos) {
7423 // No new views, let things keep going.
7424 postOnAnimation(this);
7425 return;
7426 }
7427
7428 final View nextView = getChildAt(nextViewIndex);
7429 final int nextViewHeight = nextView.getHeight();
7430 final int nextViewTop = nextView.getTop();
7431 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7432 if (nextPos < mBoundPos) {
7433 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
7434 mScrollDuration, true);
7435
7436 mLastSeenPos = nextPos;
7437
7438 postOnAnimation(this);
7439 } else {
7440 if (nextViewTop > extraScroll) {
7441 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
7442 }
7443 }
7444 break;
7445 }
7446
7447 case MOVE_UP_POS: {
7448 if (firstPos == mLastSeenPos) {
7449 // No new views, let things keep going.
7450 postOnAnimation(this);
7451 return;
7452 }
7453
7454 final View firstView = getChildAt(0);
7455 if (firstView == null) {
7456 return;
7457 }
7458 final int firstViewTop = firstView.getTop();
7459 final int extraScroll = firstPos > 0 ?
7460 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7461
7462 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
7463
7464 mLastSeenPos = firstPos;
7465
7466 if (firstPos > mTargetPos) {
7467 postOnAnimation(this);
7468 }
7469 break;
7470 }
7471
7472 case MOVE_UP_BOUND: {
7473 final int lastViewIndex = getChildCount() - 2;
7474 if (lastViewIndex < 0) {
7475 return;
7476 }
7477 final int lastPos = firstPos + lastViewIndex;
7478
7479 if (lastPos == mLastSeenPos) {
7480 // No new views, let things keep going.
7481 postOnAnimation(this);
7482 return;
7483 }
7484
7485 final View lastView = getChildAt(lastViewIndex);
7486 final int lastViewHeight = lastView.getHeight();
7487 final int lastViewTop = lastView.getTop();
7488 final int lastViewPixelsShowing = listHeight - lastViewTop;
7489 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7490 mLastSeenPos = lastPos;
7491 if (lastPos > mBoundPos) {
7492 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
7493 postOnAnimation(this);
7494 } else {
7495 final int bottom = listHeight - extraScroll;
7496 final int lastViewBottom = lastViewTop + lastViewHeight;
7497 if (bottom > lastViewBottom) {
7498 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
7499 }
7500 }
7501 break;
7502 }
7503
7504 case MOVE_OFFSET: {
7505 if (mLastSeenPos == firstPos) {
7506 // No new views, let things keep going.
7507 postOnAnimation(this);
7508 return;
7509 }
7510
7511 mLastSeenPos = firstPos;
7512
7513 final int childCount = getChildCount();
7514 final int position = mTargetPos;
7515 final int lastPos = firstPos + childCount - 1;
7516
7517 int viewTravelCount = 0;
7518 if (position < firstPos) {
7519 viewTravelCount = firstPos - position + 1;
7520 } else if (position > lastPos) {
7521 viewTravelCount = position - lastPos;
7522 }
7523
7524 // Estimate how many screens we should travel
7525 final float screenTravelCount = (float) viewTravelCount / childCount;
7526
7527 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7528 if (position < firstPos) {
7529 final int distance = (int) (-getHeight() * modifier);
7530 final int duration = (int) (mScrollDuration * modifier);
7531 smoothScrollBy(distance, duration, true);
7532 postOnAnimation(this);
7533 } else if (position > lastPos) {
7534 final int distance = (int) (getHeight() * modifier);
7535 final int duration = (int) (mScrollDuration * modifier);
7536 smoothScrollBy(distance, duration, true);
7537 postOnAnimation(this);
7538 } else {
7539 // On-screen, just scroll.
7540 final int targetTop = getChildAt(position - firstPos).getTop();
7541 final int distance = targetTop - mOffsetFromTop;
7542 final int duration = (int) (mScrollDuration *
7543 ((float) Math.abs(distance) / getHeight()));
7544 smoothScrollBy(distance, duration, true);
7545 }
7546 break;
7547 }
7548
7549 default:
7550 break;
7551 }
7552 }
7553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007554}