blob: 6bee58f96f8a6f32dba00e1e792b356ac7d005d2 [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);
Evan Rosky837ae0d2017-10-26 12:50:33 -07003869 hideSelector();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003870
3871 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3872 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003873 mFlingRunnable.endFling();
3874 if (mPositionScroller != null) {
3875 mPositionScroller.stop();
3876 }
3877 mTouchMode = TOUCH_MODE_OVERSCROLL;
3878 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003879 mMotionY = (int) ev.getY();
3880 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003881 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003882 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003883 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003884 final int x = (int) ev.getX();
3885 final int y = (int) ev.getY();
3886 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003887
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003888 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003889 if (mTouchMode == TOUCH_MODE_FLING) {
3890 // Stopped a fling. It is a scroll.
3891 createScrollingCache();
3892 mTouchMode = TOUCH_MODE_SCROLL;
3893 mMotionCorrection = 0;
3894 motionPosition = findMotionRow(y);
3895 mFlingRunnable.flywheelTouch();
3896 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3897 // User clicked on an actual view (and was not stopping a
3898 // fling). It might be a click or a scroll. Assume it is a
3899 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003900 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003901
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003902 // FIXME Debounce
3903 if (mPendingCheckForTap == null) {
3904 mPendingCheckForTap = new CheckForTap();
3905 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003906
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003907 mPendingCheckForTap.x = ev.getX();
3908 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003909 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003910 }
3911 }
3912
3913 if (motionPosition >= 0) {
3914 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003915 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003916 mMotionViewOriginalTop = v.getTop();
3917 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003918
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003919 mMotionX = x;
3920 mMotionY = y;
3921 mMotionPosition = motionPosition;
3922 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003923 }
3924
Alan Viveretteb339cc52013-08-12 13:29:15 -07003925 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003926 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003927 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003928 }
3929 }
3930
Adam Powell96d62af2014-05-02 10:04:38 -07003931 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003932 if (mHasPerformedLongPress) {
3933 // Consume all move events following a successful long press.
3934 return;
3935 }
3936
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003937 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3938 if (pointerIndex == -1) {
3939 pointerIndex = 0;
3940 mActivePointerId = ev.getPointerId(pointerIndex);
3941 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003942
3943 if (mDataChanged) {
3944 // Re-sync everything if data has been changed
3945 // since the scroll operation can query the adapter.
3946 layoutChildren();
3947 }
3948
Alan Viverette8fa327a2013-05-31 14:53:13 -07003949 final int y = (int) ev.getY(pointerIndex);
3950
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003951 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003952 case TOUCH_MODE_DOWN:
3953 case TOUCH_MODE_TAP:
3954 case TOUCH_MODE_DONE_WAITING:
3955 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003956 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003957 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003958 break;
3959 }
3960 // Otherwise, check containment within list bounds. If we're
3961 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003962 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003963 final float x = ev.getX(pointerIndex);
3964 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003965 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003966 if (motionView != null) {
3967 motionView.setPressed(false);
3968 }
3969 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3970 mPendingCheckForTap : mPendingCheckForLongPress);
3971 mTouchMode = TOUCH_MODE_DONE_WAITING;
3972 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003973 } else if (motionView != null) {
3974 // Still within bounds, update the hotspot.
3975 final float[] point = mTmpPoint;
3976 point[0] = x;
3977 point[1] = y;
3978 transformPointToViewLocal(point, motionView);
3979 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003980 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003981 break;
3982 case TOUCH_MODE_SCROLL:
3983 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003984 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003985 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003986 }
3987 }
3988
3989 private void onTouchUp(MotionEvent ev) {
3990 switch (mTouchMode) {
3991 case TOUCH_MODE_DOWN:
3992 case TOUCH_MODE_TAP:
3993 case TOUCH_MODE_DONE_WAITING:
3994 final int motionPosition = mMotionPosition;
3995 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003996 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003997 if (mTouchMode != TOUCH_MODE_DOWN) {
3998 child.setPressed(false);
3999 }
4000
Alan Viverette74ded292013-06-03 15:34:11 -07004001 final float x = ev.getX();
4002 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
Adam Powell0f552f42017-02-03 11:50:42 -08004003 if (inList && !child.hasExplicitFocusable()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004004 if (mPerformClick == null) {
4005 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004006 }
Alan Viverette74ded292013-06-03 15:34:11 -07004007
4008 final AbsListView.PerformClick performClick = mPerformClick;
4009 performClick.mClickMotionPosition = motionPosition;
4010 performClick.rememberWindowAttachCount();
4011
4012 mResurrectToPosition = motionPosition;
4013
4014 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4015 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4016 mPendingCheckForTap : mPendingCheckForLongPress);
4017 mLayoutMode = LAYOUT_NORMAL;
4018 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4019 mTouchMode = TOUCH_MODE_TAP;
4020 setSelectedPositionInt(mMotionPosition);
4021 layoutChildren();
4022 child.setPressed(true);
4023 positionSelector(mMotionPosition, child);
4024 setPressed(true);
4025 if (mSelector != null) {
4026 Drawable d = mSelector.getCurrent();
4027 if (d != null && d instanceof TransitionDrawable) {
4028 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004029 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004030 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004031 }
Alan Viverette74ded292013-06-03 15:34:11 -07004032 if (mTouchModeReset != null) {
4033 removeCallbacks(mTouchModeReset);
4034 }
4035 mTouchModeReset = new Runnable() {
4036 @Override
4037 public void run() {
4038 mTouchModeReset = null;
4039 mTouchMode = TOUCH_MODE_REST;
4040 child.setPressed(false);
4041 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004042 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004043 performClick.run();
4044 }
4045 }
4046 };
4047 postDelayed(mTouchModeReset,
4048 ViewConfiguration.getPressedStateDuration());
4049 } else {
4050 mTouchMode = TOUCH_MODE_REST;
4051 updateSelectorState();
4052 }
4053 return;
4054 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4055 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004056 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004057 }
4058 }
4059 mTouchMode = TOUCH_MODE_REST;
4060 updateSelectorState();
4061 break;
4062 case TOUCH_MODE_SCROLL:
4063 final int childCount = getChildCount();
4064 if (childCount > 0) {
4065 final int firstChildTop = getChildAt(0).getTop();
4066 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4067 final int contentTop = mListPadding.top;
4068 final int contentBottom = getHeight() - mListPadding.bottom;
4069 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4070 mFirstPosition + childCount < mItemCount &&
4071 lastChildBottom <= getHeight() - contentBottom) {
4072 mTouchMode = TOUCH_MODE_REST;
4073 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4074 } else {
4075 final VelocityTracker velocityTracker = mVelocityTracker;
4076 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4077
4078 final int initialVelocity = (int)
4079 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4080 // Fling if we have enough velocity and we aren't at a boundary.
4081 // Since we can potentially overfling more than we can overscroll, don't
4082 // allow the weird behavior where you can scroll to a boundary then
4083 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004084 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4085 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004086 !((mFirstPosition == 0 &&
4087 firstChildTop == contentTop - mOverscrollDistance) ||
4088 (mFirstPosition + childCount == mItemCount &&
4089 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004090 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4091 if (mFlingRunnable == null) {
4092 mFlingRunnable = new FlingRunnable();
4093 }
4094 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4095 mFlingRunnable.start(-initialVelocity);
4096 dispatchNestedFling(0, -initialVelocity, true);
4097 } else {
4098 mTouchMode = TOUCH_MODE_REST;
4099 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004100 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004101 } else {
4102 mTouchMode = TOUCH_MODE_REST;
4103 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4104 if (mFlingRunnable != null) {
4105 mFlingRunnable.endFling();
4106 }
4107 if (mPositionScroller != null) {
4108 mPositionScroller.stop();
4109 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004110 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004111 dispatchNestedFling(0, -initialVelocity, false);
4112 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004113 }
4114 }
4115 } else {
4116 mTouchMode = TOUCH_MODE_REST;
4117 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4118 }
4119 break;
4120
4121 case TOUCH_MODE_OVERSCROLL:
4122 if (mFlingRunnable == null) {
4123 mFlingRunnable = new FlingRunnable();
4124 }
4125 final VelocityTracker velocityTracker = mVelocityTracker;
4126 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4127 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4128
4129 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4130 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4131 mFlingRunnable.startOverfling(-initialVelocity);
4132 } else {
4133 mFlingRunnable.startSpringback();
4134 }
4135
4136 break;
4137 }
4138
4139 setPressed(false);
4140
4141 if (mEdgeGlowTop != null) {
4142 mEdgeGlowTop.onRelease();
4143 mEdgeGlowBottom.onRelease();
4144 }
4145
4146 // Need to redraw since we probably aren't drawing the selector anymore
4147 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004148 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004149 recycleVelocityTracker();
4150
4151 mActivePointerId = INVALID_POINTER;
4152
4153 if (PROFILE_SCROLLING) {
4154 if (mScrollProfilingStarted) {
4155 Debug.stopMethodTracing();
4156 mScrollProfilingStarted = false;
4157 }
4158 }
4159
4160 if (mScrollStrictSpan != null) {
4161 mScrollStrictSpan.finish();
4162 mScrollStrictSpan = null;
4163 }
4164 }
4165
4166 private void onTouchCancel() {
4167 switch (mTouchMode) {
4168 case TOUCH_MODE_OVERSCROLL:
4169 if (mFlingRunnable == null) {
4170 mFlingRunnable = new FlingRunnable();
4171 }
4172 mFlingRunnable.startSpringback();
4173 break;
4174
4175 case TOUCH_MODE_OVERFLING:
4176 // Do nothing - let it play out.
4177 break;
4178
4179 default:
4180 mTouchMode = TOUCH_MODE_REST;
4181 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004182 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004183 if (motionView != null) {
4184 motionView.setPressed(false);
4185 }
4186 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004187 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004188 recycleVelocityTracker();
4189 }
4190
4191 if (mEdgeGlowTop != null) {
4192 mEdgeGlowTop.onRelease();
4193 mEdgeGlowBottom.onRelease();
4194 }
4195 mActivePointerId = INVALID_POINTER;
4196 }
4197
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004198 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004199 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4200 if (mScrollY != scrollY) {
4201 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4202 mScrollY = scrollY;
4203 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004204
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004205 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004206 }
Adam Powell637d3372010-08-25 14:37:03 -07004207 }
4208
4209 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004210 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004211 switch (event.getAction()) {
4212 case MotionEvent.ACTION_SCROLL:
4213 final float axisValue;
4214 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4215 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4216 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4217 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4218 } else {
4219 axisValue = 0;
4220 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004221
Aaron Whytef8306522017-03-22 16:30:58 -07004222 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -04004223 if (delta != 0) {
4224 if (!trackMotionScroll(delta, delta)) {
4225 return true;
4226 }
4227 }
4228 break;
4229 case MotionEvent.ACTION_BUTTON_PRESS:
4230 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004231 int actionButton = event.getActionButton();
4232 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4233 || actionButton == MotionEvent.BUTTON_SECONDARY)
4234 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4235 if (performStylusButtonPressAction(event)) {
4236 removeCallbacks(mPendingCheckForLongPress);
4237 removeCallbacks(mPendingCheckForTap);
4238 }
4239 }
Ned Burns20ad0732016-08-18 14:22:57 -04004240 }
4241 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004242 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004243
Jeff Brown33bbfd22011-02-24 20:55:35 -08004244 return super.onGenericMotionEvent(event);
4245 }
4246
Adam Powell4884c642014-08-07 13:52:53 -07004247 /**
4248 * Initiate a fling with the given velocity.
4249 *
4250 * <p>Applications can use this method to manually initiate a fling as if the user
4251 * initiated it via touch interaction.</p>
4252 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004253 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4254 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004255 */
4256 public void fling(int velocityY) {
4257 if (mFlingRunnable == null) {
4258 mFlingRunnable = new FlingRunnable();
4259 }
4260 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004261 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004262 }
4263
Jeff Brown33bbfd22011-02-24 20:55:35 -08004264 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004265 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4266 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4267 }
4268
4269 @Override
4270 public void onNestedScrollAccepted(View child, View target, int axes) {
4271 super.onNestedScrollAccepted(child, target, axes);
4272 startNestedScroll(SCROLL_AXIS_VERTICAL);
4273 }
4274
4275 @Override
4276 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4277 int dxUnconsumed, int dyUnconsumed) {
4278 final int motionIndex = getChildCount() / 2;
4279 final View motionView = getChildAt(motionIndex);
4280 final int oldTop = motionView != null ? motionView.getTop() : 0;
4281 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4282 int myUnconsumed = dyUnconsumed;
4283 int myConsumed = 0;
4284 if (motionView != null) {
4285 myConsumed = motionView.getTop() - oldTop;
4286 myUnconsumed -= myConsumed;
4287 }
4288 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4289 }
4290 }
4291
4292 @Override
4293 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4294 final int childCount = getChildCount();
4295 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4296 Math.abs(velocityY) > mMinimumVelocity) {
4297 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4298 if (mFlingRunnable == null) {
4299 mFlingRunnable = new FlingRunnable();
4300 }
Adam Powell9413b242014-08-06 17:34:24 -07004301 if (!dispatchNestedPreFling(0, velocityY)) {
4302 mFlingRunnable.start((int) velocityY);
4303 }
Adam Powell96d62af2014-05-02 10:04:38 -07004304 return true;
4305 }
4306 return dispatchNestedFling(velocityX, velocityY, consumed);
4307 }
4308
4309 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004310 public void draw(Canvas canvas) {
4311 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004312 if (mEdgeGlowTop != null) {
4313 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004314 final boolean clipToPadding = getClipToPadding();
4315 final int width;
4316 final int height;
4317 final int translateX;
4318 final int translateY;
4319
4320 if (clipToPadding) {
4321 width = getWidth() - mPaddingLeft - mPaddingRight;
4322 height = getHeight() - mPaddingTop - mPaddingBottom;
4323 translateX = mPaddingLeft;
4324 translateY = mPaddingTop;
4325 } else {
4326 width = getWidth();
4327 height = getHeight();
4328 translateX = 0;
4329 translateY = 0;
4330 }
Adam Powell637d3372010-08-25 14:37:03 -07004331 if (!mEdgeGlowTop.isFinished()) {
4332 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004333 canvas.clipRect(translateX, translateY,
4334 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4335 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4336 canvas.translate(translateX, edgeY);
4337 mEdgeGlowTop.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004338 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004339 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004340 }
4341 canvas.restoreToCount(restoreCount);
4342 }
4343 if (!mEdgeGlowBottom.isFinished()) {
4344 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004345 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4346 translateX + width, translateY + height);
4347 final int edgeX = -width + translateX;
4348 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4349 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004350 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004351 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08004352 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004353 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004354 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004355 }
4356 canvas.restoreToCount(restoreCount);
4357 }
4358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004359 }
4360
Michael Jurka13451a42011-08-22 15:54:21 -07004361 private void initOrResetVelocityTracker() {
4362 if (mVelocityTracker == null) {
4363 mVelocityTracker = VelocityTracker.obtain();
4364 } else {
4365 mVelocityTracker.clear();
4366 }
4367 }
4368
4369 private void initVelocityTrackerIfNotExists() {
4370 if (mVelocityTracker == null) {
4371 mVelocityTracker = VelocityTracker.obtain();
4372 }
4373 }
4374
4375 private void recycleVelocityTracker() {
4376 if (mVelocityTracker != null) {
4377 mVelocityTracker.recycle();
4378 mVelocityTracker = null;
4379 }
4380 }
4381
4382 @Override
4383 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4384 if (disallowIntercept) {
4385 recycleVelocityTracker();
4386 }
4387 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4388 }
4389
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004390 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004391 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004392 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004393 return true;
4394 }
4395
4396 return super.onInterceptHoverEvent(event);
4397 }
4398
4399 @Override
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08004400 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4401 if (mFastScroll != null) {
4402 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4403 if (pointerIcon != null) {
4404 return pointerIcon;
4405 }
4406 }
4407 return super.onResolvePointerIcon(event, pointerIndex);
4408 }
4409
4410 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004411 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004412 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004413 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004414
Adam Powell1fa179ef2012-04-12 15:01:40 -07004415 if (mPositionScroller != null) {
4416 mPositionScroller.stop();
4417 }
4418
Alan Viverette462c2172014-02-24 12:24:11 -08004419 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004420 // Something isn't right.
4421 // Since we rely on being attached to get data set change notifications,
4422 // don't risk doing anything where we might try to resync and find things
4423 // in a bogus state.
4424 return false;
4425 }
4426
Alan Viverette8636ace2013-10-31 15:41:31 -07004427 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004428 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004429 }
Romain Guy0a637162009-05-29 14:43:54 -07004430
Adam Powell744beff2014-09-22 09:47:48 -07004431 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004432 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004433 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004434 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4435 mMotionCorrection = 0;
4436 return true;
4437 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004438
Adam Powell4cd47702010-02-25 11:21:14 -08004439 final int x = (int) ev.getX();
4440 final int y = (int) ev.getY();
4441 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004442
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004443 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004444 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004445 // User clicked on an actual view (and was not stopping a fling).
4446 // Remember where the motion event started
4447 v = getChildAt(motionPosition - mFirstPosition);
4448 mMotionViewOriginalTop = v.getTop();
4449 mMotionX = x;
4450 mMotionY = y;
4451 mMotionPosition = motionPosition;
4452 mTouchMode = TOUCH_MODE_DOWN;
4453 clearScrollingCache();
4454 }
4455 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004456 initOrResetVelocityTracker();
4457 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004458 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004459 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004460 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004461 return true;
4462 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004463 break;
4464 }
4465
4466 case MotionEvent.ACTION_MOVE: {
4467 switch (mTouchMode) {
4468 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004469 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4470 if (pointerIndex == -1) {
4471 pointerIndex = 0;
4472 mActivePointerId = ev.getPointerId(pointerIndex);
4473 }
Adam Powell4cd47702010-02-25 11:21:14 -08004474 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004475 initVelocityTrackerIfNotExists();
4476 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004477 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004478 return true;
4479 }
4480 break;
4481 }
4482 break;
4483 }
4484
Michael Jurka13451a42011-08-22 15:54:21 -07004485 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004486 case MotionEvent.ACTION_UP: {
4487 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004488 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004489 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004490 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004491 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004492 break;
4493 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004494
Adam Powell4cd47702010-02-25 11:21:14 -08004495 case MotionEvent.ACTION_POINTER_UP: {
4496 onSecondaryPointerUp(ev);
4497 break;
4498 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004499 }
4500
4501 return false;
4502 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004503
Adam Powell4cd47702010-02-25 11:21:14 -08004504 private void onSecondaryPointerUp(MotionEvent ev) {
4505 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4506 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4507 final int pointerId = ev.getPointerId(pointerIndex);
4508 if (pointerId == mActivePointerId) {
4509 // This was our active pointer going up. Choose a new
4510 // active pointer and adjust accordingly.
4511 // TODO: Make this decision more intelligent.
4512 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4513 mMotionX = (int) ev.getX(newPointerIndex);
4514 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004515 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004516 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004517 }
4518 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004519
4520 /**
4521 * {@inheritDoc}
4522 */
4523 @Override
4524 public void addTouchables(ArrayList<View> views) {
4525 final int count = getChildCount();
4526 final int firstPosition = mFirstPosition;
4527 final ListAdapter adapter = mAdapter;
4528
4529 if (adapter == null) {
4530 return;
4531 }
4532
4533 for (int i = 0; i < count; i++) {
4534 final View child = getChildAt(i);
4535 if (adapter.isEnabled(firstPosition + i)) {
4536 views.add(child);
4537 }
4538 child.addTouchables(views);
4539 }
4540 }
4541
4542 /**
4543 * Fires an "on scroll state changed" event to the registered
4544 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4545 * is fired only if the specified state is different from the previously known state.
4546 *
4547 * @param newState The new scroll state.
4548 */
4549 void reportScrollStateChange(int newState) {
4550 if (newState != mLastScrollState) {
4551 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004552 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004553 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004554 }
4555 }
4556 }
4557
4558 /**
4559 * Responsible for fling behavior. Use {@link #start(int)} to
4560 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4561 * A FlingRunnable will keep re-posting itself until the fling is done.
4562 *
4563 */
4564 private class FlingRunnable implements Runnable {
4565 /**
4566 * Tracks the decay of a fling scroll
4567 */
Adam Powell637d3372010-08-25 14:37:03 -07004568 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004569
4570 /**
4571 * Y value reported by mScroller on the previous fling
4572 */
4573 private int mLastFlingY;
4574
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004575 /**
4576 * If true, {@link #endFling()} will not report scroll state change to
4577 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4578 */
4579 private boolean mSuppressIdleStateChangeCall;
4580
Gilles Debunned348bb42010-11-15 12:19:35 -08004581 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004582 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004583 public void run() {
4584 final int activeId = mActivePointerId;
4585 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004586 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004587 if (vt == null || activeId == INVALID_POINTER) {
4588 return;
4589 }
4590
4591 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4592 final float yvel = -vt.getYVelocity(activeId);
4593
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004594 if (Math.abs(yvel) >= mMinimumVelocity
4595 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004596 // Keep the fling alive a little longer
4597 postDelayed(this, FLYWHEEL_TIMEOUT);
4598 } else {
4599 endFling();
4600 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004601 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004602 }
4603 }
4604 };
4605
4606 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4607
Adam Powell79ac3392010-01-28 21:22:20 -08004608 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004609 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004610 }
4611
Adam Powell79ac3392010-01-28 21:22:20 -08004612 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004613 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4614 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004615 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004616 mScroller.fling(0, initialY, 0, initialVelocity,
4617 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4618 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004619 mSuppressIdleStateChangeCall = false;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004620 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004621
4622 if (PROFILE_FLINGING) {
4623 if (!mFlingProfilingStarted) {
4624 Debug.startMethodTracing("AbsListViewFling");
4625 mFlingProfilingStarted = true;
4626 }
4627 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004628
4629 if (mFlingStrictSpan == null) {
4630 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4631 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004632 }
Adam Powell45803472010-01-25 15:10:44 -08004633
Adam Powell637d3372010-08-25 14:37:03 -07004634 void startSpringback() {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004635 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004636 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4637 mTouchMode = TOUCH_MODE_OVERFLING;
4638 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004639 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004640 } else {
4641 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004642 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004643 }
4644 }
4645
4646 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004647 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004648 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4649 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004650 mTouchMode = TOUCH_MODE_OVERFLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004651 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004652 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004653 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004654 }
4655
4656 void edgeReached(int delta) {
4657 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4658 final int overscrollMode = getOverScrollMode();
4659 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4660 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4661 mTouchMode = TOUCH_MODE_OVERFLING;
4662 final int vel = (int) mScroller.getCurrVelocity();
4663 if (delta > 0) {
4664 mEdgeGlowTop.onAbsorb(vel);
4665 } else {
4666 mEdgeGlowBottom.onAbsorb(vel);
4667 }
Adam Powell40322522011-01-12 21:58:20 -08004668 } else {
4669 mTouchMode = TOUCH_MODE_REST;
4670 if (mPositionScroller != null) {
4671 mPositionScroller.stop();
4672 }
Adam Powell637d3372010-08-25 14:37:03 -07004673 }
4674 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004675 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004676 }
4677
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004678 void startScroll(int distance, int duration, boolean linear,
4679 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004680 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4681 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004682 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004683 mScroller.startScroll(0, initialY, 0, distance, duration);
4684 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004685 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004686 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004687 }
4688
Gilles Debunned348bb42010-11-15 12:19:35 -08004689 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004690 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004691
Adam Powell79ac3392010-01-28 21:22:20 -08004692 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004693 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004694
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004695 if (!mSuppressIdleStateChangeCall) {
4696 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4697 }
Romain Guy21317d12010-10-12 13:32:31 -07004698 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004699 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004700
4701 if (mFlingStrictSpan != null) {
4702 mFlingStrictSpan.finish();
4703 mFlingStrictSpan = null;
4704 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004705 }
4706
4707 void flywheelTouch() {
4708 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004709 }
4710
Alan Viverette8fa327a2013-05-31 14:53:13 -07004711 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004712 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004713 switch (mTouchMode) {
4714 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004715 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004716 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004717
Gilles Debunned348bb42010-11-15 12:19:35 -08004718 case TOUCH_MODE_SCROLL:
4719 if (mScroller.isFinished()) {
4720 return;
4721 }
4722 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004723 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004724 if (mDataChanged) {
4725 layoutChildren();
4726 }
4727
Adam Powell79ac3392010-01-28 21:22:20 -08004728 if (mItemCount == 0 || getChildCount() == 0) {
4729 endFling();
4730 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004731 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004732
Adam Powell637d3372010-08-25 14:37:03 -07004733 final OverScroller scroller = mScroller;
4734 boolean more = scroller.computeScrollOffset();
4735 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004736
Adam Powell637d3372010-08-25 14:37:03 -07004737 // Flip sign to convert finger direction to list items direction
4738 // (e.g. finger moving down means list is moving towards the top)
4739 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004740
Adam Powell637d3372010-08-25 14:37:03 -07004741 // Pretend that each frame of a fling scroll is a touch scroll
4742 if (delta > 0) {
4743 // List is moving towards the top. Use first view as mMotionPosition
4744 mMotionPosition = mFirstPosition;
4745 final View firstView = getChildAt(0);
4746 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004747
Adam Powell637d3372010-08-25 14:37:03 -07004748 // Don't fling more than 1 screen
4749 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4750 } else {
4751 // List is moving towards the bottom. Use last view as mMotionPosition
4752 int offsetToLast = getChildCount() - 1;
4753 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004754
Adam Powell637d3372010-08-25 14:37:03 -07004755 final View lastView = getChildAt(offsetToLast);
4756 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004757
Adam Powell637d3372010-08-25 14:37:03 -07004758 // Don't fling more than 1 screen
4759 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4760 }
Adam Powell79ac3392010-01-28 21:22:20 -08004761
Adam Powell637d3372010-08-25 14:37:03 -07004762 // Check to see if we have bumped into the scroll limit
4763 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4764 int oldTop = 0;
4765 if (motionView != null) {
4766 oldTop = motionView.getTop();
4767 }
Adam Powell9d32d242010-03-29 16:02:07 -07004768
Adam Powell637d3372010-08-25 14:37:03 -07004769 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004770 final boolean atEdge = trackMotionScroll(delta, delta);
4771 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004772 if (atEnd) {
4773 if (motionView != null) {
4774 // Tweak the scroll for how far we overshot
4775 int overshoot = -(delta - (motionView.getTop() - oldTop));
4776 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4777 0, mOverflingDistance, false);
4778 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004779 if (more) {
4780 edgeReached(delta);
4781 }
Adam Powell637d3372010-08-25 14:37:03 -07004782 break;
4783 }
4784
4785 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004786 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004787 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004788 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004789 } else {
4790 endFling();
4791
4792 if (PROFILE_FLINGING) {
4793 if (mFlingProfilingStarted) {
4794 Debug.stopMethodTracing();
4795 mFlingProfilingStarted = false;
4796 }
4797
4798 if (mFlingStrictSpan != null) {
4799 mFlingStrictSpan.finish();
4800 mFlingStrictSpan = null;
4801 }
Adam Powell79ac3392010-01-28 21:22:20 -08004802 }
4803 }
Adam Powell637d3372010-08-25 14:37:03 -07004804 break;
4805 }
4806
4807 case TOUCH_MODE_OVERFLING: {
4808 final OverScroller scroller = mScroller;
4809 if (scroller.computeScrollOffset()) {
4810 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004811 final int currY = scroller.getCurrY();
4812 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004813 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4814 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004815 final boolean crossDown = scrollY <= 0 && currY > 0;
4816 final boolean crossUp = scrollY >= 0 && currY < 0;
4817 if (crossDown || crossUp) {
4818 int velocity = (int) scroller.getCurrVelocity();
4819 if (crossUp) velocity = -velocity;
4820
4821 // Don't flywheel from this; we're just continuing things.
4822 scroller.abortAnimation();
4823 start(velocity);
4824 } else {
4825 startSpringback();
4826 }
Adam Powell637d3372010-08-25 14:37:03 -07004827 } else {
4828 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004829 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004830 }
4831 } else {
4832 endFling();
4833 }
4834 break;
4835 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004836 }
4837 }
4838 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004839
Adam Powell45803472010-01-25 15:10:44 -08004840 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004841 * The amount of friction applied to flings. The default value
4842 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004843 */
4844 public void setFriction(float friction) {
4845 if (mFlingRunnable == null) {
4846 mFlingRunnable = new FlingRunnable();
4847 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004848 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004849 }
Romain Guy21317d12010-10-12 13:32:31 -07004850
4851 /**
4852 * Sets a scale factor for the fling velocity. The initial scale
4853 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004854 *
Romain Guy21317d12010-10-12 13:32:31 -07004855 * @param scale The scale factor to multiply the velocity by.
4856 */
4857 public void setVelocityScale(float scale) {
4858 mVelocityScale = scale;
4859 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004860
Romain Guy4bede9e2010-10-11 19:36:59 -07004861 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004862 * Override this for better control over position scrolling.
4863 */
4864 AbsPositionScroller createPositionScroller() {
4865 return new PositionScroller();
4866 }
4867
4868 /**
Adam Powell45803472010-01-25 15:10:44 -08004869 * Smoothly scroll to the specified adapter position. The view will
4870 * scroll such that the indicated position is displayed.
4871 * @param position Scroll to this adapter position.
4872 */
4873 public void smoothScrollToPosition(int position) {
4874 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004875 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004876 }
4877 mPositionScroller.start(position);
4878 }
Erik322171b2010-10-13 15:46:00 -07004879
4880 /**
4881 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004882 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004883 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4884 * the first or last item beyond the boundaries of the list) it will get as close
4885 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4886 *
4887 * @param position Position to scroll to
4888 * @param offset Desired distance in pixels of <code>position</code> from the top
4889 * of the view when scrolling is finished
4890 * @param duration Number of milliseconds to use for the scroll
4891 */
4892 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4893 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004894 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004895 }
4896 mPositionScroller.startWithOffset(position, offset, duration);
4897 }
4898
Adam Powell45803472010-01-25 15:10:44 -08004899 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004900 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004901 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004902 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4903 * the first or last item beyond the boundaries of the list) it will get as close
4904 * as possible.
4905 *
4906 * @param position Position to scroll to
4907 * @param offset Desired distance in pixels of <code>position</code> from the top
4908 * of the view when scrolling is finished
4909 */
4910 public void smoothScrollToPositionFromTop(int position, int offset) {
4911 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004912 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004913 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004914 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004915 }
4916
4917 /**
Adam Powell45803472010-01-25 15:10:44 -08004918 * Smoothly scroll to the specified adapter position. The view will
4919 * scroll such that the indicated position is displayed, but it will
4920 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004921 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004922 *
Adam Powell45803472010-01-25 15:10:44 -08004923 * @param position Scroll to this adapter position.
4924 * @param boundPosition Do not scroll if it would move this adapter
4925 * position out of view.
4926 */
4927 public void smoothScrollToPosition(int position, int boundPosition) {
4928 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004929 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004930 }
4931 mPositionScroller.start(position, boundPosition);
4932 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004933
Adam Powell45803472010-01-25 15:10:44 -08004934 /**
4935 * Smoothly scroll by distance pixels over duration milliseconds.
4936 * @param distance Distance to scroll in pixels.
4937 * @param duration Duration of the scroll animation in milliseconds.
4938 */
4939 public void smoothScrollBy(int distance, int duration) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004940 smoothScrollBy(distance, duration, false, false);
Adam Powell0b8acd82012-04-25 20:29:23 -07004941 }
4942
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004943 void smoothScrollBy(int distance, int duration, boolean linear,
4944 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004945 if (mFlingRunnable == null) {
4946 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004947 }
Adam Powell40322522011-01-12 21:58:20 -08004948
Marc Blank299acb52010-10-21 11:03:53 -07004949 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004950 final int firstPos = mFirstPosition;
4951 final int childCount = getChildCount();
4952 final int lastPos = firstPos + childCount;
4953 final int topLimit = getPaddingTop();
4954 final int bottomLimit = getHeight() - getPaddingBottom();
4955
Adam Powell79303752011-01-13 22:06:49 -08004956 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004957 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004958 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004959 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4960 mFlingRunnable.endFling();
4961 if (mPositionScroller != null) {
4962 mPositionScroller.stop();
4963 }
4964 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004965 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004966 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
Marc Blank299acb52010-10-21 11:03:53 -07004967 }
Adam Powell45803472010-01-25 15:10:44 -08004968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004969
Winson Chung499cb9f2010-07-16 11:18:17 -07004970 /**
4971 * Allows RemoteViews to scroll relatively to a position.
4972 */
4973 void smoothScrollByOffset(int position) {
4974 int index = -1;
4975 if (position < 0) {
4976 index = getFirstVisiblePosition();
4977 } else if (position > 0) {
4978 index = getLastVisiblePosition();
4979 }
4980
4981 if (index > -1) {
4982 View child = getChildAt(index - getFirstVisiblePosition());
4983 if (child != null) {
4984 Rect visibleRect = new Rect();
4985 if (child.getGlobalVisibleRect(visibleRect)) {
4986 // the child is partially visible
4987 int childRectArea = child.getWidth() * child.getHeight();
4988 int visibleRectArea = visibleRect.width() * visibleRect.height();
4989 float visibleArea = (visibleRectArea / (float) childRectArea);
4990 final float visibleThreshold = 0.75f;
4991 if ((position < 0) && (visibleArea < visibleThreshold)) {
4992 // the top index is not perceivably visible so offset
4993 // to account for showing that top index as well
4994 ++index;
4995 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4996 // the bottom index is not perceivably visible so offset
4997 // to account for showing that bottom index as well
4998 --index;
4999 }
5000 }
5001 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5002 }
5003 }
5004 }
5005
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005006 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005007 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005008 setChildrenDrawnWithCacheEnabled(true);
5009 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08005010 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005011 }
5012 }
5013
5014 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005015 if (!isHardwareAccelerated()) {
5016 if (mClearScrollingCache == null) {
5017 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07005018 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07005019 public void run() {
5020 if (mCachingStarted) {
5021 mCachingStarted = mCachingActive = false;
5022 setChildrenDrawnWithCacheEnabled(false);
5023 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5024 setChildrenDrawingCacheEnabled(false);
5025 }
5026 if (!isAlwaysDrawnWithCacheEnabled()) {
5027 invalidate();
5028 }
Romain Guy6dfed242009-05-11 18:25:05 -07005029 }
5030 }
Romain Guy9d849a22012-03-14 16:41:42 -07005031 };
5032 }
5033 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005034 }
5035 }
5036
5037 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005038 * Scrolls the list items within the view by a specified number of pixels.
5039 *
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005040 * <p>The actual amount of scroll is capped by the list content viewport height
5041 * which is the list height minus top and bottom paddings minus one pixel.</p>
5042 *
Alan Viverette2f3317a2013-08-06 18:19:48 -07005043 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005044 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005045 */
Alan Viveretteba299062013-09-03 16:01:51 -07005046 public void scrollListBy(int y) {
5047 trackMotionScroll(-y, -y);
5048 }
5049
5050 /**
5051 * Check if the items in the list can be scrolled in a certain direction.
5052 *
5053 * @param direction Negative to check scrolling up, positive to check
5054 * scrolling down.
5055 * @return true if the list can be scrolled in the specified direction,
5056 * false otherwise.
5057 * @see #scrollListBy(int)
5058 */
5059 public boolean canScrollList(int direction) {
5060 final int childCount = getChildCount();
5061 if (childCount == 0) {
5062 return false;
5063 }
5064
5065 final int firstPosition = mFirstPosition;
5066 final Rect listPadding = mListPadding;
5067 if (direction > 0) {
5068 final int lastBottom = getChildAt(childCount - 1).getBottom();
5069 final int lastPosition = firstPosition + childCount;
5070 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5071 } else {
5072 final int firstTop = getChildAt(0).getTop();
5073 return firstPosition > 0 || firstTop < listPadding.top;
5074 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005075 }
5076
5077 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005078 * Track a motion scroll
5079 *
5080 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5081 * began. Positive numbers mean the user's finger is moving down the screen.
5082 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005083 * @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 -08005084 */
Adam Powell45803472010-01-25 15:10:44 -08005085 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005086 final int childCount = getChildCount();
5087 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005088 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005089 }
5090
5091 final int firstTop = getChildAt(0).getTop();
5092 final int lastBottom = getChildAt(childCount - 1).getBottom();
5093
5094 final Rect listPadding = mListPadding;
5095
Adam Powellbdccc2d2010-12-14 17:34:27 -08005096 // "effective padding" In this case is the amount of padding that affects
5097 // how much space should not be filled by items. If we don't clip to padding
5098 // there is no effective padding.
5099 int effectivePaddingTop = 0;
5100 int effectivePaddingBottom = 0;
5101 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5102 effectivePaddingTop = listPadding.top;
5103 effectivePaddingBottom = listPadding.bottom;
5104 }
5105
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005106 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005107 final int spaceAbove = effectivePaddingTop - firstTop;
5108 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005109 final int spaceBelow = lastBottom - end;
5110
5111 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5112 if (deltaY < 0) {
5113 deltaY = Math.max(-(height - 1), deltaY);
5114 } else {
5115 deltaY = Math.min(height - 1, deltaY);
5116 }
5117
5118 if (incrementalDeltaY < 0) {
5119 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5120 } else {
5121 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5122 }
5123
Adam Powell45803472010-01-25 15:10:44 -08005124 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005125
Adam Powell637d3372010-08-25 14:37:03 -07005126 // Update our guesses for where the first and last views are
5127 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005128 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005129 } else {
5130 mFirstPositionDistanceGuess += incrementalDeltaY;
5131 }
5132 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005133 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005134 } else {
5135 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005136 }
Adam Powell45803472010-01-25 15:10:44 -08005137
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005138 final boolean cannotScrollDown = (firstPosition == 0 &&
5139 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5140 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5141 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005142
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005143 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005144 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005145 }
5146
5147 final boolean down = incrementalDeltaY < 0;
5148
Adam Powell029cfbd2010-03-08 19:03:54 -08005149 final boolean inTouchMode = isInTouchMode();
5150 if (inTouchMode) {
5151 hideSelector();
5152 }
Adam Powell45803472010-01-25 15:10:44 -08005153
5154 final int headerViewsCount = getHeaderViewsCount();
5155 final int footerViewsStart = mItemCount - getFooterViewsCount();
5156
5157 int start = 0;
5158 int count = 0;
5159
5160 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005161 int top = -incrementalDeltaY;
5162 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5163 top += listPadding.top;
5164 }
Adam Powell45803472010-01-25 15:10:44 -08005165 for (int i = 0; i < childCount; i++) {
5166 final View child = getChildAt(i);
5167 if (child.getBottom() >= top) {
5168 break;
5169 } else {
5170 count++;
5171 int position = firstPosition + i;
5172 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005173 // The view will be rebound to new data, clear any
5174 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005175 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005176 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005177 }
5178 }
5179 }
5180 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005181 int bottom = getHeight() - incrementalDeltaY;
5182 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5183 bottom -= listPadding.bottom;
5184 }
Adam Powell45803472010-01-25 15:10:44 -08005185 for (int i = childCount - 1; i >= 0; i--) {
5186 final View child = getChildAt(i);
5187 if (child.getTop() <= bottom) {
5188 break;
5189 } else {
5190 start = i;
5191 count++;
5192 int position = firstPosition + i;
5193 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005194 // The view will be rebound to new data, clear any
5195 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005196 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005197 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005198 }
5199 }
5200 }
5201 }
5202
5203 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5204
5205 mBlockLayoutRequests = true;
5206
5207 if (count > 0) {
5208 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005209 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005210 }
Adam Powell539ee872012-02-03 19:00:49 -08005211
Romain Guy9d849a22012-03-14 16:41:42 -07005212 // invalidate before moving the children to avoid unnecessary invalidate
5213 // calls to bubble up from the children all the way to the top
5214 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005215 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005216 }
5217
Adam Powell45803472010-01-25 15:10:44 -08005218 offsetChildrenTopAndBottom(incrementalDeltaY);
5219
5220 if (down) {
5221 mFirstPosition += count;
5222 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005223
Adam Powell45803472010-01-25 15:10:44 -08005224 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5225 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5226 fillGap(down);
5227 }
5228
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005229 mRecycler.fullyDetachScrapViews();
Evan Rosky837ae0d2017-10-26 12:50:33 -07005230 boolean selectorOnScreen = false;
Adam Powell029cfbd2010-03-08 19:03:54 -08005231 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005232 final int childIndex = mSelectedPosition - mFirstPosition;
5233 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005234 positionSelector(mSelectedPosition, getChildAt(childIndex));
Evan Rosky837ae0d2017-10-26 12:50:33 -07005235 selectorOnScreen = true;
Adam Powell2a20ddd2010-03-11 18:09:59 -08005236 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005237 } else if (mSelectorPosition != INVALID_POSITION) {
5238 final int childIndex = mSelectorPosition - mFirstPosition;
5239 if (childIndex >= 0 && childIndex < getChildCount()) {
Evan Rosky837ae0d2017-10-26 12:50:33 -07005240 positionSelector(mSelectorPosition, getChildAt(childIndex));
5241 selectorOnScreen = true;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005242 }
Evan Rosky837ae0d2017-10-26 12:50:33 -07005243 }
5244 if (!selectorOnScreen) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005245 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005246 }
5247
Adam Powell45803472010-01-25 15:10:44 -08005248 mBlockLayoutRequests = false;
5249
5250 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005251
Adam Powell45803472010-01-25 15:10:44 -08005252 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005253 }
5254
5255 /**
5256 * Returns the number of header views in the list. Header views are special views
5257 * at the top of the list that should not be recycled during a layout.
5258 *
5259 * @return The number of header views, 0 in the default implementation.
5260 */
5261 int getHeaderViewsCount() {
5262 return 0;
5263 }
5264
5265 /**
5266 * Returns the number of footer views in the list. Footer views are special views
5267 * at the bottom of the list that should not be recycled during a layout.
5268 *
5269 * @return The number of footer views, 0 in the default implementation.
5270 */
5271 int getFooterViewsCount() {
5272 return 0;
5273 }
5274
5275 /**
5276 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5277 * remain on screen are shifted and the other ones are discarded. The role of this
5278 * method is to fill the gap thus created by performing a partial layout in the
5279 * empty space.
5280 *
5281 * @param down true if the scroll is going down, false if it is going up
5282 */
5283 abstract void fillGap(boolean down);
5284
5285 void hideSelector() {
5286 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005287 if (mLayoutMode != LAYOUT_SPECIFIC) {
5288 mResurrectToPosition = mSelectedPosition;
5289 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005290 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5291 mResurrectToPosition = mNextSelectedPosition;
5292 }
5293 setSelectedPositionInt(INVALID_POSITION);
5294 setNextSelectedPositionInt(INVALID_POSITION);
5295 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005296 }
5297 }
5298
5299 /**
5300 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5301 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5302 * of items available in the adapter
5303 */
5304 int reconcileSelectedPosition() {
5305 int position = mSelectedPosition;
5306 if (position < 0) {
5307 position = mResurrectToPosition;
5308 }
5309 position = Math.max(0, position);
5310 position = Math.min(position, mItemCount - 1);
5311 return position;
5312 }
5313
5314 /**
5315 * Find the row closest to y. This row will be used as the motion row when scrolling
5316 *
5317 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005318 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005319 */
5320 abstract int findMotionRow(int y);
5321
5322 /**
Adam Powell637d3372010-08-25 14:37:03 -07005323 * Find the row closest to y. This row will be used as the motion row when scrolling.
5324 *
5325 * @param y Where the user touched
5326 * @return The position of the first (or only) item in the row closest to y
5327 */
5328 int findClosestMotionRow(int y) {
5329 final int childCount = getChildCount();
5330 if (childCount == 0) {
5331 return INVALID_POSITION;
5332 }
5333
5334 final int motionRow = findMotionRow(y);
5335 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5336 }
5337
5338 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005339 * Causes all the views to be rebuilt and redrawn.
5340 */
5341 public void invalidateViews() {
5342 mDataChanged = true;
5343 rememberSyncState();
5344 requestLayout();
5345 invalidate();
5346 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005347
Jeff Brown4e6319b2010-12-13 10:36:51 -08005348 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005349 * If there is a selection returns false.
5350 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005351 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005352 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005353 if (mSelectedPosition < 0 && resurrectSelection()) {
5354 updateSelectorState();
5355 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005356 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005357 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005359
5360 /**
5361 * Makes the item at the supplied position selected.
5362 *
5363 * @param position the position of the new selection
5364 */
5365 abstract void setSelectionInt(int position);
5366
5367 /**
5368 * Attempt to bring the selection back if the user is switching from touch
5369 * to trackball mode
5370 * @return Whether selection was set to something.
5371 */
5372 boolean resurrectSelection() {
5373 final int childCount = getChildCount();
5374
5375 if (childCount <= 0) {
5376 return false;
5377 }
5378
5379 int selectedTop = 0;
5380 int selectedPos;
5381 int childrenTop = mListPadding.top;
5382 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5383 final int firstPosition = mFirstPosition;
5384 final int toPosition = mResurrectToPosition;
5385 boolean down = true;
5386
5387 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5388 selectedPos = toPosition;
5389
5390 final View selected = getChildAt(selectedPos - mFirstPosition);
5391 selectedTop = selected.getTop();
5392 int selectedBottom = selected.getBottom();
5393
5394 // We are scrolled, don't get in the fade
5395 if (selectedTop < childrenTop) {
5396 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5397 } else if (selectedBottom > childrenBottom) {
5398 selectedTop = childrenBottom - selected.getMeasuredHeight()
5399 - getVerticalFadingEdgeLength();
5400 }
5401 } else {
5402 if (toPosition < firstPosition) {
5403 // Default to selecting whatever is first
5404 selectedPos = firstPosition;
5405 for (int i = 0; i < childCount; i++) {
5406 final View v = getChildAt(i);
5407 final int top = v.getTop();
5408
5409 if (i == 0) {
5410 // Remember the position of the first item
5411 selectedTop = top;
5412 // See if we are scrolled at all
5413 if (firstPosition > 0 || top < childrenTop) {
5414 // If we are scrolled, don't select anything that is
5415 // in the fade region
5416 childrenTop += getVerticalFadingEdgeLength();
5417 }
5418 }
5419 if (top >= childrenTop) {
5420 // Found a view whose top is fully visisble
5421 selectedPos = firstPosition + i;
5422 selectedTop = top;
5423 break;
5424 }
5425 }
5426 } else {
5427 final int itemCount = mItemCount;
5428 down = false;
5429 selectedPos = firstPosition + childCount - 1;
5430
5431 for (int i = childCount - 1; i >= 0; i--) {
5432 final View v = getChildAt(i);
5433 final int top = v.getTop();
5434 final int bottom = v.getBottom();
5435
5436 if (i == childCount - 1) {
5437 selectedTop = top;
5438 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5439 childrenBottom -= getVerticalFadingEdgeLength();
5440 }
5441 }
5442
5443 if (bottom <= childrenBottom) {
5444 selectedPos = firstPosition + i;
5445 selectedTop = top;
5446 break;
5447 }
5448 }
5449 }
5450 }
5451
5452 mResurrectToPosition = INVALID_POSITION;
5453 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005454 if (mPositionScroller != null) {
5455 mPositionScroller.stop();
5456 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005457 mTouchMode = TOUCH_MODE_REST;
5458 clearScrollingCache();
5459 mSpecificTop = selectedTop;
5460 selectedPos = lookForSelectablePosition(selectedPos, down);
5461 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5462 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005463 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005464 setSelectionInt(selectedPos);
5465 invokeOnItemScrollListener();
5466 } else {
5467 selectedPos = INVALID_POSITION;
5468 }
5469 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5470
5471 return selectedPos >= 0;
5472 }
5473
Adam Powell14c08042011-10-06 19:46:18 -07005474 void confirmCheckedPositionsById() {
5475 // Clear out the positional check states, we'll rebuild it below from IDs.
5476 mCheckStates.clear();
5477
5478 boolean checkedCountChanged = false;
5479 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5480 final long id = mCheckedIdStates.keyAt(checkedIndex);
5481 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5482
5483 final long lastPosId = mAdapter.getItemId(lastPos);
5484 if (id != lastPosId) {
5485 // Look around to see if the ID is nearby. If not, uncheck it.
5486 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5487 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5488 boolean found = false;
5489 for (int searchPos = start; searchPos < end; searchPos++) {
5490 final long searchId = mAdapter.getItemId(searchPos);
5491 if (id == searchId) {
5492 found = true;
5493 mCheckStates.put(searchPos, true);
5494 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5495 break;
5496 }
5497 }
5498
5499 if (!found) {
5500 mCheckedIdStates.delete(id);
5501 checkedIndex--;
5502 mCheckedItemCount--;
5503 checkedCountChanged = true;
5504 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5505 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5506 lastPos, id, false);
5507 }
5508 }
5509 } else {
5510 mCheckStates.put(lastPos, true);
5511 }
5512 }
5513
5514 if (checkedCountChanged && mChoiceActionMode != null) {
5515 mChoiceActionMode.invalidate();
5516 }
5517 }
5518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005519 @Override
5520 protected void handleDataChanged() {
5521 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005522 int lastHandledItemCount = mLastHandledItemCount;
5523 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005524
5525 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5526 confirmCheckedPositionsById();
5527 }
5528
Adam Powell539ee872012-02-03 19:00:49 -08005529 // TODO: In the future we can recycle these views based on stable ID instead.
5530 mRecycler.clearTransientStateViews();
5531
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005532 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005533 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005534 int selectablePos;
5535
5536 // Find the row we are supposed to sync to
5537 if (mNeedSync) {
5538 // Update this first, since setNextSelectedPositionInt inspects it
5539 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005540 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005541
Adam Powell07852792010-11-10 16:57:05 -08005542 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005543 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5544 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005545 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5546 if (mForceTranscriptScroll) {
5547 mForceTranscriptScroll = false;
5548 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5549 return;
5550 }
Adam Powell07852792010-11-10 16:57:05 -08005551 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005552 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005553 final View lastChild = getChildAt(childCount - 1);
5554 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005555 if (mFirstPosition + childCount >= lastHandledItemCount &&
5556 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005557 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5558 return;
5559 }
5560 // Something new came in and we didn't scroll; give the user a clue that
5561 // there's something new.
5562 awakenScrollBars();
5563 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005564
5565 switch (mSyncMode) {
5566 case SYNC_SELECTED_POSITION:
5567 if (isInTouchMode()) {
5568 // We saved our state when not in touch mode. (We know this because
5569 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5570 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5571 // adjusting if the available range changed) and return.
5572 mLayoutMode = LAYOUT_SYNC;
5573 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5574
5575 return;
5576 } else {
5577 // See if we can find a position in the new data with the same
5578 // id as the old selection. This will change mSyncPosition.
5579 newPos = findSyncPosition();
5580 if (newPos >= 0) {
5581 // Found it. Now verify that new selection is still selectable
5582 selectablePos = lookForSelectablePosition(newPos, true);
5583 if (selectablePos == newPos) {
5584 // Same row id is selected
5585 mSyncPosition = newPos;
5586
5587 if (mSyncHeight == getHeight()) {
5588 // If we are at the same height as when we saved state, try
5589 // to restore the scroll position too.
5590 mLayoutMode = LAYOUT_SYNC;
5591 } else {
5592 // We are not the same height as when the selection was saved, so
5593 // don't try to restore the exact position
5594 mLayoutMode = LAYOUT_SET_SELECTION;
5595 }
5596
5597 // Restore selection
5598 setNextSelectedPositionInt(newPos);
5599 return;
5600 }
5601 }
5602 }
5603 break;
5604 case SYNC_FIRST_POSITION:
5605 // Leave mSyncPosition as it is -- just pin to available range
5606 mLayoutMode = LAYOUT_SYNC;
5607 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5608
5609 return;
5610 }
5611 }
5612
5613 if (!isInTouchMode()) {
5614 // We couldn't find matching data -- try to use the same position
5615 newPos = getSelectedItemPosition();
5616
5617 // Pin position to the available range
5618 if (newPos >= count) {
5619 newPos = count - 1;
5620 }
5621 if (newPos < 0) {
5622 newPos = 0;
5623 }
5624
5625 // Make sure we select something selectable -- first look down
5626 selectablePos = lookForSelectablePosition(newPos, true);
5627
5628 if (selectablePos >= 0) {
5629 setNextSelectedPositionInt(selectablePos);
5630 return;
5631 } else {
5632 // Looking down didn't work -- try looking up
5633 selectablePos = lookForSelectablePosition(newPos, false);
5634 if (selectablePos >= 0) {
5635 setNextSelectedPositionInt(selectablePos);
5636 return;
5637 }
5638 }
5639 } else {
5640
5641 // We already know where we want to resurrect the selection
5642 if (mResurrectToPosition >= 0) {
5643 return;
5644 }
5645 }
5646
5647 }
5648
5649 // Nothing is selected. Give up and reset everything.
5650 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5651 mSelectedPosition = INVALID_POSITION;
5652 mSelectedRowId = INVALID_ROW_ID;
5653 mNextSelectedPosition = INVALID_POSITION;
5654 mNextSelectedRowId = INVALID_ROW_ID;
5655 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005656 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005657 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005658 checkSelectionChanged();
5659 }
5660
Romain Guy43c9cdf2010-01-27 13:53:55 -08005661 @Override
5662 protected void onDisplayHint(int hint) {
5663 super.onDisplayHint(hint);
5664 switch (hint) {
5665 case INVISIBLE:
5666 if (mPopup != null && mPopup.isShowing()) {
5667 dismissPopup();
5668 }
5669 break;
5670 case VISIBLE:
5671 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5672 showPopup();
5673 }
5674 break;
5675 }
Romain Guy24562482010-02-01 14:56:19 -08005676 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005677 }
5678
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005679 /**
5680 * Removes the filter window
5681 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005682 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005683 if (mPopup != null) {
5684 mPopup.dismiss();
5685 }
5686 }
5687
5688 /**
5689 * Shows the filter window
5690 */
5691 private void showPopup() {
5692 // Make sure we have a window before showing the popup
5693 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005694 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005695 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005696 // Make sure we get focus if we are showing the popup
5697 checkFocus();
5698 }
5699 }
5700
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005701 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005702 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5703 final int[] xy = new int[2];
5704 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005705 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005706 // TODO: And the gravity should be defined in the theme as well
5707 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005708 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005709 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5710 xy[0], bottomGap);
5711 } else {
5712 mPopup.update(xy[0], bottomGap, -1, -1);
5713 }
5714 }
5715
5716 /**
5717 * What is the distance between the source and destination rectangles given the direction of
5718 * focus navigation between them? The direction basically helps figure out more quickly what is
5719 * self evident by the relationship between the rects...
5720 *
5721 * @param source the source rectangle
5722 * @param dest the destination rectangle
5723 * @param direction the direction
5724 * @return the distance between the rectangles
5725 */
5726 static int getDistance(Rect source, Rect dest, int direction) {
5727 int sX, sY; // source x, y
5728 int dX, dY; // dest x, y
5729 switch (direction) {
5730 case View.FOCUS_RIGHT:
5731 sX = source.right;
5732 sY = source.top + source.height() / 2;
5733 dX = dest.left;
5734 dY = dest.top + dest.height() / 2;
5735 break;
5736 case View.FOCUS_DOWN:
5737 sX = source.left + source.width() / 2;
5738 sY = source.bottom;
5739 dX = dest.left + dest.width() / 2;
5740 dY = dest.top;
5741 break;
5742 case View.FOCUS_LEFT:
5743 sX = source.left;
5744 sY = source.top + source.height() / 2;
5745 dX = dest.right;
5746 dY = dest.top + dest.height() / 2;
5747 break;
5748 case View.FOCUS_UP:
5749 sX = source.left + source.width() / 2;
5750 sY = source.top;
5751 dX = dest.left + dest.width() / 2;
5752 dY = dest.bottom;
5753 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005754 case View.FOCUS_FORWARD:
5755 case View.FOCUS_BACKWARD:
5756 sX = source.right + source.width() / 2;
5757 sY = source.top + source.height() / 2;
5758 dX = dest.left + dest.width() / 2;
5759 dY = dest.top + dest.height() / 2;
5760 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005761 default:
5762 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005763 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5764 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005765 }
5766 int deltaX = dX - sX;
5767 int deltaY = dY - sY;
5768 return deltaY * deltaY + deltaX * deltaX;
5769 }
5770
5771 @Override
5772 protected boolean isInFilterMode() {
5773 return mFiltered;
5774 }
5775
5776 /**
5777 * Sends a key to the text filter window
5778 *
5779 * @param keyCode The keycode for the event
5780 * @param event The actual key event
5781 *
5782 * @return True if the text filter handled the event, false otherwise.
5783 */
5784 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005785 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005786 return false;
5787 }
5788
5789 boolean handled = false;
5790 boolean okToSend = true;
5791 switch (keyCode) {
5792 case KeyEvent.KEYCODE_DPAD_UP:
5793 case KeyEvent.KEYCODE_DPAD_DOWN:
5794 case KeyEvent.KEYCODE_DPAD_LEFT:
5795 case KeyEvent.KEYCODE_DPAD_RIGHT:
5796 case KeyEvent.KEYCODE_DPAD_CENTER:
5797 case KeyEvent.KEYCODE_ENTER:
5798 okToSend = false;
5799 break;
5800 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005801 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005802 if (event.getAction() == KeyEvent.ACTION_DOWN
5803 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005804 KeyEvent.DispatcherState state = getKeyDispatcherState();
5805 if (state != null) {
5806 state.startTracking(event, this);
5807 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005808 handled = true;
5809 } else if (event.getAction() == KeyEvent.ACTION_UP
5810 && event.isTracking() && !event.isCanceled()) {
5811 handled = true;
5812 mTextFilter.setText("");
5813 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005814 }
5815 okToSend = false;
5816 break;
5817 case KeyEvent.KEYCODE_SPACE:
5818 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005819 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005820 break;
5821 }
5822
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005823 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005824 createTextFilter(true);
5825
5826 KeyEvent forwardEvent = event;
5827 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005828 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005829 }
5830
5831 int action = event.getAction();
5832 switch (action) {
5833 case KeyEvent.ACTION_DOWN:
5834 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5835 break;
5836
5837 case KeyEvent.ACTION_UP:
5838 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5839 break;
5840
5841 case KeyEvent.ACTION_MULTIPLE:
5842 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5843 break;
5844 }
5845 }
5846 return handled;
5847 }
5848
5849 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005850 * Return an InputConnection for editing of the filter text.
5851 */
5852 @Override
5853 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005854 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005855 if (mPublicInputConnection == null) {
5856 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005857 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005858 }
Romain Guyf6991302013-06-05 17:19:01 -07005859 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005860 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5861 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005862 }
5863 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005864 }
Romain Guy0a637162009-05-29 14:43:54 -07005865
Romain Guyf6991302013-06-05 17:19:01 -07005866 private class InputConnectionWrapper implements InputConnection {
5867 private final EditorInfo mOutAttrs;
5868 private InputConnection mTarget;
5869
5870 public InputConnectionWrapper(EditorInfo outAttrs) {
5871 mOutAttrs = outAttrs;
5872 }
5873
5874 private InputConnection getTarget() {
5875 if (mTarget == null) {
5876 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5877 }
5878 return mTarget;
5879 }
5880
5881 @Override
5882 public boolean reportFullscreenMode(boolean enabled) {
5883 // Use our own input connection, since it is
5884 // the "real" one the IME is talking with.
5885 return mDefInputConnection.reportFullscreenMode(enabled);
5886 }
5887
5888 @Override
5889 public boolean performEditorAction(int editorAction) {
5890 // The editor is off in its own window; we need to be
5891 // the one that does this.
5892 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005893 InputMethodManager imm =
5894 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005895 if (imm != null) {
5896 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5897 }
5898 return true;
5899 }
5900 return false;
5901 }
5902
5903 @Override
5904 public boolean sendKeyEvent(KeyEvent event) {
5905 // Use our own input connection, since the filter
5906 // text view may not be shown in a window so has
5907 // no ViewAncestor to dispatch events with.
5908 return mDefInputConnection.sendKeyEvent(event);
5909 }
5910
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005911 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005912 public CharSequence getTextBeforeCursor(int n, int flags) {
5913 if (mTarget == null) return "";
5914 return mTarget.getTextBeforeCursor(n, flags);
5915 }
5916
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005917 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005918 public CharSequence getTextAfterCursor(int n, int flags) {
5919 if (mTarget == null) return "";
5920 return mTarget.getTextAfterCursor(n, flags);
5921 }
5922
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005923 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005924 public CharSequence getSelectedText(int flags) {
5925 if (mTarget == null) return "";
5926 return mTarget.getSelectedText(flags);
5927 }
5928
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005929 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005930 public int getCursorCapsMode(int reqModes) {
5931 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5932 return mTarget.getCursorCapsMode(reqModes);
5933 }
5934
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005935 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005936 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5937 return getTarget().getExtractedText(request, flags);
5938 }
5939
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005940 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005941 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5942 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5943 }
5944
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005945 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08005946 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5947 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5948 }
5949
5950 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005951 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5952 return getTarget().setComposingText(text, newCursorPosition);
5953 }
5954
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005955 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005956 public boolean setComposingRegion(int start, int end) {
5957 return getTarget().setComposingRegion(start, end);
5958 }
5959
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005960 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005961 public boolean finishComposingText() {
5962 return mTarget == null || mTarget.finishComposingText();
5963 }
5964
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005965 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005966 public boolean commitText(CharSequence text, int newCursorPosition) {
5967 return getTarget().commitText(text, newCursorPosition);
5968 }
5969
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005970 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005971 public boolean commitCompletion(CompletionInfo text) {
5972 return getTarget().commitCompletion(text);
5973 }
5974
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005975 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005976 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5977 return getTarget().commitCorrection(correctionInfo);
5978 }
5979
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005980 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005981 public boolean setSelection(int start, int end) {
5982 return getTarget().setSelection(start, end);
5983 }
5984
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005985 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005986 public boolean performContextMenuAction(int id) {
5987 return getTarget().performContextMenuAction(id);
5988 }
5989
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005990 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005991 public boolean beginBatchEdit() {
5992 return getTarget().beginBatchEdit();
5993 }
5994
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005995 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005996 public boolean endBatchEdit() {
5997 return getTarget().endBatchEdit();
5998 }
5999
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006000 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006001 public boolean clearMetaKeyStates(int states) {
6002 return getTarget().clearMetaKeyStates(states);
6003 }
6004
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006005 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006006 public boolean performPrivateCommand(String action, Bundle data) {
6007 return getTarget().performPrivateCommand(action, data);
6008 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09006009
6010 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07006011 public boolean requestCursorUpdates(int cursorUpdateMode) {
6012 return getTarget().requestCursorUpdates(cursorUpdateMode);
6013 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08006014
6015 @Override
6016 public Handler getHandler() {
6017 return getTarget().getHandler();
6018 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07006019
6020 @Override
6021 public void closeConnection() {
6022 getTarget().closeConnection();
6023 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07006024
6025 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07006026 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6027 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07006028 }
Romain Guyf6991302013-06-05 17:19:01 -07006029 }
6030
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006031 /**
6032 * For filtering we proxy an input connection to an internal text editor,
6033 * and this allows the proxying to happen.
6034 */
6035 @Override
6036 public boolean checkInputConnectionProxy(View view) {
6037 return view == mTextFilter;
6038 }
Romain Guy0a637162009-05-29 14:43:54 -07006039
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006040 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006041 * Creates the window for the text filter and populates it with an EditText field;
6042 *
6043 * @param animateEntrance true if the window should appear with an animation
6044 */
6045 private void createTextFilter(boolean animateEntrance) {
6046 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006047 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006048 p.setFocusable(false);
6049 p.setTouchable(false);
6050 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006051 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006052 p.setWidth(LayoutParams.WRAP_CONTENT);
6053 p.setHeight(LayoutParams.WRAP_CONTENT);
6054 p.setBackgroundDrawable(null);
6055 mPopup = p;
6056 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006057 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006058 }
6059 if (animateEntrance) {
6060 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6061 } else {
6062 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6063 }
6064 }
6065
Romain Guyf6991302013-06-05 17:19:01 -07006066 private EditText getTextFilterInput() {
6067 if (mTextFilter == null) {
6068 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6069 mTextFilter = (EditText) layoutInflater.inflate(
6070 com.android.internal.R.layout.typing_filter, null);
6071 // For some reason setting this as the "real" input type changes
6072 // the text view in some way that it doesn't work, and I don't
6073 // want to figure out why this is.
6074 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6075 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6076 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6077 mTextFilter.addTextChangedListener(this);
6078 }
6079 return mTextFilter;
6080 }
6081
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006082 /**
6083 * Clear the text filter.
6084 */
6085 public void clearTextFilter() {
6086 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006087 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006088 mFiltered = false;
6089 if (mPopup != null && mPopup.isShowing()) {
6090 dismissPopup();
6091 }
6092 }
6093 }
6094
6095 /**
6096 * Returns if the ListView currently has a text filter.
6097 */
6098 public boolean hasTextFilter() {
6099 return mFiltered;
6100 }
6101
Alan Viverette8fa327a2013-05-31 14:53:13 -07006102 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006103 public void onGlobalLayout() {
6104 if (isShown()) {
6105 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006106 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006107 showPopup();
6108 }
6109 } else {
6110 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006111 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006112 dismissPopup();
6113 }
6114 }
6115
6116 }
6117
6118 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006119 * For our text watcher that is associated with the text filter. Does
6120 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006121 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006122 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006123 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6124 }
6125
6126 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006127 * For our text watcher that is associated with the text filter. Performs
6128 * the actual filtering as the text changes, and takes care of hiding and
6129 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006130 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006131 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006132 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006133 if (isTextFilterEnabled()) {
6134 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006135 int length = s.length();
6136 boolean showing = mPopup.isShowing();
6137 if (!showing && length > 0) {
6138 // Show the filter popup if necessary
6139 showPopup();
6140 mFiltered = true;
6141 } else if (showing && length == 0) {
6142 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006143 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006144 mFiltered = false;
6145 }
6146 if (mAdapter instanceof Filterable) {
6147 Filter f = ((Filterable) mAdapter).getFilter();
6148 // Filter should not be null when we reach this part
6149 if (f != null) {
6150 f.filter(s, this);
6151 } else {
6152 throw new IllegalStateException("You cannot call onTextChanged with a non "
6153 + "filterable adapter");
6154 }
6155 }
6156 }
6157 }
6158
6159 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006160 * For our text watcher that is associated with the text filter. Does
6161 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006162 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006163 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006164 public void afterTextChanged(Editable s) {
6165 }
6166
Alan Viverette8fa327a2013-05-31 14:53:13 -07006167 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006168 public void onFilterComplete(int count) {
6169 if (mSelectedPosition < 0 && count > 0) {
6170 mResurrectToPosition = INVALID_POSITION;
6171 resurrectSelection();
6172 }
6173 }
6174
6175 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006176 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6177 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6178 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6179 }
6180
6181 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006182 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6183 return new LayoutParams(p);
6184 }
6185
6186 @Override
6187 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6188 return new AbsListView.LayoutParams(getContext(), attrs);
6189 }
6190
6191 @Override
6192 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6193 return p instanceof AbsListView.LayoutParams;
6194 }
6195
6196 /**
6197 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6198 * to the bottom to show new items.
6199 *
6200 * @param mode the transcript mode to set
6201 *
6202 * @see #TRANSCRIPT_MODE_DISABLED
6203 * @see #TRANSCRIPT_MODE_NORMAL
6204 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6205 */
6206 public void setTranscriptMode(int mode) {
6207 mTranscriptMode = mode;
6208 }
6209
6210 /**
6211 * Returns the current transcript mode.
6212 *
6213 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6214 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6215 */
6216 public int getTranscriptMode() {
6217 return mTranscriptMode;
6218 }
6219
6220 @Override
6221 public int getSolidColor() {
6222 return mCacheColorHint;
6223 }
6224
6225 /**
6226 * 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 -07006227 * on top of a solid, single-color, opaque background.
6228 *
6229 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6230 * single color. This hint will not affect any existing background drawable set on this view (
6231 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006232 *
6233 * @param color The background color
6234 */
Tor Norbye80756e32015-03-02 09:39:27 -08006235 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006236 if (color != mCacheColorHint) {
6237 mCacheColorHint = color;
6238 int count = getChildCount();
6239 for (int i = 0; i < count; i++) {
6240 getChildAt(i).setDrawingCacheBackgroundColor(color);
6241 }
6242 mRecycler.setCacheColorHint(color);
6243 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006244 }
6245
6246 /**
6247 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6248 * on top of a solid, single-color, opaque background
6249 *
6250 * @return The cache color hint
6251 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006252 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006253 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006254 public int getCacheColorHint() {
6255 return mCacheColorHint;
6256 }
6257
6258 /**
6259 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6260 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6261 * internal view recycler.
6262 *
6263 * @param views A list into which to put the reclaimed views
6264 */
6265 public void reclaimViews(List<View> views) {
6266 int childCount = getChildCount();
6267 RecyclerListener listener = mRecycler.mRecyclerListener;
6268
6269 // Reclaim views on screen
6270 for (int i = 0; i < childCount; i++) {
6271 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006272 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006273 // Don't reclaim header or footer views, or views that should be ignored
6274 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6275 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006276 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006277 if (listener != null) {
6278 // Pretend they went through the scrap heap
6279 listener.onMovedToScrapHeap(child);
6280 }
6281 }
6282 }
6283 mRecycler.reclaimScrapViews(views);
6284 removeAllViewsInLayout();
6285 }
6286
Adam Powell637d3372010-08-25 14:37:03 -07006287 private void finishGlows() {
6288 if (mEdgeGlowTop != null) {
6289 mEdgeGlowTop.finish();
6290 mEdgeGlowBottom.finish();
6291 }
6292 }
6293
Romain Guy13922e02009-05-12 17:56:14 -07006294 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006295 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6296 * through the specified intent.
6297 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6298 */
6299 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -08006300 setRemoteViewsAdapter(intent, false);
6301 }
6302
6303 /** @hide **/
6304 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6305 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6306 }
6307
6308 /** @hide **/
6309 @Override
6310 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006311 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6312 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006313 if (mRemoteAdapter != null) {
6314 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6315 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6316 mRemoteAdapter.getRemoteViewsServiceIntent());
6317 if (fcNew.equals(fcOld)) {
6318 return;
6319 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006320 }
Adam Cohen2148d432011-07-28 14:59:54 -07006321 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006322 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08006323 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07006324 if (mRemoteAdapter.isDataReady()) {
6325 setAdapter(mRemoteAdapter);
6326 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006327 }
6328
6329 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006330 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006331 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006332 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006333 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006334 * @hide
6335 */
6336 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6337 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6338 // service handling the specified intent.
6339 if (mRemoteAdapter != null) {
6340 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6341 }
6342 }
6343
6344 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006345 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6346 * connected yet.
6347 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006348 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006349 public void deferNotifyDataSetChanged() {
6350 mDeferNotifyDataSetChanged = true;
6351 }
6352
6353 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006354 * Called back when the adapter connects to the RemoteViewsService.
6355 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006356 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006357 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006358 if (mRemoteAdapter != mAdapter) {
6359 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006360 if (mDeferNotifyDataSetChanged) {
6361 mRemoteAdapter.notifyDataSetChanged();
6362 mDeferNotifyDataSetChanged = false;
6363 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006364 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006365 } else if (mRemoteAdapter != null) {
6366 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006367 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006368 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006369 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006370 }
6371
6372 /**
6373 * Called back when the adapter disconnects from the RemoteViewsService.
6374 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006375 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006376 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006377 // If the remote adapter disconnects, we keep it around
6378 // since the currently displayed items are still cached.
6379 // Further, we want the service to eventually reconnect
6380 // when necessary, as triggered by this view requesting
6381 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006382 }
6383
6384 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006385 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6386 * being displayed by the AbsListView.
6387 */
6388 void setVisibleRangeHint(int start, int end) {
6389 if (mRemoteAdapter != null) {
6390 mRemoteAdapter.setVisibleRangeHint(start, end);
6391 }
6392 }
6393
6394 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006395 * Sets the recycler listener to be notified whenever a View is set aside in
6396 * the recycler for later reuse. This listener can be used to free resources
6397 * associated to the View.
6398 *
6399 * @param listener The recycler listener to be notified of views set aside
6400 * in the recycler.
6401 *
6402 * @see android.widget.AbsListView.RecycleBin
6403 * @see android.widget.AbsListView.RecyclerListener
6404 */
6405 public void setRecyclerListener(RecyclerListener listener) {
6406 mRecycler.mRecyclerListener = listener;
6407 }
6408
Adam Powellb1f498a2011-01-18 20:43:23 -08006409 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6410 @Override
6411 public void onChanged() {
6412 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006413 if (mFastScroll != null) {
6414 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006415 }
6416 }
6417
6418 @Override
6419 public void onInvalidated() {
6420 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006421 if (mFastScroll != null) {
6422 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006423 }
6424 }
6425 }
6426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006427 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006428 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6429 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6430 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6431 * selects and deselects list items.
6432 */
6433 public interface MultiChoiceModeListener extends ActionMode.Callback {
6434 /**
6435 * Called when an item is checked or unchecked during selection mode.
6436 *
6437 * @param mode The {@link ActionMode} providing the selection mode
6438 * @param position Adapter position of the item that was checked or unchecked
6439 * @param id Adapter ID of the item that was checked or unchecked
6440 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6441 * if the item is now unchecked.
6442 */
6443 public void onItemCheckedStateChanged(ActionMode mode,
6444 int position, long id, boolean checked);
6445 }
6446
6447 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6448 private MultiChoiceModeListener mWrapped;
6449
6450 public void setWrapped(MultiChoiceModeListener wrapped) {
6451 mWrapped = wrapped;
6452 }
6453
Adam Powella7981702012-08-24 12:43:41 -07006454 public boolean hasWrappedCallback() {
6455 return mWrapped != null;
6456 }
6457
Alan Viverette8fa327a2013-05-31 14:53:13 -07006458 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006459 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6460 if (mWrapped.onCreateActionMode(mode, menu)) {
6461 // Initialize checked graphic state?
6462 setLongClickable(false);
6463 return true;
6464 }
6465 return false;
6466 }
6467
Alan Viverette8fa327a2013-05-31 14:53:13 -07006468 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006469 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6470 return mWrapped.onPrepareActionMode(mode, menu);
6471 }
6472
Alan Viverette8fa327a2013-05-31 14:53:13 -07006473 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006474 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6475 return mWrapped.onActionItemClicked(mode, item);
6476 }
6477
Alan Viverette8fa327a2013-05-31 14:53:13 -07006478 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006479 public void onDestroyActionMode(ActionMode mode) {
6480 mWrapped.onDestroyActionMode(mode);
6481 mChoiceActionMode = null;
6482
6483 // Ending selection mode means deselecting everything.
6484 clearChoices();
6485
6486 mDataChanged = true;
6487 rememberSyncState();
6488 requestLayout();
6489
6490 setLongClickable(true);
6491 }
6492
Alan Viverette8fa327a2013-05-31 14:53:13 -07006493 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006494 public void onItemCheckedStateChanged(ActionMode mode,
6495 int position, long id, boolean checked) {
6496 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6497
6498 // If there are no items selected we no longer need the selection mode.
6499 if (getCheckedItemCount() == 0) {
6500 mode.finish();
6501 }
6502 }
6503 }
6504
6505 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006506 * AbsListView extends LayoutParams to provide a place to hold the view type.
6507 */
6508 public static class LayoutParams extends ViewGroup.LayoutParams {
6509 /**
6510 * View type for this view, as returned by
6511 * {@link android.widget.Adapter#getItemViewType(int) }
6512 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006513 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006514 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6515 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6516 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006517 int viewType;
6518
The Android Open Source Project4df24232009-03-05 14:34:35 -08006519 /**
6520 * When this boolean is set, the view has been added to the AbsListView
6521 * at least once. It is used to know whether headers/footers have already
6522 * been added to the list view and whether they should be treated as
6523 * recycled views or not.
6524 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006525 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006526 boolean recycledHeaderFooter;
6527
Romain Guy0bf88592010-03-02 13:38:44 -08006528 /**
6529 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6530 * to obtain children views to measure itself. When doing so, the children
6531 * are not attached to the window, but put in the recycler which assumes
6532 * they've been attached before. Setting this flag will force the reused
6533 * view to be attached to the window rather than just attached to the
6534 * parent.
6535 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006536 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006537 boolean forceAdd;
6538
Dianne Hackborn079e2352010-10-18 17:02:43 -07006539 /**
6540 * The position the view was removed from when pulled out of the
6541 * scrap heap.
6542 * @hide
6543 */
6544 int scrappedFromPosition;
6545
Adam Powell539ee872012-02-03 19:00:49 -08006546 /**
6547 * The ID the view represents
6548 */
6549 long itemId = -1;
6550
Alan Viverette92539d52015-09-14 10:49:25 -04006551 /** Whether the adapter considers the item enabled. */
6552 boolean isEnabled;
6553
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006554 public LayoutParams(Context c, AttributeSet attrs) {
6555 super(c, attrs);
6556 }
6557
6558 public LayoutParams(int w, int h) {
6559 super(w, h);
6560 }
6561
6562 public LayoutParams(int w, int h, int viewType) {
6563 super(w, h);
6564 this.viewType = viewType;
6565 }
6566
6567 public LayoutParams(ViewGroup.LayoutParams source) {
6568 super(source);
6569 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006570
6571 /** @hide */
6572 @Override
6573 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6574 super.encodeProperties(encoder);
6575
6576 encoder.addProperty("list:viewType", viewType);
6577 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6578 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006579 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006580 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006581 }
6582
6583 /**
6584 * A RecyclerListener is used to receive a notification whenever a View is placed
6585 * inside the RecycleBin's scrap heap. This listener is used to free resources
6586 * associated to Views placed in the RecycleBin.
6587 *
6588 * @see android.widget.AbsListView.RecycleBin
6589 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6590 */
6591 public static interface RecyclerListener {
6592 /**
6593 * Indicates that the specified View was moved into the recycler's scrap heap.
6594 * The view is not displayed on screen any more and any expensive resource
6595 * associated with the view should be discarded.
6596 *
6597 * @param view
6598 */
6599 void onMovedToScrapHeap(View view);
6600 }
6601
6602 /**
6603 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6604 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6605 * start of a layout. By construction, they are displaying current information. At the end of
6606 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6607 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6608 *
6609 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6610 * @see android.widget.AbsListView.RecyclerListener
6611 */
6612 class RecycleBin {
6613 private RecyclerListener mRecyclerListener;
6614
6615 /**
6616 * The position of the first view stored in mActiveViews.
6617 */
6618 private int mFirstActivePosition;
6619
6620 /**
6621 * Views that were on screen at the start of layout. This array is populated at the start of
6622 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6623 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6624 * view store in mFirstActivePosition.
6625 */
6626 private View[] mActiveViews = new View[0];
6627
6628 /**
6629 * Unsorted views that can be used by the adapter as a convert view.
6630 */
6631 private ArrayList<View>[] mScrapViews;
6632
6633 private int mViewTypeCount;
6634
6635 private ArrayList<View> mCurrentScrap;
6636
Adam Powell539ee872012-02-03 19:00:49 -08006637 private ArrayList<View> mSkippedScrap;
6638
6639 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006640 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006641
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006642 public void setViewTypeCount(int viewTypeCount) {
6643 if (viewTypeCount < 1) {
6644 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6645 }
6646 //noinspection unchecked
6647 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6648 for (int i = 0; i < viewTypeCount; i++) {
6649 scrapViews[i] = new ArrayList<View>();
6650 }
6651 mViewTypeCount = viewTypeCount;
6652 mCurrentScrap = scrapViews[0];
6653 mScrapViews = scrapViews;
6654 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006655
Adam Powellf3c2eda2010-03-16 17:31:01 -07006656 public void markChildrenDirty() {
6657 if (mViewTypeCount == 1) {
6658 final ArrayList<View> scrap = mCurrentScrap;
6659 final int scrapCount = scrap.size();
6660 for (int i = 0; i < scrapCount; i++) {
6661 scrap.get(i).forceLayout();
6662 }
6663 } else {
6664 final int typeCount = mViewTypeCount;
6665 for (int i = 0; i < typeCount; i++) {
6666 final ArrayList<View> scrap = mScrapViews[i];
6667 final int scrapCount = scrap.size();
6668 for (int j = 0; j < scrapCount; j++) {
6669 scrap.get(j).forceLayout();
6670 }
6671 }
6672 }
Adam Powell539ee872012-02-03 19:00:49 -08006673 if (mTransientStateViews != null) {
6674 final int count = mTransientStateViews.size();
6675 for (int i = 0; i < count; i++) {
6676 mTransientStateViews.valueAt(i).forceLayout();
6677 }
6678 }
Chet Haase72871322013-02-26 16:12:13 -07006679 if (mTransientStateViewsById != null) {
6680 final int count = mTransientStateViewsById.size();
6681 for (int i = 0; i < count; i++) {
6682 mTransientStateViewsById.valueAt(i).forceLayout();
6683 }
6684 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006685 }
Romain Guy0a637162009-05-29 14:43:54 -07006686
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006687 public boolean shouldRecycleViewType(int viewType) {
6688 return viewType >= 0;
6689 }
6690
6691 /**
6692 * Clears the scrap heap.
6693 */
6694 void clear() {
6695 if (mViewTypeCount == 1) {
6696 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006697 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006698 } else {
6699 final int typeCount = mViewTypeCount;
6700 for (int i = 0; i < typeCount; i++) {
6701 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006702 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006703 }
6704 }
Alan Viverette59511502013-12-09 13:49:25 -08006705
6706 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006707 }
6708
6709 /**
6710 * Fill ActiveViews with all of the children of the AbsListView.
6711 *
6712 * @param childCount The minimum number of views mActiveViews should hold
6713 * @param firstActivePosition The position of the first view that will be stored in
6714 * mActiveViews
6715 */
6716 void fillActiveViews(int childCount, int firstActivePosition) {
6717 if (mActiveViews.length < childCount) {
6718 mActiveViews = new View[childCount];
6719 }
6720 mFirstActivePosition = firstActivePosition;
6721
Romain Guyf6991302013-06-05 17:19:01 -07006722 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006723 final View[] activeViews = mActiveViews;
6724 for (int i = 0; i < childCount; i++) {
6725 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006726 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006727 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006728 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006729 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6730 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006731 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006732 // Remember the position so that setupChild() doesn't reset state.
6733 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006734 }
6735 }
6736 }
6737
6738 /**
6739 * Get the view corresponding to the specified position. The view will be removed from
6740 * mActiveViews if it is found.
6741 *
6742 * @param position The position to look up in mActiveViews
6743 * @return The view if it is found, null otherwise
6744 */
6745 View getActiveView(int position) {
6746 int index = position - mFirstActivePosition;
6747 final View[] activeViews = mActiveViews;
6748 if (index >=0 && index < activeViews.length) {
6749 final View match = activeViews[index];
6750 activeViews[index] = null;
6751 return match;
6752 }
6753 return null;
6754 }
6755
Adam Powell539ee872012-02-03 19:00:49 -08006756 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006757 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6758 long id = mAdapter.getItemId(position);
6759 View result = mTransientStateViewsById.get(id);
6760 mTransientStateViewsById.remove(id);
6761 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006762 }
Chet Haase72871322013-02-26 16:12:13 -07006763 if (mTransientStateViews != null) {
6764 final int index = mTransientStateViews.indexOfKey(position);
6765 if (index >= 0) {
6766 View result = mTransientStateViews.valueAt(index);
6767 mTransientStateViews.removeAt(index);
6768 return result;
6769 }
Adam Powell539ee872012-02-03 19:00:49 -08006770 }
Chet Haase72871322013-02-26 16:12:13 -07006771 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006772 }
6773
6774 /**
Alan Viverette59511502013-12-09 13:49:25 -08006775 * Dumps and fully detaches any currently saved views with transient
6776 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006777 */
6778 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006779 final SparseArray<View> viewsByPos = mTransientStateViews;
6780 if (viewsByPos != null) {
6781 final int N = viewsByPos.size();
6782 for (int i = 0; i < N; i++) {
6783 removeDetachedView(viewsByPos.valueAt(i), false);
6784 }
6785 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006786 }
Alan Viverette59511502013-12-09 13:49:25 -08006787
6788 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6789 if (viewsById != null) {
6790 final int N = viewsById.size();
6791 for (int i = 0; i < N; i++) {
6792 removeDetachedView(viewsById.valueAt(i), false);
6793 }
6794 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006795 }
Adam Powell539ee872012-02-03 19:00:49 -08006796 }
6797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006798 /**
6799 * @return A view from the ScrapViews collection. These are unordered.
6800 */
6801 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006802 final int whichScrap = mAdapter.getItemViewType(position);
6803 if (whichScrap < 0) {
6804 return null;
6805 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006806 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006807 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006808 } else if (whichScrap < mScrapViews.length) {
6809 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006810 }
6811 return null;
6812 }
6813
6814 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006815 * Puts a view into the list of scrap views.
6816 * <p>
6817 * If the list data hasn't changed or the adapter has stable IDs, views
6818 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006819 *
6820 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006821 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006822 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006823 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006824 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006825 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006826 // Can't recycle, but we don't know anything about the view.
6827 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006828 return;
6829 }
6830
Adam Powell539ee872012-02-03 19:00:49 -08006831 lp.scrappedFromPosition = position;
6832
Alan Viverette1e51cc72013-09-27 14:32:20 -07006833 // Remove but don't scrap header or footer views, or views that
6834 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006835 final int viewType = lp.viewType;
6836 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006837 // Can't recycle. If it's not a header or footer, which have
6838 // special handling and should be ignored, then skip the scrap
6839 // heap and we'll fully detach the view later.
6840 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6841 getSkippedScrap().add(scrap);
6842 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006843 return;
6844 }
6845
6846 scrap.dispatchStartTemporaryDetach();
6847
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006848 // The the accessibility state of the view may change while temporary
6849 // detached and we do not allow detached views to fire accessibility
6850 // events. So we are announcing that the subtree changed giving a chance
6851 // to clients holding on to a view in this subtree to refresh it.
Eugene Suslae4d31b32017-06-01 11:16:42 -07006852 notifyAccessibilityStateChanged(
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006853 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6854
Alan Viveretted44696c2013-07-18 10:37:15 -07006855 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006856 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006857 if (scrapHasTransientState) {
6858 if (mAdapter != null && mAdapterHasStableIds) {
6859 // If the adapter has stable IDs, we can reuse the view for
6860 // the same data.
6861 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006862 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006863 }
6864 mTransientStateViewsById.put(lp.itemId, scrap);
6865 } else if (!mDataChanged) {
6866 // If the data hasn't changed, we can reuse the views at
6867 // their old positions.
6868 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006869 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006870 }
6871 mTransientStateViews.put(position, scrap);
6872 } else {
6873 // Otherwise, we'll have to remove the view and start over.
Phil Weaverec66fb82017-03-23 12:21:53 -07006874 clearScrapForRebind(scrap);
Alan Viverette8bbae342015-06-25 14:49:29 -07006875 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006876 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006877 } else {
Phil Weaverec66fb82017-03-23 12:21:53 -07006878 clearScrapForRebind(scrap);
Alan Viveretted44696c2013-07-18 10:37:15 -07006879 if (mViewTypeCount == 1) {
6880 mCurrentScrap.add(scrap);
6881 } else {
6882 mScrapViews[viewType].add(scrap);
6883 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006884
Alan Viveretted44696c2013-07-18 10:37:15 -07006885 if (mRecyclerListener != null) {
6886 mRecyclerListener.onMovedToScrapHeap(scrap);
6887 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006888 }
6889 }
6890
Alan Viverette8bbae342015-06-25 14:49:29 -07006891 private ArrayList<View> getSkippedScrap() {
6892 if (mSkippedScrap == null) {
6893 mSkippedScrap = new ArrayList<>();
6894 }
6895 return mSkippedScrap;
6896 }
6897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006898 /**
Adam Powell539ee872012-02-03 19:00:49 -08006899 * Finish the removal of any views that skipped the scrap heap.
6900 */
6901 void removeSkippedScrap() {
6902 if (mSkippedScrap == null) {
6903 return;
6904 }
6905 final int count = mSkippedScrap.size();
6906 for (int i = 0; i < count; i++) {
6907 removeDetachedView(mSkippedScrap.get(i), false);
6908 }
6909 mSkippedScrap.clear();
6910 }
6911
6912 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006913 * Move all views remaining in mActiveViews to mScrapViews.
6914 */
6915 void scrapActiveViews() {
6916 final View[] activeViews = mActiveViews;
6917 final boolean hasListener = mRecyclerListener != null;
6918 final boolean multipleScraps = mViewTypeCount > 1;
6919
6920 ArrayList<View> scrapViews = mCurrentScrap;
6921 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006922 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006923 final View victim = activeViews[i];
6924 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006925 final AbsListView.LayoutParams lp
6926 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006927 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006928
6929 activeViews[i] = null;
6930
Alan Viverette59511502013-12-09 13:49:25 -08006931 if (victim.hasTransientState()) {
6932 // Store views with transient state for later use.
6933 victim.dispatchStartTemporaryDetach();
6934
6935 if (mAdapter != null && mAdapterHasStableIds) {
6936 if (mTransientStateViewsById == null) {
6937 mTransientStateViewsById = new LongSparseArray<View>();
6938 }
6939 long id = mAdapter.getItemId(mFirstActivePosition + i);
6940 mTransientStateViewsById.put(id, victim);
6941 } else if (!mDataChanged) {
6942 if (mTransientStateViews == null) {
6943 mTransientStateViews = new SparseArray<View>();
6944 }
6945 mTransientStateViews.put(mFirstActivePosition + i, victim);
6946 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6947 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006948 removeDetachedView(victim, false);
6949 }
Alan Viverette59511502013-12-09 13:49:25 -08006950 } else if (!shouldRecycleViewType(whichScrap)) {
6951 // Discard non-recyclable views except headers/footers.
6952 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6953 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006954 }
Alan Viverette59511502013-12-09 13:49:25 -08006955 } else {
6956 // Store everything else on the appropriate scrap heap.
6957 if (multipleScraps) {
6958 scrapViews = mScrapViews[whichScrap];
6959 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006960
Alan Viverette59511502013-12-09 13:49:25 -08006961 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006962 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08006963 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006964
Alan Viverette59511502013-12-09 13:49:25 -08006965 if (hasListener) {
6966 mRecyclerListener.onMovedToScrapHeap(victim);
6967 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006969 }
6970 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006971 pruneScrapViews();
6972 }
6973
6974 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006975 * At the end of a layout pass, all temp detached views should either be re-attached or
6976 * completely detached. This method ensures that any remaining view in the scrap list is
6977 * fully detached.
6978 */
6979 void fullyDetachScrapViews() {
6980 final int viewTypeCount = mViewTypeCount;
6981 final ArrayList<View>[] scrapViews = mScrapViews;
6982 for (int i = 0; i < viewTypeCount; ++i) {
6983 final ArrayList<View> scrapPile = scrapViews[i];
6984 for (int j = scrapPile.size() - 1; j >= 0; j--) {
6985 final View view = scrapPile.get(j);
6986 if (view.isTemporarilyDetached()) {
6987 removeDetachedView(view, false);
6988 }
6989 }
6990 }
6991 }
6992
6993 /**
Alan Viverette59511502013-12-09 13:49:25 -08006994 * Makes sure that the size of mScrapViews does not exceed the size of
6995 * mActiveViews, which can happen if an adapter does not recycle its
6996 * views. Removes cached transient state views that no longer have
6997 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006998 */
6999 private void pruneScrapViews() {
7000 final int maxViews = mActiveViews.length;
7001 final int viewTypeCount = mViewTypeCount;
7002 final ArrayList<View>[] scrapViews = mScrapViews;
7003 for (int i = 0; i < viewTypeCount; ++i) {
7004 final ArrayList<View> scrapPile = scrapViews[i];
7005 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007006 while (size > maxViews) {
7007 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007008 }
7009 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07007010
Alan Viverette59511502013-12-09 13:49:25 -08007011 final SparseArray<View> transViewsByPos = mTransientStateViews;
7012 if (transViewsByPos != null) {
7013 for (int i = 0; i < transViewsByPos.size(); i++) {
7014 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007015 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007016 removeDetachedView(v, false);
7017 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007018 i--;
7019 }
7020 }
7021 }
Alan Viverette59511502013-12-09 13:49:25 -08007022
7023 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7024 if (transViewsById != null) {
7025 for (int i = 0; i < transViewsById.size(); i++) {
7026 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007027 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007028 removeDetachedView(v, false);
7029 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007030 i--;
7031 }
7032 }
7033 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007034 }
7035
7036 /**
7037 * Puts all views in the scrap heap into the supplied list.
7038 */
7039 void reclaimScrapViews(List<View> views) {
7040 if (mViewTypeCount == 1) {
7041 views.addAll(mCurrentScrap);
7042 } else {
7043 final int viewTypeCount = mViewTypeCount;
7044 final ArrayList<View>[] scrapViews = mScrapViews;
7045 for (int i = 0; i < viewTypeCount; ++i) {
7046 final ArrayList<View> scrapPile = scrapViews[i];
7047 views.addAll(scrapPile);
7048 }
7049 }
7050 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007051
7052 /**
7053 * Updates the cache color hint of all known views.
7054 *
7055 * @param color The new cache color hint.
7056 */
7057 void setCacheColorHint(int color) {
7058 if (mViewTypeCount == 1) {
7059 final ArrayList<View> scrap = mCurrentScrap;
7060 final int scrapCount = scrap.size();
7061 for (int i = 0; i < scrapCount; i++) {
7062 scrap.get(i).setDrawingCacheBackgroundColor(color);
7063 }
7064 } else {
7065 final int typeCount = mViewTypeCount;
7066 for (int i = 0; i < typeCount; i++) {
7067 final ArrayList<View> scrap = mScrapViews[i];
7068 final int scrapCount = scrap.size();
7069 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007070 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007071 }
7072 }
7073 }
7074 // Just in case this is called during a layout pass
7075 final View[] activeViews = mActiveViews;
7076 final int count = activeViews.length;
7077 for (int i = 0; i < count; ++i) {
7078 final View victim = activeViews[i];
7079 if (victim != null) {
7080 victim.setDrawingCacheBackgroundColor(color);
7081 }
7082 }
7083 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007084
Alan Viverette3e141622014-02-18 17:05:13 -08007085 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7086 final int size = scrapViews.size();
7087 if (size > 0) {
7088 // See if we still have a view for this position or ID.
Phil Weavere28c03b2017-04-24 13:23:10 -07007089 // Traverse backwards to find the most recently used scrap view
7090 for (int i = size - 1; i >= 0; i--) {
Alan Viverette3e141622014-02-18 17:05:13 -08007091 final View view = scrapViews.get(i);
7092 final AbsListView.LayoutParams params =
7093 (AbsListView.LayoutParams) view.getLayoutParams();
7094
7095 if (mAdapterHasStableIds) {
7096 final long id = mAdapter.getItemId(position);
7097 if (id == params.itemId) {
7098 return scrapViews.remove(i);
7099 }
7100 } else if (params.scrappedFromPosition == position) {
7101 final View scrap = scrapViews.remove(i);
Phil Weaverec66fb82017-03-23 12:21:53 -07007102 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007103 return scrap;
7104 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007105 }
Alan Viverette3e141622014-02-18 17:05:13 -08007106 final View scrap = scrapViews.remove(size - 1);
Phil Weaverec66fb82017-03-23 12:21:53 -07007107 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007108 return scrap;
7109 } else {
7110 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007111 }
Alan Viverette3e141622014-02-18 17:05:13 -08007112 }
7113
7114 private void clearScrap(final ArrayList<View> scrap) {
7115 final int scrapCount = scrap.size();
7116 for (int j = 0; j < scrapCount; j++) {
7117 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7118 }
7119 }
7120
Phil Weaverec66fb82017-03-23 12:21:53 -07007121 private void clearScrapForRebind(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007122 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007123 view.setAccessibilityDelegate(null);
7124 }
7125
7126 private void removeDetachedView(View child, boolean animate) {
7127 child.setAccessibilityDelegate(null);
7128 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007129 }
7130 }
Alan Viverette441b4372014-02-12 13:30:20 -08007131
7132 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007133 * Returns the height of the view for the specified position.
7134 *
7135 * @param position the item position
7136 * @return view height in pixels
7137 */
7138 int getHeightForPosition(int position) {
7139 final int firstVisiblePosition = getFirstVisiblePosition();
7140 final int childCount = getChildCount();
7141 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007142 if (index >= 0 && index < childCount) {
7143 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007144 final View view = getChildAt(index);
7145 return view.getHeight();
7146 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007147 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007148 final View view = obtainView(position, mIsScrap);
7149 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7150 final int height = view.getMeasuredHeight();
7151 mRecycler.addScrapView(view, position);
7152 return height;
7153 }
7154 }
7155
7156 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007157 * Sets the selected item and positions the selection y pixels from the top edge
7158 * of the ListView. (If in touch mode, the item will not be selected but it will
7159 * still be positioned appropriately.)
7160 *
7161 * @param position Index (starting at 0) of the data item to be selected.
7162 * @param y The distance from the top edge of the ListView (plus padding) that the
7163 * item will be positioned.
7164 */
7165 public void setSelectionFromTop(int position, int y) {
7166 if (mAdapter == null) {
7167 return;
7168 }
7169
7170 if (!isInTouchMode()) {
7171 position = lookForSelectablePosition(position, true);
7172 if (position >= 0) {
7173 setNextSelectedPositionInt(position);
7174 }
7175 } else {
7176 mResurrectToPosition = position;
7177 }
7178
7179 if (position >= 0) {
7180 mLayoutMode = LAYOUT_SPECIFIC;
7181 mSpecificTop = mListPadding.top + y;
7182
7183 if (mNeedSync) {
7184 mSyncPosition = position;
7185 mSyncRowId = mAdapter.getItemId(position);
7186 }
7187
7188 if (mPositionScroller != null) {
7189 mPositionScroller.stop();
7190 }
7191 requestLayout();
7192 }
7193 }
7194
Siva Velusamy94a6d152015-05-05 15:07:00 -07007195 /** @hide */
7196 @Override
7197 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7198 super.encodeProperties(encoder);
7199
7200 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7201 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7202 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7203 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7204 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7205 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7206
7207 View selectedView = getSelectedView();
7208 if (selectedView != null) {
7209 encoder.addPropertyKey("selectedView");
7210 selectedView.encode(encoder);
7211 }
7212 }
7213
Alan Viveretted22db212014-02-13 17:47:38 -08007214 /**
7215 * Abstract positon scroller used to handle smooth scrolling.
7216 */
7217 static abstract class AbsPositionScroller {
7218 public abstract void start(int position);
7219 public abstract void start(int position, int boundPosition);
7220 public abstract void startWithOffset(int position, int offset);
7221 public abstract void startWithOffset(int position, int offset, int duration);
7222 public abstract void stop();
7223 }
7224
7225 /**
7226 * Default position scroller that simulates a fling.
7227 */
7228 class PositionScroller extends AbsPositionScroller implements Runnable {
7229 private static final int SCROLL_DURATION = 200;
7230
7231 private static final int MOVE_DOWN_POS = 1;
7232 private static final int MOVE_UP_POS = 2;
7233 private static final int MOVE_DOWN_BOUND = 3;
7234 private static final int MOVE_UP_BOUND = 4;
7235 private static final int MOVE_OFFSET = 5;
7236
7237 private int mMode;
7238 private int mTargetPos;
7239 private int mBoundPos;
7240 private int mLastSeenPos;
7241 private int mScrollDuration;
7242 private final int mExtraScroll;
7243
7244 private int mOffsetFromTop;
7245
7246 PositionScroller() {
7247 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7248 }
7249
7250 @Override
7251 public void start(final int position) {
7252 stop();
7253
7254 if (mDataChanged) {
7255 // Wait until we're back in a stable state to try this.
7256 mPositionScrollAfterLayout = new Runnable() {
7257 @Override public void run() {
7258 start(position);
7259 }
7260 };
7261 return;
7262 }
7263
7264 final int childCount = getChildCount();
7265 if (childCount == 0) {
7266 // Can't scroll without children.
7267 return;
7268 }
7269
7270 final int firstPos = mFirstPosition;
7271 final int lastPos = firstPos + childCount - 1;
7272
7273 int viewTravelCount;
7274 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7275 if (clampedPosition < firstPos) {
7276 viewTravelCount = firstPos - clampedPosition + 1;
7277 mMode = MOVE_UP_POS;
7278 } else if (clampedPosition > lastPos) {
7279 viewTravelCount = clampedPosition - lastPos + 1;
7280 mMode = MOVE_DOWN_POS;
7281 } else {
7282 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7283 return;
7284 }
7285
7286 if (viewTravelCount > 0) {
7287 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7288 } else {
7289 mScrollDuration = SCROLL_DURATION;
7290 }
7291 mTargetPos = clampedPosition;
7292 mBoundPos = INVALID_POSITION;
7293 mLastSeenPos = INVALID_POSITION;
7294
7295 postOnAnimation(this);
7296 }
7297
7298 @Override
7299 public void start(final int position, final int boundPosition) {
7300 stop();
7301
7302 if (boundPosition == INVALID_POSITION) {
7303 start(position);
7304 return;
7305 }
7306
7307 if (mDataChanged) {
7308 // Wait until we're back in a stable state to try this.
7309 mPositionScrollAfterLayout = new Runnable() {
7310 @Override public void run() {
7311 start(position, boundPosition);
7312 }
7313 };
7314 return;
7315 }
7316
7317 final int childCount = getChildCount();
7318 if (childCount == 0) {
7319 // Can't scroll without children.
7320 return;
7321 }
7322
7323 final int firstPos = mFirstPosition;
7324 final int lastPos = firstPos + childCount - 1;
7325
7326 int viewTravelCount;
7327 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7328 if (clampedPosition < firstPos) {
7329 final int boundPosFromLast = lastPos - boundPosition;
7330 if (boundPosFromLast < 1) {
7331 // Moving would shift our bound position off the screen. Abort.
7332 return;
7333 }
7334
7335 final int posTravel = firstPos - clampedPosition + 1;
7336 final int boundTravel = boundPosFromLast - 1;
7337 if (boundTravel < posTravel) {
7338 viewTravelCount = boundTravel;
7339 mMode = MOVE_UP_BOUND;
7340 } else {
7341 viewTravelCount = posTravel;
7342 mMode = MOVE_UP_POS;
7343 }
7344 } else if (clampedPosition > lastPos) {
7345 final int boundPosFromFirst = boundPosition - firstPos;
7346 if (boundPosFromFirst < 1) {
7347 // Moving would shift our bound position off the screen. Abort.
7348 return;
7349 }
7350
7351 final int posTravel = clampedPosition - lastPos + 1;
7352 final int boundTravel = boundPosFromFirst - 1;
7353 if (boundTravel < posTravel) {
7354 viewTravelCount = boundTravel;
7355 mMode = MOVE_DOWN_BOUND;
7356 } else {
7357 viewTravelCount = posTravel;
7358 mMode = MOVE_DOWN_POS;
7359 }
7360 } else {
7361 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7362 return;
7363 }
7364
7365 if (viewTravelCount > 0) {
7366 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7367 } else {
7368 mScrollDuration = SCROLL_DURATION;
7369 }
7370 mTargetPos = clampedPosition;
7371 mBoundPos = boundPosition;
7372 mLastSeenPos = INVALID_POSITION;
7373
7374 postOnAnimation(this);
7375 }
7376
7377 @Override
7378 public void startWithOffset(int position, int offset) {
7379 startWithOffset(position, offset, SCROLL_DURATION);
7380 }
7381
7382 @Override
7383 public void startWithOffset(final int position, int offset, final int duration) {
7384 stop();
7385
7386 if (mDataChanged) {
7387 // Wait until we're back in a stable state to try this.
7388 final int postOffset = offset;
7389 mPositionScrollAfterLayout = new Runnable() {
7390 @Override public void run() {
7391 startWithOffset(position, postOffset, duration);
7392 }
7393 };
7394 return;
7395 }
7396
7397 final int childCount = getChildCount();
7398 if (childCount == 0) {
7399 // Can't scroll without children.
7400 return;
7401 }
7402
7403 offset += getPaddingTop();
7404
7405 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7406 mOffsetFromTop = offset;
7407 mBoundPos = INVALID_POSITION;
7408 mLastSeenPos = INVALID_POSITION;
7409 mMode = MOVE_OFFSET;
7410
7411 final int firstPos = mFirstPosition;
7412 final int lastPos = firstPos + childCount - 1;
7413
7414 int viewTravelCount;
7415 if (mTargetPos < firstPos) {
7416 viewTravelCount = firstPos - mTargetPos;
7417 } else if (mTargetPos > lastPos) {
7418 viewTravelCount = mTargetPos - lastPos;
7419 } else {
7420 // On-screen, just scroll.
7421 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007422 smoothScrollBy(targetTop - offset, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007423 return;
7424 }
7425
7426 // Estimate how many screens we should travel
7427 final float screenTravelCount = (float) viewTravelCount / childCount;
7428 mScrollDuration = screenTravelCount < 1 ?
7429 duration : (int) (duration / screenTravelCount);
7430 mLastSeenPos = INVALID_POSITION;
7431
7432 postOnAnimation(this);
7433 }
7434
7435 /**
7436 * Scroll such that targetPos is in the visible padded region without scrolling
7437 * boundPos out of view. Assumes targetPos is onscreen.
7438 */
7439 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7440 final int firstPos = mFirstPosition;
7441 final int childCount = getChildCount();
7442 final int lastPos = firstPos + childCount - 1;
7443 final int paddedTop = mListPadding.top;
7444 final int paddedBottom = getHeight() - mListPadding.bottom;
7445
7446 if (targetPos < firstPos || targetPos > lastPos) {
7447 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7448 " not visible [" + firstPos + ", " + lastPos + "]");
7449 }
7450 if (boundPos < firstPos || boundPos > lastPos) {
7451 // boundPos doesn't matter, it's already offscreen.
7452 boundPos = INVALID_POSITION;
7453 }
7454
7455 final View targetChild = getChildAt(targetPos - firstPos);
7456 final int targetTop = targetChild.getTop();
7457 final int targetBottom = targetChild.getBottom();
7458 int scrollBy = 0;
7459
7460 if (targetBottom > paddedBottom) {
7461 scrollBy = targetBottom - paddedBottom;
7462 }
7463 if (targetTop < paddedTop) {
7464 scrollBy = targetTop - paddedTop;
7465 }
7466
7467 if (scrollBy == 0) {
7468 return;
7469 }
7470
7471 if (boundPos >= 0) {
7472 final View boundChild = getChildAt(boundPos - firstPos);
7473 final int boundTop = boundChild.getTop();
7474 final int boundBottom = boundChild.getBottom();
7475 final int absScroll = Math.abs(scrollBy);
7476
7477 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7478 // Don't scroll the bound view off the bottom of the screen.
7479 scrollBy = Math.max(0, boundBottom - paddedBottom);
7480 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7481 // Don't scroll the bound view off the top of the screen.
7482 scrollBy = Math.min(0, boundTop - paddedTop);
7483 }
7484 }
7485
7486 smoothScrollBy(scrollBy, duration);
7487 }
7488
7489 @Override
7490 public void stop() {
7491 removeCallbacks(this);
7492 }
7493
7494 @Override
7495 public void run() {
7496 final int listHeight = getHeight();
7497 final int firstPos = mFirstPosition;
7498
7499 switch (mMode) {
7500 case MOVE_DOWN_POS: {
7501 final int lastViewIndex = getChildCount() - 1;
7502 final int lastPos = firstPos + lastViewIndex;
7503
7504 if (lastViewIndex < 0) {
7505 return;
7506 }
7507
7508 if (lastPos == mLastSeenPos) {
7509 // No new views, let things keep going.
7510 postOnAnimation(this);
7511 return;
7512 }
7513
7514 final View lastView = getChildAt(lastViewIndex);
7515 final int lastViewHeight = lastView.getHeight();
7516 final int lastViewTop = lastView.getTop();
7517 final int lastViewPixelsShowing = listHeight - lastViewTop;
7518 final int extraScroll = lastPos < mItemCount - 1 ?
7519 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7520
7521 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007522 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007523
7524 mLastSeenPos = lastPos;
7525 if (lastPos < mTargetPos) {
7526 postOnAnimation(this);
7527 }
7528 break;
7529 }
7530
7531 case MOVE_DOWN_BOUND: {
7532 final int nextViewIndex = 1;
7533 final int childCount = getChildCount();
7534
7535 if (firstPos == mBoundPos || childCount <= nextViewIndex
7536 || firstPos + childCount >= mItemCount) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007537 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007538 return;
7539 }
7540 final int nextPos = firstPos + nextViewIndex;
7541
7542 if (nextPos == mLastSeenPos) {
7543 // No new views, let things keep going.
7544 postOnAnimation(this);
7545 return;
7546 }
7547
7548 final View nextView = getChildAt(nextViewIndex);
7549 final int nextViewHeight = nextView.getHeight();
7550 final int nextViewTop = nextView.getTop();
7551 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7552 if (nextPos < mBoundPos) {
7553 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007554 mScrollDuration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007555
7556 mLastSeenPos = nextPos;
7557
7558 postOnAnimation(this);
7559 } else {
7560 if (nextViewTop > extraScroll) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007561 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7562 } else {
7563 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007564 }
7565 }
7566 break;
7567 }
7568
7569 case MOVE_UP_POS: {
7570 if (firstPos == mLastSeenPos) {
7571 // No new views, let things keep going.
7572 postOnAnimation(this);
7573 return;
7574 }
7575
7576 final View firstView = getChildAt(0);
7577 if (firstView == null) {
7578 return;
7579 }
7580 final int firstViewTop = firstView.getTop();
7581 final int extraScroll = firstPos > 0 ?
7582 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7583
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007584 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7585 firstPos > mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007586
7587 mLastSeenPos = firstPos;
7588
7589 if (firstPos > mTargetPos) {
7590 postOnAnimation(this);
7591 }
7592 break;
7593 }
7594
7595 case MOVE_UP_BOUND: {
7596 final int lastViewIndex = getChildCount() - 2;
7597 if (lastViewIndex < 0) {
7598 return;
7599 }
7600 final int lastPos = firstPos + lastViewIndex;
7601
7602 if (lastPos == mLastSeenPos) {
7603 // No new views, let things keep going.
7604 postOnAnimation(this);
7605 return;
7606 }
7607
7608 final View lastView = getChildAt(lastViewIndex);
7609 final int lastViewHeight = lastView.getHeight();
7610 final int lastViewTop = lastView.getTop();
7611 final int lastViewPixelsShowing = listHeight - lastViewTop;
7612 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7613 mLastSeenPos = lastPos;
7614 if (lastPos > mBoundPos) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007615 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7616 true);
Alan Viveretted22db212014-02-13 17:47:38 -08007617 postOnAnimation(this);
7618 } else {
7619 final int bottom = listHeight - extraScroll;
7620 final int lastViewBottom = lastViewTop + lastViewHeight;
7621 if (bottom > lastViewBottom) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007622 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7623 } else {
7624 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007625 }
7626 }
7627 break;
7628 }
7629
7630 case MOVE_OFFSET: {
7631 if (mLastSeenPos == firstPos) {
7632 // No new views, let things keep going.
7633 postOnAnimation(this);
7634 return;
7635 }
7636
7637 mLastSeenPos = firstPos;
7638
7639 final int childCount = getChildCount();
7640 final int position = mTargetPos;
7641 final int lastPos = firstPos + childCount - 1;
7642
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007643 // Account for the visible "portion" of the first / last child when we estimate
7644 // how many screens we should travel to reach our target
7645 final View firstChild = getChildAt(0);
7646 final int firstChildHeight = firstChild.getHeight();
7647 final View lastChild = getChildAt(childCount - 1);
7648 final int lastChildHeight = lastChild.getHeight();
7649 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7650 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7651 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7652 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7653 / lastChildHeight;
7654
7655 float viewTravelCount = 0;
Alan Viveretted22db212014-02-13 17:47:38 -08007656 if (position < firstPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007657 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
Alan Viveretted22db212014-02-13 17:47:38 -08007658 } else if (position > lastPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007659 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
Alan Viveretted22db212014-02-13 17:47:38 -08007660 }
7661
7662 // Estimate how many screens we should travel
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007663 final float screenTravelCount = viewTravelCount / childCount;
Alan Viveretted22db212014-02-13 17:47:38 -08007664
7665 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7666 if (position < firstPos) {
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 if (position > lastPos) {
7672 final int distance = (int) (getHeight() * modifier);
7673 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007674 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007675 postOnAnimation(this);
7676 } else {
7677 // On-screen, just scroll.
7678 final int targetTop = getChildAt(position - firstPos).getTop();
7679 final int distance = targetTop - mOffsetFromTop;
7680 final int duration = (int) (mScrollDuration *
7681 ((float) Math.abs(distance) / getHeight()));
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007682 smoothScrollBy(distance, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007683 }
7684 break;
7685 }
7686
7687 default:
7688 break;
7689 }
7690 }
7691 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007692}