blob: edeb794faa5f0bd2db6e361aa3f9f9714dea28eb [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;
Evan Rosky8e5bd812018-01-22 09:36:41 -080022import android.annotation.TestApi;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070024import android.content.Intent;
Adam Powell2fe301d2016-08-15 16:34:37 -070025import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.res.TypedArray;
27import android.graphics.Canvas;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070031import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.os.Debug;
Yohei Yukawa612cce92016-02-11 17:47:33 -080033import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.os.Parcel;
35import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080036import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070037import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070039import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.text.TextUtils;
41import android.text.TextWatcher;
42import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080043import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070044import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080045import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070046import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070047import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070048import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070049import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.view.Gravity;
51import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080052import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.view.KeyEvent;
54import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070055import android.view.Menu;
56import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.view.MotionEvent;
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -080058import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059import android.view.VelocityTracker;
60import android.view.View;
61import android.view.ViewConfiguration;
62import android.view.ViewDebug;
63import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070064import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070065import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070067import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070068import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070069import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070070import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080071import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070072import android.view.animation.Interpolator;
73import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070074import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070075import android.view.inputmethod.CompletionInfo;
76import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070077import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070078import android.view.inputmethod.ExtractedText;
79import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070080import android.view.inputmethod.InputConnection;
Yohei Yukawa152944f2016-06-10 19:04:34 -070081import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070083import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084
Adam Cohen335c3b62012-07-24 17:18:16 -070085import com.android.internal.R;
86
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087import java.util.ArrayList;
88import java.util.List;
89
90/**
Romain Guyd6a463a2009-05-21 23:10:10 -070091 * Base class that can be used to implement virtualized lists of items. A list does
kopriva60b06bd2018-03-20 20:11:31 -070092 * not have a spatial definition here. For instance, subclasses of this class can
Romain Guyd6a463a2009-05-21 23:10:10 -070093 * 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 -080094 *
95 * @attr ref android.R.styleable#AbsListView_listSelector
96 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
97 * @attr ref android.R.styleable#AbsListView_stackFromBottom
98 * @attr ref android.R.styleable#AbsListView_scrollingCache
99 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
100 * @attr ref android.R.styleable#AbsListView_transcriptMode
101 * @attr ref android.R.styleable#AbsListView_cacheColorHint
102 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
103 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -0700104 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 */
106public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
107 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700108 ViewTreeObserver.OnTouchModeChangeListener,
109 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110
Romain Guy9d849a22012-03-14 16:41:42 -0700111 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800112 private static final String TAG = "AbsListView";
113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 /**
115 * Disables the transcript mode.
116 *
117 * @see #setTranscriptMode(int)
118 */
119 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700120
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 /**
122 * The list will automatically scroll to the bottom when a data set change
123 * notification is received and only if the last item is already visible
124 * on screen.
125 *
126 * @see #setTranscriptMode(int)
127 */
128 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700129
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 /**
131 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700132 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 *
134 * @see #setTranscriptMode(int)
135 */
136 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
137
138 /**
139 * Indicates that we are not in the middle of a touch gesture
140 */
141 static final int TOUCH_MODE_REST = -1;
142
143 /**
144 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
145 * scroll gesture.
146 */
147 static final int TOUCH_MODE_DOWN = 0;
148
149 /**
150 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
151 * is a longpress
152 */
153 static final int TOUCH_MODE_TAP = 1;
154
155 /**
156 * Indicates we have waited for everything we can wait for, but the user's finger is still down
157 */
158 static final int TOUCH_MODE_DONE_WAITING = 2;
159
160 /**
161 * Indicates the touch gesture is a scroll
162 */
163 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800164
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 /**
166 * Indicates the view is in the process of being flung
167 */
168 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 /**
Adam Powell637d3372010-08-25 14:37:03 -0700171 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
172 */
173 static final int TOUCH_MODE_OVERSCROLL = 5;
174
175 /**
176 * Indicates the view is being flung outside of normal content bounds
177 * and will spring back.
178 */
179 static final int TOUCH_MODE_OVERFLING = 6;
180
181 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 * Regular layout - usually an unsolicited layout from the view system
183 */
184 static final int LAYOUT_NORMAL = 0;
185
186 /**
187 * Show the first item
188 */
189 static final int LAYOUT_FORCE_TOP = 1;
190
191 /**
192 * Force the selected item to be on somewhere on the screen
193 */
194 static final int LAYOUT_SET_SELECTION = 2;
195
196 /**
197 * Show the last item
198 */
199 static final int LAYOUT_FORCE_BOTTOM = 3;
200
201 /**
202 * Make a mSelectedItem appear in a specific location and build the rest of
203 * the views from there. The top is specified by mSpecificTop.
204 */
205 static final int LAYOUT_SPECIFIC = 4;
206
207 /**
208 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
209 * at mSpecificTop
210 */
211 static final int LAYOUT_SYNC = 5;
212
213 /**
214 * Layout as a result of using the navigation keys
215 */
216 static final int LAYOUT_MOVE_SELECTION = 6;
217
218 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700219 * Normal list that does not indicate choices
220 */
221 public static final int CHOICE_MODE_NONE = 0;
222
223 /**
224 * The list allows up to one choice
225 */
226 public static final int CHOICE_MODE_SINGLE = 1;
227
228 /**
229 * The list allows multiple choices
230 */
231 public static final int CHOICE_MODE_MULTIPLE = 2;
232
233 /**
234 * The list allows multiple choices in a modal selection mode
235 */
236 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
237
238 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700239 * The thread that created this view.
240 */
241 private final Thread mOwnerThread;
242
243 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700244 * Controls if/how the user may choose/check items in the list
245 */
246 int mChoiceMode = CHOICE_MODE_NONE;
247
248 /**
249 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
250 */
251 ActionMode mChoiceActionMode;
252
253 /**
254 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
255 * a few extra actions around what application code does.
256 */
257 MultiChoiceModeWrapper mMultiChoiceModeCallback;
258
259 /**
260 * Running count of how many items are currently checked
261 */
262 int mCheckedItemCount;
263
264 /**
265 * Running state of which positions are currently checked
266 */
267 SparseBooleanArray mCheckStates;
268
269 /**
Adam Powell14c08042011-10-06 19:46:18 -0700270 * Running state of which IDs are currently checked.
271 * If there is a value for a given key, the checked state for that ID is true
272 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700273 */
Adam Powell14c08042011-10-06 19:46:18 -0700274 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700275
276 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 * Controls how the next layout will happen
278 */
279 int mLayoutMode = LAYOUT_NORMAL;
280
281 /**
282 * Should be used by subclasses to listen to changes in the dataset
283 */
284 AdapterDataSetObserver mDataSetObserver;
285
286 /**
287 * The adapter containing the data to be displayed by this view
288 */
289 ListAdapter mAdapter;
290
291 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700292 * The remote adapter containing the data to be displayed by this view to be set
293 */
294 private RemoteViewsAdapter mRemoteAdapter;
295
296 /**
Adam Powell539ee872012-02-03 19:00:49 -0800297 * If mAdapter != null, whenever this is true the adapter has stable IDs.
298 */
299 boolean mAdapterHasStableIds;
300
301 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700302 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
303 */
304 private boolean mDeferNotifyDataSetChanged = false;
305
306 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 * Indicates whether the list selector should be drawn on top of the children or behind
308 */
309 boolean mDrawSelectorOnTop = false;
310
311 /**
312 * The drawable used to draw the selector
313 */
314 Drawable mSelector;
315
316 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700317 * The current position of the selector in the list.
318 */
319 int mSelectorPosition = INVALID_POSITION;
320
321 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 * Defines the selector's location and dimension at drawing time
323 */
324 Rect mSelectorRect = new Rect();
325
326 /**
327 * The data set used to store unused views that should be reused during the next layout
328 * to avoid creating new ones
329 */
330 final RecycleBin mRecycler = new RecycleBin();
331
332 /**
333 * The selection's left padding
334 */
335 int mSelectionLeftPadding = 0;
336
337 /**
338 * The selection's top padding
339 */
340 int mSelectionTopPadding = 0;
341
342 /**
343 * The selection's right padding
344 */
345 int mSelectionRightPadding = 0;
346
347 /**
348 * The selection's bottom padding
349 */
350 int mSelectionBottomPadding = 0;
351
352 /**
353 * This view's padding
354 */
355 Rect mListPadding = new Rect();
356
357 /**
358 * Subclasses must retain their measure spec from onMeasure() into this member
359 */
360 int mWidthMeasureSpec = 0;
361
362 /**
363 * The top scroll indicator
364 */
365 View mScrollUp;
366
367 /**
368 * The down scroll indicator
369 */
370 View mScrollDown;
371
372 /**
373 * When the view is scrolling, this flag is set to true to indicate subclasses that
374 * the drawing cache was enabled on the children
375 */
376 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800377 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378
379 /**
380 * The position of the view that received the down motion event
381 */
382 int mMotionPosition;
383
384 /**
385 * The offset to the top of the mMotionPosition view when the down motion event was received
386 */
387 int mMotionViewOriginalTop;
388
389 /**
390 * The desired offset to the top of the mMotionPosition view after a scroll
391 */
392 int mMotionViewNewTop;
393
394 /**
395 * The X value associated with the the down motion event
396 */
397 int mMotionX;
398
399 /**
400 * The Y value associated with the the down motion event
401 */
402 int mMotionY;
403
404 /**
405 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
406 * TOUCH_MODE_DONE_WAITING
407 */
408 int mTouchMode = TOUCH_MODE_REST;
409
410 /**
411 * Y value from on the previous motion event (if any)
412 */
413 int mLastY;
414
415 /**
416 * How far the finger moved before we started scrolling
417 */
418 int mMotionCorrection;
419
420 /**
421 * Determines speed during touch scrolling
422 */
423 private VelocityTracker mVelocityTracker;
424
425 /**
426 * Handles one frame of a fling
427 */
428 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800429
Adam Powell45803472010-01-25 15:10:44 -0800430 /**
431 * Handles scrolling between positions within the list.
432 */
Alan Viveretted22db212014-02-13 17:47:38 -0800433 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434
435 /**
436 * The offset in pixels form the top of the AdapterView to the top
437 * of the currently selected view. Used to save and restore state.
438 */
439 int mSelectedTop = 0;
440
441 /**
442 * Indicates whether the list is stacked from the bottom edge or
443 * the top edge.
444 */
445 boolean mStackFromBottom;
446
447 /**
448 * When set to true, the list automatically discards the children's
449 * bitmap cache after scrolling.
450 */
451 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 /**
454 * Whether or not to enable the fast scroll feature on this list
455 */
456 boolean mFastScrollEnabled;
457
458 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700459 * Whether or not to always show the fast scroll feature on this list
460 */
461 boolean mFastScrollAlwaysVisible;
462
463 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 * Optional callback to notify client when scroll position has changed
465 */
466 private OnScrollListener mOnScrollListener;
467
468 /**
469 * Keeps track of our accessory window
470 */
471 PopupWindow mPopup;
472
473 /**
474 * Used with type filter window
475 */
476 EditText mTextFilter;
477
478 /**
479 * Indicates whether to use pixels-based or position-based scrollbar
480 * properties.
481 */
482 private boolean mSmoothScrollbarEnabled = true;
483
484 /**
485 * Indicates that this view supports filtering
486 */
487 private boolean mTextFilterEnabled;
488
489 /**
490 * Indicates that this view is currently displaying a filtered view of the data
491 */
492 private boolean mFiltered;
493
494 /**
495 * Rectangle used for hit testing children
496 */
497 private Rect mTouchFrame;
498
499 /**
500 * The position to resurrect the selected position to.
501 */
502 int mResurrectToPosition = INVALID_POSITION;
503
504 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800505
Adam Powell0b8bb422010-02-08 14:30:45 -0800506 /**
Adam Powell637d3372010-08-25 14:37:03 -0700507 * Maximum distance to record overscroll
508 */
509 int mOverscrollMax;
510
511 /**
512 * Content height divided by this is the overscroll limit.
513 */
514 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
515
516 /**
Adam Powell14c08042011-10-06 19:46:18 -0700517 * How many positions in either direction we will search to try to
518 * find a checked item with a stable ID that moved position across
519 * a data set change. If the item isn't found it will be unselected.
520 */
521 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
522
523 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 * Used to request a layout when we changed touch mode
525 */
526 private static final int TOUCH_MODE_UNKNOWN = -1;
527 private static final int TOUCH_MODE_ON = 0;
528 private static final int TOUCH_MODE_OFF = 1;
529
530 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
531
532 private static final boolean PROFILE_SCROLLING = false;
533 private boolean mScrollProfilingStarted = false;
534
535 private static final boolean PROFILE_FLINGING = false;
536 private boolean mFlingProfilingStarted = false;
537
538 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800539 * The StrictMode "critical time span" objects to catch animation
540 * stutters. Non-null when a time-sensitive animation is
541 * in-flight. Must call finish() on them when done animating.
542 * These are no-ops on user builds.
543 */
544 private StrictMode.Span mScrollStrictSpan = null;
545 private StrictMode.Span mFlingStrictSpan = null;
546
547 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 * The last CheckForLongPress runnable we posted, if any
549 */
550 private CheckForLongPress mPendingCheckForLongPress;
551
552 /**
553 * The last CheckForTap runnable we posted, if any
554 */
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700555 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700556
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 /**
558 * The last CheckForKeyLongPress runnable we posted, if any
559 */
560 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
561
562 /**
563 * Acts upon click
564 */
565 private AbsListView.PerformClick mPerformClick;
566
567 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800568 * Delayed action for touch mode.
569 */
570 private Runnable mTouchModeReset;
571
572 /**
Alan Viverette66df60f2016-01-28 14:56:07 -0500573 * Whether the most recent touch event stream resulted in a successful
574 * long-press action. This is reset on TOUCH_DOWN.
575 */
576 private boolean mHasPerformedLongPress;
577
578 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 * This view is in transcript mode -- it shows the bottom of the list when the data
580 * changes
581 */
582 private int mTranscriptMode;
583
584 /**
585 * Indicates that this list is always drawn on top of a solid, single-color, opaque
586 * background
587 */
588 private int mCacheColorHint;
589
590 /**
591 * The select child's view (from the adapter's getView) is enabled.
592 */
593 private boolean mIsChildViewEnabled;
594
595 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800596 * The cached drawable state for the selector. Accounts for child enabled
597 * state, but otherwise identical to the view's own drawable state.
598 */
599 private int[] mSelectorState;
600
601 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 * The last scroll state reported to clients through {@link OnScrollListener}.
603 */
604 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
605
606 /**
607 * Helper object that renders and controls the fast scroll thumb.
608 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700609 private FastScroller mFastScroll;
610
611 /**
612 * Temporary holder for fast scroller style until a FastScroller object
613 * is created.
614 */
615 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616
Romain Guyd6a463a2009-05-21 23:10:10 -0700617 private boolean mGlobalLayoutListenerAddedFilter;
618
619 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 private float mDensityScale;
621
Aaron Whytef8306522017-03-22 16:30:58 -0700622 private float mVerticalScrollFactor;
Ned Burns20ad0732016-08-18 14:22:57 -0400623
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700624 private InputConnection mDefInputConnection;
625 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700626
627 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700628 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700629 private int mMinimumVelocity;
630 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700631 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800632
Romain Guy21875052010-01-06 18:48:08 -0800633 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800634
Adam Powell96d62af2014-05-02 10:04:38 -0700635 private final int[] mScrollOffset = new int[2];
636 private final int[] mScrollConsumed = new int[2];
637
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800638 private final float[] mTmpPoint = new float[2];
639
Adam Powell744beff2014-09-22 09:47:48 -0700640 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
641 // In the future it would be nice to be able to give this to the VelocityTracker
642 // directly, or alternatively put a VT into absolute-positioning mode that only
643 // reads the raw screen-coordinate x/y values.
644 private int mNestedYOffset = 0;
645
Romain Guy24562482010-02-01 14:56:19 -0800646 // True when the popup should be hidden because of a call to
647 // dispatchDisplayHint()
648 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800649
Adam Powell4cd47702010-02-25 11:21:14 -0800650 /**
651 * ID of the active pointer. This is used to retain consistency during
652 * drags/flings if multiple pointers are used.
653 */
654 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800655
Adam Powell4cd47702010-02-25 11:21:14 -0800656 /**
657 * Sentinel value for no current active pointer.
658 * Used by {@link #mActivePointerId}.
659 */
660 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700661
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 /**
Adam Powell637d3372010-08-25 14:37:03 -0700663 * Maximum distance to overscroll by during edge effects
664 */
665 int mOverscrollDistance;
666
667 /**
668 * Maximum distance to overfling during edge effects
669 */
670 int mOverflingDistance;
671
672 // These two EdgeGlows are always set and used together.
673 // Checking one for null is as good as checking both.
674
675 /**
676 * Tracks the state of the top edge glow.
677 */
Adam Powell89935e42011-08-31 14:26:12 -0700678 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700679
680 /**
681 * Tracks the state of the bottom edge glow.
682 */
Adam Powell89935e42011-08-31 14:26:12 -0700683 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700684
685 /**
686 * An estimate of how many pixels are between the top of the list and
687 * the top of the first position in the adapter, based on the last time
688 * we saw it. Used to hint where to draw edge glows.
689 */
690 private int mFirstPositionDistanceGuess;
691
692 /**
693 * An estimate of how many pixels are between the bottom of the list and
694 * the bottom of the last position in the adapter, based on the last time
695 * we saw it. Used to hint where to draw edge glows.
696 */
697 private int mLastPositionDistanceGuess;
698
699 /**
700 * Used for determining when to cancel out of overscroll.
701 */
702 private int mDirection = 0;
703
704 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800705 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
706 * the bottom correctly on resizes.
707 */
708 private boolean mForceTranscriptScroll;
709
alanvc1d7e772012-05-08 14:47:24 -0700710 /**
711 * Used for interacting with list items from an accessibility service.
712 */
713 private ListItemAccessibilityDelegate mAccessibilityDelegate;
714
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700715 private int mLastAccessibilityScrollEventFromIndex;
716 private int mLastAccessibilityScrollEventToIndex;
717
Adam Powellda13dba2010-12-05 13:47:23 -0800718 /**
Adam Powellee78b172011-08-16 16:39:20 -0700719 * Track the item count from the last time we handled a data change.
720 */
721 private int mLastHandledItemCount;
722
723 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700724 * Used for smooth scrolling at a consistent rate
725 */
726 static final Interpolator sLinearInterpolator = new LinearInterpolator();
727
728 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700729 * The saved state that we will be restoring from when we next sync.
730 * Kept here so that if we happen to be asked to save our state before
731 * the sync happens, we can return this existing data rather than losing
732 * it.
733 */
734 private SavedState mPendingSync;
735
736 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800737 * Whether the view is in the process of detaching from its window.
738 */
739 private boolean mIsDetaching;
740
741 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 * Interface definition for a callback to be invoked when the list or grid
743 * has been scrolled.
744 */
745 public interface OnScrollListener {
746
747 /**
748 * The view is not scrolling. Note navigating the list using the trackball counts as
749 * being in the idle state since these transitions are not animated.
750 */
751 public static int SCROLL_STATE_IDLE = 0;
752
753 /**
754 * The user is scrolling using touch, and their finger is still on the screen
755 */
756 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
757
758 /**
759 * The user had previously been scrolling using touch and had performed a fling. The
760 * animation is now coasting to a stop
761 */
762 public static int SCROLL_STATE_FLING = 2;
763
764 /**
765 * Callback method to be invoked while the list view or grid view is being scrolled. If the
766 * view is being scrolled, this method will be called before the next frame of the scroll is
767 * rendered. In particular, it will be called before any calls to
768 * {@link Adapter#getView(int, View, ViewGroup)}.
769 *
770 * @param view The view whose scroll state is being reported
771 *
Yorke Lee43943d82014-05-08 10:15:20 -0700772 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
774 */
775 public void onScrollStateChanged(AbsListView view, int scrollState);
776
777 /**
778 * Callback method to be invoked when the list or grid has been scrolled. This will be
779 * called after the scroll has completed
780 * @param view The view whose scroll state is being reported
781 * @param firstVisibleItem the index of the first visible cell (ignore if
782 * visibleItemCount == 0)
783 * @param visibleItemCount the number of visible cells
784 * @param totalItemCount the number of items in the list adaptor
785 */
786 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
787 int totalItemCount);
788 }
789
Dianne Hackborne2136772010-11-04 15:08:59 -0700790 /**
791 * The top-level view of a list item can implement this interface to allow
792 * itself to modify the bounds of the selection shown for that item.
793 */
794 public interface SelectionBoundsAdjuster {
795 /**
796 * Called to allow the list item to adjust the bounds shown for
797 * its selection.
798 *
799 * @param bounds On call, this contains the bounds the list has
800 * selected for the item (that is the bounds of the entire view). The
801 * values can be modified as desired.
802 */
803 public void adjustListItemSelectionBounds(Rect bounds);
804 }
805
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 public AbsListView(Context context) {
807 super(context);
808 initAbsListView();
809
Alan Viverette39bed692013-08-07 15:47:04 -0700810 mOwnerThread = Thread.currentThread();
811
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800812 setVerticalScrollBarEnabled(true);
813 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700814 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 a.recycle();
816 }
817
818 public AbsListView(Context context, AttributeSet attrs) {
819 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
820 }
821
Alan Viverette617feb92013-09-09 18:09:13 -0700822 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
823 this(context, attrs, defStyleAttr, 0);
824 }
825
826 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
827 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828 initAbsListView();
829
Alan Viverette39bed692013-08-07 15:47:04 -0700830 mOwnerThread = Thread.currentThread();
831
Alan Viverette617feb92013-09-09 18:09:13 -0700832 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700833 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834
Alan Viverette7eceda32015-06-01 10:47:29 -0700835 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
836 if (selector != null) {
837 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 }
839
Alan Viverette7eceda32015-06-01 10:47:29 -0700840 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800841
Alan Viverette7eceda32015-06-01 10:47:29 -0700842 setStackFromBottom(a.getBoolean(
843 R.styleable.AbsListView_stackFromBottom, false));
844 setScrollingCacheEnabled(a.getBoolean(
845 R.styleable.AbsListView_scrollingCache, true));
846 setTextFilterEnabled(a.getBoolean(
847 R.styleable.AbsListView_textFilterEnabled, false));
848 setTranscriptMode(a.getInt(
849 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
850 setCacheColorHint(a.getColor(
851 R.styleable.AbsListView_cacheColorHint, 0));
852 setSmoothScrollbarEnabled(a.getBoolean(
853 R.styleable.AbsListView_smoothScrollbar, true));
854 setChoiceMode(a.getInt(
855 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856
Alan Viverette7eceda32015-06-01 10:47:29 -0700857 setFastScrollEnabled(a.getBoolean(
858 R.styleable.AbsListView_fastScrollEnabled, false));
859 setFastScrollStyle(a.getResourceId(
860 R.styleable.AbsListView_fastScrollStyle, 0));
861 setFastScrollAlwaysVisible(a.getBoolean(
862 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 a.recycle();
Adam Powell2fe301d2016-08-15 16:34:37 -0700865
866 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
867 setRevealOnFocusHint(false);
868 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 }
870
Romain Guyd6a463a2009-05-21 23:10:10 -0700871 private void initAbsListView() {
872 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700873 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700874 setFocusableInTouchMode(true);
875 setWillNotDraw(false);
876 setAlwaysDrawnWithCacheEnabled(false);
877 setScrollingCacheEnabled(true);
878
Romain Guy4296fc42009-07-06 11:48:52 -0700879 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
880 mTouchSlop = configuration.getScaledTouchSlop();
Aaron Whytef8306522017-03-22 16:30:58 -0700881 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
Romain Guy4296fc42009-07-06 11:48:52 -0700882 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
883 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700884 mOverscrollDistance = configuration.getScaledOverscrollDistance();
885 mOverflingDistance = configuration.getScaledOverflingDistance();
886
Romain Guyd6a463a2009-05-21 23:10:10 -0700887 mDensityScale = getContext().getResources().getDisplayMetrics().density;
888 }
Romain Guy0a637162009-05-29 14:43:54 -0700889
Adam Powell637d3372010-08-25 14:37:03 -0700890 @Override
891 public void setOverScrollMode(int mode) {
892 if (mode != OVER_SCROLL_NEVER) {
893 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800894 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700895 mEdgeGlowTop = new EdgeEffect(context);
896 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700897 }
898 } else {
899 mEdgeGlowTop = null;
900 mEdgeGlowBottom = null;
901 }
902 super.setOverScrollMode(mode);
903 }
904
Romain Guyd6a463a2009-05-21 23:10:10 -0700905 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700906 * {@inheritDoc}
907 */
908 @Override
909 public void setAdapter(ListAdapter adapter) {
910 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800911 mAdapterHasStableIds = mAdapter.hasStableIds();
912 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700913 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700914 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700915 }
916 }
Sumir Kataria37b85672017-07-11 15:35:30 -0700917 clearChoices();
Adam Powellf343e1b2010-08-13 18:27:04 -0700918 }
919
920 /**
921 * Returns the number of items currently selected. This will only be valid
922 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
923 *
924 * <p>To determine the specific items that are currently selected, use one of
925 * the <code>getChecked*</code> methods.
926 *
927 * @return The number of items currently selected
928 *
929 * @see #getCheckedItemPosition()
930 * @see #getCheckedItemPositions()
931 * @see #getCheckedItemIds()
932 */
933 public int getCheckedItemCount() {
934 return mCheckedItemCount;
935 }
936
937 /**
938 * Returns the checked state of the specified position. The result is only
939 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
940 * or {@link #CHOICE_MODE_MULTIPLE}.
941 *
942 * @param position The item whose checked state to return
943 * @return The item's checked state or <code>false</code> if choice mode
944 * is invalid
945 *
946 * @see #setChoiceMode(int)
947 */
948 public boolean isItemChecked(int position) {
949 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
950 return mCheckStates.get(position);
951 }
952
953 return false;
954 }
955
956 /**
957 * Returns the currently checked item. The result is only valid if the choice
958 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
959 *
960 * @return The position of the currently checked item or
961 * {@link #INVALID_POSITION} if nothing is selected
962 *
963 * @see #setChoiceMode(int)
964 */
965 public int getCheckedItemPosition() {
966 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
967 return mCheckStates.keyAt(0);
968 }
969
970 return INVALID_POSITION;
971 }
972
973 /**
974 * Returns the set of checked items in the list. The result is only valid if
975 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
976 *
977 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200978 * get(int position) where position is a checked position in the
979 * list and false otherwise, or <code>null</code> if the choice
980 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700981 */
982 public SparseBooleanArray getCheckedItemPositions() {
983 if (mChoiceMode != CHOICE_MODE_NONE) {
984 return mCheckStates;
985 }
986 return null;
987 }
988
989 /**
990 * Returns the set of checked items ids. The result is only valid if the
991 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
992 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
993 *
994 * @return A new array which contains the id of each checked item in the
995 * list.
996 */
997 public long[] getCheckedItemIds() {
998 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
999 return new long[0];
1000 }
1001
Adam Powell14c08042011-10-06 19:46:18 -07001002 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -07001003 final int count = idStates.size();
1004 final long[] ids = new long[count];
1005
1006 for (int i = 0; i < count; i++) {
1007 ids[i] = idStates.keyAt(i);
1008 }
1009
1010 return ids;
1011 }
1012
1013 /**
1014 * Clear any choices previously set
1015 */
1016 public void clearChoices() {
1017 if (mCheckStates != null) {
1018 mCheckStates.clear();
1019 }
1020 if (mCheckedIdStates != null) {
1021 mCheckedIdStates.clear();
1022 }
1023 mCheckedItemCount = 0;
1024 }
1025
1026 /**
1027 * Sets the checked state of the specified position. The is only valid if
1028 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1029 * {@link #CHOICE_MODE_MULTIPLE}.
1030 *
1031 * @param position The item whose checked state is to be checked
1032 * @param value The new checked state for the item
1033 */
1034 public void setItemChecked(int position, boolean value) {
1035 if (mChoiceMode == CHOICE_MODE_NONE) {
1036 return;
1037 }
1038
1039 // Start selection mode if needed. We don't need to if we're unchecking something.
1040 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001041 if (mMultiChoiceModeCallback == null ||
1042 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1043 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1044 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1045 "supplied. Call setMultiChoiceModeListener to set a callback.");
1046 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001047 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1048 }
1049
Siyamed Sinir135554e2016-01-22 18:40:42 -08001050 final boolean itemCheckChanged;
Adam Powellf343e1b2010-08-13 18:27:04 -07001051 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1052 boolean oldValue = mCheckStates.get(position);
1053 mCheckStates.put(position, value);
1054 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1055 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001056 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001057 } else {
1058 mCheckedIdStates.delete(mAdapter.getItemId(position));
1059 }
1060 }
Siyamed Sinir135554e2016-01-22 18:40:42 -08001061 itemCheckChanged = oldValue != value;
1062 if (itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001063 if (value) {
1064 mCheckedItemCount++;
1065 } else {
1066 mCheckedItemCount--;
1067 }
1068 }
1069 if (mChoiceActionMode != null) {
1070 final long id = mAdapter.getItemId(position);
1071 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1072 position, id, value);
1073 }
1074 } else {
1075 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1076 // Clear all values if we're checking something, or unchecking the currently
1077 // selected item
Siyamed Sinir135554e2016-01-22 18:40:42 -08001078 itemCheckChanged = isItemChecked(position) != value;
Adam Powellf343e1b2010-08-13 18:27:04 -07001079 if (value || isItemChecked(position)) {
1080 mCheckStates.clear();
1081 if (updateIds) {
1082 mCheckedIdStates.clear();
1083 }
1084 }
1085 // this may end up selecting the value we just cleared but this way
1086 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1087 if (value) {
1088 mCheckStates.put(position, true);
1089 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001090 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001091 }
1092 mCheckedItemCount = 1;
1093 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1094 mCheckedItemCount = 0;
1095 }
1096 }
1097
Siyamed Sinir135554e2016-01-22 18:40:42 -08001098 // Do not generate a data change while we are in the layout phase or data has not changed
1099 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001100 mDataChanged = true;
1101 rememberSyncState();
1102 requestLayout();
1103 }
1104 }
1105
1106 @Override
1107 public boolean performItemClick(View view, int position, long id) {
1108 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001109 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001110
1111 if (mChoiceMode != CHOICE_MODE_NONE) {
1112 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001113 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001114
1115 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1116 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001117 boolean checked = !mCheckStates.get(position, false);
1118 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001119 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001120 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001121 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001122 } else {
1123 mCheckedIdStates.delete(mAdapter.getItemId(position));
1124 }
1125 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001126 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001127 mCheckedItemCount++;
1128 } else {
1129 mCheckedItemCount--;
1130 }
1131 if (mChoiceActionMode != null) {
1132 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001133 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001134 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001135 }
Adam Powell29382d92012-02-23 11:03:22 -08001136 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001137 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001138 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001139 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001140 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001141 mCheckStates.put(position, true);
1142 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1143 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001144 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001145 }
1146 mCheckedItemCount = 1;
1147 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1148 mCheckedItemCount = 0;
1149 }
Adam Powell29382d92012-02-23 11:03:22 -08001150 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001151 }
1152
Adam Powell29382d92012-02-23 11:03:22 -08001153 if (checkedStateChanged) {
1154 updateOnScreenCheckedViews();
1155 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001156 }
1157
Adam Powellbf5f2b32010-10-24 16:45:44 -07001158 if (dispatchItemClick) {
1159 handled |= super.performItemClick(view, position, id);
1160 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001161
1162 return handled;
1163 }
1164
1165 /**
Adam Powell29382d92012-02-23 11:03:22 -08001166 * Perform a quick, in-place update of the checked or activated state
1167 * on all visible item views. This should only be called when a valid
1168 * choice mode is active.
1169 */
1170 private void updateOnScreenCheckedViews() {
1171 final int firstPos = mFirstPosition;
1172 final int count = getChildCount();
1173 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1174 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1175 for (int i = 0; i < count; i++) {
1176 final View child = getChildAt(i);
1177 final int position = firstPos + i;
1178
1179 if (child instanceof Checkable) {
1180 ((Checkable) child).setChecked(mCheckStates.get(position));
1181 } else if (useActivated) {
1182 child.setActivated(mCheckStates.get(position));
1183 }
1184 }
1185 }
1186
1187 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001188 * @see #setChoiceMode(int)
1189 *
1190 * @return The current choice mode
1191 */
1192 public int getChoiceMode() {
1193 return mChoiceMode;
1194 }
1195
1196 /**
1197 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1198 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1199 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1200 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1201 *
1202 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1203 * {@link #CHOICE_MODE_MULTIPLE}
1204 */
1205 public void setChoiceMode(int choiceMode) {
1206 mChoiceMode = choiceMode;
1207 if (mChoiceActionMode != null) {
1208 mChoiceActionMode.finish();
1209 mChoiceActionMode = null;
1210 }
1211 if (mChoiceMode != CHOICE_MODE_NONE) {
1212 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001213 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001214 }
1215 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001216 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001217 }
1218 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1219 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1220 clearChoices();
1221 setLongClickable(true);
1222 }
1223 }
1224 }
1225
1226 /**
1227 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1228 * selection {@link ActionMode}. Only used when the choice mode is set to
1229 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1230 *
1231 * @param listener Listener that will manage the selection mode
1232 *
1233 * @see #setChoiceMode(int)
1234 */
1235 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1236 if (mMultiChoiceModeCallback == null) {
1237 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1238 }
1239 mMultiChoiceModeCallback.setWrapped(listener);
1240 }
1241
1242 /**
Adam Powell637d3372010-08-25 14:37:03 -07001243 * @return true if all list content currently fits within the view boundaries
1244 */
1245 private boolean contentFits() {
1246 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001247 if (childCount == 0) return true;
1248 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001249
Adam Powell4ce35412011-01-24 14:55:00 -08001250 return getChildAt(0).getTop() >= mListPadding.top &&
1251 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001252 }
1253
1254 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001255 * Specifies whether fast scrolling is enabled or disabled.
1256 * <p>
1257 * When fast scrolling is enabled, the user can quickly scroll through lists
1258 * by dragging the fast scroll thumb.
1259 * <p>
1260 * If the adapter backing this list implements {@link SectionIndexer}, the
1261 * fast scroller will display section header previews as the user scrolls.
1262 * Additionally, the user will be able to quickly jump between sections by
1263 * tapping along the length of the scroll bar.
1264 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001265 * @see SectionIndexer
1266 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001267 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268 */
Alan Viverette39bed692013-08-07 15:47:04 -07001269 public void setFastScrollEnabled(final boolean enabled) {
1270 if (mFastScrollEnabled != enabled) {
1271 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001272
Alan Viverette39bed692013-08-07 15:47:04 -07001273 if (isOwnerThread()) {
1274 setFastScrollerEnabledUiThread(enabled);
1275 } else {
1276 post(new Runnable() {
1277 @Override
1278 public void run() {
1279 setFastScrollerEnabledUiThread(enabled);
1280 }
1281 });
1282 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001283 }
Alan Viverette39bed692013-08-07 15:47:04 -07001284 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001285
Alan Viverette39bed692013-08-07 15:47:04 -07001286 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001287 if (mFastScroll != null) {
1288 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001289 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001290 mFastScroll = new FastScroller(this, mFastScrollStyle);
1291 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001292 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001293
Alan Viveretteb9f27222013-09-06 19:39:47 -07001294 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001295
Alan Viverette8636ace2013-10-31 15:41:31 -07001296 if (mFastScroll != null) {
1297 mFastScroll.updateLayout();
1298 }
1299 }
1300
1301 /**
1302 * Specifies the style of the fast scroller decorations.
1303 *
1304 * @param styleResId style resource containing fast scroller properties
1305 * @see android.R.styleable#FastScroll
1306 */
1307 public void setFastScrollStyle(int styleResId) {
1308 if (mFastScroll == null) {
1309 mFastScrollStyle = styleResId;
1310 } else {
1311 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 }
Romain Guy0a637162009-05-29 14:43:54 -07001314
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001316 * Set whether or not the fast scroller should always be shown in place of
1317 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001318 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001319 * <p>
1320 * Fast scrollers shown in this way will not fade out and will be a
1321 * permanent fixture within the list. This is best combined with an inset
1322 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001323 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001324 * @param alwaysShow true if the fast scroller should always be displayed,
1325 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001326 * @see #setScrollBarStyle(int)
1327 * @see #setFastScrollEnabled(boolean)
1328 */
Alan Viverette39bed692013-08-07 15:47:04 -07001329 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1330 if (mFastScrollAlwaysVisible != alwaysShow) {
1331 if (alwaysShow && !mFastScrollEnabled) {
1332 setFastScrollEnabled(true);
1333 }
Adam Powell20232d02010-12-08 21:08:53 -08001334
Alan Viverette39bed692013-08-07 15:47:04 -07001335 mFastScrollAlwaysVisible = alwaysShow;
1336
1337 if (isOwnerThread()) {
1338 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1339 } else {
1340 post(new Runnable() {
1341 @Override
1342 public void run() {
1343 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1344 }
1345 });
1346 }
1347 }
1348 }
1349
1350 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001351 if (mFastScroll != null) {
1352 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001353 }
Alan Viverette39bed692013-08-07 15:47:04 -07001354 }
Adam Powell20232d02010-12-08 21:08:53 -08001355
Alan Viverette39bed692013-08-07 15:47:04 -07001356 /**
1357 * @return whether the current thread is the one that created the view
1358 */
1359 private boolean isOwnerThread() {
1360 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001361 }
1362
1363 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001364 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001365 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001366 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001367 * @see #setFastScrollAlwaysVisible(boolean)
1368 */
1369 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001370 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001371 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1372 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001373 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001374 }
Adam Powell20232d02010-12-08 21:08:53 -08001375 }
1376
1377 @Override
1378 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001379 if (mFastScroll != null && mFastScroll.isEnabled()) {
1380 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001381 }
1382 return super.getVerticalScrollbarWidth();
1383 }
1384
1385 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001386 * Returns true if the fast scroller is enabled.
1387 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001388 * @see #setFastScrollEnabled(boolean)
1389 * @return true if fast scroll is enabled, false otherwise
1390 */
1391 @ViewDebug.ExportedProperty
1392 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001393 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001394 return mFastScrollEnabled;
1395 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001396 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001397 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398 }
Romain Guy0a637162009-05-29 14:43:54 -07001399
Adam Powell20232d02010-12-08 21:08:53 -08001400 @Override
1401 public void setVerticalScrollbarPosition(int position) {
1402 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001403 if (mFastScroll != null) {
1404 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001405 }
1406 }
1407
Alan Viverette26bb2532013-08-09 10:40:50 -07001408 @Override
1409 public void setScrollBarStyle(int style) {
1410 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001411 if (mFastScroll != null) {
1412 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001413 }
1414 }
1415
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001417 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001418 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419 */
1420 @Override
1421 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001422 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001423 }
1424
1425 /**
1426 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1427 * is computed based on the number of visible pixels in the visible items. This
1428 * however assumes that all list items have the same height. If you use a list in
1429 * which items have different heights, the scrollbar will change appearance as the
1430 * user scrolls through the list. To avoid this issue, you need to disable this
1431 * property.
1432 *
1433 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1434 * is based solely on the number of items in the adapter and the position of the
1435 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001436 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 *
1438 * @param enabled Whether or not to enable smooth scrollbar.
1439 *
Romain Guy0a637162009-05-29 14:43:54 -07001440 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001441 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1442 */
1443 public void setSmoothScrollbarEnabled(boolean enabled) {
1444 mSmoothScrollbarEnabled = enabled;
1445 }
1446
1447 /**
1448 * Returns the current state of the fast scroll feature.
1449 *
1450 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1451 *
1452 * @see #setSmoothScrollbarEnabled(boolean)
1453 */
1454 @ViewDebug.ExportedProperty
1455 public boolean isSmoothScrollbarEnabled() {
1456 return mSmoothScrollbarEnabled;
1457 }
1458
1459 /**
1460 * Set the listener that will receive notifications every time the list scrolls.
1461 *
1462 * @param l the scroll listener
1463 */
1464 public void setOnScrollListener(OnScrollListener l) {
1465 mOnScrollListener = l;
1466 invokeOnItemScrollListener();
1467 }
1468
1469 /**
1470 * Notify our scroll listener (if there is one) of a change in scroll state
1471 */
1472 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001473 if (mFastScroll != null) {
1474 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001475 }
1476 if (mOnScrollListener != null) {
1477 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1478 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001479 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001480 }
1481
Alan Viverettea54956a2015-01-07 16:05:02 -08001482 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001483 @Override
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001484 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001485 // Since this class calls onScrollChanged even if the mFirstPosition and the
1486 // child count have not changed we will avoid sending duplicate accessibility
1487 // events.
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001488 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001489 final int firstVisiblePosition = getFirstVisiblePosition();
1490 final int lastVisiblePosition = getLastVisiblePosition();
1491 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1492 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001493 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001494 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001495 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1496 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001497 }
1498 }
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001499 super.sendAccessibilityEventUnchecked(event);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001500 }
1501
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001502 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001503 public CharSequence getAccessibilityClassName() {
1504 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001505 }
1506
Alan Viverettea54956a2015-01-07 16:05:02 -08001507 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001508 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001509 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1510 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001511 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001512 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001513 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001514 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001515 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001516 }
Alan Viverette947a9692014-09-25 12:43:47 -07001517 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001518 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001519 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001520 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001521 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001522 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001523
1524 info.removeAction(AccessibilityAction.ACTION_CLICK);
1525 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001526 }
1527
Alan Viverette76769ae2014-02-12 16:38:10 -08001528 int getSelectionModeForAccessibility() {
1529 final int choiceMode = getChoiceMode();
1530 switch (choiceMode) {
1531 case CHOICE_MODE_NONE:
1532 return CollectionInfo.SELECTION_MODE_NONE;
1533 case CHOICE_MODE_SINGLE:
1534 return CollectionInfo.SELECTION_MODE_SINGLE;
1535 case CHOICE_MODE_MULTIPLE:
1536 case CHOICE_MODE_MULTIPLE_MODAL:
1537 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1538 default:
1539 return CollectionInfo.SELECTION_MODE_NONE;
1540 }
1541 }
1542
Alan Viverettea54956a2015-01-07 16:05:02 -08001543 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001544 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001545 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1546 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001547 return true;
1548 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001549 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001550 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1551 case R.id.accessibilityActionScrollDown: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001552 if (isEnabled() && canScrollDown()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001553 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1554 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1555 return true;
1556 }
1557 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001558 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1559 case R.id.accessibilityActionScrollUp: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001560 if (isEnabled() && canScrollUp()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001561 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1562 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1563 return true;
1564 }
1565 } return false;
1566 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001567 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001568 }
1569
Svetoslav5b578da2013-05-08 14:23:32 -07001570 /** @hide */
1571 @Override
1572 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1573 if (accessibilityId == getAccessibilityViewId()) {
1574 return this;
1575 }
Svetoslav5b578da2013-05-08 14:23:32 -07001576 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1577 }
1578
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001579 /**
1580 * Indicates whether the children's drawing cache is used during a scroll.
1581 * By default, the drawing cache is enabled but this will consume more memory.
1582 *
1583 * @return true if the scrolling cache is enabled, false otherwise
1584 *
1585 * @see #setScrollingCacheEnabled(boolean)
1586 * @see View#setDrawingCacheEnabled(boolean)
1587 */
1588 @ViewDebug.ExportedProperty
1589 public boolean isScrollingCacheEnabled() {
1590 return mScrollingCacheEnabled;
1591 }
1592
1593 /**
1594 * Enables or disables the children's drawing cache during a scroll.
1595 * By default, the drawing cache is enabled but this will use more memory.
1596 *
1597 * When the scrolling cache is enabled, the caches are kept after the
1598 * first scrolling. You can manually clear the cache by calling
1599 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1600 *
1601 * @param enabled true to enable the scroll cache, false otherwise
1602 *
1603 * @see #isScrollingCacheEnabled()
1604 * @see View#setDrawingCacheEnabled(boolean)
1605 */
1606 public void setScrollingCacheEnabled(boolean enabled) {
1607 if (mScrollingCacheEnabled && !enabled) {
1608 clearScrollingCache();
1609 }
1610 mScrollingCacheEnabled = enabled;
1611 }
1612
1613 /**
1614 * Enables or disables the type filter window. If enabled, typing when
1615 * this view has focus will filter the children to match the users input.
1616 * Note that the {@link Adapter} used by this view must implement the
1617 * {@link Filterable} interface.
1618 *
1619 * @param textFilterEnabled true to enable type filtering, false otherwise
1620 *
1621 * @see Filterable
1622 */
1623 public void setTextFilterEnabled(boolean textFilterEnabled) {
1624 mTextFilterEnabled = textFilterEnabled;
1625 }
1626
1627 /**
1628 * Indicates whether type filtering is enabled for this view
1629 *
1630 * @return true if type filtering is enabled, false otherwise
1631 *
1632 * @see #setTextFilterEnabled(boolean)
1633 * @see Filterable
1634 */
1635 @ViewDebug.ExportedProperty
1636 public boolean isTextFilterEnabled() {
1637 return mTextFilterEnabled;
1638 }
1639
1640 @Override
1641 public void getFocusedRect(Rect r) {
1642 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001643 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001644 // the focused rectangle of the selected view offset into the
1645 // coordinate space of this view.
1646 view.getFocusedRect(r);
1647 offsetDescendantRectToMyCoords(view, r);
1648 } else {
1649 // otherwise, just the norm
1650 super.getFocusedRect(r);
1651 }
1652 }
1653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001654 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001655 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001656 com.android.internal.R.drawable.list_selector_background));
1657 }
1658
1659 /**
1660 * Indicates whether the content of this view is pinned to, or stacked from,
1661 * the bottom edge.
1662 *
1663 * @return true if the content is stacked from the bottom edge, false otherwise
1664 */
1665 @ViewDebug.ExportedProperty
1666 public boolean isStackFromBottom() {
1667 return mStackFromBottom;
1668 }
1669
1670 /**
1671 * When stack from bottom is set to true, the list fills its content starting from
1672 * the bottom of the view.
1673 *
1674 * @param stackFromBottom true to pin the view's content to the bottom edge,
1675 * false to pin the view's content to the top edge
1676 */
1677 public void setStackFromBottom(boolean stackFromBottom) {
1678 if (mStackFromBottom != stackFromBottom) {
1679 mStackFromBottom = stackFromBottom;
1680 requestLayoutIfNecessary();
1681 }
1682 }
1683
1684 void requestLayoutIfNecessary() {
1685 if (getChildCount() > 0) {
1686 resetList();
1687 requestLayout();
1688 invalidate();
1689 }
1690 }
1691
1692 static class SavedState extends BaseSavedState {
1693 long selectedId;
1694 long firstId;
1695 int viewTop;
1696 int position;
1697 int height;
1698 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001699 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001700 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001701 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001702 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001703
1704 /**
1705 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1706 */
1707 SavedState(Parcelable superState) {
1708 super(superState);
1709 }
1710
1711 /**
1712 * Constructor called from {@link #CREATOR}
1713 */
1714 private SavedState(Parcel in) {
1715 super(in);
1716 selectedId = in.readLong();
1717 firstId = in.readLong();
1718 viewTop = in.readInt();
1719 position = in.readInt();
1720 height = in.readInt();
1721 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001722 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001723 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001724 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001725 final int N = in.readInt();
1726 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001727 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001728 for (int i=0; i<N; i++) {
1729 final long key = in.readLong();
1730 final int value = in.readInt();
1731 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001732 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001733 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001734 }
1735
1736 @Override
1737 public void writeToParcel(Parcel out, int flags) {
1738 super.writeToParcel(out, flags);
1739 out.writeLong(selectedId);
1740 out.writeLong(firstId);
1741 out.writeInt(viewTop);
1742 out.writeInt(position);
1743 out.writeInt(height);
1744 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001745 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001746 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001747 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001748 final int N = checkIdState != null ? checkIdState.size() : 0;
1749 out.writeInt(N);
1750 for (int i=0; i<N; i++) {
1751 out.writeLong(checkIdState.keyAt(i));
1752 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001753 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001754 }
1755
1756 @Override
1757 public String toString() {
1758 return "AbsListView.SavedState{"
1759 + Integer.toHexString(System.identityHashCode(this))
1760 + " selectedId=" + selectedId
1761 + " firstId=" + firstId
1762 + " viewTop=" + viewTop
1763 + " position=" + position
1764 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001765 + " filter=" + filter
1766 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001767 }
1768
1769 public static final Parcelable.Creator<SavedState> CREATOR
1770 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001771 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001772 public SavedState createFromParcel(Parcel in) {
1773 return new SavedState(in);
1774 }
1775
Alan Viverette8fa327a2013-05-31 14:53:13 -07001776 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001777 public SavedState[] newArray(int size) {
1778 return new SavedState[size];
1779 }
1780 };
1781 }
1782
1783 @Override
1784 public Parcelable onSaveInstanceState() {
1785 /*
1786 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001787 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001788 * that happen early enough to keep from getting complaints
1789 * about having leaked the window.
1790 */
1791 dismissPopup();
1792
1793 Parcelable superState = super.onSaveInstanceState();
1794
1795 SavedState ss = new SavedState(superState);
1796
Dianne Hackborne181bd92012-09-25 14:15:15 -07001797 if (mPendingSync != null) {
1798 // Just keep what we last restored.
1799 ss.selectedId = mPendingSync.selectedId;
1800 ss.firstId = mPendingSync.firstId;
1801 ss.viewTop = mPendingSync.viewTop;
1802 ss.position = mPendingSync.position;
1803 ss.height = mPendingSync.height;
1804 ss.filter = mPendingSync.filter;
1805 ss.inActionMode = mPendingSync.inActionMode;
1806 ss.checkedItemCount = mPendingSync.checkedItemCount;
1807 ss.checkState = mPendingSync.checkState;
1808 ss.checkIdState = mPendingSync.checkIdState;
1809 return ss;
1810 }
1811
Dianne Hackborn99441c42010-12-15 11:02:55 -08001812 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001813 long selectedId = getSelectedItemId();
1814 ss.selectedId = selectedId;
1815 ss.height = getHeight();
1816
1817 if (selectedId >= 0) {
1818 // Remember the selection
1819 ss.viewTop = mSelectedTop;
1820 ss.position = getSelectedItemPosition();
1821 ss.firstId = INVALID_POSITION;
1822 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001823 if (haveChildren && mFirstPosition > 0) {
1824 // Remember the position of the first child.
1825 // We only do this if we are not currently at the top of
1826 // the list, for two reasons:
1827 // (1) The list may be in the process of becoming empty, in
1828 // which case mItemCount may not be 0, but if we try to
1829 // ask for any information about position 0 we will crash.
1830 // (2) Being "at the top" seems like a special case, anyway,
1831 // and the user wouldn't expect to end up somewhere else when
1832 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001833 View v = getChildAt(0);
1834 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001835 int firstPos = mFirstPosition;
1836 if (firstPos >= mItemCount) {
1837 firstPos = mItemCount - 1;
1838 }
1839 ss.position = firstPos;
1840 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001841 } else {
1842 ss.viewTop = 0;
1843 ss.firstId = INVALID_POSITION;
1844 ss.position = 0;
1845 }
1846 }
1847
1848 ss.filter = null;
1849 if (mFiltered) {
1850 final EditText textFilter = mTextFilter;
1851 if (textFilter != null) {
1852 Editable filterText = textFilter.getText();
1853 if (filterText != null) {
1854 ss.filter = filterText.toString();
1855 }
1856 }
1857 }
1858
Adam Powella0eeeac2010-11-05 11:55:05 -07001859 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1860
Adam Powell9a5cc282011-08-28 16:18:16 -07001861 if (mCheckStates != null) {
1862 ss.checkState = mCheckStates.clone();
1863 }
1864 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001865 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001866 final int count = mCheckedIdStates.size();
1867 for (int i = 0; i < count; i++) {
1868 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1869 }
1870 ss.checkIdState = idState;
1871 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001872 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001873
Adam Cohen335c3b62012-07-24 17:18:16 -07001874 if (mRemoteAdapter != null) {
1875 mRemoteAdapter.saveRemoteViewsCache();
1876 }
1877
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001878 return ss;
1879 }
1880
1881 @Override
1882 public void onRestoreInstanceState(Parcelable state) {
1883 SavedState ss = (SavedState) state;
1884
1885 super.onRestoreInstanceState(ss.getSuperState());
1886 mDataChanged = true;
1887
1888 mSyncHeight = ss.height;
1889
1890 if (ss.selectedId >= 0) {
1891 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001892 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001893 mSyncRowId = ss.selectedId;
1894 mSyncPosition = ss.position;
1895 mSpecificTop = ss.viewTop;
1896 mSyncMode = SYNC_SELECTED_POSITION;
1897 } else if (ss.firstId >= 0) {
1898 setSelectedPositionInt(INVALID_POSITION);
1899 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1900 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001901 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001902 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001903 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001904 mSyncRowId = ss.firstId;
1905 mSyncPosition = ss.position;
1906 mSpecificTop = ss.viewTop;
1907 mSyncMode = SYNC_FIRST_POSITION;
1908 }
1909
1910 setFilterText(ss.filter);
1911
Adam Powellf343e1b2010-08-13 18:27:04 -07001912 if (ss.checkState != null) {
1913 mCheckStates = ss.checkState;
1914 }
1915
1916 if (ss.checkIdState != null) {
1917 mCheckedIdStates = ss.checkIdState;
1918 }
1919
Adam Powell2614c6c2010-11-04 17:54:45 -07001920 mCheckedItemCount = ss.checkedItemCount;
1921
Adam Powella0eeeac2010-11-05 11:55:05 -07001922 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1923 mMultiChoiceModeCallback != null) {
1924 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1925 }
1926
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001927 requestLayout();
1928 }
1929
1930 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001931 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1932 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001933 }
1934
1935 /**
1936 * Sets the initial value for the text filter.
1937 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001938 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001939 * @see #setTextFilterEnabled
1940 */
1941 public void setFilterText(String filterText) {
1942 // TODO: Should we check for acceptFilter()?
1943 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1944 createTextFilter(false);
1945 // This is going to call our listener onTextChanged, but we might not
1946 // be ready to bring up a window yet
1947 mTextFilter.setText(filterText);
1948 mTextFilter.setSelection(filterText.length());
1949 if (mAdapter instanceof Filterable) {
1950 // if mPopup is non-null, then onTextChanged will do the filtering
1951 if (mPopup == null) {
1952 Filter f = ((Filterable) mAdapter).getFilter();
1953 f.filter(filterText);
1954 }
1955 // Set filtered to true so we will display the filter window when our main
1956 // window is ready
1957 mFiltered = true;
1958 mDataSetObserver.clearSavedState();
1959 }
1960 }
1961 }
1962
1963 /**
Romain Guy0a637162009-05-29 14:43:54 -07001964 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001965 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001966 */
1967 public CharSequence getTextFilter() {
1968 if (mTextFilterEnabled && mTextFilter != null) {
1969 return mTextFilter.getText();
1970 }
1971 return null;
1972 }
Romain Guy0a637162009-05-29 14:43:54 -07001973
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001974 @Override
1975 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1976 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1977 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001978 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001979 // Data may have changed while we were detached and it's valid
1980 // to change focus while detached. Refresh so we don't die.
1981 mDataChanged = true;
1982 mOldItemCount = mItemCount;
1983 mItemCount = mAdapter.getCount();
1984 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001985 resurrectSelection();
1986 }
1987 }
1988
1989 @Override
1990 public void requestLayout() {
1991 if (!mBlockLayoutRequests && !mInLayout) {
1992 super.requestLayout();
1993 }
1994 }
1995
1996 /**
1997 * The list is empty. Clear everything out.
1998 */
1999 void resetList() {
2000 removeAllViewsInLayout();
2001 mFirstPosition = 0;
2002 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07002003 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002004 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07002005 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002006 mOldSelectedPosition = INVALID_POSITION;
2007 mOldSelectedRowId = INVALID_ROW_ID;
2008 setSelectedPositionInt(INVALID_POSITION);
2009 setNextSelectedPositionInt(INVALID_POSITION);
2010 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002011 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002012 mSelectorRect.setEmpty();
2013 invalidate();
2014 }
2015
2016 @Override
2017 protected int computeVerticalScrollExtent() {
2018 final int count = getChildCount();
2019 if (count > 0) {
2020 if (mSmoothScrollbarEnabled) {
2021 int extent = count * 100;
2022
2023 View view = getChildAt(0);
2024 final int top = view.getTop();
2025 int height = view.getHeight();
2026 if (height > 0) {
2027 extent += (top * 100) / height;
2028 }
2029
2030 view = getChildAt(count - 1);
2031 final int bottom = view.getBottom();
2032 height = view.getHeight();
2033 if (height > 0) {
2034 extent -= ((bottom - getHeight()) * 100) / height;
2035 }
2036
2037 return extent;
2038 } else {
2039 return 1;
2040 }
2041 }
2042 return 0;
2043 }
2044
2045 @Override
2046 protected int computeVerticalScrollOffset() {
2047 final int firstPosition = mFirstPosition;
2048 final int childCount = getChildCount();
2049 if (firstPosition >= 0 && childCount > 0) {
2050 if (mSmoothScrollbarEnabled) {
2051 final View view = getChildAt(0);
2052 final int top = view.getTop();
2053 int height = view.getHeight();
2054 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002055 return Math.max(firstPosition * 100 - (top * 100) / height +
2056 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002057 }
2058 } else {
2059 int index;
2060 final int count = mItemCount;
2061 if (firstPosition == 0) {
2062 index = 0;
2063 } else if (firstPosition + childCount == count) {
2064 index = count;
2065 } else {
2066 index = firstPosition + childCount / 2;
2067 }
2068 return (int) (firstPosition + childCount * (index / (float) count));
2069 }
2070 }
2071 return 0;
2072 }
2073
2074 @Override
2075 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002076 int result;
2077 if (mSmoothScrollbarEnabled) {
2078 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002079 if (mScrollY != 0) {
2080 // Compensate for overscroll
2081 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2082 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002083 } else {
2084 result = mItemCount;
2085 }
2086 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002087 }
2088
2089 @Override
2090 protected float getTopFadingEdgeStrength() {
2091 final int count = getChildCount();
2092 final float fadeEdge = super.getTopFadingEdgeStrength();
2093 if (count == 0) {
2094 return fadeEdge;
2095 } else {
2096 if (mFirstPosition > 0) {
2097 return 1.0f;
2098 }
2099
2100 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002101 final float fadeLength = getVerticalFadingEdgeLength();
2102 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002103 }
2104 }
2105
2106 @Override
2107 protected float getBottomFadingEdgeStrength() {
2108 final int count = getChildCount();
2109 final float fadeEdge = super.getBottomFadingEdgeStrength();
2110 if (count == 0) {
2111 return fadeEdge;
2112 } else {
2113 if (mFirstPosition + count - 1 < mItemCount - 1) {
2114 return 1.0f;
2115 }
2116
2117 final int bottom = getChildAt(count - 1).getBottom();
2118 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002119 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002120 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002121 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002122 }
2123 }
2124
2125 @Override
2126 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2127 if (mSelector == null) {
2128 useDefaultSelector();
2129 }
2130 final Rect listPadding = mListPadding;
2131 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2132 listPadding.top = mSelectionTopPadding + mPaddingTop;
2133 listPadding.right = mSelectionRightPadding + mPaddingRight;
2134 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002135
2136 // Check if our previous measured size was at a point where we should scroll later.
2137 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2138 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002139 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002140 final View lastChild = getChildAt(childCount - 1);
2141 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002142 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002143 lastBottom <= listBottom;
2144 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002145 }
2146
Romain Guyd6a463a2009-05-21 23:10:10 -07002147 /**
2148 * Subclasses should NOT override this method but
2149 * {@link #layoutChildren()} instead.
2150 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002151 @Override
2152 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2153 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002155 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002156
Alan Viverette4b95cc72014-01-14 16:54:02 -08002157 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002158 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002159 for (int i = 0; i < childCount; i++) {
2160 getChildAt(i).forceLayout();
2161 }
2162 mRecycler.markChildrenDirty();
2163 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002164
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002165 layoutChildren();
Adam Powell637d3372010-08-25 14:37:03 -07002166
2167 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002168
2169 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2170 if (mFastScroll != null) {
2171 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2172 }
Phil Weavera9d976f2016-11-01 09:55:24 -07002173 mInLayout = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002174 }
2175
2176 /**
2177 * @hide
2178 */
2179 @Override
2180 protected boolean setFrame(int left, int top, int right, int bottom) {
2181 final boolean changed = super.setFrame(left, top, right, bottom);
2182
Romain Guyd6a463a2009-05-21 23:10:10 -07002183 if (changed) {
2184 // Reposition the popup when the frame has changed. This includes
2185 // translating the widget, not just changing its dimension. The
2186 // filter popup needs to follow the widget.
2187 final boolean visible = getWindowVisibility() == View.VISIBLE;
2188 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2189 positionPopup();
2190 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002191 }
2192
2193 return changed;
2194 }
2195
Romain Guyd6a463a2009-05-21 23:10:10 -07002196 /**
2197 * Subclasses must override this method to layout their children.
2198 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002199 protected void layoutChildren() {
2200 }
2201
Alan Viverette5d565fa2013-10-30 11:09:03 -07002202 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002203 * @param focusedView view that holds accessibility focus
2204 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002205 * child contains accessibility focus
2206 */
Alan Viverette3e141622014-02-18 17:05:13 -08002207 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002208 ViewParent viewParent = focusedView.getParent();
2209 while ((viewParent instanceof View) && (viewParent != this)) {
2210 focusedView = (View) viewParent;
2211 viewParent = viewParent.getParent();
2212 }
2213
2214 if (!(viewParent instanceof View)) {
2215 return null;
2216 }
2217
2218 return focusedView;
2219 }
2220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002221 void updateScrollIndicators() {
2222 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002223 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002224 }
2225
2226 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002227 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002228 }
2229 }
2230
Alan Viverette947a9692014-09-25 12:43:47 -07002231 private boolean canScrollUp() {
2232 boolean canScrollUp;
2233 // 0th element is not visible
2234 canScrollUp = mFirstPosition > 0;
2235
2236 // ... Or top of 0th element is not visible
2237 if (!canScrollUp) {
2238 if (getChildCount() > 0) {
2239 View child = getChildAt(0);
2240 canScrollUp = child.getTop() < mListPadding.top;
2241 }
2242 }
2243
2244 return canScrollUp;
2245 }
2246
2247 private boolean canScrollDown() {
2248 boolean canScrollDown;
2249 int count = getChildCount();
2250
2251 // Last item is not visible
2252 canScrollDown = (mFirstPosition + count) < mItemCount;
2253
2254 // ... Or bottom of the last element is not visible
2255 if (!canScrollDown && count > 0) {
2256 View child = getChildAt(count - 1);
2257 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2258 }
2259
2260 return canScrollDown;
2261 }
2262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002263 @Override
2264 @ViewDebug.ExportedProperty
2265 public View getSelectedView() {
2266 if (mItemCount > 0 && mSelectedPosition >= 0) {
2267 return getChildAt(mSelectedPosition - mFirstPosition);
2268 } else {
2269 return null;
2270 }
2271 }
2272
2273 /**
2274 * List padding is the maximum of the normal view's padding and the padding of the selector.
2275 *
2276 * @see android.view.View#getPaddingTop()
2277 * @see #getSelector()
2278 *
2279 * @return The top list padding.
2280 */
2281 public int getListPaddingTop() {
2282 return mListPadding.top;
2283 }
2284
2285 /**
2286 * List padding is the maximum of the normal view's padding and the padding of the selector.
2287 *
2288 * @see android.view.View#getPaddingBottom()
2289 * @see #getSelector()
2290 *
2291 * @return The bottom list padding.
2292 */
2293 public int getListPaddingBottom() {
2294 return mListPadding.bottom;
2295 }
2296
2297 /**
2298 * List padding is the maximum of the normal view's padding and the padding of the selector.
2299 *
2300 * @see android.view.View#getPaddingLeft()
2301 * @see #getSelector()
2302 *
2303 * @return The left list padding.
2304 */
2305 public int getListPaddingLeft() {
2306 return mListPadding.left;
2307 }
2308
2309 /**
2310 * List padding is the maximum of the normal view's padding and the padding of the selector.
2311 *
2312 * @see android.view.View#getPaddingRight()
2313 * @see #getSelector()
2314 *
2315 * @return The right list padding.
2316 */
2317 public int getListPaddingRight() {
2318 return mListPadding.right;
2319 }
2320
2321 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002322 * Gets a view and have it show the data associated with the specified
2323 * position. This is called when we have already discovered that the view
2324 * is not available for reuse in the recycle bin. The only choices left are
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002325 * converting an old view or making a new one.
2326 *
Alan Viverette26489e12016-07-07 16:39:27 -04002327 * @param position the position to display
2328 * @param outMetadata an array of at least 1 boolean where the first entry
2329 * will be set {@code true} if the view is currently
2330 * attached to the window, {@code false} otherwise (e.g.
2331 * newly-inflated or remained scrap for multiple layout
2332 * passes)
Mindy Pereira4e30d892010-11-24 15:32:39 -08002333 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002334 * @return A view displaying the data associated with the specified position
2335 */
Alan Viverette26489e12016-07-07 16:39:27 -04002336 View obtainView(int position, boolean[] outMetadata) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002337 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2338
Alan Viverette26489e12016-07-07 16:39:27 -04002339 outMetadata[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002340
Alan Viverette59511502013-12-09 13:49:25 -08002341 // Check whether we have a transient state view. Attempt to re-bind the
2342 // data and discard the view if we fail.
2343 final View transientView = mRecycler.getTransientStateView(position);
2344 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002345 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2346
2347 // If the view type hasn't changed, attempt to re-bind the data.
2348 if (params.viewType == mAdapter.getItemViewType(position)) {
2349 final View updatedView = mAdapter.getView(position, transientView, this);
2350
2351 // If we failed to re-bind the data, scrap the obtained view.
2352 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002353 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002354 mRecycler.addScrapView(updatedView, position);
2355 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002356 }
2357
Alan Viverette26489e12016-07-07 16:39:27 -04002358 outMetadata[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002359
2360 // Finish the temporary detach started in addScrapView().
2361 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002362 return transientView;
2363 }
2364
2365 final View scrapView = mRecycler.getScrapView(position);
2366 final View child = mAdapter.getView(position, scrapView, this);
2367 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002368 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002369 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002370 mRecycler.addScrapView(scrapView, position);
Alan Viverette26489e12016-07-07 16:39:27 -04002371 } else if (child.isTemporarilyDetached()) {
2372 outMetadata[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002373
Alan Viverette26489e12016-07-07 16:39:27 -04002374 // Finish the temporary detach started in addScrapView().
2375 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002376 }
Alan Viverette59511502013-12-09 13:49:25 -08002377 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002378
Alan Viverette59511502013-12-09 13:49:25 -08002379 if (mCacheColorHint != 0) {
2380 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2381 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002382
Alan Viverette59511502013-12-09 13:49:25 -08002383 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2384 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002385 }
2386
Alan Viverettee6be9c782014-02-26 18:16:36 -08002387 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002388
alanvc1d7e772012-05-08 14:47:24 -07002389 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2390 if (mAccessibilityDelegate == null) {
2391 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2392 }
alanvb72fe7a2012-08-27 16:44:25 -07002393 if (child.getAccessibilityDelegate() == null) {
2394 child.setAccessibilityDelegate(mAccessibilityDelegate);
2395 }
alanvc1d7e772012-05-08 14:47:24 -07002396 }
2397
Romain Guy5fade8c2013-07-10 16:36:18 -07002398 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2399
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002400 return child;
2401 }
2402
Alan Viverettee6be9c782014-02-26 18:16:36 -08002403 private void setItemViewLayoutParams(View child, int position) {
2404 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2405 LayoutParams lp;
2406 if (vlp == null) {
2407 lp = (LayoutParams) generateDefaultLayoutParams();
2408 } else if (!checkLayoutParams(vlp)) {
2409 lp = (LayoutParams) generateLayoutParams(vlp);
2410 } else {
2411 lp = (LayoutParams) vlp;
2412 }
2413
2414 if (mAdapterHasStableIds) {
2415 lp.itemId = mAdapter.getItemId(position);
2416 }
2417 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002418 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002419 if (lp != vlp) {
2420 child.setLayoutParams(lp);
2421 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002422 }
2423
alanvc1d7e772012-05-08 14:47:24 -07002424 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2425 @Override
2426 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2427 super.onInitializeAccessibilityNodeInfo(host, info);
2428
2429 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002430 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002431 }
2432
2433 @Override
2434 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002435 if (super.performAccessibilityAction(host, action, arguments)) {
2436 return true;
2437 }
2438
alanvc1d7e772012-05-08 14:47:24 -07002439 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002440 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002441 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002442 return false;
2443 }
2444
Alan Viverette92539d52015-09-14 10:49:25 -04002445 if (position >= mAdapter.getCount()) {
2446 // The position is no longer valid, likely due to a data set
2447 // change. We could fail here for all data set changes, since
2448 // there is a chance that the data bound to the view may no
2449 // longer exist at the same position within the adapter, but
2450 // it's more consistent with the standard touch interaction to
2451 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002452 return false;
2453 }
2454
Alan Viverette92539d52015-09-14 10:49:25 -04002455 final boolean isItemEnabled;
2456 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2457 if (lp instanceof AbsListView.LayoutParams) {
2458 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2459 } else {
2460 isItemEnabled = false;
2461 }
2462
2463 if (!isEnabled() || !isItemEnabled) {
2464 // Cannot perform actions on disabled items.
2465 return false;
2466 }
alanvc1d7e772012-05-08 14:47:24 -07002467
2468 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002469 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2470 if (getSelectedItemPosition() == position) {
2471 setSelection(INVALID_POSITION);
2472 return true;
2473 }
2474 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002475 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002476 if (getSelectedItemPosition() != position) {
2477 setSelection(position);
2478 return true;
2479 }
2480 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002481 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002482 if (isItemClickable(host)) {
2483 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002484 return performItemClick(host, position, id);
2485 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002486 } return false;
2487 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2488 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002489 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002490 return performLongPress(host, position, id);
2491 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002492 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002493 }
2494
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002495 return false;
alanvc1d7e772012-05-08 14:47:24 -07002496 }
2497 }
2498
Alan Viverette5b2081d2013-08-28 10:43:07 -07002499 /**
2500 * Initializes an {@link AccessibilityNodeInfo} with information about a
2501 * particular item in the list.
2502 *
2503 * @param view View representing the list item.
2504 * @param position Position of the list item within the adapter.
2505 * @param info Node info to populate.
2506 */
2507 public void onInitializeAccessibilityNodeInfoForItem(
2508 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002509 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002510 // The item doesn't exist, so there's not much we can do here.
2511 return;
2512 }
2513
Alan Viverette92539d52015-09-14 10:49:25 -04002514 final boolean isItemEnabled;
2515 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2516 if (lp instanceof AbsListView.LayoutParams) {
2517 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2518 } else {
2519 isItemEnabled = false;
2520 }
2521
2522 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002523 info.setEnabled(false);
2524 return;
2525 }
2526
2527 if (position == getSelectedItemPosition()) {
2528 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002529 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002530 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002531 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002532 }
2533
Alan Viverette92539d52015-09-14 10:49:25 -04002534 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002535 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002536 info.setClickable(true);
2537 }
2538
2539 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002540 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002541 info.setLongClickable(true);
2542 }
2543 }
2544
Alan Viverette92539d52015-09-14 10:49:25 -04002545 private boolean isItemClickable(View view) {
Adam Powell0f552f42017-02-03 11:50:42 -08002546 return !view.hasExplicitFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002547 }
2548
Alan Viverettede399392014-05-01 17:20:55 -07002549 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002550 * Positions the selector in a way that mimics touch.
2551 */
2552 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002553 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002554 }
2555
2556 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002557 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002558 */
2559 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002560 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002561 final Rect bounds = mSelectorRect;
2562 final float x = bounds.exactCenterX();
2563 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002564 positionSelector(position, sel, true, x, y);
2565 } else {
2566 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002567 }
2568 }
2569
Dianne Hackborn079e2352010-10-18 17:02:43 -07002570 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002571 positionSelector(position, sel, false, -1, -1);
2572 }
2573
2574 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2575 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002576 if (position != INVALID_POSITION) {
2577 mSelectorPosition = position;
2578 }
2579
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002580 final Rect selectorRect = mSelectorRect;
2581 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002582 if (sel instanceof SelectionBoundsAdjuster) {
2583 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2584 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002585
2586 // Adjust for selection padding.
2587 selectorRect.left -= mSelectionLeftPadding;
2588 selectorRect.top -= mSelectionTopPadding;
2589 selectorRect.right += mSelectionRightPadding;
2590 selectorRect.bottom += mSelectionBottomPadding;
2591
Alan Viverettea19ab342015-05-18 13:20:52 -07002592 // Update the child enabled state prior to updating the selector.
2593 final boolean isChildViewEnabled = sel.isEnabled();
2594 if (mIsChildViewEnabled != isChildViewEnabled) {
2595 mIsChildViewEnabled = isChildViewEnabled;
2596 }
2597
2598 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002599 final Drawable selector = mSelector;
2600 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002601 if (positionChanged) {
2602 // Wipe out the current selector state so that we can start
2603 // over in the new position with a fresh state.
2604 selector.setVisible(false, false);
2605 selector.setState(StateSet.NOTHING);
2606 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002607 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002608 if (positionChanged) {
2609 if (getVisibility() == VISIBLE) {
2610 selector.setVisible(true, false);
2611 }
Chet Haase2167b112014-12-19 16:37:18 -08002612 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002613 }
2614 if (manageHotspot) {
2615 selector.setHotspot(x, y);
2616 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002617 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002618 }
2619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002620 @Override
2621 protected void dispatchDraw(Canvas canvas) {
2622 int saveCount = 0;
2623 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2624 if (clipToPadding) {
2625 saveCount = canvas.save();
2626 final int scrollX = mScrollX;
2627 final int scrollY = mScrollY;
2628 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2629 scrollX + mRight - mLeft - mPaddingRight,
2630 scrollY + mBottom - mTop - mPaddingBottom);
2631 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2632 }
2633
2634 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2635 if (!drawSelectorOnTop) {
2636 drawSelector(canvas);
2637 }
2638
2639 super.dispatchDraw(canvas);
2640
2641 if (drawSelectorOnTop) {
2642 drawSelector(canvas);
2643 }
2644
2645 if (clipToPadding) {
2646 canvas.restoreToCount(saveCount);
2647 mGroupFlags |= CLIP_TO_PADDING_MASK;
2648 }
2649 }
2650
2651 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002652 protected boolean isPaddingOffsetRequired() {
2653 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2654 }
2655
2656 @Override
2657 protected int getLeftPaddingOffset() {
2658 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2659 }
2660
2661 @Override
2662 protected int getTopPaddingOffset() {
2663 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2664 }
2665
2666 @Override
2667 protected int getRightPaddingOffset() {
2668 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2669 }
2670
2671 @Override
2672 protected int getBottomPaddingOffset() {
2673 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2674 }
2675
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002676 /**
2677 * @hide
2678 */
2679 @Override
2680 protected void internalSetPadding(int left, int top, int right, int bottom) {
2681 super.internalSetPadding(left, top, right, bottom);
2682 if (isLayoutRequested()) {
2683 handleBoundsChange();
2684 }
2685 }
2686
Adam Powell20232d02010-12-08 21:08:53 -08002687 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002688 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002689 handleBoundsChange();
Alan Viverette8636ace2013-10-31 15:41:31 -07002690 if (mFastScroll != null) {
2691 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002692 }
2693 }
2694
2695 /**
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002696 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2697 * and force layouts all children that don't have exact measure specs.
2698 * <p>
2699 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2700 * fail to relayout them properly to accommodate for new bounds.
2701 */
2702 void handleBoundsChange() {
Phil Weavera9d976f2016-11-01 09:55:24 -07002703 if (mInLayout) {
2704 return;
2705 }
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002706 final int childCount = getChildCount();
2707 if (childCount > 0) {
2708 mDataChanged = true;
2709 rememberSyncState();
2710 for (int i = 0; i < childCount; i++) {
2711 final View child = getChildAt(i);
2712 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2713 // force layout child unless it has exact specs
2714 if (lp == null || lp.width < 1 || lp.height < 1) {
2715 child.forceLayout();
2716 }
2717 }
2718 }
2719 }
2720
2721 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002722 * @return True if the current touch mode requires that we draw the selector in the pressed
2723 * state.
2724 */
2725 boolean touchModeDrawsInPressedState() {
2726 // FIXME use isPressed for this
2727 switch (mTouchMode) {
2728 case TOUCH_MODE_TAP:
2729 case TOUCH_MODE_DONE_WAITING:
2730 return true;
2731 default:
2732 return false;
2733 }
2734 }
2735
2736 /**
2737 * Indicates whether this view is in a state where the selector should be drawn. This will
2738 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2739 * the pressed state for an item.
2740 *
2741 * @return True if the selector should be shown
2742 */
2743 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002744 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002745 }
2746
2747 private void drawSelector(Canvas canvas) {
Evan Rosky8e5bd812018-01-22 09:36:41 -08002748 if (shouldDrawSelector()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002749 final Drawable selector = mSelector;
2750 selector.setBounds(mSelectorRect);
2751 selector.draw(canvas);
2752 }
2753 }
2754
2755 /**
Evan Rosky8e5bd812018-01-22 09:36:41 -08002756 * @hide
2757 */
2758 @TestApi
2759 public final boolean shouldDrawSelector() {
2760 return !mSelectorRect.isEmpty();
2761 }
2762
2763 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002764 * Controls whether the selection highlight drawable should be drawn on top of the item or
2765 * behind it.
2766 *
2767 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2768 * is false.
2769 *
2770 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2771 */
2772 public void setDrawSelectorOnTop(boolean onTop) {
2773 mDrawSelectorOnTop = onTop;
2774 }
2775
2776 /**
2777 * Set a Drawable that should be used to highlight the currently selected item.
2778 *
2779 * @param resID A Drawable resource to use as the selection highlight.
2780 *
2781 * @attr ref android.R.styleable#AbsListView_listSelector
2782 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002783 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002784 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002785 }
2786
2787 public void setSelector(Drawable sel) {
2788 if (mSelector != null) {
2789 mSelector.setCallback(null);
2790 unscheduleDrawable(mSelector);
2791 }
2792 mSelector = sel;
2793 Rect padding = new Rect();
2794 sel.getPadding(padding);
2795 mSelectionLeftPadding = padding.left;
2796 mSelectionTopPadding = padding.top;
2797 mSelectionRightPadding = padding.right;
2798 mSelectionBottomPadding = padding.bottom;
2799 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002800 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002801 }
2802
2803 /**
2804 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2805 * selection in the list.
2806 *
2807 * @return the drawable used to display the selector
2808 */
2809 public Drawable getSelector() {
2810 return mSelector;
2811 }
2812
2813 /**
2814 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2815 * this is a long press.
2816 */
2817 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002818 if (!isEnabled() || !isClickable()) {
2819 return;
2820 }
2821
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002822 Drawable selector = mSelector;
2823 Rect selectorRect = mSelectorRect;
2824 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002825 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002826
2827 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2828
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002829 if (v != null) {
Adam Powell0f552f42017-02-03 11:50:42 -08002830 if (v.hasExplicitFocusable()) return;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002831 v.setPressed(true);
2832 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002833 setPressed(true);
2834
2835 final boolean longClickable = isLongClickable();
2836 Drawable d = selector.getCurrent();
2837 if (d != null && d instanceof TransitionDrawable) {
2838 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002839 ((TransitionDrawable) d).startTransition(
2840 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002841 } else {
2842 ((TransitionDrawable) d).resetTransition();
2843 }
2844 }
2845 if (longClickable && !mDataChanged) {
2846 if (mPendingCheckForKeyLongPress == null) {
2847 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2848 }
2849 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2850 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2851 }
2852 }
2853 }
2854
2855 public void setScrollIndicators(View up, View down) {
2856 mScrollUp = up;
2857 mScrollDown = down;
2858 }
2859
Dianne Hackborn079e2352010-10-18 17:02:43 -07002860 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002861 final Drawable selector = mSelector;
2862 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002863 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002864 if (selector.setState(getDrawableStateForSelector())) {
2865 invalidateDrawable(selector);
2866 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002867 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002868 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002869 }
2870 }
2871 }
2872
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002873 @Override
2874 protected void drawableStateChanged() {
2875 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002876 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002877 }
2878
Alan Viverettef723c832015-02-03 16:31:46 -08002879 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002880 // If the child view is enabled then do the default behavior.
2881 if (mIsChildViewEnabled) {
2882 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002883 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002884 }
2885
2886 // The selector uses this View's drawable state. The selected child view
2887 // is disabled, so we need to remove the enabled state from the drawable
2888 // states.
2889 final int enabledState = ENABLED_STATE_SET[0];
2890
Alan Viverettef723c832015-02-03 16:31:46 -08002891 // If we don't have any extra space, it will return one of the static
2892 // state arrays, and clearing the enabled state on those arrays is a
2893 // bad thing! If we specify we need extra space, it will create+copy
2894 // into a new array that is safely mutable.
2895 final int[] state = onCreateDrawableState(1);
2896
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002897 int enabledPos = -1;
2898 for (int i = state.length - 1; i >= 0; i--) {
2899 if (state[i] == enabledState) {
2900 enabledPos = i;
2901 break;
2902 }
2903 }
2904
2905 // Remove the enabled state
2906 if (enabledPos >= 0) {
2907 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2908 state.length - enabledPos - 1);
2909 }
Romain Guy0a637162009-05-29 14:43:54 -07002910
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002911 return state;
2912 }
2913
2914 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -05002915 public boolean verifyDrawable(@NonNull Drawable dr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002916 return mSelector == dr || super.verifyDrawable(dr);
2917 }
2918
2919 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002920 public void jumpDrawablesToCurrentState() {
2921 super.jumpDrawablesToCurrentState();
2922 if (mSelector != null) mSelector.jumpToCurrentState();
2923 }
2924
2925 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002926 protected void onAttachedToWindow() {
2927 super.onAttachedToWindow();
2928
2929 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002930 treeObserver.addOnTouchModeChangeListener(this);
2931 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2932 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002933 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002934
Romain Guy82afc7b2010-05-13 11:52:37 -07002935 if (mAdapter != null && mDataSetObserver == null) {
2936 mDataSetObserver = new AdapterDataSetObserver();
2937 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002938
2939 // Data may have changed while we were detached. Refresh.
2940 mDataChanged = true;
2941 mOldItemCount = mItemCount;
2942 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002943 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002944 }
2945
2946 @Override
2947 protected void onDetachedFromWindow() {
2948 super.onDetachedFromWindow();
2949
Alan Viverette462c2172014-02-24 12:24:11 -08002950 mIsDetaching = true;
2951
Romain Guy1f7f3c32009-07-22 11:25:42 -07002952 // Dismiss the popup in case onSaveInstanceState() was not invoked
2953 dismissPopup();
2954
Romain Guy21875052010-01-06 18:48:08 -08002955 // Detach any view left in the scrap heap
2956 mRecycler.clear();
2957
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002958 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002959 treeObserver.removeOnTouchModeChangeListener(this);
2960 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002961 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002962 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002963 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002964
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002965 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002966 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2967 mDataSetObserver = null;
2968 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002969
2970 if (mScrollStrictSpan != null) {
2971 mScrollStrictSpan.finish();
2972 mScrollStrictSpan = null;
2973 }
2974
2975 if (mFlingStrictSpan != null) {
2976 mFlingStrictSpan.finish();
2977 mFlingStrictSpan = null;
2978 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002979
2980 if (mFlingRunnable != null) {
2981 removeCallbacks(mFlingRunnable);
2982 }
2983
2984 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002985 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002986 }
2987
2988 if (mClearScrollingCache != null) {
2989 removeCallbacks(mClearScrollingCache);
2990 }
2991
2992 if (mPerformClick != null) {
2993 removeCallbacks(mPerformClick);
2994 }
2995
2996 if (mTouchModeReset != null) {
2997 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002998 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002999 }
Alan Viverette462c2172014-02-24 12:24:11 -08003000
3001 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003002 }
3003
3004 @Override
3005 public void onWindowFocusChanged(boolean hasWindowFocus) {
3006 super.onWindowFocusChanged(hasWindowFocus);
3007
3008 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3009
3010 if (!hasWindowFocus) {
3011 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08003012 if (mFlingRunnable != null) {
3013 removeCallbacks(mFlingRunnable);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003014 // let the fling runnable report its new state which
Mark Wagner670dd812010-01-13 16:17:47 -08003015 // should be idle
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003016 mFlingRunnable.mSuppressIdleStateChangeCall = false;
Mark Wagner670dd812010-01-13 16:17:47 -08003017 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003018 if (mPositionScroller != null) {
3019 mPositionScroller.stop();
3020 }
Adam Powell45803472010-01-25 15:10:44 -08003021 if (mScrollY != 0) {
3022 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003023 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003024 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08003025 invalidate();
3026 }
Mark Wagner670dd812010-01-13 16:17:47 -08003027 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003028 // Always hide the type filter
3029 dismissPopup();
3030
3031 if (touchMode == TOUCH_MODE_OFF) {
3032 // Remember the last selected element
3033 mResurrectToPosition = mSelectedPosition;
3034 }
3035 } else {
Adam Powell97566042010-03-09 15:34:09 -08003036 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003037 // Show the type filter only if a filter is in effect
3038 showPopup();
3039 }
3040
3041 // If we changed touch mode since the last time we had focus
3042 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3043 // If we come back in trackball mode, we bring the selection back
3044 if (touchMode == TOUCH_MODE_OFF) {
3045 // This will trigger a layout
3046 resurrectSelection();
3047
3048 // If we come back in touch mode, then we want to hide the selector
3049 } else {
3050 hideSelector();
3051 mLayoutMode = LAYOUT_NORMAL;
3052 layoutChildren();
3053 }
3054 }
3055 }
3056
3057 mLastTouchMode = touchMode;
3058 }
3059
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003060 @Override
3061 public void onRtlPropertiesChanged(int layoutDirection) {
3062 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003063 if (mFastScroll != null) {
3064 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003065 }
3066 }
3067
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003068 /**
3069 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3070 * methods knows the view, position and ID of the item that received the
3071 * long press.
3072 *
3073 * @param view The view that received the long press.
3074 * @param position The position of the item that received the long press.
3075 * @param id The ID of the item that received the long press.
3076 * @return The extra information that should be returned by
3077 * {@link #getContextMenuInfo()}.
3078 */
3079 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3080 return new AdapterContextMenuInfo(view, position, id);
3081 }
3082
Adam Powell14874662013-07-18 19:42:41 -07003083 @Override
3084 public void onCancelPendingInputEvents() {
3085 super.onCancelPendingInputEvents();
3086 if (mPerformClick != null) {
3087 removeCallbacks(mPerformClick);
3088 }
3089 if (mPendingCheckForTap != null) {
3090 removeCallbacks(mPendingCheckForTap);
3091 }
3092 if (mPendingCheckForLongPress != null) {
3093 removeCallbacks(mPendingCheckForLongPress);
3094 }
3095 if (mPendingCheckForKeyLongPress != null) {
3096 removeCallbacks(mPendingCheckForKeyLongPress);
3097 }
3098 }
3099
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003100 /**
3101 * A base class for Runnables that will check that their view is still attached to
3102 * the original window as when the Runnable was created.
3103 *
3104 */
3105 private class WindowRunnnable {
3106 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003108 public void rememberWindowAttachCount() {
3109 mOriginalAttachCount = getWindowAttachCount();
3110 }
Romain Guy0a637162009-05-29 14:43:54 -07003111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003112 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003113 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003114 }
3115 }
Romain Guy0a637162009-05-29 14:43:54 -07003116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003117 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003118 int mClickMotionPosition;
3119
Alan Viverette8fa327a2013-05-31 14:53:13 -07003120 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003121 public void run() {
3122 // The data has changed since we posted this action in the event queue,
3123 // bail out before bad things happen
3124 if (mDataChanged) return;
3125
Adam Powell005c0a42010-03-30 16:26:36 -07003126 final ListAdapter adapter = mAdapter;
3127 final int motionPosition = mClickMotionPosition;
3128 if (adapter != null && mItemCount > 0 &&
3129 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003130 motionPosition < adapter.getCount() && sameWindow() &&
3131 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003132 final View view = getChildAt(motionPosition - mFirstPosition);
3133 // If there is no view, something bad happened (the view scrolled off the
3134 // screen, etc.) and we should cancel the click
3135 if (view != null) {
3136 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3137 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003138 }
3139 }
3140 }
3141
3142 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003143 private static final int INVALID_COORD = -1;
3144 private float mX = INVALID_COORD;
3145 private float mY = INVALID_COORD;
3146
3147 private void setCoords(float x, float y) {
3148 mX = x;
3149 mY = y;
3150 }
3151
Alan Viverette8fa327a2013-05-31 14:53:13 -07003152 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003153 public void run() {
3154 final int motionPosition = mMotionPosition;
3155 final View child = getChildAt(motionPosition - mFirstPosition);
3156 if (child != null) {
3157 final int longPressPosition = mMotionPosition;
3158 final long longPressId = mAdapter.getItemId(mMotionPosition);
3159
3160 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003161 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003162 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3163 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3164 } else {
3165 handled = performLongPress(child, longPressPosition, longPressId);
3166 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003167 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003169 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003170 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003171 mTouchMode = TOUCH_MODE_REST;
3172 setPressed(false);
3173 child.setPressed(false);
3174 } else {
3175 mTouchMode = TOUCH_MODE_DONE_WAITING;
3176 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003177 }
3178 }
3179 }
Romain Guy0a637162009-05-29 14:43:54 -07003180
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003181 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003182 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003183 public void run() {
3184 if (isPressed() && mSelectedPosition >= 0) {
3185 int index = mSelectedPosition - mFirstPosition;
3186 View v = getChildAt(index);
3187
3188 if (!mDataChanged) {
3189 boolean handled = false;
3190 if (sameWindow()) {
3191 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3192 }
3193 if (handled) {
3194 setPressed(false);
3195 v.setPressed(false);
3196 }
3197 } else {
3198 setPressed(false);
3199 if (v != null) v.setPressed(false);
3200 }
3201 }
3202 }
3203 }
3204
Mady Mellore5561982015-04-14 15:06:40 -07003205 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003206 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003207 final View child = getChildAt(mMotionPosition - mFirstPosition);
3208 if (child != null) {
3209 final int longPressPosition = mMotionPosition;
3210 final long longPressId = mAdapter.getItemId(mMotionPosition);
3211 if (performLongPress(child, longPressPosition, longPressId)) {
3212 mTouchMode = TOUCH_MODE_REST;
3213 setPressed(false);
3214 child.setPressed(false);
3215 return true;
3216 }
3217 }
3218 }
3219 return false;
3220 }
3221
Adam Powell8350f7d2010-07-28 14:27:28 -07003222 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003223 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003224 return performLongPress(
3225 child,
3226 longPressPosition,
3227 longPressId,
3228 CheckForLongPress.INVALID_COORD,
3229 CheckForLongPress.INVALID_COORD);
3230 }
3231
3232 boolean performLongPress(final View child,
3233 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003234 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3235 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003236 if (mChoiceActionMode == null &&
3237 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003238 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003239 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003240 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003241 return true;
3242 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003243
Adam Powellf343e1b2010-08-13 18:27:04 -07003244 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003245 if (mOnItemLongClickListener != null) {
3246 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3247 longPressPosition, longPressId);
3248 }
3249 if (!handled) {
3250 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003251 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3252 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3253 } else {
3254 handled = super.showContextMenuForChild(AbsListView.this);
3255 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003256 }
3257 if (handled) {
3258 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3259 }
3260 return handled;
3261 }
3262
3263 @Override
3264 protected ContextMenuInfo getContextMenuInfo() {
3265 return mContextMenuInfo;
3266 }
3267
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003268 @Override
3269 public boolean showContextMenu() {
3270 return showContextMenuInternal(0, 0, false);
3271 }
3272
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003273 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003274 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003275 return showContextMenuInternal(x, y, true);
3276 }
3277
3278 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003279 final int position = pointToPosition((int)x, (int)y);
3280 if (position != INVALID_POSITION) {
3281 final long id = mAdapter.getItemId(position);
3282 View child = getChildAt(position - mFirstPosition);
3283 if (child != null) {
3284 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003285 if (useOffsets) {
3286 return super.showContextMenuForChild(this, x, y);
3287 } else {
3288 return super.showContextMenuForChild(this);
3289 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003290 }
3291 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003292 if (useOffsets) {
3293 return super.showContextMenu(x, y);
3294 } else {
3295 return super.showContextMenu();
3296 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003297 }
3298
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003299 @Override
3300 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003301 if (isShowingContextMenuWithCoords()) {
3302 return false;
3303 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003304 return showContextMenuForChildInternal(originalView, 0, 0, false);
3305 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003306
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003307 @Override
3308 public boolean showContextMenuForChild(View originalView, float x, float y) {
3309 return showContextMenuForChildInternal(originalView,x, y, true);
3310 }
3311
3312 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3313 boolean useOffsets) {
3314 final int longPressPosition = getPositionForView(originalView);
3315 if (longPressPosition < 0) {
3316 return false;
3317 }
3318
3319 final long longPressId = mAdapter.getItemId(longPressPosition);
3320 boolean handled = false;
3321
3322 if (mOnItemLongClickListener != null) {
3323 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3324 longPressPosition, longPressId);
3325 }
3326
3327 if (!handled) {
3328 final View child = getChildAt(longPressPosition - mFirstPosition);
3329 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3330
3331 if (useOffsets) {
3332 handled = super.showContextMenuForChild(originalView, x, y);
3333 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003334 handled = super.showContextMenuForChild(originalView);
3335 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003336 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003337
3338 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003339 }
3340
3341 @Override
Romain Guydf016072009-08-17 12:51:30 -07003342 public boolean onKeyDown(int keyCode, KeyEvent event) {
3343 return false;
3344 }
3345
3346 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003347 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003348 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003349 if (!isEnabled()) {
3350 return true;
3351 }
Romain Guydf016072009-08-17 12:51:30 -07003352 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003353 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003354 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003356 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003357 if (view != null) {
3358 performItemClick(view, mSelectedPosition, mSelectedRowId);
3359 view.setPressed(false);
3360 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003361 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362 return true;
3363 }
3364 }
3365 return super.onKeyUp(keyCode, event);
3366 }
3367
3368 @Override
3369 protected void dispatchSetPressed(boolean pressed) {
3370 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3371 // get the selector in the right state, but we don't want to press each child.
3372 }
3373
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003374 @Override
3375 public void dispatchDrawableHotspotChanged(float x, float y) {
3376 // Don't dispatch hotspot changes to children. We'll manually handle
3377 // calling drawableHotspotChanged on the correct child.
3378 }
3379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003380 /**
3381 * Maps a point to a position in the list.
3382 *
3383 * @param x X in local coordinate
3384 * @param y Y in local coordinate
3385 * @return The position of the item which contains the specified point, or
3386 * {@link #INVALID_POSITION} if the point does not intersect an item.
3387 */
3388 public int pointToPosition(int x, int y) {
3389 Rect frame = mTouchFrame;
3390 if (frame == null) {
3391 mTouchFrame = new Rect();
3392 frame = mTouchFrame;
3393 }
3394
3395 final int count = getChildCount();
3396 for (int i = count - 1; i >= 0; i--) {
3397 final View child = getChildAt(i);
3398 if (child.getVisibility() == View.VISIBLE) {
3399 child.getHitRect(frame);
3400 if (frame.contains(x, y)) {
3401 return mFirstPosition + i;
3402 }
3403 }
3404 }
3405 return INVALID_POSITION;
3406 }
3407
3408
3409 /**
3410 * Maps a point to a the rowId of the item which intersects that point.
3411 *
3412 * @param x X in local coordinate
3413 * @param y Y in local coordinate
3414 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3415 * if the point does not intersect an item.
3416 */
3417 public long pointToRowId(int x, int y) {
3418 int position = pointToPosition(x, y);
3419 if (position >= 0) {
3420 return mAdapter.getItemId(position);
3421 }
3422 return INVALID_ROW_ID;
3423 }
3424
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003425 private final class CheckForTap implements Runnable {
3426 float x;
3427 float y;
3428
Alan Viverette8fa327a2013-05-31 14:53:13 -07003429 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003430 public void run() {
3431 if (mTouchMode == TOUCH_MODE_DOWN) {
3432 mTouchMode = TOUCH_MODE_TAP;
3433 final View child = getChildAt(mMotionPosition - mFirstPosition);
Adam Powell0f552f42017-02-03 11:50:42 -08003434 if (child != null && !child.hasExplicitFocusable()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003435 mLayoutMode = LAYOUT_NORMAL;
3436
3437 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003438 final float[] point = mTmpPoint;
3439 point[0] = x;
3440 point[1] = y;
3441 transformPointToViewLocal(point, child);
3442 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003443 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003444 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003445 layoutChildren();
3446 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003447 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003448
3449 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3450 final boolean longClickable = isLongClickable();
3451
3452 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003453 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003454 if (d != null && d instanceof TransitionDrawable) {
3455 if (longClickable) {
3456 ((TransitionDrawable) d).startTransition(longPressTimeout);
3457 } else {
3458 ((TransitionDrawable) d).resetTransition();
3459 }
3460 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003461 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003462 }
3463
3464 if (longClickable) {
3465 if (mPendingCheckForLongPress == null) {
3466 mPendingCheckForLongPress = new CheckForLongPress();
3467 }
Oren Blasberged391262015-09-01 12:12:51 -07003468 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003469 mPendingCheckForLongPress.rememberWindowAttachCount();
3470 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3471 } else {
3472 mTouchMode = TOUCH_MODE_DONE_WAITING;
3473 }
3474 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003475 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003476 }
3477 }
3478 }
3479 }
3480 }
3481
Adam Powellc501db9f2014-05-08 12:50:10 -07003482 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003483 // Check if we have moved far enough that it looks more like a
3484 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003485 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003486 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003487 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003488 if ((overscroll || distance > mTouchSlop) &&
3489 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003490 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003491 if (overscroll) {
3492 mTouchMode = TOUCH_MODE_OVERSCROLL;
3493 mMotionCorrection = 0;
3494 } else {
3495 mTouchMode = TOUCH_MODE_SCROLL;
3496 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3497 }
Alan Viverette74ded292013-06-03 15:34:11 -07003498 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003499 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003500 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003501 if (motionView != null) {
3502 motionView.setPressed(false);
3503 }
3504 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3505 // Time to start stealing events! Once we've stolen them, don't let anyone
3506 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003507 final ViewParent parent = getParent();
3508 if (parent != null) {
3509 parent.requestDisallowInterceptTouchEvent(true);
3510 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003511 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003512 return true;
3513 }
3514
3515 return false;
3516 }
3517
Adam Powellc501db9f2014-05-08 12:50:10 -07003518 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003519 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003520 int scrollOffsetCorrection = 0;
3521 int scrollConsumedCorrection = 0;
3522 if (mLastY == Integer.MIN_VALUE) {
3523 rawDeltaY -= mMotionCorrection;
3524 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003525 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3526 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003527 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003528 scrollOffsetCorrection = -mScrollOffset[1];
3529 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003530 if (vtev != null) {
3531 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003532 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003533 }
3534 }
Yorke Lee43943d82014-05-08 10:15:20 -07003535 final int deltaY = rawDeltaY;
3536 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003537 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003538 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003539
3540 if (mTouchMode == TOUCH_MODE_SCROLL) {
3541 if (PROFILE_SCROLLING) {
3542 if (!mScrollProfilingStarted) {
3543 Debug.startMethodTracing("AbsListViewScroll");
3544 mScrollProfilingStarted = true;
3545 }
3546 }
3547
3548 if (mScrollStrictSpan == null) {
3549 // If it's non-null, we're already in a scroll.
3550 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3551 }
3552
3553 if (y != mLastY) {
3554 // We may be here after stopping a fling and continuing to scroll.
3555 // If so, we haven't disallowed intercepting touch events yet.
3556 // Make sure that we do so in case we're in a parent that can intercept.
3557 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3558 Math.abs(rawDeltaY) > mTouchSlop) {
3559 final ViewParent parent = getParent();
3560 if (parent != null) {
3561 parent.requestDisallowInterceptTouchEvent(true);
3562 }
3563 }
3564
3565 final int motionIndex;
3566 if (mMotionPosition >= 0) {
3567 motionIndex = mMotionPosition - mFirstPosition;
3568 } else {
3569 // If we don't have a motion position that we can reliably track,
3570 // pick something in the middle to make a best guess at things below.
3571 motionIndex = getChildCount() / 2;
3572 }
3573
3574 int motionViewPrevTop = 0;
3575 View motionView = this.getChildAt(motionIndex);
3576 if (motionView != null) {
3577 motionViewPrevTop = motionView.getTop();
3578 }
3579
3580 // No need to do all this work if we're not going to move anyway
3581 boolean atEdge = false;
3582 if (incrementalDeltaY != 0) {
3583 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3584 }
3585
3586 // Check to see if we have bumped into the scroll limit
3587 motionView = this.getChildAt(motionIndex);
3588 if (motionView != null) {
3589 // Check if the top of the motion view is where it is
3590 // supposed to be
3591 final int motionViewRealTop = motionView.getTop();
3592 if (atEdge) {
3593 // Apply overscroll
3594
3595 int overscroll = -incrementalDeltaY -
3596 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003597 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3598 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003599 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003600 if (vtev != null) {
3601 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003602 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003603 }
Adam Powell96d62af2014-05-02 10:04:38 -07003604 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003605 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3606 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3607
3608 if (atOverscrollEdge && mVelocityTracker != null) {
3609 // Don't allow overfling if we're at the edge
3610 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003611 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003612
Adam Powell96d62af2014-05-02 10:04:38 -07003613 final int overscrollMode = getOverScrollMode();
3614 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3615 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3616 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003617 if (!atOverscrollEdge) {
3618 mDirection = 0; // Reset when entering overscroll.
3619 mTouchMode = TOUCH_MODE_OVERSCROLL;
3620 }
3621 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003622 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003623 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003624 if (!mEdgeGlowBottom.isFinished()) {
3625 mEdgeGlowBottom.onRelease();
3626 }
Doris Liuf36c0612015-06-04 11:11:14 -07003627 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003628 } else if (incrementalDeltaY < 0) {
3629 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3630 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003631 if (!mEdgeGlowTop.isFinished()) {
3632 mEdgeGlowTop.onRelease();
3633 }
Doris Liuf36c0612015-06-04 11:11:14 -07003634 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003635 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003636 }
3637 }
3638 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003639 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003640 }
Yorke Lee43943d82014-05-08 10:15:20 -07003641 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003642 }
3643 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3644 if (y != mLastY) {
3645 final int oldScroll = mScrollY;
3646 final int newScroll = oldScroll - incrementalDeltaY;
3647 int newDirection = y > mLastY ? 1 : -1;
3648
3649 if (mDirection == 0) {
3650 mDirection = newDirection;
3651 }
3652
3653 int overScrollDistance = -incrementalDeltaY;
3654 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3655 overScrollDistance = -oldScroll;
3656 incrementalDeltaY += overScrollDistance;
3657 } else {
3658 incrementalDeltaY = 0;
3659 }
3660
3661 if (overScrollDistance != 0) {
3662 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3663 0, mOverscrollDistance, true);
3664 final int overscrollMode = getOverScrollMode();
3665 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3666 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3667 !contentFits())) {
3668 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003669 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3670 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003671 if (!mEdgeGlowBottom.isFinished()) {
3672 mEdgeGlowBottom.onRelease();
3673 }
Doris Liuf36c0612015-06-04 11:11:14 -07003674 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003675 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003676 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3677 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003678 if (!mEdgeGlowTop.isFinished()) {
3679 mEdgeGlowTop.onRelease();
3680 }
Doris Liuf36c0612015-06-04 11:11:14 -07003681 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003682 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003683 }
3684 }
3685
3686 if (incrementalDeltaY != 0) {
3687 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003688 if (mScrollY != 0) {
3689 mScrollY = 0;
3690 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003691 }
3692
Romain Guy9d849a22012-03-14 16:41:42 -07003693 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3694
Jeff Brown78f6e632011-09-09 17:15:31 -07003695 mTouchMode = TOUCH_MODE_SCROLL;
3696
3697 // We did not scroll the full amount. Treat this essentially like the
3698 // start of a new touch scroll
3699 final int motionPosition = findClosestMotionRow(y);
3700
3701 mMotionCorrection = 0;
3702 View motionView = getChildAt(motionPosition - mFirstPosition);
3703 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003704 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003705 mMotionPosition = motionPosition;
3706 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003707 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003708 mDirection = newDirection;
3709 }
3710 }
3711 }
3712
Doris Liuf36c0612015-06-04 11:11:14 -07003713 private void invalidateTopGlow() {
3714 if (mEdgeGlowTop == null) {
3715 return;
3716 }
3717 final boolean clipToPadding = getClipToPadding();
3718 final int top = clipToPadding ? mPaddingTop : 0;
3719 final int left = clipToPadding ? mPaddingLeft : 0;
3720 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3721 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3722 }
3723
3724 private void invalidateBottomGlow() {
3725 if (mEdgeGlowBottom == null) {
3726 return;
3727 }
3728 final boolean clipToPadding = getClipToPadding();
3729 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3730 final int left = clipToPadding ? mPaddingLeft : 0;
3731 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3732 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3733 }
3734
Alan Viverette8fa327a2013-05-31 14:53:13 -07003735 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003736 public void onTouchModeChanged(boolean isInTouchMode) {
3737 if (isInTouchMode) {
3738 // Get rid of the selection when we enter touch mode
3739 hideSelector();
3740 // Layout, but only if we already have done so previously.
3741 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3742 // state.)
3743 if (getHeight() > 0 && getChildCount() > 0) {
3744 // We do not lose focus initiating a touch (since AbsListView is focusable in
3745 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003746 layoutChildren();
3747 }
Jeff Brown1e209462011-07-14 22:19:19 -07003748 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003749 } else {
3750 int touchMode = mTouchMode;
3751 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3752 if (mFlingRunnable != null) {
3753 mFlingRunnable.endFling();
3754 }
Adam Powell40322522011-01-12 21:58:20 -08003755 if (mPositionScroller != null) {
3756 mPositionScroller.stop();
3757 }
Adam Powell637d3372010-08-25 14:37:03 -07003758
3759 if (mScrollY != 0) {
3760 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003761 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003762 finishGlows();
3763 invalidate();
3764 }
3765 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003766 }
3767 }
3768
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003769 /** @hide */
3770 @Override
3771 protected boolean handleScrollBarDragging(MotionEvent event) {
3772 // Doesn't support normal scroll bar dragging. Use FastScroller.
3773 return false;
3774 }
3775
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003776 @Override
3777 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003778 if (!isEnabled()) {
3779 // A disabled view that is clickable still consumes the touch
3780 // events, it just doesn't respond to them.
3781 return isClickable() || isLongClickable();
3782 }
3783
Adam Powell1fa179ef2012-04-12 15:01:40 -07003784 if (mPositionScroller != null) {
3785 mPositionScroller.stop();
3786 }
3787
Alan Viverette462c2172014-02-24 12:24:11 -08003788 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003789 // Something isn't right.
3790 // Since we rely on being attached to get data set change notifications,
3791 // don't risk doing anything where we might try to resync and find things
3792 // in a bogus state.
3793 return false;
3794 }
3795
Adam Powell96d62af2014-05-02 10:04:38 -07003796 startNestedScroll(SCROLL_AXIS_VERTICAL);
3797
Alan Viverettefb99ba82015-05-01 10:10:15 -07003798 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3799 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003800 }
Romain Guy82f34952009-05-24 18:40:45 -07003801
Michael Jurka13451a42011-08-22 15:54:21 -07003802 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003803 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003804
Alan Viverette8fa327a2013-05-31 14:53:13 -07003805 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003806 if (actionMasked == MotionEvent.ACTION_DOWN) {
3807 mNestedYOffset = 0;
3808 }
3809 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003810 switch (actionMasked) {
3811 case MotionEvent.ACTION_DOWN: {
3812 onTouchDown(ev);
3813 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003814 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003815
Alan Viverette8fa327a2013-05-31 14:53:13 -07003816 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003817 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003818 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003819 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003820
3821 case MotionEvent.ACTION_UP: {
3822 onTouchUp(ev);
3823 break;
3824 }
3825
3826 case MotionEvent.ACTION_CANCEL: {
3827 onTouchCancel();
3828 break;
3829 }
3830
3831 case MotionEvent.ACTION_POINTER_UP: {
3832 onSecondaryPointerUp(ev);
3833 final int x = mMotionX;
3834 final int y = mMotionY;
3835 final int motionPosition = pointToPosition(x, y);
3836 if (motionPosition >= 0) {
3837 // Remember where the motion event started
3838 final View child = getChildAt(motionPosition - mFirstPosition);
3839 mMotionViewOriginalTop = child.getTop();
3840 mMotionPosition = motionPosition;
3841 }
3842 mLastY = y;
3843 break;
3844 }
3845
3846 case MotionEvent.ACTION_POINTER_DOWN: {
3847 // New pointers take over dragging duties
3848 final int index = ev.getActionIndex();
3849 final int id = ev.getPointerId(index);
3850 final int x = (int) ev.getX(index);
3851 final int y = (int) ev.getY(index);
3852 mMotionCorrection = 0;
3853 mActivePointerId = id;
3854 mMotionX = x;
3855 mMotionY = y;
3856 final int motionPosition = pointToPosition(x, y);
3857 if (motionPosition >= 0) {
3858 // Remember where the motion event started
3859 final View child = getChildAt(motionPosition - mFirstPosition);
3860 mMotionViewOriginalTop = child.getTop();
3861 mMotionPosition = motionPosition;
3862 }
3863 mLastY = y;
3864 break;
3865 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003866 }
3867
Adam Powell96d62af2014-05-02 10:04:38 -07003868 if (mVelocityTracker != null) {
3869 mVelocityTracker.addMovement(vtev);
3870 }
3871 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003872 return true;
3873 }
Romain Guy0a637162009-05-29 14:43:54 -07003874
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003875 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003876 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003877 mActivePointerId = ev.getPointerId(0);
Evan Rosky837ae0d2017-10-26 12:50:33 -07003878 hideSelector();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003879
3880 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3881 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003882 mFlingRunnable.endFling();
3883 if (mPositionScroller != null) {
3884 mPositionScroller.stop();
3885 }
3886 mTouchMode = TOUCH_MODE_OVERSCROLL;
3887 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003888 mMotionY = (int) ev.getY();
3889 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003890 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003891 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003892 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003893 final int x = (int) ev.getX();
3894 final int y = (int) ev.getY();
3895 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003896
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003897 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003898 if (mTouchMode == TOUCH_MODE_FLING) {
3899 // Stopped a fling. It is a scroll.
3900 createScrollingCache();
3901 mTouchMode = TOUCH_MODE_SCROLL;
3902 mMotionCorrection = 0;
3903 motionPosition = findMotionRow(y);
3904 mFlingRunnable.flywheelTouch();
3905 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3906 // User clicked on an actual view (and was not stopping a
3907 // fling). It might be a click or a scroll. Assume it is a
3908 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003909 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003910
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003911 // FIXME Debounce
3912 if (mPendingCheckForTap == null) {
3913 mPendingCheckForTap = new CheckForTap();
3914 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003915
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003916 mPendingCheckForTap.x = ev.getX();
3917 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003918 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003919 }
3920 }
3921
3922 if (motionPosition >= 0) {
3923 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003924 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003925 mMotionViewOriginalTop = v.getTop();
3926 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003927
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003928 mMotionX = x;
3929 mMotionY = y;
3930 mMotionPosition = motionPosition;
3931 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003932 }
3933
Alan Viveretteb339cc52013-08-12 13:29:15 -07003934 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003935 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003936 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003937 }
3938 }
3939
Adam Powell96d62af2014-05-02 10:04:38 -07003940 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003941 if (mHasPerformedLongPress) {
3942 // Consume all move events following a successful long press.
3943 return;
3944 }
3945
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003946 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3947 if (pointerIndex == -1) {
3948 pointerIndex = 0;
3949 mActivePointerId = ev.getPointerId(pointerIndex);
3950 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003951
3952 if (mDataChanged) {
3953 // Re-sync everything if data has been changed
3954 // since the scroll operation can query the adapter.
3955 layoutChildren();
3956 }
3957
Alan Viverette8fa327a2013-05-31 14:53:13 -07003958 final int y = (int) ev.getY(pointerIndex);
3959
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003960 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003961 case TOUCH_MODE_DOWN:
3962 case TOUCH_MODE_TAP:
3963 case TOUCH_MODE_DONE_WAITING:
3964 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003965 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003966 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003967 break;
3968 }
3969 // Otherwise, check containment within list bounds. If we're
3970 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003971 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003972 final float x = ev.getX(pointerIndex);
3973 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003974 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003975 if (motionView != null) {
3976 motionView.setPressed(false);
3977 }
3978 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3979 mPendingCheckForTap : mPendingCheckForLongPress);
3980 mTouchMode = TOUCH_MODE_DONE_WAITING;
3981 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003982 } else if (motionView != null) {
3983 // Still within bounds, update the hotspot.
3984 final float[] point = mTmpPoint;
3985 point[0] = x;
3986 point[1] = y;
3987 transformPointToViewLocal(point, motionView);
3988 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003989 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003990 break;
3991 case TOUCH_MODE_SCROLL:
3992 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003993 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003994 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003995 }
3996 }
3997
3998 private void onTouchUp(MotionEvent ev) {
3999 switch (mTouchMode) {
4000 case TOUCH_MODE_DOWN:
4001 case TOUCH_MODE_TAP:
4002 case TOUCH_MODE_DONE_WAITING:
4003 final int motionPosition = mMotionPosition;
4004 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07004005 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004006 if (mTouchMode != TOUCH_MODE_DOWN) {
4007 child.setPressed(false);
4008 }
4009
Alan Viverette74ded292013-06-03 15:34:11 -07004010 final float x = ev.getX();
4011 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
Adam Powell0f552f42017-02-03 11:50:42 -08004012 if (inList && !child.hasExplicitFocusable()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004013 if (mPerformClick == null) {
4014 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004015 }
Alan Viverette74ded292013-06-03 15:34:11 -07004016
4017 final AbsListView.PerformClick performClick = mPerformClick;
4018 performClick.mClickMotionPosition = motionPosition;
4019 performClick.rememberWindowAttachCount();
4020
4021 mResurrectToPosition = motionPosition;
4022
4023 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4024 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4025 mPendingCheckForTap : mPendingCheckForLongPress);
4026 mLayoutMode = LAYOUT_NORMAL;
4027 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4028 mTouchMode = TOUCH_MODE_TAP;
4029 setSelectedPositionInt(mMotionPosition);
4030 layoutChildren();
4031 child.setPressed(true);
4032 positionSelector(mMotionPosition, child);
4033 setPressed(true);
4034 if (mSelector != null) {
4035 Drawable d = mSelector.getCurrent();
4036 if (d != null && d instanceof TransitionDrawable) {
4037 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004038 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004039 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004040 }
Alan Viverette74ded292013-06-03 15:34:11 -07004041 if (mTouchModeReset != null) {
4042 removeCallbacks(mTouchModeReset);
4043 }
4044 mTouchModeReset = new Runnable() {
4045 @Override
4046 public void run() {
4047 mTouchModeReset = null;
4048 mTouchMode = TOUCH_MODE_REST;
4049 child.setPressed(false);
4050 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004051 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004052 performClick.run();
4053 }
4054 }
4055 };
4056 postDelayed(mTouchModeReset,
4057 ViewConfiguration.getPressedStateDuration());
4058 } else {
4059 mTouchMode = TOUCH_MODE_REST;
4060 updateSelectorState();
4061 }
4062 return;
4063 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4064 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004065 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004066 }
4067 }
4068 mTouchMode = TOUCH_MODE_REST;
4069 updateSelectorState();
4070 break;
4071 case TOUCH_MODE_SCROLL:
4072 final int childCount = getChildCount();
4073 if (childCount > 0) {
4074 final int firstChildTop = getChildAt(0).getTop();
4075 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4076 final int contentTop = mListPadding.top;
4077 final int contentBottom = getHeight() - mListPadding.bottom;
4078 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4079 mFirstPosition + childCount < mItemCount &&
4080 lastChildBottom <= getHeight() - contentBottom) {
4081 mTouchMode = TOUCH_MODE_REST;
4082 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4083 } else {
4084 final VelocityTracker velocityTracker = mVelocityTracker;
4085 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4086
4087 final int initialVelocity = (int)
4088 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4089 // Fling if we have enough velocity and we aren't at a boundary.
4090 // Since we can potentially overfling more than we can overscroll, don't
4091 // allow the weird behavior where you can scroll to a boundary then
4092 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004093 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4094 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004095 !((mFirstPosition == 0 &&
4096 firstChildTop == contentTop - mOverscrollDistance) ||
4097 (mFirstPosition + childCount == mItemCount &&
4098 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004099 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4100 if (mFlingRunnable == null) {
4101 mFlingRunnable = new FlingRunnable();
4102 }
4103 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4104 mFlingRunnable.start(-initialVelocity);
4105 dispatchNestedFling(0, -initialVelocity, true);
4106 } else {
4107 mTouchMode = TOUCH_MODE_REST;
4108 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004109 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004110 } else {
4111 mTouchMode = TOUCH_MODE_REST;
4112 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4113 if (mFlingRunnable != null) {
4114 mFlingRunnable.endFling();
4115 }
4116 if (mPositionScroller != null) {
4117 mPositionScroller.stop();
4118 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004119 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004120 dispatchNestedFling(0, -initialVelocity, false);
4121 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004122 }
4123 }
4124 } else {
4125 mTouchMode = TOUCH_MODE_REST;
4126 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4127 }
4128 break;
4129
4130 case TOUCH_MODE_OVERSCROLL:
4131 if (mFlingRunnable == null) {
4132 mFlingRunnable = new FlingRunnable();
4133 }
4134 final VelocityTracker velocityTracker = mVelocityTracker;
4135 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4136 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4137
4138 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4139 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4140 mFlingRunnable.startOverfling(-initialVelocity);
4141 } else {
4142 mFlingRunnable.startSpringback();
4143 }
4144
4145 break;
4146 }
4147
4148 setPressed(false);
4149
4150 if (mEdgeGlowTop != null) {
4151 mEdgeGlowTop.onRelease();
4152 mEdgeGlowBottom.onRelease();
4153 }
4154
4155 // Need to redraw since we probably aren't drawing the selector anymore
4156 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004157 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004158 recycleVelocityTracker();
4159
4160 mActivePointerId = INVALID_POINTER;
4161
4162 if (PROFILE_SCROLLING) {
4163 if (mScrollProfilingStarted) {
4164 Debug.stopMethodTracing();
4165 mScrollProfilingStarted = false;
4166 }
4167 }
4168
4169 if (mScrollStrictSpan != null) {
4170 mScrollStrictSpan.finish();
4171 mScrollStrictSpan = null;
4172 }
4173 }
4174
4175 private void onTouchCancel() {
4176 switch (mTouchMode) {
4177 case TOUCH_MODE_OVERSCROLL:
4178 if (mFlingRunnable == null) {
4179 mFlingRunnable = new FlingRunnable();
4180 }
4181 mFlingRunnable.startSpringback();
4182 break;
4183
4184 case TOUCH_MODE_OVERFLING:
4185 // Do nothing - let it play out.
4186 break;
4187
4188 default:
4189 mTouchMode = TOUCH_MODE_REST;
4190 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004191 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004192 if (motionView != null) {
4193 motionView.setPressed(false);
4194 }
4195 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004196 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004197 recycleVelocityTracker();
4198 }
4199
4200 if (mEdgeGlowTop != null) {
4201 mEdgeGlowTop.onRelease();
4202 mEdgeGlowBottom.onRelease();
4203 }
4204 mActivePointerId = INVALID_POINTER;
4205 }
4206
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004207 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004208 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4209 if (mScrollY != scrollY) {
4210 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4211 mScrollY = scrollY;
4212 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004213
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004214 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004215 }
Adam Powell637d3372010-08-25 14:37:03 -07004216 }
4217
4218 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004219 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004220 switch (event.getAction()) {
4221 case MotionEvent.ACTION_SCROLL:
4222 final float axisValue;
4223 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4224 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4225 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4226 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4227 } else {
4228 axisValue = 0;
4229 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004230
Aaron Whytef8306522017-03-22 16:30:58 -07004231 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -04004232 if (delta != 0) {
4233 if (!trackMotionScroll(delta, delta)) {
4234 return true;
4235 }
4236 }
4237 break;
4238 case MotionEvent.ACTION_BUTTON_PRESS:
4239 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004240 int actionButton = event.getActionButton();
4241 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4242 || actionButton == MotionEvent.BUTTON_SECONDARY)
4243 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4244 if (performStylusButtonPressAction(event)) {
4245 removeCallbacks(mPendingCheckForLongPress);
4246 removeCallbacks(mPendingCheckForTap);
4247 }
4248 }
Ned Burns20ad0732016-08-18 14:22:57 -04004249 }
4250 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004251 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004252
Jeff Brown33bbfd22011-02-24 20:55:35 -08004253 return super.onGenericMotionEvent(event);
4254 }
4255
Adam Powell4884c642014-08-07 13:52:53 -07004256 /**
4257 * Initiate a fling with the given velocity.
4258 *
4259 * <p>Applications can use this method to manually initiate a fling as if the user
4260 * initiated it via touch interaction.</p>
4261 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004262 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4263 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004264 */
4265 public void fling(int velocityY) {
4266 if (mFlingRunnable == null) {
4267 mFlingRunnable = new FlingRunnable();
4268 }
4269 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004270 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004271 }
4272
Jeff Brown33bbfd22011-02-24 20:55:35 -08004273 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004274 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4275 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4276 }
4277
4278 @Override
4279 public void onNestedScrollAccepted(View child, View target, int axes) {
4280 super.onNestedScrollAccepted(child, target, axes);
4281 startNestedScroll(SCROLL_AXIS_VERTICAL);
4282 }
4283
4284 @Override
4285 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4286 int dxUnconsumed, int dyUnconsumed) {
4287 final int motionIndex = getChildCount() / 2;
4288 final View motionView = getChildAt(motionIndex);
4289 final int oldTop = motionView != null ? motionView.getTop() : 0;
4290 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4291 int myUnconsumed = dyUnconsumed;
4292 int myConsumed = 0;
4293 if (motionView != null) {
4294 myConsumed = motionView.getTop() - oldTop;
4295 myUnconsumed -= myConsumed;
4296 }
4297 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4298 }
4299 }
4300
4301 @Override
4302 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4303 final int childCount = getChildCount();
4304 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4305 Math.abs(velocityY) > mMinimumVelocity) {
4306 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4307 if (mFlingRunnable == null) {
4308 mFlingRunnable = new FlingRunnable();
4309 }
Adam Powell9413b242014-08-06 17:34:24 -07004310 if (!dispatchNestedPreFling(0, velocityY)) {
4311 mFlingRunnable.start((int) velocityY);
4312 }
Adam Powell96d62af2014-05-02 10:04:38 -07004313 return true;
4314 }
4315 return dispatchNestedFling(velocityX, velocityY, consumed);
4316 }
4317
4318 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004319 public void draw(Canvas canvas) {
4320 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004321 if (mEdgeGlowTop != null) {
4322 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004323 final boolean clipToPadding = getClipToPadding();
4324 final int width;
4325 final int height;
4326 final int translateX;
4327 final int translateY;
4328
4329 if (clipToPadding) {
4330 width = getWidth() - mPaddingLeft - mPaddingRight;
4331 height = getHeight() - mPaddingTop - mPaddingBottom;
4332 translateX = mPaddingLeft;
4333 translateY = mPaddingTop;
4334 } else {
4335 width = getWidth();
4336 height = getHeight();
4337 translateX = 0;
4338 translateY = 0;
4339 }
Vu Thanh Cong3af8c962016-11-10 22:40:24 +09004340 mEdgeGlowTop.setSize(width, height);
4341 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004342 if (!mEdgeGlowTop.isFinished()) {
4343 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004344 canvas.clipRect(translateX, translateY,
4345 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4346 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4347 canvas.translate(translateX, edgeY);
Adam Powell637d3372010-08-25 14:37:03 -07004348 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004349 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004350 }
4351 canvas.restoreToCount(restoreCount);
4352 }
4353 if (!mEdgeGlowBottom.isFinished()) {
4354 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004355 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4356 translateX + width, translateY + height);
4357 final int edgeX = -width + translateX;
4358 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4359 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004360 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004361 canvas.rotate(180, width, 0);
Adam Powell637d3372010-08-25 14:37:03 -07004362 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004363 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004364 }
4365 canvas.restoreToCount(restoreCount);
4366 }
4367 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004368 }
4369
Michael Jurka13451a42011-08-22 15:54:21 -07004370 private void initOrResetVelocityTracker() {
4371 if (mVelocityTracker == null) {
4372 mVelocityTracker = VelocityTracker.obtain();
4373 } else {
4374 mVelocityTracker.clear();
4375 }
4376 }
4377
4378 private void initVelocityTrackerIfNotExists() {
4379 if (mVelocityTracker == null) {
4380 mVelocityTracker = VelocityTracker.obtain();
4381 }
4382 }
4383
4384 private void recycleVelocityTracker() {
4385 if (mVelocityTracker != null) {
4386 mVelocityTracker.recycle();
4387 mVelocityTracker = null;
4388 }
4389 }
4390
4391 @Override
4392 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4393 if (disallowIntercept) {
4394 recycleVelocityTracker();
4395 }
4396 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4397 }
4398
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004399 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004400 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004401 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004402 return true;
4403 }
4404
4405 return super.onInterceptHoverEvent(event);
4406 }
4407
4408 @Override
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08004409 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4410 if (mFastScroll != null) {
4411 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4412 if (pointerIcon != null) {
4413 return pointerIcon;
4414 }
4415 }
4416 return super.onResolvePointerIcon(event, pointerIndex);
4417 }
4418
4419 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004420 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004421 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004422 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004423
Adam Powell1fa179ef2012-04-12 15:01:40 -07004424 if (mPositionScroller != null) {
4425 mPositionScroller.stop();
4426 }
4427
Alan Viverette462c2172014-02-24 12:24:11 -08004428 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004429 // Something isn't right.
4430 // Since we rely on being attached to get data set change notifications,
4431 // don't risk doing anything where we might try to resync and find things
4432 // in a bogus state.
4433 return false;
4434 }
4435
Alan Viverette8636ace2013-10-31 15:41:31 -07004436 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004437 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004438 }
Romain Guy0a637162009-05-29 14:43:54 -07004439
Adam Powell744beff2014-09-22 09:47:48 -07004440 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004441 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004442 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004443 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4444 mMotionCorrection = 0;
4445 return true;
4446 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004447
Adam Powell4cd47702010-02-25 11:21:14 -08004448 final int x = (int) ev.getX();
4449 final int y = (int) ev.getY();
4450 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004451
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004452 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004453 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004454 // User clicked on an actual view (and was not stopping a fling).
4455 // Remember where the motion event started
4456 v = getChildAt(motionPosition - mFirstPosition);
4457 mMotionViewOriginalTop = v.getTop();
4458 mMotionX = x;
4459 mMotionY = y;
4460 mMotionPosition = motionPosition;
4461 mTouchMode = TOUCH_MODE_DOWN;
4462 clearScrollingCache();
4463 }
4464 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004465 initOrResetVelocityTracker();
4466 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004467 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004468 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004469 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004470 return true;
4471 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004472 break;
4473 }
4474
4475 case MotionEvent.ACTION_MOVE: {
4476 switch (mTouchMode) {
4477 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004478 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4479 if (pointerIndex == -1) {
4480 pointerIndex = 0;
4481 mActivePointerId = ev.getPointerId(pointerIndex);
4482 }
Adam Powell4cd47702010-02-25 11:21:14 -08004483 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004484 initVelocityTrackerIfNotExists();
4485 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004486 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004487 return true;
4488 }
4489 break;
4490 }
4491 break;
4492 }
4493
Michael Jurka13451a42011-08-22 15:54:21 -07004494 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004495 case MotionEvent.ACTION_UP: {
4496 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004497 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004498 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004499 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004500 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004501 break;
4502 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004503
Adam Powell4cd47702010-02-25 11:21:14 -08004504 case MotionEvent.ACTION_POINTER_UP: {
4505 onSecondaryPointerUp(ev);
4506 break;
4507 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004508 }
4509
4510 return false;
4511 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004512
Adam Powell4cd47702010-02-25 11:21:14 -08004513 private void onSecondaryPointerUp(MotionEvent ev) {
4514 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4515 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4516 final int pointerId = ev.getPointerId(pointerIndex);
4517 if (pointerId == mActivePointerId) {
4518 // This was our active pointer going up. Choose a new
4519 // active pointer and adjust accordingly.
4520 // TODO: Make this decision more intelligent.
4521 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4522 mMotionX = (int) ev.getX(newPointerIndex);
4523 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004524 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004525 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004526 }
4527 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004528
4529 /**
4530 * {@inheritDoc}
4531 */
4532 @Override
4533 public void addTouchables(ArrayList<View> views) {
4534 final int count = getChildCount();
4535 final int firstPosition = mFirstPosition;
4536 final ListAdapter adapter = mAdapter;
4537
4538 if (adapter == null) {
4539 return;
4540 }
4541
4542 for (int i = 0; i < count; i++) {
4543 final View child = getChildAt(i);
4544 if (adapter.isEnabled(firstPosition + i)) {
4545 views.add(child);
4546 }
4547 child.addTouchables(views);
4548 }
4549 }
4550
4551 /**
4552 * Fires an "on scroll state changed" event to the registered
4553 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4554 * is fired only if the specified state is different from the previously known state.
4555 *
4556 * @param newState The new scroll state.
4557 */
4558 void reportScrollStateChange(int newState) {
4559 if (newState != mLastScrollState) {
4560 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004561 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004562 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004563 }
4564 }
4565 }
4566
4567 /**
4568 * Responsible for fling behavior. Use {@link #start(int)} to
4569 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4570 * A FlingRunnable will keep re-posting itself until the fling is done.
4571 *
4572 */
4573 private class FlingRunnable implements Runnable {
4574 /**
4575 * Tracks the decay of a fling scroll
4576 */
Adam Powell637d3372010-08-25 14:37:03 -07004577 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004578
4579 /**
4580 * Y value reported by mScroller on the previous fling
4581 */
4582 private int mLastFlingY;
4583
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004584 /**
4585 * If true, {@link #endFling()} will not report scroll state change to
4586 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4587 */
4588 private boolean mSuppressIdleStateChangeCall;
4589
Gilles Debunned348bb42010-11-15 12:19:35 -08004590 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004591 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004592 public void run() {
4593 final int activeId = mActivePointerId;
4594 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004595 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004596 if (vt == null || activeId == INVALID_POINTER) {
4597 return;
4598 }
4599
4600 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4601 final float yvel = -vt.getYVelocity(activeId);
4602
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004603 if (Math.abs(yvel) >= mMinimumVelocity
4604 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004605 // Keep the fling alive a little longer
4606 postDelayed(this, FLYWHEEL_TIMEOUT);
4607 } else {
4608 endFling();
4609 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004610 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004611 }
4612 }
4613 };
4614
4615 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4616
Adam Powell79ac3392010-01-28 21:22:20 -08004617 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004618 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004619 }
4620
Adam Powell79ac3392010-01-28 21:22:20 -08004621 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004622 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4623 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004624 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004625 mScroller.fling(0, initialY, 0, initialVelocity,
4626 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4627 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004628 mSuppressIdleStateChangeCall = false;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004629 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004630
4631 if (PROFILE_FLINGING) {
4632 if (!mFlingProfilingStarted) {
4633 Debug.startMethodTracing("AbsListViewFling");
4634 mFlingProfilingStarted = true;
4635 }
4636 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004637
4638 if (mFlingStrictSpan == null) {
4639 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4640 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004641 }
Adam Powell45803472010-01-25 15:10:44 -08004642
Adam Powell637d3372010-08-25 14:37:03 -07004643 void startSpringback() {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004644 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004645 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4646 mTouchMode = TOUCH_MODE_OVERFLING;
4647 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004648 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004649 } else {
4650 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004651 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004652 }
4653 }
4654
4655 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004656 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004657 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4658 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004659 mTouchMode = TOUCH_MODE_OVERFLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004660 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004661 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004662 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004663 }
4664
4665 void edgeReached(int delta) {
4666 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4667 final int overscrollMode = getOverScrollMode();
4668 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4669 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4670 mTouchMode = TOUCH_MODE_OVERFLING;
4671 final int vel = (int) mScroller.getCurrVelocity();
4672 if (delta > 0) {
4673 mEdgeGlowTop.onAbsorb(vel);
4674 } else {
4675 mEdgeGlowBottom.onAbsorb(vel);
4676 }
Adam Powell40322522011-01-12 21:58:20 -08004677 } else {
4678 mTouchMode = TOUCH_MODE_REST;
4679 if (mPositionScroller != null) {
4680 mPositionScroller.stop();
4681 }
Adam Powell637d3372010-08-25 14:37:03 -07004682 }
4683 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004684 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004685 }
4686
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004687 void startScroll(int distance, int duration, boolean linear,
4688 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004689 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4690 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004691 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004692 mScroller.startScroll(0, initialY, 0, distance, duration);
4693 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004694 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004695 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004696 }
4697
Gilles Debunned348bb42010-11-15 12:19:35 -08004698 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004699 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004700
Adam Powell79ac3392010-01-28 21:22:20 -08004701 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004702 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004703
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004704 if (!mSuppressIdleStateChangeCall) {
4705 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4706 }
Romain Guy21317d12010-10-12 13:32:31 -07004707 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004708 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004709
4710 if (mFlingStrictSpan != null) {
4711 mFlingStrictSpan.finish();
4712 mFlingStrictSpan = null;
4713 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004714 }
4715
4716 void flywheelTouch() {
4717 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004718 }
4719
Alan Viverette8fa327a2013-05-31 14:53:13 -07004720 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004721 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004722 switch (mTouchMode) {
4723 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004724 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004725 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004726
Gilles Debunned348bb42010-11-15 12:19:35 -08004727 case TOUCH_MODE_SCROLL:
4728 if (mScroller.isFinished()) {
4729 return;
4730 }
4731 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004732 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004733 if (mDataChanged) {
4734 layoutChildren();
4735 }
4736
Adam Powell79ac3392010-01-28 21:22:20 -08004737 if (mItemCount == 0 || getChildCount() == 0) {
4738 endFling();
4739 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004740 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004741
Adam Powell637d3372010-08-25 14:37:03 -07004742 final OverScroller scroller = mScroller;
4743 boolean more = scroller.computeScrollOffset();
4744 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004745
Adam Powell637d3372010-08-25 14:37:03 -07004746 // Flip sign to convert finger direction to list items direction
4747 // (e.g. finger moving down means list is moving towards the top)
4748 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004749
Adam Powell637d3372010-08-25 14:37:03 -07004750 // Pretend that each frame of a fling scroll is a touch scroll
4751 if (delta > 0) {
4752 // List is moving towards the top. Use first view as mMotionPosition
4753 mMotionPosition = mFirstPosition;
4754 final View firstView = getChildAt(0);
4755 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004756
Adam Powell637d3372010-08-25 14:37:03 -07004757 // Don't fling more than 1 screen
4758 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4759 } else {
4760 // List is moving towards the bottom. Use last view as mMotionPosition
4761 int offsetToLast = getChildCount() - 1;
4762 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004763
Adam Powell637d3372010-08-25 14:37:03 -07004764 final View lastView = getChildAt(offsetToLast);
4765 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004766
Adam Powell637d3372010-08-25 14:37:03 -07004767 // Don't fling more than 1 screen
4768 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4769 }
Adam Powell79ac3392010-01-28 21:22:20 -08004770
Adam Powell637d3372010-08-25 14:37:03 -07004771 // Check to see if we have bumped into the scroll limit
4772 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4773 int oldTop = 0;
4774 if (motionView != null) {
4775 oldTop = motionView.getTop();
4776 }
Adam Powell9d32d242010-03-29 16:02:07 -07004777
Adam Powell637d3372010-08-25 14:37:03 -07004778 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004779 final boolean atEdge = trackMotionScroll(delta, delta);
4780 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004781 if (atEnd) {
4782 if (motionView != null) {
4783 // Tweak the scroll for how far we overshot
4784 int overshoot = -(delta - (motionView.getTop() - oldTop));
4785 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4786 0, mOverflingDistance, false);
4787 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004788 if (more) {
4789 edgeReached(delta);
4790 }
Adam Powell637d3372010-08-25 14:37:03 -07004791 break;
4792 }
4793
4794 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004795 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004796 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004797 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004798 } else {
4799 endFling();
4800
4801 if (PROFILE_FLINGING) {
4802 if (mFlingProfilingStarted) {
4803 Debug.stopMethodTracing();
4804 mFlingProfilingStarted = false;
4805 }
4806
4807 if (mFlingStrictSpan != null) {
4808 mFlingStrictSpan.finish();
4809 mFlingStrictSpan = null;
4810 }
Adam Powell79ac3392010-01-28 21:22:20 -08004811 }
4812 }
Adam Powell637d3372010-08-25 14:37:03 -07004813 break;
4814 }
4815
4816 case TOUCH_MODE_OVERFLING: {
4817 final OverScroller scroller = mScroller;
4818 if (scroller.computeScrollOffset()) {
4819 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004820 final int currY = scroller.getCurrY();
4821 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004822 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4823 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004824 final boolean crossDown = scrollY <= 0 && currY > 0;
4825 final boolean crossUp = scrollY >= 0 && currY < 0;
4826 if (crossDown || crossUp) {
4827 int velocity = (int) scroller.getCurrVelocity();
4828 if (crossUp) velocity = -velocity;
4829
4830 // Don't flywheel from this; we're just continuing things.
4831 scroller.abortAnimation();
4832 start(velocity);
4833 } else {
4834 startSpringback();
4835 }
Adam Powell637d3372010-08-25 14:37:03 -07004836 } else {
4837 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004838 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004839 }
4840 } else {
4841 endFling();
4842 }
4843 break;
4844 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004845 }
4846 }
4847 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004848
Adam Powell45803472010-01-25 15:10:44 -08004849 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004850 * The amount of friction applied to flings. The default value
4851 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004852 */
4853 public void setFriction(float friction) {
4854 if (mFlingRunnable == null) {
4855 mFlingRunnable = new FlingRunnable();
4856 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004857 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004858 }
Romain Guy21317d12010-10-12 13:32:31 -07004859
4860 /**
4861 * Sets a scale factor for the fling velocity. The initial scale
4862 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004863 *
Romain Guy21317d12010-10-12 13:32:31 -07004864 * @param scale The scale factor to multiply the velocity by.
4865 */
4866 public void setVelocityScale(float scale) {
4867 mVelocityScale = scale;
4868 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004869
Romain Guy4bede9e2010-10-11 19:36:59 -07004870 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004871 * Override this for better control over position scrolling.
4872 */
4873 AbsPositionScroller createPositionScroller() {
4874 return new PositionScroller();
4875 }
4876
4877 /**
Adam Powell45803472010-01-25 15:10:44 -08004878 * Smoothly scroll to the specified adapter position. The view will
4879 * scroll such that the indicated position is displayed.
4880 * @param position Scroll to this adapter position.
4881 */
4882 public void smoothScrollToPosition(int position) {
4883 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004884 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004885 }
4886 mPositionScroller.start(position);
4887 }
Erik322171b2010-10-13 15:46:00 -07004888
4889 /**
4890 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004891 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004892 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4893 * the first or last item beyond the boundaries of the list) it will get as close
4894 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4895 *
4896 * @param position Position to scroll to
4897 * @param offset Desired distance in pixels of <code>position</code> from the top
4898 * of the view when scrolling is finished
4899 * @param duration Number of milliseconds to use for the scroll
4900 */
4901 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4902 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004903 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004904 }
4905 mPositionScroller.startWithOffset(position, offset, duration);
4906 }
4907
Adam Powell45803472010-01-25 15:10:44 -08004908 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004909 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004910 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004911 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4912 * the first or last item beyond the boundaries of the list) it will get as close
4913 * as possible.
4914 *
4915 * @param position Position to scroll to
4916 * @param offset Desired distance in pixels of <code>position</code> from the top
4917 * of the view when scrolling is finished
4918 */
4919 public void smoothScrollToPositionFromTop(int position, int offset) {
4920 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004921 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004922 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004923 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004924 }
4925
4926 /**
Adam Powell45803472010-01-25 15:10:44 -08004927 * Smoothly scroll to the specified adapter position. The view will
4928 * scroll such that the indicated position is displayed, but it will
4929 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004930 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004931 *
Adam Powell45803472010-01-25 15:10:44 -08004932 * @param position Scroll to this adapter position.
4933 * @param boundPosition Do not scroll if it would move this adapter
4934 * position out of view.
4935 */
4936 public void smoothScrollToPosition(int position, int boundPosition) {
4937 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004938 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004939 }
4940 mPositionScroller.start(position, boundPosition);
4941 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004942
Adam Powell45803472010-01-25 15:10:44 -08004943 /**
4944 * Smoothly scroll by distance pixels over duration milliseconds.
4945 * @param distance Distance to scroll in pixels.
4946 * @param duration Duration of the scroll animation in milliseconds.
4947 */
4948 public void smoothScrollBy(int distance, int duration) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004949 smoothScrollBy(distance, duration, false, false);
Adam Powell0b8acd82012-04-25 20:29:23 -07004950 }
4951
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004952 void smoothScrollBy(int distance, int duration, boolean linear,
4953 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004954 if (mFlingRunnable == null) {
4955 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004956 }
Adam Powell40322522011-01-12 21:58:20 -08004957
Marc Blank299acb52010-10-21 11:03:53 -07004958 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004959 final int firstPos = mFirstPosition;
4960 final int childCount = getChildCount();
4961 final int lastPos = firstPos + childCount;
4962 final int topLimit = getPaddingTop();
4963 final int bottomLimit = getHeight() - getPaddingBottom();
4964
Adam Powell79303752011-01-13 22:06:49 -08004965 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004966 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004967 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004968 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4969 mFlingRunnable.endFling();
4970 if (mPositionScroller != null) {
4971 mPositionScroller.stop();
4972 }
4973 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004974 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004975 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
Marc Blank299acb52010-10-21 11:03:53 -07004976 }
Adam Powell45803472010-01-25 15:10:44 -08004977 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004978
Winson Chung499cb9f2010-07-16 11:18:17 -07004979 /**
4980 * Allows RemoteViews to scroll relatively to a position.
4981 */
4982 void smoothScrollByOffset(int position) {
4983 int index = -1;
4984 if (position < 0) {
4985 index = getFirstVisiblePosition();
4986 } else if (position > 0) {
4987 index = getLastVisiblePosition();
4988 }
4989
4990 if (index > -1) {
4991 View child = getChildAt(index - getFirstVisiblePosition());
4992 if (child != null) {
4993 Rect visibleRect = new Rect();
4994 if (child.getGlobalVisibleRect(visibleRect)) {
4995 // the child is partially visible
4996 int childRectArea = child.getWidth() * child.getHeight();
4997 int visibleRectArea = visibleRect.width() * visibleRect.height();
4998 float visibleArea = (visibleRectArea / (float) childRectArea);
4999 final float visibleThreshold = 0.75f;
5000 if ((position < 0) && (visibleArea < visibleThreshold)) {
5001 // the top index is not perceivably visible so offset
5002 // to account for showing that top index as well
5003 ++index;
5004 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
5005 // the bottom index is not perceivably visible so offset
5006 // to account for showing that bottom index as well
5007 --index;
5008 }
5009 }
5010 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5011 }
5012 }
5013 }
5014
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005015 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005016 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005017 setChildrenDrawnWithCacheEnabled(true);
5018 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08005019 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005020 }
5021 }
5022
5023 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005024 if (!isHardwareAccelerated()) {
5025 if (mClearScrollingCache == null) {
5026 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07005027 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07005028 public void run() {
5029 if (mCachingStarted) {
5030 mCachingStarted = mCachingActive = false;
5031 setChildrenDrawnWithCacheEnabled(false);
5032 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5033 setChildrenDrawingCacheEnabled(false);
5034 }
5035 if (!isAlwaysDrawnWithCacheEnabled()) {
5036 invalidate();
5037 }
Romain Guy6dfed242009-05-11 18:25:05 -07005038 }
5039 }
Romain Guy9d849a22012-03-14 16:41:42 -07005040 };
5041 }
5042 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005043 }
5044 }
5045
5046 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005047 * Scrolls the list items within the view by a specified number of pixels.
5048 *
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005049 * <p>The actual amount of scroll is capped by the list content viewport height
5050 * which is the list height minus top and bottom paddings minus one pixel.</p>
5051 *
Alan Viverette2f3317a2013-08-06 18:19:48 -07005052 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005053 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005054 */
Alan Viveretteba299062013-09-03 16:01:51 -07005055 public void scrollListBy(int y) {
5056 trackMotionScroll(-y, -y);
5057 }
5058
5059 /**
5060 * Check if the items in the list can be scrolled in a certain direction.
5061 *
5062 * @param direction Negative to check scrolling up, positive to check
5063 * scrolling down.
5064 * @return true if the list can be scrolled in the specified direction,
5065 * false otherwise.
5066 * @see #scrollListBy(int)
5067 */
5068 public boolean canScrollList(int direction) {
5069 final int childCount = getChildCount();
5070 if (childCount == 0) {
5071 return false;
5072 }
5073
5074 final int firstPosition = mFirstPosition;
5075 final Rect listPadding = mListPadding;
5076 if (direction > 0) {
5077 final int lastBottom = getChildAt(childCount - 1).getBottom();
5078 final int lastPosition = firstPosition + childCount;
5079 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5080 } else {
5081 final int firstTop = getChildAt(0).getTop();
5082 return firstPosition > 0 || firstTop < listPadding.top;
5083 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005084 }
5085
5086 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005087 * Track a motion scroll
5088 *
5089 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5090 * began. Positive numbers mean the user's finger is moving down the screen.
5091 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005092 * @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 -08005093 */
Adam Powell45803472010-01-25 15:10:44 -08005094 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005095 final int childCount = getChildCount();
5096 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005097 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005098 }
5099
5100 final int firstTop = getChildAt(0).getTop();
5101 final int lastBottom = getChildAt(childCount - 1).getBottom();
5102
5103 final Rect listPadding = mListPadding;
5104
Adam Powellbdccc2d2010-12-14 17:34:27 -08005105 // "effective padding" In this case is the amount of padding that affects
5106 // how much space should not be filled by items. If we don't clip to padding
5107 // there is no effective padding.
5108 int effectivePaddingTop = 0;
5109 int effectivePaddingBottom = 0;
5110 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5111 effectivePaddingTop = listPadding.top;
5112 effectivePaddingBottom = listPadding.bottom;
5113 }
5114
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005115 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005116 final int spaceAbove = effectivePaddingTop - firstTop;
5117 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005118 final int spaceBelow = lastBottom - end;
5119
5120 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5121 if (deltaY < 0) {
5122 deltaY = Math.max(-(height - 1), deltaY);
5123 } else {
5124 deltaY = Math.min(height - 1, deltaY);
5125 }
5126
5127 if (incrementalDeltaY < 0) {
5128 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5129 } else {
5130 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5131 }
5132
Adam Powell45803472010-01-25 15:10:44 -08005133 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005134
Adam Powell637d3372010-08-25 14:37:03 -07005135 // Update our guesses for where the first and last views are
5136 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005137 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005138 } else {
5139 mFirstPositionDistanceGuess += incrementalDeltaY;
5140 }
5141 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005142 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005143 } else {
5144 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005145 }
Adam Powell45803472010-01-25 15:10:44 -08005146
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005147 final boolean cannotScrollDown = (firstPosition == 0 &&
5148 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5149 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5150 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005151
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005152 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005153 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005154 }
5155
5156 final boolean down = incrementalDeltaY < 0;
5157
Adam Powell029cfbd2010-03-08 19:03:54 -08005158 final boolean inTouchMode = isInTouchMode();
5159 if (inTouchMode) {
5160 hideSelector();
5161 }
Adam Powell45803472010-01-25 15:10:44 -08005162
5163 final int headerViewsCount = getHeaderViewsCount();
5164 final int footerViewsStart = mItemCount - getFooterViewsCount();
5165
5166 int start = 0;
5167 int count = 0;
5168
5169 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005170 int top = -incrementalDeltaY;
5171 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5172 top += listPadding.top;
5173 }
Adam Powell45803472010-01-25 15:10:44 -08005174 for (int i = 0; i < childCount; i++) {
5175 final View child = getChildAt(i);
5176 if (child.getBottom() >= top) {
5177 break;
5178 } else {
5179 count++;
5180 int position = firstPosition + i;
5181 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005182 // The view will be rebound to new data, clear any
5183 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005184 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005185 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005186 }
5187 }
5188 }
5189 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005190 int bottom = getHeight() - incrementalDeltaY;
5191 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5192 bottom -= listPadding.bottom;
5193 }
Adam Powell45803472010-01-25 15:10:44 -08005194 for (int i = childCount - 1; i >= 0; i--) {
5195 final View child = getChildAt(i);
5196 if (child.getTop() <= bottom) {
5197 break;
5198 } else {
5199 start = i;
5200 count++;
5201 int position = firstPosition + i;
5202 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005203 // The view will be rebound to new data, clear any
5204 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005205 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005206 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005207 }
5208 }
5209 }
5210 }
5211
5212 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5213
5214 mBlockLayoutRequests = true;
5215
5216 if (count > 0) {
5217 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005218 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005219 }
Adam Powell539ee872012-02-03 19:00:49 -08005220
Romain Guy9d849a22012-03-14 16:41:42 -07005221 // invalidate before moving the children to avoid unnecessary invalidate
5222 // calls to bubble up from the children all the way to the top
5223 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005224 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005225 }
5226
Adam Powell45803472010-01-25 15:10:44 -08005227 offsetChildrenTopAndBottom(incrementalDeltaY);
5228
5229 if (down) {
5230 mFirstPosition += count;
5231 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005232
Adam Powell45803472010-01-25 15:10:44 -08005233 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5234 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5235 fillGap(down);
5236 }
5237
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005238 mRecycler.fullyDetachScrapViews();
Evan Rosky837ae0d2017-10-26 12:50:33 -07005239 boolean selectorOnScreen = false;
Adam Powell029cfbd2010-03-08 19:03:54 -08005240 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005241 final int childIndex = mSelectedPosition - mFirstPosition;
5242 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005243 positionSelector(mSelectedPosition, getChildAt(childIndex));
Evan Rosky837ae0d2017-10-26 12:50:33 -07005244 selectorOnScreen = true;
Adam Powell2a20ddd2010-03-11 18:09:59 -08005245 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005246 } else if (mSelectorPosition != INVALID_POSITION) {
5247 final int childIndex = mSelectorPosition - mFirstPosition;
5248 if (childIndex >= 0 && childIndex < getChildCount()) {
Evan Rosky837ae0d2017-10-26 12:50:33 -07005249 positionSelector(mSelectorPosition, getChildAt(childIndex));
5250 selectorOnScreen = true;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005251 }
Evan Rosky837ae0d2017-10-26 12:50:33 -07005252 }
5253 if (!selectorOnScreen) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005254 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005255 }
5256
Adam Powell45803472010-01-25 15:10:44 -08005257 mBlockLayoutRequests = false;
5258
5259 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005260
Adam Powell45803472010-01-25 15:10:44 -08005261 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005262 }
5263
5264 /**
5265 * Returns the number of header views in the list. Header views are special views
5266 * at the top of the list that should not be recycled during a layout.
5267 *
5268 * @return The number of header views, 0 in the default implementation.
5269 */
5270 int getHeaderViewsCount() {
5271 return 0;
5272 }
5273
5274 /**
5275 * Returns the number of footer views in the list. Footer views are special views
5276 * at the bottom of the list that should not be recycled during a layout.
5277 *
5278 * @return The number of footer views, 0 in the default implementation.
5279 */
5280 int getFooterViewsCount() {
5281 return 0;
5282 }
5283
5284 /**
5285 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5286 * remain on screen are shifted and the other ones are discarded. The role of this
5287 * method is to fill the gap thus created by performing a partial layout in the
5288 * empty space.
5289 *
5290 * @param down true if the scroll is going down, false if it is going up
5291 */
5292 abstract void fillGap(boolean down);
5293
5294 void hideSelector() {
5295 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005296 if (mLayoutMode != LAYOUT_SPECIFIC) {
5297 mResurrectToPosition = mSelectedPosition;
5298 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005299 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5300 mResurrectToPosition = mNextSelectedPosition;
5301 }
5302 setSelectedPositionInt(INVALID_POSITION);
5303 setNextSelectedPositionInt(INVALID_POSITION);
5304 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005305 }
5306 }
5307
5308 /**
5309 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5310 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5311 * of items available in the adapter
5312 */
5313 int reconcileSelectedPosition() {
5314 int position = mSelectedPosition;
5315 if (position < 0) {
5316 position = mResurrectToPosition;
5317 }
5318 position = Math.max(0, position);
5319 position = Math.min(position, mItemCount - 1);
5320 return position;
5321 }
5322
5323 /**
5324 * Find the row closest to y. This row will be used as the motion row when scrolling
5325 *
5326 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005327 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005328 */
5329 abstract int findMotionRow(int y);
5330
5331 /**
Adam Powell637d3372010-08-25 14:37:03 -07005332 * Find the row closest to y. This row will be used as the motion row when scrolling.
5333 *
5334 * @param y Where the user touched
5335 * @return The position of the first (or only) item in the row closest to y
5336 */
5337 int findClosestMotionRow(int y) {
5338 final int childCount = getChildCount();
5339 if (childCount == 0) {
5340 return INVALID_POSITION;
5341 }
5342
5343 final int motionRow = findMotionRow(y);
5344 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5345 }
5346
5347 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005348 * Causes all the views to be rebuilt and redrawn.
5349 */
5350 public void invalidateViews() {
5351 mDataChanged = true;
5352 rememberSyncState();
5353 requestLayout();
5354 invalidate();
5355 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005356
Jeff Brown4e6319b2010-12-13 10:36:51 -08005357 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005358 * If there is a selection returns false.
5359 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005360 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005361 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005362 if (mSelectedPosition < 0 && resurrectSelection()) {
5363 updateSelectorState();
5364 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005365 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005366 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005367 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005368
5369 /**
5370 * Makes the item at the supplied position selected.
5371 *
5372 * @param position the position of the new selection
5373 */
5374 abstract void setSelectionInt(int position);
5375
5376 /**
5377 * Attempt to bring the selection back if the user is switching from touch
5378 * to trackball mode
5379 * @return Whether selection was set to something.
5380 */
5381 boolean resurrectSelection() {
5382 final int childCount = getChildCount();
5383
5384 if (childCount <= 0) {
5385 return false;
5386 }
5387
5388 int selectedTop = 0;
5389 int selectedPos;
5390 int childrenTop = mListPadding.top;
5391 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5392 final int firstPosition = mFirstPosition;
5393 final int toPosition = mResurrectToPosition;
5394 boolean down = true;
5395
5396 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5397 selectedPos = toPosition;
5398
5399 final View selected = getChildAt(selectedPos - mFirstPosition);
5400 selectedTop = selected.getTop();
5401 int selectedBottom = selected.getBottom();
5402
5403 // We are scrolled, don't get in the fade
5404 if (selectedTop < childrenTop) {
5405 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5406 } else if (selectedBottom > childrenBottom) {
5407 selectedTop = childrenBottom - selected.getMeasuredHeight()
5408 - getVerticalFadingEdgeLength();
5409 }
5410 } else {
5411 if (toPosition < firstPosition) {
5412 // Default to selecting whatever is first
5413 selectedPos = firstPosition;
5414 for (int i = 0; i < childCount; i++) {
5415 final View v = getChildAt(i);
5416 final int top = v.getTop();
5417
5418 if (i == 0) {
5419 // Remember the position of the first item
5420 selectedTop = top;
5421 // See if we are scrolled at all
5422 if (firstPosition > 0 || top < childrenTop) {
5423 // If we are scrolled, don't select anything that is
5424 // in the fade region
5425 childrenTop += getVerticalFadingEdgeLength();
5426 }
5427 }
5428 if (top >= childrenTop) {
5429 // Found a view whose top is fully visisble
5430 selectedPos = firstPosition + i;
5431 selectedTop = top;
5432 break;
5433 }
5434 }
5435 } else {
5436 final int itemCount = mItemCount;
5437 down = false;
5438 selectedPos = firstPosition + childCount - 1;
5439
5440 for (int i = childCount - 1; i >= 0; i--) {
5441 final View v = getChildAt(i);
5442 final int top = v.getTop();
5443 final int bottom = v.getBottom();
5444
5445 if (i == childCount - 1) {
5446 selectedTop = top;
5447 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5448 childrenBottom -= getVerticalFadingEdgeLength();
5449 }
5450 }
5451
5452 if (bottom <= childrenBottom) {
5453 selectedPos = firstPosition + i;
5454 selectedTop = top;
5455 break;
5456 }
5457 }
5458 }
5459 }
5460
5461 mResurrectToPosition = INVALID_POSITION;
5462 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005463 if (mPositionScroller != null) {
5464 mPositionScroller.stop();
5465 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005466 mTouchMode = TOUCH_MODE_REST;
5467 clearScrollingCache();
5468 mSpecificTop = selectedTop;
5469 selectedPos = lookForSelectablePosition(selectedPos, down);
5470 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5471 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005472 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005473 setSelectionInt(selectedPos);
5474 invokeOnItemScrollListener();
5475 } else {
5476 selectedPos = INVALID_POSITION;
5477 }
5478 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5479
5480 return selectedPos >= 0;
5481 }
5482
Adam Powell14c08042011-10-06 19:46:18 -07005483 void confirmCheckedPositionsById() {
5484 // Clear out the positional check states, we'll rebuild it below from IDs.
5485 mCheckStates.clear();
5486
5487 boolean checkedCountChanged = false;
5488 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5489 final long id = mCheckedIdStates.keyAt(checkedIndex);
5490 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5491
5492 final long lastPosId = mAdapter.getItemId(lastPos);
5493 if (id != lastPosId) {
5494 // Look around to see if the ID is nearby. If not, uncheck it.
5495 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5496 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5497 boolean found = false;
5498 for (int searchPos = start; searchPos < end; searchPos++) {
5499 final long searchId = mAdapter.getItemId(searchPos);
5500 if (id == searchId) {
5501 found = true;
5502 mCheckStates.put(searchPos, true);
5503 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5504 break;
5505 }
5506 }
5507
5508 if (!found) {
5509 mCheckedIdStates.delete(id);
5510 checkedIndex--;
5511 mCheckedItemCount--;
5512 checkedCountChanged = true;
5513 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5514 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5515 lastPos, id, false);
5516 }
5517 }
5518 } else {
5519 mCheckStates.put(lastPos, true);
5520 }
5521 }
5522
5523 if (checkedCountChanged && mChoiceActionMode != null) {
5524 mChoiceActionMode.invalidate();
5525 }
5526 }
5527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005528 @Override
5529 protected void handleDataChanged() {
5530 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005531 int lastHandledItemCount = mLastHandledItemCount;
5532 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005533
5534 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5535 confirmCheckedPositionsById();
5536 }
5537
Adam Powell539ee872012-02-03 19:00:49 -08005538 // TODO: In the future we can recycle these views based on stable ID instead.
5539 mRecycler.clearTransientStateViews();
5540
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005541 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005542 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005543 int selectablePos;
5544
5545 // Find the row we are supposed to sync to
5546 if (mNeedSync) {
5547 // Update this first, since setNextSelectedPositionInt inspects it
5548 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005549 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005550
Adam Powell07852792010-11-10 16:57:05 -08005551 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005552 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5553 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005554 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5555 if (mForceTranscriptScroll) {
5556 mForceTranscriptScroll = false;
5557 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5558 return;
5559 }
Adam Powell07852792010-11-10 16:57:05 -08005560 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005561 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005562 final View lastChild = getChildAt(childCount - 1);
5563 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005564 if (mFirstPosition + childCount >= lastHandledItemCount &&
5565 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005566 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5567 return;
5568 }
5569 // Something new came in and we didn't scroll; give the user a clue that
5570 // there's something new.
5571 awakenScrollBars();
5572 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005573
5574 switch (mSyncMode) {
5575 case SYNC_SELECTED_POSITION:
5576 if (isInTouchMode()) {
5577 // We saved our state when not in touch mode. (We know this because
5578 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5579 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5580 // adjusting if the available range changed) and return.
5581 mLayoutMode = LAYOUT_SYNC;
5582 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5583
5584 return;
5585 } else {
5586 // See if we can find a position in the new data with the same
5587 // id as the old selection. This will change mSyncPosition.
5588 newPos = findSyncPosition();
5589 if (newPos >= 0) {
5590 // Found it. Now verify that new selection is still selectable
5591 selectablePos = lookForSelectablePosition(newPos, true);
5592 if (selectablePos == newPos) {
5593 // Same row id is selected
5594 mSyncPosition = newPos;
5595
5596 if (mSyncHeight == getHeight()) {
5597 // If we are at the same height as when we saved state, try
5598 // to restore the scroll position too.
5599 mLayoutMode = LAYOUT_SYNC;
5600 } else {
5601 // We are not the same height as when the selection was saved, so
5602 // don't try to restore the exact position
5603 mLayoutMode = LAYOUT_SET_SELECTION;
5604 }
5605
5606 // Restore selection
5607 setNextSelectedPositionInt(newPos);
5608 return;
5609 }
5610 }
5611 }
5612 break;
5613 case SYNC_FIRST_POSITION:
5614 // Leave mSyncPosition as it is -- just pin to available range
5615 mLayoutMode = LAYOUT_SYNC;
5616 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5617
5618 return;
5619 }
5620 }
5621
5622 if (!isInTouchMode()) {
5623 // We couldn't find matching data -- try to use the same position
5624 newPos = getSelectedItemPosition();
5625
5626 // Pin position to the available range
5627 if (newPos >= count) {
5628 newPos = count - 1;
5629 }
5630 if (newPos < 0) {
5631 newPos = 0;
5632 }
5633
5634 // Make sure we select something selectable -- first look down
5635 selectablePos = lookForSelectablePosition(newPos, true);
5636
5637 if (selectablePos >= 0) {
5638 setNextSelectedPositionInt(selectablePos);
5639 return;
5640 } else {
5641 // Looking down didn't work -- try looking up
5642 selectablePos = lookForSelectablePosition(newPos, false);
5643 if (selectablePos >= 0) {
5644 setNextSelectedPositionInt(selectablePos);
5645 return;
5646 }
5647 }
5648 } else {
5649
5650 // We already know where we want to resurrect the selection
5651 if (mResurrectToPosition >= 0) {
5652 return;
5653 }
5654 }
5655
5656 }
5657
5658 // Nothing is selected. Give up and reset everything.
5659 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5660 mSelectedPosition = INVALID_POSITION;
5661 mSelectedRowId = INVALID_ROW_ID;
5662 mNextSelectedPosition = INVALID_POSITION;
5663 mNextSelectedRowId = INVALID_ROW_ID;
5664 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005665 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005666 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005667 checkSelectionChanged();
5668 }
5669
Romain Guy43c9cdf2010-01-27 13:53:55 -08005670 @Override
5671 protected void onDisplayHint(int hint) {
5672 super.onDisplayHint(hint);
5673 switch (hint) {
5674 case INVISIBLE:
5675 if (mPopup != null && mPopup.isShowing()) {
5676 dismissPopup();
5677 }
5678 break;
5679 case VISIBLE:
5680 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5681 showPopup();
5682 }
5683 break;
5684 }
Romain Guy24562482010-02-01 14:56:19 -08005685 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005686 }
5687
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005688 /**
5689 * Removes the filter window
5690 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005691 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005692 if (mPopup != null) {
5693 mPopup.dismiss();
5694 }
5695 }
5696
5697 /**
5698 * Shows the filter window
5699 */
5700 private void showPopup() {
5701 // Make sure we have a window before showing the popup
5702 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005703 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005704 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005705 // Make sure we get focus if we are showing the popup
5706 checkFocus();
5707 }
5708 }
5709
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005710 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005711 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5712 final int[] xy = new int[2];
5713 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005714 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005715 // TODO: And the gravity should be defined in the theme as well
5716 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005717 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005718 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5719 xy[0], bottomGap);
5720 } else {
5721 mPopup.update(xy[0], bottomGap, -1, -1);
5722 }
5723 }
5724
5725 /**
5726 * What is the distance between the source and destination rectangles given the direction of
5727 * focus navigation between them? The direction basically helps figure out more quickly what is
5728 * self evident by the relationship between the rects...
5729 *
5730 * @param source the source rectangle
5731 * @param dest the destination rectangle
5732 * @param direction the direction
5733 * @return the distance between the rectangles
5734 */
5735 static int getDistance(Rect source, Rect dest, int direction) {
5736 int sX, sY; // source x, y
5737 int dX, dY; // dest x, y
5738 switch (direction) {
5739 case View.FOCUS_RIGHT:
5740 sX = source.right;
5741 sY = source.top + source.height() / 2;
5742 dX = dest.left;
5743 dY = dest.top + dest.height() / 2;
5744 break;
5745 case View.FOCUS_DOWN:
5746 sX = source.left + source.width() / 2;
5747 sY = source.bottom;
5748 dX = dest.left + dest.width() / 2;
5749 dY = dest.top;
5750 break;
5751 case View.FOCUS_LEFT:
5752 sX = source.left;
5753 sY = source.top + source.height() / 2;
5754 dX = dest.right;
5755 dY = dest.top + dest.height() / 2;
5756 break;
5757 case View.FOCUS_UP:
5758 sX = source.left + source.width() / 2;
5759 sY = source.top;
5760 dX = dest.left + dest.width() / 2;
5761 dY = dest.bottom;
5762 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005763 case View.FOCUS_FORWARD:
5764 case View.FOCUS_BACKWARD:
5765 sX = source.right + source.width() / 2;
5766 sY = source.top + source.height() / 2;
5767 dX = dest.left + dest.width() / 2;
5768 dY = dest.top + dest.height() / 2;
5769 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005770 default:
5771 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005772 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5773 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005774 }
5775 int deltaX = dX - sX;
5776 int deltaY = dY - sY;
5777 return deltaY * deltaY + deltaX * deltaX;
5778 }
5779
5780 @Override
5781 protected boolean isInFilterMode() {
5782 return mFiltered;
5783 }
5784
5785 /**
5786 * Sends a key to the text filter window
5787 *
5788 * @param keyCode The keycode for the event
5789 * @param event The actual key event
5790 *
5791 * @return True if the text filter handled the event, false otherwise.
5792 */
5793 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005794 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005795 return false;
5796 }
5797
5798 boolean handled = false;
5799 boolean okToSend = true;
5800 switch (keyCode) {
5801 case KeyEvent.KEYCODE_DPAD_UP:
5802 case KeyEvent.KEYCODE_DPAD_DOWN:
5803 case KeyEvent.KEYCODE_DPAD_LEFT:
5804 case KeyEvent.KEYCODE_DPAD_RIGHT:
5805 case KeyEvent.KEYCODE_DPAD_CENTER:
5806 case KeyEvent.KEYCODE_ENTER:
5807 okToSend = false;
5808 break;
5809 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005810 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005811 if (event.getAction() == KeyEvent.ACTION_DOWN
5812 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005813 KeyEvent.DispatcherState state = getKeyDispatcherState();
5814 if (state != null) {
5815 state.startTracking(event, this);
5816 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005817 handled = true;
5818 } else if (event.getAction() == KeyEvent.ACTION_UP
5819 && event.isTracking() && !event.isCanceled()) {
5820 handled = true;
5821 mTextFilter.setText("");
5822 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005823 }
5824 okToSend = false;
5825 break;
5826 case KeyEvent.KEYCODE_SPACE:
5827 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005828 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005829 break;
5830 }
5831
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005832 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005833 createTextFilter(true);
5834
5835 KeyEvent forwardEvent = event;
5836 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005837 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005838 }
5839
5840 int action = event.getAction();
5841 switch (action) {
5842 case KeyEvent.ACTION_DOWN:
5843 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5844 break;
5845
5846 case KeyEvent.ACTION_UP:
5847 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5848 break;
5849
5850 case KeyEvent.ACTION_MULTIPLE:
5851 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5852 break;
5853 }
5854 }
5855 return handled;
5856 }
5857
5858 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005859 * Return an InputConnection for editing of the filter text.
5860 */
5861 @Override
5862 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005863 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005864 if (mPublicInputConnection == null) {
5865 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005866 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005867 }
Romain Guyf6991302013-06-05 17:19:01 -07005868 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005869 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5870 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005871 }
5872 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005873 }
Romain Guy0a637162009-05-29 14:43:54 -07005874
Romain Guyf6991302013-06-05 17:19:01 -07005875 private class InputConnectionWrapper implements InputConnection {
5876 private final EditorInfo mOutAttrs;
5877 private InputConnection mTarget;
5878
5879 public InputConnectionWrapper(EditorInfo outAttrs) {
5880 mOutAttrs = outAttrs;
5881 }
5882
5883 private InputConnection getTarget() {
5884 if (mTarget == null) {
5885 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5886 }
5887 return mTarget;
5888 }
5889
5890 @Override
5891 public boolean reportFullscreenMode(boolean enabled) {
5892 // Use our own input connection, since it is
5893 // the "real" one the IME is talking with.
5894 return mDefInputConnection.reportFullscreenMode(enabled);
5895 }
5896
5897 @Override
5898 public boolean performEditorAction(int editorAction) {
5899 // The editor is off in its own window; we need to be
5900 // the one that does this.
5901 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005902 InputMethodManager imm =
5903 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005904 if (imm != null) {
5905 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5906 }
5907 return true;
5908 }
5909 return false;
5910 }
5911
5912 @Override
5913 public boolean sendKeyEvent(KeyEvent event) {
5914 // Use our own input connection, since the filter
5915 // text view may not be shown in a window so has
5916 // no ViewAncestor to dispatch events with.
5917 return mDefInputConnection.sendKeyEvent(event);
5918 }
5919
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005920 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005921 public CharSequence getTextBeforeCursor(int n, int flags) {
5922 if (mTarget == null) return "";
5923 return mTarget.getTextBeforeCursor(n, flags);
5924 }
5925
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005926 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005927 public CharSequence getTextAfterCursor(int n, int flags) {
5928 if (mTarget == null) return "";
5929 return mTarget.getTextAfterCursor(n, flags);
5930 }
5931
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005932 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005933 public CharSequence getSelectedText(int flags) {
5934 if (mTarget == null) return "";
5935 return mTarget.getSelectedText(flags);
5936 }
5937
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005938 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005939 public int getCursorCapsMode(int reqModes) {
5940 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5941 return mTarget.getCursorCapsMode(reqModes);
5942 }
5943
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005944 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005945 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5946 return getTarget().getExtractedText(request, flags);
5947 }
5948
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005949 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005950 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5951 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5952 }
5953
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005954 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08005955 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5956 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5957 }
5958
5959 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005960 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5961 return getTarget().setComposingText(text, newCursorPosition);
5962 }
5963
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005964 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005965 public boolean setComposingRegion(int start, int end) {
5966 return getTarget().setComposingRegion(start, end);
5967 }
5968
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005969 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005970 public boolean finishComposingText() {
5971 return mTarget == null || mTarget.finishComposingText();
5972 }
5973
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005974 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005975 public boolean commitText(CharSequence text, int newCursorPosition) {
5976 return getTarget().commitText(text, newCursorPosition);
5977 }
5978
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005979 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005980 public boolean commitCompletion(CompletionInfo text) {
5981 return getTarget().commitCompletion(text);
5982 }
5983
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005984 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005985 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5986 return getTarget().commitCorrection(correctionInfo);
5987 }
5988
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005989 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005990 public boolean setSelection(int start, int end) {
5991 return getTarget().setSelection(start, end);
5992 }
5993
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005994 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005995 public boolean performContextMenuAction(int id) {
5996 return getTarget().performContextMenuAction(id);
5997 }
5998
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005999 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006000 public boolean beginBatchEdit() {
6001 return getTarget().beginBatchEdit();
6002 }
6003
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006004 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006005 public boolean endBatchEdit() {
6006 return getTarget().endBatchEdit();
6007 }
6008
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006009 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006010 public boolean clearMetaKeyStates(int states) {
6011 return getTarget().clearMetaKeyStates(states);
6012 }
6013
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006014 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006015 public boolean performPrivateCommand(String action, Bundle data) {
6016 return getTarget().performPrivateCommand(action, data);
6017 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09006018
6019 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07006020 public boolean requestCursorUpdates(int cursorUpdateMode) {
6021 return getTarget().requestCursorUpdates(cursorUpdateMode);
6022 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08006023
6024 @Override
6025 public Handler getHandler() {
6026 return getTarget().getHandler();
6027 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07006028
6029 @Override
6030 public void closeConnection() {
6031 getTarget().closeConnection();
6032 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07006033
6034 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07006035 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6036 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07006037 }
Romain Guyf6991302013-06-05 17:19:01 -07006038 }
6039
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006040 /**
6041 * For filtering we proxy an input connection to an internal text editor,
6042 * and this allows the proxying to happen.
6043 */
6044 @Override
6045 public boolean checkInputConnectionProxy(View view) {
6046 return view == mTextFilter;
6047 }
Romain Guy0a637162009-05-29 14:43:54 -07006048
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006049 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006050 * Creates the window for the text filter and populates it with an EditText field;
6051 *
6052 * @param animateEntrance true if the window should appear with an animation
6053 */
6054 private void createTextFilter(boolean animateEntrance) {
6055 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006056 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006057 p.setFocusable(false);
6058 p.setTouchable(false);
6059 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006060 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006061 p.setWidth(LayoutParams.WRAP_CONTENT);
6062 p.setHeight(LayoutParams.WRAP_CONTENT);
6063 p.setBackgroundDrawable(null);
6064 mPopup = p;
6065 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006066 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006067 }
6068 if (animateEntrance) {
6069 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6070 } else {
6071 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6072 }
6073 }
6074
Romain Guyf6991302013-06-05 17:19:01 -07006075 private EditText getTextFilterInput() {
6076 if (mTextFilter == null) {
6077 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6078 mTextFilter = (EditText) layoutInflater.inflate(
6079 com.android.internal.R.layout.typing_filter, null);
6080 // For some reason setting this as the "real" input type changes
6081 // the text view in some way that it doesn't work, and I don't
6082 // want to figure out why this is.
6083 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6084 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6085 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6086 mTextFilter.addTextChangedListener(this);
6087 }
6088 return mTextFilter;
6089 }
6090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006091 /**
6092 * Clear the text filter.
6093 */
6094 public void clearTextFilter() {
6095 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006096 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006097 mFiltered = false;
6098 if (mPopup != null && mPopup.isShowing()) {
6099 dismissPopup();
6100 }
6101 }
6102 }
6103
6104 /**
6105 * Returns if the ListView currently has a text filter.
6106 */
6107 public boolean hasTextFilter() {
6108 return mFiltered;
6109 }
6110
Alan Viverette8fa327a2013-05-31 14:53:13 -07006111 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006112 public void onGlobalLayout() {
6113 if (isShown()) {
6114 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006115 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006116 showPopup();
6117 }
6118 } else {
6119 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006120 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006121 dismissPopup();
6122 }
6123 }
6124
6125 }
6126
6127 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006128 * For our text watcher that is associated with the text filter. Does
6129 * nothing.
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 beforeTextChanged(CharSequence s, int start, int count, int after) {
6133 }
6134
6135 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006136 * For our text watcher that is associated with the text filter. Performs
6137 * the actual filtering as the text changes, and takes care of hiding and
6138 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006139 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006140 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006141 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006142 if (isTextFilterEnabled()) {
6143 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006144 int length = s.length();
6145 boolean showing = mPopup.isShowing();
6146 if (!showing && length > 0) {
6147 // Show the filter popup if necessary
6148 showPopup();
6149 mFiltered = true;
6150 } else if (showing && length == 0) {
6151 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006152 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006153 mFiltered = false;
6154 }
6155 if (mAdapter instanceof Filterable) {
6156 Filter f = ((Filterable) mAdapter).getFilter();
6157 // Filter should not be null when we reach this part
6158 if (f != null) {
6159 f.filter(s, this);
6160 } else {
6161 throw new IllegalStateException("You cannot call onTextChanged with a non "
6162 + "filterable adapter");
6163 }
6164 }
6165 }
6166 }
6167
6168 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006169 * For our text watcher that is associated with the text filter. Does
6170 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006171 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006172 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006173 public void afterTextChanged(Editable s) {
6174 }
6175
Alan Viverette8fa327a2013-05-31 14:53:13 -07006176 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006177 public void onFilterComplete(int count) {
6178 if (mSelectedPosition < 0 && count > 0) {
6179 mResurrectToPosition = INVALID_POSITION;
6180 resurrectSelection();
6181 }
6182 }
6183
6184 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006185 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6186 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6187 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6188 }
6189
6190 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006191 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6192 return new LayoutParams(p);
6193 }
6194
6195 @Override
6196 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6197 return new AbsListView.LayoutParams(getContext(), attrs);
6198 }
6199
6200 @Override
6201 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6202 return p instanceof AbsListView.LayoutParams;
6203 }
6204
6205 /**
6206 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6207 * to the bottom to show new items.
6208 *
6209 * @param mode the transcript mode to set
6210 *
6211 * @see #TRANSCRIPT_MODE_DISABLED
6212 * @see #TRANSCRIPT_MODE_NORMAL
6213 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6214 */
6215 public void setTranscriptMode(int mode) {
6216 mTranscriptMode = mode;
6217 }
6218
6219 /**
6220 * Returns the current transcript mode.
6221 *
6222 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6223 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6224 */
6225 public int getTranscriptMode() {
6226 return mTranscriptMode;
6227 }
6228
6229 @Override
6230 public int getSolidColor() {
6231 return mCacheColorHint;
6232 }
6233
6234 /**
6235 * 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 -07006236 * on top of a solid, single-color, opaque background.
6237 *
6238 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6239 * single color. This hint will not affect any existing background drawable set on this view (
6240 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006241 *
6242 * @param color The background color
6243 */
Tor Norbye80756e32015-03-02 09:39:27 -08006244 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006245 if (color != mCacheColorHint) {
6246 mCacheColorHint = color;
6247 int count = getChildCount();
6248 for (int i = 0; i < count; i++) {
6249 getChildAt(i).setDrawingCacheBackgroundColor(color);
6250 }
6251 mRecycler.setCacheColorHint(color);
6252 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006253 }
6254
6255 /**
6256 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6257 * on top of a solid, single-color, opaque background
6258 *
6259 * @return The cache color hint
6260 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006261 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006262 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006263 public int getCacheColorHint() {
6264 return mCacheColorHint;
6265 }
6266
6267 /**
6268 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6269 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6270 * internal view recycler.
6271 *
6272 * @param views A list into which to put the reclaimed views
6273 */
6274 public void reclaimViews(List<View> views) {
6275 int childCount = getChildCount();
6276 RecyclerListener listener = mRecycler.mRecyclerListener;
6277
6278 // Reclaim views on screen
6279 for (int i = 0; i < childCount; i++) {
6280 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006281 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006282 // Don't reclaim header or footer views, or views that should be ignored
6283 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6284 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006285 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006286 if (listener != null) {
6287 // Pretend they went through the scrap heap
6288 listener.onMovedToScrapHeap(child);
6289 }
6290 }
6291 }
6292 mRecycler.reclaimScrapViews(views);
6293 removeAllViewsInLayout();
6294 }
6295
Adam Powell637d3372010-08-25 14:37:03 -07006296 private void finishGlows() {
6297 if (mEdgeGlowTop != null) {
6298 mEdgeGlowTop.finish();
6299 mEdgeGlowBottom.finish();
6300 }
6301 }
6302
Romain Guy13922e02009-05-12 17:56:14 -07006303 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006304 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6305 * through the specified intent.
6306 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6307 */
6308 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -08006309 setRemoteViewsAdapter(intent, false);
6310 }
6311
6312 /** @hide **/
6313 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6314 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6315 }
6316
6317 /** @hide **/
6318 @Override
6319 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006320 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6321 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006322 if (mRemoteAdapter != null) {
6323 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6324 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6325 mRemoteAdapter.getRemoteViewsServiceIntent());
6326 if (fcNew.equals(fcOld)) {
6327 return;
6328 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006329 }
Adam Cohen2148d432011-07-28 14:59:54 -07006330 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006331 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08006332 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07006333 if (mRemoteAdapter.isDataReady()) {
6334 setAdapter(mRemoteAdapter);
6335 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006336 }
6337
6338 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006339 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006340 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006341 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006342 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006343 * @hide
6344 */
6345 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6346 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6347 // service handling the specified intent.
6348 if (mRemoteAdapter != null) {
6349 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6350 }
6351 }
6352
6353 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006354 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6355 * connected yet.
6356 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006357 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006358 public void deferNotifyDataSetChanged() {
6359 mDeferNotifyDataSetChanged = true;
6360 }
6361
6362 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006363 * Called back when the adapter connects to the RemoteViewsService.
6364 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006365 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006366 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006367 if (mRemoteAdapter != mAdapter) {
6368 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006369 if (mDeferNotifyDataSetChanged) {
6370 mRemoteAdapter.notifyDataSetChanged();
6371 mDeferNotifyDataSetChanged = false;
6372 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006373 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006374 } else if (mRemoteAdapter != null) {
6375 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006376 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006377 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006378 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006379 }
6380
6381 /**
6382 * Called back when the adapter disconnects from the RemoteViewsService.
6383 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006384 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006385 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006386 // If the remote adapter disconnects, we keep it around
6387 // since the currently displayed items are still cached.
6388 // Further, we want the service to eventually reconnect
6389 // when necessary, as triggered by this view requesting
6390 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006391 }
6392
6393 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006394 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6395 * being displayed by the AbsListView.
6396 */
6397 void setVisibleRangeHint(int start, int end) {
6398 if (mRemoteAdapter != null) {
6399 mRemoteAdapter.setVisibleRangeHint(start, end);
6400 }
6401 }
6402
6403 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006404 * Sets the recycler listener to be notified whenever a View is set aside in
6405 * the recycler for later reuse. This listener can be used to free resources
6406 * associated to the View.
6407 *
6408 * @param listener The recycler listener to be notified of views set aside
6409 * in the recycler.
6410 *
6411 * @see android.widget.AbsListView.RecycleBin
6412 * @see android.widget.AbsListView.RecyclerListener
6413 */
6414 public void setRecyclerListener(RecyclerListener listener) {
6415 mRecycler.mRecyclerListener = listener;
6416 }
6417
Adam Powellb1f498a2011-01-18 20:43:23 -08006418 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6419 @Override
6420 public void onChanged() {
6421 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006422 if (mFastScroll != null) {
6423 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006424 }
6425 }
6426
6427 @Override
6428 public void onInvalidated() {
6429 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006430 if (mFastScroll != null) {
6431 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006432 }
6433 }
6434 }
6435
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006436 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006437 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6438 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6439 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6440 * selects and deselects list items.
6441 */
6442 public interface MultiChoiceModeListener extends ActionMode.Callback {
6443 /**
6444 * Called when an item is checked or unchecked during selection mode.
6445 *
6446 * @param mode The {@link ActionMode} providing the selection mode
6447 * @param position Adapter position of the item that was checked or unchecked
6448 * @param id Adapter ID of the item that was checked or unchecked
6449 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6450 * if the item is now unchecked.
6451 */
6452 public void onItemCheckedStateChanged(ActionMode mode,
6453 int position, long id, boolean checked);
6454 }
6455
6456 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6457 private MultiChoiceModeListener mWrapped;
6458
6459 public void setWrapped(MultiChoiceModeListener wrapped) {
6460 mWrapped = wrapped;
6461 }
6462
Adam Powella7981702012-08-24 12:43:41 -07006463 public boolean hasWrappedCallback() {
6464 return mWrapped != null;
6465 }
6466
Alan Viverette8fa327a2013-05-31 14:53:13 -07006467 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006468 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6469 if (mWrapped.onCreateActionMode(mode, menu)) {
6470 // Initialize checked graphic state?
6471 setLongClickable(false);
6472 return true;
6473 }
6474 return false;
6475 }
6476
Alan Viverette8fa327a2013-05-31 14:53:13 -07006477 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006478 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6479 return mWrapped.onPrepareActionMode(mode, menu);
6480 }
6481
Alan Viverette8fa327a2013-05-31 14:53:13 -07006482 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006483 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6484 return mWrapped.onActionItemClicked(mode, item);
6485 }
6486
Alan Viverette8fa327a2013-05-31 14:53:13 -07006487 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006488 public void onDestroyActionMode(ActionMode mode) {
6489 mWrapped.onDestroyActionMode(mode);
6490 mChoiceActionMode = null;
6491
6492 // Ending selection mode means deselecting everything.
6493 clearChoices();
6494
6495 mDataChanged = true;
6496 rememberSyncState();
6497 requestLayout();
6498
6499 setLongClickable(true);
6500 }
6501
Alan Viverette8fa327a2013-05-31 14:53:13 -07006502 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006503 public void onItemCheckedStateChanged(ActionMode mode,
6504 int position, long id, boolean checked) {
6505 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6506
6507 // If there are no items selected we no longer need the selection mode.
6508 if (getCheckedItemCount() == 0) {
6509 mode.finish();
6510 }
6511 }
6512 }
6513
6514 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006515 * AbsListView extends LayoutParams to provide a place to hold the view type.
6516 */
6517 public static class LayoutParams extends ViewGroup.LayoutParams {
6518 /**
6519 * View type for this view, as returned by
6520 * {@link android.widget.Adapter#getItemViewType(int) }
6521 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006522 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006523 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6524 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6525 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006526 int viewType;
6527
The Android Open Source Project4df24232009-03-05 14:34:35 -08006528 /**
6529 * When this boolean is set, the view has been added to the AbsListView
6530 * at least once. It is used to know whether headers/footers have already
6531 * been added to the list view and whether they should be treated as
6532 * recycled views or not.
6533 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006534 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006535 boolean recycledHeaderFooter;
6536
Romain Guy0bf88592010-03-02 13:38:44 -08006537 /**
6538 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6539 * to obtain children views to measure itself. When doing so, the children
6540 * are not attached to the window, but put in the recycler which assumes
6541 * they've been attached before. Setting this flag will force the reused
6542 * view to be attached to the window rather than just attached to the
6543 * parent.
6544 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006545 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006546 boolean forceAdd;
6547
Dianne Hackborn079e2352010-10-18 17:02:43 -07006548 /**
6549 * The position the view was removed from when pulled out of the
6550 * scrap heap.
6551 * @hide
6552 */
6553 int scrappedFromPosition;
6554
Adam Powell539ee872012-02-03 19:00:49 -08006555 /**
6556 * The ID the view represents
6557 */
6558 long itemId = -1;
6559
Alan Viverette92539d52015-09-14 10:49:25 -04006560 /** Whether the adapter considers the item enabled. */
6561 boolean isEnabled;
6562
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006563 public LayoutParams(Context c, AttributeSet attrs) {
6564 super(c, attrs);
6565 }
6566
6567 public LayoutParams(int w, int h) {
6568 super(w, h);
6569 }
6570
6571 public LayoutParams(int w, int h, int viewType) {
6572 super(w, h);
6573 this.viewType = viewType;
6574 }
6575
6576 public LayoutParams(ViewGroup.LayoutParams source) {
6577 super(source);
6578 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006579
6580 /** @hide */
6581 @Override
6582 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6583 super.encodeProperties(encoder);
6584
6585 encoder.addProperty("list:viewType", viewType);
6586 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6587 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006588 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006590 }
6591
6592 /**
6593 * A RecyclerListener is used to receive a notification whenever a View is placed
6594 * inside the RecycleBin's scrap heap. This listener is used to free resources
6595 * associated to Views placed in the RecycleBin.
6596 *
6597 * @see android.widget.AbsListView.RecycleBin
6598 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6599 */
6600 public static interface RecyclerListener {
6601 /**
6602 * Indicates that the specified View was moved into the recycler's scrap heap.
6603 * The view is not displayed on screen any more and any expensive resource
6604 * associated with the view should be discarded.
6605 *
6606 * @param view
6607 */
6608 void onMovedToScrapHeap(View view);
6609 }
6610
6611 /**
6612 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6613 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6614 * start of a layout. By construction, they are displaying current information. At the end of
6615 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6616 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6617 *
6618 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6619 * @see android.widget.AbsListView.RecyclerListener
6620 */
6621 class RecycleBin {
6622 private RecyclerListener mRecyclerListener;
6623
6624 /**
6625 * The position of the first view stored in mActiveViews.
6626 */
6627 private int mFirstActivePosition;
6628
6629 /**
6630 * Views that were on screen at the start of layout. This array is populated at the start of
6631 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6632 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6633 * view store in mFirstActivePosition.
6634 */
6635 private View[] mActiveViews = new View[0];
6636
6637 /**
6638 * Unsorted views that can be used by the adapter as a convert view.
6639 */
6640 private ArrayList<View>[] mScrapViews;
6641
6642 private int mViewTypeCount;
6643
6644 private ArrayList<View> mCurrentScrap;
6645
Adam Powell539ee872012-02-03 19:00:49 -08006646 private ArrayList<View> mSkippedScrap;
6647
6648 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006649 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006650
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006651 public void setViewTypeCount(int viewTypeCount) {
6652 if (viewTypeCount < 1) {
6653 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6654 }
6655 //noinspection unchecked
6656 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6657 for (int i = 0; i < viewTypeCount; i++) {
6658 scrapViews[i] = new ArrayList<View>();
6659 }
6660 mViewTypeCount = viewTypeCount;
6661 mCurrentScrap = scrapViews[0];
6662 mScrapViews = scrapViews;
6663 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006664
Adam Powellf3c2eda2010-03-16 17:31:01 -07006665 public void markChildrenDirty() {
6666 if (mViewTypeCount == 1) {
6667 final ArrayList<View> scrap = mCurrentScrap;
6668 final int scrapCount = scrap.size();
6669 for (int i = 0; i < scrapCount; i++) {
6670 scrap.get(i).forceLayout();
6671 }
6672 } else {
6673 final int typeCount = mViewTypeCount;
6674 for (int i = 0; i < typeCount; i++) {
6675 final ArrayList<View> scrap = mScrapViews[i];
6676 final int scrapCount = scrap.size();
6677 for (int j = 0; j < scrapCount; j++) {
6678 scrap.get(j).forceLayout();
6679 }
6680 }
6681 }
Adam Powell539ee872012-02-03 19:00:49 -08006682 if (mTransientStateViews != null) {
6683 final int count = mTransientStateViews.size();
6684 for (int i = 0; i < count; i++) {
6685 mTransientStateViews.valueAt(i).forceLayout();
6686 }
6687 }
Chet Haase72871322013-02-26 16:12:13 -07006688 if (mTransientStateViewsById != null) {
6689 final int count = mTransientStateViewsById.size();
6690 for (int i = 0; i < count; i++) {
6691 mTransientStateViewsById.valueAt(i).forceLayout();
6692 }
6693 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006694 }
Romain Guy0a637162009-05-29 14:43:54 -07006695
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006696 public boolean shouldRecycleViewType(int viewType) {
6697 return viewType >= 0;
6698 }
6699
6700 /**
6701 * Clears the scrap heap.
6702 */
6703 void clear() {
6704 if (mViewTypeCount == 1) {
6705 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006706 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006707 } else {
6708 final int typeCount = mViewTypeCount;
6709 for (int i = 0; i < typeCount; i++) {
6710 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006711 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006712 }
6713 }
Alan Viverette59511502013-12-09 13:49:25 -08006714
6715 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006716 }
6717
6718 /**
6719 * Fill ActiveViews with all of the children of the AbsListView.
6720 *
6721 * @param childCount The minimum number of views mActiveViews should hold
6722 * @param firstActivePosition The position of the first view that will be stored in
6723 * mActiveViews
6724 */
6725 void fillActiveViews(int childCount, int firstActivePosition) {
6726 if (mActiveViews.length < childCount) {
6727 mActiveViews = new View[childCount];
6728 }
6729 mFirstActivePosition = firstActivePosition;
6730
Romain Guyf6991302013-06-05 17:19:01 -07006731 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006732 final View[] activeViews = mActiveViews;
6733 for (int i = 0; i < childCount; i++) {
6734 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006735 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006736 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006737 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006738 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6739 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006740 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006741 // Remember the position so that setupChild() doesn't reset state.
6742 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006743 }
6744 }
6745 }
6746
6747 /**
6748 * Get the view corresponding to the specified position. The view will be removed from
6749 * mActiveViews if it is found.
6750 *
6751 * @param position The position to look up in mActiveViews
6752 * @return The view if it is found, null otherwise
6753 */
6754 View getActiveView(int position) {
6755 int index = position - mFirstActivePosition;
6756 final View[] activeViews = mActiveViews;
6757 if (index >=0 && index < activeViews.length) {
6758 final View match = activeViews[index];
6759 activeViews[index] = null;
6760 return match;
6761 }
6762 return null;
6763 }
6764
Adam Powell539ee872012-02-03 19:00:49 -08006765 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006766 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6767 long id = mAdapter.getItemId(position);
6768 View result = mTransientStateViewsById.get(id);
6769 mTransientStateViewsById.remove(id);
6770 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006771 }
Chet Haase72871322013-02-26 16:12:13 -07006772 if (mTransientStateViews != null) {
6773 final int index = mTransientStateViews.indexOfKey(position);
6774 if (index >= 0) {
6775 View result = mTransientStateViews.valueAt(index);
6776 mTransientStateViews.removeAt(index);
6777 return result;
6778 }
Adam Powell539ee872012-02-03 19:00:49 -08006779 }
Chet Haase72871322013-02-26 16:12:13 -07006780 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006781 }
6782
6783 /**
Alan Viverette59511502013-12-09 13:49:25 -08006784 * Dumps and fully detaches any currently saved views with transient
6785 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006786 */
6787 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006788 final SparseArray<View> viewsByPos = mTransientStateViews;
6789 if (viewsByPos != null) {
6790 final int N = viewsByPos.size();
6791 for (int i = 0; i < N; i++) {
6792 removeDetachedView(viewsByPos.valueAt(i), false);
6793 }
6794 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006795 }
Alan Viverette59511502013-12-09 13:49:25 -08006796
6797 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6798 if (viewsById != null) {
6799 final int N = viewsById.size();
6800 for (int i = 0; i < N; i++) {
6801 removeDetachedView(viewsById.valueAt(i), false);
6802 }
6803 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006804 }
Adam Powell539ee872012-02-03 19:00:49 -08006805 }
6806
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006807 /**
6808 * @return A view from the ScrapViews collection. These are unordered.
6809 */
6810 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006811 final int whichScrap = mAdapter.getItemViewType(position);
6812 if (whichScrap < 0) {
6813 return null;
6814 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006815 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006816 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006817 } else if (whichScrap < mScrapViews.length) {
6818 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006819 }
6820 return null;
6821 }
6822
6823 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006824 * Puts a view into the list of scrap views.
6825 * <p>
6826 * If the list data hasn't changed or the adapter has stable IDs, views
6827 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006828 *
6829 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006830 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006831 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006832 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006833 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006834 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006835 // Can't recycle, but we don't know anything about the view.
6836 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006837 return;
6838 }
6839
Adam Powell539ee872012-02-03 19:00:49 -08006840 lp.scrappedFromPosition = position;
6841
Alan Viverette1e51cc72013-09-27 14:32:20 -07006842 // Remove but don't scrap header or footer views, or views that
6843 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006844 final int viewType = lp.viewType;
6845 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006846 // Can't recycle. If it's not a header or footer, which have
6847 // special handling and should be ignored, then skip the scrap
6848 // heap and we'll fully detach the view later.
6849 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6850 getSkippedScrap().add(scrap);
6851 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006852 return;
6853 }
6854
6855 scrap.dispatchStartTemporaryDetach();
6856
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006857 // The the accessibility state of the view may change while temporary
6858 // detached and we do not allow detached views to fire accessibility
6859 // events. So we are announcing that the subtree changed giving a chance
6860 // to clients holding on to a view in this subtree to refresh it.
Eugene Susla72c510f2018-01-23 21:12:11 +00006861 notifyViewAccessibilityStateChangedIfNeeded(
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006862 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6863
Alan Viveretted44696c2013-07-18 10:37:15 -07006864 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006865 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006866 if (scrapHasTransientState) {
6867 if (mAdapter != null && mAdapterHasStableIds) {
6868 // If the adapter has stable IDs, we can reuse the view for
6869 // the same data.
6870 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006871 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006872 }
6873 mTransientStateViewsById.put(lp.itemId, scrap);
6874 } else if (!mDataChanged) {
6875 // If the data hasn't changed, we can reuse the views at
6876 // their old positions.
6877 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006878 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006879 }
6880 mTransientStateViews.put(position, scrap);
6881 } else {
6882 // Otherwise, we'll have to remove the view and start over.
Phil Weaverec66fb82017-03-23 12:21:53 -07006883 clearScrapForRebind(scrap);
Alan Viverette8bbae342015-06-25 14:49:29 -07006884 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006885 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006886 } else {
Phil Weaverec66fb82017-03-23 12:21:53 -07006887 clearScrapForRebind(scrap);
Alan Viveretted44696c2013-07-18 10:37:15 -07006888 if (mViewTypeCount == 1) {
6889 mCurrentScrap.add(scrap);
6890 } else {
6891 mScrapViews[viewType].add(scrap);
6892 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006893
Alan Viveretted44696c2013-07-18 10:37:15 -07006894 if (mRecyclerListener != null) {
6895 mRecyclerListener.onMovedToScrapHeap(scrap);
6896 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006897 }
6898 }
6899
Alan Viverette8bbae342015-06-25 14:49:29 -07006900 private ArrayList<View> getSkippedScrap() {
6901 if (mSkippedScrap == null) {
6902 mSkippedScrap = new ArrayList<>();
6903 }
6904 return mSkippedScrap;
6905 }
6906
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006907 /**
Adam Powell539ee872012-02-03 19:00:49 -08006908 * Finish the removal of any views that skipped the scrap heap.
6909 */
6910 void removeSkippedScrap() {
6911 if (mSkippedScrap == null) {
6912 return;
6913 }
6914 final int count = mSkippedScrap.size();
6915 for (int i = 0; i < count; i++) {
6916 removeDetachedView(mSkippedScrap.get(i), false);
6917 }
6918 mSkippedScrap.clear();
6919 }
6920
6921 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006922 * Move all views remaining in mActiveViews to mScrapViews.
6923 */
6924 void scrapActiveViews() {
6925 final View[] activeViews = mActiveViews;
6926 final boolean hasListener = mRecyclerListener != null;
6927 final boolean multipleScraps = mViewTypeCount > 1;
6928
6929 ArrayList<View> scrapViews = mCurrentScrap;
6930 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006931 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006932 final View victim = activeViews[i];
6933 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006934 final AbsListView.LayoutParams lp
6935 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006936 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006937
6938 activeViews[i] = null;
6939
Alan Viverette59511502013-12-09 13:49:25 -08006940 if (victim.hasTransientState()) {
6941 // Store views with transient state for later use.
6942 victim.dispatchStartTemporaryDetach();
6943
6944 if (mAdapter != null && mAdapterHasStableIds) {
6945 if (mTransientStateViewsById == null) {
6946 mTransientStateViewsById = new LongSparseArray<View>();
6947 }
6948 long id = mAdapter.getItemId(mFirstActivePosition + i);
6949 mTransientStateViewsById.put(id, victim);
6950 } else if (!mDataChanged) {
6951 if (mTransientStateViews == null) {
6952 mTransientStateViews = new SparseArray<View>();
6953 }
6954 mTransientStateViews.put(mFirstActivePosition + i, victim);
6955 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6956 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006957 removeDetachedView(victim, false);
6958 }
Alan Viverette59511502013-12-09 13:49:25 -08006959 } else if (!shouldRecycleViewType(whichScrap)) {
6960 // Discard non-recyclable views except headers/footers.
6961 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6962 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006963 }
Alan Viverette59511502013-12-09 13:49:25 -08006964 } else {
6965 // Store everything else on the appropriate scrap heap.
6966 if (multipleScraps) {
6967 scrapViews = mScrapViews[whichScrap];
6968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006969
Alan Viverette59511502013-12-09 13:49:25 -08006970 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006971 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08006972 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006973
Alan Viverette59511502013-12-09 13:49:25 -08006974 if (hasListener) {
6975 mRecyclerListener.onMovedToScrapHeap(victim);
6976 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006977 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006978 }
6979 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006980 pruneScrapViews();
6981 }
6982
6983 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006984 * At the end of a layout pass, all temp detached views should either be re-attached or
6985 * completely detached. This method ensures that any remaining view in the scrap list is
6986 * fully detached.
6987 */
6988 void fullyDetachScrapViews() {
6989 final int viewTypeCount = mViewTypeCount;
6990 final ArrayList<View>[] scrapViews = mScrapViews;
6991 for (int i = 0; i < viewTypeCount; ++i) {
6992 final ArrayList<View> scrapPile = scrapViews[i];
6993 for (int j = scrapPile.size() - 1; j >= 0; j--) {
6994 final View view = scrapPile.get(j);
6995 if (view.isTemporarilyDetached()) {
6996 removeDetachedView(view, false);
6997 }
6998 }
6999 }
7000 }
7001
7002 /**
Alan Viverette59511502013-12-09 13:49:25 -08007003 * Makes sure that the size of mScrapViews does not exceed the size of
7004 * mActiveViews, which can happen if an adapter does not recycle its
7005 * views. Removes cached transient state views that no longer have
7006 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007007 */
7008 private void pruneScrapViews() {
7009 final int maxViews = mActiveViews.length;
7010 final int viewTypeCount = mViewTypeCount;
7011 final ArrayList<View>[] scrapViews = mScrapViews;
7012 for (int i = 0; i < viewTypeCount; ++i) {
7013 final ArrayList<View> scrapPile = scrapViews[i];
7014 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007015 while (size > maxViews) {
7016 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007017 }
7018 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07007019
Alan Viverette59511502013-12-09 13:49:25 -08007020 final SparseArray<View> transViewsByPos = mTransientStateViews;
7021 if (transViewsByPos != null) {
7022 for (int i = 0; i < transViewsByPos.size(); i++) {
7023 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007024 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007025 removeDetachedView(v, false);
7026 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007027 i--;
7028 }
7029 }
7030 }
Alan Viverette59511502013-12-09 13:49:25 -08007031
7032 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7033 if (transViewsById != null) {
7034 for (int i = 0; i < transViewsById.size(); i++) {
7035 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007036 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007037 removeDetachedView(v, false);
7038 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007039 i--;
7040 }
7041 }
7042 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007043 }
7044
7045 /**
7046 * Puts all views in the scrap heap into the supplied list.
7047 */
7048 void reclaimScrapViews(List<View> views) {
7049 if (mViewTypeCount == 1) {
7050 views.addAll(mCurrentScrap);
7051 } else {
7052 final int viewTypeCount = mViewTypeCount;
7053 final ArrayList<View>[] scrapViews = mScrapViews;
7054 for (int i = 0; i < viewTypeCount; ++i) {
7055 final ArrayList<View> scrapPile = scrapViews[i];
7056 views.addAll(scrapPile);
7057 }
7058 }
7059 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007060
7061 /**
7062 * Updates the cache color hint of all known views.
7063 *
7064 * @param color The new cache color hint.
7065 */
7066 void setCacheColorHint(int color) {
7067 if (mViewTypeCount == 1) {
7068 final ArrayList<View> scrap = mCurrentScrap;
7069 final int scrapCount = scrap.size();
7070 for (int i = 0; i < scrapCount; i++) {
7071 scrap.get(i).setDrawingCacheBackgroundColor(color);
7072 }
7073 } else {
7074 final int typeCount = mViewTypeCount;
7075 for (int i = 0; i < typeCount; i++) {
7076 final ArrayList<View> scrap = mScrapViews[i];
7077 final int scrapCount = scrap.size();
7078 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007079 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007080 }
7081 }
7082 }
7083 // Just in case this is called during a layout pass
7084 final View[] activeViews = mActiveViews;
7085 final int count = activeViews.length;
7086 for (int i = 0; i < count; ++i) {
7087 final View victim = activeViews[i];
7088 if (victim != null) {
7089 victim.setDrawingCacheBackgroundColor(color);
7090 }
7091 }
7092 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007093
Alan Viverette3e141622014-02-18 17:05:13 -08007094 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7095 final int size = scrapViews.size();
7096 if (size > 0) {
7097 // See if we still have a view for this position or ID.
Phil Weavere28c03b2017-04-24 13:23:10 -07007098 // Traverse backwards to find the most recently used scrap view
7099 for (int i = size - 1; i >= 0; i--) {
Alan Viverette3e141622014-02-18 17:05:13 -08007100 final View view = scrapViews.get(i);
7101 final AbsListView.LayoutParams params =
7102 (AbsListView.LayoutParams) view.getLayoutParams();
7103
7104 if (mAdapterHasStableIds) {
7105 final long id = mAdapter.getItemId(position);
7106 if (id == params.itemId) {
7107 return scrapViews.remove(i);
7108 }
7109 } else if (params.scrappedFromPosition == position) {
7110 final View scrap = scrapViews.remove(i);
Phil Weaverec66fb82017-03-23 12:21:53 -07007111 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007112 return scrap;
7113 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007114 }
Alan Viverette3e141622014-02-18 17:05:13 -08007115 final View scrap = scrapViews.remove(size - 1);
Phil Weaverec66fb82017-03-23 12:21:53 -07007116 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007117 return scrap;
7118 } else {
7119 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007120 }
Alan Viverette3e141622014-02-18 17:05:13 -08007121 }
7122
7123 private void clearScrap(final ArrayList<View> scrap) {
7124 final int scrapCount = scrap.size();
7125 for (int j = 0; j < scrapCount; j++) {
7126 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7127 }
7128 }
7129
Phil Weaverec66fb82017-03-23 12:21:53 -07007130 private void clearScrapForRebind(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007131 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007132 view.setAccessibilityDelegate(null);
7133 }
7134
7135 private void removeDetachedView(View child, boolean animate) {
7136 child.setAccessibilityDelegate(null);
7137 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007138 }
7139 }
Alan Viverette441b4372014-02-12 13:30:20 -08007140
7141 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007142 * Returns the height of the view for the specified position.
7143 *
7144 * @param position the item position
7145 * @return view height in pixels
7146 */
7147 int getHeightForPosition(int position) {
7148 final int firstVisiblePosition = getFirstVisiblePosition();
7149 final int childCount = getChildCount();
7150 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007151 if (index >= 0 && index < childCount) {
7152 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007153 final View view = getChildAt(index);
7154 return view.getHeight();
7155 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007156 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007157 final View view = obtainView(position, mIsScrap);
7158 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7159 final int height = view.getMeasuredHeight();
7160 mRecycler.addScrapView(view, position);
7161 return height;
7162 }
7163 }
7164
7165 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007166 * Sets the selected item and positions the selection y pixels from the top edge
7167 * of the ListView. (If in touch mode, the item will not be selected but it will
7168 * still be positioned appropriately.)
7169 *
7170 * @param position Index (starting at 0) of the data item to be selected.
7171 * @param y The distance from the top edge of the ListView (plus padding) that the
7172 * item will be positioned.
7173 */
7174 public void setSelectionFromTop(int position, int y) {
7175 if (mAdapter == null) {
7176 return;
7177 }
7178
7179 if (!isInTouchMode()) {
7180 position = lookForSelectablePosition(position, true);
7181 if (position >= 0) {
7182 setNextSelectedPositionInt(position);
7183 }
7184 } else {
7185 mResurrectToPosition = position;
7186 }
7187
7188 if (position >= 0) {
7189 mLayoutMode = LAYOUT_SPECIFIC;
7190 mSpecificTop = mListPadding.top + y;
7191
7192 if (mNeedSync) {
7193 mSyncPosition = position;
7194 mSyncRowId = mAdapter.getItemId(position);
7195 }
7196
7197 if (mPositionScroller != null) {
7198 mPositionScroller.stop();
7199 }
7200 requestLayout();
7201 }
7202 }
7203
Siva Velusamy94a6d152015-05-05 15:07:00 -07007204 /** @hide */
7205 @Override
7206 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7207 super.encodeProperties(encoder);
7208
7209 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7210 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7211 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7212 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7213 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7214 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7215
7216 View selectedView = getSelectedView();
7217 if (selectedView != null) {
7218 encoder.addPropertyKey("selectedView");
7219 selectedView.encode(encoder);
7220 }
7221 }
7222
Alan Viveretted22db212014-02-13 17:47:38 -08007223 /**
7224 * Abstract positon scroller used to handle smooth scrolling.
7225 */
7226 static abstract class AbsPositionScroller {
7227 public abstract void start(int position);
7228 public abstract void start(int position, int boundPosition);
7229 public abstract void startWithOffset(int position, int offset);
7230 public abstract void startWithOffset(int position, int offset, int duration);
7231 public abstract void stop();
7232 }
7233
7234 /**
7235 * Default position scroller that simulates a fling.
7236 */
7237 class PositionScroller extends AbsPositionScroller implements Runnable {
7238 private static final int SCROLL_DURATION = 200;
7239
7240 private static final int MOVE_DOWN_POS = 1;
7241 private static final int MOVE_UP_POS = 2;
7242 private static final int MOVE_DOWN_BOUND = 3;
7243 private static final int MOVE_UP_BOUND = 4;
7244 private static final int MOVE_OFFSET = 5;
7245
7246 private int mMode;
7247 private int mTargetPos;
7248 private int mBoundPos;
7249 private int mLastSeenPos;
7250 private int mScrollDuration;
7251 private final int mExtraScroll;
7252
7253 private int mOffsetFromTop;
7254
7255 PositionScroller() {
7256 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7257 }
7258
7259 @Override
7260 public void start(final int position) {
7261 stop();
7262
7263 if (mDataChanged) {
7264 // Wait until we're back in a stable state to try this.
7265 mPositionScrollAfterLayout = new Runnable() {
7266 @Override public void run() {
7267 start(position);
7268 }
7269 };
7270 return;
7271 }
7272
7273 final int childCount = getChildCount();
7274 if (childCount == 0) {
7275 // Can't scroll without children.
7276 return;
7277 }
7278
7279 final int firstPos = mFirstPosition;
7280 final int lastPos = firstPos + childCount - 1;
7281
7282 int viewTravelCount;
7283 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7284 if (clampedPosition < firstPos) {
7285 viewTravelCount = firstPos - clampedPosition + 1;
7286 mMode = MOVE_UP_POS;
7287 } else if (clampedPosition > lastPos) {
7288 viewTravelCount = clampedPosition - lastPos + 1;
7289 mMode = MOVE_DOWN_POS;
7290 } else {
7291 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7292 return;
7293 }
7294
7295 if (viewTravelCount > 0) {
7296 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7297 } else {
7298 mScrollDuration = SCROLL_DURATION;
7299 }
7300 mTargetPos = clampedPosition;
7301 mBoundPos = INVALID_POSITION;
7302 mLastSeenPos = INVALID_POSITION;
7303
7304 postOnAnimation(this);
7305 }
7306
7307 @Override
7308 public void start(final int position, final int boundPosition) {
7309 stop();
7310
7311 if (boundPosition == INVALID_POSITION) {
7312 start(position);
7313 return;
7314 }
7315
7316 if (mDataChanged) {
7317 // Wait until we're back in a stable state to try this.
7318 mPositionScrollAfterLayout = new Runnable() {
7319 @Override public void run() {
7320 start(position, boundPosition);
7321 }
7322 };
7323 return;
7324 }
7325
7326 final int childCount = getChildCount();
7327 if (childCount == 0) {
7328 // Can't scroll without children.
7329 return;
7330 }
7331
7332 final int firstPos = mFirstPosition;
7333 final int lastPos = firstPos + childCount - 1;
7334
7335 int viewTravelCount;
7336 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7337 if (clampedPosition < firstPos) {
7338 final int boundPosFromLast = lastPos - boundPosition;
7339 if (boundPosFromLast < 1) {
7340 // Moving would shift our bound position off the screen. Abort.
7341 return;
7342 }
7343
7344 final int posTravel = firstPos - clampedPosition + 1;
7345 final int boundTravel = boundPosFromLast - 1;
7346 if (boundTravel < posTravel) {
7347 viewTravelCount = boundTravel;
7348 mMode = MOVE_UP_BOUND;
7349 } else {
7350 viewTravelCount = posTravel;
7351 mMode = MOVE_UP_POS;
7352 }
7353 } else if (clampedPosition > lastPos) {
7354 final int boundPosFromFirst = boundPosition - firstPos;
7355 if (boundPosFromFirst < 1) {
7356 // Moving would shift our bound position off the screen. Abort.
7357 return;
7358 }
7359
7360 final int posTravel = clampedPosition - lastPos + 1;
7361 final int boundTravel = boundPosFromFirst - 1;
7362 if (boundTravel < posTravel) {
7363 viewTravelCount = boundTravel;
7364 mMode = MOVE_DOWN_BOUND;
7365 } else {
7366 viewTravelCount = posTravel;
7367 mMode = MOVE_DOWN_POS;
7368 }
7369 } else {
7370 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7371 return;
7372 }
7373
7374 if (viewTravelCount > 0) {
7375 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7376 } else {
7377 mScrollDuration = SCROLL_DURATION;
7378 }
7379 mTargetPos = clampedPosition;
7380 mBoundPos = boundPosition;
7381 mLastSeenPos = INVALID_POSITION;
7382
7383 postOnAnimation(this);
7384 }
7385
7386 @Override
7387 public void startWithOffset(int position, int offset) {
7388 startWithOffset(position, offset, SCROLL_DURATION);
7389 }
7390
7391 @Override
7392 public void startWithOffset(final int position, int offset, final int duration) {
7393 stop();
7394
7395 if (mDataChanged) {
7396 // Wait until we're back in a stable state to try this.
7397 final int postOffset = offset;
7398 mPositionScrollAfterLayout = new Runnable() {
7399 @Override public void run() {
7400 startWithOffset(position, postOffset, duration);
7401 }
7402 };
7403 return;
7404 }
7405
7406 final int childCount = getChildCount();
7407 if (childCount == 0) {
7408 // Can't scroll without children.
7409 return;
7410 }
7411
7412 offset += getPaddingTop();
7413
7414 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7415 mOffsetFromTop = offset;
7416 mBoundPos = INVALID_POSITION;
7417 mLastSeenPos = INVALID_POSITION;
7418 mMode = MOVE_OFFSET;
7419
7420 final int firstPos = mFirstPosition;
7421 final int lastPos = firstPos + childCount - 1;
7422
7423 int viewTravelCount;
7424 if (mTargetPos < firstPos) {
7425 viewTravelCount = firstPos - mTargetPos;
7426 } else if (mTargetPos > lastPos) {
7427 viewTravelCount = mTargetPos - lastPos;
7428 } else {
7429 // On-screen, just scroll.
7430 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007431 smoothScrollBy(targetTop - offset, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007432 return;
7433 }
7434
7435 // Estimate how many screens we should travel
7436 final float screenTravelCount = (float) viewTravelCount / childCount;
7437 mScrollDuration = screenTravelCount < 1 ?
7438 duration : (int) (duration / screenTravelCount);
7439 mLastSeenPos = INVALID_POSITION;
7440
7441 postOnAnimation(this);
7442 }
7443
7444 /**
7445 * Scroll such that targetPos is in the visible padded region without scrolling
7446 * boundPos out of view. Assumes targetPos is onscreen.
7447 */
7448 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7449 final int firstPos = mFirstPosition;
7450 final int childCount = getChildCount();
7451 final int lastPos = firstPos + childCount - 1;
7452 final int paddedTop = mListPadding.top;
7453 final int paddedBottom = getHeight() - mListPadding.bottom;
7454
7455 if (targetPos < firstPos || targetPos > lastPos) {
7456 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7457 " not visible [" + firstPos + ", " + lastPos + "]");
7458 }
7459 if (boundPos < firstPos || boundPos > lastPos) {
7460 // boundPos doesn't matter, it's already offscreen.
7461 boundPos = INVALID_POSITION;
7462 }
7463
7464 final View targetChild = getChildAt(targetPos - firstPos);
7465 final int targetTop = targetChild.getTop();
7466 final int targetBottom = targetChild.getBottom();
7467 int scrollBy = 0;
7468
7469 if (targetBottom > paddedBottom) {
7470 scrollBy = targetBottom - paddedBottom;
7471 }
7472 if (targetTop < paddedTop) {
7473 scrollBy = targetTop - paddedTop;
7474 }
7475
7476 if (scrollBy == 0) {
7477 return;
7478 }
7479
7480 if (boundPos >= 0) {
7481 final View boundChild = getChildAt(boundPos - firstPos);
7482 final int boundTop = boundChild.getTop();
7483 final int boundBottom = boundChild.getBottom();
7484 final int absScroll = Math.abs(scrollBy);
7485
7486 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7487 // Don't scroll the bound view off the bottom of the screen.
7488 scrollBy = Math.max(0, boundBottom - paddedBottom);
7489 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7490 // Don't scroll the bound view off the top of the screen.
7491 scrollBy = Math.min(0, boundTop - paddedTop);
7492 }
7493 }
7494
7495 smoothScrollBy(scrollBy, duration);
7496 }
7497
7498 @Override
7499 public void stop() {
7500 removeCallbacks(this);
7501 }
7502
7503 @Override
7504 public void run() {
7505 final int listHeight = getHeight();
7506 final int firstPos = mFirstPosition;
7507
7508 switch (mMode) {
7509 case MOVE_DOWN_POS: {
7510 final int lastViewIndex = getChildCount() - 1;
7511 final int lastPos = firstPos + lastViewIndex;
7512
7513 if (lastViewIndex < 0) {
7514 return;
7515 }
7516
7517 if (lastPos == mLastSeenPos) {
7518 // No new views, let things keep going.
7519 postOnAnimation(this);
7520 return;
7521 }
7522
7523 final View lastView = getChildAt(lastViewIndex);
7524 final int lastViewHeight = lastView.getHeight();
7525 final int lastViewTop = lastView.getTop();
7526 final int lastViewPixelsShowing = listHeight - lastViewTop;
7527 final int extraScroll = lastPos < mItemCount - 1 ?
7528 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7529
7530 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007531 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007532
7533 mLastSeenPos = lastPos;
7534 if (lastPos < mTargetPos) {
7535 postOnAnimation(this);
7536 }
7537 break;
7538 }
7539
7540 case MOVE_DOWN_BOUND: {
7541 final int nextViewIndex = 1;
7542 final int childCount = getChildCount();
7543
7544 if (firstPos == mBoundPos || childCount <= nextViewIndex
7545 || firstPos + childCount >= mItemCount) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007546 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007547 return;
7548 }
7549 final int nextPos = firstPos + nextViewIndex;
7550
7551 if (nextPos == mLastSeenPos) {
7552 // No new views, let things keep going.
7553 postOnAnimation(this);
7554 return;
7555 }
7556
7557 final View nextView = getChildAt(nextViewIndex);
7558 final int nextViewHeight = nextView.getHeight();
7559 final int nextViewTop = nextView.getTop();
7560 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7561 if (nextPos < mBoundPos) {
7562 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007563 mScrollDuration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007564
7565 mLastSeenPos = nextPos;
7566
7567 postOnAnimation(this);
7568 } else {
7569 if (nextViewTop > extraScroll) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007570 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7571 } else {
7572 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007573 }
7574 }
7575 break;
7576 }
7577
7578 case MOVE_UP_POS: {
7579 if (firstPos == mLastSeenPos) {
7580 // No new views, let things keep going.
7581 postOnAnimation(this);
7582 return;
7583 }
7584
7585 final View firstView = getChildAt(0);
7586 if (firstView == null) {
7587 return;
7588 }
7589 final int firstViewTop = firstView.getTop();
7590 final int extraScroll = firstPos > 0 ?
7591 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7592
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007593 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7594 firstPos > mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007595
7596 mLastSeenPos = firstPos;
7597
7598 if (firstPos > mTargetPos) {
7599 postOnAnimation(this);
7600 }
7601 break;
7602 }
7603
7604 case MOVE_UP_BOUND: {
7605 final int lastViewIndex = getChildCount() - 2;
7606 if (lastViewIndex < 0) {
7607 return;
7608 }
7609 final int lastPos = firstPos + lastViewIndex;
7610
7611 if (lastPos == mLastSeenPos) {
7612 // No new views, let things keep going.
7613 postOnAnimation(this);
7614 return;
7615 }
7616
7617 final View lastView = getChildAt(lastViewIndex);
7618 final int lastViewHeight = lastView.getHeight();
7619 final int lastViewTop = lastView.getTop();
7620 final int lastViewPixelsShowing = listHeight - lastViewTop;
7621 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7622 mLastSeenPos = lastPos;
7623 if (lastPos > mBoundPos) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007624 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7625 true);
Alan Viveretted22db212014-02-13 17:47:38 -08007626 postOnAnimation(this);
7627 } else {
7628 final int bottom = listHeight - extraScroll;
7629 final int lastViewBottom = lastViewTop + lastViewHeight;
7630 if (bottom > lastViewBottom) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007631 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7632 } else {
7633 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007634 }
7635 }
7636 break;
7637 }
7638
7639 case MOVE_OFFSET: {
7640 if (mLastSeenPos == firstPos) {
7641 // No new views, let things keep going.
7642 postOnAnimation(this);
7643 return;
7644 }
7645
7646 mLastSeenPos = firstPos;
7647
7648 final int childCount = getChildCount();
7649 final int position = mTargetPos;
7650 final int lastPos = firstPos + childCount - 1;
7651
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007652 // Account for the visible "portion" of the first / last child when we estimate
7653 // how many screens we should travel to reach our target
7654 final View firstChild = getChildAt(0);
7655 final int firstChildHeight = firstChild.getHeight();
7656 final View lastChild = getChildAt(childCount - 1);
7657 final int lastChildHeight = lastChild.getHeight();
7658 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7659 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7660 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7661 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7662 / lastChildHeight;
7663
7664 float viewTravelCount = 0;
Alan Viveretted22db212014-02-13 17:47:38 -08007665 if (position < firstPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007666 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
Alan Viveretted22db212014-02-13 17:47:38 -08007667 } else if (position > lastPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007668 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
Alan Viveretted22db212014-02-13 17:47:38 -08007669 }
7670
7671 // Estimate how many screens we should travel
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007672 final float screenTravelCount = viewTravelCount / childCount;
Alan Viveretted22db212014-02-13 17:47:38 -08007673
7674 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7675 if (position < firstPos) {
7676 final int distance = (int) (-getHeight() * modifier);
7677 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007678 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007679 postOnAnimation(this);
7680 } else if (position > lastPos) {
7681 final int distance = (int) (getHeight() * modifier);
7682 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007683 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007684 postOnAnimation(this);
7685 } else {
7686 // On-screen, just scroll.
7687 final int targetTop = getChildAt(position - firstPos).getTop();
7688 final int distance = targetTop - mOffsetFromTop;
7689 final int duration = (int) (mScrollDuration *
7690 ((float) Math.abs(distance) / getHeight()));
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007691 smoothScrollBy(distance, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007692 }
7693 break;
7694 }
7695
7696 default:
7697 break;
7698 }
7699 }
7700 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007701}