blob: 170582b3c3f54c0ad7d65196a21050ca45f96a96 [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;
Adam Powell2fe301d2016-08-15 16:34:37 -070024import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.res.TypedArray;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070030import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.os.Debug;
Yohei Yukawa612cce92016-02-11 17:47:33 -080032import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.os.Parcel;
34import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080035import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070036import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070038import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.text.TextUtils;
40import android.text.TextWatcher;
41import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080042import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070043import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080044import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070045import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070046import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070047import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070048import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.view.Gravity;
50import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080051import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.view.KeyEvent;
53import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070054import android.view.Menu;
55import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056import android.view.MotionEvent;
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -080057import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058import android.view.VelocityTracker;
59import android.view.View;
60import android.view.ViewConfiguration;
61import android.view.ViewDebug;
62import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070063import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070064import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070066import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070067import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070068import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070069import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080070import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070071import android.view.animation.Interpolator;
72import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070073import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070074import android.view.inputmethod.CompletionInfo;
75import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070076import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070077import android.view.inputmethod.ExtractedText;
78import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070079import android.view.inputmethod.InputConnection;
Yohei Yukawa152944f2016-06-10 19:04:34 -070080import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070082import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083
Adam Cohen335c3b62012-07-24 17:18:16 -070084import com.android.internal.R;
85
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086import java.util.ArrayList;
87import java.util.List;
88
89/**
Romain Guyd6a463a2009-05-21 23:10:10 -070090 * Base class that can be used to implement virtualized lists of items. A list does
91 * not have a spatial definition here. For instance, subclases of this class can
92 * 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 -080093 *
94 * @attr ref android.R.styleable#AbsListView_listSelector
95 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
96 * @attr ref android.R.styleable#AbsListView_stackFromBottom
97 * @attr ref android.R.styleable#AbsListView_scrollingCache
98 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
99 * @attr ref android.R.styleable#AbsListView_transcriptMode
100 * @attr ref android.R.styleable#AbsListView_cacheColorHint
101 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
102 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -0700103 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 */
105public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
106 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700107 ViewTreeObserver.OnTouchModeChangeListener,
108 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109
Romain Guy9d849a22012-03-14 16:41:42 -0700110 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800111 private static final String TAG = "AbsListView";
112
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 /**
114 * Disables the transcript mode.
115 *
116 * @see #setTranscriptMode(int)
117 */
118 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700119
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 /**
121 * The list will automatically scroll to the bottom when a data set change
122 * notification is received and only if the last item is already visible
123 * on screen.
124 *
125 * @see #setTranscriptMode(int)
126 */
127 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700128
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 /**
130 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700131 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 *
133 * @see #setTranscriptMode(int)
134 */
135 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
136
137 /**
138 * Indicates that we are not in the middle of a touch gesture
139 */
140 static final int TOUCH_MODE_REST = -1;
141
142 /**
143 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
144 * scroll gesture.
145 */
146 static final int TOUCH_MODE_DOWN = 0;
147
148 /**
149 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
150 * is a longpress
151 */
152 static final int TOUCH_MODE_TAP = 1;
153
154 /**
155 * Indicates we have waited for everything we can wait for, but the user's finger is still down
156 */
157 static final int TOUCH_MODE_DONE_WAITING = 2;
158
159 /**
160 * Indicates the touch gesture is a scroll
161 */
162 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800163
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 /**
165 * Indicates the view is in the process of being flung
166 */
167 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 /**
Adam Powell637d3372010-08-25 14:37:03 -0700170 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
171 */
172 static final int TOUCH_MODE_OVERSCROLL = 5;
173
174 /**
175 * Indicates the view is being flung outside of normal content bounds
176 * and will spring back.
177 */
178 static final int TOUCH_MODE_OVERFLING = 6;
179
180 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 * Regular layout - usually an unsolicited layout from the view system
182 */
183 static final int LAYOUT_NORMAL = 0;
184
185 /**
186 * Show the first item
187 */
188 static final int LAYOUT_FORCE_TOP = 1;
189
190 /**
191 * Force the selected item to be on somewhere on the screen
192 */
193 static final int LAYOUT_SET_SELECTION = 2;
194
195 /**
196 * Show the last item
197 */
198 static final int LAYOUT_FORCE_BOTTOM = 3;
199
200 /**
201 * Make a mSelectedItem appear in a specific location and build the rest of
202 * the views from there. The top is specified by mSpecificTop.
203 */
204 static final int LAYOUT_SPECIFIC = 4;
205
206 /**
207 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
208 * at mSpecificTop
209 */
210 static final int LAYOUT_SYNC = 5;
211
212 /**
213 * Layout as a result of using the navigation keys
214 */
215 static final int LAYOUT_MOVE_SELECTION = 6;
216
217 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700218 * Normal list that does not indicate choices
219 */
220 public static final int CHOICE_MODE_NONE = 0;
221
222 /**
223 * The list allows up to one choice
224 */
225 public static final int CHOICE_MODE_SINGLE = 1;
226
227 /**
228 * The list allows multiple choices
229 */
230 public static final int CHOICE_MODE_MULTIPLE = 2;
231
232 /**
233 * The list allows multiple choices in a modal selection mode
234 */
235 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
236
237 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700238 * The thread that created this view.
239 */
240 private final Thread mOwnerThread;
241
242 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700243 * Controls if/how the user may choose/check items in the list
244 */
245 int mChoiceMode = CHOICE_MODE_NONE;
246
247 /**
248 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
249 */
250 ActionMode mChoiceActionMode;
251
252 /**
253 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
254 * a few extra actions around what application code does.
255 */
256 MultiChoiceModeWrapper mMultiChoiceModeCallback;
257
258 /**
259 * Running count of how many items are currently checked
260 */
261 int mCheckedItemCount;
262
263 /**
264 * Running state of which positions are currently checked
265 */
266 SparseBooleanArray mCheckStates;
267
268 /**
Adam Powell14c08042011-10-06 19:46:18 -0700269 * Running state of which IDs are currently checked.
270 * If there is a value for a given key, the checked state for that ID is true
271 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700272 */
Adam Powell14c08042011-10-06 19:46:18 -0700273 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700274
275 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 * Controls how the next layout will happen
277 */
278 int mLayoutMode = LAYOUT_NORMAL;
279
280 /**
281 * Should be used by subclasses to listen to changes in the dataset
282 */
283 AdapterDataSetObserver mDataSetObserver;
284
285 /**
286 * The adapter containing the data to be displayed by this view
287 */
288 ListAdapter mAdapter;
289
290 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700291 * The remote adapter containing the data to be displayed by this view to be set
292 */
293 private RemoteViewsAdapter mRemoteAdapter;
294
295 /**
Adam Powell539ee872012-02-03 19:00:49 -0800296 * If mAdapter != null, whenever this is true the adapter has stable IDs.
297 */
298 boolean mAdapterHasStableIds;
299
300 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700301 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
302 */
303 private boolean mDeferNotifyDataSetChanged = false;
304
305 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 * Indicates whether the list selector should be drawn on top of the children or behind
307 */
308 boolean mDrawSelectorOnTop = false;
309
310 /**
311 * The drawable used to draw the selector
312 */
313 Drawable mSelector;
314
315 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700316 * The current position of the selector in the list.
317 */
318 int mSelectorPosition = INVALID_POSITION;
319
320 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 * Defines the selector's location and dimension at drawing time
322 */
323 Rect mSelectorRect = new Rect();
324
325 /**
326 * The data set used to store unused views that should be reused during the next layout
327 * to avoid creating new ones
328 */
329 final RecycleBin mRecycler = new RecycleBin();
330
331 /**
332 * The selection's left padding
333 */
334 int mSelectionLeftPadding = 0;
335
336 /**
337 * The selection's top padding
338 */
339 int mSelectionTopPadding = 0;
340
341 /**
342 * The selection's right padding
343 */
344 int mSelectionRightPadding = 0;
345
346 /**
347 * The selection's bottom padding
348 */
349 int mSelectionBottomPadding = 0;
350
351 /**
352 * This view's padding
353 */
354 Rect mListPadding = new Rect();
355
356 /**
357 * Subclasses must retain their measure spec from onMeasure() into this member
358 */
359 int mWidthMeasureSpec = 0;
360
361 /**
362 * The top scroll indicator
363 */
364 View mScrollUp;
365
366 /**
367 * The down scroll indicator
368 */
369 View mScrollDown;
370
371 /**
372 * When the view is scrolling, this flag is set to true to indicate subclasses that
373 * the drawing cache was enabled on the children
374 */
375 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800376 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377
378 /**
379 * The position of the view that received the down motion event
380 */
381 int mMotionPosition;
382
383 /**
384 * The offset to the top of the mMotionPosition view when the down motion event was received
385 */
386 int mMotionViewOriginalTop;
387
388 /**
389 * The desired offset to the top of the mMotionPosition view after a scroll
390 */
391 int mMotionViewNewTop;
392
393 /**
394 * The X value associated with the the down motion event
395 */
396 int mMotionX;
397
398 /**
399 * The Y value associated with the the down motion event
400 */
401 int mMotionY;
402
403 /**
404 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
405 * TOUCH_MODE_DONE_WAITING
406 */
407 int mTouchMode = TOUCH_MODE_REST;
408
409 /**
410 * Y value from on the previous motion event (if any)
411 */
412 int mLastY;
413
414 /**
415 * How far the finger moved before we started scrolling
416 */
417 int mMotionCorrection;
418
419 /**
420 * Determines speed during touch scrolling
421 */
422 private VelocityTracker mVelocityTracker;
423
424 /**
425 * Handles one frame of a fling
426 */
427 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800428
Adam Powell45803472010-01-25 15:10:44 -0800429 /**
430 * Handles scrolling between positions within the list.
431 */
Alan Viveretted22db212014-02-13 17:47:38 -0800432 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433
434 /**
435 * The offset in pixels form the top of the AdapterView to the top
436 * of the currently selected view. Used to save and restore state.
437 */
438 int mSelectedTop = 0;
439
440 /**
441 * Indicates whether the list is stacked from the bottom edge or
442 * the top edge.
443 */
444 boolean mStackFromBottom;
445
446 /**
447 * When set to true, the list automatically discards the children's
448 * bitmap cache after scrolling.
449 */
450 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700451
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 /**
453 * Whether or not to enable the fast scroll feature on this list
454 */
455 boolean mFastScrollEnabled;
456
457 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700458 * Whether or not to always show the fast scroll feature on this list
459 */
460 boolean mFastScrollAlwaysVisible;
461
462 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 * Optional callback to notify client when scroll position has changed
464 */
465 private OnScrollListener mOnScrollListener;
466
467 /**
468 * Keeps track of our accessory window
469 */
470 PopupWindow mPopup;
471
472 /**
473 * Used with type filter window
474 */
475 EditText mTextFilter;
476
477 /**
478 * Indicates whether to use pixels-based or position-based scrollbar
479 * properties.
480 */
481 private boolean mSmoothScrollbarEnabled = true;
482
483 /**
484 * Indicates that this view supports filtering
485 */
486 private boolean mTextFilterEnabled;
487
488 /**
489 * Indicates that this view is currently displaying a filtered view of the data
490 */
491 private boolean mFiltered;
492
493 /**
494 * Rectangle used for hit testing children
495 */
496 private Rect mTouchFrame;
497
498 /**
499 * The position to resurrect the selected position to.
500 */
501 int mResurrectToPosition = INVALID_POSITION;
502
503 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800504
Adam Powell0b8bb422010-02-08 14:30:45 -0800505 /**
Adam Powell637d3372010-08-25 14:37:03 -0700506 * Maximum distance to record overscroll
507 */
508 int mOverscrollMax;
509
510 /**
511 * Content height divided by this is the overscroll limit.
512 */
513 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
514
515 /**
Adam Powell14c08042011-10-06 19:46:18 -0700516 * How many positions in either direction we will search to try to
517 * find a checked item with a stable ID that moved position across
518 * a data set change. If the item isn't found it will be unselected.
519 */
520 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
521
522 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 * Used to request a layout when we changed touch mode
524 */
525 private static final int TOUCH_MODE_UNKNOWN = -1;
526 private static final int TOUCH_MODE_ON = 0;
527 private static final int TOUCH_MODE_OFF = 1;
528
529 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
530
531 private static final boolean PROFILE_SCROLLING = false;
532 private boolean mScrollProfilingStarted = false;
533
534 private static final boolean PROFILE_FLINGING = false;
535 private boolean mFlingProfilingStarted = false;
536
537 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800538 * The StrictMode "critical time span" objects to catch animation
539 * stutters. Non-null when a time-sensitive animation is
540 * in-flight. Must call finish() on them when done animating.
541 * These are no-ops on user builds.
542 */
543 private StrictMode.Span mScrollStrictSpan = null;
544 private StrictMode.Span mFlingStrictSpan = null;
545
546 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800547 * The last CheckForLongPress runnable we posted, if any
548 */
549 private CheckForLongPress mPendingCheckForLongPress;
550
551 /**
552 * The last CheckForTap runnable we posted, if any
553 */
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700554 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 /**
557 * The last CheckForKeyLongPress runnable we posted, if any
558 */
559 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
560
561 /**
562 * Acts upon click
563 */
564 private AbsListView.PerformClick mPerformClick;
565
566 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800567 * Delayed action for touch mode.
568 */
569 private Runnable mTouchModeReset;
570
571 /**
Alan Viverette66df60f2016-01-28 14:56:07 -0500572 * Whether the most recent touch event stream resulted in a successful
573 * long-press action. This is reset on TOUCH_DOWN.
574 */
575 private boolean mHasPerformedLongPress;
576
577 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 * This view is in transcript mode -- it shows the bottom of the list when the data
579 * changes
580 */
581 private int mTranscriptMode;
582
583 /**
584 * Indicates that this list is always drawn on top of a solid, single-color, opaque
585 * background
586 */
587 private int mCacheColorHint;
588
589 /**
590 * The select child's view (from the adapter's getView) is enabled.
591 */
592 private boolean mIsChildViewEnabled;
593
594 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800595 * The cached drawable state for the selector. Accounts for child enabled
596 * state, but otherwise identical to the view's own drawable state.
597 */
598 private int[] mSelectorState;
599
600 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 * The last scroll state reported to clients through {@link OnScrollListener}.
602 */
603 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
604
605 /**
606 * Helper object that renders and controls the fast scroll thumb.
607 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700608 private FastScroller mFastScroll;
609
610 /**
611 * Temporary holder for fast scroller style until a FastScroller object
612 * is created.
613 */
614 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615
Romain Guyd6a463a2009-05-21 23:10:10 -0700616 private boolean mGlobalLayoutListenerAddedFilter;
617
618 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 private float mDensityScale;
620
Aaron Whytef8306522017-03-22 16:30:58 -0700621 private float mVerticalScrollFactor;
Ned Burns20ad0732016-08-18 14:22:57 -0400622
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700623 private InputConnection mDefInputConnection;
624 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700625
626 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700627 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700628 private int mMinimumVelocity;
629 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700630 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800631
Romain Guy21875052010-01-06 18:48:08 -0800632 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800633
Adam Powell96d62af2014-05-02 10:04:38 -0700634 private final int[] mScrollOffset = new int[2];
635 private final int[] mScrollConsumed = new int[2];
636
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800637 private final float[] mTmpPoint = new float[2];
638
Adam Powell744beff2014-09-22 09:47:48 -0700639 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
640 // In the future it would be nice to be able to give this to the VelocityTracker
641 // directly, or alternatively put a VT into absolute-positioning mode that only
642 // reads the raw screen-coordinate x/y values.
643 private int mNestedYOffset = 0;
644
Romain Guy24562482010-02-01 14:56:19 -0800645 // True when the popup should be hidden because of a call to
646 // dispatchDisplayHint()
647 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800648
Adam Powell4cd47702010-02-25 11:21:14 -0800649 /**
650 * ID of the active pointer. This is used to retain consistency during
651 * drags/flings if multiple pointers are used.
652 */
653 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800654
Adam Powell4cd47702010-02-25 11:21:14 -0800655 /**
656 * Sentinel value for no current active pointer.
657 * Used by {@link #mActivePointerId}.
658 */
659 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 /**
Adam Powell637d3372010-08-25 14:37:03 -0700662 * Maximum distance to overscroll by during edge effects
663 */
664 int mOverscrollDistance;
665
666 /**
667 * Maximum distance to overfling during edge effects
668 */
669 int mOverflingDistance;
670
671 // These two EdgeGlows are always set and used together.
672 // Checking one for null is as good as checking both.
673
674 /**
675 * Tracks the state of the top edge glow.
676 */
Adam Powell89935e42011-08-31 14:26:12 -0700677 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700678
679 /**
680 * Tracks the state of the bottom edge glow.
681 */
Adam Powell89935e42011-08-31 14:26:12 -0700682 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700683
684 /**
685 * An estimate of how many pixels are between the top of the list and
686 * the top of the first position in the adapter, based on the last time
687 * we saw it. Used to hint where to draw edge glows.
688 */
689 private int mFirstPositionDistanceGuess;
690
691 /**
692 * An estimate of how many pixels are between the bottom of the list and
693 * the bottom of the last position in the adapter, based on the last time
694 * we saw it. Used to hint where to draw edge glows.
695 */
696 private int mLastPositionDistanceGuess;
697
698 /**
699 * Used for determining when to cancel out of overscroll.
700 */
701 private int mDirection = 0;
702
703 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800704 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
705 * the bottom correctly on resizes.
706 */
707 private boolean mForceTranscriptScroll;
708
alanvc1d7e772012-05-08 14:47:24 -0700709 /**
710 * Used for interacting with list items from an accessibility service.
711 */
712 private ListItemAccessibilityDelegate mAccessibilityDelegate;
713
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700714 private int mLastAccessibilityScrollEventFromIndex;
715 private int mLastAccessibilityScrollEventToIndex;
716
Adam Powellda13dba2010-12-05 13:47:23 -0800717 /**
Adam Powellee78b172011-08-16 16:39:20 -0700718 * Track the item count from the last time we handled a data change.
719 */
720 private int mLastHandledItemCount;
721
722 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700723 * Used for smooth scrolling at a consistent rate
724 */
725 static final Interpolator sLinearInterpolator = new LinearInterpolator();
726
727 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700728 * The saved state that we will be restoring from when we next sync.
729 * Kept here so that if we happen to be asked to save our state before
730 * the sync happens, we can return this existing data rather than losing
731 * it.
732 */
733 private SavedState mPendingSync;
734
735 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800736 * Whether the view is in the process of detaching from its window.
737 */
738 private boolean mIsDetaching;
739
740 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 * Interface definition for a callback to be invoked when the list or grid
742 * has been scrolled.
743 */
744 public interface OnScrollListener {
745
746 /**
747 * The view is not scrolling. Note navigating the list using the trackball counts as
748 * being in the idle state since these transitions are not animated.
749 */
750 public static int SCROLL_STATE_IDLE = 0;
751
752 /**
753 * The user is scrolling using touch, and their finger is still on the screen
754 */
755 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
756
757 /**
758 * The user had previously been scrolling using touch and had performed a fling. The
759 * animation is now coasting to a stop
760 */
761 public static int SCROLL_STATE_FLING = 2;
762
763 /**
764 * Callback method to be invoked while the list view or grid view is being scrolled. If the
765 * view is being scrolled, this method will be called before the next frame of the scroll is
766 * rendered. In particular, it will be called before any calls to
767 * {@link Adapter#getView(int, View, ViewGroup)}.
768 *
769 * @param view The view whose scroll state is being reported
770 *
Yorke Lee43943d82014-05-08 10:15:20 -0700771 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800772 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
773 */
774 public void onScrollStateChanged(AbsListView view, int scrollState);
775
776 /**
777 * Callback method to be invoked when the list or grid has been scrolled. This will be
778 * called after the scroll has completed
779 * @param view The view whose scroll state is being reported
780 * @param firstVisibleItem the index of the first visible cell (ignore if
781 * visibleItemCount == 0)
782 * @param visibleItemCount the number of visible cells
783 * @param totalItemCount the number of items in the list adaptor
784 */
785 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
786 int totalItemCount);
787 }
788
Dianne Hackborne2136772010-11-04 15:08:59 -0700789 /**
790 * The top-level view of a list item can implement this interface to allow
791 * itself to modify the bounds of the selection shown for that item.
792 */
793 public interface SelectionBoundsAdjuster {
794 /**
795 * Called to allow the list item to adjust the bounds shown for
796 * its selection.
797 *
798 * @param bounds On call, this contains the bounds the list has
799 * selected for the item (that is the bounds of the entire view). The
800 * values can be modified as desired.
801 */
802 public void adjustListItemSelectionBounds(Rect bounds);
803 }
804
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 public AbsListView(Context context) {
806 super(context);
807 initAbsListView();
808
Alan Viverette39bed692013-08-07 15:47:04 -0700809 mOwnerThread = Thread.currentThread();
810
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 setVerticalScrollBarEnabled(true);
812 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700813 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800814 a.recycle();
815 }
816
817 public AbsListView(Context context, AttributeSet attrs) {
818 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
819 }
820
Alan Viverette617feb92013-09-09 18:09:13 -0700821 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
822 this(context, attrs, defStyleAttr, 0);
823 }
824
825 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
826 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 initAbsListView();
828
Alan Viverette39bed692013-08-07 15:47:04 -0700829 mOwnerThread = Thread.currentThread();
830
Alan Viverette617feb92013-09-09 18:09:13 -0700831 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700832 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800833
Alan Viverette7eceda32015-06-01 10:47:29 -0700834 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
835 if (selector != null) {
836 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 }
838
Alan Viverette7eceda32015-06-01 10:47:29 -0700839 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840
Alan Viverette7eceda32015-06-01 10:47:29 -0700841 setStackFromBottom(a.getBoolean(
842 R.styleable.AbsListView_stackFromBottom, false));
843 setScrollingCacheEnabled(a.getBoolean(
844 R.styleable.AbsListView_scrollingCache, true));
845 setTextFilterEnabled(a.getBoolean(
846 R.styleable.AbsListView_textFilterEnabled, false));
847 setTranscriptMode(a.getInt(
848 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
849 setCacheColorHint(a.getColor(
850 R.styleable.AbsListView_cacheColorHint, 0));
851 setSmoothScrollbarEnabled(a.getBoolean(
852 R.styleable.AbsListView_smoothScrollbar, true));
853 setChoiceMode(a.getInt(
854 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800855
Alan Viverette7eceda32015-06-01 10:47:29 -0700856 setFastScrollEnabled(a.getBoolean(
857 R.styleable.AbsListView_fastScrollEnabled, false));
858 setFastScrollStyle(a.getResourceId(
859 R.styleable.AbsListView_fastScrollStyle, 0));
860 setFastScrollAlwaysVisible(a.getBoolean(
861 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700862
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 a.recycle();
Adam Powell2fe301d2016-08-15 16:34:37 -0700864
865 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
866 setRevealOnFocusHint(false);
867 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 }
869
Romain Guyd6a463a2009-05-21 23:10:10 -0700870 private void initAbsListView() {
871 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700872 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700873 setFocusableInTouchMode(true);
874 setWillNotDraw(false);
875 setAlwaysDrawnWithCacheEnabled(false);
876 setScrollingCacheEnabled(true);
877
Romain Guy4296fc42009-07-06 11:48:52 -0700878 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
879 mTouchSlop = configuration.getScaledTouchSlop();
Aaron Whytef8306522017-03-22 16:30:58 -0700880 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
Romain Guy4296fc42009-07-06 11:48:52 -0700881 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
882 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700883 mOverscrollDistance = configuration.getScaledOverscrollDistance();
884 mOverflingDistance = configuration.getScaledOverflingDistance();
885
Romain Guyd6a463a2009-05-21 23:10:10 -0700886 mDensityScale = getContext().getResources().getDisplayMetrics().density;
887 }
Romain Guy0a637162009-05-29 14:43:54 -0700888
Adam Powell637d3372010-08-25 14:37:03 -0700889 @Override
890 public void setOverScrollMode(int mode) {
891 if (mode != OVER_SCROLL_NEVER) {
892 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800893 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700894 mEdgeGlowTop = new EdgeEffect(context);
895 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700896 }
897 } else {
898 mEdgeGlowTop = null;
899 mEdgeGlowBottom = null;
900 }
901 super.setOverScrollMode(mode);
902 }
903
Romain Guyd6a463a2009-05-21 23:10:10 -0700904 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700905 * {@inheritDoc}
906 */
907 @Override
908 public void setAdapter(ListAdapter adapter) {
909 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800910 mAdapterHasStableIds = mAdapter.hasStableIds();
911 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700912 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700913 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700914 }
915 }
Sumir Kataria37b85672017-07-11 15:35:30 -0700916 clearChoices();
Adam Powellf343e1b2010-08-13 18:27:04 -0700917 }
918
919 /**
920 * Returns the number of items currently selected. This will only be valid
921 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
922 *
923 * <p>To determine the specific items that are currently selected, use one of
924 * the <code>getChecked*</code> methods.
925 *
926 * @return The number of items currently selected
927 *
928 * @see #getCheckedItemPosition()
929 * @see #getCheckedItemPositions()
930 * @see #getCheckedItemIds()
931 */
932 public int getCheckedItemCount() {
933 return mCheckedItemCount;
934 }
935
936 /**
937 * Returns the checked state of the specified position. The result is only
938 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
939 * or {@link #CHOICE_MODE_MULTIPLE}.
940 *
941 * @param position The item whose checked state to return
942 * @return The item's checked state or <code>false</code> if choice mode
943 * is invalid
944 *
945 * @see #setChoiceMode(int)
946 */
947 public boolean isItemChecked(int position) {
948 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
949 return mCheckStates.get(position);
950 }
951
952 return false;
953 }
954
955 /**
956 * Returns the currently checked item. The result is only valid if the choice
957 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
958 *
959 * @return The position of the currently checked item or
960 * {@link #INVALID_POSITION} if nothing is selected
961 *
962 * @see #setChoiceMode(int)
963 */
964 public int getCheckedItemPosition() {
965 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
966 return mCheckStates.keyAt(0);
967 }
968
969 return INVALID_POSITION;
970 }
971
972 /**
973 * Returns the set of checked items in the list. The result is only valid if
974 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
975 *
976 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200977 * get(int position) where position is a checked position in the
978 * list and false otherwise, or <code>null</code> if the choice
979 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700980 */
981 public SparseBooleanArray getCheckedItemPositions() {
982 if (mChoiceMode != CHOICE_MODE_NONE) {
983 return mCheckStates;
984 }
985 return null;
986 }
987
988 /**
989 * Returns the set of checked items ids. The result is only valid if the
990 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
991 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
992 *
993 * @return A new array which contains the id of each checked item in the
994 * list.
995 */
996 public long[] getCheckedItemIds() {
997 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
998 return new long[0];
999 }
1000
Adam Powell14c08042011-10-06 19:46:18 -07001001 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -07001002 final int count = idStates.size();
1003 final long[] ids = new long[count];
1004
1005 for (int i = 0; i < count; i++) {
1006 ids[i] = idStates.keyAt(i);
1007 }
1008
1009 return ids;
1010 }
1011
1012 /**
1013 * Clear any choices previously set
1014 */
1015 public void clearChoices() {
1016 if (mCheckStates != null) {
1017 mCheckStates.clear();
1018 }
1019 if (mCheckedIdStates != null) {
1020 mCheckedIdStates.clear();
1021 }
1022 mCheckedItemCount = 0;
1023 }
1024
1025 /**
1026 * Sets the checked state of the specified position. The is only valid if
1027 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1028 * {@link #CHOICE_MODE_MULTIPLE}.
1029 *
1030 * @param position The item whose checked state is to be checked
1031 * @param value The new checked state for the item
1032 */
1033 public void setItemChecked(int position, boolean value) {
1034 if (mChoiceMode == CHOICE_MODE_NONE) {
1035 return;
1036 }
1037
1038 // Start selection mode if needed. We don't need to if we're unchecking something.
1039 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001040 if (mMultiChoiceModeCallback == null ||
1041 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1042 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1043 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1044 "supplied. Call setMultiChoiceModeListener to set a callback.");
1045 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001046 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1047 }
1048
Siyamed Sinir135554e2016-01-22 18:40:42 -08001049 final boolean itemCheckChanged;
Adam Powellf343e1b2010-08-13 18:27:04 -07001050 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1051 boolean oldValue = mCheckStates.get(position);
1052 mCheckStates.put(position, value);
1053 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1054 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001055 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001056 } else {
1057 mCheckedIdStates.delete(mAdapter.getItemId(position));
1058 }
1059 }
Siyamed Sinir135554e2016-01-22 18:40:42 -08001060 itemCheckChanged = oldValue != value;
1061 if (itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001062 if (value) {
1063 mCheckedItemCount++;
1064 } else {
1065 mCheckedItemCount--;
1066 }
1067 }
1068 if (mChoiceActionMode != null) {
1069 final long id = mAdapter.getItemId(position);
1070 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1071 position, id, value);
1072 }
1073 } else {
1074 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1075 // Clear all values if we're checking something, or unchecking the currently
1076 // selected item
Siyamed Sinir135554e2016-01-22 18:40:42 -08001077 itemCheckChanged = isItemChecked(position) != value;
Adam Powellf343e1b2010-08-13 18:27:04 -07001078 if (value || isItemChecked(position)) {
1079 mCheckStates.clear();
1080 if (updateIds) {
1081 mCheckedIdStates.clear();
1082 }
1083 }
1084 // this may end up selecting the value we just cleared but this way
1085 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1086 if (value) {
1087 mCheckStates.put(position, true);
1088 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001089 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001090 }
1091 mCheckedItemCount = 1;
1092 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1093 mCheckedItemCount = 0;
1094 }
1095 }
1096
Siyamed Sinir135554e2016-01-22 18:40:42 -08001097 // Do not generate a data change while we are in the layout phase or data has not changed
1098 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001099 mDataChanged = true;
1100 rememberSyncState();
1101 requestLayout();
1102 }
1103 }
1104
1105 @Override
1106 public boolean performItemClick(View view, int position, long id) {
1107 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001108 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001109
1110 if (mChoiceMode != CHOICE_MODE_NONE) {
1111 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001112 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001113
1114 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1115 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001116 boolean checked = !mCheckStates.get(position, false);
1117 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001118 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001119 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001120 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001121 } else {
1122 mCheckedIdStates.delete(mAdapter.getItemId(position));
1123 }
1124 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001125 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001126 mCheckedItemCount++;
1127 } else {
1128 mCheckedItemCount--;
1129 }
1130 if (mChoiceActionMode != null) {
1131 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001132 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001133 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001134 }
Adam Powell29382d92012-02-23 11:03:22 -08001135 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001136 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001137 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001138 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001139 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001140 mCheckStates.put(position, true);
1141 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1142 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001143 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001144 }
1145 mCheckedItemCount = 1;
1146 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1147 mCheckedItemCount = 0;
1148 }
Adam Powell29382d92012-02-23 11:03:22 -08001149 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001150 }
1151
Adam Powell29382d92012-02-23 11:03:22 -08001152 if (checkedStateChanged) {
1153 updateOnScreenCheckedViews();
1154 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001155 }
1156
Adam Powellbf5f2b32010-10-24 16:45:44 -07001157 if (dispatchItemClick) {
1158 handled |= super.performItemClick(view, position, id);
1159 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001160
1161 return handled;
1162 }
1163
1164 /**
Adam Powell29382d92012-02-23 11:03:22 -08001165 * Perform a quick, in-place update of the checked or activated state
1166 * on all visible item views. This should only be called when a valid
1167 * choice mode is active.
1168 */
1169 private void updateOnScreenCheckedViews() {
1170 final int firstPos = mFirstPosition;
1171 final int count = getChildCount();
1172 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1173 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1174 for (int i = 0; i < count; i++) {
1175 final View child = getChildAt(i);
1176 final int position = firstPos + i;
1177
1178 if (child instanceof Checkable) {
1179 ((Checkable) child).setChecked(mCheckStates.get(position));
1180 } else if (useActivated) {
1181 child.setActivated(mCheckStates.get(position));
1182 }
1183 }
1184 }
1185
1186 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001187 * @see #setChoiceMode(int)
1188 *
1189 * @return The current choice mode
1190 */
1191 public int getChoiceMode() {
1192 return mChoiceMode;
1193 }
1194
1195 /**
1196 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1197 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1198 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1199 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1200 *
1201 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1202 * {@link #CHOICE_MODE_MULTIPLE}
1203 */
1204 public void setChoiceMode(int choiceMode) {
1205 mChoiceMode = choiceMode;
1206 if (mChoiceActionMode != null) {
1207 mChoiceActionMode.finish();
1208 mChoiceActionMode = null;
1209 }
1210 if (mChoiceMode != CHOICE_MODE_NONE) {
1211 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001212 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001213 }
1214 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001215 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001216 }
1217 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1218 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1219 clearChoices();
1220 setLongClickable(true);
1221 }
1222 }
1223 }
1224
1225 /**
1226 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1227 * selection {@link ActionMode}. Only used when the choice mode is set to
1228 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1229 *
1230 * @param listener Listener that will manage the selection mode
1231 *
1232 * @see #setChoiceMode(int)
1233 */
1234 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1235 if (mMultiChoiceModeCallback == null) {
1236 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1237 }
1238 mMultiChoiceModeCallback.setWrapped(listener);
1239 }
1240
1241 /**
Adam Powell637d3372010-08-25 14:37:03 -07001242 * @return true if all list content currently fits within the view boundaries
1243 */
1244 private boolean contentFits() {
1245 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001246 if (childCount == 0) return true;
1247 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001248
Adam Powell4ce35412011-01-24 14:55:00 -08001249 return getChildAt(0).getTop() >= mListPadding.top &&
1250 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001251 }
1252
1253 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001254 * Specifies whether fast scrolling is enabled or disabled.
1255 * <p>
1256 * When fast scrolling is enabled, the user can quickly scroll through lists
1257 * by dragging the fast scroll thumb.
1258 * <p>
1259 * If the adapter backing this list implements {@link SectionIndexer}, the
1260 * fast scroller will display section header previews as the user scrolls.
1261 * Additionally, the user will be able to quickly jump between sections by
1262 * tapping along the length of the scroll bar.
1263 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 * @see SectionIndexer
1265 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001266 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267 */
Alan Viverette39bed692013-08-07 15:47:04 -07001268 public void setFastScrollEnabled(final boolean enabled) {
1269 if (mFastScrollEnabled != enabled) {
1270 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001271
Alan Viverette39bed692013-08-07 15:47:04 -07001272 if (isOwnerThread()) {
1273 setFastScrollerEnabledUiThread(enabled);
1274 } else {
1275 post(new Runnable() {
1276 @Override
1277 public void run() {
1278 setFastScrollerEnabledUiThread(enabled);
1279 }
1280 });
1281 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001282 }
Alan Viverette39bed692013-08-07 15:47:04 -07001283 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001284
Alan Viverette39bed692013-08-07 15:47:04 -07001285 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001286 if (mFastScroll != null) {
1287 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001288 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001289 mFastScroll = new FastScroller(this, mFastScrollStyle);
1290 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001291 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001292
Alan Viveretteb9f27222013-09-06 19:39:47 -07001293 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001294
Alan Viverette8636ace2013-10-31 15:41:31 -07001295 if (mFastScroll != null) {
1296 mFastScroll.updateLayout();
1297 }
1298 }
1299
1300 /**
1301 * Specifies the style of the fast scroller decorations.
1302 *
1303 * @param styleResId style resource containing fast scroller properties
1304 * @see android.R.styleable#FastScroll
1305 */
1306 public void setFastScrollStyle(int styleResId) {
1307 if (mFastScroll == null) {
1308 mFastScrollStyle = styleResId;
1309 } else {
1310 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 }
Romain Guy0a637162009-05-29 14:43:54 -07001313
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001315 * Set whether or not the fast scroller should always be shown in place of
1316 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001317 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001318 * <p>
1319 * Fast scrollers shown in this way will not fade out and will be a
1320 * permanent fixture within the list. This is best combined with an inset
1321 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001322 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001323 * @param alwaysShow true if the fast scroller should always be displayed,
1324 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001325 * @see #setScrollBarStyle(int)
1326 * @see #setFastScrollEnabled(boolean)
1327 */
Alan Viverette39bed692013-08-07 15:47:04 -07001328 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1329 if (mFastScrollAlwaysVisible != alwaysShow) {
1330 if (alwaysShow && !mFastScrollEnabled) {
1331 setFastScrollEnabled(true);
1332 }
Adam Powell20232d02010-12-08 21:08:53 -08001333
Alan Viverette39bed692013-08-07 15:47:04 -07001334 mFastScrollAlwaysVisible = alwaysShow;
1335
1336 if (isOwnerThread()) {
1337 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1338 } else {
1339 post(new Runnable() {
1340 @Override
1341 public void run() {
1342 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1343 }
1344 });
1345 }
1346 }
1347 }
1348
1349 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001350 if (mFastScroll != null) {
1351 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001352 }
Alan Viverette39bed692013-08-07 15:47:04 -07001353 }
Adam Powell20232d02010-12-08 21:08:53 -08001354
Alan Viverette39bed692013-08-07 15:47:04 -07001355 /**
1356 * @return whether the current thread is the one that created the view
1357 */
1358 private boolean isOwnerThread() {
1359 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001360 }
1361
1362 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001363 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001364 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001365 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001366 * @see #setFastScrollAlwaysVisible(boolean)
1367 */
1368 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001369 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001370 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1371 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001372 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001373 }
Adam Powell20232d02010-12-08 21:08:53 -08001374 }
1375
1376 @Override
1377 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001378 if (mFastScroll != null && mFastScroll.isEnabled()) {
1379 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001380 }
1381 return super.getVerticalScrollbarWidth();
1382 }
1383
1384 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001385 * Returns true if the fast scroller is enabled.
1386 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001387 * @see #setFastScrollEnabled(boolean)
1388 * @return true if fast scroll is enabled, false otherwise
1389 */
1390 @ViewDebug.ExportedProperty
1391 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001392 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001393 return mFastScrollEnabled;
1394 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001395 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001396 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001397 }
Romain Guy0a637162009-05-29 14:43:54 -07001398
Adam Powell20232d02010-12-08 21:08:53 -08001399 @Override
1400 public void setVerticalScrollbarPosition(int position) {
1401 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001402 if (mFastScroll != null) {
1403 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001404 }
1405 }
1406
Alan Viverette26bb2532013-08-09 10:40:50 -07001407 @Override
1408 public void setScrollBarStyle(int style) {
1409 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001410 if (mFastScroll != null) {
1411 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001412 }
1413 }
1414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001416 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001417 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001418 */
1419 @Override
1420 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001421 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001422 }
1423
1424 /**
1425 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1426 * is computed based on the number of visible pixels in the visible items. This
1427 * however assumes that all list items have the same height. If you use a list in
1428 * which items have different heights, the scrollbar will change appearance as the
1429 * user scrolls through the list. To avoid this issue, you need to disable this
1430 * property.
1431 *
1432 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1433 * is based solely on the number of items in the adapter and the position of the
1434 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001435 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001436 *
1437 * @param enabled Whether or not to enable smooth scrollbar.
1438 *
Romain Guy0a637162009-05-29 14:43:54 -07001439 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1441 */
1442 public void setSmoothScrollbarEnabled(boolean enabled) {
1443 mSmoothScrollbarEnabled = enabled;
1444 }
1445
1446 /**
1447 * Returns the current state of the fast scroll feature.
1448 *
1449 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1450 *
1451 * @see #setSmoothScrollbarEnabled(boolean)
1452 */
1453 @ViewDebug.ExportedProperty
1454 public boolean isSmoothScrollbarEnabled() {
1455 return mSmoothScrollbarEnabled;
1456 }
1457
1458 /**
1459 * Set the listener that will receive notifications every time the list scrolls.
1460 *
1461 * @param l the scroll listener
1462 */
1463 public void setOnScrollListener(OnScrollListener l) {
1464 mOnScrollListener = l;
1465 invokeOnItemScrollListener();
1466 }
1467
1468 /**
1469 * Notify our scroll listener (if there is one) of a change in scroll state
1470 */
1471 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001472 if (mFastScroll != null) {
1473 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001474 }
1475 if (mOnScrollListener != null) {
1476 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1477 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001478 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001479 }
1480
Alan Viverettea54956a2015-01-07 16:05:02 -08001481 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001482 @Override
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001483 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001484 // Since this class calls onScrollChanged even if the mFirstPosition and the
1485 // child count have not changed we will avoid sending duplicate accessibility
1486 // events.
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001487 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001488 final int firstVisiblePosition = getFirstVisiblePosition();
1489 final int lastVisiblePosition = getLastVisiblePosition();
1490 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1491 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001492 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001493 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001494 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1495 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001496 }
1497 }
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001498 super.sendAccessibilityEventUnchecked(event);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001499 }
1500
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001501 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001502 public CharSequence getAccessibilityClassName() {
1503 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001504 }
1505
Alan Viverettea54956a2015-01-07 16:05:02 -08001506 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001507 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001508 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1509 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001510 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001511 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001512 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001513 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001514 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001515 }
Alan Viverette947a9692014-09-25 12:43:47 -07001516 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001517 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001518 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001519 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001520 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001521 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001522
1523 info.removeAction(AccessibilityAction.ACTION_CLICK);
1524 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001525 }
1526
Alan Viverette76769ae2014-02-12 16:38:10 -08001527 int getSelectionModeForAccessibility() {
1528 final int choiceMode = getChoiceMode();
1529 switch (choiceMode) {
1530 case CHOICE_MODE_NONE:
1531 return CollectionInfo.SELECTION_MODE_NONE;
1532 case CHOICE_MODE_SINGLE:
1533 return CollectionInfo.SELECTION_MODE_SINGLE;
1534 case CHOICE_MODE_MULTIPLE:
1535 case CHOICE_MODE_MULTIPLE_MODAL:
1536 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1537 default:
1538 return CollectionInfo.SELECTION_MODE_NONE;
1539 }
1540 }
1541
Alan Viverettea54956a2015-01-07 16:05:02 -08001542 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001543 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001544 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1545 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001546 return true;
1547 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001548 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001549 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1550 case R.id.accessibilityActionScrollDown: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001551 if (isEnabled() && canScrollDown()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001552 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1553 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1554 return true;
1555 }
1556 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001557 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1558 case R.id.accessibilityActionScrollUp: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001559 if (isEnabled() && canScrollUp()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001560 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1561 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1562 return true;
1563 }
1564 } return false;
1565 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001566 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001567 }
1568
Svetoslav5b578da2013-05-08 14:23:32 -07001569 /** @hide */
1570 @Override
1571 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1572 if (accessibilityId == getAccessibilityViewId()) {
1573 return this;
1574 }
Svetoslav5b578da2013-05-08 14:23:32 -07001575 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1576 }
1577
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001578 /**
1579 * Indicates whether the children's drawing cache is used during a scroll.
1580 * By default, the drawing cache is enabled but this will consume more memory.
1581 *
1582 * @return true if the scrolling cache is enabled, false otherwise
1583 *
1584 * @see #setScrollingCacheEnabled(boolean)
1585 * @see View#setDrawingCacheEnabled(boolean)
1586 */
1587 @ViewDebug.ExportedProperty
1588 public boolean isScrollingCacheEnabled() {
1589 return mScrollingCacheEnabled;
1590 }
1591
1592 /**
1593 * Enables or disables the children's drawing cache during a scroll.
1594 * By default, the drawing cache is enabled but this will use more memory.
1595 *
1596 * When the scrolling cache is enabled, the caches are kept after the
1597 * first scrolling. You can manually clear the cache by calling
1598 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1599 *
1600 * @param enabled true to enable the scroll cache, false otherwise
1601 *
1602 * @see #isScrollingCacheEnabled()
1603 * @see View#setDrawingCacheEnabled(boolean)
1604 */
1605 public void setScrollingCacheEnabled(boolean enabled) {
1606 if (mScrollingCacheEnabled && !enabled) {
1607 clearScrollingCache();
1608 }
1609 mScrollingCacheEnabled = enabled;
1610 }
1611
1612 /**
1613 * Enables or disables the type filter window. If enabled, typing when
1614 * this view has focus will filter the children to match the users input.
1615 * Note that the {@link Adapter} used by this view must implement the
1616 * {@link Filterable} interface.
1617 *
1618 * @param textFilterEnabled true to enable type filtering, false otherwise
1619 *
1620 * @see Filterable
1621 */
1622 public void setTextFilterEnabled(boolean textFilterEnabled) {
1623 mTextFilterEnabled = textFilterEnabled;
1624 }
1625
1626 /**
1627 * Indicates whether type filtering is enabled for this view
1628 *
1629 * @return true if type filtering is enabled, false otherwise
1630 *
1631 * @see #setTextFilterEnabled(boolean)
1632 * @see Filterable
1633 */
1634 @ViewDebug.ExportedProperty
1635 public boolean isTextFilterEnabled() {
1636 return mTextFilterEnabled;
1637 }
1638
1639 @Override
1640 public void getFocusedRect(Rect r) {
1641 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001642 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001643 // the focused rectangle of the selected view offset into the
1644 // coordinate space of this view.
1645 view.getFocusedRect(r);
1646 offsetDescendantRectToMyCoords(view, r);
1647 } else {
1648 // otherwise, just the norm
1649 super.getFocusedRect(r);
1650 }
1651 }
1652
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001653 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001654 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 com.android.internal.R.drawable.list_selector_background));
1656 }
1657
1658 /**
1659 * Indicates whether the content of this view is pinned to, or stacked from,
1660 * the bottom edge.
1661 *
1662 * @return true if the content is stacked from the bottom edge, false otherwise
1663 */
1664 @ViewDebug.ExportedProperty
1665 public boolean isStackFromBottom() {
1666 return mStackFromBottom;
1667 }
1668
1669 /**
1670 * When stack from bottom is set to true, the list fills its content starting from
1671 * the bottom of the view.
1672 *
1673 * @param stackFromBottom true to pin the view's content to the bottom edge,
1674 * false to pin the view's content to the top edge
1675 */
1676 public void setStackFromBottom(boolean stackFromBottom) {
1677 if (mStackFromBottom != stackFromBottom) {
1678 mStackFromBottom = stackFromBottom;
1679 requestLayoutIfNecessary();
1680 }
1681 }
1682
1683 void requestLayoutIfNecessary() {
1684 if (getChildCount() > 0) {
1685 resetList();
1686 requestLayout();
1687 invalidate();
1688 }
1689 }
1690
1691 static class SavedState extends BaseSavedState {
1692 long selectedId;
1693 long firstId;
1694 int viewTop;
1695 int position;
1696 int height;
1697 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001698 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001699 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001700 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001701 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001702
1703 /**
1704 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1705 */
1706 SavedState(Parcelable superState) {
1707 super(superState);
1708 }
1709
1710 /**
1711 * Constructor called from {@link #CREATOR}
1712 */
1713 private SavedState(Parcel in) {
1714 super(in);
1715 selectedId = in.readLong();
1716 firstId = in.readLong();
1717 viewTop = in.readInt();
1718 position = in.readInt();
1719 height = in.readInt();
1720 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001721 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001722 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001723 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001724 final int N = in.readInt();
1725 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001726 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001727 for (int i=0; i<N; i++) {
1728 final long key = in.readLong();
1729 final int value = in.readInt();
1730 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001731 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001732 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001733 }
1734
1735 @Override
1736 public void writeToParcel(Parcel out, int flags) {
1737 super.writeToParcel(out, flags);
1738 out.writeLong(selectedId);
1739 out.writeLong(firstId);
1740 out.writeInt(viewTop);
1741 out.writeInt(position);
1742 out.writeInt(height);
1743 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001744 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001745 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001746 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001747 final int N = checkIdState != null ? checkIdState.size() : 0;
1748 out.writeInt(N);
1749 for (int i=0; i<N; i++) {
1750 out.writeLong(checkIdState.keyAt(i));
1751 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001752 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001753 }
1754
1755 @Override
1756 public String toString() {
1757 return "AbsListView.SavedState{"
1758 + Integer.toHexString(System.identityHashCode(this))
1759 + " selectedId=" + selectedId
1760 + " firstId=" + firstId
1761 + " viewTop=" + viewTop
1762 + " position=" + position
1763 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001764 + " filter=" + filter
1765 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001766 }
1767
1768 public static final Parcelable.Creator<SavedState> CREATOR
1769 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001770 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001771 public SavedState createFromParcel(Parcel in) {
1772 return new SavedState(in);
1773 }
1774
Alan Viverette8fa327a2013-05-31 14:53:13 -07001775 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001776 public SavedState[] newArray(int size) {
1777 return new SavedState[size];
1778 }
1779 };
1780 }
1781
1782 @Override
1783 public Parcelable onSaveInstanceState() {
1784 /*
1785 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001786 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001787 * that happen early enough to keep from getting complaints
1788 * about having leaked the window.
1789 */
1790 dismissPopup();
1791
1792 Parcelable superState = super.onSaveInstanceState();
1793
1794 SavedState ss = new SavedState(superState);
1795
Dianne Hackborne181bd92012-09-25 14:15:15 -07001796 if (mPendingSync != null) {
1797 // Just keep what we last restored.
1798 ss.selectedId = mPendingSync.selectedId;
1799 ss.firstId = mPendingSync.firstId;
1800 ss.viewTop = mPendingSync.viewTop;
1801 ss.position = mPendingSync.position;
1802 ss.height = mPendingSync.height;
1803 ss.filter = mPendingSync.filter;
1804 ss.inActionMode = mPendingSync.inActionMode;
1805 ss.checkedItemCount = mPendingSync.checkedItemCount;
1806 ss.checkState = mPendingSync.checkState;
1807 ss.checkIdState = mPendingSync.checkIdState;
1808 return ss;
1809 }
1810
Dianne Hackborn99441c42010-12-15 11:02:55 -08001811 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001812 long selectedId = getSelectedItemId();
1813 ss.selectedId = selectedId;
1814 ss.height = getHeight();
1815
1816 if (selectedId >= 0) {
1817 // Remember the selection
1818 ss.viewTop = mSelectedTop;
1819 ss.position = getSelectedItemPosition();
1820 ss.firstId = INVALID_POSITION;
1821 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001822 if (haveChildren && mFirstPosition > 0) {
1823 // Remember the position of the first child.
1824 // We only do this if we are not currently at the top of
1825 // the list, for two reasons:
1826 // (1) The list may be in the process of becoming empty, in
1827 // which case mItemCount may not be 0, but if we try to
1828 // ask for any information about position 0 we will crash.
1829 // (2) Being "at the top" seems like a special case, anyway,
1830 // and the user wouldn't expect to end up somewhere else when
1831 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001832 View v = getChildAt(0);
1833 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001834 int firstPos = mFirstPosition;
1835 if (firstPos >= mItemCount) {
1836 firstPos = mItemCount - 1;
1837 }
1838 ss.position = firstPos;
1839 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001840 } else {
1841 ss.viewTop = 0;
1842 ss.firstId = INVALID_POSITION;
1843 ss.position = 0;
1844 }
1845 }
1846
1847 ss.filter = null;
1848 if (mFiltered) {
1849 final EditText textFilter = mTextFilter;
1850 if (textFilter != null) {
1851 Editable filterText = textFilter.getText();
1852 if (filterText != null) {
1853 ss.filter = filterText.toString();
1854 }
1855 }
1856 }
1857
Adam Powella0eeeac2010-11-05 11:55:05 -07001858 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1859
Adam Powell9a5cc282011-08-28 16:18:16 -07001860 if (mCheckStates != null) {
1861 ss.checkState = mCheckStates.clone();
1862 }
1863 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001864 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001865 final int count = mCheckedIdStates.size();
1866 for (int i = 0; i < count; i++) {
1867 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1868 }
1869 ss.checkIdState = idState;
1870 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001871 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001872
Adam Cohen335c3b62012-07-24 17:18:16 -07001873 if (mRemoteAdapter != null) {
1874 mRemoteAdapter.saveRemoteViewsCache();
1875 }
1876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001877 return ss;
1878 }
1879
1880 @Override
1881 public void onRestoreInstanceState(Parcelable state) {
1882 SavedState ss = (SavedState) state;
1883
1884 super.onRestoreInstanceState(ss.getSuperState());
1885 mDataChanged = true;
1886
1887 mSyncHeight = ss.height;
1888
1889 if (ss.selectedId >= 0) {
1890 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001891 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001892 mSyncRowId = ss.selectedId;
1893 mSyncPosition = ss.position;
1894 mSpecificTop = ss.viewTop;
1895 mSyncMode = SYNC_SELECTED_POSITION;
1896 } else if (ss.firstId >= 0) {
1897 setSelectedPositionInt(INVALID_POSITION);
1898 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1899 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001900 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001901 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001902 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001903 mSyncRowId = ss.firstId;
1904 mSyncPosition = ss.position;
1905 mSpecificTop = ss.viewTop;
1906 mSyncMode = SYNC_FIRST_POSITION;
1907 }
1908
1909 setFilterText(ss.filter);
1910
Adam Powellf343e1b2010-08-13 18:27:04 -07001911 if (ss.checkState != null) {
1912 mCheckStates = ss.checkState;
1913 }
1914
1915 if (ss.checkIdState != null) {
1916 mCheckedIdStates = ss.checkIdState;
1917 }
1918
Adam Powell2614c6c2010-11-04 17:54:45 -07001919 mCheckedItemCount = ss.checkedItemCount;
1920
Adam Powella0eeeac2010-11-05 11:55:05 -07001921 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1922 mMultiChoiceModeCallback != null) {
1923 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1924 }
1925
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001926 requestLayout();
1927 }
1928
1929 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001930 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1931 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001932 }
1933
1934 /**
1935 * Sets the initial value for the text filter.
1936 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001937 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001938 * @see #setTextFilterEnabled
1939 */
1940 public void setFilterText(String filterText) {
1941 // TODO: Should we check for acceptFilter()?
1942 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1943 createTextFilter(false);
1944 // This is going to call our listener onTextChanged, but we might not
1945 // be ready to bring up a window yet
1946 mTextFilter.setText(filterText);
1947 mTextFilter.setSelection(filterText.length());
1948 if (mAdapter instanceof Filterable) {
1949 // if mPopup is non-null, then onTextChanged will do the filtering
1950 if (mPopup == null) {
1951 Filter f = ((Filterable) mAdapter).getFilter();
1952 f.filter(filterText);
1953 }
1954 // Set filtered to true so we will display the filter window when our main
1955 // window is ready
1956 mFiltered = true;
1957 mDataSetObserver.clearSavedState();
1958 }
1959 }
1960 }
1961
1962 /**
Romain Guy0a637162009-05-29 14:43:54 -07001963 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001964 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001965 */
1966 public CharSequence getTextFilter() {
1967 if (mTextFilterEnabled && mTextFilter != null) {
1968 return mTextFilter.getText();
1969 }
1970 return null;
1971 }
Romain Guy0a637162009-05-29 14:43:54 -07001972
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001973 @Override
1974 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1975 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1976 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001977 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001978 // Data may have changed while we were detached and it's valid
1979 // to change focus while detached. Refresh so we don't die.
1980 mDataChanged = true;
1981 mOldItemCount = mItemCount;
1982 mItemCount = mAdapter.getCount();
1983 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001984 resurrectSelection();
1985 }
1986 }
1987
1988 @Override
1989 public void requestLayout() {
1990 if (!mBlockLayoutRequests && !mInLayout) {
1991 super.requestLayout();
1992 }
1993 }
1994
1995 /**
1996 * The list is empty. Clear everything out.
1997 */
1998 void resetList() {
1999 removeAllViewsInLayout();
2000 mFirstPosition = 0;
2001 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07002002 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002003 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07002004 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002005 mOldSelectedPosition = INVALID_POSITION;
2006 mOldSelectedRowId = INVALID_ROW_ID;
2007 setSelectedPositionInt(INVALID_POSITION);
2008 setNextSelectedPositionInt(INVALID_POSITION);
2009 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002010 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002011 mSelectorRect.setEmpty();
2012 invalidate();
2013 }
2014
2015 @Override
2016 protected int computeVerticalScrollExtent() {
2017 final int count = getChildCount();
2018 if (count > 0) {
2019 if (mSmoothScrollbarEnabled) {
2020 int extent = count * 100;
2021
2022 View view = getChildAt(0);
2023 final int top = view.getTop();
2024 int height = view.getHeight();
2025 if (height > 0) {
2026 extent += (top * 100) / height;
2027 }
2028
2029 view = getChildAt(count - 1);
2030 final int bottom = view.getBottom();
2031 height = view.getHeight();
2032 if (height > 0) {
2033 extent -= ((bottom - getHeight()) * 100) / height;
2034 }
2035
2036 return extent;
2037 } else {
2038 return 1;
2039 }
2040 }
2041 return 0;
2042 }
2043
2044 @Override
2045 protected int computeVerticalScrollOffset() {
2046 final int firstPosition = mFirstPosition;
2047 final int childCount = getChildCount();
2048 if (firstPosition >= 0 && childCount > 0) {
2049 if (mSmoothScrollbarEnabled) {
2050 final View view = getChildAt(0);
2051 final int top = view.getTop();
2052 int height = view.getHeight();
2053 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002054 return Math.max(firstPosition * 100 - (top * 100) / height +
2055 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002056 }
2057 } else {
2058 int index;
2059 final int count = mItemCount;
2060 if (firstPosition == 0) {
2061 index = 0;
2062 } else if (firstPosition + childCount == count) {
2063 index = count;
2064 } else {
2065 index = firstPosition + childCount / 2;
2066 }
2067 return (int) (firstPosition + childCount * (index / (float) count));
2068 }
2069 }
2070 return 0;
2071 }
2072
2073 @Override
2074 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002075 int result;
2076 if (mSmoothScrollbarEnabled) {
2077 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002078 if (mScrollY != 0) {
2079 // Compensate for overscroll
2080 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2081 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002082 } else {
2083 result = mItemCount;
2084 }
2085 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002086 }
2087
2088 @Override
2089 protected float getTopFadingEdgeStrength() {
2090 final int count = getChildCount();
2091 final float fadeEdge = super.getTopFadingEdgeStrength();
2092 if (count == 0) {
2093 return fadeEdge;
2094 } else {
2095 if (mFirstPosition > 0) {
2096 return 1.0f;
2097 }
2098
2099 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002100 final float fadeLength = getVerticalFadingEdgeLength();
2101 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002102 }
2103 }
2104
2105 @Override
2106 protected float getBottomFadingEdgeStrength() {
2107 final int count = getChildCount();
2108 final float fadeEdge = super.getBottomFadingEdgeStrength();
2109 if (count == 0) {
2110 return fadeEdge;
2111 } else {
2112 if (mFirstPosition + count - 1 < mItemCount - 1) {
2113 return 1.0f;
2114 }
2115
2116 final int bottom = getChildAt(count - 1).getBottom();
2117 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002118 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002119 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002120 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002121 }
2122 }
2123
2124 @Override
2125 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2126 if (mSelector == null) {
2127 useDefaultSelector();
2128 }
2129 final Rect listPadding = mListPadding;
2130 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2131 listPadding.top = mSelectionTopPadding + mPaddingTop;
2132 listPadding.right = mSelectionRightPadding + mPaddingRight;
2133 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002134
2135 // Check if our previous measured size was at a point where we should scroll later.
2136 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2137 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002138 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002139 final View lastChild = getChildAt(childCount - 1);
2140 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002141 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002142 lastBottom <= listBottom;
2143 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002144 }
2145
Romain Guyd6a463a2009-05-21 23:10:10 -07002146 /**
2147 * Subclasses should NOT override this method but
2148 * {@link #layoutChildren()} instead.
2149 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002150 @Override
2151 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2152 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002153
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002154 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002155
Alan Viverette4b95cc72014-01-14 16:54:02 -08002156 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002157 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002158 for (int i = 0; i < childCount; i++) {
2159 getChildAt(i).forceLayout();
2160 }
2161 mRecycler.markChildrenDirty();
2162 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002163
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002164 layoutChildren();
Adam Powell637d3372010-08-25 14:37:03 -07002165
2166 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002167
2168 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2169 if (mFastScroll != null) {
2170 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2171 }
Phil Weavera9d976f2016-11-01 09:55:24 -07002172 mInLayout = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002173 }
2174
2175 /**
2176 * @hide
2177 */
2178 @Override
2179 protected boolean setFrame(int left, int top, int right, int bottom) {
2180 final boolean changed = super.setFrame(left, top, right, bottom);
2181
Romain Guyd6a463a2009-05-21 23:10:10 -07002182 if (changed) {
2183 // Reposition the popup when the frame has changed. This includes
2184 // translating the widget, not just changing its dimension. The
2185 // filter popup needs to follow the widget.
2186 final boolean visible = getWindowVisibility() == View.VISIBLE;
2187 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2188 positionPopup();
2189 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002190 }
2191
2192 return changed;
2193 }
2194
Romain Guyd6a463a2009-05-21 23:10:10 -07002195 /**
2196 * Subclasses must override this method to layout their children.
2197 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002198 protected void layoutChildren() {
2199 }
2200
Alan Viverette5d565fa2013-10-30 11:09:03 -07002201 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002202 * @param focusedView view that holds accessibility focus
2203 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002204 * child contains accessibility focus
2205 */
Alan Viverette3e141622014-02-18 17:05:13 -08002206 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002207 ViewParent viewParent = focusedView.getParent();
2208 while ((viewParent instanceof View) && (viewParent != this)) {
2209 focusedView = (View) viewParent;
2210 viewParent = viewParent.getParent();
2211 }
2212
2213 if (!(viewParent instanceof View)) {
2214 return null;
2215 }
2216
2217 return focusedView;
2218 }
2219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002220 void updateScrollIndicators() {
2221 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002222 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002223 }
2224
2225 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002226 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002227 }
2228 }
2229
Alan Viverette947a9692014-09-25 12:43:47 -07002230 private boolean canScrollUp() {
2231 boolean canScrollUp;
2232 // 0th element is not visible
2233 canScrollUp = mFirstPosition > 0;
2234
2235 // ... Or top of 0th element is not visible
2236 if (!canScrollUp) {
2237 if (getChildCount() > 0) {
2238 View child = getChildAt(0);
2239 canScrollUp = child.getTop() < mListPadding.top;
2240 }
2241 }
2242
2243 return canScrollUp;
2244 }
2245
2246 private boolean canScrollDown() {
2247 boolean canScrollDown;
2248 int count = getChildCount();
2249
2250 // Last item is not visible
2251 canScrollDown = (mFirstPosition + count) < mItemCount;
2252
2253 // ... Or bottom of the last element is not visible
2254 if (!canScrollDown && count > 0) {
2255 View child = getChildAt(count - 1);
2256 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2257 }
2258
2259 return canScrollDown;
2260 }
2261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002262 @Override
2263 @ViewDebug.ExportedProperty
2264 public View getSelectedView() {
2265 if (mItemCount > 0 && mSelectedPosition >= 0) {
2266 return getChildAt(mSelectedPosition - mFirstPosition);
2267 } else {
2268 return null;
2269 }
2270 }
2271
2272 /**
2273 * List padding is the maximum of the normal view's padding and the padding of the selector.
2274 *
2275 * @see android.view.View#getPaddingTop()
2276 * @see #getSelector()
2277 *
2278 * @return The top list padding.
2279 */
2280 public int getListPaddingTop() {
2281 return mListPadding.top;
2282 }
2283
2284 /**
2285 * List padding is the maximum of the normal view's padding and the padding of the selector.
2286 *
2287 * @see android.view.View#getPaddingBottom()
2288 * @see #getSelector()
2289 *
2290 * @return The bottom list padding.
2291 */
2292 public int getListPaddingBottom() {
2293 return mListPadding.bottom;
2294 }
2295
2296 /**
2297 * List padding is the maximum of the normal view's padding and the padding of the selector.
2298 *
2299 * @see android.view.View#getPaddingLeft()
2300 * @see #getSelector()
2301 *
2302 * @return The left list padding.
2303 */
2304 public int getListPaddingLeft() {
2305 return mListPadding.left;
2306 }
2307
2308 /**
2309 * List padding is the maximum of the normal view's padding and the padding of the selector.
2310 *
2311 * @see android.view.View#getPaddingRight()
2312 * @see #getSelector()
2313 *
2314 * @return The right list padding.
2315 */
2316 public int getListPaddingRight() {
2317 return mListPadding.right;
2318 }
2319
2320 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002321 * Gets a view and have it show the data associated with the specified
2322 * position. This is called when we have already discovered that the view
2323 * is not available for reuse in the recycle bin. The only choices left are
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002324 * converting an old view or making a new one.
2325 *
Alan Viverette26489e12016-07-07 16:39:27 -04002326 * @param position the position to display
2327 * @param outMetadata an array of at least 1 boolean where the first entry
2328 * will be set {@code true} if the view is currently
2329 * attached to the window, {@code false} otherwise (e.g.
2330 * newly-inflated or remained scrap for multiple layout
2331 * passes)
Mindy Pereira4e30d892010-11-24 15:32:39 -08002332 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002333 * @return A view displaying the data associated with the specified position
2334 */
Alan Viverette26489e12016-07-07 16:39:27 -04002335 View obtainView(int position, boolean[] outMetadata) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002336 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2337
Alan Viverette26489e12016-07-07 16:39:27 -04002338 outMetadata[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002339
Alan Viverette59511502013-12-09 13:49:25 -08002340 // Check whether we have a transient state view. Attempt to re-bind the
2341 // data and discard the view if we fail.
2342 final View transientView = mRecycler.getTransientStateView(position);
2343 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002344 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2345
2346 // If the view type hasn't changed, attempt to re-bind the data.
2347 if (params.viewType == mAdapter.getItemViewType(position)) {
2348 final View updatedView = mAdapter.getView(position, transientView, this);
2349
2350 // If we failed to re-bind the data, scrap the obtained view.
2351 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002352 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002353 mRecycler.addScrapView(updatedView, position);
2354 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002355 }
2356
Alan Viverette26489e12016-07-07 16:39:27 -04002357 outMetadata[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002358
2359 // Finish the temporary detach started in addScrapView().
2360 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002361 return transientView;
2362 }
2363
2364 final View scrapView = mRecycler.getScrapView(position);
2365 final View child = mAdapter.getView(position, scrapView, this);
2366 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002367 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002368 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002369 mRecycler.addScrapView(scrapView, position);
Alan Viverette26489e12016-07-07 16:39:27 -04002370 } else if (child.isTemporarilyDetached()) {
2371 outMetadata[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002372
Alan Viverette26489e12016-07-07 16:39:27 -04002373 // Finish the temporary detach started in addScrapView().
2374 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002375 }
Alan Viverette59511502013-12-09 13:49:25 -08002376 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002377
Alan Viverette59511502013-12-09 13:49:25 -08002378 if (mCacheColorHint != 0) {
2379 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2380 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002381
Alan Viverette59511502013-12-09 13:49:25 -08002382 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2383 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002384 }
2385
Alan Viverettee6be9c782014-02-26 18:16:36 -08002386 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002387
alanvc1d7e772012-05-08 14:47:24 -07002388 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2389 if (mAccessibilityDelegate == null) {
2390 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2391 }
alanvb72fe7a2012-08-27 16:44:25 -07002392 if (child.getAccessibilityDelegate() == null) {
2393 child.setAccessibilityDelegate(mAccessibilityDelegate);
2394 }
alanvc1d7e772012-05-08 14:47:24 -07002395 }
2396
Romain Guy5fade8c2013-07-10 16:36:18 -07002397 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2398
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002399 return child;
2400 }
2401
Alan Viverettee6be9c782014-02-26 18:16:36 -08002402 private void setItemViewLayoutParams(View child, int position) {
2403 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2404 LayoutParams lp;
2405 if (vlp == null) {
2406 lp = (LayoutParams) generateDefaultLayoutParams();
2407 } else if (!checkLayoutParams(vlp)) {
2408 lp = (LayoutParams) generateLayoutParams(vlp);
2409 } else {
2410 lp = (LayoutParams) vlp;
2411 }
2412
2413 if (mAdapterHasStableIds) {
2414 lp.itemId = mAdapter.getItemId(position);
2415 }
2416 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002417 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002418 if (lp != vlp) {
2419 child.setLayoutParams(lp);
2420 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002421 }
2422
alanvc1d7e772012-05-08 14:47:24 -07002423 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2424 @Override
2425 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2426 super.onInitializeAccessibilityNodeInfo(host, info);
2427
2428 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002429 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002430 }
2431
2432 @Override
2433 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002434 if (super.performAccessibilityAction(host, action, arguments)) {
2435 return true;
2436 }
2437
alanvc1d7e772012-05-08 14:47:24 -07002438 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002439 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002440 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002441 return false;
2442 }
2443
Alan Viverette92539d52015-09-14 10:49:25 -04002444 if (position >= mAdapter.getCount()) {
2445 // The position is no longer valid, likely due to a data set
2446 // change. We could fail here for all data set changes, since
2447 // there is a chance that the data bound to the view may no
2448 // longer exist at the same position within the adapter, but
2449 // it's more consistent with the standard touch interaction to
2450 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002451 return false;
2452 }
2453
Alan Viverette92539d52015-09-14 10:49:25 -04002454 final boolean isItemEnabled;
2455 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2456 if (lp instanceof AbsListView.LayoutParams) {
2457 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2458 } else {
2459 isItemEnabled = false;
2460 }
2461
2462 if (!isEnabled() || !isItemEnabled) {
2463 // Cannot perform actions on disabled items.
2464 return false;
2465 }
alanvc1d7e772012-05-08 14:47:24 -07002466
2467 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002468 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2469 if (getSelectedItemPosition() == position) {
2470 setSelection(INVALID_POSITION);
2471 return true;
2472 }
2473 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002474 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002475 if (getSelectedItemPosition() != position) {
2476 setSelection(position);
2477 return true;
2478 }
2479 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002480 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002481 if (isItemClickable(host)) {
2482 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002483 return performItemClick(host, position, id);
2484 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002485 } return false;
2486 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2487 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002488 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002489 return performLongPress(host, position, id);
2490 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002491 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002492 }
2493
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002494 return false;
alanvc1d7e772012-05-08 14:47:24 -07002495 }
2496 }
2497
Alan Viverette5b2081d2013-08-28 10:43:07 -07002498 /**
2499 * Initializes an {@link AccessibilityNodeInfo} with information about a
2500 * particular item in the list.
2501 *
2502 * @param view View representing the list item.
2503 * @param position Position of the list item within the adapter.
2504 * @param info Node info to populate.
2505 */
2506 public void onInitializeAccessibilityNodeInfoForItem(
2507 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002508 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002509 // The item doesn't exist, so there's not much we can do here.
2510 return;
2511 }
2512
Alan Viverette92539d52015-09-14 10:49:25 -04002513 final boolean isItemEnabled;
2514 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2515 if (lp instanceof AbsListView.LayoutParams) {
2516 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2517 } else {
2518 isItemEnabled = false;
2519 }
2520
2521 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002522 info.setEnabled(false);
2523 return;
2524 }
2525
2526 if (position == getSelectedItemPosition()) {
2527 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002528 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002529 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002530 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002531 }
2532
Alan Viverette92539d52015-09-14 10:49:25 -04002533 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002534 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002535 info.setClickable(true);
2536 }
2537
2538 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002539 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002540 info.setLongClickable(true);
2541 }
2542 }
2543
Alan Viverette92539d52015-09-14 10:49:25 -04002544 private boolean isItemClickable(View view) {
Adam Powell0f552f42017-02-03 11:50:42 -08002545 return !view.hasExplicitFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002546 }
2547
Alan Viverettede399392014-05-01 17:20:55 -07002548 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002549 * Positions the selector in a way that mimics touch.
2550 */
2551 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002552 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002553 }
2554
2555 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002556 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002557 */
2558 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002559 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002560 final Rect bounds = mSelectorRect;
2561 final float x = bounds.exactCenterX();
2562 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002563 positionSelector(position, sel, true, x, y);
2564 } else {
2565 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002566 }
2567 }
2568
Dianne Hackborn079e2352010-10-18 17:02:43 -07002569 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002570 positionSelector(position, sel, false, -1, -1);
2571 }
2572
2573 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2574 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002575 if (position != INVALID_POSITION) {
2576 mSelectorPosition = position;
2577 }
2578
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002579 final Rect selectorRect = mSelectorRect;
2580 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002581 if (sel instanceof SelectionBoundsAdjuster) {
2582 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2583 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002584
2585 // Adjust for selection padding.
2586 selectorRect.left -= mSelectionLeftPadding;
2587 selectorRect.top -= mSelectionTopPadding;
2588 selectorRect.right += mSelectionRightPadding;
2589 selectorRect.bottom += mSelectionBottomPadding;
2590
Alan Viverettea19ab342015-05-18 13:20:52 -07002591 // Update the child enabled state prior to updating the selector.
2592 final boolean isChildViewEnabled = sel.isEnabled();
2593 if (mIsChildViewEnabled != isChildViewEnabled) {
2594 mIsChildViewEnabled = isChildViewEnabled;
2595 }
2596
2597 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002598 final Drawable selector = mSelector;
2599 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002600 if (positionChanged) {
2601 // Wipe out the current selector state so that we can start
2602 // over in the new position with a fresh state.
2603 selector.setVisible(false, false);
2604 selector.setState(StateSet.NOTHING);
2605 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002606 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002607 if (positionChanged) {
2608 if (getVisibility() == VISIBLE) {
2609 selector.setVisible(true, false);
2610 }
Chet Haase2167b112014-12-19 16:37:18 -08002611 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002612 }
2613 if (manageHotspot) {
2614 selector.setHotspot(x, y);
2615 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002616 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002617 }
2618
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002619 @Override
2620 protected void dispatchDraw(Canvas canvas) {
2621 int saveCount = 0;
2622 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2623 if (clipToPadding) {
2624 saveCount = canvas.save();
2625 final int scrollX = mScrollX;
2626 final int scrollY = mScrollY;
2627 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2628 scrollX + mRight - mLeft - mPaddingRight,
2629 scrollY + mBottom - mTop - mPaddingBottom);
2630 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2631 }
2632
2633 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2634 if (!drawSelectorOnTop) {
2635 drawSelector(canvas);
2636 }
2637
2638 super.dispatchDraw(canvas);
2639
2640 if (drawSelectorOnTop) {
2641 drawSelector(canvas);
2642 }
2643
2644 if (clipToPadding) {
2645 canvas.restoreToCount(saveCount);
2646 mGroupFlags |= CLIP_TO_PADDING_MASK;
2647 }
2648 }
2649
2650 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002651 protected boolean isPaddingOffsetRequired() {
2652 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2653 }
2654
2655 @Override
2656 protected int getLeftPaddingOffset() {
2657 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2658 }
2659
2660 @Override
2661 protected int getTopPaddingOffset() {
2662 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2663 }
2664
2665 @Override
2666 protected int getRightPaddingOffset() {
2667 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2668 }
2669
2670 @Override
2671 protected int getBottomPaddingOffset() {
2672 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2673 }
2674
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002675 /**
2676 * @hide
2677 */
2678 @Override
2679 protected void internalSetPadding(int left, int top, int right, int bottom) {
2680 super.internalSetPadding(left, top, right, bottom);
2681 if (isLayoutRequested()) {
2682 handleBoundsChange();
2683 }
2684 }
2685
Adam Powell20232d02010-12-08 21:08:53 -08002686 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002687 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002688 handleBoundsChange();
Alan Viverette8636ace2013-10-31 15:41:31 -07002689 if (mFastScroll != null) {
2690 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002691 }
2692 }
2693
2694 /**
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002695 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2696 * and force layouts all children that don't have exact measure specs.
2697 * <p>
2698 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2699 * fail to relayout them properly to accommodate for new bounds.
2700 */
2701 void handleBoundsChange() {
Phil Weavera9d976f2016-11-01 09:55:24 -07002702 if (mInLayout) {
2703 return;
2704 }
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002705 final int childCount = getChildCount();
2706 if (childCount > 0) {
2707 mDataChanged = true;
2708 rememberSyncState();
2709 for (int i = 0; i < childCount; i++) {
2710 final View child = getChildAt(i);
2711 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2712 // force layout child unless it has exact specs
2713 if (lp == null || lp.width < 1 || lp.height < 1) {
2714 child.forceLayout();
2715 }
2716 }
2717 }
2718 }
2719
2720 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002721 * @return True if the current touch mode requires that we draw the selector in the pressed
2722 * state.
2723 */
2724 boolean touchModeDrawsInPressedState() {
2725 // FIXME use isPressed for this
2726 switch (mTouchMode) {
2727 case TOUCH_MODE_TAP:
2728 case TOUCH_MODE_DONE_WAITING:
2729 return true;
2730 default:
2731 return false;
2732 }
2733 }
2734
2735 /**
2736 * Indicates whether this view is in a state where the selector should be drawn. This will
2737 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2738 * the pressed state for an item.
2739 *
2740 * @return True if the selector should be shown
2741 */
2742 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002743 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002744 }
2745
2746 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002747 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002748 final Drawable selector = mSelector;
2749 selector.setBounds(mSelectorRect);
2750 selector.draw(canvas);
2751 }
2752 }
2753
2754 /**
2755 * Controls whether the selection highlight drawable should be drawn on top of the item or
2756 * behind it.
2757 *
2758 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2759 * is false.
2760 *
2761 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2762 */
2763 public void setDrawSelectorOnTop(boolean onTop) {
2764 mDrawSelectorOnTop = onTop;
2765 }
2766
2767 /**
2768 * Set a Drawable that should be used to highlight the currently selected item.
2769 *
2770 * @param resID A Drawable resource to use as the selection highlight.
2771 *
2772 * @attr ref android.R.styleable#AbsListView_listSelector
2773 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002774 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002775 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002776 }
2777
2778 public void setSelector(Drawable sel) {
2779 if (mSelector != null) {
2780 mSelector.setCallback(null);
2781 unscheduleDrawable(mSelector);
2782 }
2783 mSelector = sel;
2784 Rect padding = new Rect();
2785 sel.getPadding(padding);
2786 mSelectionLeftPadding = padding.left;
2787 mSelectionTopPadding = padding.top;
2788 mSelectionRightPadding = padding.right;
2789 mSelectionBottomPadding = padding.bottom;
2790 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002791 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002792 }
2793
2794 /**
2795 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2796 * selection in the list.
2797 *
2798 * @return the drawable used to display the selector
2799 */
2800 public Drawable getSelector() {
2801 return mSelector;
2802 }
2803
2804 /**
2805 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2806 * this is a long press.
2807 */
2808 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002809 if (!isEnabled() || !isClickable()) {
2810 return;
2811 }
2812
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002813 Drawable selector = mSelector;
2814 Rect selectorRect = mSelectorRect;
2815 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002816 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002817
2818 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2819
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002820 if (v != null) {
Adam Powell0f552f42017-02-03 11:50:42 -08002821 if (v.hasExplicitFocusable()) return;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002822 v.setPressed(true);
2823 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002824 setPressed(true);
2825
2826 final boolean longClickable = isLongClickable();
2827 Drawable d = selector.getCurrent();
2828 if (d != null && d instanceof TransitionDrawable) {
2829 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002830 ((TransitionDrawable) d).startTransition(
2831 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002832 } else {
2833 ((TransitionDrawable) d).resetTransition();
2834 }
2835 }
2836 if (longClickable && !mDataChanged) {
2837 if (mPendingCheckForKeyLongPress == null) {
2838 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2839 }
2840 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2841 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2842 }
2843 }
2844 }
2845
2846 public void setScrollIndicators(View up, View down) {
2847 mScrollUp = up;
2848 mScrollDown = down;
2849 }
2850
Dianne Hackborn079e2352010-10-18 17:02:43 -07002851 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002852 final Drawable selector = mSelector;
2853 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002854 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002855 if (selector.setState(getDrawableStateForSelector())) {
2856 invalidateDrawable(selector);
2857 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002858 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002859 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002860 }
2861 }
2862 }
2863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002864 @Override
2865 protected void drawableStateChanged() {
2866 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002867 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002868 }
2869
Alan Viverettef723c832015-02-03 16:31:46 -08002870 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002871 // If the child view is enabled then do the default behavior.
2872 if (mIsChildViewEnabled) {
2873 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002874 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002875 }
2876
2877 // The selector uses this View's drawable state. The selected child view
2878 // is disabled, so we need to remove the enabled state from the drawable
2879 // states.
2880 final int enabledState = ENABLED_STATE_SET[0];
2881
Alan Viverettef723c832015-02-03 16:31:46 -08002882 // If we don't have any extra space, it will return one of the static
2883 // state arrays, and clearing the enabled state on those arrays is a
2884 // bad thing! If we specify we need extra space, it will create+copy
2885 // into a new array that is safely mutable.
2886 final int[] state = onCreateDrawableState(1);
2887
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002888 int enabledPos = -1;
2889 for (int i = state.length - 1; i >= 0; i--) {
2890 if (state[i] == enabledState) {
2891 enabledPos = i;
2892 break;
2893 }
2894 }
2895
2896 // Remove the enabled state
2897 if (enabledPos >= 0) {
2898 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2899 state.length - enabledPos - 1);
2900 }
Romain Guy0a637162009-05-29 14:43:54 -07002901
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002902 return state;
2903 }
2904
2905 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -05002906 public boolean verifyDrawable(@NonNull Drawable dr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002907 return mSelector == dr || super.verifyDrawable(dr);
2908 }
2909
2910 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002911 public void jumpDrawablesToCurrentState() {
2912 super.jumpDrawablesToCurrentState();
2913 if (mSelector != null) mSelector.jumpToCurrentState();
2914 }
2915
2916 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002917 protected void onAttachedToWindow() {
2918 super.onAttachedToWindow();
2919
2920 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002921 treeObserver.addOnTouchModeChangeListener(this);
2922 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2923 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002924 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002925
Romain Guy82afc7b2010-05-13 11:52:37 -07002926 if (mAdapter != null && mDataSetObserver == null) {
2927 mDataSetObserver = new AdapterDataSetObserver();
2928 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002929
2930 // Data may have changed while we were detached. Refresh.
2931 mDataChanged = true;
2932 mOldItemCount = mItemCount;
2933 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002934 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002935 }
2936
2937 @Override
2938 protected void onDetachedFromWindow() {
2939 super.onDetachedFromWindow();
2940
Alan Viverette462c2172014-02-24 12:24:11 -08002941 mIsDetaching = true;
2942
Romain Guy1f7f3c32009-07-22 11:25:42 -07002943 // Dismiss the popup in case onSaveInstanceState() was not invoked
2944 dismissPopup();
2945
Romain Guy21875052010-01-06 18:48:08 -08002946 // Detach any view left in the scrap heap
2947 mRecycler.clear();
2948
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002949 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002950 treeObserver.removeOnTouchModeChangeListener(this);
2951 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002952 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002953 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002954 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002955
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002956 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002957 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2958 mDataSetObserver = null;
2959 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002960
2961 if (mScrollStrictSpan != null) {
2962 mScrollStrictSpan.finish();
2963 mScrollStrictSpan = null;
2964 }
2965
2966 if (mFlingStrictSpan != null) {
2967 mFlingStrictSpan.finish();
2968 mFlingStrictSpan = null;
2969 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002970
2971 if (mFlingRunnable != null) {
2972 removeCallbacks(mFlingRunnable);
2973 }
2974
2975 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002976 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002977 }
2978
2979 if (mClearScrollingCache != null) {
2980 removeCallbacks(mClearScrollingCache);
2981 }
2982
2983 if (mPerformClick != null) {
2984 removeCallbacks(mPerformClick);
2985 }
2986
2987 if (mTouchModeReset != null) {
2988 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002989 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002990 }
Alan Viverette462c2172014-02-24 12:24:11 -08002991
2992 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002993 }
2994
2995 @Override
2996 public void onWindowFocusChanged(boolean hasWindowFocus) {
2997 super.onWindowFocusChanged(hasWindowFocus);
2998
2999 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3000
3001 if (!hasWindowFocus) {
3002 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08003003 if (mFlingRunnable != null) {
3004 removeCallbacks(mFlingRunnable);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003005 // let the fling runnable report its new state which
Mark Wagner670dd812010-01-13 16:17:47 -08003006 // should be idle
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003007 mFlingRunnable.mSuppressIdleStateChangeCall = false;
Mark Wagner670dd812010-01-13 16:17:47 -08003008 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003009 if (mPositionScroller != null) {
3010 mPositionScroller.stop();
3011 }
Adam Powell45803472010-01-25 15:10:44 -08003012 if (mScrollY != 0) {
3013 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003014 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003015 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08003016 invalidate();
3017 }
Mark Wagner670dd812010-01-13 16:17:47 -08003018 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003019 // Always hide the type filter
3020 dismissPopup();
3021
3022 if (touchMode == TOUCH_MODE_OFF) {
3023 // Remember the last selected element
3024 mResurrectToPosition = mSelectedPosition;
3025 }
3026 } else {
Adam Powell97566042010-03-09 15:34:09 -08003027 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003028 // Show the type filter only if a filter is in effect
3029 showPopup();
3030 }
3031
3032 // If we changed touch mode since the last time we had focus
3033 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3034 // If we come back in trackball mode, we bring the selection back
3035 if (touchMode == TOUCH_MODE_OFF) {
3036 // This will trigger a layout
3037 resurrectSelection();
3038
3039 // If we come back in touch mode, then we want to hide the selector
3040 } else {
3041 hideSelector();
3042 mLayoutMode = LAYOUT_NORMAL;
3043 layoutChildren();
3044 }
3045 }
3046 }
3047
3048 mLastTouchMode = touchMode;
3049 }
3050
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003051 @Override
3052 public void onRtlPropertiesChanged(int layoutDirection) {
3053 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003054 if (mFastScroll != null) {
3055 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003056 }
3057 }
3058
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003059 /**
3060 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3061 * methods knows the view, position and ID of the item that received the
3062 * long press.
3063 *
3064 * @param view The view that received the long press.
3065 * @param position The position of the item that received the long press.
3066 * @param id The ID of the item that received the long press.
3067 * @return The extra information that should be returned by
3068 * {@link #getContextMenuInfo()}.
3069 */
3070 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3071 return new AdapterContextMenuInfo(view, position, id);
3072 }
3073
Adam Powell14874662013-07-18 19:42:41 -07003074 @Override
3075 public void onCancelPendingInputEvents() {
3076 super.onCancelPendingInputEvents();
3077 if (mPerformClick != null) {
3078 removeCallbacks(mPerformClick);
3079 }
3080 if (mPendingCheckForTap != null) {
3081 removeCallbacks(mPendingCheckForTap);
3082 }
3083 if (mPendingCheckForLongPress != null) {
3084 removeCallbacks(mPendingCheckForLongPress);
3085 }
3086 if (mPendingCheckForKeyLongPress != null) {
3087 removeCallbacks(mPendingCheckForKeyLongPress);
3088 }
3089 }
3090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003091 /**
3092 * A base class for Runnables that will check that their view is still attached to
3093 * the original window as when the Runnable was created.
3094 *
3095 */
3096 private class WindowRunnnable {
3097 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003098
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003099 public void rememberWindowAttachCount() {
3100 mOriginalAttachCount = getWindowAttachCount();
3101 }
Romain Guy0a637162009-05-29 14:43:54 -07003102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003103 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003104 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003105 }
3106 }
Romain Guy0a637162009-05-29 14:43:54 -07003107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003108 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003109 int mClickMotionPosition;
3110
Alan Viverette8fa327a2013-05-31 14:53:13 -07003111 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003112 public void run() {
3113 // The data has changed since we posted this action in the event queue,
3114 // bail out before bad things happen
3115 if (mDataChanged) return;
3116
Adam Powell005c0a42010-03-30 16:26:36 -07003117 final ListAdapter adapter = mAdapter;
3118 final int motionPosition = mClickMotionPosition;
3119 if (adapter != null && mItemCount > 0 &&
3120 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003121 motionPosition < adapter.getCount() && sameWindow() &&
3122 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003123 final View view = getChildAt(motionPosition - mFirstPosition);
3124 // If there is no view, something bad happened (the view scrolled off the
3125 // screen, etc.) and we should cancel the click
3126 if (view != null) {
3127 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3128 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003129 }
3130 }
3131 }
3132
3133 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003134 private static final int INVALID_COORD = -1;
3135 private float mX = INVALID_COORD;
3136 private float mY = INVALID_COORD;
3137
3138 private void setCoords(float x, float y) {
3139 mX = x;
3140 mY = y;
3141 }
3142
Alan Viverette8fa327a2013-05-31 14:53:13 -07003143 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003144 public void run() {
3145 final int motionPosition = mMotionPosition;
3146 final View child = getChildAt(motionPosition - mFirstPosition);
3147 if (child != null) {
3148 final int longPressPosition = mMotionPosition;
3149 final long longPressId = mAdapter.getItemId(mMotionPosition);
3150
3151 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003152 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003153 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3154 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3155 } else {
3156 handled = performLongPress(child, longPressPosition, longPressId);
3157 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003158 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003159
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003160 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003161 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003162 mTouchMode = TOUCH_MODE_REST;
3163 setPressed(false);
3164 child.setPressed(false);
3165 } else {
3166 mTouchMode = TOUCH_MODE_DONE_WAITING;
3167 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003168 }
3169 }
3170 }
Romain Guy0a637162009-05-29 14:43:54 -07003171
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003172 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003173 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003174 public void run() {
3175 if (isPressed() && mSelectedPosition >= 0) {
3176 int index = mSelectedPosition - mFirstPosition;
3177 View v = getChildAt(index);
3178
3179 if (!mDataChanged) {
3180 boolean handled = false;
3181 if (sameWindow()) {
3182 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3183 }
3184 if (handled) {
3185 setPressed(false);
3186 v.setPressed(false);
3187 }
3188 } else {
3189 setPressed(false);
3190 if (v != null) v.setPressed(false);
3191 }
3192 }
3193 }
3194 }
3195
Mady Mellore5561982015-04-14 15:06:40 -07003196 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003197 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003198 final View child = getChildAt(mMotionPosition - mFirstPosition);
3199 if (child != null) {
3200 final int longPressPosition = mMotionPosition;
3201 final long longPressId = mAdapter.getItemId(mMotionPosition);
3202 if (performLongPress(child, longPressPosition, longPressId)) {
3203 mTouchMode = TOUCH_MODE_REST;
3204 setPressed(false);
3205 child.setPressed(false);
3206 return true;
3207 }
3208 }
3209 }
3210 return false;
3211 }
3212
Adam Powell8350f7d2010-07-28 14:27:28 -07003213 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003214 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003215 return performLongPress(
3216 child,
3217 longPressPosition,
3218 longPressId,
3219 CheckForLongPress.INVALID_COORD,
3220 CheckForLongPress.INVALID_COORD);
3221 }
3222
3223 boolean performLongPress(final View child,
3224 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003225 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3226 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003227 if (mChoiceActionMode == null &&
3228 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003229 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003230 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003231 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003232 return true;
3233 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003234
Adam Powellf343e1b2010-08-13 18:27:04 -07003235 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003236 if (mOnItemLongClickListener != null) {
3237 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3238 longPressPosition, longPressId);
3239 }
3240 if (!handled) {
3241 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003242 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3243 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3244 } else {
3245 handled = super.showContextMenuForChild(AbsListView.this);
3246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003247 }
3248 if (handled) {
3249 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3250 }
3251 return handled;
3252 }
3253
3254 @Override
3255 protected ContextMenuInfo getContextMenuInfo() {
3256 return mContextMenuInfo;
3257 }
3258
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003259 @Override
3260 public boolean showContextMenu() {
3261 return showContextMenuInternal(0, 0, false);
3262 }
3263
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003264 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003265 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003266 return showContextMenuInternal(x, y, true);
3267 }
3268
3269 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003270 final int position = pointToPosition((int)x, (int)y);
3271 if (position != INVALID_POSITION) {
3272 final long id = mAdapter.getItemId(position);
3273 View child = getChildAt(position - mFirstPosition);
3274 if (child != null) {
3275 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003276 if (useOffsets) {
3277 return super.showContextMenuForChild(this, x, y);
3278 } else {
3279 return super.showContextMenuForChild(this);
3280 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003281 }
3282 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003283 if (useOffsets) {
3284 return super.showContextMenu(x, y);
3285 } else {
3286 return super.showContextMenu();
3287 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003288 }
3289
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003290 @Override
3291 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003292 if (isShowingContextMenuWithCoords()) {
3293 return false;
3294 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003295 return showContextMenuForChildInternal(originalView, 0, 0, false);
3296 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003297
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003298 @Override
3299 public boolean showContextMenuForChild(View originalView, float x, float y) {
3300 return showContextMenuForChildInternal(originalView,x, y, true);
3301 }
3302
3303 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3304 boolean useOffsets) {
3305 final int longPressPosition = getPositionForView(originalView);
3306 if (longPressPosition < 0) {
3307 return false;
3308 }
3309
3310 final long longPressId = mAdapter.getItemId(longPressPosition);
3311 boolean handled = false;
3312
3313 if (mOnItemLongClickListener != null) {
3314 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3315 longPressPosition, longPressId);
3316 }
3317
3318 if (!handled) {
3319 final View child = getChildAt(longPressPosition - mFirstPosition);
3320 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3321
3322 if (useOffsets) {
3323 handled = super.showContextMenuForChild(originalView, x, y);
3324 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003325 handled = super.showContextMenuForChild(originalView);
3326 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003327 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003328
3329 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003330 }
3331
3332 @Override
Romain Guydf016072009-08-17 12:51:30 -07003333 public boolean onKeyDown(int keyCode, KeyEvent event) {
3334 return false;
3335 }
3336
3337 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003338 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003339 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003340 if (!isEnabled()) {
3341 return true;
3342 }
Romain Guydf016072009-08-17 12:51:30 -07003343 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003344 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003345 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003346
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003347 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003348 if (view != null) {
3349 performItemClick(view, mSelectedPosition, mSelectedRowId);
3350 view.setPressed(false);
3351 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003352 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353 return true;
3354 }
3355 }
3356 return super.onKeyUp(keyCode, event);
3357 }
3358
3359 @Override
3360 protected void dispatchSetPressed(boolean pressed) {
3361 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3362 // get the selector in the right state, but we don't want to press each child.
3363 }
3364
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003365 @Override
3366 public void dispatchDrawableHotspotChanged(float x, float y) {
3367 // Don't dispatch hotspot changes to children. We'll manually handle
3368 // calling drawableHotspotChanged on the correct child.
3369 }
3370
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003371 /**
3372 * Maps a point to a position in the list.
3373 *
3374 * @param x X in local coordinate
3375 * @param y Y in local coordinate
3376 * @return The position of the item which contains the specified point, or
3377 * {@link #INVALID_POSITION} if the point does not intersect an item.
3378 */
3379 public int pointToPosition(int x, int y) {
3380 Rect frame = mTouchFrame;
3381 if (frame == null) {
3382 mTouchFrame = new Rect();
3383 frame = mTouchFrame;
3384 }
3385
3386 final int count = getChildCount();
3387 for (int i = count - 1; i >= 0; i--) {
3388 final View child = getChildAt(i);
3389 if (child.getVisibility() == View.VISIBLE) {
3390 child.getHitRect(frame);
3391 if (frame.contains(x, y)) {
3392 return mFirstPosition + i;
3393 }
3394 }
3395 }
3396 return INVALID_POSITION;
3397 }
3398
3399
3400 /**
3401 * Maps a point to a the rowId of the item which intersects that point.
3402 *
3403 * @param x X in local coordinate
3404 * @param y Y in local coordinate
3405 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3406 * if the point does not intersect an item.
3407 */
3408 public long pointToRowId(int x, int y) {
3409 int position = pointToPosition(x, y);
3410 if (position >= 0) {
3411 return mAdapter.getItemId(position);
3412 }
3413 return INVALID_ROW_ID;
3414 }
3415
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003416 private final class CheckForTap implements Runnable {
3417 float x;
3418 float y;
3419
Alan Viverette8fa327a2013-05-31 14:53:13 -07003420 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003421 public void run() {
3422 if (mTouchMode == TOUCH_MODE_DOWN) {
3423 mTouchMode = TOUCH_MODE_TAP;
3424 final View child = getChildAt(mMotionPosition - mFirstPosition);
Adam Powell0f552f42017-02-03 11:50:42 -08003425 if (child != null && !child.hasExplicitFocusable()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003426 mLayoutMode = LAYOUT_NORMAL;
3427
3428 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003429 final float[] point = mTmpPoint;
3430 point[0] = x;
3431 point[1] = y;
3432 transformPointToViewLocal(point, child);
3433 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003434 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003435 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003436 layoutChildren();
3437 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003438 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003439
3440 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3441 final boolean longClickable = isLongClickable();
3442
3443 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003444 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003445 if (d != null && d instanceof TransitionDrawable) {
3446 if (longClickable) {
3447 ((TransitionDrawable) d).startTransition(longPressTimeout);
3448 } else {
3449 ((TransitionDrawable) d).resetTransition();
3450 }
3451 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003452 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003453 }
3454
3455 if (longClickable) {
3456 if (mPendingCheckForLongPress == null) {
3457 mPendingCheckForLongPress = new CheckForLongPress();
3458 }
Oren Blasberged391262015-09-01 12:12:51 -07003459 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003460 mPendingCheckForLongPress.rememberWindowAttachCount();
3461 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3462 } else {
3463 mTouchMode = TOUCH_MODE_DONE_WAITING;
3464 }
3465 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003466 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003467 }
3468 }
3469 }
3470 }
3471 }
3472
Adam Powellc501db9f2014-05-08 12:50:10 -07003473 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003474 // Check if we have moved far enough that it looks more like a
3475 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003476 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003477 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003478 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003479 if ((overscroll || distance > mTouchSlop) &&
3480 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003481 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003482 if (overscroll) {
3483 mTouchMode = TOUCH_MODE_OVERSCROLL;
3484 mMotionCorrection = 0;
3485 } else {
3486 mTouchMode = TOUCH_MODE_SCROLL;
3487 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3488 }
Alan Viverette74ded292013-06-03 15:34:11 -07003489 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003490 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003491 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003492 if (motionView != null) {
3493 motionView.setPressed(false);
3494 }
3495 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3496 // Time to start stealing events! Once we've stolen them, don't let anyone
3497 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003498 final ViewParent parent = getParent();
3499 if (parent != null) {
3500 parent.requestDisallowInterceptTouchEvent(true);
3501 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003502 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003503 return true;
3504 }
3505
3506 return false;
3507 }
3508
Adam Powellc501db9f2014-05-08 12:50:10 -07003509 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003510 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003511 int scrollOffsetCorrection = 0;
3512 int scrollConsumedCorrection = 0;
3513 if (mLastY == Integer.MIN_VALUE) {
3514 rawDeltaY -= mMotionCorrection;
3515 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003516 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3517 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003518 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003519 scrollOffsetCorrection = -mScrollOffset[1];
3520 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003521 if (vtev != null) {
3522 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003523 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003524 }
3525 }
Yorke Lee43943d82014-05-08 10:15:20 -07003526 final int deltaY = rawDeltaY;
3527 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003528 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003529 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003530
3531 if (mTouchMode == TOUCH_MODE_SCROLL) {
3532 if (PROFILE_SCROLLING) {
3533 if (!mScrollProfilingStarted) {
3534 Debug.startMethodTracing("AbsListViewScroll");
3535 mScrollProfilingStarted = true;
3536 }
3537 }
3538
3539 if (mScrollStrictSpan == null) {
3540 // If it's non-null, we're already in a scroll.
3541 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3542 }
3543
3544 if (y != mLastY) {
3545 // We may be here after stopping a fling and continuing to scroll.
3546 // If so, we haven't disallowed intercepting touch events yet.
3547 // Make sure that we do so in case we're in a parent that can intercept.
3548 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3549 Math.abs(rawDeltaY) > mTouchSlop) {
3550 final ViewParent parent = getParent();
3551 if (parent != null) {
3552 parent.requestDisallowInterceptTouchEvent(true);
3553 }
3554 }
3555
3556 final int motionIndex;
3557 if (mMotionPosition >= 0) {
3558 motionIndex = mMotionPosition - mFirstPosition;
3559 } else {
3560 // If we don't have a motion position that we can reliably track,
3561 // pick something in the middle to make a best guess at things below.
3562 motionIndex = getChildCount() / 2;
3563 }
3564
3565 int motionViewPrevTop = 0;
3566 View motionView = this.getChildAt(motionIndex);
3567 if (motionView != null) {
3568 motionViewPrevTop = motionView.getTop();
3569 }
3570
3571 // No need to do all this work if we're not going to move anyway
3572 boolean atEdge = false;
3573 if (incrementalDeltaY != 0) {
3574 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3575 }
3576
3577 // Check to see if we have bumped into the scroll limit
3578 motionView = this.getChildAt(motionIndex);
3579 if (motionView != null) {
3580 // Check if the top of the motion view is where it is
3581 // supposed to be
3582 final int motionViewRealTop = motionView.getTop();
3583 if (atEdge) {
3584 // Apply overscroll
3585
3586 int overscroll = -incrementalDeltaY -
3587 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003588 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3589 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003590 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003591 if (vtev != null) {
3592 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003593 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003594 }
Adam Powell96d62af2014-05-02 10:04:38 -07003595 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003596 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3597 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3598
3599 if (atOverscrollEdge && mVelocityTracker != null) {
3600 // Don't allow overfling if we're at the edge
3601 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003602 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003603
Adam Powell96d62af2014-05-02 10:04:38 -07003604 final int overscrollMode = getOverScrollMode();
3605 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3606 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3607 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003608 if (!atOverscrollEdge) {
3609 mDirection = 0; // Reset when entering overscroll.
3610 mTouchMode = TOUCH_MODE_OVERSCROLL;
3611 }
3612 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003613 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003614 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003615 if (!mEdgeGlowBottom.isFinished()) {
3616 mEdgeGlowBottom.onRelease();
3617 }
Doris Liuf36c0612015-06-04 11:11:14 -07003618 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003619 } else if (incrementalDeltaY < 0) {
3620 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3621 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003622 if (!mEdgeGlowTop.isFinished()) {
3623 mEdgeGlowTop.onRelease();
3624 }
Doris Liuf36c0612015-06-04 11:11:14 -07003625 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003626 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003627 }
3628 }
3629 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003630 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003631 }
Yorke Lee43943d82014-05-08 10:15:20 -07003632 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003633 }
3634 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3635 if (y != mLastY) {
3636 final int oldScroll = mScrollY;
3637 final int newScroll = oldScroll - incrementalDeltaY;
3638 int newDirection = y > mLastY ? 1 : -1;
3639
3640 if (mDirection == 0) {
3641 mDirection = newDirection;
3642 }
3643
3644 int overScrollDistance = -incrementalDeltaY;
3645 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3646 overScrollDistance = -oldScroll;
3647 incrementalDeltaY += overScrollDistance;
3648 } else {
3649 incrementalDeltaY = 0;
3650 }
3651
3652 if (overScrollDistance != 0) {
3653 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3654 0, mOverscrollDistance, true);
3655 final int overscrollMode = getOverScrollMode();
3656 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3657 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3658 !contentFits())) {
3659 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003660 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3661 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003662 if (!mEdgeGlowBottom.isFinished()) {
3663 mEdgeGlowBottom.onRelease();
3664 }
Doris Liuf36c0612015-06-04 11:11:14 -07003665 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003666 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003667 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3668 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003669 if (!mEdgeGlowTop.isFinished()) {
3670 mEdgeGlowTop.onRelease();
3671 }
Doris Liuf36c0612015-06-04 11:11:14 -07003672 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003673 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003674 }
3675 }
3676
3677 if (incrementalDeltaY != 0) {
3678 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003679 if (mScrollY != 0) {
3680 mScrollY = 0;
3681 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003682 }
3683
Romain Guy9d849a22012-03-14 16:41:42 -07003684 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3685
Jeff Brown78f6e632011-09-09 17:15:31 -07003686 mTouchMode = TOUCH_MODE_SCROLL;
3687
3688 // We did not scroll the full amount. Treat this essentially like the
3689 // start of a new touch scroll
3690 final int motionPosition = findClosestMotionRow(y);
3691
3692 mMotionCorrection = 0;
3693 View motionView = getChildAt(motionPosition - mFirstPosition);
3694 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003695 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003696 mMotionPosition = motionPosition;
3697 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003698 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003699 mDirection = newDirection;
3700 }
3701 }
3702 }
3703
Doris Liuf36c0612015-06-04 11:11:14 -07003704 private void invalidateTopGlow() {
3705 if (mEdgeGlowTop == null) {
3706 return;
3707 }
3708 final boolean clipToPadding = getClipToPadding();
3709 final int top = clipToPadding ? mPaddingTop : 0;
3710 final int left = clipToPadding ? mPaddingLeft : 0;
3711 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3712 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3713 }
3714
3715 private void invalidateBottomGlow() {
3716 if (mEdgeGlowBottom == null) {
3717 return;
3718 }
3719 final boolean clipToPadding = getClipToPadding();
3720 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3721 final int left = clipToPadding ? mPaddingLeft : 0;
3722 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3723 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3724 }
3725
Alan Viverette8fa327a2013-05-31 14:53:13 -07003726 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003727 public void onTouchModeChanged(boolean isInTouchMode) {
3728 if (isInTouchMode) {
3729 // Get rid of the selection when we enter touch mode
3730 hideSelector();
3731 // Layout, but only if we already have done so previously.
3732 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3733 // state.)
3734 if (getHeight() > 0 && getChildCount() > 0) {
3735 // We do not lose focus initiating a touch (since AbsListView is focusable in
3736 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003737 layoutChildren();
3738 }
Jeff Brown1e209462011-07-14 22:19:19 -07003739 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003740 } else {
3741 int touchMode = mTouchMode;
3742 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3743 if (mFlingRunnable != null) {
3744 mFlingRunnable.endFling();
3745 }
Adam Powell40322522011-01-12 21:58:20 -08003746 if (mPositionScroller != null) {
3747 mPositionScroller.stop();
3748 }
Adam Powell637d3372010-08-25 14:37:03 -07003749
3750 if (mScrollY != 0) {
3751 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003752 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003753 finishGlows();
3754 invalidate();
3755 }
3756 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003757 }
3758 }
3759
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003760 /** @hide */
3761 @Override
3762 protected boolean handleScrollBarDragging(MotionEvent event) {
3763 // Doesn't support normal scroll bar dragging. Use FastScroller.
3764 return false;
3765 }
3766
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003767 @Override
3768 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003769 if (!isEnabled()) {
3770 // A disabled view that is clickable still consumes the touch
3771 // events, it just doesn't respond to them.
3772 return isClickable() || isLongClickable();
3773 }
3774
Adam Powell1fa179ef2012-04-12 15:01:40 -07003775 if (mPositionScroller != null) {
3776 mPositionScroller.stop();
3777 }
3778
Alan Viverette462c2172014-02-24 12:24:11 -08003779 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003780 // Something isn't right.
3781 // Since we rely on being attached to get data set change notifications,
3782 // don't risk doing anything where we might try to resync and find things
3783 // in a bogus state.
3784 return false;
3785 }
3786
Adam Powell96d62af2014-05-02 10:04:38 -07003787 startNestedScroll(SCROLL_AXIS_VERTICAL);
3788
Alan Viverettefb99ba82015-05-01 10:10:15 -07003789 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3790 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003791 }
Romain Guy82f34952009-05-24 18:40:45 -07003792
Michael Jurka13451a42011-08-22 15:54:21 -07003793 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003794 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003795
Alan Viverette8fa327a2013-05-31 14:53:13 -07003796 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003797 if (actionMasked == MotionEvent.ACTION_DOWN) {
3798 mNestedYOffset = 0;
3799 }
3800 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003801 switch (actionMasked) {
3802 case MotionEvent.ACTION_DOWN: {
3803 onTouchDown(ev);
3804 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003805 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003806
Alan Viverette8fa327a2013-05-31 14:53:13 -07003807 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003808 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003809 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003810 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003811
3812 case MotionEvent.ACTION_UP: {
3813 onTouchUp(ev);
3814 break;
3815 }
3816
3817 case MotionEvent.ACTION_CANCEL: {
3818 onTouchCancel();
3819 break;
3820 }
3821
3822 case MotionEvent.ACTION_POINTER_UP: {
3823 onSecondaryPointerUp(ev);
3824 final int x = mMotionX;
3825 final int y = mMotionY;
3826 final int motionPosition = pointToPosition(x, y);
3827 if (motionPosition >= 0) {
3828 // Remember where the motion event started
3829 final View child = getChildAt(motionPosition - mFirstPosition);
3830 mMotionViewOriginalTop = child.getTop();
3831 mMotionPosition = motionPosition;
3832 }
3833 mLastY = y;
3834 break;
3835 }
3836
3837 case MotionEvent.ACTION_POINTER_DOWN: {
3838 // New pointers take over dragging duties
3839 final int index = ev.getActionIndex();
3840 final int id = ev.getPointerId(index);
3841 final int x = (int) ev.getX(index);
3842 final int y = (int) ev.getY(index);
3843 mMotionCorrection = 0;
3844 mActivePointerId = id;
3845 mMotionX = x;
3846 mMotionY = y;
3847 final int motionPosition = pointToPosition(x, y);
3848 if (motionPosition >= 0) {
3849 // Remember where the motion event started
3850 final View child = getChildAt(motionPosition - mFirstPosition);
3851 mMotionViewOriginalTop = child.getTop();
3852 mMotionPosition = motionPosition;
3853 }
3854 mLastY = y;
3855 break;
3856 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003857 }
3858
Adam Powell96d62af2014-05-02 10:04:38 -07003859 if (mVelocityTracker != null) {
3860 mVelocityTracker.addMovement(vtev);
3861 }
3862 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003863 return true;
3864 }
Romain Guy0a637162009-05-29 14:43:54 -07003865
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003866 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003867 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003868 mActivePointerId = ev.getPointerId(0);
3869
3870 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3871 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003872 mFlingRunnable.endFling();
3873 if (mPositionScroller != null) {
3874 mPositionScroller.stop();
3875 }
3876 mTouchMode = TOUCH_MODE_OVERSCROLL;
3877 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003878 mMotionY = (int) ev.getY();
3879 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003880 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003881 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003882 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003883 final int x = (int) ev.getX();
3884 final int y = (int) ev.getY();
3885 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003886
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003887 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003888 if (mTouchMode == TOUCH_MODE_FLING) {
3889 // Stopped a fling. It is a scroll.
3890 createScrollingCache();
3891 mTouchMode = TOUCH_MODE_SCROLL;
3892 mMotionCorrection = 0;
3893 motionPosition = findMotionRow(y);
3894 mFlingRunnable.flywheelTouch();
3895 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3896 // User clicked on an actual view (and was not stopping a
3897 // fling). It might be a click or a scroll. Assume it is a
3898 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003899 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003900
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003901 // FIXME Debounce
3902 if (mPendingCheckForTap == null) {
3903 mPendingCheckForTap = new CheckForTap();
3904 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003905
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003906 mPendingCheckForTap.x = ev.getX();
3907 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003908 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003909 }
3910 }
3911
3912 if (motionPosition >= 0) {
3913 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003914 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003915 mMotionViewOriginalTop = v.getTop();
3916 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003917
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003918 mMotionX = x;
3919 mMotionY = y;
3920 mMotionPosition = motionPosition;
3921 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003922 }
3923
Alan Viveretteb339cc52013-08-12 13:29:15 -07003924 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003925 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003926 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003927 }
3928 }
3929
Adam Powell96d62af2014-05-02 10:04:38 -07003930 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003931 if (mHasPerformedLongPress) {
3932 // Consume all move events following a successful long press.
3933 return;
3934 }
3935
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003936 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3937 if (pointerIndex == -1) {
3938 pointerIndex = 0;
3939 mActivePointerId = ev.getPointerId(pointerIndex);
3940 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003941
3942 if (mDataChanged) {
3943 // Re-sync everything if data has been changed
3944 // since the scroll operation can query the adapter.
3945 layoutChildren();
3946 }
3947
Alan Viverette8fa327a2013-05-31 14:53:13 -07003948 final int y = (int) ev.getY(pointerIndex);
3949
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003950 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003951 case TOUCH_MODE_DOWN:
3952 case TOUCH_MODE_TAP:
3953 case TOUCH_MODE_DONE_WAITING:
3954 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003955 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003956 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003957 break;
3958 }
3959 // Otherwise, check containment within list bounds. If we're
3960 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003961 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003962 final float x = ev.getX(pointerIndex);
3963 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003964 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003965 if (motionView != null) {
3966 motionView.setPressed(false);
3967 }
3968 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3969 mPendingCheckForTap : mPendingCheckForLongPress);
3970 mTouchMode = TOUCH_MODE_DONE_WAITING;
3971 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003972 } else if (motionView != null) {
3973 // Still within bounds, update the hotspot.
3974 final float[] point = mTmpPoint;
3975 point[0] = x;
3976 point[1] = y;
3977 transformPointToViewLocal(point, motionView);
3978 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003979 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003980 break;
3981 case TOUCH_MODE_SCROLL:
3982 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003983 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003984 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003985 }
3986 }
3987
3988 private void onTouchUp(MotionEvent ev) {
3989 switch (mTouchMode) {
3990 case TOUCH_MODE_DOWN:
3991 case TOUCH_MODE_TAP:
3992 case TOUCH_MODE_DONE_WAITING:
3993 final int motionPosition = mMotionPosition;
3994 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003995 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003996 if (mTouchMode != TOUCH_MODE_DOWN) {
3997 child.setPressed(false);
3998 }
3999
Alan Viverette74ded292013-06-03 15:34:11 -07004000 final float x = ev.getX();
4001 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
Adam Powell0f552f42017-02-03 11:50:42 -08004002 if (inList && !child.hasExplicitFocusable()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004003 if (mPerformClick == null) {
4004 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004005 }
Alan Viverette74ded292013-06-03 15:34:11 -07004006
4007 final AbsListView.PerformClick performClick = mPerformClick;
4008 performClick.mClickMotionPosition = motionPosition;
4009 performClick.rememberWindowAttachCount();
4010
4011 mResurrectToPosition = motionPosition;
4012
4013 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4014 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4015 mPendingCheckForTap : mPendingCheckForLongPress);
4016 mLayoutMode = LAYOUT_NORMAL;
4017 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4018 mTouchMode = TOUCH_MODE_TAP;
4019 setSelectedPositionInt(mMotionPosition);
4020 layoutChildren();
4021 child.setPressed(true);
4022 positionSelector(mMotionPosition, child);
4023 setPressed(true);
4024 if (mSelector != null) {
4025 Drawable d = mSelector.getCurrent();
4026 if (d != null && d instanceof TransitionDrawable) {
4027 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004028 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004029 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004030 }
Alan Viverette74ded292013-06-03 15:34:11 -07004031 if (mTouchModeReset != null) {
4032 removeCallbacks(mTouchModeReset);
4033 }
4034 mTouchModeReset = new Runnable() {
4035 @Override
4036 public void run() {
4037 mTouchModeReset = null;
4038 mTouchMode = TOUCH_MODE_REST;
4039 child.setPressed(false);
4040 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004041 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004042 performClick.run();
4043 }
4044 }
4045 };
4046 postDelayed(mTouchModeReset,
4047 ViewConfiguration.getPressedStateDuration());
4048 } else {
4049 mTouchMode = TOUCH_MODE_REST;
4050 updateSelectorState();
4051 }
4052 return;
4053 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4054 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004055 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004056 }
4057 }
4058 mTouchMode = TOUCH_MODE_REST;
4059 updateSelectorState();
4060 break;
4061 case TOUCH_MODE_SCROLL:
4062 final int childCount = getChildCount();
4063 if (childCount > 0) {
4064 final int firstChildTop = getChildAt(0).getTop();
4065 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4066 final int contentTop = mListPadding.top;
4067 final int contentBottom = getHeight() - mListPadding.bottom;
4068 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4069 mFirstPosition + childCount < mItemCount &&
4070 lastChildBottom <= getHeight() - contentBottom) {
4071 mTouchMode = TOUCH_MODE_REST;
4072 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4073 } else {
4074 final VelocityTracker velocityTracker = mVelocityTracker;
4075 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4076
4077 final int initialVelocity = (int)
4078 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4079 // Fling if we have enough velocity and we aren't at a boundary.
4080 // Since we can potentially overfling more than we can overscroll, don't
4081 // allow the weird behavior where you can scroll to a boundary then
4082 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004083 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4084 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004085 !((mFirstPosition == 0 &&
4086 firstChildTop == contentTop - mOverscrollDistance) ||
4087 (mFirstPosition + childCount == mItemCount &&
4088 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004089 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4090 if (mFlingRunnable == null) {
4091 mFlingRunnable = new FlingRunnable();
4092 }
4093 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4094 mFlingRunnable.start(-initialVelocity);
4095 dispatchNestedFling(0, -initialVelocity, true);
4096 } else {
4097 mTouchMode = TOUCH_MODE_REST;
4098 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004099 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004100 } else {
4101 mTouchMode = TOUCH_MODE_REST;
4102 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4103 if (mFlingRunnable != null) {
4104 mFlingRunnable.endFling();
4105 }
4106 if (mPositionScroller != null) {
4107 mPositionScroller.stop();
4108 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004109 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004110 dispatchNestedFling(0, -initialVelocity, false);
4111 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004112 }
4113 }
4114 } else {
4115 mTouchMode = TOUCH_MODE_REST;
4116 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4117 }
4118 break;
4119
4120 case TOUCH_MODE_OVERSCROLL:
4121 if (mFlingRunnable == null) {
4122 mFlingRunnable = new FlingRunnable();
4123 }
4124 final VelocityTracker velocityTracker = mVelocityTracker;
4125 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4126 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4127
4128 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4129 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4130 mFlingRunnable.startOverfling(-initialVelocity);
4131 } else {
4132 mFlingRunnable.startSpringback();
4133 }
4134
4135 break;
4136 }
4137
4138 setPressed(false);
4139
4140 if (mEdgeGlowTop != null) {
4141 mEdgeGlowTop.onRelease();
4142 mEdgeGlowBottom.onRelease();
4143 }
4144
4145 // Need to redraw since we probably aren't drawing the selector anymore
4146 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004147 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004148 recycleVelocityTracker();
4149
4150 mActivePointerId = INVALID_POINTER;
4151
4152 if (PROFILE_SCROLLING) {
4153 if (mScrollProfilingStarted) {
4154 Debug.stopMethodTracing();
4155 mScrollProfilingStarted = false;
4156 }
4157 }
4158
4159 if (mScrollStrictSpan != null) {
4160 mScrollStrictSpan.finish();
4161 mScrollStrictSpan = null;
4162 }
4163 }
4164
4165 private void onTouchCancel() {
4166 switch (mTouchMode) {
4167 case TOUCH_MODE_OVERSCROLL:
4168 if (mFlingRunnable == null) {
4169 mFlingRunnable = new FlingRunnable();
4170 }
4171 mFlingRunnable.startSpringback();
4172 break;
4173
4174 case TOUCH_MODE_OVERFLING:
4175 // Do nothing - let it play out.
4176 break;
4177
4178 default:
4179 mTouchMode = TOUCH_MODE_REST;
4180 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004181 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004182 if (motionView != null) {
4183 motionView.setPressed(false);
4184 }
4185 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004186 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004187 recycleVelocityTracker();
4188 }
4189
4190 if (mEdgeGlowTop != null) {
4191 mEdgeGlowTop.onRelease();
4192 mEdgeGlowBottom.onRelease();
4193 }
4194 mActivePointerId = INVALID_POINTER;
4195 }
4196
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004197 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004198 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4199 if (mScrollY != scrollY) {
4200 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4201 mScrollY = scrollY;
4202 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004203
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004204 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004205 }
Adam Powell637d3372010-08-25 14:37:03 -07004206 }
4207
4208 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004209 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004210 switch (event.getAction()) {
4211 case MotionEvent.ACTION_SCROLL:
4212 final float axisValue;
4213 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4214 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4215 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4216 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4217 } else {
4218 axisValue = 0;
4219 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004220
Aaron Whytef8306522017-03-22 16:30:58 -07004221 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -04004222 if (delta != 0) {
4223 if (!trackMotionScroll(delta, delta)) {
4224 return true;
4225 }
4226 }
4227 break;
4228 case MotionEvent.ACTION_BUTTON_PRESS:
4229 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004230 int actionButton = event.getActionButton();
4231 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4232 || actionButton == MotionEvent.BUTTON_SECONDARY)
4233 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4234 if (performStylusButtonPressAction(event)) {
4235 removeCallbacks(mPendingCheckForLongPress);
4236 removeCallbacks(mPendingCheckForTap);
4237 }
4238 }
Ned Burns20ad0732016-08-18 14:22:57 -04004239 }
4240 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004241 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004242
Jeff Brown33bbfd22011-02-24 20:55:35 -08004243 return super.onGenericMotionEvent(event);
4244 }
4245
Adam Powell4884c642014-08-07 13:52:53 -07004246 /**
4247 * Initiate a fling with the given velocity.
4248 *
4249 * <p>Applications can use this method to manually initiate a fling as if the user
4250 * initiated it via touch interaction.</p>
4251 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004252 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4253 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004254 */
4255 public void fling(int velocityY) {
4256 if (mFlingRunnable == null) {
4257 mFlingRunnable = new FlingRunnable();
4258 }
4259 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004260 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004261 }
4262
Jeff Brown33bbfd22011-02-24 20:55:35 -08004263 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004264 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4265 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4266 }
4267
4268 @Override
4269 public void onNestedScrollAccepted(View child, View target, int axes) {
4270 super.onNestedScrollAccepted(child, target, axes);
4271 startNestedScroll(SCROLL_AXIS_VERTICAL);
4272 }
4273
4274 @Override
4275 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4276 int dxUnconsumed, int dyUnconsumed) {
4277 final int motionIndex = getChildCount() / 2;
4278 final View motionView = getChildAt(motionIndex);
4279 final int oldTop = motionView != null ? motionView.getTop() : 0;
4280 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4281 int myUnconsumed = dyUnconsumed;
4282 int myConsumed = 0;
4283 if (motionView != null) {
4284 myConsumed = motionView.getTop() - oldTop;
4285 myUnconsumed -= myConsumed;
4286 }
4287 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4288 }
4289 }
4290
4291 @Override
4292 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4293 final int childCount = getChildCount();
4294 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4295 Math.abs(velocityY) > mMinimumVelocity) {
4296 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4297 if (mFlingRunnable == null) {
4298 mFlingRunnable = new FlingRunnable();
4299 }
Adam Powell9413b242014-08-06 17:34:24 -07004300 if (!dispatchNestedPreFling(0, velocityY)) {
4301 mFlingRunnable.start((int) velocityY);
4302 }
Adam Powell96d62af2014-05-02 10:04:38 -07004303 return true;
4304 }
4305 return dispatchNestedFling(velocityX, velocityY, consumed);
4306 }
4307
4308 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004309 public void draw(Canvas canvas) {
4310 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004311 if (mEdgeGlowTop != null) {
4312 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004313 final boolean clipToPadding = getClipToPadding();
4314 final int width;
4315 final int height;
4316 final int translateX;
4317 final int translateY;
4318
4319 if (clipToPadding) {
4320 width = getWidth() - mPaddingLeft - mPaddingRight;
4321 height = getHeight() - mPaddingTop - mPaddingBottom;
4322 translateX = mPaddingLeft;
4323 translateY = mPaddingTop;
4324 } else {
4325 width = getWidth();
4326 height = getHeight();
4327 translateX = 0;
4328 translateY = 0;
4329 }
Adam Powell637d3372010-08-25 14:37:03 -07004330 if (!mEdgeGlowTop.isFinished()) {
4331 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004332 canvas.clipRect(translateX, translateY,
4333 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4334 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4335 canvas.translate(translateX, edgeY);
4336 mEdgeGlowTop.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004337 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004338 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004339 }
4340 canvas.restoreToCount(restoreCount);
4341 }
4342 if (!mEdgeGlowBottom.isFinished()) {
4343 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004344 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4345 translateX + width, translateY + height);
4346 final int edgeX = -width + translateX;
4347 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4348 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004349 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004350 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08004351 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004352 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004353 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004354 }
4355 canvas.restoreToCount(restoreCount);
4356 }
4357 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004358 }
4359
Michael Jurka13451a42011-08-22 15:54:21 -07004360 private void initOrResetVelocityTracker() {
4361 if (mVelocityTracker == null) {
4362 mVelocityTracker = VelocityTracker.obtain();
4363 } else {
4364 mVelocityTracker.clear();
4365 }
4366 }
4367
4368 private void initVelocityTrackerIfNotExists() {
4369 if (mVelocityTracker == null) {
4370 mVelocityTracker = VelocityTracker.obtain();
4371 }
4372 }
4373
4374 private void recycleVelocityTracker() {
4375 if (mVelocityTracker != null) {
4376 mVelocityTracker.recycle();
4377 mVelocityTracker = null;
4378 }
4379 }
4380
4381 @Override
4382 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4383 if (disallowIntercept) {
4384 recycleVelocityTracker();
4385 }
4386 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4387 }
4388
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004389 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004390 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004391 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004392 return true;
4393 }
4394
4395 return super.onInterceptHoverEvent(event);
4396 }
4397
4398 @Override
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08004399 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4400 if (mFastScroll != null) {
4401 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4402 if (pointerIcon != null) {
4403 return pointerIcon;
4404 }
4405 }
4406 return super.onResolvePointerIcon(event, pointerIndex);
4407 }
4408
4409 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004410 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004411 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004412 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004413
Adam Powell1fa179ef2012-04-12 15:01:40 -07004414 if (mPositionScroller != null) {
4415 mPositionScroller.stop();
4416 }
4417
Alan Viverette462c2172014-02-24 12:24:11 -08004418 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004419 // Something isn't right.
4420 // Since we rely on being attached to get data set change notifications,
4421 // don't risk doing anything where we might try to resync and find things
4422 // in a bogus state.
4423 return false;
4424 }
4425
Alan Viverette8636ace2013-10-31 15:41:31 -07004426 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004427 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004428 }
Romain Guy0a637162009-05-29 14:43:54 -07004429
Adam Powell744beff2014-09-22 09:47:48 -07004430 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004431 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004432 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004433 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4434 mMotionCorrection = 0;
4435 return true;
4436 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004437
Adam Powell4cd47702010-02-25 11:21:14 -08004438 final int x = (int) ev.getX();
4439 final int y = (int) ev.getY();
4440 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004441
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004442 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004443 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004444 // User clicked on an actual view (and was not stopping a fling).
4445 // Remember where the motion event started
4446 v = getChildAt(motionPosition - mFirstPosition);
4447 mMotionViewOriginalTop = v.getTop();
4448 mMotionX = x;
4449 mMotionY = y;
4450 mMotionPosition = motionPosition;
4451 mTouchMode = TOUCH_MODE_DOWN;
4452 clearScrollingCache();
4453 }
4454 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004455 initOrResetVelocityTracker();
4456 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004457 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004458 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004459 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004460 return true;
4461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004462 break;
4463 }
4464
4465 case MotionEvent.ACTION_MOVE: {
4466 switch (mTouchMode) {
4467 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004468 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4469 if (pointerIndex == -1) {
4470 pointerIndex = 0;
4471 mActivePointerId = ev.getPointerId(pointerIndex);
4472 }
Adam Powell4cd47702010-02-25 11:21:14 -08004473 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004474 initVelocityTrackerIfNotExists();
4475 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004476 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004477 return true;
4478 }
4479 break;
4480 }
4481 break;
4482 }
4483
Michael Jurka13451a42011-08-22 15:54:21 -07004484 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004485 case MotionEvent.ACTION_UP: {
4486 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004487 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004488 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004489 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004490 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004491 break;
4492 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004493
Adam Powell4cd47702010-02-25 11:21:14 -08004494 case MotionEvent.ACTION_POINTER_UP: {
4495 onSecondaryPointerUp(ev);
4496 break;
4497 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004498 }
4499
4500 return false;
4501 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004502
Adam Powell4cd47702010-02-25 11:21:14 -08004503 private void onSecondaryPointerUp(MotionEvent ev) {
4504 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4505 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4506 final int pointerId = ev.getPointerId(pointerIndex);
4507 if (pointerId == mActivePointerId) {
4508 // This was our active pointer going up. Choose a new
4509 // active pointer and adjust accordingly.
4510 // TODO: Make this decision more intelligent.
4511 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4512 mMotionX = (int) ev.getX(newPointerIndex);
4513 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004514 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004515 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004516 }
4517 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004518
4519 /**
4520 * {@inheritDoc}
4521 */
4522 @Override
4523 public void addTouchables(ArrayList<View> views) {
4524 final int count = getChildCount();
4525 final int firstPosition = mFirstPosition;
4526 final ListAdapter adapter = mAdapter;
4527
4528 if (adapter == null) {
4529 return;
4530 }
4531
4532 for (int i = 0; i < count; i++) {
4533 final View child = getChildAt(i);
4534 if (adapter.isEnabled(firstPosition + i)) {
4535 views.add(child);
4536 }
4537 child.addTouchables(views);
4538 }
4539 }
4540
4541 /**
4542 * Fires an "on scroll state changed" event to the registered
4543 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4544 * is fired only if the specified state is different from the previously known state.
4545 *
4546 * @param newState The new scroll state.
4547 */
4548 void reportScrollStateChange(int newState) {
4549 if (newState != mLastScrollState) {
4550 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004551 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004552 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004553 }
4554 }
4555 }
4556
4557 /**
4558 * Responsible for fling behavior. Use {@link #start(int)} to
4559 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4560 * A FlingRunnable will keep re-posting itself until the fling is done.
4561 *
4562 */
4563 private class FlingRunnable implements Runnable {
4564 /**
4565 * Tracks the decay of a fling scroll
4566 */
Adam Powell637d3372010-08-25 14:37:03 -07004567 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004568
4569 /**
4570 * Y value reported by mScroller on the previous fling
4571 */
4572 private int mLastFlingY;
4573
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004574 /**
4575 * If true, {@link #endFling()} will not report scroll state change to
4576 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4577 */
4578 private boolean mSuppressIdleStateChangeCall;
4579
Gilles Debunned348bb42010-11-15 12:19:35 -08004580 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004581 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004582 public void run() {
4583 final int activeId = mActivePointerId;
4584 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004585 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004586 if (vt == null || activeId == INVALID_POINTER) {
4587 return;
4588 }
4589
4590 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4591 final float yvel = -vt.getYVelocity(activeId);
4592
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004593 if (Math.abs(yvel) >= mMinimumVelocity
4594 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004595 // Keep the fling alive a little longer
4596 postDelayed(this, FLYWHEEL_TIMEOUT);
4597 } else {
4598 endFling();
4599 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004600 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004601 }
4602 }
4603 };
4604
4605 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4606
Adam Powell79ac3392010-01-28 21:22:20 -08004607 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004608 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004609 }
4610
Adam Powell79ac3392010-01-28 21:22:20 -08004611 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004612 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4613 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004614 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004615 mScroller.fling(0, initialY, 0, initialVelocity,
4616 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4617 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004618 mSuppressIdleStateChangeCall = false;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004619 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004620
4621 if (PROFILE_FLINGING) {
4622 if (!mFlingProfilingStarted) {
4623 Debug.startMethodTracing("AbsListViewFling");
4624 mFlingProfilingStarted = true;
4625 }
4626 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004627
4628 if (mFlingStrictSpan == null) {
4629 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4630 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004631 }
Adam Powell45803472010-01-25 15:10:44 -08004632
Adam Powell637d3372010-08-25 14:37:03 -07004633 void startSpringback() {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004634 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004635 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4636 mTouchMode = TOUCH_MODE_OVERFLING;
4637 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004638 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004639 } else {
4640 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004641 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004642 }
4643 }
4644
4645 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004646 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004647 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4648 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004649 mTouchMode = TOUCH_MODE_OVERFLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004650 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004651 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004652 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004653 }
4654
4655 void edgeReached(int delta) {
4656 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4657 final int overscrollMode = getOverScrollMode();
4658 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4659 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4660 mTouchMode = TOUCH_MODE_OVERFLING;
4661 final int vel = (int) mScroller.getCurrVelocity();
4662 if (delta > 0) {
4663 mEdgeGlowTop.onAbsorb(vel);
4664 } else {
4665 mEdgeGlowBottom.onAbsorb(vel);
4666 }
Adam Powell40322522011-01-12 21:58:20 -08004667 } else {
4668 mTouchMode = TOUCH_MODE_REST;
4669 if (mPositionScroller != null) {
4670 mPositionScroller.stop();
4671 }
Adam Powell637d3372010-08-25 14:37:03 -07004672 }
4673 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004674 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004675 }
4676
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004677 void startScroll(int distance, int duration, boolean linear,
4678 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004679 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4680 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004681 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004682 mScroller.startScroll(0, initialY, 0, distance, duration);
4683 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004684 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004685 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004686 }
4687
Gilles Debunned348bb42010-11-15 12:19:35 -08004688 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004689 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004690
Adam Powell79ac3392010-01-28 21:22:20 -08004691 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004692 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004693
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004694 if (!mSuppressIdleStateChangeCall) {
4695 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4696 }
Romain Guy21317d12010-10-12 13:32:31 -07004697 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004698 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004699
4700 if (mFlingStrictSpan != null) {
4701 mFlingStrictSpan.finish();
4702 mFlingStrictSpan = null;
4703 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004704 }
4705
4706 void flywheelTouch() {
4707 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004708 }
4709
Alan Viverette8fa327a2013-05-31 14:53:13 -07004710 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004711 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004712 switch (mTouchMode) {
4713 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004714 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004715 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004716
Gilles Debunned348bb42010-11-15 12:19:35 -08004717 case TOUCH_MODE_SCROLL:
4718 if (mScroller.isFinished()) {
4719 return;
4720 }
4721 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004722 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004723 if (mDataChanged) {
4724 layoutChildren();
4725 }
4726
Adam Powell79ac3392010-01-28 21:22:20 -08004727 if (mItemCount == 0 || getChildCount() == 0) {
4728 endFling();
4729 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004730 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004731
Adam Powell637d3372010-08-25 14:37:03 -07004732 final OverScroller scroller = mScroller;
4733 boolean more = scroller.computeScrollOffset();
4734 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004735
Adam Powell637d3372010-08-25 14:37:03 -07004736 // Flip sign to convert finger direction to list items direction
4737 // (e.g. finger moving down means list is moving towards the top)
4738 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004739
Adam Powell637d3372010-08-25 14:37:03 -07004740 // Pretend that each frame of a fling scroll is a touch scroll
4741 if (delta > 0) {
4742 // List is moving towards the top. Use first view as mMotionPosition
4743 mMotionPosition = mFirstPosition;
4744 final View firstView = getChildAt(0);
4745 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004746
Adam Powell637d3372010-08-25 14:37:03 -07004747 // Don't fling more than 1 screen
4748 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4749 } else {
4750 // List is moving towards the bottom. Use last view as mMotionPosition
4751 int offsetToLast = getChildCount() - 1;
4752 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004753
Adam Powell637d3372010-08-25 14:37:03 -07004754 final View lastView = getChildAt(offsetToLast);
4755 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004756
Adam Powell637d3372010-08-25 14:37:03 -07004757 // Don't fling more than 1 screen
4758 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4759 }
Adam Powell79ac3392010-01-28 21:22:20 -08004760
Adam Powell637d3372010-08-25 14:37:03 -07004761 // Check to see if we have bumped into the scroll limit
4762 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4763 int oldTop = 0;
4764 if (motionView != null) {
4765 oldTop = motionView.getTop();
4766 }
Adam Powell9d32d242010-03-29 16:02:07 -07004767
Adam Powell637d3372010-08-25 14:37:03 -07004768 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004769 final boolean atEdge = trackMotionScroll(delta, delta);
4770 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004771 if (atEnd) {
4772 if (motionView != null) {
4773 // Tweak the scroll for how far we overshot
4774 int overshoot = -(delta - (motionView.getTop() - oldTop));
4775 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4776 0, mOverflingDistance, false);
4777 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004778 if (more) {
4779 edgeReached(delta);
4780 }
Adam Powell637d3372010-08-25 14:37:03 -07004781 break;
4782 }
4783
4784 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004785 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004786 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004787 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004788 } else {
4789 endFling();
4790
4791 if (PROFILE_FLINGING) {
4792 if (mFlingProfilingStarted) {
4793 Debug.stopMethodTracing();
4794 mFlingProfilingStarted = false;
4795 }
4796
4797 if (mFlingStrictSpan != null) {
4798 mFlingStrictSpan.finish();
4799 mFlingStrictSpan = null;
4800 }
Adam Powell79ac3392010-01-28 21:22:20 -08004801 }
4802 }
Adam Powell637d3372010-08-25 14:37:03 -07004803 break;
4804 }
4805
4806 case TOUCH_MODE_OVERFLING: {
4807 final OverScroller scroller = mScroller;
4808 if (scroller.computeScrollOffset()) {
4809 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004810 final int currY = scroller.getCurrY();
4811 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004812 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4813 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004814 final boolean crossDown = scrollY <= 0 && currY > 0;
4815 final boolean crossUp = scrollY >= 0 && currY < 0;
4816 if (crossDown || crossUp) {
4817 int velocity = (int) scroller.getCurrVelocity();
4818 if (crossUp) velocity = -velocity;
4819
4820 // Don't flywheel from this; we're just continuing things.
4821 scroller.abortAnimation();
4822 start(velocity);
4823 } else {
4824 startSpringback();
4825 }
Adam Powell637d3372010-08-25 14:37:03 -07004826 } else {
4827 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004828 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004829 }
4830 } else {
4831 endFling();
4832 }
4833 break;
4834 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004835 }
4836 }
4837 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004838
Adam Powell45803472010-01-25 15:10:44 -08004839 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004840 * The amount of friction applied to flings. The default value
4841 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004842 */
4843 public void setFriction(float friction) {
4844 if (mFlingRunnable == null) {
4845 mFlingRunnable = new FlingRunnable();
4846 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004847 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004848 }
Romain Guy21317d12010-10-12 13:32:31 -07004849
4850 /**
4851 * Sets a scale factor for the fling velocity. The initial scale
4852 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004853 *
Romain Guy21317d12010-10-12 13:32:31 -07004854 * @param scale The scale factor to multiply the velocity by.
4855 */
4856 public void setVelocityScale(float scale) {
4857 mVelocityScale = scale;
4858 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004859
Romain Guy4bede9e2010-10-11 19:36:59 -07004860 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004861 * Override this for better control over position scrolling.
4862 */
4863 AbsPositionScroller createPositionScroller() {
4864 return new PositionScroller();
4865 }
4866
4867 /**
Adam Powell45803472010-01-25 15:10:44 -08004868 * Smoothly scroll to the specified adapter position. The view will
4869 * scroll such that the indicated position is displayed.
4870 * @param position Scroll to this adapter position.
4871 */
4872 public void smoothScrollToPosition(int position) {
4873 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004874 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004875 }
4876 mPositionScroller.start(position);
4877 }
Erik322171b2010-10-13 15:46:00 -07004878
4879 /**
4880 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004881 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004882 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4883 * the first or last item beyond the boundaries of the list) it will get as close
4884 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4885 *
4886 * @param position Position to scroll to
4887 * @param offset Desired distance in pixels of <code>position</code> from the top
4888 * of the view when scrolling is finished
4889 * @param duration Number of milliseconds to use for the scroll
4890 */
4891 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4892 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004893 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004894 }
4895 mPositionScroller.startWithOffset(position, offset, duration);
4896 }
4897
Adam Powell45803472010-01-25 15:10:44 -08004898 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004899 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004900 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004901 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4902 * the first or last item beyond the boundaries of the list) it will get as close
4903 * as possible.
4904 *
4905 * @param position Position to scroll to
4906 * @param offset Desired distance in pixels of <code>position</code> from the top
4907 * of the view when scrolling is finished
4908 */
4909 public void smoothScrollToPositionFromTop(int position, int offset) {
4910 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004911 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004912 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004913 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004914 }
4915
4916 /**
Adam Powell45803472010-01-25 15:10:44 -08004917 * Smoothly scroll to the specified adapter position. The view will
4918 * scroll such that the indicated position is displayed, but it will
4919 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004920 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004921 *
Adam Powell45803472010-01-25 15:10:44 -08004922 * @param position Scroll to this adapter position.
4923 * @param boundPosition Do not scroll if it would move this adapter
4924 * position out of view.
4925 */
4926 public void smoothScrollToPosition(int position, int boundPosition) {
4927 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004928 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004929 }
4930 mPositionScroller.start(position, boundPosition);
4931 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004932
Adam Powell45803472010-01-25 15:10:44 -08004933 /**
4934 * Smoothly scroll by distance pixels over duration milliseconds.
4935 * @param distance Distance to scroll in pixels.
4936 * @param duration Duration of the scroll animation in milliseconds.
4937 */
4938 public void smoothScrollBy(int distance, int duration) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004939 smoothScrollBy(distance, duration, false, false);
Adam Powell0b8acd82012-04-25 20:29:23 -07004940 }
4941
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004942 void smoothScrollBy(int distance, int duration, boolean linear,
4943 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004944 if (mFlingRunnable == null) {
4945 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004946 }
Adam Powell40322522011-01-12 21:58:20 -08004947
Marc Blank299acb52010-10-21 11:03:53 -07004948 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004949 final int firstPos = mFirstPosition;
4950 final int childCount = getChildCount();
4951 final int lastPos = firstPos + childCount;
4952 final int topLimit = getPaddingTop();
4953 final int bottomLimit = getHeight() - getPaddingBottom();
4954
Adam Powell79303752011-01-13 22:06:49 -08004955 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004956 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004957 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004958 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4959 mFlingRunnable.endFling();
4960 if (mPositionScroller != null) {
4961 mPositionScroller.stop();
4962 }
4963 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004964 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004965 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
Marc Blank299acb52010-10-21 11:03:53 -07004966 }
Adam Powell45803472010-01-25 15:10:44 -08004967 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004968
Winson Chung499cb9f2010-07-16 11:18:17 -07004969 /**
4970 * Allows RemoteViews to scroll relatively to a position.
4971 */
4972 void smoothScrollByOffset(int position) {
4973 int index = -1;
4974 if (position < 0) {
4975 index = getFirstVisiblePosition();
4976 } else if (position > 0) {
4977 index = getLastVisiblePosition();
4978 }
4979
4980 if (index > -1) {
4981 View child = getChildAt(index - getFirstVisiblePosition());
4982 if (child != null) {
4983 Rect visibleRect = new Rect();
4984 if (child.getGlobalVisibleRect(visibleRect)) {
4985 // the child is partially visible
4986 int childRectArea = child.getWidth() * child.getHeight();
4987 int visibleRectArea = visibleRect.width() * visibleRect.height();
4988 float visibleArea = (visibleRectArea / (float) childRectArea);
4989 final float visibleThreshold = 0.75f;
4990 if ((position < 0) && (visibleArea < visibleThreshold)) {
4991 // the top index is not perceivably visible so offset
4992 // to account for showing that top index as well
4993 ++index;
4994 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4995 // the bottom index is not perceivably visible so offset
4996 // to account for showing that bottom index as well
4997 --index;
4998 }
4999 }
5000 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5001 }
5002 }
5003 }
5004
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005005 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005006 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005007 setChildrenDrawnWithCacheEnabled(true);
5008 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08005009 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005010 }
5011 }
5012
5013 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005014 if (!isHardwareAccelerated()) {
5015 if (mClearScrollingCache == null) {
5016 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07005017 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07005018 public void run() {
5019 if (mCachingStarted) {
5020 mCachingStarted = mCachingActive = false;
5021 setChildrenDrawnWithCacheEnabled(false);
5022 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5023 setChildrenDrawingCacheEnabled(false);
5024 }
5025 if (!isAlwaysDrawnWithCacheEnabled()) {
5026 invalidate();
5027 }
Romain Guy6dfed242009-05-11 18:25:05 -07005028 }
5029 }
Romain Guy9d849a22012-03-14 16:41:42 -07005030 };
5031 }
5032 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005033 }
5034 }
5035
5036 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005037 * Scrolls the list items within the view by a specified number of pixels.
5038 *
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005039 * <p>The actual amount of scroll is capped by the list content viewport height
5040 * which is the list height minus top and bottom paddings minus one pixel.</p>
5041 *
Alan Viverette2f3317a2013-08-06 18:19:48 -07005042 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005043 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005044 */
Alan Viveretteba299062013-09-03 16:01:51 -07005045 public void scrollListBy(int y) {
5046 trackMotionScroll(-y, -y);
5047 }
5048
5049 /**
5050 * Check if the items in the list can be scrolled in a certain direction.
5051 *
5052 * @param direction Negative to check scrolling up, positive to check
5053 * scrolling down.
5054 * @return true if the list can be scrolled in the specified direction,
5055 * false otherwise.
5056 * @see #scrollListBy(int)
5057 */
5058 public boolean canScrollList(int direction) {
5059 final int childCount = getChildCount();
5060 if (childCount == 0) {
5061 return false;
5062 }
5063
5064 final int firstPosition = mFirstPosition;
5065 final Rect listPadding = mListPadding;
5066 if (direction > 0) {
5067 final int lastBottom = getChildAt(childCount - 1).getBottom();
5068 final int lastPosition = firstPosition + childCount;
5069 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5070 } else {
5071 final int firstTop = getChildAt(0).getTop();
5072 return firstPosition > 0 || firstTop < listPadding.top;
5073 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005074 }
5075
5076 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005077 * Track a motion scroll
5078 *
5079 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5080 * began. Positive numbers mean the user's finger is moving down the screen.
5081 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005082 * @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 -08005083 */
Adam Powell45803472010-01-25 15:10:44 -08005084 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005085 final int childCount = getChildCount();
5086 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005087 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005088 }
5089
5090 final int firstTop = getChildAt(0).getTop();
5091 final int lastBottom = getChildAt(childCount - 1).getBottom();
5092
5093 final Rect listPadding = mListPadding;
5094
Adam Powellbdccc2d2010-12-14 17:34:27 -08005095 // "effective padding" In this case is the amount of padding that affects
5096 // how much space should not be filled by items. If we don't clip to padding
5097 // there is no effective padding.
5098 int effectivePaddingTop = 0;
5099 int effectivePaddingBottom = 0;
5100 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5101 effectivePaddingTop = listPadding.top;
5102 effectivePaddingBottom = listPadding.bottom;
5103 }
5104
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005105 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005106 final int spaceAbove = effectivePaddingTop - firstTop;
5107 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005108 final int spaceBelow = lastBottom - end;
5109
5110 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5111 if (deltaY < 0) {
5112 deltaY = Math.max(-(height - 1), deltaY);
5113 } else {
5114 deltaY = Math.min(height - 1, deltaY);
5115 }
5116
5117 if (incrementalDeltaY < 0) {
5118 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5119 } else {
5120 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5121 }
5122
Adam Powell45803472010-01-25 15:10:44 -08005123 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005124
Adam Powell637d3372010-08-25 14:37:03 -07005125 // Update our guesses for where the first and last views are
5126 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005127 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005128 } else {
5129 mFirstPositionDistanceGuess += incrementalDeltaY;
5130 }
5131 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005132 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005133 } else {
5134 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005135 }
Adam Powell45803472010-01-25 15:10:44 -08005136
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005137 final boolean cannotScrollDown = (firstPosition == 0 &&
5138 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5139 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5140 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005141
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005142 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005143 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005144 }
5145
5146 final boolean down = incrementalDeltaY < 0;
5147
Adam Powell029cfbd2010-03-08 19:03:54 -08005148 final boolean inTouchMode = isInTouchMode();
5149 if (inTouchMode) {
5150 hideSelector();
5151 }
Adam Powell45803472010-01-25 15:10:44 -08005152
5153 final int headerViewsCount = getHeaderViewsCount();
5154 final int footerViewsStart = mItemCount - getFooterViewsCount();
5155
5156 int start = 0;
5157 int count = 0;
5158
5159 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005160 int top = -incrementalDeltaY;
5161 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5162 top += listPadding.top;
5163 }
Adam Powell45803472010-01-25 15:10:44 -08005164 for (int i = 0; i < childCount; i++) {
5165 final View child = getChildAt(i);
5166 if (child.getBottom() >= top) {
5167 break;
5168 } else {
5169 count++;
5170 int position = firstPosition + i;
5171 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005172 // The view will be rebound to new data, clear any
5173 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005174 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005175 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005176 }
5177 }
5178 }
5179 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005180 int bottom = getHeight() - incrementalDeltaY;
5181 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5182 bottom -= listPadding.bottom;
5183 }
Adam Powell45803472010-01-25 15:10:44 -08005184 for (int i = childCount - 1; i >= 0; i--) {
5185 final View child = getChildAt(i);
5186 if (child.getTop() <= bottom) {
5187 break;
5188 } else {
5189 start = i;
5190 count++;
5191 int position = firstPosition + i;
5192 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005193 // The view will be rebound to new data, clear any
5194 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005195 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005196 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005197 }
5198 }
5199 }
5200 }
5201
5202 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5203
5204 mBlockLayoutRequests = true;
5205
5206 if (count > 0) {
5207 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005208 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005209 }
Adam Powell539ee872012-02-03 19:00:49 -08005210
Romain Guy9d849a22012-03-14 16:41:42 -07005211 // invalidate before moving the children to avoid unnecessary invalidate
5212 // calls to bubble up from the children all the way to the top
5213 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005214 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005215 }
5216
Adam Powell45803472010-01-25 15:10:44 -08005217 offsetChildrenTopAndBottom(incrementalDeltaY);
5218
5219 if (down) {
5220 mFirstPosition += count;
5221 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005222
Adam Powell45803472010-01-25 15:10:44 -08005223 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5224 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5225 fillGap(down);
5226 }
5227
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005228 mRecycler.fullyDetachScrapViews();
Adam Powell029cfbd2010-03-08 19:03:54 -08005229 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005230 final int childIndex = mSelectedPosition - mFirstPosition;
5231 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005232 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08005233 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005234 } else if (mSelectorPosition != INVALID_POSITION) {
5235 final int childIndex = mSelectorPosition - mFirstPosition;
5236 if (childIndex >= 0 && childIndex < getChildCount()) {
5237 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5238 }
5239 } else {
5240 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005241 }
5242
Adam Powell45803472010-01-25 15:10:44 -08005243 mBlockLayoutRequests = false;
5244
5245 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005246
Adam Powell45803472010-01-25 15:10:44 -08005247 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005248 }
5249
5250 /**
5251 * Returns the number of header views in the list. Header views are special views
5252 * at the top of the list that should not be recycled during a layout.
5253 *
5254 * @return The number of header views, 0 in the default implementation.
5255 */
5256 int getHeaderViewsCount() {
5257 return 0;
5258 }
5259
5260 /**
5261 * Returns the number of footer views in the list. Footer views are special views
5262 * at the bottom of the list that should not be recycled during a layout.
5263 *
5264 * @return The number of footer views, 0 in the default implementation.
5265 */
5266 int getFooterViewsCount() {
5267 return 0;
5268 }
5269
5270 /**
5271 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5272 * remain on screen are shifted and the other ones are discarded. The role of this
5273 * method is to fill the gap thus created by performing a partial layout in the
5274 * empty space.
5275 *
5276 * @param down true if the scroll is going down, false if it is going up
5277 */
5278 abstract void fillGap(boolean down);
5279
5280 void hideSelector() {
5281 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005282 if (mLayoutMode != LAYOUT_SPECIFIC) {
5283 mResurrectToPosition = mSelectedPosition;
5284 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005285 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5286 mResurrectToPosition = mNextSelectedPosition;
5287 }
5288 setSelectedPositionInt(INVALID_POSITION);
5289 setNextSelectedPositionInt(INVALID_POSITION);
5290 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005291 }
5292 }
5293
5294 /**
5295 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5296 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5297 * of items available in the adapter
5298 */
5299 int reconcileSelectedPosition() {
5300 int position = mSelectedPosition;
5301 if (position < 0) {
5302 position = mResurrectToPosition;
5303 }
5304 position = Math.max(0, position);
5305 position = Math.min(position, mItemCount - 1);
5306 return position;
5307 }
5308
5309 /**
5310 * Find the row closest to y. This row will be used as the motion row when scrolling
5311 *
5312 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005313 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005314 */
5315 abstract int findMotionRow(int y);
5316
5317 /**
Adam Powell637d3372010-08-25 14:37:03 -07005318 * Find the row closest to y. This row will be used as the motion row when scrolling.
5319 *
5320 * @param y Where the user touched
5321 * @return The position of the first (or only) item in the row closest to y
5322 */
5323 int findClosestMotionRow(int y) {
5324 final int childCount = getChildCount();
5325 if (childCount == 0) {
5326 return INVALID_POSITION;
5327 }
5328
5329 final int motionRow = findMotionRow(y);
5330 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5331 }
5332
5333 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005334 * Causes all the views to be rebuilt and redrawn.
5335 */
5336 public void invalidateViews() {
5337 mDataChanged = true;
5338 rememberSyncState();
5339 requestLayout();
5340 invalidate();
5341 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005342
Jeff Brown4e6319b2010-12-13 10:36:51 -08005343 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005344 * If there is a selection returns false.
5345 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005346 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005347 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005348 if (mSelectedPosition < 0 && resurrectSelection()) {
5349 updateSelectorState();
5350 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005351 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005352 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005353 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005354
5355 /**
5356 * Makes the item at the supplied position selected.
5357 *
5358 * @param position the position of the new selection
5359 */
5360 abstract void setSelectionInt(int position);
5361
5362 /**
5363 * Attempt to bring the selection back if the user is switching from touch
5364 * to trackball mode
5365 * @return Whether selection was set to something.
5366 */
5367 boolean resurrectSelection() {
5368 final int childCount = getChildCount();
5369
5370 if (childCount <= 0) {
5371 return false;
5372 }
5373
5374 int selectedTop = 0;
5375 int selectedPos;
5376 int childrenTop = mListPadding.top;
5377 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5378 final int firstPosition = mFirstPosition;
5379 final int toPosition = mResurrectToPosition;
5380 boolean down = true;
5381
5382 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5383 selectedPos = toPosition;
5384
5385 final View selected = getChildAt(selectedPos - mFirstPosition);
5386 selectedTop = selected.getTop();
5387 int selectedBottom = selected.getBottom();
5388
5389 // We are scrolled, don't get in the fade
5390 if (selectedTop < childrenTop) {
5391 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5392 } else if (selectedBottom > childrenBottom) {
5393 selectedTop = childrenBottom - selected.getMeasuredHeight()
5394 - getVerticalFadingEdgeLength();
5395 }
5396 } else {
5397 if (toPosition < firstPosition) {
5398 // Default to selecting whatever is first
5399 selectedPos = firstPosition;
5400 for (int i = 0; i < childCount; i++) {
5401 final View v = getChildAt(i);
5402 final int top = v.getTop();
5403
5404 if (i == 0) {
5405 // Remember the position of the first item
5406 selectedTop = top;
5407 // See if we are scrolled at all
5408 if (firstPosition > 0 || top < childrenTop) {
5409 // If we are scrolled, don't select anything that is
5410 // in the fade region
5411 childrenTop += getVerticalFadingEdgeLength();
5412 }
5413 }
5414 if (top >= childrenTop) {
5415 // Found a view whose top is fully visisble
5416 selectedPos = firstPosition + i;
5417 selectedTop = top;
5418 break;
5419 }
5420 }
5421 } else {
5422 final int itemCount = mItemCount;
5423 down = false;
5424 selectedPos = firstPosition + childCount - 1;
5425
5426 for (int i = childCount - 1; i >= 0; i--) {
5427 final View v = getChildAt(i);
5428 final int top = v.getTop();
5429 final int bottom = v.getBottom();
5430
5431 if (i == childCount - 1) {
5432 selectedTop = top;
5433 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5434 childrenBottom -= getVerticalFadingEdgeLength();
5435 }
5436 }
5437
5438 if (bottom <= childrenBottom) {
5439 selectedPos = firstPosition + i;
5440 selectedTop = top;
5441 break;
5442 }
5443 }
5444 }
5445 }
5446
5447 mResurrectToPosition = INVALID_POSITION;
5448 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005449 if (mPositionScroller != null) {
5450 mPositionScroller.stop();
5451 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005452 mTouchMode = TOUCH_MODE_REST;
5453 clearScrollingCache();
5454 mSpecificTop = selectedTop;
5455 selectedPos = lookForSelectablePosition(selectedPos, down);
5456 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5457 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005458 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005459 setSelectionInt(selectedPos);
5460 invokeOnItemScrollListener();
5461 } else {
5462 selectedPos = INVALID_POSITION;
5463 }
5464 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5465
5466 return selectedPos >= 0;
5467 }
5468
Adam Powell14c08042011-10-06 19:46:18 -07005469 void confirmCheckedPositionsById() {
5470 // Clear out the positional check states, we'll rebuild it below from IDs.
5471 mCheckStates.clear();
5472
5473 boolean checkedCountChanged = false;
5474 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5475 final long id = mCheckedIdStates.keyAt(checkedIndex);
5476 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5477
5478 final long lastPosId = mAdapter.getItemId(lastPos);
5479 if (id != lastPosId) {
5480 // Look around to see if the ID is nearby. If not, uncheck it.
5481 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5482 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5483 boolean found = false;
5484 for (int searchPos = start; searchPos < end; searchPos++) {
5485 final long searchId = mAdapter.getItemId(searchPos);
5486 if (id == searchId) {
5487 found = true;
5488 mCheckStates.put(searchPos, true);
5489 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5490 break;
5491 }
5492 }
5493
5494 if (!found) {
5495 mCheckedIdStates.delete(id);
5496 checkedIndex--;
5497 mCheckedItemCount--;
5498 checkedCountChanged = true;
5499 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5500 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5501 lastPos, id, false);
5502 }
5503 }
5504 } else {
5505 mCheckStates.put(lastPos, true);
5506 }
5507 }
5508
5509 if (checkedCountChanged && mChoiceActionMode != null) {
5510 mChoiceActionMode.invalidate();
5511 }
5512 }
5513
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005514 @Override
5515 protected void handleDataChanged() {
5516 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005517 int lastHandledItemCount = mLastHandledItemCount;
5518 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005519
5520 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5521 confirmCheckedPositionsById();
5522 }
5523
Adam Powell539ee872012-02-03 19:00:49 -08005524 // TODO: In the future we can recycle these views based on stable ID instead.
5525 mRecycler.clearTransientStateViews();
5526
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005527 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005528 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005529 int selectablePos;
5530
5531 // Find the row we are supposed to sync to
5532 if (mNeedSync) {
5533 // Update this first, since setNextSelectedPositionInt inspects it
5534 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005535 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005536
Adam Powell07852792010-11-10 16:57:05 -08005537 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005538 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5539 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005540 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5541 if (mForceTranscriptScroll) {
5542 mForceTranscriptScroll = false;
5543 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5544 return;
5545 }
Adam Powell07852792010-11-10 16:57:05 -08005546 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005547 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005548 final View lastChild = getChildAt(childCount - 1);
5549 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005550 if (mFirstPosition + childCount >= lastHandledItemCount &&
5551 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005552 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5553 return;
5554 }
5555 // Something new came in and we didn't scroll; give the user a clue that
5556 // there's something new.
5557 awakenScrollBars();
5558 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005559
5560 switch (mSyncMode) {
5561 case SYNC_SELECTED_POSITION:
5562 if (isInTouchMode()) {
5563 // We saved our state when not in touch mode. (We know this because
5564 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5565 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5566 // adjusting if the available range changed) and return.
5567 mLayoutMode = LAYOUT_SYNC;
5568 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5569
5570 return;
5571 } else {
5572 // See if we can find a position in the new data with the same
5573 // id as the old selection. This will change mSyncPosition.
5574 newPos = findSyncPosition();
5575 if (newPos >= 0) {
5576 // Found it. Now verify that new selection is still selectable
5577 selectablePos = lookForSelectablePosition(newPos, true);
5578 if (selectablePos == newPos) {
5579 // Same row id is selected
5580 mSyncPosition = newPos;
5581
5582 if (mSyncHeight == getHeight()) {
5583 // If we are at the same height as when we saved state, try
5584 // to restore the scroll position too.
5585 mLayoutMode = LAYOUT_SYNC;
5586 } else {
5587 // We are not the same height as when the selection was saved, so
5588 // don't try to restore the exact position
5589 mLayoutMode = LAYOUT_SET_SELECTION;
5590 }
5591
5592 // Restore selection
5593 setNextSelectedPositionInt(newPos);
5594 return;
5595 }
5596 }
5597 }
5598 break;
5599 case SYNC_FIRST_POSITION:
5600 // Leave mSyncPosition as it is -- just pin to available range
5601 mLayoutMode = LAYOUT_SYNC;
5602 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5603
5604 return;
5605 }
5606 }
5607
5608 if (!isInTouchMode()) {
5609 // We couldn't find matching data -- try to use the same position
5610 newPos = getSelectedItemPosition();
5611
5612 // Pin position to the available range
5613 if (newPos >= count) {
5614 newPos = count - 1;
5615 }
5616 if (newPos < 0) {
5617 newPos = 0;
5618 }
5619
5620 // Make sure we select something selectable -- first look down
5621 selectablePos = lookForSelectablePosition(newPos, true);
5622
5623 if (selectablePos >= 0) {
5624 setNextSelectedPositionInt(selectablePos);
5625 return;
5626 } else {
5627 // Looking down didn't work -- try looking up
5628 selectablePos = lookForSelectablePosition(newPos, false);
5629 if (selectablePos >= 0) {
5630 setNextSelectedPositionInt(selectablePos);
5631 return;
5632 }
5633 }
5634 } else {
5635
5636 // We already know where we want to resurrect the selection
5637 if (mResurrectToPosition >= 0) {
5638 return;
5639 }
5640 }
5641
5642 }
5643
5644 // Nothing is selected. Give up and reset everything.
5645 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5646 mSelectedPosition = INVALID_POSITION;
5647 mSelectedRowId = INVALID_ROW_ID;
5648 mNextSelectedPosition = INVALID_POSITION;
5649 mNextSelectedRowId = INVALID_ROW_ID;
5650 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005651 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005652 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005653 checkSelectionChanged();
5654 }
5655
Romain Guy43c9cdf2010-01-27 13:53:55 -08005656 @Override
5657 protected void onDisplayHint(int hint) {
5658 super.onDisplayHint(hint);
5659 switch (hint) {
5660 case INVISIBLE:
5661 if (mPopup != null && mPopup.isShowing()) {
5662 dismissPopup();
5663 }
5664 break;
5665 case VISIBLE:
5666 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5667 showPopup();
5668 }
5669 break;
5670 }
Romain Guy24562482010-02-01 14:56:19 -08005671 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005672 }
5673
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005674 /**
5675 * Removes the filter window
5676 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005677 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005678 if (mPopup != null) {
5679 mPopup.dismiss();
5680 }
5681 }
5682
5683 /**
5684 * Shows the filter window
5685 */
5686 private void showPopup() {
5687 // Make sure we have a window before showing the popup
5688 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005689 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005690 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005691 // Make sure we get focus if we are showing the popup
5692 checkFocus();
5693 }
5694 }
5695
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005696 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005697 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5698 final int[] xy = new int[2];
5699 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005700 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005701 // TODO: And the gravity should be defined in the theme as well
5702 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005703 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005704 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5705 xy[0], bottomGap);
5706 } else {
5707 mPopup.update(xy[0], bottomGap, -1, -1);
5708 }
5709 }
5710
5711 /**
5712 * What is the distance between the source and destination rectangles given the direction of
5713 * focus navigation between them? The direction basically helps figure out more quickly what is
5714 * self evident by the relationship between the rects...
5715 *
5716 * @param source the source rectangle
5717 * @param dest the destination rectangle
5718 * @param direction the direction
5719 * @return the distance between the rectangles
5720 */
5721 static int getDistance(Rect source, Rect dest, int direction) {
5722 int sX, sY; // source x, y
5723 int dX, dY; // dest x, y
5724 switch (direction) {
5725 case View.FOCUS_RIGHT:
5726 sX = source.right;
5727 sY = source.top + source.height() / 2;
5728 dX = dest.left;
5729 dY = dest.top + dest.height() / 2;
5730 break;
5731 case View.FOCUS_DOWN:
5732 sX = source.left + source.width() / 2;
5733 sY = source.bottom;
5734 dX = dest.left + dest.width() / 2;
5735 dY = dest.top;
5736 break;
5737 case View.FOCUS_LEFT:
5738 sX = source.left;
5739 sY = source.top + source.height() / 2;
5740 dX = dest.right;
5741 dY = dest.top + dest.height() / 2;
5742 break;
5743 case View.FOCUS_UP:
5744 sX = source.left + source.width() / 2;
5745 sY = source.top;
5746 dX = dest.left + dest.width() / 2;
5747 dY = dest.bottom;
5748 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005749 case View.FOCUS_FORWARD:
5750 case View.FOCUS_BACKWARD:
5751 sX = source.right + source.width() / 2;
5752 sY = source.top + source.height() / 2;
5753 dX = dest.left + dest.width() / 2;
5754 dY = dest.top + dest.height() / 2;
5755 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005756 default:
5757 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005758 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5759 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005760 }
5761 int deltaX = dX - sX;
5762 int deltaY = dY - sY;
5763 return deltaY * deltaY + deltaX * deltaX;
5764 }
5765
5766 @Override
5767 protected boolean isInFilterMode() {
5768 return mFiltered;
5769 }
5770
5771 /**
5772 * Sends a key to the text filter window
5773 *
5774 * @param keyCode The keycode for the event
5775 * @param event The actual key event
5776 *
5777 * @return True if the text filter handled the event, false otherwise.
5778 */
5779 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005780 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005781 return false;
5782 }
5783
5784 boolean handled = false;
5785 boolean okToSend = true;
5786 switch (keyCode) {
5787 case KeyEvent.KEYCODE_DPAD_UP:
5788 case KeyEvent.KEYCODE_DPAD_DOWN:
5789 case KeyEvent.KEYCODE_DPAD_LEFT:
5790 case KeyEvent.KEYCODE_DPAD_RIGHT:
5791 case KeyEvent.KEYCODE_DPAD_CENTER:
5792 case KeyEvent.KEYCODE_ENTER:
5793 okToSend = false;
5794 break;
5795 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005796 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005797 if (event.getAction() == KeyEvent.ACTION_DOWN
5798 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005799 KeyEvent.DispatcherState state = getKeyDispatcherState();
5800 if (state != null) {
5801 state.startTracking(event, this);
5802 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005803 handled = true;
5804 } else if (event.getAction() == KeyEvent.ACTION_UP
5805 && event.isTracking() && !event.isCanceled()) {
5806 handled = true;
5807 mTextFilter.setText("");
5808 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005809 }
5810 okToSend = false;
5811 break;
5812 case KeyEvent.KEYCODE_SPACE:
5813 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005814 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005815 break;
5816 }
5817
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005818 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005819 createTextFilter(true);
5820
5821 KeyEvent forwardEvent = event;
5822 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005823 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005824 }
5825
5826 int action = event.getAction();
5827 switch (action) {
5828 case KeyEvent.ACTION_DOWN:
5829 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5830 break;
5831
5832 case KeyEvent.ACTION_UP:
5833 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5834 break;
5835
5836 case KeyEvent.ACTION_MULTIPLE:
5837 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5838 break;
5839 }
5840 }
5841 return handled;
5842 }
5843
5844 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005845 * Return an InputConnection for editing of the filter text.
5846 */
5847 @Override
5848 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005849 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005850 if (mPublicInputConnection == null) {
5851 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005852 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005853 }
Romain Guyf6991302013-06-05 17:19:01 -07005854 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005855 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5856 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005857 }
5858 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005859 }
Romain Guy0a637162009-05-29 14:43:54 -07005860
Romain Guyf6991302013-06-05 17:19:01 -07005861 private class InputConnectionWrapper implements InputConnection {
5862 private final EditorInfo mOutAttrs;
5863 private InputConnection mTarget;
5864
5865 public InputConnectionWrapper(EditorInfo outAttrs) {
5866 mOutAttrs = outAttrs;
5867 }
5868
5869 private InputConnection getTarget() {
5870 if (mTarget == null) {
5871 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5872 }
5873 return mTarget;
5874 }
5875
5876 @Override
5877 public boolean reportFullscreenMode(boolean enabled) {
5878 // Use our own input connection, since it is
5879 // the "real" one the IME is talking with.
5880 return mDefInputConnection.reportFullscreenMode(enabled);
5881 }
5882
5883 @Override
5884 public boolean performEditorAction(int editorAction) {
5885 // The editor is off in its own window; we need to be
5886 // the one that does this.
5887 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005888 InputMethodManager imm =
5889 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005890 if (imm != null) {
5891 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5892 }
5893 return true;
5894 }
5895 return false;
5896 }
5897
5898 @Override
5899 public boolean sendKeyEvent(KeyEvent event) {
5900 // Use our own input connection, since the filter
5901 // text view may not be shown in a window so has
5902 // no ViewAncestor to dispatch events with.
5903 return mDefInputConnection.sendKeyEvent(event);
5904 }
5905
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005906 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005907 public CharSequence getTextBeforeCursor(int n, int flags) {
5908 if (mTarget == null) return "";
5909 return mTarget.getTextBeforeCursor(n, flags);
5910 }
5911
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005912 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005913 public CharSequence getTextAfterCursor(int n, int flags) {
5914 if (mTarget == null) return "";
5915 return mTarget.getTextAfterCursor(n, flags);
5916 }
5917
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005918 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005919 public CharSequence getSelectedText(int flags) {
5920 if (mTarget == null) return "";
5921 return mTarget.getSelectedText(flags);
5922 }
5923
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005924 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005925 public int getCursorCapsMode(int reqModes) {
5926 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5927 return mTarget.getCursorCapsMode(reqModes);
5928 }
5929
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005930 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005931 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5932 return getTarget().getExtractedText(request, flags);
5933 }
5934
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005935 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005936 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5937 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5938 }
5939
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005940 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08005941 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5942 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5943 }
5944
5945 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005946 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5947 return getTarget().setComposingText(text, newCursorPosition);
5948 }
5949
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005950 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005951 public boolean setComposingRegion(int start, int end) {
5952 return getTarget().setComposingRegion(start, end);
5953 }
5954
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005955 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005956 public boolean finishComposingText() {
5957 return mTarget == null || mTarget.finishComposingText();
5958 }
5959
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005960 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005961 public boolean commitText(CharSequence text, int newCursorPosition) {
5962 return getTarget().commitText(text, newCursorPosition);
5963 }
5964
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005965 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005966 public boolean commitCompletion(CompletionInfo text) {
5967 return getTarget().commitCompletion(text);
5968 }
5969
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005970 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005971 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5972 return getTarget().commitCorrection(correctionInfo);
5973 }
5974
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005975 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005976 public boolean setSelection(int start, int end) {
5977 return getTarget().setSelection(start, end);
5978 }
5979
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005980 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005981 public boolean performContextMenuAction(int id) {
5982 return getTarget().performContextMenuAction(id);
5983 }
5984
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005985 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005986 public boolean beginBatchEdit() {
5987 return getTarget().beginBatchEdit();
5988 }
5989
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005990 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005991 public boolean endBatchEdit() {
5992 return getTarget().endBatchEdit();
5993 }
5994
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005995 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005996 public boolean clearMetaKeyStates(int states) {
5997 return getTarget().clearMetaKeyStates(states);
5998 }
5999
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006000 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006001 public boolean performPrivateCommand(String action, Bundle data) {
6002 return getTarget().performPrivateCommand(action, data);
6003 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09006004
6005 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07006006 public boolean requestCursorUpdates(int cursorUpdateMode) {
6007 return getTarget().requestCursorUpdates(cursorUpdateMode);
6008 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08006009
6010 @Override
6011 public Handler getHandler() {
6012 return getTarget().getHandler();
6013 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07006014
6015 @Override
6016 public void closeConnection() {
6017 getTarget().closeConnection();
6018 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07006019
6020 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07006021 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6022 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07006023 }
Romain Guyf6991302013-06-05 17:19:01 -07006024 }
6025
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006026 /**
6027 * For filtering we proxy an input connection to an internal text editor,
6028 * and this allows the proxying to happen.
6029 */
6030 @Override
6031 public boolean checkInputConnectionProxy(View view) {
6032 return view == mTextFilter;
6033 }
Romain Guy0a637162009-05-29 14:43:54 -07006034
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006035 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006036 * Creates the window for the text filter and populates it with an EditText field;
6037 *
6038 * @param animateEntrance true if the window should appear with an animation
6039 */
6040 private void createTextFilter(boolean animateEntrance) {
6041 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006042 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006043 p.setFocusable(false);
6044 p.setTouchable(false);
6045 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006046 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006047 p.setWidth(LayoutParams.WRAP_CONTENT);
6048 p.setHeight(LayoutParams.WRAP_CONTENT);
6049 p.setBackgroundDrawable(null);
6050 mPopup = p;
6051 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006052 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006053 }
6054 if (animateEntrance) {
6055 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6056 } else {
6057 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6058 }
6059 }
6060
Romain Guyf6991302013-06-05 17:19:01 -07006061 private EditText getTextFilterInput() {
6062 if (mTextFilter == null) {
6063 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6064 mTextFilter = (EditText) layoutInflater.inflate(
6065 com.android.internal.R.layout.typing_filter, null);
6066 // For some reason setting this as the "real" input type changes
6067 // the text view in some way that it doesn't work, and I don't
6068 // want to figure out why this is.
6069 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6070 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6071 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6072 mTextFilter.addTextChangedListener(this);
6073 }
6074 return mTextFilter;
6075 }
6076
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006077 /**
6078 * Clear the text filter.
6079 */
6080 public void clearTextFilter() {
6081 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006082 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006083 mFiltered = false;
6084 if (mPopup != null && mPopup.isShowing()) {
6085 dismissPopup();
6086 }
6087 }
6088 }
6089
6090 /**
6091 * Returns if the ListView currently has a text filter.
6092 */
6093 public boolean hasTextFilter() {
6094 return mFiltered;
6095 }
6096
Alan Viverette8fa327a2013-05-31 14:53:13 -07006097 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006098 public void onGlobalLayout() {
6099 if (isShown()) {
6100 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006101 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006102 showPopup();
6103 }
6104 } else {
6105 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006106 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006107 dismissPopup();
6108 }
6109 }
6110
6111 }
6112
6113 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006114 * For our text watcher that is associated with the text filter. Does
6115 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006116 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006117 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006118 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6119 }
6120
6121 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006122 * For our text watcher that is associated with the text filter. Performs
6123 * the actual filtering as the text changes, and takes care of hiding and
6124 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006125 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006126 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006127 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006128 if (isTextFilterEnabled()) {
6129 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006130 int length = s.length();
6131 boolean showing = mPopup.isShowing();
6132 if (!showing && length > 0) {
6133 // Show the filter popup if necessary
6134 showPopup();
6135 mFiltered = true;
6136 } else if (showing && length == 0) {
6137 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006138 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006139 mFiltered = false;
6140 }
6141 if (mAdapter instanceof Filterable) {
6142 Filter f = ((Filterable) mAdapter).getFilter();
6143 // Filter should not be null when we reach this part
6144 if (f != null) {
6145 f.filter(s, this);
6146 } else {
6147 throw new IllegalStateException("You cannot call onTextChanged with a non "
6148 + "filterable adapter");
6149 }
6150 }
6151 }
6152 }
6153
6154 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006155 * For our text watcher that is associated with the text filter. Does
6156 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006157 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006158 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006159 public void afterTextChanged(Editable s) {
6160 }
6161
Alan Viverette8fa327a2013-05-31 14:53:13 -07006162 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006163 public void onFilterComplete(int count) {
6164 if (mSelectedPosition < 0 && count > 0) {
6165 mResurrectToPosition = INVALID_POSITION;
6166 resurrectSelection();
6167 }
6168 }
6169
6170 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006171 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6172 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6173 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6174 }
6175
6176 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006177 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6178 return new LayoutParams(p);
6179 }
6180
6181 @Override
6182 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6183 return new AbsListView.LayoutParams(getContext(), attrs);
6184 }
6185
6186 @Override
6187 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6188 return p instanceof AbsListView.LayoutParams;
6189 }
6190
6191 /**
6192 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6193 * to the bottom to show new items.
6194 *
6195 * @param mode the transcript mode to set
6196 *
6197 * @see #TRANSCRIPT_MODE_DISABLED
6198 * @see #TRANSCRIPT_MODE_NORMAL
6199 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6200 */
6201 public void setTranscriptMode(int mode) {
6202 mTranscriptMode = mode;
6203 }
6204
6205 /**
6206 * Returns the current transcript mode.
6207 *
6208 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6209 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6210 */
6211 public int getTranscriptMode() {
6212 return mTranscriptMode;
6213 }
6214
6215 @Override
6216 public int getSolidColor() {
6217 return mCacheColorHint;
6218 }
6219
6220 /**
6221 * 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 -07006222 * on top of a solid, single-color, opaque background.
6223 *
6224 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6225 * single color. This hint will not affect any existing background drawable set on this view (
6226 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006227 *
6228 * @param color The background color
6229 */
Tor Norbye80756e32015-03-02 09:39:27 -08006230 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006231 if (color != mCacheColorHint) {
6232 mCacheColorHint = color;
6233 int count = getChildCount();
6234 for (int i = 0; i < count; i++) {
6235 getChildAt(i).setDrawingCacheBackgroundColor(color);
6236 }
6237 mRecycler.setCacheColorHint(color);
6238 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006239 }
6240
6241 /**
6242 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6243 * on top of a solid, single-color, opaque background
6244 *
6245 * @return The cache color hint
6246 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006247 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006248 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006249 public int getCacheColorHint() {
6250 return mCacheColorHint;
6251 }
6252
6253 /**
6254 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6255 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6256 * internal view recycler.
6257 *
6258 * @param views A list into which to put the reclaimed views
6259 */
6260 public void reclaimViews(List<View> views) {
6261 int childCount = getChildCount();
6262 RecyclerListener listener = mRecycler.mRecyclerListener;
6263
6264 // Reclaim views on screen
6265 for (int i = 0; i < childCount; i++) {
6266 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006267 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006268 // Don't reclaim header or footer views, or views that should be ignored
6269 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6270 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006271 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006272 if (listener != null) {
6273 // Pretend they went through the scrap heap
6274 listener.onMovedToScrapHeap(child);
6275 }
6276 }
6277 }
6278 mRecycler.reclaimScrapViews(views);
6279 removeAllViewsInLayout();
6280 }
6281
Adam Powell637d3372010-08-25 14:37:03 -07006282 private void finishGlows() {
6283 if (mEdgeGlowTop != null) {
6284 mEdgeGlowTop.finish();
6285 mEdgeGlowBottom.finish();
6286 }
6287 }
6288
Romain Guy13922e02009-05-12 17:56:14 -07006289 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006290 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6291 * through the specified intent.
6292 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6293 */
6294 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -08006295 setRemoteViewsAdapter(intent, false);
6296 }
6297
6298 /** @hide **/
6299 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6300 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6301 }
6302
6303 /** @hide **/
6304 @Override
6305 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006306 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6307 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006308 if (mRemoteAdapter != null) {
6309 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6310 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6311 mRemoteAdapter.getRemoteViewsServiceIntent());
6312 if (fcNew.equals(fcOld)) {
6313 return;
6314 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006315 }
Adam Cohen2148d432011-07-28 14:59:54 -07006316 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006317 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08006318 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07006319 if (mRemoteAdapter.isDataReady()) {
6320 setAdapter(mRemoteAdapter);
6321 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006322 }
6323
6324 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006325 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006326 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006327 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006328 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006329 * @hide
6330 */
6331 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6332 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6333 // service handling the specified intent.
6334 if (mRemoteAdapter != null) {
6335 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6336 }
6337 }
6338
6339 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006340 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6341 * connected yet.
6342 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006343 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006344 public void deferNotifyDataSetChanged() {
6345 mDeferNotifyDataSetChanged = true;
6346 }
6347
6348 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006349 * Called back when the adapter connects to the RemoteViewsService.
6350 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006351 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006352 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006353 if (mRemoteAdapter != mAdapter) {
6354 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006355 if (mDeferNotifyDataSetChanged) {
6356 mRemoteAdapter.notifyDataSetChanged();
6357 mDeferNotifyDataSetChanged = false;
6358 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006359 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006360 } else if (mRemoteAdapter != null) {
6361 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006362 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006363 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006364 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006365 }
6366
6367 /**
6368 * Called back when the adapter disconnects from the RemoteViewsService.
6369 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006370 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006371 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006372 // If the remote adapter disconnects, we keep it around
6373 // since the currently displayed items are still cached.
6374 // Further, we want the service to eventually reconnect
6375 // when necessary, as triggered by this view requesting
6376 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006377 }
6378
6379 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006380 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6381 * being displayed by the AbsListView.
6382 */
6383 void setVisibleRangeHint(int start, int end) {
6384 if (mRemoteAdapter != null) {
6385 mRemoteAdapter.setVisibleRangeHint(start, end);
6386 }
6387 }
6388
6389 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006390 * Sets the recycler listener to be notified whenever a View is set aside in
6391 * the recycler for later reuse. This listener can be used to free resources
6392 * associated to the View.
6393 *
6394 * @param listener The recycler listener to be notified of views set aside
6395 * in the recycler.
6396 *
6397 * @see android.widget.AbsListView.RecycleBin
6398 * @see android.widget.AbsListView.RecyclerListener
6399 */
6400 public void setRecyclerListener(RecyclerListener listener) {
6401 mRecycler.mRecyclerListener = listener;
6402 }
6403
Adam Powellb1f498a2011-01-18 20:43:23 -08006404 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6405 @Override
6406 public void onChanged() {
6407 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006408 if (mFastScroll != null) {
6409 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006410 }
6411 }
6412
6413 @Override
6414 public void onInvalidated() {
6415 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006416 if (mFastScroll != null) {
6417 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006418 }
6419 }
6420 }
6421
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006422 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006423 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6424 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6425 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6426 * selects and deselects list items.
6427 */
6428 public interface MultiChoiceModeListener extends ActionMode.Callback {
6429 /**
6430 * Called when an item is checked or unchecked during selection mode.
6431 *
6432 * @param mode The {@link ActionMode} providing the selection mode
6433 * @param position Adapter position of the item that was checked or unchecked
6434 * @param id Adapter ID of the item that was checked or unchecked
6435 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6436 * if the item is now unchecked.
6437 */
6438 public void onItemCheckedStateChanged(ActionMode mode,
6439 int position, long id, boolean checked);
6440 }
6441
6442 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6443 private MultiChoiceModeListener mWrapped;
6444
6445 public void setWrapped(MultiChoiceModeListener wrapped) {
6446 mWrapped = wrapped;
6447 }
6448
Adam Powella7981702012-08-24 12:43:41 -07006449 public boolean hasWrappedCallback() {
6450 return mWrapped != null;
6451 }
6452
Alan Viverette8fa327a2013-05-31 14:53:13 -07006453 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006454 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6455 if (mWrapped.onCreateActionMode(mode, menu)) {
6456 // Initialize checked graphic state?
6457 setLongClickable(false);
6458 return true;
6459 }
6460 return false;
6461 }
6462
Alan Viverette8fa327a2013-05-31 14:53:13 -07006463 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006464 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6465 return mWrapped.onPrepareActionMode(mode, menu);
6466 }
6467
Alan Viverette8fa327a2013-05-31 14:53:13 -07006468 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006469 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6470 return mWrapped.onActionItemClicked(mode, item);
6471 }
6472
Alan Viverette8fa327a2013-05-31 14:53:13 -07006473 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006474 public void onDestroyActionMode(ActionMode mode) {
6475 mWrapped.onDestroyActionMode(mode);
6476 mChoiceActionMode = null;
6477
6478 // Ending selection mode means deselecting everything.
6479 clearChoices();
6480
6481 mDataChanged = true;
6482 rememberSyncState();
6483 requestLayout();
6484
6485 setLongClickable(true);
6486 }
6487
Alan Viverette8fa327a2013-05-31 14:53:13 -07006488 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006489 public void onItemCheckedStateChanged(ActionMode mode,
6490 int position, long id, boolean checked) {
6491 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6492
6493 // If there are no items selected we no longer need the selection mode.
6494 if (getCheckedItemCount() == 0) {
6495 mode.finish();
6496 }
6497 }
6498 }
6499
6500 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006501 * AbsListView extends LayoutParams to provide a place to hold the view type.
6502 */
6503 public static class LayoutParams extends ViewGroup.LayoutParams {
6504 /**
6505 * View type for this view, as returned by
6506 * {@link android.widget.Adapter#getItemViewType(int) }
6507 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006508 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006509 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6510 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6511 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006512 int viewType;
6513
The Android Open Source Project4df24232009-03-05 14:34:35 -08006514 /**
6515 * When this boolean is set, the view has been added to the AbsListView
6516 * at least once. It is used to know whether headers/footers have already
6517 * been added to the list view and whether they should be treated as
6518 * recycled views or not.
6519 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006520 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006521 boolean recycledHeaderFooter;
6522
Romain Guy0bf88592010-03-02 13:38:44 -08006523 /**
6524 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6525 * to obtain children views to measure itself. When doing so, the children
6526 * are not attached to the window, but put in the recycler which assumes
6527 * they've been attached before. Setting this flag will force the reused
6528 * view to be attached to the window rather than just attached to the
6529 * parent.
6530 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006531 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006532 boolean forceAdd;
6533
Dianne Hackborn079e2352010-10-18 17:02:43 -07006534 /**
6535 * The position the view was removed from when pulled out of the
6536 * scrap heap.
6537 * @hide
6538 */
6539 int scrappedFromPosition;
6540
Adam Powell539ee872012-02-03 19:00:49 -08006541 /**
6542 * The ID the view represents
6543 */
6544 long itemId = -1;
6545
Alan Viverette92539d52015-09-14 10:49:25 -04006546 /** Whether the adapter considers the item enabled. */
6547 boolean isEnabled;
6548
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006549 public LayoutParams(Context c, AttributeSet attrs) {
6550 super(c, attrs);
6551 }
6552
6553 public LayoutParams(int w, int h) {
6554 super(w, h);
6555 }
6556
6557 public LayoutParams(int w, int h, int viewType) {
6558 super(w, h);
6559 this.viewType = viewType;
6560 }
6561
6562 public LayoutParams(ViewGroup.LayoutParams source) {
6563 super(source);
6564 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006565
6566 /** @hide */
6567 @Override
6568 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6569 super.encodeProperties(encoder);
6570
6571 encoder.addProperty("list:viewType", viewType);
6572 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6573 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006574 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006575 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006576 }
6577
6578 /**
6579 * A RecyclerListener is used to receive a notification whenever a View is placed
6580 * inside the RecycleBin's scrap heap. This listener is used to free resources
6581 * associated to Views placed in the RecycleBin.
6582 *
6583 * @see android.widget.AbsListView.RecycleBin
6584 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6585 */
6586 public static interface RecyclerListener {
6587 /**
6588 * Indicates that the specified View was moved into the recycler's scrap heap.
6589 * The view is not displayed on screen any more and any expensive resource
6590 * associated with the view should be discarded.
6591 *
6592 * @param view
6593 */
6594 void onMovedToScrapHeap(View view);
6595 }
6596
6597 /**
6598 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6599 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6600 * start of a layout. By construction, they are displaying current information. At the end of
6601 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6602 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6603 *
6604 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6605 * @see android.widget.AbsListView.RecyclerListener
6606 */
6607 class RecycleBin {
6608 private RecyclerListener mRecyclerListener;
6609
6610 /**
6611 * The position of the first view stored in mActiveViews.
6612 */
6613 private int mFirstActivePosition;
6614
6615 /**
6616 * Views that were on screen at the start of layout. This array is populated at the start of
6617 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6618 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6619 * view store in mFirstActivePosition.
6620 */
6621 private View[] mActiveViews = new View[0];
6622
6623 /**
6624 * Unsorted views that can be used by the adapter as a convert view.
6625 */
6626 private ArrayList<View>[] mScrapViews;
6627
6628 private int mViewTypeCount;
6629
6630 private ArrayList<View> mCurrentScrap;
6631
Adam Powell539ee872012-02-03 19:00:49 -08006632 private ArrayList<View> mSkippedScrap;
6633
6634 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006635 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006636
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006637 public void setViewTypeCount(int viewTypeCount) {
6638 if (viewTypeCount < 1) {
6639 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6640 }
6641 //noinspection unchecked
6642 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6643 for (int i = 0; i < viewTypeCount; i++) {
6644 scrapViews[i] = new ArrayList<View>();
6645 }
6646 mViewTypeCount = viewTypeCount;
6647 mCurrentScrap = scrapViews[0];
6648 mScrapViews = scrapViews;
6649 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006650
Adam Powellf3c2eda2010-03-16 17:31:01 -07006651 public void markChildrenDirty() {
6652 if (mViewTypeCount == 1) {
6653 final ArrayList<View> scrap = mCurrentScrap;
6654 final int scrapCount = scrap.size();
6655 for (int i = 0; i < scrapCount; i++) {
6656 scrap.get(i).forceLayout();
6657 }
6658 } else {
6659 final int typeCount = mViewTypeCount;
6660 for (int i = 0; i < typeCount; i++) {
6661 final ArrayList<View> scrap = mScrapViews[i];
6662 final int scrapCount = scrap.size();
6663 for (int j = 0; j < scrapCount; j++) {
6664 scrap.get(j).forceLayout();
6665 }
6666 }
6667 }
Adam Powell539ee872012-02-03 19:00:49 -08006668 if (mTransientStateViews != null) {
6669 final int count = mTransientStateViews.size();
6670 for (int i = 0; i < count; i++) {
6671 mTransientStateViews.valueAt(i).forceLayout();
6672 }
6673 }
Chet Haase72871322013-02-26 16:12:13 -07006674 if (mTransientStateViewsById != null) {
6675 final int count = mTransientStateViewsById.size();
6676 for (int i = 0; i < count; i++) {
6677 mTransientStateViewsById.valueAt(i).forceLayout();
6678 }
6679 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006680 }
Romain Guy0a637162009-05-29 14:43:54 -07006681
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006682 public boolean shouldRecycleViewType(int viewType) {
6683 return viewType >= 0;
6684 }
6685
6686 /**
6687 * Clears the scrap heap.
6688 */
6689 void clear() {
6690 if (mViewTypeCount == 1) {
6691 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006692 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006693 } else {
6694 final int typeCount = mViewTypeCount;
6695 for (int i = 0; i < typeCount; i++) {
6696 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006697 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006698 }
6699 }
Alan Viverette59511502013-12-09 13:49:25 -08006700
6701 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006702 }
6703
6704 /**
6705 * Fill ActiveViews with all of the children of the AbsListView.
6706 *
6707 * @param childCount The minimum number of views mActiveViews should hold
6708 * @param firstActivePosition The position of the first view that will be stored in
6709 * mActiveViews
6710 */
6711 void fillActiveViews(int childCount, int firstActivePosition) {
6712 if (mActiveViews.length < childCount) {
6713 mActiveViews = new View[childCount];
6714 }
6715 mFirstActivePosition = firstActivePosition;
6716
Romain Guyf6991302013-06-05 17:19:01 -07006717 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006718 final View[] activeViews = mActiveViews;
6719 for (int i = 0; i < childCount; i++) {
6720 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006721 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006722 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006723 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006724 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6725 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006726 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006727 // Remember the position so that setupChild() doesn't reset state.
6728 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006729 }
6730 }
6731 }
6732
6733 /**
6734 * Get the view corresponding to the specified position. The view will be removed from
6735 * mActiveViews if it is found.
6736 *
6737 * @param position The position to look up in mActiveViews
6738 * @return The view if it is found, null otherwise
6739 */
6740 View getActiveView(int position) {
6741 int index = position - mFirstActivePosition;
6742 final View[] activeViews = mActiveViews;
6743 if (index >=0 && index < activeViews.length) {
6744 final View match = activeViews[index];
6745 activeViews[index] = null;
6746 return match;
6747 }
6748 return null;
6749 }
6750
Adam Powell539ee872012-02-03 19:00:49 -08006751 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006752 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6753 long id = mAdapter.getItemId(position);
6754 View result = mTransientStateViewsById.get(id);
6755 mTransientStateViewsById.remove(id);
6756 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006757 }
Chet Haase72871322013-02-26 16:12:13 -07006758 if (mTransientStateViews != null) {
6759 final int index = mTransientStateViews.indexOfKey(position);
6760 if (index >= 0) {
6761 View result = mTransientStateViews.valueAt(index);
6762 mTransientStateViews.removeAt(index);
6763 return result;
6764 }
Adam Powell539ee872012-02-03 19:00:49 -08006765 }
Chet Haase72871322013-02-26 16:12:13 -07006766 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006767 }
6768
6769 /**
Alan Viverette59511502013-12-09 13:49:25 -08006770 * Dumps and fully detaches any currently saved views with transient
6771 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006772 */
6773 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006774 final SparseArray<View> viewsByPos = mTransientStateViews;
6775 if (viewsByPos != null) {
6776 final int N = viewsByPos.size();
6777 for (int i = 0; i < N; i++) {
6778 removeDetachedView(viewsByPos.valueAt(i), false);
6779 }
6780 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006781 }
Alan Viverette59511502013-12-09 13:49:25 -08006782
6783 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6784 if (viewsById != null) {
6785 final int N = viewsById.size();
6786 for (int i = 0; i < N; i++) {
6787 removeDetachedView(viewsById.valueAt(i), false);
6788 }
6789 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006790 }
Adam Powell539ee872012-02-03 19:00:49 -08006791 }
6792
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006793 /**
6794 * @return A view from the ScrapViews collection. These are unordered.
6795 */
6796 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006797 final int whichScrap = mAdapter.getItemViewType(position);
6798 if (whichScrap < 0) {
6799 return null;
6800 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006801 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006802 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006803 } else if (whichScrap < mScrapViews.length) {
6804 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006805 }
6806 return null;
6807 }
6808
6809 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006810 * Puts a view into the list of scrap views.
6811 * <p>
6812 * If the list data hasn't changed or the adapter has stable IDs, views
6813 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006814 *
6815 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006816 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006817 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006818 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006819 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006820 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006821 // Can't recycle, but we don't know anything about the view.
6822 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006823 return;
6824 }
6825
Adam Powell539ee872012-02-03 19:00:49 -08006826 lp.scrappedFromPosition = position;
6827
Alan Viverette1e51cc72013-09-27 14:32:20 -07006828 // Remove but don't scrap header or footer views, or views that
6829 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006830 final int viewType = lp.viewType;
6831 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006832 // Can't recycle. If it's not a header or footer, which have
6833 // special handling and should be ignored, then skip the scrap
6834 // heap and we'll fully detach the view later.
6835 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6836 getSkippedScrap().add(scrap);
6837 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006838 return;
6839 }
6840
6841 scrap.dispatchStartTemporaryDetach();
6842
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006843 // The the accessibility state of the view may change while temporary
6844 // detached and we do not allow detached views to fire accessibility
6845 // events. So we are announcing that the subtree changed giving a chance
6846 // to clients holding on to a view in this subtree to refresh it.
6847 notifyViewAccessibilityStateChangedIfNeeded(
6848 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6849
Alan Viveretted44696c2013-07-18 10:37:15 -07006850 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006851 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006852 if (scrapHasTransientState) {
6853 if (mAdapter != null && mAdapterHasStableIds) {
6854 // If the adapter has stable IDs, we can reuse the view for
6855 // the same data.
6856 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006857 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006858 }
6859 mTransientStateViewsById.put(lp.itemId, scrap);
6860 } else if (!mDataChanged) {
6861 // If the data hasn't changed, we can reuse the views at
6862 // their old positions.
6863 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006864 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006865 }
6866 mTransientStateViews.put(position, scrap);
6867 } else {
6868 // Otherwise, we'll have to remove the view and start over.
Phil Weaverec66fb82017-03-23 12:21:53 -07006869 clearScrapForRebind(scrap);
Alan Viverette8bbae342015-06-25 14:49:29 -07006870 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006871 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006872 } else {
Phil Weaverec66fb82017-03-23 12:21:53 -07006873 clearScrapForRebind(scrap);
Alan Viveretted44696c2013-07-18 10:37:15 -07006874 if (mViewTypeCount == 1) {
6875 mCurrentScrap.add(scrap);
6876 } else {
6877 mScrapViews[viewType].add(scrap);
6878 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006879
Alan Viveretted44696c2013-07-18 10:37:15 -07006880 if (mRecyclerListener != null) {
6881 mRecyclerListener.onMovedToScrapHeap(scrap);
6882 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006883 }
6884 }
6885
Alan Viverette8bbae342015-06-25 14:49:29 -07006886 private ArrayList<View> getSkippedScrap() {
6887 if (mSkippedScrap == null) {
6888 mSkippedScrap = new ArrayList<>();
6889 }
6890 return mSkippedScrap;
6891 }
6892
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006893 /**
Adam Powell539ee872012-02-03 19:00:49 -08006894 * Finish the removal of any views that skipped the scrap heap.
6895 */
6896 void removeSkippedScrap() {
6897 if (mSkippedScrap == null) {
6898 return;
6899 }
6900 final int count = mSkippedScrap.size();
6901 for (int i = 0; i < count; i++) {
6902 removeDetachedView(mSkippedScrap.get(i), false);
6903 }
6904 mSkippedScrap.clear();
6905 }
6906
6907 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006908 * Move all views remaining in mActiveViews to mScrapViews.
6909 */
6910 void scrapActiveViews() {
6911 final View[] activeViews = mActiveViews;
6912 final boolean hasListener = mRecyclerListener != null;
6913 final boolean multipleScraps = mViewTypeCount > 1;
6914
6915 ArrayList<View> scrapViews = mCurrentScrap;
6916 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006917 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006918 final View victim = activeViews[i];
6919 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006920 final AbsListView.LayoutParams lp
6921 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006922 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006923
6924 activeViews[i] = null;
6925
Alan Viverette59511502013-12-09 13:49:25 -08006926 if (victim.hasTransientState()) {
6927 // Store views with transient state for later use.
6928 victim.dispatchStartTemporaryDetach();
6929
6930 if (mAdapter != null && mAdapterHasStableIds) {
6931 if (mTransientStateViewsById == null) {
6932 mTransientStateViewsById = new LongSparseArray<View>();
6933 }
6934 long id = mAdapter.getItemId(mFirstActivePosition + i);
6935 mTransientStateViewsById.put(id, victim);
6936 } else if (!mDataChanged) {
6937 if (mTransientStateViews == null) {
6938 mTransientStateViews = new SparseArray<View>();
6939 }
6940 mTransientStateViews.put(mFirstActivePosition + i, victim);
6941 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6942 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006943 removeDetachedView(victim, false);
6944 }
Alan Viverette59511502013-12-09 13:49:25 -08006945 } else if (!shouldRecycleViewType(whichScrap)) {
6946 // Discard non-recyclable views except headers/footers.
6947 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6948 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006949 }
Alan Viverette59511502013-12-09 13:49:25 -08006950 } else {
6951 // Store everything else on the appropriate scrap heap.
6952 if (multipleScraps) {
6953 scrapViews = mScrapViews[whichScrap];
6954 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006955
Alan Viverette59511502013-12-09 13:49:25 -08006956 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006957 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08006958 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006959
Alan Viverette59511502013-12-09 13:49:25 -08006960 if (hasListener) {
6961 mRecyclerListener.onMovedToScrapHeap(victim);
6962 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006963 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006964 }
6965 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006966 pruneScrapViews();
6967 }
6968
6969 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006970 * At the end of a layout pass, all temp detached views should either be re-attached or
6971 * completely detached. This method ensures that any remaining view in the scrap list is
6972 * fully detached.
6973 */
6974 void fullyDetachScrapViews() {
6975 final int viewTypeCount = mViewTypeCount;
6976 final ArrayList<View>[] scrapViews = mScrapViews;
6977 for (int i = 0; i < viewTypeCount; ++i) {
6978 final ArrayList<View> scrapPile = scrapViews[i];
6979 for (int j = scrapPile.size() - 1; j >= 0; j--) {
6980 final View view = scrapPile.get(j);
6981 if (view.isTemporarilyDetached()) {
6982 removeDetachedView(view, false);
6983 }
6984 }
6985 }
6986 }
6987
6988 /**
Alan Viverette59511502013-12-09 13:49:25 -08006989 * Makes sure that the size of mScrapViews does not exceed the size of
6990 * mActiveViews, which can happen if an adapter does not recycle its
6991 * views. Removes cached transient state views that no longer have
6992 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006993 */
6994 private void pruneScrapViews() {
6995 final int maxViews = mActiveViews.length;
6996 final int viewTypeCount = mViewTypeCount;
6997 final ArrayList<View>[] scrapViews = mScrapViews;
6998 for (int i = 0; i < viewTypeCount; ++i) {
6999 final ArrayList<View> scrapPile = scrapViews[i];
7000 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007001 while (size > maxViews) {
7002 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007003 }
7004 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07007005
Alan Viverette59511502013-12-09 13:49:25 -08007006 final SparseArray<View> transViewsByPos = mTransientStateViews;
7007 if (transViewsByPos != null) {
7008 for (int i = 0; i < transViewsByPos.size(); i++) {
7009 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007010 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007011 removeDetachedView(v, false);
7012 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007013 i--;
7014 }
7015 }
7016 }
Alan Viverette59511502013-12-09 13:49:25 -08007017
7018 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7019 if (transViewsById != null) {
7020 for (int i = 0; i < transViewsById.size(); i++) {
7021 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007022 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007023 removeDetachedView(v, false);
7024 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007025 i--;
7026 }
7027 }
7028 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007029 }
7030
7031 /**
7032 * Puts all views in the scrap heap into the supplied list.
7033 */
7034 void reclaimScrapViews(List<View> views) {
7035 if (mViewTypeCount == 1) {
7036 views.addAll(mCurrentScrap);
7037 } else {
7038 final int viewTypeCount = mViewTypeCount;
7039 final ArrayList<View>[] scrapViews = mScrapViews;
7040 for (int i = 0; i < viewTypeCount; ++i) {
7041 final ArrayList<View> scrapPile = scrapViews[i];
7042 views.addAll(scrapPile);
7043 }
7044 }
7045 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007046
7047 /**
7048 * Updates the cache color hint of all known views.
7049 *
7050 * @param color The new cache color hint.
7051 */
7052 void setCacheColorHint(int color) {
7053 if (mViewTypeCount == 1) {
7054 final ArrayList<View> scrap = mCurrentScrap;
7055 final int scrapCount = scrap.size();
7056 for (int i = 0; i < scrapCount; i++) {
7057 scrap.get(i).setDrawingCacheBackgroundColor(color);
7058 }
7059 } else {
7060 final int typeCount = mViewTypeCount;
7061 for (int i = 0; i < typeCount; i++) {
7062 final ArrayList<View> scrap = mScrapViews[i];
7063 final int scrapCount = scrap.size();
7064 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007065 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007066 }
7067 }
7068 }
7069 // Just in case this is called during a layout pass
7070 final View[] activeViews = mActiveViews;
7071 final int count = activeViews.length;
7072 for (int i = 0; i < count; ++i) {
7073 final View victim = activeViews[i];
7074 if (victim != null) {
7075 victim.setDrawingCacheBackgroundColor(color);
7076 }
7077 }
7078 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007079
Alan Viverette3e141622014-02-18 17:05:13 -08007080 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7081 final int size = scrapViews.size();
7082 if (size > 0) {
7083 // See if we still have a view for this position or ID.
Phil Weavere28c03b2017-04-24 13:23:10 -07007084 // Traverse backwards to find the most recently used scrap view
7085 for (int i = size - 1; i >= 0; i--) {
Alan Viverette3e141622014-02-18 17:05:13 -08007086 final View view = scrapViews.get(i);
7087 final AbsListView.LayoutParams params =
7088 (AbsListView.LayoutParams) view.getLayoutParams();
7089
7090 if (mAdapterHasStableIds) {
7091 final long id = mAdapter.getItemId(position);
7092 if (id == params.itemId) {
7093 return scrapViews.remove(i);
7094 }
7095 } else if (params.scrappedFromPosition == position) {
7096 final View scrap = scrapViews.remove(i);
Phil Weaverec66fb82017-03-23 12:21:53 -07007097 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007098 return scrap;
7099 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007100 }
Alan Viverette3e141622014-02-18 17:05:13 -08007101 final View scrap = scrapViews.remove(size - 1);
Phil Weaverec66fb82017-03-23 12:21:53 -07007102 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007103 return scrap;
7104 } else {
7105 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007106 }
Alan Viverette3e141622014-02-18 17:05:13 -08007107 }
7108
7109 private void clearScrap(final ArrayList<View> scrap) {
7110 final int scrapCount = scrap.size();
7111 for (int j = 0; j < scrapCount; j++) {
7112 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7113 }
7114 }
7115
Phil Weaverec66fb82017-03-23 12:21:53 -07007116 private void clearScrapForRebind(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007117 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007118 view.setAccessibilityDelegate(null);
7119 }
7120
7121 private void removeDetachedView(View child, boolean animate) {
7122 child.setAccessibilityDelegate(null);
7123 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007124 }
7125 }
Alan Viverette441b4372014-02-12 13:30:20 -08007126
7127 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007128 * Returns the height of the view for the specified position.
7129 *
7130 * @param position the item position
7131 * @return view height in pixels
7132 */
7133 int getHeightForPosition(int position) {
7134 final int firstVisiblePosition = getFirstVisiblePosition();
7135 final int childCount = getChildCount();
7136 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007137 if (index >= 0 && index < childCount) {
7138 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007139 final View view = getChildAt(index);
7140 return view.getHeight();
7141 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007142 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007143 final View view = obtainView(position, mIsScrap);
7144 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7145 final int height = view.getMeasuredHeight();
7146 mRecycler.addScrapView(view, position);
7147 return height;
7148 }
7149 }
7150
7151 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007152 * Sets the selected item and positions the selection y pixels from the top edge
7153 * of the ListView. (If in touch mode, the item will not be selected but it will
7154 * still be positioned appropriately.)
7155 *
7156 * @param position Index (starting at 0) of the data item to be selected.
7157 * @param y The distance from the top edge of the ListView (plus padding) that the
7158 * item will be positioned.
7159 */
7160 public void setSelectionFromTop(int position, int y) {
7161 if (mAdapter == null) {
7162 return;
7163 }
7164
7165 if (!isInTouchMode()) {
7166 position = lookForSelectablePosition(position, true);
7167 if (position >= 0) {
7168 setNextSelectedPositionInt(position);
7169 }
7170 } else {
7171 mResurrectToPosition = position;
7172 }
7173
7174 if (position >= 0) {
7175 mLayoutMode = LAYOUT_SPECIFIC;
7176 mSpecificTop = mListPadding.top + y;
7177
7178 if (mNeedSync) {
7179 mSyncPosition = position;
7180 mSyncRowId = mAdapter.getItemId(position);
7181 }
7182
7183 if (mPositionScroller != null) {
7184 mPositionScroller.stop();
7185 }
7186 requestLayout();
7187 }
7188 }
7189
Siva Velusamy94a6d152015-05-05 15:07:00 -07007190 /** @hide */
7191 @Override
7192 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7193 super.encodeProperties(encoder);
7194
7195 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7196 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7197 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7198 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7199 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7200 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7201
7202 View selectedView = getSelectedView();
7203 if (selectedView != null) {
7204 encoder.addPropertyKey("selectedView");
7205 selectedView.encode(encoder);
7206 }
7207 }
7208
Alan Viveretted22db212014-02-13 17:47:38 -08007209 /**
7210 * Abstract positon scroller used to handle smooth scrolling.
7211 */
7212 static abstract class AbsPositionScroller {
7213 public abstract void start(int position);
7214 public abstract void start(int position, int boundPosition);
7215 public abstract void startWithOffset(int position, int offset);
7216 public abstract void startWithOffset(int position, int offset, int duration);
7217 public abstract void stop();
7218 }
7219
7220 /**
7221 * Default position scroller that simulates a fling.
7222 */
7223 class PositionScroller extends AbsPositionScroller implements Runnable {
7224 private static final int SCROLL_DURATION = 200;
7225
7226 private static final int MOVE_DOWN_POS = 1;
7227 private static final int MOVE_UP_POS = 2;
7228 private static final int MOVE_DOWN_BOUND = 3;
7229 private static final int MOVE_UP_BOUND = 4;
7230 private static final int MOVE_OFFSET = 5;
7231
7232 private int mMode;
7233 private int mTargetPos;
7234 private int mBoundPos;
7235 private int mLastSeenPos;
7236 private int mScrollDuration;
7237 private final int mExtraScroll;
7238
7239 private int mOffsetFromTop;
7240
7241 PositionScroller() {
7242 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7243 }
7244
7245 @Override
7246 public void start(final int position) {
7247 stop();
7248
7249 if (mDataChanged) {
7250 // Wait until we're back in a stable state to try this.
7251 mPositionScrollAfterLayout = new Runnable() {
7252 @Override public void run() {
7253 start(position);
7254 }
7255 };
7256 return;
7257 }
7258
7259 final int childCount = getChildCount();
7260 if (childCount == 0) {
7261 // Can't scroll without children.
7262 return;
7263 }
7264
7265 final int firstPos = mFirstPosition;
7266 final int lastPos = firstPos + childCount - 1;
7267
7268 int viewTravelCount;
7269 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7270 if (clampedPosition < firstPos) {
7271 viewTravelCount = firstPos - clampedPosition + 1;
7272 mMode = MOVE_UP_POS;
7273 } else if (clampedPosition > lastPos) {
7274 viewTravelCount = clampedPosition - lastPos + 1;
7275 mMode = MOVE_DOWN_POS;
7276 } else {
7277 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7278 return;
7279 }
7280
7281 if (viewTravelCount > 0) {
7282 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7283 } else {
7284 mScrollDuration = SCROLL_DURATION;
7285 }
7286 mTargetPos = clampedPosition;
7287 mBoundPos = INVALID_POSITION;
7288 mLastSeenPos = INVALID_POSITION;
7289
7290 postOnAnimation(this);
7291 }
7292
7293 @Override
7294 public void start(final int position, final int boundPosition) {
7295 stop();
7296
7297 if (boundPosition == INVALID_POSITION) {
7298 start(position);
7299 return;
7300 }
7301
7302 if (mDataChanged) {
7303 // Wait until we're back in a stable state to try this.
7304 mPositionScrollAfterLayout = new Runnable() {
7305 @Override public void run() {
7306 start(position, boundPosition);
7307 }
7308 };
7309 return;
7310 }
7311
7312 final int childCount = getChildCount();
7313 if (childCount == 0) {
7314 // Can't scroll without children.
7315 return;
7316 }
7317
7318 final int firstPos = mFirstPosition;
7319 final int lastPos = firstPos + childCount - 1;
7320
7321 int viewTravelCount;
7322 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7323 if (clampedPosition < firstPos) {
7324 final int boundPosFromLast = lastPos - boundPosition;
7325 if (boundPosFromLast < 1) {
7326 // Moving would shift our bound position off the screen. Abort.
7327 return;
7328 }
7329
7330 final int posTravel = firstPos - clampedPosition + 1;
7331 final int boundTravel = boundPosFromLast - 1;
7332 if (boundTravel < posTravel) {
7333 viewTravelCount = boundTravel;
7334 mMode = MOVE_UP_BOUND;
7335 } else {
7336 viewTravelCount = posTravel;
7337 mMode = MOVE_UP_POS;
7338 }
7339 } else if (clampedPosition > lastPos) {
7340 final int boundPosFromFirst = boundPosition - firstPos;
7341 if (boundPosFromFirst < 1) {
7342 // Moving would shift our bound position off the screen. Abort.
7343 return;
7344 }
7345
7346 final int posTravel = clampedPosition - lastPos + 1;
7347 final int boundTravel = boundPosFromFirst - 1;
7348 if (boundTravel < posTravel) {
7349 viewTravelCount = boundTravel;
7350 mMode = MOVE_DOWN_BOUND;
7351 } else {
7352 viewTravelCount = posTravel;
7353 mMode = MOVE_DOWN_POS;
7354 }
7355 } else {
7356 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7357 return;
7358 }
7359
7360 if (viewTravelCount > 0) {
7361 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7362 } else {
7363 mScrollDuration = SCROLL_DURATION;
7364 }
7365 mTargetPos = clampedPosition;
7366 mBoundPos = boundPosition;
7367 mLastSeenPos = INVALID_POSITION;
7368
7369 postOnAnimation(this);
7370 }
7371
7372 @Override
7373 public void startWithOffset(int position, int offset) {
7374 startWithOffset(position, offset, SCROLL_DURATION);
7375 }
7376
7377 @Override
7378 public void startWithOffset(final int position, int offset, final int duration) {
7379 stop();
7380
7381 if (mDataChanged) {
7382 // Wait until we're back in a stable state to try this.
7383 final int postOffset = offset;
7384 mPositionScrollAfterLayout = new Runnable() {
7385 @Override public void run() {
7386 startWithOffset(position, postOffset, duration);
7387 }
7388 };
7389 return;
7390 }
7391
7392 final int childCount = getChildCount();
7393 if (childCount == 0) {
7394 // Can't scroll without children.
7395 return;
7396 }
7397
7398 offset += getPaddingTop();
7399
7400 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7401 mOffsetFromTop = offset;
7402 mBoundPos = INVALID_POSITION;
7403 mLastSeenPos = INVALID_POSITION;
7404 mMode = MOVE_OFFSET;
7405
7406 final int firstPos = mFirstPosition;
7407 final int lastPos = firstPos + childCount - 1;
7408
7409 int viewTravelCount;
7410 if (mTargetPos < firstPos) {
7411 viewTravelCount = firstPos - mTargetPos;
7412 } else if (mTargetPos > lastPos) {
7413 viewTravelCount = mTargetPos - lastPos;
7414 } else {
7415 // On-screen, just scroll.
7416 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007417 smoothScrollBy(targetTop - offset, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007418 return;
7419 }
7420
7421 // Estimate how many screens we should travel
7422 final float screenTravelCount = (float) viewTravelCount / childCount;
7423 mScrollDuration = screenTravelCount < 1 ?
7424 duration : (int) (duration / screenTravelCount);
7425 mLastSeenPos = INVALID_POSITION;
7426
7427 postOnAnimation(this);
7428 }
7429
7430 /**
7431 * Scroll such that targetPos is in the visible padded region without scrolling
7432 * boundPos out of view. Assumes targetPos is onscreen.
7433 */
7434 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7435 final int firstPos = mFirstPosition;
7436 final int childCount = getChildCount();
7437 final int lastPos = firstPos + childCount - 1;
7438 final int paddedTop = mListPadding.top;
7439 final int paddedBottom = getHeight() - mListPadding.bottom;
7440
7441 if (targetPos < firstPos || targetPos > lastPos) {
7442 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7443 " not visible [" + firstPos + ", " + lastPos + "]");
7444 }
7445 if (boundPos < firstPos || boundPos > lastPos) {
7446 // boundPos doesn't matter, it's already offscreen.
7447 boundPos = INVALID_POSITION;
7448 }
7449
7450 final View targetChild = getChildAt(targetPos - firstPos);
7451 final int targetTop = targetChild.getTop();
7452 final int targetBottom = targetChild.getBottom();
7453 int scrollBy = 0;
7454
7455 if (targetBottom > paddedBottom) {
7456 scrollBy = targetBottom - paddedBottom;
7457 }
7458 if (targetTop < paddedTop) {
7459 scrollBy = targetTop - paddedTop;
7460 }
7461
7462 if (scrollBy == 0) {
7463 return;
7464 }
7465
7466 if (boundPos >= 0) {
7467 final View boundChild = getChildAt(boundPos - firstPos);
7468 final int boundTop = boundChild.getTop();
7469 final int boundBottom = boundChild.getBottom();
7470 final int absScroll = Math.abs(scrollBy);
7471
7472 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7473 // Don't scroll the bound view off the bottom of the screen.
7474 scrollBy = Math.max(0, boundBottom - paddedBottom);
7475 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7476 // Don't scroll the bound view off the top of the screen.
7477 scrollBy = Math.min(0, boundTop - paddedTop);
7478 }
7479 }
7480
7481 smoothScrollBy(scrollBy, duration);
7482 }
7483
7484 @Override
7485 public void stop() {
7486 removeCallbacks(this);
7487 }
7488
7489 @Override
7490 public void run() {
7491 final int listHeight = getHeight();
7492 final int firstPos = mFirstPosition;
7493
7494 switch (mMode) {
7495 case MOVE_DOWN_POS: {
7496 final int lastViewIndex = getChildCount() - 1;
7497 final int lastPos = firstPos + lastViewIndex;
7498
7499 if (lastViewIndex < 0) {
7500 return;
7501 }
7502
7503 if (lastPos == mLastSeenPos) {
7504 // No new views, let things keep going.
7505 postOnAnimation(this);
7506 return;
7507 }
7508
7509 final View lastView = getChildAt(lastViewIndex);
7510 final int lastViewHeight = lastView.getHeight();
7511 final int lastViewTop = lastView.getTop();
7512 final int lastViewPixelsShowing = listHeight - lastViewTop;
7513 final int extraScroll = lastPos < mItemCount - 1 ?
7514 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7515
7516 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007517 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007518
7519 mLastSeenPos = lastPos;
7520 if (lastPos < mTargetPos) {
7521 postOnAnimation(this);
7522 }
7523 break;
7524 }
7525
7526 case MOVE_DOWN_BOUND: {
7527 final int nextViewIndex = 1;
7528 final int childCount = getChildCount();
7529
7530 if (firstPos == mBoundPos || childCount <= nextViewIndex
7531 || firstPos + childCount >= mItemCount) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007532 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007533 return;
7534 }
7535 final int nextPos = firstPos + nextViewIndex;
7536
7537 if (nextPos == mLastSeenPos) {
7538 // No new views, let things keep going.
7539 postOnAnimation(this);
7540 return;
7541 }
7542
7543 final View nextView = getChildAt(nextViewIndex);
7544 final int nextViewHeight = nextView.getHeight();
7545 final int nextViewTop = nextView.getTop();
7546 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7547 if (nextPos < mBoundPos) {
7548 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007549 mScrollDuration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007550
7551 mLastSeenPos = nextPos;
7552
7553 postOnAnimation(this);
7554 } else {
7555 if (nextViewTop > extraScroll) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007556 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7557 } else {
7558 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007559 }
7560 }
7561 break;
7562 }
7563
7564 case MOVE_UP_POS: {
7565 if (firstPos == mLastSeenPos) {
7566 // No new views, let things keep going.
7567 postOnAnimation(this);
7568 return;
7569 }
7570
7571 final View firstView = getChildAt(0);
7572 if (firstView == null) {
7573 return;
7574 }
7575 final int firstViewTop = firstView.getTop();
7576 final int extraScroll = firstPos > 0 ?
7577 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7578
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007579 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7580 firstPos > mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007581
7582 mLastSeenPos = firstPos;
7583
7584 if (firstPos > mTargetPos) {
7585 postOnAnimation(this);
7586 }
7587 break;
7588 }
7589
7590 case MOVE_UP_BOUND: {
7591 final int lastViewIndex = getChildCount() - 2;
7592 if (lastViewIndex < 0) {
7593 return;
7594 }
7595 final int lastPos = firstPos + lastViewIndex;
7596
7597 if (lastPos == mLastSeenPos) {
7598 // No new views, let things keep going.
7599 postOnAnimation(this);
7600 return;
7601 }
7602
7603 final View lastView = getChildAt(lastViewIndex);
7604 final int lastViewHeight = lastView.getHeight();
7605 final int lastViewTop = lastView.getTop();
7606 final int lastViewPixelsShowing = listHeight - lastViewTop;
7607 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7608 mLastSeenPos = lastPos;
7609 if (lastPos > mBoundPos) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007610 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7611 true);
Alan Viveretted22db212014-02-13 17:47:38 -08007612 postOnAnimation(this);
7613 } else {
7614 final int bottom = listHeight - extraScroll;
7615 final int lastViewBottom = lastViewTop + lastViewHeight;
7616 if (bottom > lastViewBottom) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007617 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7618 } else {
7619 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007620 }
7621 }
7622 break;
7623 }
7624
7625 case MOVE_OFFSET: {
7626 if (mLastSeenPos == firstPos) {
7627 // No new views, let things keep going.
7628 postOnAnimation(this);
7629 return;
7630 }
7631
7632 mLastSeenPos = firstPos;
7633
7634 final int childCount = getChildCount();
7635 final int position = mTargetPos;
7636 final int lastPos = firstPos + childCount - 1;
7637
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007638 // Account for the visible "portion" of the first / last child when we estimate
7639 // how many screens we should travel to reach our target
7640 final View firstChild = getChildAt(0);
7641 final int firstChildHeight = firstChild.getHeight();
7642 final View lastChild = getChildAt(childCount - 1);
7643 final int lastChildHeight = lastChild.getHeight();
7644 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7645 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7646 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7647 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7648 / lastChildHeight;
7649
7650 float viewTravelCount = 0;
Alan Viveretted22db212014-02-13 17:47:38 -08007651 if (position < firstPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007652 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
Alan Viveretted22db212014-02-13 17:47:38 -08007653 } else if (position > lastPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007654 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
Alan Viveretted22db212014-02-13 17:47:38 -08007655 }
7656
7657 // Estimate how many screens we should travel
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007658 final float screenTravelCount = viewTravelCount / childCount;
Alan Viveretted22db212014-02-13 17:47:38 -08007659
7660 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7661 if (position < firstPos) {
7662 final int distance = (int) (-getHeight() * modifier);
7663 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007664 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007665 postOnAnimation(this);
7666 } else if (position > lastPos) {
7667 final int distance = (int) (getHeight() * modifier);
7668 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007669 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007670 postOnAnimation(this);
7671 } else {
7672 // On-screen, just scroll.
7673 final int targetTop = getChildAt(position - firstPos).getTop();
7674 final int distance = targetTop - mOffsetFromTop;
7675 final int duration = (int) (mScrollDuration *
7676 ((float) Math.abs(distance) / getHeight()));
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007677 smoothScrollBy(distance, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007678 }
7679 break;
7680 }
7681
7682 default:
7683 break;
7684 }
7685 }
7686 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007687}