blob: 594d24005262cc88dca7085ebc45ecef1affd9a0 [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;
Yohei Yukawae77386e2018-01-23 10:39:32 -080034import android.os.LocaleList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.os.Parcel;
36import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080037import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070038import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070040import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.text.TextUtils;
42import android.text.TextWatcher;
43import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080044import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070045import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080046import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070047import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070048import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070049import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070050import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.view.Gravity;
52import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080053import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054import android.view.KeyEvent;
55import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070056import android.view.Menu;
57import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058import android.view.MotionEvent;
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -080059import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.view.VelocityTracker;
61import android.view.View;
62import android.view.ViewConfiguration;
63import android.view.ViewDebug;
64import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070065import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070066import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070068import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070069import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070070import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070071import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080072import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070073import android.view.animation.Interpolator;
74import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070075import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070076import android.view.inputmethod.CompletionInfo;
77import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070078import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070079import android.view.inputmethod.ExtractedText;
80import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070081import android.view.inputmethod.InputConnection;
Yohei Yukawa152944f2016-06-10 19:04:34 -070082import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070084import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085
Adam Cohen335c3b62012-07-24 17:18:16 -070086import com.android.internal.R;
87
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088import java.util.ArrayList;
89import java.util.List;
90
91/**
Romain Guyd6a463a2009-05-21 23:10:10 -070092 * Base class that can be used to implement virtualized lists of items. A list does
93 * not have a spatial definition here. For instance, subclases of this class can
94 * 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 -080095 *
96 * @attr ref android.R.styleable#AbsListView_listSelector
97 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
98 * @attr ref android.R.styleable#AbsListView_stackFromBottom
99 * @attr ref android.R.styleable#AbsListView_scrollingCache
100 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
101 * @attr ref android.R.styleable#AbsListView_transcriptMode
102 * @attr ref android.R.styleable#AbsListView_cacheColorHint
103 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
104 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -0700105 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 */
107public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
108 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700109 ViewTreeObserver.OnTouchModeChangeListener,
110 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
Romain Guy9d849a22012-03-14 16:41:42 -0700112 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800113 private static final String TAG = "AbsListView";
114
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 /**
116 * Disables the transcript mode.
117 *
118 * @see #setTranscriptMode(int)
119 */
120 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 /**
123 * The list will automatically scroll to the bottom when a data set change
124 * notification is received and only if the last item is already visible
125 * on screen.
126 *
127 * @see #setTranscriptMode(int)
128 */
129 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700130
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 /**
132 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700133 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 *
135 * @see #setTranscriptMode(int)
136 */
137 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
138
139 /**
140 * Indicates that we are not in the middle of a touch gesture
141 */
142 static final int TOUCH_MODE_REST = -1;
143
144 /**
145 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
146 * scroll gesture.
147 */
148 static final int TOUCH_MODE_DOWN = 0;
149
150 /**
151 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
152 * is a longpress
153 */
154 static final int TOUCH_MODE_TAP = 1;
155
156 /**
157 * Indicates we have waited for everything we can wait for, but the user's finger is still down
158 */
159 static final int TOUCH_MODE_DONE_WAITING = 2;
160
161 /**
162 * Indicates the touch gesture is a scroll
163 */
164 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 /**
167 * Indicates the view is in the process of being flung
168 */
169 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 /**
Adam Powell637d3372010-08-25 14:37:03 -0700172 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
173 */
174 static final int TOUCH_MODE_OVERSCROLL = 5;
175
176 /**
177 * Indicates the view is being flung outside of normal content bounds
178 * and will spring back.
179 */
180 static final int TOUCH_MODE_OVERFLING = 6;
181
182 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 * Regular layout - usually an unsolicited layout from the view system
184 */
185 static final int LAYOUT_NORMAL = 0;
186
187 /**
188 * Show the first item
189 */
190 static final int LAYOUT_FORCE_TOP = 1;
191
192 /**
193 * Force the selected item to be on somewhere on the screen
194 */
195 static final int LAYOUT_SET_SELECTION = 2;
196
197 /**
198 * Show the last item
199 */
200 static final int LAYOUT_FORCE_BOTTOM = 3;
201
202 /**
203 * Make a mSelectedItem appear in a specific location and build the rest of
204 * the views from there. The top is specified by mSpecificTop.
205 */
206 static final int LAYOUT_SPECIFIC = 4;
207
208 /**
209 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
210 * at mSpecificTop
211 */
212 static final int LAYOUT_SYNC = 5;
213
214 /**
215 * Layout as a result of using the navigation keys
216 */
217 static final int LAYOUT_MOVE_SELECTION = 6;
218
219 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700220 * Normal list that does not indicate choices
221 */
222 public static final int CHOICE_MODE_NONE = 0;
223
224 /**
225 * The list allows up to one choice
226 */
227 public static final int CHOICE_MODE_SINGLE = 1;
228
229 /**
230 * The list allows multiple choices
231 */
232 public static final int CHOICE_MODE_MULTIPLE = 2;
233
234 /**
235 * The list allows multiple choices in a modal selection mode
236 */
237 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
238
239 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700240 * The thread that created this view.
241 */
242 private final Thread mOwnerThread;
243
244 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700245 * Controls if/how the user may choose/check items in the list
246 */
247 int mChoiceMode = CHOICE_MODE_NONE;
248
249 /**
250 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
251 */
252 ActionMode mChoiceActionMode;
253
254 /**
255 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
256 * a few extra actions around what application code does.
257 */
258 MultiChoiceModeWrapper mMultiChoiceModeCallback;
259
260 /**
261 * Running count of how many items are currently checked
262 */
263 int mCheckedItemCount;
264
265 /**
266 * Running state of which positions are currently checked
267 */
268 SparseBooleanArray mCheckStates;
269
270 /**
Adam Powell14c08042011-10-06 19:46:18 -0700271 * Running state of which IDs are currently checked.
272 * If there is a value for a given key, the checked state for that ID is true
273 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700274 */
Adam Powell14c08042011-10-06 19:46:18 -0700275 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700276
277 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 * Controls how the next layout will happen
279 */
280 int mLayoutMode = LAYOUT_NORMAL;
281
282 /**
283 * Should be used by subclasses to listen to changes in the dataset
284 */
285 AdapterDataSetObserver mDataSetObserver;
286
287 /**
288 * The adapter containing the data to be displayed by this view
289 */
290 ListAdapter mAdapter;
291
292 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700293 * The remote adapter containing the data to be displayed by this view to be set
294 */
295 private RemoteViewsAdapter mRemoteAdapter;
296
297 /**
Adam Powell539ee872012-02-03 19:00:49 -0800298 * If mAdapter != null, whenever this is true the adapter has stable IDs.
299 */
300 boolean mAdapterHasStableIds;
301
302 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700303 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
304 */
305 private boolean mDeferNotifyDataSetChanged = false;
306
307 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 * Indicates whether the list selector should be drawn on top of the children or behind
309 */
310 boolean mDrawSelectorOnTop = false;
311
312 /**
313 * The drawable used to draw the selector
314 */
315 Drawable mSelector;
316
317 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700318 * The current position of the selector in the list.
319 */
320 int mSelectorPosition = INVALID_POSITION;
321
322 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 * Defines the selector's location and dimension at drawing time
324 */
325 Rect mSelectorRect = new Rect();
326
327 /**
328 * The data set used to store unused views that should be reused during the next layout
329 * to avoid creating new ones
330 */
331 final RecycleBin mRecycler = new RecycleBin();
332
333 /**
334 * The selection's left padding
335 */
336 int mSelectionLeftPadding = 0;
337
338 /**
339 * The selection's top padding
340 */
341 int mSelectionTopPadding = 0;
342
343 /**
344 * The selection's right padding
345 */
346 int mSelectionRightPadding = 0;
347
348 /**
349 * The selection's bottom padding
350 */
351 int mSelectionBottomPadding = 0;
352
353 /**
354 * This view's padding
355 */
356 Rect mListPadding = new Rect();
357
358 /**
359 * Subclasses must retain their measure spec from onMeasure() into this member
360 */
361 int mWidthMeasureSpec = 0;
362
363 /**
364 * The top scroll indicator
365 */
366 View mScrollUp;
367
368 /**
369 * The down scroll indicator
370 */
371 View mScrollDown;
372
373 /**
374 * When the view is scrolling, this flag is set to true to indicate subclasses that
375 * the drawing cache was enabled on the children
376 */
377 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800378 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379
380 /**
381 * The position of the view that received the down motion event
382 */
383 int mMotionPosition;
384
385 /**
386 * The offset to the top of the mMotionPosition view when the down motion event was received
387 */
388 int mMotionViewOriginalTop;
389
390 /**
391 * The desired offset to the top of the mMotionPosition view after a scroll
392 */
393 int mMotionViewNewTop;
394
395 /**
396 * The X value associated with the the down motion event
397 */
398 int mMotionX;
399
400 /**
401 * The Y value associated with the the down motion event
402 */
403 int mMotionY;
404
405 /**
406 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
407 * TOUCH_MODE_DONE_WAITING
408 */
409 int mTouchMode = TOUCH_MODE_REST;
410
411 /**
412 * Y value from on the previous motion event (if any)
413 */
414 int mLastY;
415
416 /**
417 * How far the finger moved before we started scrolling
418 */
419 int mMotionCorrection;
420
421 /**
422 * Determines speed during touch scrolling
423 */
424 private VelocityTracker mVelocityTracker;
425
426 /**
427 * Handles one frame of a fling
428 */
429 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800430
Adam Powell45803472010-01-25 15:10:44 -0800431 /**
432 * Handles scrolling between positions within the list.
433 */
Alan Viveretted22db212014-02-13 17:47:38 -0800434 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435
436 /**
437 * The offset in pixels form the top of the AdapterView to the top
438 * of the currently selected view. Used to save and restore state.
439 */
440 int mSelectedTop = 0;
441
442 /**
443 * Indicates whether the list is stacked from the bottom edge or
444 * the top edge.
445 */
446 boolean mStackFromBottom;
447
448 /**
449 * When set to true, the list automatically discards the children's
450 * bitmap cache after scrolling.
451 */
452 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 /**
455 * Whether or not to enable the fast scroll feature on this list
456 */
457 boolean mFastScrollEnabled;
458
459 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700460 * Whether or not to always show the fast scroll feature on this list
461 */
462 boolean mFastScrollAlwaysVisible;
463
464 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 * Optional callback to notify client when scroll position has changed
466 */
467 private OnScrollListener mOnScrollListener;
468
469 /**
470 * Keeps track of our accessory window
471 */
472 PopupWindow mPopup;
473
474 /**
475 * Used with type filter window
476 */
477 EditText mTextFilter;
478
479 /**
480 * Indicates whether to use pixels-based or position-based scrollbar
481 * properties.
482 */
483 private boolean mSmoothScrollbarEnabled = true;
484
485 /**
486 * Indicates that this view supports filtering
487 */
488 private boolean mTextFilterEnabled;
489
490 /**
491 * Indicates that this view is currently displaying a filtered view of the data
492 */
493 private boolean mFiltered;
494
495 /**
496 * Rectangle used for hit testing children
497 */
498 private Rect mTouchFrame;
499
500 /**
501 * The position to resurrect the selected position to.
502 */
503 int mResurrectToPosition = INVALID_POSITION;
504
505 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800506
Adam Powell0b8bb422010-02-08 14:30:45 -0800507 /**
Adam Powell637d3372010-08-25 14:37:03 -0700508 * Maximum distance to record overscroll
509 */
510 int mOverscrollMax;
511
512 /**
513 * Content height divided by this is the overscroll limit.
514 */
515 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
516
517 /**
Adam Powell14c08042011-10-06 19:46:18 -0700518 * How many positions in either direction we will search to try to
519 * find a checked item with a stable ID that moved position across
520 * a data set change. If the item isn't found it will be unselected.
521 */
522 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
523
524 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 * Used to request a layout when we changed touch mode
526 */
527 private static final int TOUCH_MODE_UNKNOWN = -1;
528 private static final int TOUCH_MODE_ON = 0;
529 private static final int TOUCH_MODE_OFF = 1;
530
531 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
532
533 private static final boolean PROFILE_SCROLLING = false;
534 private boolean mScrollProfilingStarted = false;
535
536 private static final boolean PROFILE_FLINGING = false;
537 private boolean mFlingProfilingStarted = false;
538
539 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800540 * The StrictMode "critical time span" objects to catch animation
541 * stutters. Non-null when a time-sensitive animation is
542 * in-flight. Must call finish() on them when done animating.
543 * These are no-ops on user builds.
544 */
545 private StrictMode.Span mScrollStrictSpan = null;
546 private StrictMode.Span mFlingStrictSpan = null;
547
548 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 * The last CheckForLongPress runnable we posted, if any
550 */
551 private CheckForLongPress mPendingCheckForLongPress;
552
553 /**
554 * The last CheckForTap runnable we posted, if any
555 */
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700556 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700557
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 /**
559 * The last CheckForKeyLongPress runnable we posted, if any
560 */
561 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
562
563 /**
564 * Acts upon click
565 */
566 private AbsListView.PerformClick mPerformClick;
567
568 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800569 * Delayed action for touch mode.
570 */
571 private Runnable mTouchModeReset;
572
573 /**
Alan Viverette66df60f2016-01-28 14:56:07 -0500574 * Whether the most recent touch event stream resulted in a successful
575 * long-press action. This is reset on TOUCH_DOWN.
576 */
577 private boolean mHasPerformedLongPress;
578
579 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 * This view is in transcript mode -- it shows the bottom of the list when the data
581 * changes
582 */
583 private int mTranscriptMode;
584
585 /**
586 * Indicates that this list is always drawn on top of a solid, single-color, opaque
587 * background
588 */
589 private int mCacheColorHint;
590
591 /**
592 * The select child's view (from the adapter's getView) is enabled.
593 */
594 private boolean mIsChildViewEnabled;
595
596 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800597 * The cached drawable state for the selector. Accounts for child enabled
598 * state, but otherwise identical to the view's own drawable state.
599 */
600 private int[] mSelectorState;
601
602 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 * The last scroll state reported to clients through {@link OnScrollListener}.
604 */
605 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
606
607 /**
608 * Helper object that renders and controls the fast scroll thumb.
609 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700610 private FastScroller mFastScroll;
611
612 /**
613 * Temporary holder for fast scroller style until a FastScroller object
614 * is created.
615 */
616 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617
Romain Guyd6a463a2009-05-21 23:10:10 -0700618 private boolean mGlobalLayoutListenerAddedFilter;
619
620 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 private float mDensityScale;
622
Aaron Whytef8306522017-03-22 16:30:58 -0700623 private float mVerticalScrollFactor;
Ned Burns20ad0732016-08-18 14:22:57 -0400624
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700625 private InputConnection mDefInputConnection;
626 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700627
628 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700629 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700630 private int mMinimumVelocity;
631 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700632 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800633
Romain Guy21875052010-01-06 18:48:08 -0800634 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800635
Adam Powell96d62af2014-05-02 10:04:38 -0700636 private final int[] mScrollOffset = new int[2];
637 private final int[] mScrollConsumed = new int[2];
638
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800639 private final float[] mTmpPoint = new float[2];
640
Adam Powell744beff2014-09-22 09:47:48 -0700641 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
642 // In the future it would be nice to be able to give this to the VelocityTracker
643 // directly, or alternatively put a VT into absolute-positioning mode that only
644 // reads the raw screen-coordinate x/y values.
645 private int mNestedYOffset = 0;
646
Romain Guy24562482010-02-01 14:56:19 -0800647 // True when the popup should be hidden because of a call to
648 // dispatchDisplayHint()
649 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800650
Adam Powell4cd47702010-02-25 11:21:14 -0800651 /**
652 * ID of the active pointer. This is used to retain consistency during
653 * drags/flings if multiple pointers are used.
654 */
655 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800656
Adam Powell4cd47702010-02-25 11:21:14 -0800657 /**
658 * Sentinel value for no current active pointer.
659 * Used by {@link #mActivePointerId}.
660 */
661 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 /**
Adam Powell637d3372010-08-25 14:37:03 -0700664 * Maximum distance to overscroll by during edge effects
665 */
666 int mOverscrollDistance;
667
668 /**
669 * Maximum distance to overfling during edge effects
670 */
671 int mOverflingDistance;
672
673 // These two EdgeGlows are always set and used together.
674 // Checking one for null is as good as checking both.
675
676 /**
677 * Tracks the state of the top edge glow.
678 */
Adam Powell89935e42011-08-31 14:26:12 -0700679 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700680
681 /**
682 * Tracks the state of the bottom edge glow.
683 */
Adam Powell89935e42011-08-31 14:26:12 -0700684 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700685
686 /**
687 * An estimate of how many pixels are between the top of the list and
688 * the top of the first position in the adapter, based on the last time
689 * we saw it. Used to hint where to draw edge glows.
690 */
691 private int mFirstPositionDistanceGuess;
692
693 /**
694 * An estimate of how many pixels are between the bottom of the list and
695 * the bottom of the last position in the adapter, based on the last time
696 * we saw it. Used to hint where to draw edge glows.
697 */
698 private int mLastPositionDistanceGuess;
699
700 /**
701 * Used for determining when to cancel out of overscroll.
702 */
703 private int mDirection = 0;
704
705 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800706 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
707 * the bottom correctly on resizes.
708 */
709 private boolean mForceTranscriptScroll;
710
alanvc1d7e772012-05-08 14:47:24 -0700711 /**
712 * Used for interacting with list items from an accessibility service.
713 */
714 private ListItemAccessibilityDelegate mAccessibilityDelegate;
715
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700716 private int mLastAccessibilityScrollEventFromIndex;
717 private int mLastAccessibilityScrollEventToIndex;
718
Adam Powellda13dba2010-12-05 13:47:23 -0800719 /**
Adam Powellee78b172011-08-16 16:39:20 -0700720 * Track the item count from the last time we handled a data change.
721 */
722 private int mLastHandledItemCount;
723
724 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700725 * Used for smooth scrolling at a consistent rate
726 */
727 static final Interpolator sLinearInterpolator = new LinearInterpolator();
728
729 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700730 * The saved state that we will be restoring from when we next sync.
731 * Kept here so that if we happen to be asked to save our state before
732 * the sync happens, we can return this existing data rather than losing
733 * it.
734 */
735 private SavedState mPendingSync;
736
737 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800738 * Whether the view is in the process of detaching from its window.
739 */
740 private boolean mIsDetaching;
741
742 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743 * Interface definition for a callback to be invoked when the list or grid
744 * has been scrolled.
745 */
746 public interface OnScrollListener {
747
748 /**
749 * The view is not scrolling. Note navigating the list using the trackball counts as
750 * being in the idle state since these transitions are not animated.
751 */
752 public static int SCROLL_STATE_IDLE = 0;
753
754 /**
755 * The user is scrolling using touch, and their finger is still on the screen
756 */
757 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
758
759 /**
760 * The user had previously been scrolling using touch and had performed a fling. The
761 * animation is now coasting to a stop
762 */
763 public static int SCROLL_STATE_FLING = 2;
764
765 /**
766 * Callback method to be invoked while the list view or grid view is being scrolled. If the
767 * view is being scrolled, this method will be called before the next frame of the scroll is
768 * rendered. In particular, it will be called before any calls to
769 * {@link Adapter#getView(int, View, ViewGroup)}.
770 *
771 * @param view The view whose scroll state is being reported
772 *
Yorke Lee43943d82014-05-08 10:15:20 -0700773 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
775 */
776 public void onScrollStateChanged(AbsListView view, int scrollState);
777
778 /**
779 * Callback method to be invoked when the list or grid has been scrolled. This will be
780 * called after the scroll has completed
781 * @param view The view whose scroll state is being reported
782 * @param firstVisibleItem the index of the first visible cell (ignore if
783 * visibleItemCount == 0)
784 * @param visibleItemCount the number of visible cells
785 * @param totalItemCount the number of items in the list adaptor
786 */
787 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
788 int totalItemCount);
789 }
790
Dianne Hackborne2136772010-11-04 15:08:59 -0700791 /**
792 * The top-level view of a list item can implement this interface to allow
793 * itself to modify the bounds of the selection shown for that item.
794 */
795 public interface SelectionBoundsAdjuster {
796 /**
797 * Called to allow the list item to adjust the bounds shown for
798 * its selection.
799 *
800 * @param bounds On call, this contains the bounds the list has
801 * selected for the item (that is the bounds of the entire view). The
802 * values can be modified as desired.
803 */
804 public void adjustListItemSelectionBounds(Rect bounds);
805 }
806
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 public AbsListView(Context context) {
808 super(context);
809 initAbsListView();
810
Alan Viverette39bed692013-08-07 15:47:04 -0700811 mOwnerThread = Thread.currentThread();
812
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813 setVerticalScrollBarEnabled(true);
814 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700815 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800816 a.recycle();
817 }
818
819 public AbsListView(Context context, AttributeSet attrs) {
820 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
821 }
822
Alan Viverette617feb92013-09-09 18:09:13 -0700823 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
824 this(context, attrs, defStyleAttr, 0);
825 }
826
827 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
828 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 initAbsListView();
830
Alan Viverette39bed692013-08-07 15:47:04 -0700831 mOwnerThread = Thread.currentThread();
832
Alan Viverette617feb92013-09-09 18:09:13 -0700833 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700834 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835
Alan Viverette7eceda32015-06-01 10:47:29 -0700836 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
837 if (selector != null) {
838 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 }
840
Alan Viverette7eceda32015-06-01 10:47:29 -0700841 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842
Alan Viverette7eceda32015-06-01 10:47:29 -0700843 setStackFromBottom(a.getBoolean(
844 R.styleable.AbsListView_stackFromBottom, false));
845 setScrollingCacheEnabled(a.getBoolean(
846 R.styleable.AbsListView_scrollingCache, true));
847 setTextFilterEnabled(a.getBoolean(
848 R.styleable.AbsListView_textFilterEnabled, false));
849 setTranscriptMode(a.getInt(
850 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
851 setCacheColorHint(a.getColor(
852 R.styleable.AbsListView_cacheColorHint, 0));
853 setSmoothScrollbarEnabled(a.getBoolean(
854 R.styleable.AbsListView_smoothScrollbar, true));
855 setChoiceMode(a.getInt(
856 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857
Alan Viverette7eceda32015-06-01 10:47:29 -0700858 setFastScrollEnabled(a.getBoolean(
859 R.styleable.AbsListView_fastScrollEnabled, false));
860 setFastScrollStyle(a.getResourceId(
861 R.styleable.AbsListView_fastScrollStyle, 0));
862 setFastScrollAlwaysVisible(a.getBoolean(
863 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700864
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800865 a.recycle();
Adam Powell2fe301d2016-08-15 16:34:37 -0700866
867 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
868 setRevealOnFocusHint(false);
869 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 }
871
Romain Guyd6a463a2009-05-21 23:10:10 -0700872 private void initAbsListView() {
873 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700874 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700875 setFocusableInTouchMode(true);
876 setWillNotDraw(false);
877 setAlwaysDrawnWithCacheEnabled(false);
878 setScrollingCacheEnabled(true);
879
Romain Guy4296fc42009-07-06 11:48:52 -0700880 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
881 mTouchSlop = configuration.getScaledTouchSlop();
Aaron Whytef8306522017-03-22 16:30:58 -0700882 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
Romain Guy4296fc42009-07-06 11:48:52 -0700883 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
884 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700885 mOverscrollDistance = configuration.getScaledOverscrollDistance();
886 mOverflingDistance = configuration.getScaledOverflingDistance();
887
Romain Guyd6a463a2009-05-21 23:10:10 -0700888 mDensityScale = getContext().getResources().getDisplayMetrics().density;
889 }
Romain Guy0a637162009-05-29 14:43:54 -0700890
Adam Powell637d3372010-08-25 14:37:03 -0700891 @Override
892 public void setOverScrollMode(int mode) {
893 if (mode != OVER_SCROLL_NEVER) {
894 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800895 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700896 mEdgeGlowTop = new EdgeEffect(context);
897 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700898 }
899 } else {
900 mEdgeGlowTop = null;
901 mEdgeGlowBottom = null;
902 }
903 super.setOverScrollMode(mode);
904 }
905
Romain Guyd6a463a2009-05-21 23:10:10 -0700906 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700907 * {@inheritDoc}
908 */
909 @Override
910 public void setAdapter(ListAdapter adapter) {
911 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800912 mAdapterHasStableIds = mAdapter.hasStableIds();
913 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700914 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700915 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700916 }
917 }
Sumir Kataria37b85672017-07-11 15:35:30 -0700918 clearChoices();
Adam Powellf343e1b2010-08-13 18:27:04 -0700919 }
920
921 /**
922 * Returns the number of items currently selected. This will only be valid
923 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
924 *
925 * <p>To determine the specific items that are currently selected, use one of
926 * the <code>getChecked*</code> methods.
927 *
928 * @return The number of items currently selected
929 *
930 * @see #getCheckedItemPosition()
931 * @see #getCheckedItemPositions()
932 * @see #getCheckedItemIds()
933 */
934 public int getCheckedItemCount() {
935 return mCheckedItemCount;
936 }
937
938 /**
939 * Returns the checked state of the specified position. The result is only
940 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
941 * or {@link #CHOICE_MODE_MULTIPLE}.
942 *
943 * @param position The item whose checked state to return
944 * @return The item's checked state or <code>false</code> if choice mode
945 * is invalid
946 *
947 * @see #setChoiceMode(int)
948 */
949 public boolean isItemChecked(int position) {
950 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
951 return mCheckStates.get(position);
952 }
953
954 return false;
955 }
956
957 /**
958 * Returns the currently checked item. The result is only valid if the choice
959 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
960 *
961 * @return The position of the currently checked item or
962 * {@link #INVALID_POSITION} if nothing is selected
963 *
964 * @see #setChoiceMode(int)
965 */
966 public int getCheckedItemPosition() {
967 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
968 return mCheckStates.keyAt(0);
969 }
970
971 return INVALID_POSITION;
972 }
973
974 /**
975 * Returns the set of checked items in the list. The result is only valid if
976 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
977 *
978 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200979 * get(int position) where position is a checked position in the
980 * list and false otherwise, or <code>null</code> if the choice
981 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700982 */
983 public SparseBooleanArray getCheckedItemPositions() {
984 if (mChoiceMode != CHOICE_MODE_NONE) {
985 return mCheckStates;
986 }
987 return null;
988 }
989
990 /**
991 * Returns the set of checked items ids. The result is only valid if the
992 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
993 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
994 *
995 * @return A new array which contains the id of each checked item in the
996 * list.
997 */
998 public long[] getCheckedItemIds() {
999 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
1000 return new long[0];
1001 }
1002
Adam Powell14c08042011-10-06 19:46:18 -07001003 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -07001004 final int count = idStates.size();
1005 final long[] ids = new long[count];
1006
1007 for (int i = 0; i < count; i++) {
1008 ids[i] = idStates.keyAt(i);
1009 }
1010
1011 return ids;
1012 }
1013
1014 /**
1015 * Clear any choices previously set
1016 */
1017 public void clearChoices() {
1018 if (mCheckStates != null) {
1019 mCheckStates.clear();
1020 }
1021 if (mCheckedIdStates != null) {
1022 mCheckedIdStates.clear();
1023 }
1024 mCheckedItemCount = 0;
1025 }
1026
1027 /**
1028 * Sets the checked state of the specified position. The is only valid if
1029 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1030 * {@link #CHOICE_MODE_MULTIPLE}.
1031 *
1032 * @param position The item whose checked state is to be checked
1033 * @param value The new checked state for the item
1034 */
1035 public void setItemChecked(int position, boolean value) {
1036 if (mChoiceMode == CHOICE_MODE_NONE) {
1037 return;
1038 }
1039
1040 // Start selection mode if needed. We don't need to if we're unchecking something.
1041 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001042 if (mMultiChoiceModeCallback == null ||
1043 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1044 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1045 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1046 "supplied. Call setMultiChoiceModeListener to set a callback.");
1047 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001048 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1049 }
1050
Siyamed Sinir135554e2016-01-22 18:40:42 -08001051 final boolean itemCheckChanged;
Adam Powellf343e1b2010-08-13 18:27:04 -07001052 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1053 boolean oldValue = mCheckStates.get(position);
1054 mCheckStates.put(position, value);
1055 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1056 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001057 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001058 } else {
1059 mCheckedIdStates.delete(mAdapter.getItemId(position));
1060 }
1061 }
Siyamed Sinir135554e2016-01-22 18:40:42 -08001062 itemCheckChanged = oldValue != value;
1063 if (itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001064 if (value) {
1065 mCheckedItemCount++;
1066 } else {
1067 mCheckedItemCount--;
1068 }
1069 }
1070 if (mChoiceActionMode != null) {
1071 final long id = mAdapter.getItemId(position);
1072 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1073 position, id, value);
1074 }
1075 } else {
1076 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1077 // Clear all values if we're checking something, or unchecking the currently
1078 // selected item
Siyamed Sinir135554e2016-01-22 18:40:42 -08001079 itemCheckChanged = isItemChecked(position) != value;
Adam Powellf343e1b2010-08-13 18:27:04 -07001080 if (value || isItemChecked(position)) {
1081 mCheckStates.clear();
1082 if (updateIds) {
1083 mCheckedIdStates.clear();
1084 }
1085 }
1086 // this may end up selecting the value we just cleared but this way
1087 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1088 if (value) {
1089 mCheckStates.put(position, true);
1090 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001091 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001092 }
1093 mCheckedItemCount = 1;
1094 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1095 mCheckedItemCount = 0;
1096 }
1097 }
1098
Siyamed Sinir135554e2016-01-22 18:40:42 -08001099 // Do not generate a data change while we are in the layout phase or data has not changed
1100 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001101 mDataChanged = true;
1102 rememberSyncState();
1103 requestLayout();
1104 }
1105 }
1106
1107 @Override
1108 public boolean performItemClick(View view, int position, long id) {
1109 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001110 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001111
1112 if (mChoiceMode != CHOICE_MODE_NONE) {
1113 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001114 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001115
1116 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1117 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001118 boolean checked = !mCheckStates.get(position, false);
1119 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001120 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001121 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001122 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001123 } else {
1124 mCheckedIdStates.delete(mAdapter.getItemId(position));
1125 }
1126 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001127 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001128 mCheckedItemCount++;
1129 } else {
1130 mCheckedItemCount--;
1131 }
1132 if (mChoiceActionMode != null) {
1133 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001134 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001135 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001136 }
Adam Powell29382d92012-02-23 11:03:22 -08001137 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001138 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001139 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001140 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001141 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001142 mCheckStates.put(position, true);
1143 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1144 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001145 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001146 }
1147 mCheckedItemCount = 1;
1148 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1149 mCheckedItemCount = 0;
1150 }
Adam Powell29382d92012-02-23 11:03:22 -08001151 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001152 }
1153
Adam Powell29382d92012-02-23 11:03:22 -08001154 if (checkedStateChanged) {
1155 updateOnScreenCheckedViews();
1156 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001157 }
1158
Adam Powellbf5f2b32010-10-24 16:45:44 -07001159 if (dispatchItemClick) {
1160 handled |= super.performItemClick(view, position, id);
1161 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001162
1163 return handled;
1164 }
1165
1166 /**
Adam Powell29382d92012-02-23 11:03:22 -08001167 * Perform a quick, in-place update of the checked or activated state
1168 * on all visible item views. This should only be called when a valid
1169 * choice mode is active.
1170 */
1171 private void updateOnScreenCheckedViews() {
1172 final int firstPos = mFirstPosition;
1173 final int count = getChildCount();
1174 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1175 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1176 for (int i = 0; i < count; i++) {
1177 final View child = getChildAt(i);
1178 final int position = firstPos + i;
1179
1180 if (child instanceof Checkable) {
1181 ((Checkable) child).setChecked(mCheckStates.get(position));
1182 } else if (useActivated) {
1183 child.setActivated(mCheckStates.get(position));
1184 }
1185 }
1186 }
1187
1188 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001189 * @see #setChoiceMode(int)
1190 *
1191 * @return The current choice mode
1192 */
1193 public int getChoiceMode() {
1194 return mChoiceMode;
1195 }
1196
1197 /**
1198 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1199 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1200 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1201 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1202 *
1203 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1204 * {@link #CHOICE_MODE_MULTIPLE}
1205 */
1206 public void setChoiceMode(int choiceMode) {
1207 mChoiceMode = choiceMode;
1208 if (mChoiceActionMode != null) {
1209 mChoiceActionMode.finish();
1210 mChoiceActionMode = null;
1211 }
1212 if (mChoiceMode != CHOICE_MODE_NONE) {
1213 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001214 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001215 }
1216 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001217 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001218 }
1219 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1220 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1221 clearChoices();
1222 setLongClickable(true);
1223 }
1224 }
1225 }
1226
1227 /**
1228 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1229 * selection {@link ActionMode}. Only used when the choice mode is set to
1230 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1231 *
1232 * @param listener Listener that will manage the selection mode
1233 *
1234 * @see #setChoiceMode(int)
1235 */
1236 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1237 if (mMultiChoiceModeCallback == null) {
1238 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1239 }
1240 mMultiChoiceModeCallback.setWrapped(listener);
1241 }
1242
1243 /**
Adam Powell637d3372010-08-25 14:37:03 -07001244 * @return true if all list content currently fits within the view boundaries
1245 */
1246 private boolean contentFits() {
1247 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001248 if (childCount == 0) return true;
1249 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001250
Adam Powell4ce35412011-01-24 14:55:00 -08001251 return getChildAt(0).getTop() >= mListPadding.top &&
1252 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001253 }
1254
1255 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001256 * Specifies whether fast scrolling is enabled or disabled.
1257 * <p>
1258 * When fast scrolling is enabled, the user can quickly scroll through lists
1259 * by dragging the fast scroll thumb.
1260 * <p>
1261 * If the adapter backing this list implements {@link SectionIndexer}, the
1262 * fast scroller will display section header previews as the user scrolls.
1263 * Additionally, the user will be able to quickly jump between sections by
1264 * tapping along the length of the scroll bar.
1265 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001266 * @see SectionIndexer
1267 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001268 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001269 */
Alan Viverette39bed692013-08-07 15:47:04 -07001270 public void setFastScrollEnabled(final boolean enabled) {
1271 if (mFastScrollEnabled != enabled) {
1272 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001273
Alan Viverette39bed692013-08-07 15:47:04 -07001274 if (isOwnerThread()) {
1275 setFastScrollerEnabledUiThread(enabled);
1276 } else {
1277 post(new Runnable() {
1278 @Override
1279 public void run() {
1280 setFastScrollerEnabledUiThread(enabled);
1281 }
1282 });
1283 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001284 }
Alan Viverette39bed692013-08-07 15:47:04 -07001285 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001286
Alan Viverette39bed692013-08-07 15:47:04 -07001287 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001288 if (mFastScroll != null) {
1289 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001290 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001291 mFastScroll = new FastScroller(this, mFastScrollStyle);
1292 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001293 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001294
Alan Viveretteb9f27222013-09-06 19:39:47 -07001295 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001296
Alan Viverette8636ace2013-10-31 15:41:31 -07001297 if (mFastScroll != null) {
1298 mFastScroll.updateLayout();
1299 }
1300 }
1301
1302 /**
1303 * Specifies the style of the fast scroller decorations.
1304 *
1305 * @param styleResId style resource containing fast scroller properties
1306 * @see android.R.styleable#FastScroll
1307 */
1308 public void setFastScrollStyle(int styleResId) {
1309 if (mFastScroll == null) {
1310 mFastScrollStyle = styleResId;
1311 } else {
1312 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001313 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 }
Romain Guy0a637162009-05-29 14:43:54 -07001315
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001316 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001317 * Set whether or not the fast scroller should always be shown in place of
1318 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001319 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001320 * <p>
1321 * Fast scrollers shown in this way will not fade out and will be a
1322 * permanent fixture within the list. This is best combined with an inset
1323 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001324 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001325 * @param alwaysShow true if the fast scroller should always be displayed,
1326 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001327 * @see #setScrollBarStyle(int)
1328 * @see #setFastScrollEnabled(boolean)
1329 */
Alan Viverette39bed692013-08-07 15:47:04 -07001330 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1331 if (mFastScrollAlwaysVisible != alwaysShow) {
1332 if (alwaysShow && !mFastScrollEnabled) {
1333 setFastScrollEnabled(true);
1334 }
Adam Powell20232d02010-12-08 21:08:53 -08001335
Alan Viverette39bed692013-08-07 15:47:04 -07001336 mFastScrollAlwaysVisible = alwaysShow;
1337
1338 if (isOwnerThread()) {
1339 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1340 } else {
1341 post(new Runnable() {
1342 @Override
1343 public void run() {
1344 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1345 }
1346 });
1347 }
1348 }
1349 }
1350
1351 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001352 if (mFastScroll != null) {
1353 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001354 }
Alan Viverette39bed692013-08-07 15:47:04 -07001355 }
Adam Powell20232d02010-12-08 21:08:53 -08001356
Alan Viverette39bed692013-08-07 15:47:04 -07001357 /**
1358 * @return whether the current thread is the one that created the view
1359 */
1360 private boolean isOwnerThread() {
1361 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001362 }
1363
1364 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001365 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001366 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001367 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001368 * @see #setFastScrollAlwaysVisible(boolean)
1369 */
1370 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001371 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001372 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1373 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001374 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001375 }
Adam Powell20232d02010-12-08 21:08:53 -08001376 }
1377
1378 @Override
1379 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001380 if (mFastScroll != null && mFastScroll.isEnabled()) {
1381 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001382 }
1383 return super.getVerticalScrollbarWidth();
1384 }
1385
1386 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001387 * Returns true if the fast scroller is enabled.
1388 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001389 * @see #setFastScrollEnabled(boolean)
1390 * @return true if fast scroll is enabled, false otherwise
1391 */
1392 @ViewDebug.ExportedProperty
1393 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001394 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001395 return mFastScrollEnabled;
1396 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001397 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001398 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399 }
Romain Guy0a637162009-05-29 14:43:54 -07001400
Adam Powell20232d02010-12-08 21:08:53 -08001401 @Override
1402 public void setVerticalScrollbarPosition(int position) {
1403 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001404 if (mFastScroll != null) {
1405 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001406 }
1407 }
1408
Alan Viverette26bb2532013-08-09 10:40:50 -07001409 @Override
1410 public void setScrollBarStyle(int style) {
1411 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001412 if (mFastScroll != null) {
1413 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001414 }
1415 }
1416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001417 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001418 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001419 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001420 */
1421 @Override
1422 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001423 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001424 }
1425
1426 /**
1427 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1428 * is computed based on the number of visible pixels in the visible items. This
1429 * however assumes that all list items have the same height. If you use a list in
1430 * which items have different heights, the scrollbar will change appearance as the
1431 * user scrolls through the list. To avoid this issue, you need to disable this
1432 * property.
1433 *
1434 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1435 * is based solely on the number of items in the adapter and the position of the
1436 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001437 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 *
1439 * @param enabled Whether or not to enable smooth scrollbar.
1440 *
Romain Guy0a637162009-05-29 14:43:54 -07001441 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1443 */
1444 public void setSmoothScrollbarEnabled(boolean enabled) {
1445 mSmoothScrollbarEnabled = enabled;
1446 }
1447
1448 /**
1449 * Returns the current state of the fast scroll feature.
1450 *
1451 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1452 *
1453 * @see #setSmoothScrollbarEnabled(boolean)
1454 */
1455 @ViewDebug.ExportedProperty
1456 public boolean isSmoothScrollbarEnabled() {
1457 return mSmoothScrollbarEnabled;
1458 }
1459
1460 /**
1461 * Set the listener that will receive notifications every time the list scrolls.
1462 *
1463 * @param l the scroll listener
1464 */
1465 public void setOnScrollListener(OnScrollListener l) {
1466 mOnScrollListener = l;
1467 invokeOnItemScrollListener();
1468 }
1469
1470 /**
1471 * Notify our scroll listener (if there is one) of a change in scroll state
1472 */
1473 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001474 if (mFastScroll != null) {
1475 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 }
1477 if (mOnScrollListener != null) {
1478 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1479 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001480 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 }
1482
Alan Viverettea54956a2015-01-07 16:05:02 -08001483 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001484 @Override
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001485 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001486 // Since this class calls onScrollChanged even if the mFirstPosition and the
1487 // child count have not changed we will avoid sending duplicate accessibility
1488 // events.
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001489 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001490 final int firstVisiblePosition = getFirstVisiblePosition();
1491 final int lastVisiblePosition = getLastVisiblePosition();
1492 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1493 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001494 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001495 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001496 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1497 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001498 }
1499 }
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001500 super.sendAccessibilityEventUnchecked(event);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001501 }
1502
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001503 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001504 public CharSequence getAccessibilityClassName() {
1505 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001506 }
1507
Alan Viverettea54956a2015-01-07 16:05:02 -08001508 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001509 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001510 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1511 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001512 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001513 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001514 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001515 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001516 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001517 }
Alan Viverette947a9692014-09-25 12:43:47 -07001518 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001519 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001520 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001521 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001522 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001523 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001524
1525 info.removeAction(AccessibilityAction.ACTION_CLICK);
1526 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001527 }
1528
Alan Viverette76769ae2014-02-12 16:38:10 -08001529 int getSelectionModeForAccessibility() {
1530 final int choiceMode = getChoiceMode();
1531 switch (choiceMode) {
1532 case CHOICE_MODE_NONE:
1533 return CollectionInfo.SELECTION_MODE_NONE;
1534 case CHOICE_MODE_SINGLE:
1535 return CollectionInfo.SELECTION_MODE_SINGLE;
1536 case CHOICE_MODE_MULTIPLE:
1537 case CHOICE_MODE_MULTIPLE_MODAL:
1538 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1539 default:
1540 return CollectionInfo.SELECTION_MODE_NONE;
1541 }
1542 }
1543
Alan Viverettea54956a2015-01-07 16:05:02 -08001544 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001545 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001546 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1547 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001548 return true;
1549 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001550 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001551 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1552 case R.id.accessibilityActionScrollDown: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001553 if (isEnabled() && canScrollDown()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001554 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1555 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1556 return true;
1557 }
1558 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001559 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1560 case R.id.accessibilityActionScrollUp: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001561 if (isEnabled() && canScrollUp()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001562 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1563 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1564 return true;
1565 }
1566 } return false;
1567 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001568 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001569 }
1570
Svetoslav5b578da2013-05-08 14:23:32 -07001571 /** @hide */
1572 @Override
1573 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1574 if (accessibilityId == getAccessibilityViewId()) {
1575 return this;
1576 }
Svetoslav5b578da2013-05-08 14:23:32 -07001577 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1578 }
1579
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001580 /**
1581 * Indicates whether the children's drawing cache is used during a scroll.
1582 * By default, the drawing cache is enabled but this will consume more memory.
1583 *
1584 * @return true if the scrolling cache is enabled, false otherwise
1585 *
1586 * @see #setScrollingCacheEnabled(boolean)
1587 * @see View#setDrawingCacheEnabled(boolean)
1588 */
1589 @ViewDebug.ExportedProperty
1590 public boolean isScrollingCacheEnabled() {
1591 return mScrollingCacheEnabled;
1592 }
1593
1594 /**
1595 * Enables or disables the children's drawing cache during a scroll.
1596 * By default, the drawing cache is enabled but this will use more memory.
1597 *
1598 * When the scrolling cache is enabled, the caches are kept after the
1599 * first scrolling. You can manually clear the cache by calling
1600 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1601 *
1602 * @param enabled true to enable the scroll cache, false otherwise
1603 *
1604 * @see #isScrollingCacheEnabled()
1605 * @see View#setDrawingCacheEnabled(boolean)
1606 */
1607 public void setScrollingCacheEnabled(boolean enabled) {
1608 if (mScrollingCacheEnabled && !enabled) {
1609 clearScrollingCache();
1610 }
1611 mScrollingCacheEnabled = enabled;
1612 }
1613
1614 /**
1615 * Enables or disables the type filter window. If enabled, typing when
1616 * this view has focus will filter the children to match the users input.
1617 * Note that the {@link Adapter} used by this view must implement the
1618 * {@link Filterable} interface.
1619 *
1620 * @param textFilterEnabled true to enable type filtering, false otherwise
1621 *
1622 * @see Filterable
1623 */
1624 public void setTextFilterEnabled(boolean textFilterEnabled) {
1625 mTextFilterEnabled = textFilterEnabled;
1626 }
1627
1628 /**
1629 * Indicates whether type filtering is enabled for this view
1630 *
1631 * @return true if type filtering is enabled, false otherwise
1632 *
1633 * @see #setTextFilterEnabled(boolean)
1634 * @see Filterable
1635 */
1636 @ViewDebug.ExportedProperty
1637 public boolean isTextFilterEnabled() {
1638 return mTextFilterEnabled;
1639 }
1640
1641 @Override
1642 public void getFocusedRect(Rect r) {
1643 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001644 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001645 // the focused rectangle of the selected view offset into the
1646 // coordinate space of this view.
1647 view.getFocusedRect(r);
1648 offsetDescendantRectToMyCoords(view, r);
1649 } else {
1650 // otherwise, just the norm
1651 super.getFocusedRect(r);
1652 }
1653 }
1654
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001656 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001657 com.android.internal.R.drawable.list_selector_background));
1658 }
1659
1660 /**
1661 * Indicates whether the content of this view is pinned to, or stacked from,
1662 * the bottom edge.
1663 *
1664 * @return true if the content is stacked from the bottom edge, false otherwise
1665 */
1666 @ViewDebug.ExportedProperty
1667 public boolean isStackFromBottom() {
1668 return mStackFromBottom;
1669 }
1670
1671 /**
1672 * When stack from bottom is set to true, the list fills its content starting from
1673 * the bottom of the view.
1674 *
1675 * @param stackFromBottom true to pin the view's content to the bottom edge,
1676 * false to pin the view's content to the top edge
1677 */
1678 public void setStackFromBottom(boolean stackFromBottom) {
1679 if (mStackFromBottom != stackFromBottom) {
1680 mStackFromBottom = stackFromBottom;
1681 requestLayoutIfNecessary();
1682 }
1683 }
1684
1685 void requestLayoutIfNecessary() {
1686 if (getChildCount() > 0) {
1687 resetList();
1688 requestLayout();
1689 invalidate();
1690 }
1691 }
1692
1693 static class SavedState extends BaseSavedState {
1694 long selectedId;
1695 long firstId;
1696 int viewTop;
1697 int position;
1698 int height;
1699 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001700 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001701 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001702 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001703 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001704
1705 /**
1706 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1707 */
1708 SavedState(Parcelable superState) {
1709 super(superState);
1710 }
1711
1712 /**
1713 * Constructor called from {@link #CREATOR}
1714 */
1715 private SavedState(Parcel in) {
1716 super(in);
1717 selectedId = in.readLong();
1718 firstId = in.readLong();
1719 viewTop = in.readInt();
1720 position = in.readInt();
1721 height = in.readInt();
1722 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001723 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001724 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001725 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001726 final int N = in.readInt();
1727 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001728 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001729 for (int i=0; i<N; i++) {
1730 final long key = in.readLong();
1731 final int value = in.readInt();
1732 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001733 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001734 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001735 }
1736
1737 @Override
1738 public void writeToParcel(Parcel out, int flags) {
1739 super.writeToParcel(out, flags);
1740 out.writeLong(selectedId);
1741 out.writeLong(firstId);
1742 out.writeInt(viewTop);
1743 out.writeInt(position);
1744 out.writeInt(height);
1745 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001746 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001747 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001748 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001749 final int N = checkIdState != null ? checkIdState.size() : 0;
1750 out.writeInt(N);
1751 for (int i=0; i<N; i++) {
1752 out.writeLong(checkIdState.keyAt(i));
1753 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001754 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001755 }
1756
1757 @Override
1758 public String toString() {
1759 return "AbsListView.SavedState{"
1760 + Integer.toHexString(System.identityHashCode(this))
1761 + " selectedId=" + selectedId
1762 + " firstId=" + firstId
1763 + " viewTop=" + viewTop
1764 + " position=" + position
1765 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001766 + " filter=" + filter
1767 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001768 }
1769
1770 public static final Parcelable.Creator<SavedState> CREATOR
1771 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001772 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001773 public SavedState createFromParcel(Parcel in) {
1774 return new SavedState(in);
1775 }
1776
Alan Viverette8fa327a2013-05-31 14:53:13 -07001777 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001778 public SavedState[] newArray(int size) {
1779 return new SavedState[size];
1780 }
1781 };
1782 }
1783
1784 @Override
1785 public Parcelable onSaveInstanceState() {
1786 /*
1787 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001788 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001789 * that happen early enough to keep from getting complaints
1790 * about having leaked the window.
1791 */
1792 dismissPopup();
1793
1794 Parcelable superState = super.onSaveInstanceState();
1795
1796 SavedState ss = new SavedState(superState);
1797
Dianne Hackborne181bd92012-09-25 14:15:15 -07001798 if (mPendingSync != null) {
1799 // Just keep what we last restored.
1800 ss.selectedId = mPendingSync.selectedId;
1801 ss.firstId = mPendingSync.firstId;
1802 ss.viewTop = mPendingSync.viewTop;
1803 ss.position = mPendingSync.position;
1804 ss.height = mPendingSync.height;
1805 ss.filter = mPendingSync.filter;
1806 ss.inActionMode = mPendingSync.inActionMode;
1807 ss.checkedItemCount = mPendingSync.checkedItemCount;
1808 ss.checkState = mPendingSync.checkState;
1809 ss.checkIdState = mPendingSync.checkIdState;
1810 return ss;
1811 }
1812
Dianne Hackborn99441c42010-12-15 11:02:55 -08001813 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001814 long selectedId = getSelectedItemId();
1815 ss.selectedId = selectedId;
1816 ss.height = getHeight();
1817
1818 if (selectedId >= 0) {
1819 // Remember the selection
1820 ss.viewTop = mSelectedTop;
1821 ss.position = getSelectedItemPosition();
1822 ss.firstId = INVALID_POSITION;
1823 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001824 if (haveChildren && mFirstPosition > 0) {
1825 // Remember the position of the first child.
1826 // We only do this if we are not currently at the top of
1827 // the list, for two reasons:
1828 // (1) The list may be in the process of becoming empty, in
1829 // which case mItemCount may not be 0, but if we try to
1830 // ask for any information about position 0 we will crash.
1831 // (2) Being "at the top" seems like a special case, anyway,
1832 // and the user wouldn't expect to end up somewhere else when
1833 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834 View v = getChildAt(0);
1835 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001836 int firstPos = mFirstPosition;
1837 if (firstPos >= mItemCount) {
1838 firstPos = mItemCount - 1;
1839 }
1840 ss.position = firstPos;
1841 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001842 } else {
1843 ss.viewTop = 0;
1844 ss.firstId = INVALID_POSITION;
1845 ss.position = 0;
1846 }
1847 }
1848
1849 ss.filter = null;
1850 if (mFiltered) {
1851 final EditText textFilter = mTextFilter;
1852 if (textFilter != null) {
1853 Editable filterText = textFilter.getText();
1854 if (filterText != null) {
1855 ss.filter = filterText.toString();
1856 }
1857 }
1858 }
1859
Adam Powella0eeeac2010-11-05 11:55:05 -07001860 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1861
Adam Powell9a5cc282011-08-28 16:18:16 -07001862 if (mCheckStates != null) {
1863 ss.checkState = mCheckStates.clone();
1864 }
1865 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001866 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001867 final int count = mCheckedIdStates.size();
1868 for (int i = 0; i < count; i++) {
1869 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1870 }
1871 ss.checkIdState = idState;
1872 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001873 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001874
Adam Cohen335c3b62012-07-24 17:18:16 -07001875 if (mRemoteAdapter != null) {
1876 mRemoteAdapter.saveRemoteViewsCache();
1877 }
1878
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001879 return ss;
1880 }
1881
1882 @Override
1883 public void onRestoreInstanceState(Parcelable state) {
1884 SavedState ss = (SavedState) state;
1885
1886 super.onRestoreInstanceState(ss.getSuperState());
1887 mDataChanged = true;
1888
1889 mSyncHeight = ss.height;
1890
1891 if (ss.selectedId >= 0) {
1892 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001893 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001894 mSyncRowId = ss.selectedId;
1895 mSyncPosition = ss.position;
1896 mSpecificTop = ss.viewTop;
1897 mSyncMode = SYNC_SELECTED_POSITION;
1898 } else if (ss.firstId >= 0) {
1899 setSelectedPositionInt(INVALID_POSITION);
1900 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1901 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001902 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001903 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001904 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001905 mSyncRowId = ss.firstId;
1906 mSyncPosition = ss.position;
1907 mSpecificTop = ss.viewTop;
1908 mSyncMode = SYNC_FIRST_POSITION;
1909 }
1910
1911 setFilterText(ss.filter);
1912
Adam Powellf343e1b2010-08-13 18:27:04 -07001913 if (ss.checkState != null) {
1914 mCheckStates = ss.checkState;
1915 }
1916
1917 if (ss.checkIdState != null) {
1918 mCheckedIdStates = ss.checkIdState;
1919 }
1920
Adam Powell2614c6c2010-11-04 17:54:45 -07001921 mCheckedItemCount = ss.checkedItemCount;
1922
Adam Powella0eeeac2010-11-05 11:55:05 -07001923 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1924 mMultiChoiceModeCallback != null) {
1925 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1926 }
1927
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001928 requestLayout();
1929 }
1930
1931 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001932 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1933 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001934 }
1935
1936 /**
1937 * Sets the initial value for the text filter.
1938 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001939 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001940 * @see #setTextFilterEnabled
1941 */
1942 public void setFilterText(String filterText) {
1943 // TODO: Should we check for acceptFilter()?
1944 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1945 createTextFilter(false);
1946 // This is going to call our listener onTextChanged, but we might not
1947 // be ready to bring up a window yet
1948 mTextFilter.setText(filterText);
1949 mTextFilter.setSelection(filterText.length());
1950 if (mAdapter instanceof Filterable) {
1951 // if mPopup is non-null, then onTextChanged will do the filtering
1952 if (mPopup == null) {
1953 Filter f = ((Filterable) mAdapter).getFilter();
1954 f.filter(filterText);
1955 }
1956 // Set filtered to true so we will display the filter window when our main
1957 // window is ready
1958 mFiltered = true;
1959 mDataSetObserver.clearSavedState();
1960 }
1961 }
1962 }
1963
1964 /**
Romain Guy0a637162009-05-29 14:43:54 -07001965 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001966 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001967 */
1968 public CharSequence getTextFilter() {
1969 if (mTextFilterEnabled && mTextFilter != null) {
1970 return mTextFilter.getText();
1971 }
1972 return null;
1973 }
Romain Guy0a637162009-05-29 14:43:54 -07001974
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001975 @Override
1976 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1977 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1978 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001979 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001980 // Data may have changed while we were detached and it's valid
1981 // to change focus while detached. Refresh so we don't die.
1982 mDataChanged = true;
1983 mOldItemCount = mItemCount;
1984 mItemCount = mAdapter.getCount();
1985 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001986 resurrectSelection();
1987 }
1988 }
1989
1990 @Override
1991 public void requestLayout() {
1992 if (!mBlockLayoutRequests && !mInLayout) {
1993 super.requestLayout();
1994 }
1995 }
1996
1997 /**
1998 * The list is empty. Clear everything out.
1999 */
2000 void resetList() {
2001 removeAllViewsInLayout();
2002 mFirstPosition = 0;
2003 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07002004 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002005 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07002006 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002007 mOldSelectedPosition = INVALID_POSITION;
2008 mOldSelectedRowId = INVALID_ROW_ID;
2009 setSelectedPositionInt(INVALID_POSITION);
2010 setNextSelectedPositionInt(INVALID_POSITION);
2011 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002012 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002013 mSelectorRect.setEmpty();
2014 invalidate();
2015 }
2016
2017 @Override
2018 protected int computeVerticalScrollExtent() {
2019 final int count = getChildCount();
2020 if (count > 0) {
2021 if (mSmoothScrollbarEnabled) {
2022 int extent = count * 100;
2023
2024 View view = getChildAt(0);
2025 final int top = view.getTop();
2026 int height = view.getHeight();
2027 if (height > 0) {
2028 extent += (top * 100) / height;
2029 }
2030
2031 view = getChildAt(count - 1);
2032 final int bottom = view.getBottom();
2033 height = view.getHeight();
2034 if (height > 0) {
2035 extent -= ((bottom - getHeight()) * 100) / height;
2036 }
2037
2038 return extent;
2039 } else {
2040 return 1;
2041 }
2042 }
2043 return 0;
2044 }
2045
2046 @Override
2047 protected int computeVerticalScrollOffset() {
2048 final int firstPosition = mFirstPosition;
2049 final int childCount = getChildCount();
2050 if (firstPosition >= 0 && childCount > 0) {
2051 if (mSmoothScrollbarEnabled) {
2052 final View view = getChildAt(0);
2053 final int top = view.getTop();
2054 int height = view.getHeight();
2055 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002056 return Math.max(firstPosition * 100 - (top * 100) / height +
2057 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002058 }
2059 } else {
2060 int index;
2061 final int count = mItemCount;
2062 if (firstPosition == 0) {
2063 index = 0;
2064 } else if (firstPosition + childCount == count) {
2065 index = count;
2066 } else {
2067 index = firstPosition + childCount / 2;
2068 }
2069 return (int) (firstPosition + childCount * (index / (float) count));
2070 }
2071 }
2072 return 0;
2073 }
2074
2075 @Override
2076 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002077 int result;
2078 if (mSmoothScrollbarEnabled) {
2079 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002080 if (mScrollY != 0) {
2081 // Compensate for overscroll
2082 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2083 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002084 } else {
2085 result = mItemCount;
2086 }
2087 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002088 }
2089
2090 @Override
2091 protected float getTopFadingEdgeStrength() {
2092 final int count = getChildCount();
2093 final float fadeEdge = super.getTopFadingEdgeStrength();
2094 if (count == 0) {
2095 return fadeEdge;
2096 } else {
2097 if (mFirstPosition > 0) {
2098 return 1.0f;
2099 }
2100
2101 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002102 final float fadeLength = getVerticalFadingEdgeLength();
2103 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002104 }
2105 }
2106
2107 @Override
2108 protected float getBottomFadingEdgeStrength() {
2109 final int count = getChildCount();
2110 final float fadeEdge = super.getBottomFadingEdgeStrength();
2111 if (count == 0) {
2112 return fadeEdge;
2113 } else {
2114 if (mFirstPosition + count - 1 < mItemCount - 1) {
2115 return 1.0f;
2116 }
2117
2118 final int bottom = getChildAt(count - 1).getBottom();
2119 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002120 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002121 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002122 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002123 }
2124 }
2125
2126 @Override
2127 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2128 if (mSelector == null) {
2129 useDefaultSelector();
2130 }
2131 final Rect listPadding = mListPadding;
2132 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2133 listPadding.top = mSelectionTopPadding + mPaddingTop;
2134 listPadding.right = mSelectionRightPadding + mPaddingRight;
2135 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002136
2137 // Check if our previous measured size was at a point where we should scroll later.
2138 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2139 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002140 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002141 final View lastChild = getChildAt(childCount - 1);
2142 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002143 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002144 lastBottom <= listBottom;
2145 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002146 }
2147
Romain Guyd6a463a2009-05-21 23:10:10 -07002148 /**
2149 * Subclasses should NOT override this method but
2150 * {@link #layoutChildren()} instead.
2151 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002152 @Override
2153 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2154 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002155
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002156 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002157
Alan Viverette4b95cc72014-01-14 16:54:02 -08002158 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002159 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002160 for (int i = 0; i < childCount; i++) {
2161 getChildAt(i).forceLayout();
2162 }
2163 mRecycler.markChildrenDirty();
2164 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002166 layoutChildren();
Adam Powell637d3372010-08-25 14:37:03 -07002167
2168 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002169
2170 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2171 if (mFastScroll != null) {
2172 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2173 }
Phil Weavera9d976f2016-11-01 09:55:24 -07002174 mInLayout = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002175 }
2176
2177 /**
2178 * @hide
2179 */
2180 @Override
2181 protected boolean setFrame(int left, int top, int right, int bottom) {
2182 final boolean changed = super.setFrame(left, top, right, bottom);
2183
Romain Guyd6a463a2009-05-21 23:10:10 -07002184 if (changed) {
2185 // Reposition the popup when the frame has changed. This includes
2186 // translating the widget, not just changing its dimension. The
2187 // filter popup needs to follow the widget.
2188 final boolean visible = getWindowVisibility() == View.VISIBLE;
2189 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2190 positionPopup();
2191 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002192 }
2193
2194 return changed;
2195 }
2196
Romain Guyd6a463a2009-05-21 23:10:10 -07002197 /**
2198 * Subclasses must override this method to layout their children.
2199 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002200 protected void layoutChildren() {
2201 }
2202
Alan Viverette5d565fa2013-10-30 11:09:03 -07002203 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002204 * @param focusedView view that holds accessibility focus
2205 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002206 * child contains accessibility focus
2207 */
Alan Viverette3e141622014-02-18 17:05:13 -08002208 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002209 ViewParent viewParent = focusedView.getParent();
2210 while ((viewParent instanceof View) && (viewParent != this)) {
2211 focusedView = (View) viewParent;
2212 viewParent = viewParent.getParent();
2213 }
2214
2215 if (!(viewParent instanceof View)) {
2216 return null;
2217 }
2218
2219 return focusedView;
2220 }
2221
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002222 void updateScrollIndicators() {
2223 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002224 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002225 }
2226
2227 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002228 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002229 }
2230 }
2231
Alan Viverette947a9692014-09-25 12:43:47 -07002232 private boolean canScrollUp() {
2233 boolean canScrollUp;
2234 // 0th element is not visible
2235 canScrollUp = mFirstPosition > 0;
2236
2237 // ... Or top of 0th element is not visible
2238 if (!canScrollUp) {
2239 if (getChildCount() > 0) {
2240 View child = getChildAt(0);
2241 canScrollUp = child.getTop() < mListPadding.top;
2242 }
2243 }
2244
2245 return canScrollUp;
2246 }
2247
2248 private boolean canScrollDown() {
2249 boolean canScrollDown;
2250 int count = getChildCount();
2251
2252 // Last item is not visible
2253 canScrollDown = (mFirstPosition + count) < mItemCount;
2254
2255 // ... Or bottom of the last element is not visible
2256 if (!canScrollDown && count > 0) {
2257 View child = getChildAt(count - 1);
2258 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2259 }
2260
2261 return canScrollDown;
2262 }
2263
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002264 @Override
2265 @ViewDebug.ExportedProperty
2266 public View getSelectedView() {
2267 if (mItemCount > 0 && mSelectedPosition >= 0) {
2268 return getChildAt(mSelectedPosition - mFirstPosition);
2269 } else {
2270 return null;
2271 }
2272 }
2273
2274 /**
2275 * List padding is the maximum of the normal view's padding and the padding of the selector.
2276 *
2277 * @see android.view.View#getPaddingTop()
2278 * @see #getSelector()
2279 *
2280 * @return The top list padding.
2281 */
2282 public int getListPaddingTop() {
2283 return mListPadding.top;
2284 }
2285
2286 /**
2287 * List padding is the maximum of the normal view's padding and the padding of the selector.
2288 *
2289 * @see android.view.View#getPaddingBottom()
2290 * @see #getSelector()
2291 *
2292 * @return The bottom list padding.
2293 */
2294 public int getListPaddingBottom() {
2295 return mListPadding.bottom;
2296 }
2297
2298 /**
2299 * List padding is the maximum of the normal view's padding and the padding of the selector.
2300 *
2301 * @see android.view.View#getPaddingLeft()
2302 * @see #getSelector()
2303 *
2304 * @return The left list padding.
2305 */
2306 public int getListPaddingLeft() {
2307 return mListPadding.left;
2308 }
2309
2310 /**
2311 * List padding is the maximum of the normal view's padding and the padding of the selector.
2312 *
2313 * @see android.view.View#getPaddingRight()
2314 * @see #getSelector()
2315 *
2316 * @return The right list padding.
2317 */
2318 public int getListPaddingRight() {
2319 return mListPadding.right;
2320 }
2321
2322 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002323 * Gets a view and have it show the data associated with the specified
2324 * position. This is called when we have already discovered that the view
2325 * is not available for reuse in the recycle bin. The only choices left are
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002326 * converting an old view or making a new one.
2327 *
Alan Viverette26489e12016-07-07 16:39:27 -04002328 * @param position the position to display
2329 * @param outMetadata an array of at least 1 boolean where the first entry
2330 * will be set {@code true} if the view is currently
2331 * attached to the window, {@code false} otherwise (e.g.
2332 * newly-inflated or remained scrap for multiple layout
2333 * passes)
Mindy Pereira4e30d892010-11-24 15:32:39 -08002334 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002335 * @return A view displaying the data associated with the specified position
2336 */
Alan Viverette26489e12016-07-07 16:39:27 -04002337 View obtainView(int position, boolean[] outMetadata) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002338 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2339
Alan Viverette26489e12016-07-07 16:39:27 -04002340 outMetadata[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002341
Alan Viverette59511502013-12-09 13:49:25 -08002342 // Check whether we have a transient state view. Attempt to re-bind the
2343 // data and discard the view if we fail.
2344 final View transientView = mRecycler.getTransientStateView(position);
2345 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002346 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2347
2348 // If the view type hasn't changed, attempt to re-bind the data.
2349 if (params.viewType == mAdapter.getItemViewType(position)) {
2350 final View updatedView = mAdapter.getView(position, transientView, this);
2351
2352 // If we failed to re-bind the data, scrap the obtained view.
2353 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002354 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002355 mRecycler.addScrapView(updatedView, position);
2356 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002357 }
2358
Alan Viverette26489e12016-07-07 16:39:27 -04002359 outMetadata[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002360
2361 // Finish the temporary detach started in addScrapView().
2362 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002363 return transientView;
2364 }
2365
2366 final View scrapView = mRecycler.getScrapView(position);
2367 final View child = mAdapter.getView(position, scrapView, this);
2368 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002369 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002370 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002371 mRecycler.addScrapView(scrapView, position);
Alan Viverette26489e12016-07-07 16:39:27 -04002372 } else if (child.isTemporarilyDetached()) {
2373 outMetadata[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002374
Alan Viverette26489e12016-07-07 16:39:27 -04002375 // Finish the temporary detach started in addScrapView().
2376 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002377 }
Alan Viverette59511502013-12-09 13:49:25 -08002378 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002379
Alan Viverette59511502013-12-09 13:49:25 -08002380 if (mCacheColorHint != 0) {
2381 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2382 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002383
Alan Viverette59511502013-12-09 13:49:25 -08002384 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2385 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002386 }
2387
Alan Viverettee6be9c782014-02-26 18:16:36 -08002388 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002389
alanvc1d7e772012-05-08 14:47:24 -07002390 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2391 if (mAccessibilityDelegate == null) {
2392 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2393 }
alanvb72fe7a2012-08-27 16:44:25 -07002394 if (child.getAccessibilityDelegate() == null) {
2395 child.setAccessibilityDelegate(mAccessibilityDelegate);
2396 }
alanvc1d7e772012-05-08 14:47:24 -07002397 }
2398
Romain Guy5fade8c2013-07-10 16:36:18 -07002399 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2400
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002401 return child;
2402 }
2403
Alan Viverettee6be9c782014-02-26 18:16:36 -08002404 private void setItemViewLayoutParams(View child, int position) {
2405 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2406 LayoutParams lp;
2407 if (vlp == null) {
2408 lp = (LayoutParams) generateDefaultLayoutParams();
2409 } else if (!checkLayoutParams(vlp)) {
2410 lp = (LayoutParams) generateLayoutParams(vlp);
2411 } else {
2412 lp = (LayoutParams) vlp;
2413 }
2414
2415 if (mAdapterHasStableIds) {
2416 lp.itemId = mAdapter.getItemId(position);
2417 }
2418 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002419 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002420 if (lp != vlp) {
2421 child.setLayoutParams(lp);
2422 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002423 }
2424
alanvc1d7e772012-05-08 14:47:24 -07002425 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2426 @Override
2427 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2428 super.onInitializeAccessibilityNodeInfo(host, info);
2429
2430 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002431 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002432 }
2433
2434 @Override
2435 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002436 if (super.performAccessibilityAction(host, action, arguments)) {
2437 return true;
2438 }
2439
alanvc1d7e772012-05-08 14:47:24 -07002440 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002441 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002442 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002443 return false;
2444 }
2445
Alan Viverette92539d52015-09-14 10:49:25 -04002446 if (position >= mAdapter.getCount()) {
2447 // The position is no longer valid, likely due to a data set
2448 // change. We could fail here for all data set changes, since
2449 // there is a chance that the data bound to the view may no
2450 // longer exist at the same position within the adapter, but
2451 // it's more consistent with the standard touch interaction to
2452 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002453 return false;
2454 }
2455
Alan Viverette92539d52015-09-14 10:49:25 -04002456 final boolean isItemEnabled;
2457 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2458 if (lp instanceof AbsListView.LayoutParams) {
2459 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2460 } else {
2461 isItemEnabled = false;
2462 }
2463
2464 if (!isEnabled() || !isItemEnabled) {
2465 // Cannot perform actions on disabled items.
2466 return false;
2467 }
alanvc1d7e772012-05-08 14:47:24 -07002468
2469 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002470 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2471 if (getSelectedItemPosition() == position) {
2472 setSelection(INVALID_POSITION);
2473 return true;
2474 }
2475 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002476 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002477 if (getSelectedItemPosition() != position) {
2478 setSelection(position);
2479 return true;
2480 }
2481 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002482 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002483 if (isItemClickable(host)) {
2484 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002485 return performItemClick(host, position, id);
2486 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002487 } return false;
2488 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2489 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002490 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002491 return performLongPress(host, position, id);
2492 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002493 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002494 }
2495
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002496 return false;
alanvc1d7e772012-05-08 14:47:24 -07002497 }
2498 }
2499
Alan Viverette5b2081d2013-08-28 10:43:07 -07002500 /**
2501 * Initializes an {@link AccessibilityNodeInfo} with information about a
2502 * particular item in the list.
2503 *
2504 * @param view View representing the list item.
2505 * @param position Position of the list item within the adapter.
2506 * @param info Node info to populate.
2507 */
2508 public void onInitializeAccessibilityNodeInfoForItem(
2509 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002510 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002511 // The item doesn't exist, so there's not much we can do here.
2512 return;
2513 }
2514
Alan Viverette92539d52015-09-14 10:49:25 -04002515 final boolean isItemEnabled;
2516 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2517 if (lp instanceof AbsListView.LayoutParams) {
2518 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2519 } else {
2520 isItemEnabled = false;
2521 }
2522
2523 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002524 info.setEnabled(false);
2525 return;
2526 }
2527
2528 if (position == getSelectedItemPosition()) {
2529 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002530 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002531 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002532 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002533 }
2534
Alan Viverette92539d52015-09-14 10:49:25 -04002535 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002536 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002537 info.setClickable(true);
2538 }
2539
2540 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002541 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002542 info.setLongClickable(true);
2543 }
2544 }
2545
Alan Viverette92539d52015-09-14 10:49:25 -04002546 private boolean isItemClickable(View view) {
Adam Powell0f552f42017-02-03 11:50:42 -08002547 return !view.hasExplicitFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002548 }
2549
Alan Viverettede399392014-05-01 17:20:55 -07002550 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002551 * Positions the selector in a way that mimics touch.
2552 */
2553 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002554 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002555 }
2556
2557 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002558 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002559 */
2560 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002561 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002562 final Rect bounds = mSelectorRect;
2563 final float x = bounds.exactCenterX();
2564 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002565 positionSelector(position, sel, true, x, y);
2566 } else {
2567 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002568 }
2569 }
2570
Dianne Hackborn079e2352010-10-18 17:02:43 -07002571 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002572 positionSelector(position, sel, false, -1, -1);
2573 }
2574
2575 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2576 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002577 if (position != INVALID_POSITION) {
2578 mSelectorPosition = position;
2579 }
2580
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002581 final Rect selectorRect = mSelectorRect;
2582 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002583 if (sel instanceof SelectionBoundsAdjuster) {
2584 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2585 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002586
2587 // Adjust for selection padding.
2588 selectorRect.left -= mSelectionLeftPadding;
2589 selectorRect.top -= mSelectionTopPadding;
2590 selectorRect.right += mSelectionRightPadding;
2591 selectorRect.bottom += mSelectionBottomPadding;
2592
Alan Viverettea19ab342015-05-18 13:20:52 -07002593 // Update the child enabled state prior to updating the selector.
2594 final boolean isChildViewEnabled = sel.isEnabled();
2595 if (mIsChildViewEnabled != isChildViewEnabled) {
2596 mIsChildViewEnabled = isChildViewEnabled;
2597 }
2598
2599 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002600 final Drawable selector = mSelector;
2601 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002602 if (positionChanged) {
2603 // Wipe out the current selector state so that we can start
2604 // over in the new position with a fresh state.
2605 selector.setVisible(false, false);
2606 selector.setState(StateSet.NOTHING);
2607 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002608 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002609 if (positionChanged) {
2610 if (getVisibility() == VISIBLE) {
2611 selector.setVisible(true, false);
2612 }
Chet Haase2167b112014-12-19 16:37:18 -08002613 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002614 }
2615 if (manageHotspot) {
2616 selector.setHotspot(x, y);
2617 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002618 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002619 }
2620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002621 @Override
2622 protected void dispatchDraw(Canvas canvas) {
2623 int saveCount = 0;
2624 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2625 if (clipToPadding) {
2626 saveCount = canvas.save();
2627 final int scrollX = mScrollX;
2628 final int scrollY = mScrollY;
2629 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2630 scrollX + mRight - mLeft - mPaddingRight,
2631 scrollY + mBottom - mTop - mPaddingBottom);
2632 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2633 }
2634
2635 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2636 if (!drawSelectorOnTop) {
2637 drawSelector(canvas);
2638 }
2639
2640 super.dispatchDraw(canvas);
2641
2642 if (drawSelectorOnTop) {
2643 drawSelector(canvas);
2644 }
2645
2646 if (clipToPadding) {
2647 canvas.restoreToCount(saveCount);
2648 mGroupFlags |= CLIP_TO_PADDING_MASK;
2649 }
2650 }
2651
2652 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002653 protected boolean isPaddingOffsetRequired() {
2654 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2655 }
2656
2657 @Override
2658 protected int getLeftPaddingOffset() {
2659 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2660 }
2661
2662 @Override
2663 protected int getTopPaddingOffset() {
2664 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2665 }
2666
2667 @Override
2668 protected int getRightPaddingOffset() {
2669 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2670 }
2671
2672 @Override
2673 protected int getBottomPaddingOffset() {
2674 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2675 }
2676
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002677 /**
2678 * @hide
2679 */
2680 @Override
2681 protected void internalSetPadding(int left, int top, int right, int bottom) {
2682 super.internalSetPadding(left, top, right, bottom);
2683 if (isLayoutRequested()) {
2684 handleBoundsChange();
2685 }
2686 }
2687
Adam Powell20232d02010-12-08 21:08:53 -08002688 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002689 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002690 handleBoundsChange();
Alan Viverette8636ace2013-10-31 15:41:31 -07002691 if (mFastScroll != null) {
2692 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002693 }
2694 }
2695
2696 /**
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002697 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2698 * and force layouts all children that don't have exact measure specs.
2699 * <p>
2700 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2701 * fail to relayout them properly to accommodate for new bounds.
2702 */
2703 void handleBoundsChange() {
Phil Weavera9d976f2016-11-01 09:55:24 -07002704 if (mInLayout) {
2705 return;
2706 }
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002707 final int childCount = getChildCount();
2708 if (childCount > 0) {
2709 mDataChanged = true;
2710 rememberSyncState();
2711 for (int i = 0; i < childCount; i++) {
2712 final View child = getChildAt(i);
2713 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2714 // force layout child unless it has exact specs
2715 if (lp == null || lp.width < 1 || lp.height < 1) {
2716 child.forceLayout();
2717 }
2718 }
2719 }
2720 }
2721
2722 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002723 * @return True if the current touch mode requires that we draw the selector in the pressed
2724 * state.
2725 */
2726 boolean touchModeDrawsInPressedState() {
2727 // FIXME use isPressed for this
2728 switch (mTouchMode) {
2729 case TOUCH_MODE_TAP:
2730 case TOUCH_MODE_DONE_WAITING:
2731 return true;
2732 default:
2733 return false;
2734 }
2735 }
2736
2737 /**
2738 * Indicates whether this view is in a state where the selector should be drawn. This will
2739 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2740 * the pressed state for an item.
2741 *
2742 * @return True if the selector should be shown
2743 */
2744 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002745 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002746 }
2747
2748 private void drawSelector(Canvas canvas) {
Evan Rosky8e5bd812018-01-22 09:36:41 -08002749 if (shouldDrawSelector()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002750 final Drawable selector = mSelector;
2751 selector.setBounds(mSelectorRect);
2752 selector.draw(canvas);
2753 }
2754 }
2755
2756 /**
Evan Rosky8e5bd812018-01-22 09:36:41 -08002757 * @hide
2758 */
2759 @TestApi
2760 public final boolean shouldDrawSelector() {
2761 return !mSelectorRect.isEmpty();
2762 }
2763
2764 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002765 * Controls whether the selection highlight drawable should be drawn on top of the item or
2766 * behind it.
2767 *
2768 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2769 * is false.
2770 *
2771 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2772 */
2773 public void setDrawSelectorOnTop(boolean onTop) {
2774 mDrawSelectorOnTop = onTop;
2775 }
2776
2777 /**
2778 * Set a Drawable that should be used to highlight the currently selected item.
2779 *
2780 * @param resID A Drawable resource to use as the selection highlight.
2781 *
2782 * @attr ref android.R.styleable#AbsListView_listSelector
2783 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002784 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002785 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002786 }
2787
2788 public void setSelector(Drawable sel) {
2789 if (mSelector != null) {
2790 mSelector.setCallback(null);
2791 unscheduleDrawable(mSelector);
2792 }
2793 mSelector = sel;
2794 Rect padding = new Rect();
2795 sel.getPadding(padding);
2796 mSelectionLeftPadding = padding.left;
2797 mSelectionTopPadding = padding.top;
2798 mSelectionRightPadding = padding.right;
2799 mSelectionBottomPadding = padding.bottom;
2800 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002801 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002802 }
2803
2804 /**
2805 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2806 * selection in the list.
2807 *
2808 * @return the drawable used to display the selector
2809 */
2810 public Drawable getSelector() {
2811 return mSelector;
2812 }
2813
2814 /**
2815 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2816 * this is a long press.
2817 */
2818 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002819 if (!isEnabled() || !isClickable()) {
2820 return;
2821 }
2822
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002823 Drawable selector = mSelector;
2824 Rect selectorRect = mSelectorRect;
2825 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002826 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002827
2828 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2829
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002830 if (v != null) {
Adam Powell0f552f42017-02-03 11:50:42 -08002831 if (v.hasExplicitFocusable()) return;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002832 v.setPressed(true);
2833 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002834 setPressed(true);
2835
2836 final boolean longClickable = isLongClickable();
2837 Drawable d = selector.getCurrent();
2838 if (d != null && d instanceof TransitionDrawable) {
2839 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002840 ((TransitionDrawable) d).startTransition(
2841 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002842 } else {
2843 ((TransitionDrawable) d).resetTransition();
2844 }
2845 }
2846 if (longClickable && !mDataChanged) {
2847 if (mPendingCheckForKeyLongPress == null) {
2848 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2849 }
2850 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2851 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2852 }
2853 }
2854 }
2855
2856 public void setScrollIndicators(View up, View down) {
2857 mScrollUp = up;
2858 mScrollDown = down;
2859 }
2860
Dianne Hackborn079e2352010-10-18 17:02:43 -07002861 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002862 final Drawable selector = mSelector;
2863 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002864 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002865 if (selector.setState(getDrawableStateForSelector())) {
2866 invalidateDrawable(selector);
2867 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002868 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002869 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002870 }
2871 }
2872 }
2873
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002874 @Override
2875 protected void drawableStateChanged() {
2876 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002877 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002878 }
2879
Alan Viverettef723c832015-02-03 16:31:46 -08002880 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002881 // If the child view is enabled then do the default behavior.
2882 if (mIsChildViewEnabled) {
2883 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002884 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002885 }
2886
2887 // The selector uses this View's drawable state. The selected child view
2888 // is disabled, so we need to remove the enabled state from the drawable
2889 // states.
2890 final int enabledState = ENABLED_STATE_SET[0];
2891
Alan Viverettef723c832015-02-03 16:31:46 -08002892 // If we don't have any extra space, it will return one of the static
2893 // state arrays, and clearing the enabled state on those arrays is a
2894 // bad thing! If we specify we need extra space, it will create+copy
2895 // into a new array that is safely mutable.
2896 final int[] state = onCreateDrawableState(1);
2897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002898 int enabledPos = -1;
2899 for (int i = state.length - 1; i >= 0; i--) {
2900 if (state[i] == enabledState) {
2901 enabledPos = i;
2902 break;
2903 }
2904 }
2905
2906 // Remove the enabled state
2907 if (enabledPos >= 0) {
2908 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2909 state.length - enabledPos - 1);
2910 }
Romain Guy0a637162009-05-29 14:43:54 -07002911
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002912 return state;
2913 }
2914
2915 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -05002916 public boolean verifyDrawable(@NonNull Drawable dr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002917 return mSelector == dr || super.verifyDrawable(dr);
2918 }
2919
2920 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002921 public void jumpDrawablesToCurrentState() {
2922 super.jumpDrawablesToCurrentState();
2923 if (mSelector != null) mSelector.jumpToCurrentState();
2924 }
2925
2926 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 protected void onAttachedToWindow() {
2928 super.onAttachedToWindow();
2929
2930 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002931 treeObserver.addOnTouchModeChangeListener(this);
2932 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2933 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002934 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002935
Romain Guy82afc7b2010-05-13 11:52:37 -07002936 if (mAdapter != null && mDataSetObserver == null) {
2937 mDataSetObserver = new AdapterDataSetObserver();
2938 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002939
2940 // Data may have changed while we were detached. Refresh.
2941 mDataChanged = true;
2942 mOldItemCount = mItemCount;
2943 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002944 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002945 }
2946
2947 @Override
2948 protected void onDetachedFromWindow() {
2949 super.onDetachedFromWindow();
2950
Alan Viverette462c2172014-02-24 12:24:11 -08002951 mIsDetaching = true;
2952
Romain Guy1f7f3c32009-07-22 11:25:42 -07002953 // Dismiss the popup in case onSaveInstanceState() was not invoked
2954 dismissPopup();
2955
Romain Guy21875052010-01-06 18:48:08 -08002956 // Detach any view left in the scrap heap
2957 mRecycler.clear();
2958
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002959 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002960 treeObserver.removeOnTouchModeChangeListener(this);
2961 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002962 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002963 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002964 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002965
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002966 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002967 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2968 mDataSetObserver = null;
2969 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002970
2971 if (mScrollStrictSpan != null) {
2972 mScrollStrictSpan.finish();
2973 mScrollStrictSpan = null;
2974 }
2975
2976 if (mFlingStrictSpan != null) {
2977 mFlingStrictSpan.finish();
2978 mFlingStrictSpan = null;
2979 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002980
2981 if (mFlingRunnable != null) {
2982 removeCallbacks(mFlingRunnable);
2983 }
2984
2985 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002986 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002987 }
2988
2989 if (mClearScrollingCache != null) {
2990 removeCallbacks(mClearScrollingCache);
2991 }
2992
2993 if (mPerformClick != null) {
2994 removeCallbacks(mPerformClick);
2995 }
2996
2997 if (mTouchModeReset != null) {
2998 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002999 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003000 }
Alan Viverette462c2172014-02-24 12:24:11 -08003001
3002 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003003 }
3004
3005 @Override
3006 public void onWindowFocusChanged(boolean hasWindowFocus) {
3007 super.onWindowFocusChanged(hasWindowFocus);
3008
3009 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3010
3011 if (!hasWindowFocus) {
3012 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08003013 if (mFlingRunnable != null) {
3014 removeCallbacks(mFlingRunnable);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003015 // let the fling runnable report its new state which
Mark Wagner670dd812010-01-13 16:17:47 -08003016 // should be idle
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003017 mFlingRunnable.mSuppressIdleStateChangeCall = false;
Mark Wagner670dd812010-01-13 16:17:47 -08003018 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003019 if (mPositionScroller != null) {
3020 mPositionScroller.stop();
3021 }
Adam Powell45803472010-01-25 15:10:44 -08003022 if (mScrollY != 0) {
3023 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003024 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003025 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08003026 invalidate();
3027 }
Mark Wagner670dd812010-01-13 16:17:47 -08003028 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003029 // Always hide the type filter
3030 dismissPopup();
3031
3032 if (touchMode == TOUCH_MODE_OFF) {
3033 // Remember the last selected element
3034 mResurrectToPosition = mSelectedPosition;
3035 }
3036 } else {
Adam Powell97566042010-03-09 15:34:09 -08003037 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003038 // Show the type filter only if a filter is in effect
3039 showPopup();
3040 }
3041
3042 // If we changed touch mode since the last time we had focus
3043 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3044 // If we come back in trackball mode, we bring the selection back
3045 if (touchMode == TOUCH_MODE_OFF) {
3046 // This will trigger a layout
3047 resurrectSelection();
3048
3049 // If we come back in touch mode, then we want to hide the selector
3050 } else {
3051 hideSelector();
3052 mLayoutMode = LAYOUT_NORMAL;
3053 layoutChildren();
3054 }
3055 }
3056 }
3057
3058 mLastTouchMode = touchMode;
3059 }
3060
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003061 @Override
3062 public void onRtlPropertiesChanged(int layoutDirection) {
3063 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003064 if (mFastScroll != null) {
3065 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003066 }
3067 }
3068
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003069 /**
3070 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3071 * methods knows the view, position and ID of the item that received the
3072 * long press.
3073 *
3074 * @param view The view that received the long press.
3075 * @param position The position of the item that received the long press.
3076 * @param id The ID of the item that received the long press.
3077 * @return The extra information that should be returned by
3078 * {@link #getContextMenuInfo()}.
3079 */
3080 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3081 return new AdapterContextMenuInfo(view, position, id);
3082 }
3083
Adam Powell14874662013-07-18 19:42:41 -07003084 @Override
3085 public void onCancelPendingInputEvents() {
3086 super.onCancelPendingInputEvents();
3087 if (mPerformClick != null) {
3088 removeCallbacks(mPerformClick);
3089 }
3090 if (mPendingCheckForTap != null) {
3091 removeCallbacks(mPendingCheckForTap);
3092 }
3093 if (mPendingCheckForLongPress != null) {
3094 removeCallbacks(mPendingCheckForLongPress);
3095 }
3096 if (mPendingCheckForKeyLongPress != null) {
3097 removeCallbacks(mPendingCheckForKeyLongPress);
3098 }
3099 }
3100
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003101 /**
3102 * A base class for Runnables that will check that their view is still attached to
3103 * the original window as when the Runnable was created.
3104 *
3105 */
3106 private class WindowRunnnable {
3107 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003108
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003109 public void rememberWindowAttachCount() {
3110 mOriginalAttachCount = getWindowAttachCount();
3111 }
Romain Guy0a637162009-05-29 14:43:54 -07003112
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003113 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003114 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003115 }
3116 }
Romain Guy0a637162009-05-29 14:43:54 -07003117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003118 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003119 int mClickMotionPosition;
3120
Alan Viverette8fa327a2013-05-31 14:53:13 -07003121 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003122 public void run() {
3123 // The data has changed since we posted this action in the event queue,
3124 // bail out before bad things happen
3125 if (mDataChanged) return;
3126
Adam Powell005c0a42010-03-30 16:26:36 -07003127 final ListAdapter adapter = mAdapter;
3128 final int motionPosition = mClickMotionPosition;
3129 if (adapter != null && mItemCount > 0 &&
3130 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003131 motionPosition < adapter.getCount() && sameWindow() &&
3132 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003133 final View view = getChildAt(motionPosition - mFirstPosition);
3134 // If there is no view, something bad happened (the view scrolled off the
3135 // screen, etc.) and we should cancel the click
3136 if (view != null) {
3137 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3138 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003139 }
3140 }
3141 }
3142
3143 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003144 private static final int INVALID_COORD = -1;
3145 private float mX = INVALID_COORD;
3146 private float mY = INVALID_COORD;
3147
3148 private void setCoords(float x, float y) {
3149 mX = x;
3150 mY = y;
3151 }
3152
Alan Viverette8fa327a2013-05-31 14:53:13 -07003153 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003154 public void run() {
3155 final int motionPosition = mMotionPosition;
3156 final View child = getChildAt(motionPosition - mFirstPosition);
3157 if (child != null) {
3158 final int longPressPosition = mMotionPosition;
3159 final long longPressId = mAdapter.getItemId(mMotionPosition);
3160
3161 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003162 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003163 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3164 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3165 } else {
3166 handled = performLongPress(child, longPressPosition, longPressId);
3167 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003168 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003170 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003171 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003172 mTouchMode = TOUCH_MODE_REST;
3173 setPressed(false);
3174 child.setPressed(false);
3175 } else {
3176 mTouchMode = TOUCH_MODE_DONE_WAITING;
3177 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003178 }
3179 }
3180 }
Romain Guy0a637162009-05-29 14:43:54 -07003181
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003182 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003183 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003184 public void run() {
3185 if (isPressed() && mSelectedPosition >= 0) {
3186 int index = mSelectedPosition - mFirstPosition;
3187 View v = getChildAt(index);
3188
3189 if (!mDataChanged) {
3190 boolean handled = false;
3191 if (sameWindow()) {
3192 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3193 }
3194 if (handled) {
3195 setPressed(false);
3196 v.setPressed(false);
3197 }
3198 } else {
3199 setPressed(false);
3200 if (v != null) v.setPressed(false);
3201 }
3202 }
3203 }
3204 }
3205
Mady Mellore5561982015-04-14 15:06:40 -07003206 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003207 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003208 final View child = getChildAt(mMotionPosition - mFirstPosition);
3209 if (child != null) {
3210 final int longPressPosition = mMotionPosition;
3211 final long longPressId = mAdapter.getItemId(mMotionPosition);
3212 if (performLongPress(child, longPressPosition, longPressId)) {
3213 mTouchMode = TOUCH_MODE_REST;
3214 setPressed(false);
3215 child.setPressed(false);
3216 return true;
3217 }
3218 }
3219 }
3220 return false;
3221 }
3222
Adam Powell8350f7d2010-07-28 14:27:28 -07003223 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003224 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003225 return performLongPress(
3226 child,
3227 longPressPosition,
3228 longPressId,
3229 CheckForLongPress.INVALID_COORD,
3230 CheckForLongPress.INVALID_COORD);
3231 }
3232
3233 boolean performLongPress(final View child,
3234 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003235 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3236 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003237 if (mChoiceActionMode == null &&
3238 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003239 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003240 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003241 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003242 return true;
3243 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003244
Adam Powellf343e1b2010-08-13 18:27:04 -07003245 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003246 if (mOnItemLongClickListener != null) {
3247 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3248 longPressPosition, longPressId);
3249 }
3250 if (!handled) {
3251 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003252 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3253 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3254 } else {
3255 handled = super.showContextMenuForChild(AbsListView.this);
3256 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003257 }
3258 if (handled) {
3259 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3260 }
3261 return handled;
3262 }
3263
3264 @Override
3265 protected ContextMenuInfo getContextMenuInfo() {
3266 return mContextMenuInfo;
3267 }
3268
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003269 @Override
3270 public boolean showContextMenu() {
3271 return showContextMenuInternal(0, 0, false);
3272 }
3273
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003274 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003275 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003276 return showContextMenuInternal(x, y, true);
3277 }
3278
3279 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003280 final int position = pointToPosition((int)x, (int)y);
3281 if (position != INVALID_POSITION) {
3282 final long id = mAdapter.getItemId(position);
3283 View child = getChildAt(position - mFirstPosition);
3284 if (child != null) {
3285 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003286 if (useOffsets) {
3287 return super.showContextMenuForChild(this, x, y);
3288 } else {
3289 return super.showContextMenuForChild(this);
3290 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003291 }
3292 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003293 if (useOffsets) {
3294 return super.showContextMenu(x, y);
3295 } else {
3296 return super.showContextMenu();
3297 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003298 }
3299
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003300 @Override
3301 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003302 if (isShowingContextMenuWithCoords()) {
3303 return false;
3304 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003305 return showContextMenuForChildInternal(originalView, 0, 0, false);
3306 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003307
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003308 @Override
3309 public boolean showContextMenuForChild(View originalView, float x, float y) {
3310 return showContextMenuForChildInternal(originalView,x, y, true);
3311 }
3312
3313 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3314 boolean useOffsets) {
3315 final int longPressPosition = getPositionForView(originalView);
3316 if (longPressPosition < 0) {
3317 return false;
3318 }
3319
3320 final long longPressId = mAdapter.getItemId(longPressPosition);
3321 boolean handled = false;
3322
3323 if (mOnItemLongClickListener != null) {
3324 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3325 longPressPosition, longPressId);
3326 }
3327
3328 if (!handled) {
3329 final View child = getChildAt(longPressPosition - mFirstPosition);
3330 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3331
3332 if (useOffsets) {
3333 handled = super.showContextMenuForChild(originalView, x, y);
3334 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003335 handled = super.showContextMenuForChild(originalView);
3336 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003337 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003338
3339 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003340 }
3341
3342 @Override
Romain Guydf016072009-08-17 12:51:30 -07003343 public boolean onKeyDown(int keyCode, KeyEvent event) {
3344 return false;
3345 }
3346
3347 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003348 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003349 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003350 if (!isEnabled()) {
3351 return true;
3352 }
Romain Guydf016072009-08-17 12:51:30 -07003353 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003354 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003355 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003356
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003357 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003358 if (view != null) {
3359 performItemClick(view, mSelectedPosition, mSelectedRowId);
3360 view.setPressed(false);
3361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003363 return true;
3364 }
3365 }
3366 return super.onKeyUp(keyCode, event);
3367 }
3368
3369 @Override
3370 protected void dispatchSetPressed(boolean pressed) {
3371 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3372 // get the selector in the right state, but we don't want to press each child.
3373 }
3374
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003375 @Override
3376 public void dispatchDrawableHotspotChanged(float x, float y) {
3377 // Don't dispatch hotspot changes to children. We'll manually handle
3378 // calling drawableHotspotChanged on the correct child.
3379 }
3380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003381 /**
3382 * Maps a point to a position in the list.
3383 *
3384 * @param x X in local coordinate
3385 * @param y Y in local coordinate
3386 * @return The position of the item which contains the specified point, or
3387 * {@link #INVALID_POSITION} if the point does not intersect an item.
3388 */
3389 public int pointToPosition(int x, int y) {
3390 Rect frame = mTouchFrame;
3391 if (frame == null) {
3392 mTouchFrame = new Rect();
3393 frame = mTouchFrame;
3394 }
3395
3396 final int count = getChildCount();
3397 for (int i = count - 1; i >= 0; i--) {
3398 final View child = getChildAt(i);
3399 if (child.getVisibility() == View.VISIBLE) {
3400 child.getHitRect(frame);
3401 if (frame.contains(x, y)) {
3402 return mFirstPosition + i;
3403 }
3404 }
3405 }
3406 return INVALID_POSITION;
3407 }
3408
3409
3410 /**
3411 * Maps a point to a the rowId of the item which intersects that point.
3412 *
3413 * @param x X in local coordinate
3414 * @param y Y in local coordinate
3415 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3416 * if the point does not intersect an item.
3417 */
3418 public long pointToRowId(int x, int y) {
3419 int position = pointToPosition(x, y);
3420 if (position >= 0) {
3421 return mAdapter.getItemId(position);
3422 }
3423 return INVALID_ROW_ID;
3424 }
3425
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003426 private final class CheckForTap implements Runnable {
3427 float x;
3428 float y;
3429
Alan Viverette8fa327a2013-05-31 14:53:13 -07003430 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003431 public void run() {
3432 if (mTouchMode == TOUCH_MODE_DOWN) {
3433 mTouchMode = TOUCH_MODE_TAP;
3434 final View child = getChildAt(mMotionPosition - mFirstPosition);
Adam Powell0f552f42017-02-03 11:50:42 -08003435 if (child != null && !child.hasExplicitFocusable()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003436 mLayoutMode = LAYOUT_NORMAL;
3437
3438 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003439 final float[] point = mTmpPoint;
3440 point[0] = x;
3441 point[1] = y;
3442 transformPointToViewLocal(point, child);
3443 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003444 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003445 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003446 layoutChildren();
3447 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003448 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003449
3450 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3451 final boolean longClickable = isLongClickable();
3452
3453 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003454 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003455 if (d != null && d instanceof TransitionDrawable) {
3456 if (longClickable) {
3457 ((TransitionDrawable) d).startTransition(longPressTimeout);
3458 } else {
3459 ((TransitionDrawable) d).resetTransition();
3460 }
3461 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003462 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003463 }
3464
3465 if (longClickable) {
3466 if (mPendingCheckForLongPress == null) {
3467 mPendingCheckForLongPress = new CheckForLongPress();
3468 }
Oren Blasberged391262015-09-01 12:12:51 -07003469 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003470 mPendingCheckForLongPress.rememberWindowAttachCount();
3471 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3472 } else {
3473 mTouchMode = TOUCH_MODE_DONE_WAITING;
3474 }
3475 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003476 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003477 }
3478 }
3479 }
3480 }
3481 }
3482
Adam Powellc501db9f2014-05-08 12:50:10 -07003483 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003484 // Check if we have moved far enough that it looks more like a
3485 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003486 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003487 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003488 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003489 if ((overscroll || distance > mTouchSlop) &&
3490 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003491 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003492 if (overscroll) {
3493 mTouchMode = TOUCH_MODE_OVERSCROLL;
3494 mMotionCorrection = 0;
3495 } else {
3496 mTouchMode = TOUCH_MODE_SCROLL;
3497 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3498 }
Alan Viverette74ded292013-06-03 15:34:11 -07003499 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003500 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003501 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003502 if (motionView != null) {
3503 motionView.setPressed(false);
3504 }
3505 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3506 // Time to start stealing events! Once we've stolen them, don't let anyone
3507 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003508 final ViewParent parent = getParent();
3509 if (parent != null) {
3510 parent.requestDisallowInterceptTouchEvent(true);
3511 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003512 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003513 return true;
3514 }
3515
3516 return false;
3517 }
3518
Adam Powellc501db9f2014-05-08 12:50:10 -07003519 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003520 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003521 int scrollOffsetCorrection = 0;
3522 int scrollConsumedCorrection = 0;
3523 if (mLastY == Integer.MIN_VALUE) {
3524 rawDeltaY -= mMotionCorrection;
3525 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003526 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3527 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003528 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003529 scrollOffsetCorrection = -mScrollOffset[1];
3530 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003531 if (vtev != null) {
3532 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003533 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003534 }
3535 }
Yorke Lee43943d82014-05-08 10:15:20 -07003536 final int deltaY = rawDeltaY;
3537 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003538 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003539 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003540
3541 if (mTouchMode == TOUCH_MODE_SCROLL) {
3542 if (PROFILE_SCROLLING) {
3543 if (!mScrollProfilingStarted) {
3544 Debug.startMethodTracing("AbsListViewScroll");
3545 mScrollProfilingStarted = true;
3546 }
3547 }
3548
3549 if (mScrollStrictSpan == null) {
3550 // If it's non-null, we're already in a scroll.
3551 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3552 }
3553
3554 if (y != mLastY) {
3555 // We may be here after stopping a fling and continuing to scroll.
3556 // If so, we haven't disallowed intercepting touch events yet.
3557 // Make sure that we do so in case we're in a parent that can intercept.
3558 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3559 Math.abs(rawDeltaY) > mTouchSlop) {
3560 final ViewParent parent = getParent();
3561 if (parent != null) {
3562 parent.requestDisallowInterceptTouchEvent(true);
3563 }
3564 }
3565
3566 final int motionIndex;
3567 if (mMotionPosition >= 0) {
3568 motionIndex = mMotionPosition - mFirstPosition;
3569 } else {
3570 // If we don't have a motion position that we can reliably track,
3571 // pick something in the middle to make a best guess at things below.
3572 motionIndex = getChildCount() / 2;
3573 }
3574
3575 int motionViewPrevTop = 0;
3576 View motionView = this.getChildAt(motionIndex);
3577 if (motionView != null) {
3578 motionViewPrevTop = motionView.getTop();
3579 }
3580
3581 // No need to do all this work if we're not going to move anyway
3582 boolean atEdge = false;
3583 if (incrementalDeltaY != 0) {
3584 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3585 }
3586
3587 // Check to see if we have bumped into the scroll limit
3588 motionView = this.getChildAt(motionIndex);
3589 if (motionView != null) {
3590 // Check if the top of the motion view is where it is
3591 // supposed to be
3592 final int motionViewRealTop = motionView.getTop();
3593 if (atEdge) {
3594 // Apply overscroll
3595
3596 int overscroll = -incrementalDeltaY -
3597 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003598 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3599 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003600 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003601 if (vtev != null) {
3602 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003603 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003604 }
Adam Powell96d62af2014-05-02 10:04:38 -07003605 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003606 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3607 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3608
3609 if (atOverscrollEdge && mVelocityTracker != null) {
3610 // Don't allow overfling if we're at the edge
3611 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003612 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003613
Adam Powell96d62af2014-05-02 10:04:38 -07003614 final int overscrollMode = getOverScrollMode();
3615 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3616 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3617 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003618 if (!atOverscrollEdge) {
3619 mDirection = 0; // Reset when entering overscroll.
3620 mTouchMode = TOUCH_MODE_OVERSCROLL;
3621 }
3622 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003623 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003624 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003625 if (!mEdgeGlowBottom.isFinished()) {
3626 mEdgeGlowBottom.onRelease();
3627 }
Doris Liuf36c0612015-06-04 11:11:14 -07003628 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003629 } else if (incrementalDeltaY < 0) {
3630 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3631 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003632 if (!mEdgeGlowTop.isFinished()) {
3633 mEdgeGlowTop.onRelease();
3634 }
Doris Liuf36c0612015-06-04 11:11:14 -07003635 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003636 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003637 }
3638 }
3639 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003640 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003641 }
Yorke Lee43943d82014-05-08 10:15:20 -07003642 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003643 }
3644 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3645 if (y != mLastY) {
3646 final int oldScroll = mScrollY;
3647 final int newScroll = oldScroll - incrementalDeltaY;
3648 int newDirection = y > mLastY ? 1 : -1;
3649
3650 if (mDirection == 0) {
3651 mDirection = newDirection;
3652 }
3653
3654 int overScrollDistance = -incrementalDeltaY;
3655 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3656 overScrollDistance = -oldScroll;
3657 incrementalDeltaY += overScrollDistance;
3658 } else {
3659 incrementalDeltaY = 0;
3660 }
3661
3662 if (overScrollDistance != 0) {
3663 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3664 0, mOverscrollDistance, true);
3665 final int overscrollMode = getOverScrollMode();
3666 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3667 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3668 !contentFits())) {
3669 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003670 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3671 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003672 if (!mEdgeGlowBottom.isFinished()) {
3673 mEdgeGlowBottom.onRelease();
3674 }
Doris Liuf36c0612015-06-04 11:11:14 -07003675 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003676 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003677 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3678 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003679 if (!mEdgeGlowTop.isFinished()) {
3680 mEdgeGlowTop.onRelease();
3681 }
Doris Liuf36c0612015-06-04 11:11:14 -07003682 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003683 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003684 }
3685 }
3686
3687 if (incrementalDeltaY != 0) {
3688 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003689 if (mScrollY != 0) {
3690 mScrollY = 0;
3691 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003692 }
3693
Romain Guy9d849a22012-03-14 16:41:42 -07003694 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3695
Jeff Brown78f6e632011-09-09 17:15:31 -07003696 mTouchMode = TOUCH_MODE_SCROLL;
3697
3698 // We did not scroll the full amount. Treat this essentially like the
3699 // start of a new touch scroll
3700 final int motionPosition = findClosestMotionRow(y);
3701
3702 mMotionCorrection = 0;
3703 View motionView = getChildAt(motionPosition - mFirstPosition);
3704 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003705 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003706 mMotionPosition = motionPosition;
3707 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003708 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003709 mDirection = newDirection;
3710 }
3711 }
3712 }
3713
Doris Liuf36c0612015-06-04 11:11:14 -07003714 private void invalidateTopGlow() {
3715 if (mEdgeGlowTop == null) {
3716 return;
3717 }
3718 final boolean clipToPadding = getClipToPadding();
3719 final int top = clipToPadding ? mPaddingTop : 0;
3720 final int left = clipToPadding ? mPaddingLeft : 0;
3721 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3722 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3723 }
3724
3725 private void invalidateBottomGlow() {
3726 if (mEdgeGlowBottom == null) {
3727 return;
3728 }
3729 final boolean clipToPadding = getClipToPadding();
3730 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3731 final int left = clipToPadding ? mPaddingLeft : 0;
3732 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3733 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3734 }
3735
Alan Viverette8fa327a2013-05-31 14:53:13 -07003736 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003737 public void onTouchModeChanged(boolean isInTouchMode) {
3738 if (isInTouchMode) {
3739 // Get rid of the selection when we enter touch mode
3740 hideSelector();
3741 // Layout, but only if we already have done so previously.
3742 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3743 // state.)
3744 if (getHeight() > 0 && getChildCount() > 0) {
3745 // We do not lose focus initiating a touch (since AbsListView is focusable in
3746 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003747 layoutChildren();
3748 }
Jeff Brown1e209462011-07-14 22:19:19 -07003749 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003750 } else {
3751 int touchMode = mTouchMode;
3752 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3753 if (mFlingRunnable != null) {
3754 mFlingRunnable.endFling();
3755 }
Adam Powell40322522011-01-12 21:58:20 -08003756 if (mPositionScroller != null) {
3757 mPositionScroller.stop();
3758 }
Adam Powell637d3372010-08-25 14:37:03 -07003759
3760 if (mScrollY != 0) {
3761 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003762 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003763 finishGlows();
3764 invalidate();
3765 }
3766 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003767 }
3768 }
3769
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003770 /** @hide */
3771 @Override
3772 protected boolean handleScrollBarDragging(MotionEvent event) {
3773 // Doesn't support normal scroll bar dragging. Use FastScroller.
3774 return false;
3775 }
3776
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003777 @Override
3778 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003779 if (!isEnabled()) {
3780 // A disabled view that is clickable still consumes the touch
3781 // events, it just doesn't respond to them.
3782 return isClickable() || isLongClickable();
3783 }
3784
Adam Powell1fa179ef2012-04-12 15:01:40 -07003785 if (mPositionScroller != null) {
3786 mPositionScroller.stop();
3787 }
3788
Alan Viverette462c2172014-02-24 12:24:11 -08003789 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003790 // Something isn't right.
3791 // Since we rely on being attached to get data set change notifications,
3792 // don't risk doing anything where we might try to resync and find things
3793 // in a bogus state.
3794 return false;
3795 }
3796
Adam Powell96d62af2014-05-02 10:04:38 -07003797 startNestedScroll(SCROLL_AXIS_VERTICAL);
3798
Alan Viverettefb99ba82015-05-01 10:10:15 -07003799 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3800 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003801 }
Romain Guy82f34952009-05-24 18:40:45 -07003802
Michael Jurka13451a42011-08-22 15:54:21 -07003803 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003804 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003805
Alan Viverette8fa327a2013-05-31 14:53:13 -07003806 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003807 if (actionMasked == MotionEvent.ACTION_DOWN) {
3808 mNestedYOffset = 0;
3809 }
3810 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003811 switch (actionMasked) {
3812 case MotionEvent.ACTION_DOWN: {
3813 onTouchDown(ev);
3814 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003815 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003816
Alan Viverette8fa327a2013-05-31 14:53:13 -07003817 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003818 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003819 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003820 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003821
3822 case MotionEvent.ACTION_UP: {
3823 onTouchUp(ev);
3824 break;
3825 }
3826
3827 case MotionEvent.ACTION_CANCEL: {
3828 onTouchCancel();
3829 break;
3830 }
3831
3832 case MotionEvent.ACTION_POINTER_UP: {
3833 onSecondaryPointerUp(ev);
3834 final int x = mMotionX;
3835 final int y = mMotionY;
3836 final int motionPosition = pointToPosition(x, y);
3837 if (motionPosition >= 0) {
3838 // Remember where the motion event started
3839 final View child = getChildAt(motionPosition - mFirstPosition);
3840 mMotionViewOriginalTop = child.getTop();
3841 mMotionPosition = motionPosition;
3842 }
3843 mLastY = y;
3844 break;
3845 }
3846
3847 case MotionEvent.ACTION_POINTER_DOWN: {
3848 // New pointers take over dragging duties
3849 final int index = ev.getActionIndex();
3850 final int id = ev.getPointerId(index);
3851 final int x = (int) ev.getX(index);
3852 final int y = (int) ev.getY(index);
3853 mMotionCorrection = 0;
3854 mActivePointerId = id;
3855 mMotionX = x;
3856 mMotionY = y;
3857 final int motionPosition = pointToPosition(x, y);
3858 if (motionPosition >= 0) {
3859 // Remember where the motion event started
3860 final View child = getChildAt(motionPosition - mFirstPosition);
3861 mMotionViewOriginalTop = child.getTop();
3862 mMotionPosition = motionPosition;
3863 }
3864 mLastY = y;
3865 break;
3866 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003867 }
3868
Adam Powell96d62af2014-05-02 10:04:38 -07003869 if (mVelocityTracker != null) {
3870 mVelocityTracker.addMovement(vtev);
3871 }
3872 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003873 return true;
3874 }
Romain Guy0a637162009-05-29 14:43:54 -07003875
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003876 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003877 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003878 mActivePointerId = ev.getPointerId(0);
Evan Rosky837ae0d2017-10-26 12:50:33 -07003879 hideSelector();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003880
3881 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3882 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003883 mFlingRunnable.endFling();
3884 if (mPositionScroller != null) {
3885 mPositionScroller.stop();
3886 }
3887 mTouchMode = TOUCH_MODE_OVERSCROLL;
3888 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003889 mMotionY = (int) ev.getY();
3890 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003891 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003892 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003893 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003894 final int x = (int) ev.getX();
3895 final int y = (int) ev.getY();
3896 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003897
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003898 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003899 if (mTouchMode == TOUCH_MODE_FLING) {
3900 // Stopped a fling. It is a scroll.
3901 createScrollingCache();
3902 mTouchMode = TOUCH_MODE_SCROLL;
3903 mMotionCorrection = 0;
3904 motionPosition = findMotionRow(y);
3905 mFlingRunnable.flywheelTouch();
3906 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3907 // User clicked on an actual view (and was not stopping a
3908 // fling). It might be a click or a scroll. Assume it is a
3909 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003910 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003911
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003912 // FIXME Debounce
3913 if (mPendingCheckForTap == null) {
3914 mPendingCheckForTap = new CheckForTap();
3915 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003916
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003917 mPendingCheckForTap.x = ev.getX();
3918 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003919 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003920 }
3921 }
3922
3923 if (motionPosition >= 0) {
3924 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003925 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003926 mMotionViewOriginalTop = v.getTop();
3927 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003928
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003929 mMotionX = x;
3930 mMotionY = y;
3931 mMotionPosition = motionPosition;
3932 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003933 }
3934
Alan Viveretteb339cc52013-08-12 13:29:15 -07003935 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003936 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003937 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003938 }
3939 }
3940
Adam Powell96d62af2014-05-02 10:04:38 -07003941 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003942 if (mHasPerformedLongPress) {
3943 // Consume all move events following a successful long press.
3944 return;
3945 }
3946
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003947 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3948 if (pointerIndex == -1) {
3949 pointerIndex = 0;
3950 mActivePointerId = ev.getPointerId(pointerIndex);
3951 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003952
3953 if (mDataChanged) {
3954 // Re-sync everything if data has been changed
3955 // since the scroll operation can query the adapter.
3956 layoutChildren();
3957 }
3958
Alan Viverette8fa327a2013-05-31 14:53:13 -07003959 final int y = (int) ev.getY(pointerIndex);
3960
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003961 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003962 case TOUCH_MODE_DOWN:
3963 case TOUCH_MODE_TAP:
3964 case TOUCH_MODE_DONE_WAITING:
3965 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003966 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003967 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003968 break;
3969 }
3970 // Otherwise, check containment within list bounds. If we're
3971 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003972 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003973 final float x = ev.getX(pointerIndex);
3974 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003975 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003976 if (motionView != null) {
3977 motionView.setPressed(false);
3978 }
3979 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3980 mPendingCheckForTap : mPendingCheckForLongPress);
3981 mTouchMode = TOUCH_MODE_DONE_WAITING;
3982 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003983 } else if (motionView != null) {
3984 // Still within bounds, update the hotspot.
3985 final float[] point = mTmpPoint;
3986 point[0] = x;
3987 point[1] = y;
3988 transformPointToViewLocal(point, motionView);
3989 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003990 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003991 break;
3992 case TOUCH_MODE_SCROLL:
3993 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003994 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003995 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003996 }
3997 }
3998
3999 private void onTouchUp(MotionEvent ev) {
4000 switch (mTouchMode) {
4001 case TOUCH_MODE_DOWN:
4002 case TOUCH_MODE_TAP:
4003 case TOUCH_MODE_DONE_WAITING:
4004 final int motionPosition = mMotionPosition;
4005 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07004006 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004007 if (mTouchMode != TOUCH_MODE_DOWN) {
4008 child.setPressed(false);
4009 }
4010
Alan Viverette74ded292013-06-03 15:34:11 -07004011 final float x = ev.getX();
4012 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
Adam Powell0f552f42017-02-03 11:50:42 -08004013 if (inList && !child.hasExplicitFocusable()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004014 if (mPerformClick == null) {
4015 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004016 }
Alan Viverette74ded292013-06-03 15:34:11 -07004017
4018 final AbsListView.PerformClick performClick = mPerformClick;
4019 performClick.mClickMotionPosition = motionPosition;
4020 performClick.rememberWindowAttachCount();
4021
4022 mResurrectToPosition = motionPosition;
4023
4024 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4025 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4026 mPendingCheckForTap : mPendingCheckForLongPress);
4027 mLayoutMode = LAYOUT_NORMAL;
4028 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4029 mTouchMode = TOUCH_MODE_TAP;
4030 setSelectedPositionInt(mMotionPosition);
4031 layoutChildren();
4032 child.setPressed(true);
4033 positionSelector(mMotionPosition, child);
4034 setPressed(true);
4035 if (mSelector != null) {
4036 Drawable d = mSelector.getCurrent();
4037 if (d != null && d instanceof TransitionDrawable) {
4038 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004039 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004040 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004041 }
Alan Viverette74ded292013-06-03 15:34:11 -07004042 if (mTouchModeReset != null) {
4043 removeCallbacks(mTouchModeReset);
4044 }
4045 mTouchModeReset = new Runnable() {
4046 @Override
4047 public void run() {
4048 mTouchModeReset = null;
4049 mTouchMode = TOUCH_MODE_REST;
4050 child.setPressed(false);
4051 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004052 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004053 performClick.run();
4054 }
4055 }
4056 };
4057 postDelayed(mTouchModeReset,
4058 ViewConfiguration.getPressedStateDuration());
4059 } else {
4060 mTouchMode = TOUCH_MODE_REST;
4061 updateSelectorState();
4062 }
4063 return;
4064 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4065 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004066 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004067 }
4068 }
4069 mTouchMode = TOUCH_MODE_REST;
4070 updateSelectorState();
4071 break;
4072 case TOUCH_MODE_SCROLL:
4073 final int childCount = getChildCount();
4074 if (childCount > 0) {
4075 final int firstChildTop = getChildAt(0).getTop();
4076 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4077 final int contentTop = mListPadding.top;
4078 final int contentBottom = getHeight() - mListPadding.bottom;
4079 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4080 mFirstPosition + childCount < mItemCount &&
4081 lastChildBottom <= getHeight() - contentBottom) {
4082 mTouchMode = TOUCH_MODE_REST;
4083 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4084 } else {
4085 final VelocityTracker velocityTracker = mVelocityTracker;
4086 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4087
4088 final int initialVelocity = (int)
4089 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4090 // Fling if we have enough velocity and we aren't at a boundary.
4091 // Since we can potentially overfling more than we can overscroll, don't
4092 // allow the weird behavior where you can scroll to a boundary then
4093 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004094 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4095 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004096 !((mFirstPosition == 0 &&
4097 firstChildTop == contentTop - mOverscrollDistance) ||
4098 (mFirstPosition + childCount == mItemCount &&
4099 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004100 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4101 if (mFlingRunnable == null) {
4102 mFlingRunnable = new FlingRunnable();
4103 }
4104 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4105 mFlingRunnable.start(-initialVelocity);
4106 dispatchNestedFling(0, -initialVelocity, true);
4107 } else {
4108 mTouchMode = TOUCH_MODE_REST;
4109 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004110 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004111 } else {
4112 mTouchMode = TOUCH_MODE_REST;
4113 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4114 if (mFlingRunnable != null) {
4115 mFlingRunnable.endFling();
4116 }
4117 if (mPositionScroller != null) {
4118 mPositionScroller.stop();
4119 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004120 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004121 dispatchNestedFling(0, -initialVelocity, false);
4122 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004123 }
4124 }
4125 } else {
4126 mTouchMode = TOUCH_MODE_REST;
4127 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4128 }
4129 break;
4130
4131 case TOUCH_MODE_OVERSCROLL:
4132 if (mFlingRunnable == null) {
4133 mFlingRunnable = new FlingRunnable();
4134 }
4135 final VelocityTracker velocityTracker = mVelocityTracker;
4136 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4137 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4138
4139 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4140 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4141 mFlingRunnable.startOverfling(-initialVelocity);
4142 } else {
4143 mFlingRunnable.startSpringback();
4144 }
4145
4146 break;
4147 }
4148
4149 setPressed(false);
4150
4151 if (mEdgeGlowTop != null) {
4152 mEdgeGlowTop.onRelease();
4153 mEdgeGlowBottom.onRelease();
4154 }
4155
4156 // Need to redraw since we probably aren't drawing the selector anymore
4157 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004158 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004159 recycleVelocityTracker();
4160
4161 mActivePointerId = INVALID_POINTER;
4162
4163 if (PROFILE_SCROLLING) {
4164 if (mScrollProfilingStarted) {
4165 Debug.stopMethodTracing();
4166 mScrollProfilingStarted = false;
4167 }
4168 }
4169
4170 if (mScrollStrictSpan != null) {
4171 mScrollStrictSpan.finish();
4172 mScrollStrictSpan = null;
4173 }
4174 }
4175
4176 private void onTouchCancel() {
4177 switch (mTouchMode) {
4178 case TOUCH_MODE_OVERSCROLL:
4179 if (mFlingRunnable == null) {
4180 mFlingRunnable = new FlingRunnable();
4181 }
4182 mFlingRunnable.startSpringback();
4183 break;
4184
4185 case TOUCH_MODE_OVERFLING:
4186 // Do nothing - let it play out.
4187 break;
4188
4189 default:
4190 mTouchMode = TOUCH_MODE_REST;
4191 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004192 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004193 if (motionView != null) {
4194 motionView.setPressed(false);
4195 }
4196 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004197 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004198 recycleVelocityTracker();
4199 }
4200
4201 if (mEdgeGlowTop != null) {
4202 mEdgeGlowTop.onRelease();
4203 mEdgeGlowBottom.onRelease();
4204 }
4205 mActivePointerId = INVALID_POINTER;
4206 }
4207
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004208 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004209 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4210 if (mScrollY != scrollY) {
4211 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4212 mScrollY = scrollY;
4213 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004214
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004215 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004216 }
Adam Powell637d3372010-08-25 14:37:03 -07004217 }
4218
4219 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004220 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004221 switch (event.getAction()) {
4222 case MotionEvent.ACTION_SCROLL:
4223 final float axisValue;
4224 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4225 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4226 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4227 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4228 } else {
4229 axisValue = 0;
4230 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004231
Aaron Whytef8306522017-03-22 16:30:58 -07004232 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -04004233 if (delta != 0) {
4234 if (!trackMotionScroll(delta, delta)) {
4235 return true;
4236 }
4237 }
4238 break;
4239 case MotionEvent.ACTION_BUTTON_PRESS:
4240 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004241 int actionButton = event.getActionButton();
4242 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4243 || actionButton == MotionEvent.BUTTON_SECONDARY)
4244 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4245 if (performStylusButtonPressAction(event)) {
4246 removeCallbacks(mPendingCheckForLongPress);
4247 removeCallbacks(mPendingCheckForTap);
4248 }
4249 }
Ned Burns20ad0732016-08-18 14:22:57 -04004250 }
4251 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004252 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004253
Jeff Brown33bbfd22011-02-24 20:55:35 -08004254 return super.onGenericMotionEvent(event);
4255 }
4256
Adam Powell4884c642014-08-07 13:52:53 -07004257 /**
4258 * Initiate a fling with the given velocity.
4259 *
4260 * <p>Applications can use this method to manually initiate a fling as if the user
4261 * initiated it via touch interaction.</p>
4262 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004263 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4264 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004265 */
4266 public void fling(int velocityY) {
4267 if (mFlingRunnable == null) {
4268 mFlingRunnable = new FlingRunnable();
4269 }
4270 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004271 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004272 }
4273
Jeff Brown33bbfd22011-02-24 20:55:35 -08004274 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004275 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4276 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4277 }
4278
4279 @Override
4280 public void onNestedScrollAccepted(View child, View target, int axes) {
4281 super.onNestedScrollAccepted(child, target, axes);
4282 startNestedScroll(SCROLL_AXIS_VERTICAL);
4283 }
4284
4285 @Override
4286 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4287 int dxUnconsumed, int dyUnconsumed) {
4288 final int motionIndex = getChildCount() / 2;
4289 final View motionView = getChildAt(motionIndex);
4290 final int oldTop = motionView != null ? motionView.getTop() : 0;
4291 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4292 int myUnconsumed = dyUnconsumed;
4293 int myConsumed = 0;
4294 if (motionView != null) {
4295 myConsumed = motionView.getTop() - oldTop;
4296 myUnconsumed -= myConsumed;
4297 }
4298 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4299 }
4300 }
4301
4302 @Override
4303 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4304 final int childCount = getChildCount();
4305 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4306 Math.abs(velocityY) > mMinimumVelocity) {
4307 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4308 if (mFlingRunnable == null) {
4309 mFlingRunnable = new FlingRunnable();
4310 }
Adam Powell9413b242014-08-06 17:34:24 -07004311 if (!dispatchNestedPreFling(0, velocityY)) {
4312 mFlingRunnable.start((int) velocityY);
4313 }
Adam Powell96d62af2014-05-02 10:04:38 -07004314 return true;
4315 }
4316 return dispatchNestedFling(velocityX, velocityY, consumed);
4317 }
4318
4319 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004320 public void draw(Canvas canvas) {
4321 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004322 if (mEdgeGlowTop != null) {
4323 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004324 final boolean clipToPadding = getClipToPadding();
4325 final int width;
4326 final int height;
4327 final int translateX;
4328 final int translateY;
4329
4330 if (clipToPadding) {
4331 width = getWidth() - mPaddingLeft - mPaddingRight;
4332 height = getHeight() - mPaddingTop - mPaddingBottom;
4333 translateX = mPaddingLeft;
4334 translateY = mPaddingTop;
4335 } else {
4336 width = getWidth();
4337 height = getHeight();
4338 translateX = 0;
4339 translateY = 0;
4340 }
Adam Powell637d3372010-08-25 14:37:03 -07004341 if (!mEdgeGlowTop.isFinished()) {
4342 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004343 canvas.clipRect(translateX, translateY,
4344 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4345 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4346 canvas.translate(translateX, edgeY);
4347 mEdgeGlowTop.setSize(width, height);
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);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08004362 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004363 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004364 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004365 }
4366 canvas.restoreToCount(restoreCount);
4367 }
4368 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004369 }
4370
Michael Jurka13451a42011-08-22 15:54:21 -07004371 private void initOrResetVelocityTracker() {
4372 if (mVelocityTracker == null) {
4373 mVelocityTracker = VelocityTracker.obtain();
4374 } else {
4375 mVelocityTracker.clear();
4376 }
4377 }
4378
4379 private void initVelocityTrackerIfNotExists() {
4380 if (mVelocityTracker == null) {
4381 mVelocityTracker = VelocityTracker.obtain();
4382 }
4383 }
4384
4385 private void recycleVelocityTracker() {
4386 if (mVelocityTracker != null) {
4387 mVelocityTracker.recycle();
4388 mVelocityTracker = null;
4389 }
4390 }
4391
4392 @Override
4393 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4394 if (disallowIntercept) {
4395 recycleVelocityTracker();
4396 }
4397 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4398 }
4399
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004400 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004401 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004402 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004403 return true;
4404 }
4405
4406 return super.onInterceptHoverEvent(event);
4407 }
4408
4409 @Override
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08004410 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4411 if (mFastScroll != null) {
4412 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4413 if (pointerIcon != null) {
4414 return pointerIcon;
4415 }
4416 }
4417 return super.onResolvePointerIcon(event, pointerIndex);
4418 }
4419
4420 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004421 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004422 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004423 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004424
Adam Powell1fa179ef2012-04-12 15:01:40 -07004425 if (mPositionScroller != null) {
4426 mPositionScroller.stop();
4427 }
4428
Alan Viverette462c2172014-02-24 12:24:11 -08004429 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004430 // Something isn't right.
4431 // Since we rely on being attached to get data set change notifications,
4432 // don't risk doing anything where we might try to resync and find things
4433 // in a bogus state.
4434 return false;
4435 }
4436
Alan Viverette8636ace2013-10-31 15:41:31 -07004437 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004438 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004439 }
Romain Guy0a637162009-05-29 14:43:54 -07004440
Adam Powell744beff2014-09-22 09:47:48 -07004441 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004442 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004443 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004444 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4445 mMotionCorrection = 0;
4446 return true;
4447 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004448
Adam Powell4cd47702010-02-25 11:21:14 -08004449 final int x = (int) ev.getX();
4450 final int y = (int) ev.getY();
4451 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004453 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004454 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004455 // User clicked on an actual view (and was not stopping a fling).
4456 // Remember where the motion event started
4457 v = getChildAt(motionPosition - mFirstPosition);
4458 mMotionViewOriginalTop = v.getTop();
4459 mMotionX = x;
4460 mMotionY = y;
4461 mMotionPosition = motionPosition;
4462 mTouchMode = TOUCH_MODE_DOWN;
4463 clearScrollingCache();
4464 }
4465 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004466 initOrResetVelocityTracker();
4467 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004468 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004469 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004470 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004471 return true;
4472 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004473 break;
4474 }
4475
4476 case MotionEvent.ACTION_MOVE: {
4477 switch (mTouchMode) {
4478 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004479 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4480 if (pointerIndex == -1) {
4481 pointerIndex = 0;
4482 mActivePointerId = ev.getPointerId(pointerIndex);
4483 }
Adam Powell4cd47702010-02-25 11:21:14 -08004484 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004485 initVelocityTrackerIfNotExists();
4486 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004487 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004488 return true;
4489 }
4490 break;
4491 }
4492 break;
4493 }
4494
Michael Jurka13451a42011-08-22 15:54:21 -07004495 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004496 case MotionEvent.ACTION_UP: {
4497 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004498 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004499 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004500 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004501 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004502 break;
4503 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004504
Adam Powell4cd47702010-02-25 11:21:14 -08004505 case MotionEvent.ACTION_POINTER_UP: {
4506 onSecondaryPointerUp(ev);
4507 break;
4508 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004509 }
4510
4511 return false;
4512 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004513
Adam Powell4cd47702010-02-25 11:21:14 -08004514 private void onSecondaryPointerUp(MotionEvent ev) {
4515 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4516 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4517 final int pointerId = ev.getPointerId(pointerIndex);
4518 if (pointerId == mActivePointerId) {
4519 // This was our active pointer going up. Choose a new
4520 // active pointer and adjust accordingly.
4521 // TODO: Make this decision more intelligent.
4522 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4523 mMotionX = (int) ev.getX(newPointerIndex);
4524 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004525 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004526 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004527 }
4528 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004529
4530 /**
4531 * {@inheritDoc}
4532 */
4533 @Override
4534 public void addTouchables(ArrayList<View> views) {
4535 final int count = getChildCount();
4536 final int firstPosition = mFirstPosition;
4537 final ListAdapter adapter = mAdapter;
4538
4539 if (adapter == null) {
4540 return;
4541 }
4542
4543 for (int i = 0; i < count; i++) {
4544 final View child = getChildAt(i);
4545 if (adapter.isEnabled(firstPosition + i)) {
4546 views.add(child);
4547 }
4548 child.addTouchables(views);
4549 }
4550 }
4551
4552 /**
4553 * Fires an "on scroll state changed" event to the registered
4554 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4555 * is fired only if the specified state is different from the previously known state.
4556 *
4557 * @param newState The new scroll state.
4558 */
4559 void reportScrollStateChange(int newState) {
4560 if (newState != mLastScrollState) {
4561 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004562 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004563 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004564 }
4565 }
4566 }
4567
4568 /**
4569 * Responsible for fling behavior. Use {@link #start(int)} to
4570 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4571 * A FlingRunnable will keep re-posting itself until the fling is done.
4572 *
4573 */
4574 private class FlingRunnable implements Runnable {
4575 /**
4576 * Tracks the decay of a fling scroll
4577 */
Adam Powell637d3372010-08-25 14:37:03 -07004578 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004579
4580 /**
4581 * Y value reported by mScroller on the previous fling
4582 */
4583 private int mLastFlingY;
4584
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004585 /**
4586 * If true, {@link #endFling()} will not report scroll state change to
4587 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4588 */
4589 private boolean mSuppressIdleStateChangeCall;
4590
Gilles Debunned348bb42010-11-15 12:19:35 -08004591 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004592 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004593 public void run() {
4594 final int activeId = mActivePointerId;
4595 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004596 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004597 if (vt == null || activeId == INVALID_POINTER) {
4598 return;
4599 }
4600
4601 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4602 final float yvel = -vt.getYVelocity(activeId);
4603
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004604 if (Math.abs(yvel) >= mMinimumVelocity
4605 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004606 // Keep the fling alive a little longer
4607 postDelayed(this, FLYWHEEL_TIMEOUT);
4608 } else {
4609 endFling();
4610 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004611 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004612 }
4613 }
4614 };
4615
4616 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4617
Adam Powell79ac3392010-01-28 21:22:20 -08004618 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004619 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004620 }
4621
Adam Powell79ac3392010-01-28 21:22:20 -08004622 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004623 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4624 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004625 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004626 mScroller.fling(0, initialY, 0, initialVelocity,
4627 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4628 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004629 mSuppressIdleStateChangeCall = false;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004630 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004631
4632 if (PROFILE_FLINGING) {
4633 if (!mFlingProfilingStarted) {
4634 Debug.startMethodTracing("AbsListViewFling");
4635 mFlingProfilingStarted = true;
4636 }
4637 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004638
4639 if (mFlingStrictSpan == null) {
4640 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4641 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004642 }
Adam Powell45803472010-01-25 15:10:44 -08004643
Adam Powell637d3372010-08-25 14:37:03 -07004644 void startSpringback() {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004645 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004646 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4647 mTouchMode = TOUCH_MODE_OVERFLING;
4648 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004649 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004650 } else {
4651 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004652 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004653 }
4654 }
4655
4656 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004657 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004658 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4659 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004660 mTouchMode = TOUCH_MODE_OVERFLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004661 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004662 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004663 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004664 }
4665
4666 void edgeReached(int delta) {
4667 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4668 final int overscrollMode = getOverScrollMode();
4669 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4670 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4671 mTouchMode = TOUCH_MODE_OVERFLING;
4672 final int vel = (int) mScroller.getCurrVelocity();
4673 if (delta > 0) {
4674 mEdgeGlowTop.onAbsorb(vel);
4675 } else {
4676 mEdgeGlowBottom.onAbsorb(vel);
4677 }
Adam Powell40322522011-01-12 21:58:20 -08004678 } else {
4679 mTouchMode = TOUCH_MODE_REST;
4680 if (mPositionScroller != null) {
4681 mPositionScroller.stop();
4682 }
Adam Powell637d3372010-08-25 14:37:03 -07004683 }
4684 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004685 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004686 }
4687
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004688 void startScroll(int distance, int duration, boolean linear,
4689 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004690 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4691 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004692 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004693 mScroller.startScroll(0, initialY, 0, distance, duration);
4694 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004695 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004696 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004697 }
4698
Gilles Debunned348bb42010-11-15 12:19:35 -08004699 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004700 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004701
Adam Powell79ac3392010-01-28 21:22:20 -08004702 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004703 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004704
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004705 if (!mSuppressIdleStateChangeCall) {
4706 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4707 }
Romain Guy21317d12010-10-12 13:32:31 -07004708 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004709 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004710
4711 if (mFlingStrictSpan != null) {
4712 mFlingStrictSpan.finish();
4713 mFlingStrictSpan = null;
4714 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004715 }
4716
4717 void flywheelTouch() {
4718 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004719 }
4720
Alan Viverette8fa327a2013-05-31 14:53:13 -07004721 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004722 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004723 switch (mTouchMode) {
4724 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004725 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004726 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004727
Gilles Debunned348bb42010-11-15 12:19:35 -08004728 case TOUCH_MODE_SCROLL:
4729 if (mScroller.isFinished()) {
4730 return;
4731 }
4732 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004733 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004734 if (mDataChanged) {
4735 layoutChildren();
4736 }
4737
Adam Powell79ac3392010-01-28 21:22:20 -08004738 if (mItemCount == 0 || getChildCount() == 0) {
4739 endFling();
4740 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004741 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004742
Adam Powell637d3372010-08-25 14:37:03 -07004743 final OverScroller scroller = mScroller;
4744 boolean more = scroller.computeScrollOffset();
4745 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004746
Adam Powell637d3372010-08-25 14:37:03 -07004747 // Flip sign to convert finger direction to list items direction
4748 // (e.g. finger moving down means list is moving towards the top)
4749 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004750
Adam Powell637d3372010-08-25 14:37:03 -07004751 // Pretend that each frame of a fling scroll is a touch scroll
4752 if (delta > 0) {
4753 // List is moving towards the top. Use first view as mMotionPosition
4754 mMotionPosition = mFirstPosition;
4755 final View firstView = getChildAt(0);
4756 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004757
Adam Powell637d3372010-08-25 14:37:03 -07004758 // Don't fling more than 1 screen
4759 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4760 } else {
4761 // List is moving towards the bottom. Use last view as mMotionPosition
4762 int offsetToLast = getChildCount() - 1;
4763 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004764
Adam Powell637d3372010-08-25 14:37:03 -07004765 final View lastView = getChildAt(offsetToLast);
4766 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004767
Adam Powell637d3372010-08-25 14:37:03 -07004768 // Don't fling more than 1 screen
4769 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4770 }
Adam Powell79ac3392010-01-28 21:22:20 -08004771
Adam Powell637d3372010-08-25 14:37:03 -07004772 // Check to see if we have bumped into the scroll limit
4773 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4774 int oldTop = 0;
4775 if (motionView != null) {
4776 oldTop = motionView.getTop();
4777 }
Adam Powell9d32d242010-03-29 16:02:07 -07004778
Adam Powell637d3372010-08-25 14:37:03 -07004779 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004780 final boolean atEdge = trackMotionScroll(delta, delta);
4781 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004782 if (atEnd) {
4783 if (motionView != null) {
4784 // Tweak the scroll for how far we overshot
4785 int overshoot = -(delta - (motionView.getTop() - oldTop));
4786 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4787 0, mOverflingDistance, false);
4788 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004789 if (more) {
4790 edgeReached(delta);
4791 }
Adam Powell637d3372010-08-25 14:37:03 -07004792 break;
4793 }
4794
4795 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004796 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004797 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004798 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004799 } else {
4800 endFling();
4801
4802 if (PROFILE_FLINGING) {
4803 if (mFlingProfilingStarted) {
4804 Debug.stopMethodTracing();
4805 mFlingProfilingStarted = false;
4806 }
4807
4808 if (mFlingStrictSpan != null) {
4809 mFlingStrictSpan.finish();
4810 mFlingStrictSpan = null;
4811 }
Adam Powell79ac3392010-01-28 21:22:20 -08004812 }
4813 }
Adam Powell637d3372010-08-25 14:37:03 -07004814 break;
4815 }
4816
4817 case TOUCH_MODE_OVERFLING: {
4818 final OverScroller scroller = mScroller;
4819 if (scroller.computeScrollOffset()) {
4820 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004821 final int currY = scroller.getCurrY();
4822 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004823 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4824 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004825 final boolean crossDown = scrollY <= 0 && currY > 0;
4826 final boolean crossUp = scrollY >= 0 && currY < 0;
4827 if (crossDown || crossUp) {
4828 int velocity = (int) scroller.getCurrVelocity();
4829 if (crossUp) velocity = -velocity;
4830
4831 // Don't flywheel from this; we're just continuing things.
4832 scroller.abortAnimation();
4833 start(velocity);
4834 } else {
4835 startSpringback();
4836 }
Adam Powell637d3372010-08-25 14:37:03 -07004837 } else {
4838 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004839 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004840 }
4841 } else {
4842 endFling();
4843 }
4844 break;
4845 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004846 }
4847 }
4848 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004849
Adam Powell45803472010-01-25 15:10:44 -08004850 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004851 * The amount of friction applied to flings. The default value
4852 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004853 */
4854 public void setFriction(float friction) {
4855 if (mFlingRunnable == null) {
4856 mFlingRunnable = new FlingRunnable();
4857 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004858 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004859 }
Romain Guy21317d12010-10-12 13:32:31 -07004860
4861 /**
4862 * Sets a scale factor for the fling velocity. The initial scale
4863 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004864 *
Romain Guy21317d12010-10-12 13:32:31 -07004865 * @param scale The scale factor to multiply the velocity by.
4866 */
4867 public void setVelocityScale(float scale) {
4868 mVelocityScale = scale;
4869 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004870
Romain Guy4bede9e2010-10-11 19:36:59 -07004871 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004872 * Override this for better control over position scrolling.
4873 */
4874 AbsPositionScroller createPositionScroller() {
4875 return new PositionScroller();
4876 }
4877
4878 /**
Adam Powell45803472010-01-25 15:10:44 -08004879 * Smoothly scroll to the specified adapter position. The view will
4880 * scroll such that the indicated position is displayed.
4881 * @param position Scroll to this adapter position.
4882 */
4883 public void smoothScrollToPosition(int position) {
4884 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004885 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004886 }
4887 mPositionScroller.start(position);
4888 }
Erik322171b2010-10-13 15:46:00 -07004889
4890 /**
4891 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004892 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004893 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4894 * the first or last item beyond the boundaries of the list) it will get as close
4895 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4896 *
4897 * @param position Position to scroll to
4898 * @param offset Desired distance in pixels of <code>position</code> from the top
4899 * of the view when scrolling is finished
4900 * @param duration Number of milliseconds to use for the scroll
4901 */
4902 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4903 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004904 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004905 }
4906 mPositionScroller.startWithOffset(position, offset, duration);
4907 }
4908
Adam Powell45803472010-01-25 15:10:44 -08004909 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004910 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004911 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004912 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4913 * the first or last item beyond the boundaries of the list) it will get as close
4914 * as possible.
4915 *
4916 * @param position Position to scroll to
4917 * @param offset Desired distance in pixels of <code>position</code> from the top
4918 * of the view when scrolling is finished
4919 */
4920 public void smoothScrollToPositionFromTop(int position, int offset) {
4921 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004922 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004923 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004924 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004925 }
4926
4927 /**
Adam Powell45803472010-01-25 15:10:44 -08004928 * Smoothly scroll to the specified adapter position. The view will
4929 * scroll such that the indicated position is displayed, but it will
4930 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004931 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004932 *
Adam Powell45803472010-01-25 15:10:44 -08004933 * @param position Scroll to this adapter position.
4934 * @param boundPosition Do not scroll if it would move this adapter
4935 * position out of view.
4936 */
4937 public void smoothScrollToPosition(int position, int boundPosition) {
4938 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004939 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004940 }
4941 mPositionScroller.start(position, boundPosition);
4942 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004943
Adam Powell45803472010-01-25 15:10:44 -08004944 /**
4945 * Smoothly scroll by distance pixels over duration milliseconds.
4946 * @param distance Distance to scroll in pixels.
4947 * @param duration Duration of the scroll animation in milliseconds.
4948 */
4949 public void smoothScrollBy(int distance, int duration) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004950 smoothScrollBy(distance, duration, false, false);
Adam Powell0b8acd82012-04-25 20:29:23 -07004951 }
4952
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004953 void smoothScrollBy(int distance, int duration, boolean linear,
4954 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004955 if (mFlingRunnable == null) {
4956 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004957 }
Adam Powell40322522011-01-12 21:58:20 -08004958
Marc Blank299acb52010-10-21 11:03:53 -07004959 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004960 final int firstPos = mFirstPosition;
4961 final int childCount = getChildCount();
4962 final int lastPos = firstPos + childCount;
4963 final int topLimit = getPaddingTop();
4964 final int bottomLimit = getHeight() - getPaddingBottom();
4965
Adam Powell79303752011-01-13 22:06:49 -08004966 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004967 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004968 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004969 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4970 mFlingRunnable.endFling();
4971 if (mPositionScroller != null) {
4972 mPositionScroller.stop();
4973 }
4974 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004975 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004976 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
Marc Blank299acb52010-10-21 11:03:53 -07004977 }
Adam Powell45803472010-01-25 15:10:44 -08004978 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004979
Winson Chung499cb9f2010-07-16 11:18:17 -07004980 /**
4981 * Allows RemoteViews to scroll relatively to a position.
4982 */
4983 void smoothScrollByOffset(int position) {
4984 int index = -1;
4985 if (position < 0) {
4986 index = getFirstVisiblePosition();
4987 } else if (position > 0) {
4988 index = getLastVisiblePosition();
4989 }
4990
4991 if (index > -1) {
4992 View child = getChildAt(index - getFirstVisiblePosition());
4993 if (child != null) {
4994 Rect visibleRect = new Rect();
4995 if (child.getGlobalVisibleRect(visibleRect)) {
4996 // the child is partially visible
4997 int childRectArea = child.getWidth() * child.getHeight();
4998 int visibleRectArea = visibleRect.width() * visibleRect.height();
4999 float visibleArea = (visibleRectArea / (float) childRectArea);
5000 final float visibleThreshold = 0.75f;
5001 if ((position < 0) && (visibleArea < visibleThreshold)) {
5002 // the top index is not perceivably visible so offset
5003 // to account for showing that top index as well
5004 ++index;
5005 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
5006 // the bottom index is not perceivably visible so offset
5007 // to account for showing that bottom index as well
5008 --index;
5009 }
5010 }
5011 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5012 }
5013 }
5014 }
5015
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005016 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005017 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005018 setChildrenDrawnWithCacheEnabled(true);
5019 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08005020 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005021 }
5022 }
5023
5024 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005025 if (!isHardwareAccelerated()) {
5026 if (mClearScrollingCache == null) {
5027 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07005028 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07005029 public void run() {
5030 if (mCachingStarted) {
5031 mCachingStarted = mCachingActive = false;
5032 setChildrenDrawnWithCacheEnabled(false);
5033 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5034 setChildrenDrawingCacheEnabled(false);
5035 }
5036 if (!isAlwaysDrawnWithCacheEnabled()) {
5037 invalidate();
5038 }
Romain Guy6dfed242009-05-11 18:25:05 -07005039 }
5040 }
Romain Guy9d849a22012-03-14 16:41:42 -07005041 };
5042 }
5043 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005044 }
5045 }
5046
5047 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005048 * Scrolls the list items within the view by a specified number of pixels.
5049 *
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005050 * <p>The actual amount of scroll is capped by the list content viewport height
5051 * which is the list height minus top and bottom paddings minus one pixel.</p>
5052 *
Alan Viverette2f3317a2013-08-06 18:19:48 -07005053 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005054 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005055 */
Alan Viveretteba299062013-09-03 16:01:51 -07005056 public void scrollListBy(int y) {
5057 trackMotionScroll(-y, -y);
5058 }
5059
5060 /**
5061 * Check if the items in the list can be scrolled in a certain direction.
5062 *
5063 * @param direction Negative to check scrolling up, positive to check
5064 * scrolling down.
5065 * @return true if the list can be scrolled in the specified direction,
5066 * false otherwise.
5067 * @see #scrollListBy(int)
5068 */
5069 public boolean canScrollList(int direction) {
5070 final int childCount = getChildCount();
5071 if (childCount == 0) {
5072 return false;
5073 }
5074
5075 final int firstPosition = mFirstPosition;
5076 final Rect listPadding = mListPadding;
5077 if (direction > 0) {
5078 final int lastBottom = getChildAt(childCount - 1).getBottom();
5079 final int lastPosition = firstPosition + childCount;
5080 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5081 } else {
5082 final int firstTop = getChildAt(0).getTop();
5083 return firstPosition > 0 || firstTop < listPadding.top;
5084 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005085 }
5086
5087 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005088 * Track a motion scroll
5089 *
5090 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5091 * began. Positive numbers mean the user's finger is moving down the screen.
5092 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005093 * @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 -08005094 */
Adam Powell45803472010-01-25 15:10:44 -08005095 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005096 final int childCount = getChildCount();
5097 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005098 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005099 }
5100
5101 final int firstTop = getChildAt(0).getTop();
5102 final int lastBottom = getChildAt(childCount - 1).getBottom();
5103
5104 final Rect listPadding = mListPadding;
5105
Adam Powellbdccc2d2010-12-14 17:34:27 -08005106 // "effective padding" In this case is the amount of padding that affects
5107 // how much space should not be filled by items. If we don't clip to padding
5108 // there is no effective padding.
5109 int effectivePaddingTop = 0;
5110 int effectivePaddingBottom = 0;
5111 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5112 effectivePaddingTop = listPadding.top;
5113 effectivePaddingBottom = listPadding.bottom;
5114 }
5115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005116 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005117 final int spaceAbove = effectivePaddingTop - firstTop;
5118 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005119 final int spaceBelow = lastBottom - end;
5120
5121 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5122 if (deltaY < 0) {
5123 deltaY = Math.max(-(height - 1), deltaY);
5124 } else {
5125 deltaY = Math.min(height - 1, deltaY);
5126 }
5127
5128 if (incrementalDeltaY < 0) {
5129 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5130 } else {
5131 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5132 }
5133
Adam Powell45803472010-01-25 15:10:44 -08005134 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005135
Adam Powell637d3372010-08-25 14:37:03 -07005136 // Update our guesses for where the first and last views are
5137 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005138 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005139 } else {
5140 mFirstPositionDistanceGuess += incrementalDeltaY;
5141 }
5142 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005143 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005144 } else {
5145 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005146 }
Adam Powell45803472010-01-25 15:10:44 -08005147
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005148 final boolean cannotScrollDown = (firstPosition == 0 &&
5149 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5150 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5151 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005152
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005153 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005154 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005155 }
5156
5157 final boolean down = incrementalDeltaY < 0;
5158
Adam Powell029cfbd2010-03-08 19:03:54 -08005159 final boolean inTouchMode = isInTouchMode();
5160 if (inTouchMode) {
5161 hideSelector();
5162 }
Adam Powell45803472010-01-25 15:10:44 -08005163
5164 final int headerViewsCount = getHeaderViewsCount();
5165 final int footerViewsStart = mItemCount - getFooterViewsCount();
5166
5167 int start = 0;
5168 int count = 0;
5169
5170 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005171 int top = -incrementalDeltaY;
5172 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5173 top += listPadding.top;
5174 }
Adam Powell45803472010-01-25 15:10:44 -08005175 for (int i = 0; i < childCount; i++) {
5176 final View child = getChildAt(i);
5177 if (child.getBottom() >= top) {
5178 break;
5179 } else {
5180 count++;
5181 int position = firstPosition + i;
5182 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005183 // The view will be rebound to new data, clear any
5184 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005185 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005186 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005187 }
5188 }
5189 }
5190 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005191 int bottom = getHeight() - incrementalDeltaY;
5192 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5193 bottom -= listPadding.bottom;
5194 }
Adam Powell45803472010-01-25 15:10:44 -08005195 for (int i = childCount - 1; i >= 0; i--) {
5196 final View child = getChildAt(i);
5197 if (child.getTop() <= bottom) {
5198 break;
5199 } else {
5200 start = i;
5201 count++;
5202 int position = firstPosition + i;
5203 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005204 // The view will be rebound to new data, clear any
5205 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005206 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005207 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005208 }
5209 }
5210 }
5211 }
5212
5213 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5214
5215 mBlockLayoutRequests = true;
5216
5217 if (count > 0) {
5218 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005219 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005220 }
Adam Powell539ee872012-02-03 19:00:49 -08005221
Romain Guy9d849a22012-03-14 16:41:42 -07005222 // invalidate before moving the children to avoid unnecessary invalidate
5223 // calls to bubble up from the children all the way to the top
5224 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005225 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005226 }
5227
Adam Powell45803472010-01-25 15:10:44 -08005228 offsetChildrenTopAndBottom(incrementalDeltaY);
5229
5230 if (down) {
5231 mFirstPosition += count;
5232 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005233
Adam Powell45803472010-01-25 15:10:44 -08005234 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5235 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5236 fillGap(down);
5237 }
5238
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005239 mRecycler.fullyDetachScrapViews();
Evan Rosky837ae0d2017-10-26 12:50:33 -07005240 boolean selectorOnScreen = false;
Adam Powell029cfbd2010-03-08 19:03:54 -08005241 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005242 final int childIndex = mSelectedPosition - mFirstPosition;
5243 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005244 positionSelector(mSelectedPosition, getChildAt(childIndex));
Evan Rosky837ae0d2017-10-26 12:50:33 -07005245 selectorOnScreen = true;
Adam Powell2a20ddd2010-03-11 18:09:59 -08005246 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005247 } else if (mSelectorPosition != INVALID_POSITION) {
5248 final int childIndex = mSelectorPosition - mFirstPosition;
5249 if (childIndex >= 0 && childIndex < getChildCount()) {
Evan Rosky837ae0d2017-10-26 12:50:33 -07005250 positionSelector(mSelectorPosition, getChildAt(childIndex));
5251 selectorOnScreen = true;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005252 }
Evan Rosky837ae0d2017-10-26 12:50:33 -07005253 }
5254 if (!selectorOnScreen) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005255 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005256 }
5257
Adam Powell45803472010-01-25 15:10:44 -08005258 mBlockLayoutRequests = false;
5259
5260 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005261
Adam Powell45803472010-01-25 15:10:44 -08005262 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005263 }
5264
5265 /**
5266 * Returns the number of header views in the list. Header views are special views
5267 * at the top of the list that should not be recycled during a layout.
5268 *
5269 * @return The number of header views, 0 in the default implementation.
5270 */
5271 int getHeaderViewsCount() {
5272 return 0;
5273 }
5274
5275 /**
5276 * Returns the number of footer views in the list. Footer views are special views
5277 * at the bottom of the list that should not be recycled during a layout.
5278 *
5279 * @return The number of footer views, 0 in the default implementation.
5280 */
5281 int getFooterViewsCount() {
5282 return 0;
5283 }
5284
5285 /**
5286 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5287 * remain on screen are shifted and the other ones are discarded. The role of this
5288 * method is to fill the gap thus created by performing a partial layout in the
5289 * empty space.
5290 *
5291 * @param down true if the scroll is going down, false if it is going up
5292 */
5293 abstract void fillGap(boolean down);
5294
5295 void hideSelector() {
5296 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005297 if (mLayoutMode != LAYOUT_SPECIFIC) {
5298 mResurrectToPosition = mSelectedPosition;
5299 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005300 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5301 mResurrectToPosition = mNextSelectedPosition;
5302 }
5303 setSelectedPositionInt(INVALID_POSITION);
5304 setNextSelectedPositionInt(INVALID_POSITION);
5305 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005306 }
5307 }
5308
5309 /**
5310 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5311 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5312 * of items available in the adapter
5313 */
5314 int reconcileSelectedPosition() {
5315 int position = mSelectedPosition;
5316 if (position < 0) {
5317 position = mResurrectToPosition;
5318 }
5319 position = Math.max(0, position);
5320 position = Math.min(position, mItemCount - 1);
5321 return position;
5322 }
5323
5324 /**
5325 * Find the row closest to y. This row will be used as the motion row when scrolling
5326 *
5327 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005328 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005329 */
5330 abstract int findMotionRow(int y);
5331
5332 /**
Adam Powell637d3372010-08-25 14:37:03 -07005333 * Find the row closest to y. This row will be used as the motion row when scrolling.
5334 *
5335 * @param y Where the user touched
5336 * @return The position of the first (or only) item in the row closest to y
5337 */
5338 int findClosestMotionRow(int y) {
5339 final int childCount = getChildCount();
5340 if (childCount == 0) {
5341 return INVALID_POSITION;
5342 }
5343
5344 final int motionRow = findMotionRow(y);
5345 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5346 }
5347
5348 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005349 * Causes all the views to be rebuilt and redrawn.
5350 */
5351 public void invalidateViews() {
5352 mDataChanged = true;
5353 rememberSyncState();
5354 requestLayout();
5355 invalidate();
5356 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005357
Jeff Brown4e6319b2010-12-13 10:36:51 -08005358 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005359 * If there is a selection returns false.
5360 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005361 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005362 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005363 if (mSelectedPosition < 0 && resurrectSelection()) {
5364 updateSelectorState();
5365 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005366 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005367 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005368 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005369
5370 /**
5371 * Makes the item at the supplied position selected.
5372 *
5373 * @param position the position of the new selection
5374 */
5375 abstract void setSelectionInt(int position);
5376
5377 /**
5378 * Attempt to bring the selection back if the user is switching from touch
5379 * to trackball mode
5380 * @return Whether selection was set to something.
5381 */
5382 boolean resurrectSelection() {
5383 final int childCount = getChildCount();
5384
5385 if (childCount <= 0) {
5386 return false;
5387 }
5388
5389 int selectedTop = 0;
5390 int selectedPos;
5391 int childrenTop = mListPadding.top;
5392 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5393 final int firstPosition = mFirstPosition;
5394 final int toPosition = mResurrectToPosition;
5395 boolean down = true;
5396
5397 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5398 selectedPos = toPosition;
5399
5400 final View selected = getChildAt(selectedPos - mFirstPosition);
5401 selectedTop = selected.getTop();
5402 int selectedBottom = selected.getBottom();
5403
5404 // We are scrolled, don't get in the fade
5405 if (selectedTop < childrenTop) {
5406 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5407 } else if (selectedBottom > childrenBottom) {
5408 selectedTop = childrenBottom - selected.getMeasuredHeight()
5409 - getVerticalFadingEdgeLength();
5410 }
5411 } else {
5412 if (toPosition < firstPosition) {
5413 // Default to selecting whatever is first
5414 selectedPos = firstPosition;
5415 for (int i = 0; i < childCount; i++) {
5416 final View v = getChildAt(i);
5417 final int top = v.getTop();
5418
5419 if (i == 0) {
5420 // Remember the position of the first item
5421 selectedTop = top;
5422 // See if we are scrolled at all
5423 if (firstPosition > 0 || top < childrenTop) {
5424 // If we are scrolled, don't select anything that is
5425 // in the fade region
5426 childrenTop += getVerticalFadingEdgeLength();
5427 }
5428 }
5429 if (top >= childrenTop) {
5430 // Found a view whose top is fully visisble
5431 selectedPos = firstPosition + i;
5432 selectedTop = top;
5433 break;
5434 }
5435 }
5436 } else {
5437 final int itemCount = mItemCount;
5438 down = false;
5439 selectedPos = firstPosition + childCount - 1;
5440
5441 for (int i = childCount - 1; i >= 0; i--) {
5442 final View v = getChildAt(i);
5443 final int top = v.getTop();
5444 final int bottom = v.getBottom();
5445
5446 if (i == childCount - 1) {
5447 selectedTop = top;
5448 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5449 childrenBottom -= getVerticalFadingEdgeLength();
5450 }
5451 }
5452
5453 if (bottom <= childrenBottom) {
5454 selectedPos = firstPosition + i;
5455 selectedTop = top;
5456 break;
5457 }
5458 }
5459 }
5460 }
5461
5462 mResurrectToPosition = INVALID_POSITION;
5463 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005464 if (mPositionScroller != null) {
5465 mPositionScroller.stop();
5466 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005467 mTouchMode = TOUCH_MODE_REST;
5468 clearScrollingCache();
5469 mSpecificTop = selectedTop;
5470 selectedPos = lookForSelectablePosition(selectedPos, down);
5471 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5472 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005473 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005474 setSelectionInt(selectedPos);
5475 invokeOnItemScrollListener();
5476 } else {
5477 selectedPos = INVALID_POSITION;
5478 }
5479 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5480
5481 return selectedPos >= 0;
5482 }
5483
Adam Powell14c08042011-10-06 19:46:18 -07005484 void confirmCheckedPositionsById() {
5485 // Clear out the positional check states, we'll rebuild it below from IDs.
5486 mCheckStates.clear();
5487
5488 boolean checkedCountChanged = false;
5489 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5490 final long id = mCheckedIdStates.keyAt(checkedIndex);
5491 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5492
5493 final long lastPosId = mAdapter.getItemId(lastPos);
5494 if (id != lastPosId) {
5495 // Look around to see if the ID is nearby. If not, uncheck it.
5496 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5497 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5498 boolean found = false;
5499 for (int searchPos = start; searchPos < end; searchPos++) {
5500 final long searchId = mAdapter.getItemId(searchPos);
5501 if (id == searchId) {
5502 found = true;
5503 mCheckStates.put(searchPos, true);
5504 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5505 break;
5506 }
5507 }
5508
5509 if (!found) {
5510 mCheckedIdStates.delete(id);
5511 checkedIndex--;
5512 mCheckedItemCount--;
5513 checkedCountChanged = true;
5514 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5515 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5516 lastPos, id, false);
5517 }
5518 }
5519 } else {
5520 mCheckStates.put(lastPos, true);
5521 }
5522 }
5523
5524 if (checkedCountChanged && mChoiceActionMode != null) {
5525 mChoiceActionMode.invalidate();
5526 }
5527 }
5528
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005529 @Override
5530 protected void handleDataChanged() {
5531 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005532 int lastHandledItemCount = mLastHandledItemCount;
5533 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005534
5535 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5536 confirmCheckedPositionsById();
5537 }
5538
Adam Powell539ee872012-02-03 19:00:49 -08005539 // TODO: In the future we can recycle these views based on stable ID instead.
5540 mRecycler.clearTransientStateViews();
5541
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005542 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005543 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005544 int selectablePos;
5545
5546 // Find the row we are supposed to sync to
5547 if (mNeedSync) {
5548 // Update this first, since setNextSelectedPositionInt inspects it
5549 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005550 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005551
Adam Powell07852792010-11-10 16:57:05 -08005552 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005553 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5554 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005555 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5556 if (mForceTranscriptScroll) {
5557 mForceTranscriptScroll = false;
5558 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5559 return;
5560 }
Adam Powell07852792010-11-10 16:57:05 -08005561 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005562 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005563 final View lastChild = getChildAt(childCount - 1);
5564 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005565 if (mFirstPosition + childCount >= lastHandledItemCount &&
5566 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005567 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5568 return;
5569 }
5570 // Something new came in and we didn't scroll; give the user a clue that
5571 // there's something new.
5572 awakenScrollBars();
5573 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005574
5575 switch (mSyncMode) {
5576 case SYNC_SELECTED_POSITION:
5577 if (isInTouchMode()) {
5578 // We saved our state when not in touch mode. (We know this because
5579 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5580 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5581 // adjusting if the available range changed) and return.
5582 mLayoutMode = LAYOUT_SYNC;
5583 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5584
5585 return;
5586 } else {
5587 // See if we can find a position in the new data with the same
5588 // id as the old selection. This will change mSyncPosition.
5589 newPos = findSyncPosition();
5590 if (newPos >= 0) {
5591 // Found it. Now verify that new selection is still selectable
5592 selectablePos = lookForSelectablePosition(newPos, true);
5593 if (selectablePos == newPos) {
5594 // Same row id is selected
5595 mSyncPosition = newPos;
5596
5597 if (mSyncHeight == getHeight()) {
5598 // If we are at the same height as when we saved state, try
5599 // to restore the scroll position too.
5600 mLayoutMode = LAYOUT_SYNC;
5601 } else {
5602 // We are not the same height as when the selection was saved, so
5603 // don't try to restore the exact position
5604 mLayoutMode = LAYOUT_SET_SELECTION;
5605 }
5606
5607 // Restore selection
5608 setNextSelectedPositionInt(newPos);
5609 return;
5610 }
5611 }
5612 }
5613 break;
5614 case SYNC_FIRST_POSITION:
5615 // Leave mSyncPosition as it is -- just pin to available range
5616 mLayoutMode = LAYOUT_SYNC;
5617 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5618
5619 return;
5620 }
5621 }
5622
5623 if (!isInTouchMode()) {
5624 // We couldn't find matching data -- try to use the same position
5625 newPos = getSelectedItemPosition();
5626
5627 // Pin position to the available range
5628 if (newPos >= count) {
5629 newPos = count - 1;
5630 }
5631 if (newPos < 0) {
5632 newPos = 0;
5633 }
5634
5635 // Make sure we select something selectable -- first look down
5636 selectablePos = lookForSelectablePosition(newPos, true);
5637
5638 if (selectablePos >= 0) {
5639 setNextSelectedPositionInt(selectablePos);
5640 return;
5641 } else {
5642 // Looking down didn't work -- try looking up
5643 selectablePos = lookForSelectablePosition(newPos, false);
5644 if (selectablePos >= 0) {
5645 setNextSelectedPositionInt(selectablePos);
5646 return;
5647 }
5648 }
5649 } else {
5650
5651 // We already know where we want to resurrect the selection
5652 if (mResurrectToPosition >= 0) {
5653 return;
5654 }
5655 }
5656
5657 }
5658
5659 // Nothing is selected. Give up and reset everything.
5660 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5661 mSelectedPosition = INVALID_POSITION;
5662 mSelectedRowId = INVALID_ROW_ID;
5663 mNextSelectedPosition = INVALID_POSITION;
5664 mNextSelectedRowId = INVALID_ROW_ID;
5665 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005666 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005667 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005668 checkSelectionChanged();
5669 }
5670
Romain Guy43c9cdf2010-01-27 13:53:55 -08005671 @Override
5672 protected void onDisplayHint(int hint) {
5673 super.onDisplayHint(hint);
5674 switch (hint) {
5675 case INVISIBLE:
5676 if (mPopup != null && mPopup.isShowing()) {
5677 dismissPopup();
5678 }
5679 break;
5680 case VISIBLE:
5681 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5682 showPopup();
5683 }
5684 break;
5685 }
Romain Guy24562482010-02-01 14:56:19 -08005686 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005687 }
5688
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005689 /**
5690 * Removes the filter window
5691 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005692 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005693 if (mPopup != null) {
5694 mPopup.dismiss();
5695 }
5696 }
5697
5698 /**
5699 * Shows the filter window
5700 */
5701 private void showPopup() {
5702 // Make sure we have a window before showing the popup
5703 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005704 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005705 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005706 // Make sure we get focus if we are showing the popup
5707 checkFocus();
5708 }
5709 }
5710
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005711 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005712 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5713 final int[] xy = new int[2];
5714 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005715 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005716 // TODO: And the gravity should be defined in the theme as well
5717 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005718 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005719 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5720 xy[0], bottomGap);
5721 } else {
5722 mPopup.update(xy[0], bottomGap, -1, -1);
5723 }
5724 }
5725
5726 /**
5727 * What is the distance between the source and destination rectangles given the direction of
5728 * focus navigation between them? The direction basically helps figure out more quickly what is
5729 * self evident by the relationship between the rects...
5730 *
5731 * @param source the source rectangle
5732 * @param dest the destination rectangle
5733 * @param direction the direction
5734 * @return the distance between the rectangles
5735 */
5736 static int getDistance(Rect source, Rect dest, int direction) {
5737 int sX, sY; // source x, y
5738 int dX, dY; // dest x, y
5739 switch (direction) {
5740 case View.FOCUS_RIGHT:
5741 sX = source.right;
5742 sY = source.top + source.height() / 2;
5743 dX = dest.left;
5744 dY = dest.top + dest.height() / 2;
5745 break;
5746 case View.FOCUS_DOWN:
5747 sX = source.left + source.width() / 2;
5748 sY = source.bottom;
5749 dX = dest.left + dest.width() / 2;
5750 dY = dest.top;
5751 break;
5752 case View.FOCUS_LEFT:
5753 sX = source.left;
5754 sY = source.top + source.height() / 2;
5755 dX = dest.right;
5756 dY = dest.top + dest.height() / 2;
5757 break;
5758 case View.FOCUS_UP:
5759 sX = source.left + source.width() / 2;
5760 sY = source.top;
5761 dX = dest.left + dest.width() / 2;
5762 dY = dest.bottom;
5763 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005764 case View.FOCUS_FORWARD:
5765 case View.FOCUS_BACKWARD:
5766 sX = source.right + source.width() / 2;
5767 sY = source.top + source.height() / 2;
5768 dX = dest.left + dest.width() / 2;
5769 dY = dest.top + dest.height() / 2;
5770 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005771 default:
5772 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005773 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5774 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005775 }
5776 int deltaX = dX - sX;
5777 int deltaY = dY - sY;
5778 return deltaY * deltaY + deltaX * deltaX;
5779 }
5780
5781 @Override
5782 protected boolean isInFilterMode() {
5783 return mFiltered;
5784 }
5785
5786 /**
5787 * Sends a key to the text filter window
5788 *
5789 * @param keyCode The keycode for the event
5790 * @param event The actual key event
5791 *
5792 * @return True if the text filter handled the event, false otherwise.
5793 */
5794 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005795 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005796 return false;
5797 }
5798
5799 boolean handled = false;
5800 boolean okToSend = true;
5801 switch (keyCode) {
5802 case KeyEvent.KEYCODE_DPAD_UP:
5803 case KeyEvent.KEYCODE_DPAD_DOWN:
5804 case KeyEvent.KEYCODE_DPAD_LEFT:
5805 case KeyEvent.KEYCODE_DPAD_RIGHT:
5806 case KeyEvent.KEYCODE_DPAD_CENTER:
5807 case KeyEvent.KEYCODE_ENTER:
5808 okToSend = false;
5809 break;
5810 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005811 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005812 if (event.getAction() == KeyEvent.ACTION_DOWN
5813 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005814 KeyEvent.DispatcherState state = getKeyDispatcherState();
5815 if (state != null) {
5816 state.startTracking(event, this);
5817 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005818 handled = true;
5819 } else if (event.getAction() == KeyEvent.ACTION_UP
5820 && event.isTracking() && !event.isCanceled()) {
5821 handled = true;
5822 mTextFilter.setText("");
5823 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005824 }
5825 okToSend = false;
5826 break;
5827 case KeyEvent.KEYCODE_SPACE:
5828 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005829 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005830 break;
5831 }
5832
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005833 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005834 createTextFilter(true);
5835
5836 KeyEvent forwardEvent = event;
5837 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005838 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005839 }
5840
5841 int action = event.getAction();
5842 switch (action) {
5843 case KeyEvent.ACTION_DOWN:
5844 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5845 break;
5846
5847 case KeyEvent.ACTION_UP:
5848 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5849 break;
5850
5851 case KeyEvent.ACTION_MULTIPLE:
5852 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5853 break;
5854 }
5855 }
5856 return handled;
5857 }
5858
5859 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005860 * Return an InputConnection for editing of the filter text.
5861 */
5862 @Override
5863 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005864 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005865 if (mPublicInputConnection == null) {
5866 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005867 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005868 }
Romain Guyf6991302013-06-05 17:19:01 -07005869 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005870 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5871 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005872 }
5873 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005874 }
Romain Guy0a637162009-05-29 14:43:54 -07005875
Romain Guyf6991302013-06-05 17:19:01 -07005876 private class InputConnectionWrapper implements InputConnection {
5877 private final EditorInfo mOutAttrs;
5878 private InputConnection mTarget;
5879
5880 public InputConnectionWrapper(EditorInfo outAttrs) {
5881 mOutAttrs = outAttrs;
5882 }
5883
5884 private InputConnection getTarget() {
5885 if (mTarget == null) {
5886 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5887 }
5888 return mTarget;
5889 }
5890
5891 @Override
5892 public boolean reportFullscreenMode(boolean enabled) {
5893 // Use our own input connection, since it is
5894 // the "real" one the IME is talking with.
5895 return mDefInputConnection.reportFullscreenMode(enabled);
5896 }
5897
5898 @Override
5899 public boolean performEditorAction(int editorAction) {
5900 // The editor is off in its own window; we need to be
5901 // the one that does this.
5902 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005903 InputMethodManager imm =
5904 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005905 if (imm != null) {
5906 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5907 }
5908 return true;
5909 }
5910 return false;
5911 }
5912
5913 @Override
5914 public boolean sendKeyEvent(KeyEvent event) {
5915 // Use our own input connection, since the filter
5916 // text view may not be shown in a window so has
5917 // no ViewAncestor to dispatch events with.
5918 return mDefInputConnection.sendKeyEvent(event);
5919 }
5920
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005921 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005922 public CharSequence getTextBeforeCursor(int n, int flags) {
5923 if (mTarget == null) return "";
5924 return mTarget.getTextBeforeCursor(n, flags);
5925 }
5926
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005927 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005928 public CharSequence getTextAfterCursor(int n, int flags) {
5929 if (mTarget == null) return "";
5930 return mTarget.getTextAfterCursor(n, flags);
5931 }
5932
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005933 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005934 public CharSequence getSelectedText(int flags) {
5935 if (mTarget == null) return "";
5936 return mTarget.getSelectedText(flags);
5937 }
5938
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005939 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005940 public int getCursorCapsMode(int reqModes) {
5941 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5942 return mTarget.getCursorCapsMode(reqModes);
5943 }
5944
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005945 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005946 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5947 return getTarget().getExtractedText(request, flags);
5948 }
5949
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005950 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005951 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5952 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5953 }
5954
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005955 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08005956 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5957 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5958 }
5959
5960 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005961 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5962 return getTarget().setComposingText(text, newCursorPosition);
5963 }
5964
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005965 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005966 public boolean setComposingRegion(int start, int end) {
5967 return getTarget().setComposingRegion(start, end);
5968 }
5969
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005970 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005971 public boolean finishComposingText() {
5972 return mTarget == null || mTarget.finishComposingText();
5973 }
5974
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005975 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005976 public boolean commitText(CharSequence text, int newCursorPosition) {
5977 return getTarget().commitText(text, newCursorPosition);
5978 }
5979
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005980 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005981 public boolean commitCompletion(CompletionInfo text) {
5982 return getTarget().commitCompletion(text);
5983 }
5984
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005985 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005986 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5987 return getTarget().commitCorrection(correctionInfo);
5988 }
5989
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005990 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005991 public boolean setSelection(int start, int end) {
5992 return getTarget().setSelection(start, end);
5993 }
5994
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005995 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005996 public boolean performContextMenuAction(int id) {
5997 return getTarget().performContextMenuAction(id);
5998 }
5999
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006000 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006001 public boolean beginBatchEdit() {
6002 return getTarget().beginBatchEdit();
6003 }
6004
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006005 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006006 public boolean endBatchEdit() {
6007 return getTarget().endBatchEdit();
6008 }
6009
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006010 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006011 public boolean clearMetaKeyStates(int states) {
6012 return getTarget().clearMetaKeyStates(states);
6013 }
6014
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006015 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006016 public boolean performPrivateCommand(String action, Bundle data) {
6017 return getTarget().performPrivateCommand(action, data);
6018 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09006019
6020 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07006021 public boolean requestCursorUpdates(int cursorUpdateMode) {
6022 return getTarget().requestCursorUpdates(cursorUpdateMode);
6023 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08006024
6025 @Override
6026 public Handler getHandler() {
6027 return getTarget().getHandler();
6028 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07006029
6030 @Override
6031 public void closeConnection() {
6032 getTarget().closeConnection();
6033 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07006034
6035 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07006036 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6037 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07006038 }
Yohei Yukawae77386e2018-01-23 10:39:32 -08006039
6040 @Override
6041 public void reportLanguageHint(@NonNull LocaleList languageHint) {
6042 getTarget().reportLanguageHint(languageHint);
6043 }
Romain Guyf6991302013-06-05 17:19:01 -07006044 }
6045
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006046 /**
6047 * For filtering we proxy an input connection to an internal text editor,
6048 * and this allows the proxying to happen.
6049 */
6050 @Override
6051 public boolean checkInputConnectionProxy(View view) {
6052 return view == mTextFilter;
6053 }
Romain Guy0a637162009-05-29 14:43:54 -07006054
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006055 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006056 * Creates the window for the text filter and populates it with an EditText field;
6057 *
6058 * @param animateEntrance true if the window should appear with an animation
6059 */
6060 private void createTextFilter(boolean animateEntrance) {
6061 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006062 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006063 p.setFocusable(false);
6064 p.setTouchable(false);
6065 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006066 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006067 p.setWidth(LayoutParams.WRAP_CONTENT);
6068 p.setHeight(LayoutParams.WRAP_CONTENT);
6069 p.setBackgroundDrawable(null);
6070 mPopup = p;
6071 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006072 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006073 }
6074 if (animateEntrance) {
6075 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6076 } else {
6077 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6078 }
6079 }
6080
Romain Guyf6991302013-06-05 17:19:01 -07006081 private EditText getTextFilterInput() {
6082 if (mTextFilter == null) {
6083 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6084 mTextFilter = (EditText) layoutInflater.inflate(
6085 com.android.internal.R.layout.typing_filter, null);
6086 // For some reason setting this as the "real" input type changes
6087 // the text view in some way that it doesn't work, and I don't
6088 // want to figure out why this is.
6089 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6090 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6091 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6092 mTextFilter.addTextChangedListener(this);
6093 }
6094 return mTextFilter;
6095 }
6096
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006097 /**
6098 * Clear the text filter.
6099 */
6100 public void clearTextFilter() {
6101 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006102 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006103 mFiltered = false;
6104 if (mPopup != null && mPopup.isShowing()) {
6105 dismissPopup();
6106 }
6107 }
6108 }
6109
6110 /**
6111 * Returns if the ListView currently has a text filter.
6112 */
6113 public boolean hasTextFilter() {
6114 return mFiltered;
6115 }
6116
Alan Viverette8fa327a2013-05-31 14:53:13 -07006117 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006118 public void onGlobalLayout() {
6119 if (isShown()) {
6120 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006121 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006122 showPopup();
6123 }
6124 } else {
6125 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006126 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006127 dismissPopup();
6128 }
6129 }
6130
6131 }
6132
6133 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006134 * For our text watcher that is associated with the text filter. Does
6135 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006136 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006137 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006138 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6139 }
6140
6141 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006142 * For our text watcher that is associated with the text filter. Performs
6143 * the actual filtering as the text changes, and takes care of hiding and
6144 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006145 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006146 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006147 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006148 if (isTextFilterEnabled()) {
6149 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006150 int length = s.length();
6151 boolean showing = mPopup.isShowing();
6152 if (!showing && length > 0) {
6153 // Show the filter popup if necessary
6154 showPopup();
6155 mFiltered = true;
6156 } else if (showing && length == 0) {
6157 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006158 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006159 mFiltered = false;
6160 }
6161 if (mAdapter instanceof Filterable) {
6162 Filter f = ((Filterable) mAdapter).getFilter();
6163 // Filter should not be null when we reach this part
6164 if (f != null) {
6165 f.filter(s, this);
6166 } else {
6167 throw new IllegalStateException("You cannot call onTextChanged with a non "
6168 + "filterable adapter");
6169 }
6170 }
6171 }
6172 }
6173
6174 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006175 * For our text watcher that is associated with the text filter. Does
6176 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006177 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006178 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006179 public void afterTextChanged(Editable s) {
6180 }
6181
Alan Viverette8fa327a2013-05-31 14:53:13 -07006182 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006183 public void onFilterComplete(int count) {
6184 if (mSelectedPosition < 0 && count > 0) {
6185 mResurrectToPosition = INVALID_POSITION;
6186 resurrectSelection();
6187 }
6188 }
6189
6190 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006191 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6192 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6193 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6194 }
6195
6196 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006197 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6198 return new LayoutParams(p);
6199 }
6200
6201 @Override
6202 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6203 return new AbsListView.LayoutParams(getContext(), attrs);
6204 }
6205
6206 @Override
6207 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6208 return p instanceof AbsListView.LayoutParams;
6209 }
6210
6211 /**
6212 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6213 * to the bottom to show new items.
6214 *
6215 * @param mode the transcript mode to set
6216 *
6217 * @see #TRANSCRIPT_MODE_DISABLED
6218 * @see #TRANSCRIPT_MODE_NORMAL
6219 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6220 */
6221 public void setTranscriptMode(int mode) {
6222 mTranscriptMode = mode;
6223 }
6224
6225 /**
6226 * Returns the current transcript mode.
6227 *
6228 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6229 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6230 */
6231 public int getTranscriptMode() {
6232 return mTranscriptMode;
6233 }
6234
6235 @Override
6236 public int getSolidColor() {
6237 return mCacheColorHint;
6238 }
6239
6240 /**
6241 * 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 -07006242 * on top of a solid, single-color, opaque background.
6243 *
6244 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6245 * single color. This hint will not affect any existing background drawable set on this view (
6246 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006247 *
6248 * @param color The background color
6249 */
Tor Norbye80756e32015-03-02 09:39:27 -08006250 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006251 if (color != mCacheColorHint) {
6252 mCacheColorHint = color;
6253 int count = getChildCount();
6254 for (int i = 0; i < count; i++) {
6255 getChildAt(i).setDrawingCacheBackgroundColor(color);
6256 }
6257 mRecycler.setCacheColorHint(color);
6258 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006259 }
6260
6261 /**
6262 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6263 * on top of a solid, single-color, opaque background
6264 *
6265 * @return The cache color hint
6266 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006267 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006268 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006269 public int getCacheColorHint() {
6270 return mCacheColorHint;
6271 }
6272
6273 /**
6274 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6275 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6276 * internal view recycler.
6277 *
6278 * @param views A list into which to put the reclaimed views
6279 */
6280 public void reclaimViews(List<View> views) {
6281 int childCount = getChildCount();
6282 RecyclerListener listener = mRecycler.mRecyclerListener;
6283
6284 // Reclaim views on screen
6285 for (int i = 0; i < childCount; i++) {
6286 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006287 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006288 // Don't reclaim header or footer views, or views that should be ignored
6289 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6290 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006291 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006292 if (listener != null) {
6293 // Pretend they went through the scrap heap
6294 listener.onMovedToScrapHeap(child);
6295 }
6296 }
6297 }
6298 mRecycler.reclaimScrapViews(views);
6299 removeAllViewsInLayout();
6300 }
6301
Adam Powell637d3372010-08-25 14:37:03 -07006302 private void finishGlows() {
6303 if (mEdgeGlowTop != null) {
6304 mEdgeGlowTop.finish();
6305 mEdgeGlowBottom.finish();
6306 }
6307 }
6308
Romain Guy13922e02009-05-12 17:56:14 -07006309 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006310 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6311 * through the specified intent.
6312 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6313 */
6314 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -08006315 setRemoteViewsAdapter(intent, false);
6316 }
6317
6318 /** @hide **/
6319 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6320 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6321 }
6322
6323 /** @hide **/
6324 @Override
6325 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006326 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6327 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006328 if (mRemoteAdapter != null) {
6329 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6330 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6331 mRemoteAdapter.getRemoteViewsServiceIntent());
6332 if (fcNew.equals(fcOld)) {
6333 return;
6334 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006335 }
Adam Cohen2148d432011-07-28 14:59:54 -07006336 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006337 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08006338 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07006339 if (mRemoteAdapter.isDataReady()) {
6340 setAdapter(mRemoteAdapter);
6341 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006342 }
6343
6344 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006345 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006346 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006347 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006348 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006349 * @hide
6350 */
6351 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6352 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6353 // service handling the specified intent.
6354 if (mRemoteAdapter != null) {
6355 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6356 }
6357 }
6358
6359 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006360 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6361 * connected yet.
6362 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006363 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006364 public void deferNotifyDataSetChanged() {
6365 mDeferNotifyDataSetChanged = true;
6366 }
6367
6368 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006369 * Called back when the adapter connects to the RemoteViewsService.
6370 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006371 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006372 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006373 if (mRemoteAdapter != mAdapter) {
6374 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006375 if (mDeferNotifyDataSetChanged) {
6376 mRemoteAdapter.notifyDataSetChanged();
6377 mDeferNotifyDataSetChanged = false;
6378 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006379 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006380 } else if (mRemoteAdapter != null) {
6381 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006382 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006383 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006384 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006385 }
6386
6387 /**
6388 * Called back when the adapter disconnects from the RemoteViewsService.
6389 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006390 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006391 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006392 // If the remote adapter disconnects, we keep it around
6393 // since the currently displayed items are still cached.
6394 // Further, we want the service to eventually reconnect
6395 // when necessary, as triggered by this view requesting
6396 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006397 }
6398
6399 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006400 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6401 * being displayed by the AbsListView.
6402 */
6403 void setVisibleRangeHint(int start, int end) {
6404 if (mRemoteAdapter != null) {
6405 mRemoteAdapter.setVisibleRangeHint(start, end);
6406 }
6407 }
6408
6409 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006410 * Sets the recycler listener to be notified whenever a View is set aside in
6411 * the recycler for later reuse. This listener can be used to free resources
6412 * associated to the View.
6413 *
6414 * @param listener The recycler listener to be notified of views set aside
6415 * in the recycler.
6416 *
6417 * @see android.widget.AbsListView.RecycleBin
6418 * @see android.widget.AbsListView.RecyclerListener
6419 */
6420 public void setRecyclerListener(RecyclerListener listener) {
6421 mRecycler.mRecyclerListener = listener;
6422 }
6423
Adam Powellb1f498a2011-01-18 20:43:23 -08006424 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6425 @Override
6426 public void onChanged() {
6427 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006428 if (mFastScroll != null) {
6429 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006430 }
6431 }
6432
6433 @Override
6434 public void onInvalidated() {
6435 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006436 if (mFastScroll != null) {
6437 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006438 }
6439 }
6440 }
6441
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006442 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006443 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6444 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6445 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6446 * selects and deselects list items.
6447 */
6448 public interface MultiChoiceModeListener extends ActionMode.Callback {
6449 /**
6450 * Called when an item is checked or unchecked during selection mode.
6451 *
6452 * @param mode The {@link ActionMode} providing the selection mode
6453 * @param position Adapter position of the item that was checked or unchecked
6454 * @param id Adapter ID of the item that was checked or unchecked
6455 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6456 * if the item is now unchecked.
6457 */
6458 public void onItemCheckedStateChanged(ActionMode mode,
6459 int position, long id, boolean checked);
6460 }
6461
6462 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6463 private MultiChoiceModeListener mWrapped;
6464
6465 public void setWrapped(MultiChoiceModeListener wrapped) {
6466 mWrapped = wrapped;
6467 }
6468
Adam Powella7981702012-08-24 12:43:41 -07006469 public boolean hasWrappedCallback() {
6470 return mWrapped != null;
6471 }
6472
Alan Viverette8fa327a2013-05-31 14:53:13 -07006473 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006474 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6475 if (mWrapped.onCreateActionMode(mode, menu)) {
6476 // Initialize checked graphic state?
6477 setLongClickable(false);
6478 return true;
6479 }
6480 return false;
6481 }
6482
Alan Viverette8fa327a2013-05-31 14:53:13 -07006483 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006484 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6485 return mWrapped.onPrepareActionMode(mode, menu);
6486 }
6487
Alan Viverette8fa327a2013-05-31 14:53:13 -07006488 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006489 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6490 return mWrapped.onActionItemClicked(mode, item);
6491 }
6492
Alan Viverette8fa327a2013-05-31 14:53:13 -07006493 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006494 public void onDestroyActionMode(ActionMode mode) {
6495 mWrapped.onDestroyActionMode(mode);
6496 mChoiceActionMode = null;
6497
6498 // Ending selection mode means deselecting everything.
6499 clearChoices();
6500
6501 mDataChanged = true;
6502 rememberSyncState();
6503 requestLayout();
6504
6505 setLongClickable(true);
6506 }
6507
Alan Viverette8fa327a2013-05-31 14:53:13 -07006508 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006509 public void onItemCheckedStateChanged(ActionMode mode,
6510 int position, long id, boolean checked) {
6511 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6512
6513 // If there are no items selected we no longer need the selection mode.
6514 if (getCheckedItemCount() == 0) {
6515 mode.finish();
6516 }
6517 }
6518 }
6519
6520 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006521 * AbsListView extends LayoutParams to provide a place to hold the view type.
6522 */
6523 public static class LayoutParams extends ViewGroup.LayoutParams {
6524 /**
6525 * View type for this view, as returned by
6526 * {@link android.widget.Adapter#getItemViewType(int) }
6527 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006528 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006529 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6530 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6531 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006532 int viewType;
6533
The Android Open Source Project4df24232009-03-05 14:34:35 -08006534 /**
6535 * When this boolean is set, the view has been added to the AbsListView
6536 * at least once. It is used to know whether headers/footers have already
6537 * been added to the list view and whether they should be treated as
6538 * recycled views or not.
6539 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006540 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006541 boolean recycledHeaderFooter;
6542
Romain Guy0bf88592010-03-02 13:38:44 -08006543 /**
6544 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6545 * to obtain children views to measure itself. When doing so, the children
6546 * are not attached to the window, but put in the recycler which assumes
6547 * they've been attached before. Setting this flag will force the reused
6548 * view to be attached to the window rather than just attached to the
6549 * parent.
6550 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006551 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006552 boolean forceAdd;
6553
Dianne Hackborn079e2352010-10-18 17:02:43 -07006554 /**
6555 * The position the view was removed from when pulled out of the
6556 * scrap heap.
6557 * @hide
6558 */
6559 int scrappedFromPosition;
6560
Adam Powell539ee872012-02-03 19:00:49 -08006561 /**
6562 * The ID the view represents
6563 */
6564 long itemId = -1;
6565
Alan Viverette92539d52015-09-14 10:49:25 -04006566 /** Whether the adapter considers the item enabled. */
6567 boolean isEnabled;
6568
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006569 public LayoutParams(Context c, AttributeSet attrs) {
6570 super(c, attrs);
6571 }
6572
6573 public LayoutParams(int w, int h) {
6574 super(w, h);
6575 }
6576
6577 public LayoutParams(int w, int h, int viewType) {
6578 super(w, h);
6579 this.viewType = viewType;
6580 }
6581
6582 public LayoutParams(ViewGroup.LayoutParams source) {
6583 super(source);
6584 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006585
6586 /** @hide */
6587 @Override
6588 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6589 super.encodeProperties(encoder);
6590
6591 encoder.addProperty("list:viewType", viewType);
6592 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6593 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006594 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006595 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006596 }
6597
6598 /**
6599 * A RecyclerListener is used to receive a notification whenever a View is placed
6600 * inside the RecycleBin's scrap heap. This listener is used to free resources
6601 * associated to Views placed in the RecycleBin.
6602 *
6603 * @see android.widget.AbsListView.RecycleBin
6604 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6605 */
6606 public static interface RecyclerListener {
6607 /**
6608 * Indicates that the specified View was moved into the recycler's scrap heap.
6609 * The view is not displayed on screen any more and any expensive resource
6610 * associated with the view should be discarded.
6611 *
6612 * @param view
6613 */
6614 void onMovedToScrapHeap(View view);
6615 }
6616
6617 /**
6618 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6619 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6620 * start of a layout. By construction, they are displaying current information. At the end of
6621 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6622 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6623 *
6624 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6625 * @see android.widget.AbsListView.RecyclerListener
6626 */
6627 class RecycleBin {
6628 private RecyclerListener mRecyclerListener;
6629
6630 /**
6631 * The position of the first view stored in mActiveViews.
6632 */
6633 private int mFirstActivePosition;
6634
6635 /**
6636 * Views that were on screen at the start of layout. This array is populated at the start of
6637 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6638 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6639 * view store in mFirstActivePosition.
6640 */
6641 private View[] mActiveViews = new View[0];
6642
6643 /**
6644 * Unsorted views that can be used by the adapter as a convert view.
6645 */
6646 private ArrayList<View>[] mScrapViews;
6647
6648 private int mViewTypeCount;
6649
6650 private ArrayList<View> mCurrentScrap;
6651
Adam Powell539ee872012-02-03 19:00:49 -08006652 private ArrayList<View> mSkippedScrap;
6653
6654 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006655 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006656
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006657 public void setViewTypeCount(int viewTypeCount) {
6658 if (viewTypeCount < 1) {
6659 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6660 }
6661 //noinspection unchecked
6662 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6663 for (int i = 0; i < viewTypeCount; i++) {
6664 scrapViews[i] = new ArrayList<View>();
6665 }
6666 mViewTypeCount = viewTypeCount;
6667 mCurrentScrap = scrapViews[0];
6668 mScrapViews = scrapViews;
6669 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006670
Adam Powellf3c2eda2010-03-16 17:31:01 -07006671 public void markChildrenDirty() {
6672 if (mViewTypeCount == 1) {
6673 final ArrayList<View> scrap = mCurrentScrap;
6674 final int scrapCount = scrap.size();
6675 for (int i = 0; i < scrapCount; i++) {
6676 scrap.get(i).forceLayout();
6677 }
6678 } else {
6679 final int typeCount = mViewTypeCount;
6680 for (int i = 0; i < typeCount; i++) {
6681 final ArrayList<View> scrap = mScrapViews[i];
6682 final int scrapCount = scrap.size();
6683 for (int j = 0; j < scrapCount; j++) {
6684 scrap.get(j).forceLayout();
6685 }
6686 }
6687 }
Adam Powell539ee872012-02-03 19:00:49 -08006688 if (mTransientStateViews != null) {
6689 final int count = mTransientStateViews.size();
6690 for (int i = 0; i < count; i++) {
6691 mTransientStateViews.valueAt(i).forceLayout();
6692 }
6693 }
Chet Haase72871322013-02-26 16:12:13 -07006694 if (mTransientStateViewsById != null) {
6695 final int count = mTransientStateViewsById.size();
6696 for (int i = 0; i < count; i++) {
6697 mTransientStateViewsById.valueAt(i).forceLayout();
6698 }
6699 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006700 }
Romain Guy0a637162009-05-29 14:43:54 -07006701
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006702 public boolean shouldRecycleViewType(int viewType) {
6703 return viewType >= 0;
6704 }
6705
6706 /**
6707 * Clears the scrap heap.
6708 */
6709 void clear() {
6710 if (mViewTypeCount == 1) {
6711 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006712 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006713 } else {
6714 final int typeCount = mViewTypeCount;
6715 for (int i = 0; i < typeCount; i++) {
6716 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006717 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006718 }
6719 }
Alan Viverette59511502013-12-09 13:49:25 -08006720
6721 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006722 }
6723
6724 /**
6725 * Fill ActiveViews with all of the children of the AbsListView.
6726 *
6727 * @param childCount The minimum number of views mActiveViews should hold
6728 * @param firstActivePosition The position of the first view that will be stored in
6729 * mActiveViews
6730 */
6731 void fillActiveViews(int childCount, int firstActivePosition) {
6732 if (mActiveViews.length < childCount) {
6733 mActiveViews = new View[childCount];
6734 }
6735 mFirstActivePosition = firstActivePosition;
6736
Romain Guyf6991302013-06-05 17:19:01 -07006737 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006738 final View[] activeViews = mActiveViews;
6739 for (int i = 0; i < childCount; i++) {
6740 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006741 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006742 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006743 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006744 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6745 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006746 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006747 // Remember the position so that setupChild() doesn't reset state.
6748 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006749 }
6750 }
6751 }
6752
6753 /**
6754 * Get the view corresponding to the specified position. The view will be removed from
6755 * mActiveViews if it is found.
6756 *
6757 * @param position The position to look up in mActiveViews
6758 * @return The view if it is found, null otherwise
6759 */
6760 View getActiveView(int position) {
6761 int index = position - mFirstActivePosition;
6762 final View[] activeViews = mActiveViews;
6763 if (index >=0 && index < activeViews.length) {
6764 final View match = activeViews[index];
6765 activeViews[index] = null;
6766 return match;
6767 }
6768 return null;
6769 }
6770
Adam Powell539ee872012-02-03 19:00:49 -08006771 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006772 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6773 long id = mAdapter.getItemId(position);
6774 View result = mTransientStateViewsById.get(id);
6775 mTransientStateViewsById.remove(id);
6776 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006777 }
Chet Haase72871322013-02-26 16:12:13 -07006778 if (mTransientStateViews != null) {
6779 final int index = mTransientStateViews.indexOfKey(position);
6780 if (index >= 0) {
6781 View result = mTransientStateViews.valueAt(index);
6782 mTransientStateViews.removeAt(index);
6783 return result;
6784 }
Adam Powell539ee872012-02-03 19:00:49 -08006785 }
Chet Haase72871322013-02-26 16:12:13 -07006786 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006787 }
6788
6789 /**
Alan Viverette59511502013-12-09 13:49:25 -08006790 * Dumps and fully detaches any currently saved views with transient
6791 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006792 */
6793 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006794 final SparseArray<View> viewsByPos = mTransientStateViews;
6795 if (viewsByPos != null) {
6796 final int N = viewsByPos.size();
6797 for (int i = 0; i < N; i++) {
6798 removeDetachedView(viewsByPos.valueAt(i), false);
6799 }
6800 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006801 }
Alan Viverette59511502013-12-09 13:49:25 -08006802
6803 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6804 if (viewsById != null) {
6805 final int N = viewsById.size();
6806 for (int i = 0; i < N; i++) {
6807 removeDetachedView(viewsById.valueAt(i), false);
6808 }
6809 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006810 }
Adam Powell539ee872012-02-03 19:00:49 -08006811 }
6812
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006813 /**
6814 * @return A view from the ScrapViews collection. These are unordered.
6815 */
6816 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006817 final int whichScrap = mAdapter.getItemViewType(position);
6818 if (whichScrap < 0) {
6819 return null;
6820 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006821 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006822 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006823 } else if (whichScrap < mScrapViews.length) {
6824 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006825 }
6826 return null;
6827 }
6828
6829 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006830 * Puts a view into the list of scrap views.
6831 * <p>
6832 * If the list data hasn't changed or the adapter has stable IDs, views
6833 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006834 *
6835 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006836 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006837 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006838 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006839 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006840 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006841 // Can't recycle, but we don't know anything about the view.
6842 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006843 return;
6844 }
6845
Adam Powell539ee872012-02-03 19:00:49 -08006846 lp.scrappedFromPosition = position;
6847
Alan Viverette1e51cc72013-09-27 14:32:20 -07006848 // Remove but don't scrap header or footer views, or views that
6849 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006850 final int viewType = lp.viewType;
6851 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006852 // Can't recycle. If it's not a header or footer, which have
6853 // special handling and should be ignored, then skip the scrap
6854 // heap and we'll fully detach the view later.
6855 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6856 getSkippedScrap().add(scrap);
6857 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006858 return;
6859 }
6860
6861 scrap.dispatchStartTemporaryDetach();
6862
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006863 // The the accessibility state of the view may change while temporary
6864 // detached and we do not allow detached views to fire accessibility
6865 // events. So we are announcing that the subtree changed giving a chance
6866 // to clients holding on to a view in this subtree to refresh it.
Eugene Suslae4d31b32017-06-01 11:16:42 -07006867 notifyAccessibilityStateChanged(
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006868 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6869
Alan Viveretted44696c2013-07-18 10:37:15 -07006870 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006871 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006872 if (scrapHasTransientState) {
6873 if (mAdapter != null && mAdapterHasStableIds) {
6874 // If the adapter has stable IDs, we can reuse the view for
6875 // the same data.
6876 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006877 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006878 }
6879 mTransientStateViewsById.put(lp.itemId, scrap);
6880 } else if (!mDataChanged) {
6881 // If the data hasn't changed, we can reuse the views at
6882 // their old positions.
6883 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006884 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006885 }
6886 mTransientStateViews.put(position, scrap);
6887 } else {
6888 // Otherwise, we'll have to remove the view and start over.
Phil Weaverec66fb82017-03-23 12:21:53 -07006889 clearScrapForRebind(scrap);
Alan Viverette8bbae342015-06-25 14:49:29 -07006890 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006891 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006892 } else {
Phil Weaverec66fb82017-03-23 12:21:53 -07006893 clearScrapForRebind(scrap);
Alan Viveretted44696c2013-07-18 10:37:15 -07006894 if (mViewTypeCount == 1) {
6895 mCurrentScrap.add(scrap);
6896 } else {
6897 mScrapViews[viewType].add(scrap);
6898 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006899
Alan Viveretted44696c2013-07-18 10:37:15 -07006900 if (mRecyclerListener != null) {
6901 mRecyclerListener.onMovedToScrapHeap(scrap);
6902 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006903 }
6904 }
6905
Alan Viverette8bbae342015-06-25 14:49:29 -07006906 private ArrayList<View> getSkippedScrap() {
6907 if (mSkippedScrap == null) {
6908 mSkippedScrap = new ArrayList<>();
6909 }
6910 return mSkippedScrap;
6911 }
6912
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006913 /**
Adam Powell539ee872012-02-03 19:00:49 -08006914 * Finish the removal of any views that skipped the scrap heap.
6915 */
6916 void removeSkippedScrap() {
6917 if (mSkippedScrap == null) {
6918 return;
6919 }
6920 final int count = mSkippedScrap.size();
6921 for (int i = 0; i < count; i++) {
6922 removeDetachedView(mSkippedScrap.get(i), false);
6923 }
6924 mSkippedScrap.clear();
6925 }
6926
6927 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006928 * Move all views remaining in mActiveViews to mScrapViews.
6929 */
6930 void scrapActiveViews() {
6931 final View[] activeViews = mActiveViews;
6932 final boolean hasListener = mRecyclerListener != null;
6933 final boolean multipleScraps = mViewTypeCount > 1;
6934
6935 ArrayList<View> scrapViews = mCurrentScrap;
6936 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006937 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006938 final View victim = activeViews[i];
6939 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006940 final AbsListView.LayoutParams lp
6941 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006942 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006943
6944 activeViews[i] = null;
6945
Alan Viverette59511502013-12-09 13:49:25 -08006946 if (victim.hasTransientState()) {
6947 // Store views with transient state for later use.
6948 victim.dispatchStartTemporaryDetach();
6949
6950 if (mAdapter != null && mAdapterHasStableIds) {
6951 if (mTransientStateViewsById == null) {
6952 mTransientStateViewsById = new LongSparseArray<View>();
6953 }
6954 long id = mAdapter.getItemId(mFirstActivePosition + i);
6955 mTransientStateViewsById.put(id, victim);
6956 } else if (!mDataChanged) {
6957 if (mTransientStateViews == null) {
6958 mTransientStateViews = new SparseArray<View>();
6959 }
6960 mTransientStateViews.put(mFirstActivePosition + i, victim);
6961 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6962 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006963 removeDetachedView(victim, false);
6964 }
Alan Viverette59511502013-12-09 13:49:25 -08006965 } else if (!shouldRecycleViewType(whichScrap)) {
6966 // Discard non-recyclable views except headers/footers.
6967 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6968 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006969 }
Alan Viverette59511502013-12-09 13:49:25 -08006970 } else {
6971 // Store everything else on the appropriate scrap heap.
6972 if (multipleScraps) {
6973 scrapViews = mScrapViews[whichScrap];
6974 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006975
Alan Viverette59511502013-12-09 13:49:25 -08006976 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006977 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08006978 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006979
Alan Viverette59511502013-12-09 13:49:25 -08006980 if (hasListener) {
6981 mRecyclerListener.onMovedToScrapHeap(victim);
6982 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006983 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006984 }
6985 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006986 pruneScrapViews();
6987 }
6988
6989 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006990 * At the end of a layout pass, all temp detached views should either be re-attached or
6991 * completely detached. This method ensures that any remaining view in the scrap list is
6992 * fully detached.
6993 */
6994 void fullyDetachScrapViews() {
6995 final int viewTypeCount = mViewTypeCount;
6996 final ArrayList<View>[] scrapViews = mScrapViews;
6997 for (int i = 0; i < viewTypeCount; ++i) {
6998 final ArrayList<View> scrapPile = scrapViews[i];
6999 for (int j = scrapPile.size() - 1; j >= 0; j--) {
7000 final View view = scrapPile.get(j);
7001 if (view.isTemporarilyDetached()) {
7002 removeDetachedView(view, false);
7003 }
7004 }
7005 }
7006 }
7007
7008 /**
Alan Viverette59511502013-12-09 13:49:25 -08007009 * Makes sure that the size of mScrapViews does not exceed the size of
7010 * mActiveViews, which can happen if an adapter does not recycle its
7011 * views. Removes cached transient state views that no longer have
7012 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007013 */
7014 private void pruneScrapViews() {
7015 final int maxViews = mActiveViews.length;
7016 final int viewTypeCount = mViewTypeCount;
7017 final ArrayList<View>[] scrapViews = mScrapViews;
7018 for (int i = 0; i < viewTypeCount; ++i) {
7019 final ArrayList<View> scrapPile = scrapViews[i];
7020 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007021 while (size > maxViews) {
7022 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007023 }
7024 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07007025
Alan Viverette59511502013-12-09 13:49:25 -08007026 final SparseArray<View> transViewsByPos = mTransientStateViews;
7027 if (transViewsByPos != null) {
7028 for (int i = 0; i < transViewsByPos.size(); i++) {
7029 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007030 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007031 removeDetachedView(v, false);
7032 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007033 i--;
7034 }
7035 }
7036 }
Alan Viverette59511502013-12-09 13:49:25 -08007037
7038 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7039 if (transViewsById != null) {
7040 for (int i = 0; i < transViewsById.size(); i++) {
7041 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007042 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007043 removeDetachedView(v, false);
7044 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007045 i--;
7046 }
7047 }
7048 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007049 }
7050
7051 /**
7052 * Puts all views in the scrap heap into the supplied list.
7053 */
7054 void reclaimScrapViews(List<View> views) {
7055 if (mViewTypeCount == 1) {
7056 views.addAll(mCurrentScrap);
7057 } else {
7058 final int viewTypeCount = mViewTypeCount;
7059 final ArrayList<View>[] scrapViews = mScrapViews;
7060 for (int i = 0; i < viewTypeCount; ++i) {
7061 final ArrayList<View> scrapPile = scrapViews[i];
7062 views.addAll(scrapPile);
7063 }
7064 }
7065 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007066
7067 /**
7068 * Updates the cache color hint of all known views.
7069 *
7070 * @param color The new cache color hint.
7071 */
7072 void setCacheColorHint(int color) {
7073 if (mViewTypeCount == 1) {
7074 final ArrayList<View> scrap = mCurrentScrap;
7075 final int scrapCount = scrap.size();
7076 for (int i = 0; i < scrapCount; i++) {
7077 scrap.get(i).setDrawingCacheBackgroundColor(color);
7078 }
7079 } else {
7080 final int typeCount = mViewTypeCount;
7081 for (int i = 0; i < typeCount; i++) {
7082 final ArrayList<View> scrap = mScrapViews[i];
7083 final int scrapCount = scrap.size();
7084 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007085 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007086 }
7087 }
7088 }
7089 // Just in case this is called during a layout pass
7090 final View[] activeViews = mActiveViews;
7091 final int count = activeViews.length;
7092 for (int i = 0; i < count; ++i) {
7093 final View victim = activeViews[i];
7094 if (victim != null) {
7095 victim.setDrawingCacheBackgroundColor(color);
7096 }
7097 }
7098 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007099
Alan Viverette3e141622014-02-18 17:05:13 -08007100 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7101 final int size = scrapViews.size();
7102 if (size > 0) {
7103 // See if we still have a view for this position or ID.
Phil Weavere28c03b2017-04-24 13:23:10 -07007104 // Traverse backwards to find the most recently used scrap view
7105 for (int i = size - 1; i >= 0; i--) {
Alan Viverette3e141622014-02-18 17:05:13 -08007106 final View view = scrapViews.get(i);
7107 final AbsListView.LayoutParams params =
7108 (AbsListView.LayoutParams) view.getLayoutParams();
7109
7110 if (mAdapterHasStableIds) {
7111 final long id = mAdapter.getItemId(position);
7112 if (id == params.itemId) {
7113 return scrapViews.remove(i);
7114 }
7115 } else if (params.scrappedFromPosition == position) {
7116 final View scrap = scrapViews.remove(i);
Phil Weaverec66fb82017-03-23 12:21:53 -07007117 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007118 return scrap;
7119 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007120 }
Alan Viverette3e141622014-02-18 17:05:13 -08007121 final View scrap = scrapViews.remove(size - 1);
Phil Weaverec66fb82017-03-23 12:21:53 -07007122 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007123 return scrap;
7124 } else {
7125 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007126 }
Alan Viverette3e141622014-02-18 17:05:13 -08007127 }
7128
7129 private void clearScrap(final ArrayList<View> scrap) {
7130 final int scrapCount = scrap.size();
7131 for (int j = 0; j < scrapCount; j++) {
7132 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7133 }
7134 }
7135
Phil Weaverec66fb82017-03-23 12:21:53 -07007136 private void clearScrapForRebind(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007137 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007138 view.setAccessibilityDelegate(null);
7139 }
7140
7141 private void removeDetachedView(View child, boolean animate) {
7142 child.setAccessibilityDelegate(null);
7143 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007144 }
7145 }
Alan Viverette441b4372014-02-12 13:30:20 -08007146
7147 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007148 * Returns the height of the view for the specified position.
7149 *
7150 * @param position the item position
7151 * @return view height in pixels
7152 */
7153 int getHeightForPosition(int position) {
7154 final int firstVisiblePosition = getFirstVisiblePosition();
7155 final int childCount = getChildCount();
7156 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007157 if (index >= 0 && index < childCount) {
7158 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007159 final View view = getChildAt(index);
7160 return view.getHeight();
7161 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007162 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007163 final View view = obtainView(position, mIsScrap);
7164 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7165 final int height = view.getMeasuredHeight();
7166 mRecycler.addScrapView(view, position);
7167 return height;
7168 }
7169 }
7170
7171 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007172 * Sets the selected item and positions the selection y pixels from the top edge
7173 * of the ListView. (If in touch mode, the item will not be selected but it will
7174 * still be positioned appropriately.)
7175 *
7176 * @param position Index (starting at 0) of the data item to be selected.
7177 * @param y The distance from the top edge of the ListView (plus padding) that the
7178 * item will be positioned.
7179 */
7180 public void setSelectionFromTop(int position, int y) {
7181 if (mAdapter == null) {
7182 return;
7183 }
7184
7185 if (!isInTouchMode()) {
7186 position = lookForSelectablePosition(position, true);
7187 if (position >= 0) {
7188 setNextSelectedPositionInt(position);
7189 }
7190 } else {
7191 mResurrectToPosition = position;
7192 }
7193
7194 if (position >= 0) {
7195 mLayoutMode = LAYOUT_SPECIFIC;
7196 mSpecificTop = mListPadding.top + y;
7197
7198 if (mNeedSync) {
7199 mSyncPosition = position;
7200 mSyncRowId = mAdapter.getItemId(position);
7201 }
7202
7203 if (mPositionScroller != null) {
7204 mPositionScroller.stop();
7205 }
7206 requestLayout();
7207 }
7208 }
7209
Siva Velusamy94a6d152015-05-05 15:07:00 -07007210 /** @hide */
7211 @Override
7212 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7213 super.encodeProperties(encoder);
7214
7215 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7216 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7217 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7218 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7219 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7220 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7221
7222 View selectedView = getSelectedView();
7223 if (selectedView != null) {
7224 encoder.addPropertyKey("selectedView");
7225 selectedView.encode(encoder);
7226 }
7227 }
7228
Alan Viveretted22db212014-02-13 17:47:38 -08007229 /**
7230 * Abstract positon scroller used to handle smooth scrolling.
7231 */
7232 static abstract class AbsPositionScroller {
7233 public abstract void start(int position);
7234 public abstract void start(int position, int boundPosition);
7235 public abstract void startWithOffset(int position, int offset);
7236 public abstract void startWithOffset(int position, int offset, int duration);
7237 public abstract void stop();
7238 }
7239
7240 /**
7241 * Default position scroller that simulates a fling.
7242 */
7243 class PositionScroller extends AbsPositionScroller implements Runnable {
7244 private static final int SCROLL_DURATION = 200;
7245
7246 private static final int MOVE_DOWN_POS = 1;
7247 private static final int MOVE_UP_POS = 2;
7248 private static final int MOVE_DOWN_BOUND = 3;
7249 private static final int MOVE_UP_BOUND = 4;
7250 private static final int MOVE_OFFSET = 5;
7251
7252 private int mMode;
7253 private int mTargetPos;
7254 private int mBoundPos;
7255 private int mLastSeenPos;
7256 private int mScrollDuration;
7257 private final int mExtraScroll;
7258
7259 private int mOffsetFromTop;
7260
7261 PositionScroller() {
7262 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7263 }
7264
7265 @Override
7266 public void start(final int position) {
7267 stop();
7268
7269 if (mDataChanged) {
7270 // Wait until we're back in a stable state to try this.
7271 mPositionScrollAfterLayout = new Runnable() {
7272 @Override public void run() {
7273 start(position);
7274 }
7275 };
7276 return;
7277 }
7278
7279 final int childCount = getChildCount();
7280 if (childCount == 0) {
7281 // Can't scroll without children.
7282 return;
7283 }
7284
7285 final int firstPos = mFirstPosition;
7286 final int lastPos = firstPos + childCount - 1;
7287
7288 int viewTravelCount;
7289 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7290 if (clampedPosition < firstPos) {
7291 viewTravelCount = firstPos - clampedPosition + 1;
7292 mMode = MOVE_UP_POS;
7293 } else if (clampedPosition > lastPos) {
7294 viewTravelCount = clampedPosition - lastPos + 1;
7295 mMode = MOVE_DOWN_POS;
7296 } else {
7297 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7298 return;
7299 }
7300
7301 if (viewTravelCount > 0) {
7302 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7303 } else {
7304 mScrollDuration = SCROLL_DURATION;
7305 }
7306 mTargetPos = clampedPosition;
7307 mBoundPos = INVALID_POSITION;
7308 mLastSeenPos = INVALID_POSITION;
7309
7310 postOnAnimation(this);
7311 }
7312
7313 @Override
7314 public void start(final int position, final int boundPosition) {
7315 stop();
7316
7317 if (boundPosition == INVALID_POSITION) {
7318 start(position);
7319 return;
7320 }
7321
7322 if (mDataChanged) {
7323 // Wait until we're back in a stable state to try this.
7324 mPositionScrollAfterLayout = new Runnable() {
7325 @Override public void run() {
7326 start(position, boundPosition);
7327 }
7328 };
7329 return;
7330 }
7331
7332 final int childCount = getChildCount();
7333 if (childCount == 0) {
7334 // Can't scroll without children.
7335 return;
7336 }
7337
7338 final int firstPos = mFirstPosition;
7339 final int lastPos = firstPos + childCount - 1;
7340
7341 int viewTravelCount;
7342 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7343 if (clampedPosition < firstPos) {
7344 final int boundPosFromLast = lastPos - boundPosition;
7345 if (boundPosFromLast < 1) {
7346 // Moving would shift our bound position off the screen. Abort.
7347 return;
7348 }
7349
7350 final int posTravel = firstPos - clampedPosition + 1;
7351 final int boundTravel = boundPosFromLast - 1;
7352 if (boundTravel < posTravel) {
7353 viewTravelCount = boundTravel;
7354 mMode = MOVE_UP_BOUND;
7355 } else {
7356 viewTravelCount = posTravel;
7357 mMode = MOVE_UP_POS;
7358 }
7359 } else if (clampedPosition > lastPos) {
7360 final int boundPosFromFirst = boundPosition - firstPos;
7361 if (boundPosFromFirst < 1) {
7362 // Moving would shift our bound position off the screen. Abort.
7363 return;
7364 }
7365
7366 final int posTravel = clampedPosition - lastPos + 1;
7367 final int boundTravel = boundPosFromFirst - 1;
7368 if (boundTravel < posTravel) {
7369 viewTravelCount = boundTravel;
7370 mMode = MOVE_DOWN_BOUND;
7371 } else {
7372 viewTravelCount = posTravel;
7373 mMode = MOVE_DOWN_POS;
7374 }
7375 } else {
7376 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7377 return;
7378 }
7379
7380 if (viewTravelCount > 0) {
7381 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7382 } else {
7383 mScrollDuration = SCROLL_DURATION;
7384 }
7385 mTargetPos = clampedPosition;
7386 mBoundPos = boundPosition;
7387 mLastSeenPos = INVALID_POSITION;
7388
7389 postOnAnimation(this);
7390 }
7391
7392 @Override
7393 public void startWithOffset(int position, int offset) {
7394 startWithOffset(position, offset, SCROLL_DURATION);
7395 }
7396
7397 @Override
7398 public void startWithOffset(final int position, int offset, final int duration) {
7399 stop();
7400
7401 if (mDataChanged) {
7402 // Wait until we're back in a stable state to try this.
7403 final int postOffset = offset;
7404 mPositionScrollAfterLayout = new Runnable() {
7405 @Override public void run() {
7406 startWithOffset(position, postOffset, duration);
7407 }
7408 };
7409 return;
7410 }
7411
7412 final int childCount = getChildCount();
7413 if (childCount == 0) {
7414 // Can't scroll without children.
7415 return;
7416 }
7417
7418 offset += getPaddingTop();
7419
7420 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7421 mOffsetFromTop = offset;
7422 mBoundPos = INVALID_POSITION;
7423 mLastSeenPos = INVALID_POSITION;
7424 mMode = MOVE_OFFSET;
7425
7426 final int firstPos = mFirstPosition;
7427 final int lastPos = firstPos + childCount - 1;
7428
7429 int viewTravelCount;
7430 if (mTargetPos < firstPos) {
7431 viewTravelCount = firstPos - mTargetPos;
7432 } else if (mTargetPos > lastPos) {
7433 viewTravelCount = mTargetPos - lastPos;
7434 } else {
7435 // On-screen, just scroll.
7436 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007437 smoothScrollBy(targetTop - offset, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007438 return;
7439 }
7440
7441 // Estimate how many screens we should travel
7442 final float screenTravelCount = (float) viewTravelCount / childCount;
7443 mScrollDuration = screenTravelCount < 1 ?
7444 duration : (int) (duration / screenTravelCount);
7445 mLastSeenPos = INVALID_POSITION;
7446
7447 postOnAnimation(this);
7448 }
7449
7450 /**
7451 * Scroll such that targetPos is in the visible padded region without scrolling
7452 * boundPos out of view. Assumes targetPos is onscreen.
7453 */
7454 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7455 final int firstPos = mFirstPosition;
7456 final int childCount = getChildCount();
7457 final int lastPos = firstPos + childCount - 1;
7458 final int paddedTop = mListPadding.top;
7459 final int paddedBottom = getHeight() - mListPadding.bottom;
7460
7461 if (targetPos < firstPos || targetPos > lastPos) {
7462 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7463 " not visible [" + firstPos + ", " + lastPos + "]");
7464 }
7465 if (boundPos < firstPos || boundPos > lastPos) {
7466 // boundPos doesn't matter, it's already offscreen.
7467 boundPos = INVALID_POSITION;
7468 }
7469
7470 final View targetChild = getChildAt(targetPos - firstPos);
7471 final int targetTop = targetChild.getTop();
7472 final int targetBottom = targetChild.getBottom();
7473 int scrollBy = 0;
7474
7475 if (targetBottom > paddedBottom) {
7476 scrollBy = targetBottom - paddedBottom;
7477 }
7478 if (targetTop < paddedTop) {
7479 scrollBy = targetTop - paddedTop;
7480 }
7481
7482 if (scrollBy == 0) {
7483 return;
7484 }
7485
7486 if (boundPos >= 0) {
7487 final View boundChild = getChildAt(boundPos - firstPos);
7488 final int boundTop = boundChild.getTop();
7489 final int boundBottom = boundChild.getBottom();
7490 final int absScroll = Math.abs(scrollBy);
7491
7492 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7493 // Don't scroll the bound view off the bottom of the screen.
7494 scrollBy = Math.max(0, boundBottom - paddedBottom);
7495 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7496 // Don't scroll the bound view off the top of the screen.
7497 scrollBy = Math.min(0, boundTop - paddedTop);
7498 }
7499 }
7500
7501 smoothScrollBy(scrollBy, duration);
7502 }
7503
7504 @Override
7505 public void stop() {
7506 removeCallbacks(this);
7507 }
7508
7509 @Override
7510 public void run() {
7511 final int listHeight = getHeight();
7512 final int firstPos = mFirstPosition;
7513
7514 switch (mMode) {
7515 case MOVE_DOWN_POS: {
7516 final int lastViewIndex = getChildCount() - 1;
7517 final int lastPos = firstPos + lastViewIndex;
7518
7519 if (lastViewIndex < 0) {
7520 return;
7521 }
7522
7523 if (lastPos == mLastSeenPos) {
7524 // No new views, let things keep going.
7525 postOnAnimation(this);
7526 return;
7527 }
7528
7529 final View lastView = getChildAt(lastViewIndex);
7530 final int lastViewHeight = lastView.getHeight();
7531 final int lastViewTop = lastView.getTop();
7532 final int lastViewPixelsShowing = listHeight - lastViewTop;
7533 final int extraScroll = lastPos < mItemCount - 1 ?
7534 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7535
7536 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007537 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007538
7539 mLastSeenPos = lastPos;
7540 if (lastPos < mTargetPos) {
7541 postOnAnimation(this);
7542 }
7543 break;
7544 }
7545
7546 case MOVE_DOWN_BOUND: {
7547 final int nextViewIndex = 1;
7548 final int childCount = getChildCount();
7549
7550 if (firstPos == mBoundPos || childCount <= nextViewIndex
7551 || firstPos + childCount >= mItemCount) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007552 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007553 return;
7554 }
7555 final int nextPos = firstPos + nextViewIndex;
7556
7557 if (nextPos == mLastSeenPos) {
7558 // No new views, let things keep going.
7559 postOnAnimation(this);
7560 return;
7561 }
7562
7563 final View nextView = getChildAt(nextViewIndex);
7564 final int nextViewHeight = nextView.getHeight();
7565 final int nextViewTop = nextView.getTop();
7566 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7567 if (nextPos < mBoundPos) {
7568 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007569 mScrollDuration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007570
7571 mLastSeenPos = nextPos;
7572
7573 postOnAnimation(this);
7574 } else {
7575 if (nextViewTop > extraScroll) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007576 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7577 } else {
7578 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007579 }
7580 }
7581 break;
7582 }
7583
7584 case MOVE_UP_POS: {
7585 if (firstPos == mLastSeenPos) {
7586 // No new views, let things keep going.
7587 postOnAnimation(this);
7588 return;
7589 }
7590
7591 final View firstView = getChildAt(0);
7592 if (firstView == null) {
7593 return;
7594 }
7595 final int firstViewTop = firstView.getTop();
7596 final int extraScroll = firstPos > 0 ?
7597 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7598
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007599 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7600 firstPos > mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007601
7602 mLastSeenPos = firstPos;
7603
7604 if (firstPos > mTargetPos) {
7605 postOnAnimation(this);
7606 }
7607 break;
7608 }
7609
7610 case MOVE_UP_BOUND: {
7611 final int lastViewIndex = getChildCount() - 2;
7612 if (lastViewIndex < 0) {
7613 return;
7614 }
7615 final int lastPos = firstPos + lastViewIndex;
7616
7617 if (lastPos == mLastSeenPos) {
7618 // No new views, let things keep going.
7619 postOnAnimation(this);
7620 return;
7621 }
7622
7623 final View lastView = getChildAt(lastViewIndex);
7624 final int lastViewHeight = lastView.getHeight();
7625 final int lastViewTop = lastView.getTop();
7626 final int lastViewPixelsShowing = listHeight - lastViewTop;
7627 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7628 mLastSeenPos = lastPos;
7629 if (lastPos > mBoundPos) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007630 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7631 true);
Alan Viveretted22db212014-02-13 17:47:38 -08007632 postOnAnimation(this);
7633 } else {
7634 final int bottom = listHeight - extraScroll;
7635 final int lastViewBottom = lastViewTop + lastViewHeight;
7636 if (bottom > lastViewBottom) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007637 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7638 } else {
7639 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007640 }
7641 }
7642 break;
7643 }
7644
7645 case MOVE_OFFSET: {
7646 if (mLastSeenPos == firstPos) {
7647 // No new views, let things keep going.
7648 postOnAnimation(this);
7649 return;
7650 }
7651
7652 mLastSeenPos = firstPos;
7653
7654 final int childCount = getChildCount();
7655 final int position = mTargetPos;
7656 final int lastPos = firstPos + childCount - 1;
7657
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007658 // Account for the visible "portion" of the first / last child when we estimate
7659 // how many screens we should travel to reach our target
7660 final View firstChild = getChildAt(0);
7661 final int firstChildHeight = firstChild.getHeight();
7662 final View lastChild = getChildAt(childCount - 1);
7663 final int lastChildHeight = lastChild.getHeight();
7664 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7665 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7666 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7667 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7668 / lastChildHeight;
7669
7670 float viewTravelCount = 0;
Alan Viveretted22db212014-02-13 17:47:38 -08007671 if (position < firstPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007672 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
Alan Viveretted22db212014-02-13 17:47:38 -08007673 } else if (position > lastPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007674 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
Alan Viveretted22db212014-02-13 17:47:38 -08007675 }
7676
7677 // Estimate how many screens we should travel
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007678 final float screenTravelCount = viewTravelCount / childCount;
Alan Viveretted22db212014-02-13 17:47:38 -08007679
7680 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7681 if (position < firstPos) {
7682 final int distance = (int) (-getHeight() * modifier);
7683 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007684 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007685 postOnAnimation(this);
7686 } else if (position > lastPos) {
7687 final int distance = (int) (getHeight() * modifier);
7688 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007689 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007690 postOnAnimation(this);
7691 } else {
7692 // On-screen, just scroll.
7693 final int targetTop = getChildAt(position - firstPos).getTop();
7694 final int distance = targetTop - mOffsetFromTop;
7695 final int duration = (int) (mScrollDuration *
7696 ((float) Math.abs(distance) / getHeight()));
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007697 smoothScrollBy(distance, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007698 }
7699 break;
7700 }
7701
7702 default:
7703 break;
7704 }
7705 }
7706 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007707}