blob: 4752eadfe38526b05e0fc630e7e75121a4edd74c [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;
Artur Satayevad9254c2019-12-10 17:47:54 +000023import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070025import android.content.Intent;
Adam Powell2fe301d2016-08-15 16:34:37 -070026import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.content.res.TypedArray;
28import android.graphics.Canvas;
29import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.graphics.drawable.TransitionDrawable;
Mathew Inwood31755f92018-12-20 13:53:36 +000032import android.os.Build;
alanvc1d7e772012-05-08 14:47:24 -070033import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.os.Debug;
Yohei Yukawa612cce92016-02-11 17:47:33 -080035import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.os.Parcel;
37import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080038import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070039import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070041import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.text.TextUtils;
43import android.text.TextWatcher;
44import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080045import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070046import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080047import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070048import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070049import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070050import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070051import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.view.Gravity;
53import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080054import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import android.view.KeyEvent;
56import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070057import android.view.Menu;
58import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059import android.view.MotionEvent;
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -080060import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061import android.view.VelocityTracker;
62import android.view.View;
63import android.view.ViewConfiguration;
64import android.view.ViewDebug;
65import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070066import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070067import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070069import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070070import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070071import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070072import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080073import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070074import android.view.animation.Interpolator;
75import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070076import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070077import android.view.inputmethod.CompletionInfo;
78import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070079import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070080import android.view.inputmethod.ExtractedText;
81import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070082import android.view.inputmethod.InputConnection;
Yohei Yukawa152944f2016-06-10 19:04:34 -070083import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084import android.view.inputmethod.InputMethodManager;
Ashley Rose55f9f922019-01-28 19:29:36 -050085import android.view.inspector.InspectableProperty;
Ashley Rose73abdd3c2019-03-15 17:32:56 -040086import android.view.inspector.InspectableProperty.EnumEntry;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070087import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
Adam Cohen335c3b62012-07-24 17:18:16 -070089import com.android.internal.R;
90
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091import java.util.ArrayList;
92import java.util.List;
93
94/**
Romain Guyd6a463a2009-05-21 23:10:10 -070095 * Base class that can be used to implement virtualized lists of items. A list does
kopriva60b06bd2018-03-20 20:11:31 -070096 * not have a spatial definition here. For instance, subclasses of this class can
Romain Guyd6a463a2009-05-21 23:10:10 -070097 * 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 -080098 *
99 * @attr ref android.R.styleable#AbsListView_listSelector
100 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
101 * @attr ref android.R.styleable#AbsListView_stackFromBottom
102 * @attr ref android.R.styleable#AbsListView_scrollingCache
103 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
104 * @attr ref android.R.styleable#AbsListView_transcriptMode
105 * @attr ref android.R.styleable#AbsListView_cacheColorHint
106 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
107 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -0700108 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 */
110public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
111 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700112 ViewTreeObserver.OnTouchModeChangeListener,
113 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114
Romain Guy9d849a22012-03-14 16:41:42 -0700115 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800116 private static final String TAG = "AbsListView";
117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 /**
119 * Disables the transcript mode.
120 *
121 * @see #setTranscriptMode(int)
122 */
123 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 /**
126 * The list will automatically scroll to the bottom when a data set change
127 * notification is received and only if the last item is already visible
128 * on screen.
129 *
130 * @see #setTranscriptMode(int)
131 */
132 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700133
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 /**
135 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700136 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 *
138 * @see #setTranscriptMode(int)
139 */
140 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
141
142 /**
143 * Indicates that we are not in the middle of a touch gesture
144 */
145 static final int TOUCH_MODE_REST = -1;
146
147 /**
148 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
149 * scroll gesture.
150 */
151 static final int TOUCH_MODE_DOWN = 0;
152
153 /**
154 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
155 * is a longpress
156 */
157 static final int TOUCH_MODE_TAP = 1;
158
159 /**
160 * Indicates we have waited for everything we can wait for, but the user's finger is still down
161 */
162 static final int TOUCH_MODE_DONE_WAITING = 2;
163
164 /**
165 * Indicates the touch gesture is a scroll
166 */
167 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 /**
170 * Indicates the view is in the process of being flung
171 */
172 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 /**
Adam Powell637d3372010-08-25 14:37:03 -0700175 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
176 */
177 static final int TOUCH_MODE_OVERSCROLL = 5;
178
179 /**
180 * Indicates the view is being flung outside of normal content bounds
181 * and will spring back.
182 */
183 static final int TOUCH_MODE_OVERFLING = 6;
184
185 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 * Regular layout - usually an unsolicited layout from the view system
187 */
188 static final int LAYOUT_NORMAL = 0;
189
190 /**
191 * Show the first item
192 */
193 static final int LAYOUT_FORCE_TOP = 1;
194
195 /**
196 * Force the selected item to be on somewhere on the screen
197 */
198 static final int LAYOUT_SET_SELECTION = 2;
199
200 /**
201 * Show the last item
202 */
203 static final int LAYOUT_FORCE_BOTTOM = 3;
204
205 /**
206 * Make a mSelectedItem appear in a specific location and build the rest of
207 * the views from there. The top is specified by mSpecificTop.
208 */
209 static final int LAYOUT_SPECIFIC = 4;
210
211 /**
212 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
213 * at mSpecificTop
214 */
215 static final int LAYOUT_SYNC = 5;
216
217 /**
218 * Layout as a result of using the navigation keys
219 */
220 static final int LAYOUT_MOVE_SELECTION = 6;
221
222 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700223 * Normal list that does not indicate choices
224 */
225 public static final int CHOICE_MODE_NONE = 0;
226
227 /**
228 * The list allows up to one choice
229 */
230 public static final int CHOICE_MODE_SINGLE = 1;
231
232 /**
233 * The list allows multiple choices
234 */
235 public static final int CHOICE_MODE_MULTIPLE = 2;
236
237 /**
238 * The list allows multiple choices in a modal selection mode
239 */
240 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
241
242 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700243 * The thread that created this view.
244 */
245 private final Thread mOwnerThread;
246
247 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700248 * Controls if/how the user may choose/check items in the list
249 */
250 int mChoiceMode = CHOICE_MODE_NONE;
251
252 /**
253 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
254 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100255 @UnsupportedAppUsage
Adam Powellf343e1b2010-08-13 18:27:04 -0700256 ActionMode mChoiceActionMode;
257
258 /**
259 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
260 * a few extra actions around what application code does.
261 */
262 MultiChoiceModeWrapper mMultiChoiceModeCallback;
263
264 /**
265 * Running count of how many items are currently checked
266 */
267 int mCheckedItemCount;
268
269 /**
270 * Running state of which positions are currently checked
271 */
272 SparseBooleanArray mCheckStates;
273
274 /**
Adam Powell14c08042011-10-06 19:46:18 -0700275 * Running state of which IDs are currently checked.
276 * If there is a value for a given key, the checked state for that ID is true
277 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700278 */
Adam Powell14c08042011-10-06 19:46:18 -0700279 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700280
281 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 * Controls how the next layout will happen
283 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100284 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 int mLayoutMode = LAYOUT_NORMAL;
286
287 /**
288 * Should be used by subclasses to listen to changes in the dataset
289 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100290 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 AdapterDataSetObserver mDataSetObserver;
292
293 /**
294 * The adapter containing the data to be displayed by this view
295 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100296 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 ListAdapter mAdapter;
298
299 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700300 * The remote adapter containing the data to be displayed by this view to be set
301 */
302 private RemoteViewsAdapter mRemoteAdapter;
303
304 /**
Adam Powell539ee872012-02-03 19:00:49 -0800305 * If mAdapter != null, whenever this is true the adapter has stable IDs.
306 */
307 boolean mAdapterHasStableIds;
308
309 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700310 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
311 */
312 private boolean mDeferNotifyDataSetChanged = false;
313
314 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 * Indicates whether the list selector should be drawn on top of the children or behind
316 */
317 boolean mDrawSelectorOnTop = false;
318
319 /**
320 * The drawable used to draw the selector
321 */
Mathew Inwood31755f92018-12-20 13:53:36 +0000322 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 Drawable mSelector;
324
325 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700326 * The current position of the selector in the list.
327 */
Yigit Boyar0d305f22019-02-04 23:58:33 -0800328 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Dianne Hackborn079e2352010-10-18 17:02:43 -0700329 int mSelectorPosition = INVALID_POSITION;
330
331 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 * Defines the selector's location and dimension at drawing time
333 */
Yigit Boyar0d305f22019-02-04 23:58:33 -0800334 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 Rect mSelectorRect = new Rect();
336
337 /**
338 * The data set used to store unused views that should be reused during the next layout
339 * to avoid creating new ones
340 */
Filip Pavlisab802912019-02-07 16:06:06 +0000341 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769398)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 final RecycleBin mRecycler = new RecycleBin();
343
344 /**
345 * The selection's left padding
346 */
347 int mSelectionLeftPadding = 0;
348
349 /**
350 * The selection's top padding
351 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100352 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 int mSelectionTopPadding = 0;
354
355 /**
356 * The selection's right padding
357 */
358 int mSelectionRightPadding = 0;
359
360 /**
361 * The selection's bottom padding
362 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100363 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 int mSelectionBottomPadding = 0;
365
366 /**
367 * This view's padding
368 */
369 Rect mListPadding = new Rect();
370
371 /**
372 * Subclasses must retain their measure spec from onMeasure() into this member
373 */
374 int mWidthMeasureSpec = 0;
375
376 /**
377 * The top scroll indicator
378 */
379 View mScrollUp;
380
381 /**
382 * The down scroll indicator
383 */
384 View mScrollDown;
385
386 /**
387 * When the view is scrolling, this flag is set to true to indicate subclasses that
388 * the drawing cache was enabled on the children
389 */
390 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800391 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392
393 /**
394 * The position of the view that received the down motion event
395 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100396 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 int mMotionPosition;
398
399 /**
400 * The offset to the top of the mMotionPosition view when the down motion event was received
401 */
402 int mMotionViewOriginalTop;
403
404 /**
405 * The desired offset to the top of the mMotionPosition view after a scroll
406 */
407 int mMotionViewNewTop;
408
409 /**
410 * The X value associated with the the down motion event
411 */
412 int mMotionX;
413
414 /**
415 * The Y value associated with the the down motion event
416 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100417 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 int mMotionY;
419
420 /**
421 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
422 * TOUCH_MODE_DONE_WAITING
423 */
Filip Pavlisab802912019-02-07 16:06:06 +0000424 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769413)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 int mTouchMode = TOUCH_MODE_REST;
426
427 /**
428 * Y value from on the previous motion event (if any)
429 */
430 int mLastY;
431
432 /**
433 * How far the finger moved before we started scrolling
434 */
435 int mMotionCorrection;
436
437 /**
438 * Determines speed during touch scrolling
439 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100440 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 private VelocityTracker mVelocityTracker;
442
443 /**
444 * Handles one frame of a fling
Louis Pullen-Freilichf0955ac2019-02-06 19:29:34 +0000445 *
446 * To interrupt a fling early you should use smoothScrollBy(0,0) instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 */
Louis Pullen-Freilichf0955ac2019-02-06 19:29:34 +0000448 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800450
Adam Powell45803472010-01-25 15:10:44 -0800451 /**
452 * Handles scrolling between positions within the list.
453 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100454 @UnsupportedAppUsage
Alan Viveretted22db212014-02-13 17:47:38 -0800455 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456
457 /**
458 * The offset in pixels form the top of the AdapterView to the top
459 * of the currently selected view. Used to save and restore state.
460 */
461 int mSelectedTop = 0;
462
463 /**
464 * Indicates whether the list is stacked from the bottom edge or
465 * the top edge.
466 */
467 boolean mStackFromBottom;
468
469 /**
470 * When set to true, the list automatically discards the children's
471 * bitmap cache after scrolling.
472 */
473 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700474
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 /**
476 * Whether or not to enable the fast scroll feature on this list
477 */
478 boolean mFastScrollEnabled;
479
480 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700481 * Whether or not to always show the fast scroll feature on this list
482 */
483 boolean mFastScrollAlwaysVisible;
484
485 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 * Optional callback to notify client when scroll position has changed
487 */
Nader Jawad599a0e22019-02-05 16:54:22 -0800488 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769353)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 private OnScrollListener mOnScrollListener;
490
491 /**
492 * Keeps track of our accessory window
493 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100494 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 PopupWindow mPopup;
496
497 /**
498 * Used with type filter window
499 */
500 EditText mTextFilter;
501
502 /**
503 * Indicates whether to use pixels-based or position-based scrollbar
504 * properties.
505 */
506 private boolean mSmoothScrollbarEnabled = true;
507
508 /**
509 * Indicates that this view supports filtering
510 */
511 private boolean mTextFilterEnabled;
512
513 /**
514 * Indicates that this view is currently displaying a filtered view of the data
515 */
516 private boolean mFiltered;
517
518 /**
519 * Rectangle used for hit testing children
520 */
521 private Rect mTouchFrame;
522
523 /**
524 * The position to resurrect the selected position to.
525 */
526 int mResurrectToPosition = INVALID_POSITION;
527
Mathew Inwood978c6e22018-08-21 15:58:55 +0100528 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800530
Adam Powell0b8bb422010-02-08 14:30:45 -0800531 /**
Adam Powell637d3372010-08-25 14:37:03 -0700532 * Maximum distance to record overscroll
533 */
534 int mOverscrollMax;
535
536 /**
537 * Content height divided by this is the overscroll limit.
538 */
539 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
540
541 /**
Adam Powell14c08042011-10-06 19:46:18 -0700542 * How many positions in either direction we will search to try to
543 * find a checked item with a stable ID that moved position across
544 * a data set change. If the item isn't found it will be unselected.
545 */
546 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
547
548 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 * Used to request a layout when we changed touch mode
550 */
551 private static final int TOUCH_MODE_UNKNOWN = -1;
552 private static final int TOUCH_MODE_ON = 0;
553 private static final int TOUCH_MODE_OFF = 1;
554
555 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
556
557 private static final boolean PROFILE_SCROLLING = false;
558 private boolean mScrollProfilingStarted = false;
559
560 private static final boolean PROFILE_FLINGING = false;
561 private boolean mFlingProfilingStarted = false;
562
563 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800564 * The StrictMode "critical time span" objects to catch animation
565 * stutters. Non-null when a time-sensitive animation is
566 * in-flight. Must call finish() on them when done animating.
567 * These are no-ops on user builds.
568 */
569 private StrictMode.Span mScrollStrictSpan = null;
570 private StrictMode.Span mFlingStrictSpan = null;
571
572 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800573 * The last CheckForLongPress runnable we posted, if any
574 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100575 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 private CheckForLongPress mPendingCheckForLongPress;
577
578 /**
579 * The last CheckForTap runnable we posted, if any
580 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100581 @UnsupportedAppUsage
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700582 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700583
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 /**
585 * The last CheckForKeyLongPress runnable we posted, if any
586 */
587 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
588
589 /**
590 * Acts upon click
591 */
592 private AbsListView.PerformClick mPerformClick;
593
594 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800595 * Delayed action for touch mode.
596 */
597 private Runnable mTouchModeReset;
598
599 /**
Alan Viverette66df60f2016-01-28 14:56:07 -0500600 * Whether the most recent touch event stream resulted in a successful
601 * long-press action. This is reset on TOUCH_DOWN.
602 */
603 private boolean mHasPerformedLongPress;
604
605 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 * This view is in transcript mode -- it shows the bottom of the list when the data
607 * changes
608 */
609 private int mTranscriptMode;
610
611 /**
612 * Indicates that this list is always drawn on top of a solid, single-color, opaque
613 * background
614 */
615 private int mCacheColorHint;
616
617 /**
618 * The select child's view (from the adapter's getView) is enabled.
619 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100620 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 private boolean mIsChildViewEnabled;
622
623 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800624 * The cached drawable state for the selector. Accounts for child enabled
625 * state, but otherwise identical to the view's own drawable state.
626 */
627 private int[] mSelectorState;
628
629 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 * The last scroll state reported to clients through {@link OnScrollListener}.
631 */
632 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
633
634 /**
635 * Helper object that renders and controls the fast scroll thumb.
636 */
Filip Pavlisab802912019-02-07 16:06:06 +0000637 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768941)
Alan Viverette8636ace2013-10-31 15:41:31 -0700638 private FastScroller mFastScroll;
639
640 /**
641 * Temporary holder for fast scroller style until a FastScroller object
642 * is created.
643 */
644 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800645
Romain Guyd6a463a2009-05-21 23:10:10 -0700646 private boolean mGlobalLayoutListenerAddedFilter;
647
Mathew Inwood978c6e22018-08-21 15:58:55 +0100648 @UnsupportedAppUsage
Romain Guyd6a463a2009-05-21 23:10:10 -0700649 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 private float mDensityScale;
651
Aaron Whytef8306522017-03-22 16:30:58 -0700652 private float mVerticalScrollFactor;
Ned Burns20ad0732016-08-18 14:22:57 -0400653
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700654 private InputConnection mDefInputConnection;
655 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700656
657 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700658 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700659 private int mMinimumVelocity;
Rahul Ravikumar48a7cdd2019-02-08 16:45:00 -0800660 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051740)
Romain Guy4296fc42009-07-06 11:48:52 -0700661 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700662 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800663
Romain Guy21875052010-01-06 18:48:08 -0800664 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800665
Adam Powell96d62af2014-05-02 10:04:38 -0700666 private final int[] mScrollOffset = new int[2];
667 private final int[] mScrollConsumed = new int[2];
668
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800669 private final float[] mTmpPoint = new float[2];
670
Adam Powell744beff2014-09-22 09:47:48 -0700671 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
672 // In the future it would be nice to be able to give this to the VelocityTracker
673 // directly, or alternatively put a VT into absolute-positioning mode that only
674 // reads the raw screen-coordinate x/y values.
675 private int mNestedYOffset = 0;
676
Romain Guy24562482010-02-01 14:56:19 -0800677 // True when the popup should be hidden because of a call to
678 // dispatchDisplayHint()
679 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800680
Adam Powell4cd47702010-02-25 11:21:14 -0800681 /**
682 * ID of the active pointer. This is used to retain consistency during
683 * drags/flings if multiple pointers are used.
684 */
Mathew Inwood31755f92018-12-20 13:53:36 +0000685 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Adam Powell4cd47702010-02-25 11:21:14 -0800686 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800687
Adam Powell4cd47702010-02-25 11:21:14 -0800688 /**
689 * Sentinel value for no current active pointer.
690 * Used by {@link #mActivePointerId}.
691 */
692 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700693
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 /**
Adam Powell637d3372010-08-25 14:37:03 -0700695 * Maximum distance to overscroll by during edge effects
696 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100697 @UnsupportedAppUsage
Adam Powell637d3372010-08-25 14:37:03 -0700698 int mOverscrollDistance;
699
700 /**
701 * Maximum distance to overfling during edge effects
702 */
Filip Pavlisab802912019-02-07 16:06:06 +0000703 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769379)
Adam Powell637d3372010-08-25 14:37:03 -0700704 int mOverflingDistance;
705
706 // These two EdgeGlows are always set and used together.
707 // Checking one for null is as good as checking both.
708
709 /**
710 * Tracks the state of the top edge glow.
Yigit Boyarb6218472019-02-06 10:07:06 -0800711 *
712 * Even though this field is practically final, we cannot make it final because there are apps
713 * setting it via reflection and they need to keep working until they target Q.
Adam Powell637d3372010-08-25 14:37:03 -0700714 */
Yigit Boyarb6218472019-02-06 10:07:06 -0800715 @NonNull
716 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769408)
717 private EdgeEffect mEdgeGlowTop = new EdgeEffect(mContext);
Adam Powell637d3372010-08-25 14:37:03 -0700718
719 /**
720 * Tracks the state of the bottom edge glow.
Yigit Boyarb6218472019-02-06 10:07:06 -0800721 *
722 * Even though this field is practically final, we cannot make it final because there are apps
723 * setting it via reflection and they need to keep working until they target Q.
Adam Powell637d3372010-08-25 14:37:03 -0700724 */
Yigit Boyarb6218472019-02-06 10:07:06 -0800725 @NonNull
726 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768444)
727 private EdgeEffect mEdgeGlowBottom = new EdgeEffect(mContext);
Adam Powell637d3372010-08-25 14:37:03 -0700728
729 /**
730 * An estimate of how many pixels are between the top of the list and
731 * the top of the first position in the adapter, based on the last time
732 * we saw it. Used to hint where to draw edge glows.
733 */
734 private int mFirstPositionDistanceGuess;
735
736 /**
737 * An estimate of how many pixels are between the bottom of the list and
738 * the bottom of the last position in the adapter, based on the last time
739 * we saw it. Used to hint where to draw edge glows.
740 */
741 private int mLastPositionDistanceGuess;
742
743 /**
744 * Used for determining when to cancel out of overscroll.
745 */
746 private int mDirection = 0;
747
748 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800749 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
750 * the bottom correctly on resizes.
751 */
752 private boolean mForceTranscriptScroll;
753
alanvc1d7e772012-05-08 14:47:24 -0700754 /**
755 * Used for interacting with list items from an accessibility service.
756 */
757 private ListItemAccessibilityDelegate mAccessibilityDelegate;
758
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700759 private int mLastAccessibilityScrollEventFromIndex;
760 private int mLastAccessibilityScrollEventToIndex;
761
Adam Powellda13dba2010-12-05 13:47:23 -0800762 /**
Adam Powellee78b172011-08-16 16:39:20 -0700763 * Track the item count from the last time we handled a data change.
764 */
765 private int mLastHandledItemCount;
766
767 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700768 * Used for smooth scrolling at a consistent rate
769 */
770 static final Interpolator sLinearInterpolator = new LinearInterpolator();
771
772 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700773 * The saved state that we will be restoring from when we next sync.
774 * Kept here so that if we happen to be asked to save our state before
775 * the sync happens, we can return this existing data rather than losing
776 * it.
777 */
778 private SavedState mPendingSync;
779
780 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800781 * Whether the view is in the process of detaching from its window.
782 */
783 private boolean mIsDetaching;
784
785 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 * Interface definition for a callback to be invoked when the list or grid
787 * has been scrolled.
788 */
789 public interface OnScrollListener {
790
791 /**
792 * The view is not scrolling. Note navigating the list using the trackball counts as
793 * being in the idle state since these transitions are not animated.
794 */
795 public static int SCROLL_STATE_IDLE = 0;
796
797 /**
798 * The user is scrolling using touch, and their finger is still on the screen
799 */
800 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
801
802 /**
803 * The user had previously been scrolling using touch and had performed a fling. The
804 * animation is now coasting to a stop
805 */
806 public static int SCROLL_STATE_FLING = 2;
807
808 /**
809 * Callback method to be invoked while the list view or grid view is being scrolled. If the
810 * view is being scrolled, this method will be called before the next frame of the scroll is
811 * rendered. In particular, it will be called before any calls to
812 * {@link Adapter#getView(int, View, ViewGroup)}.
813 *
814 * @param view The view whose scroll state is being reported
815 *
Yorke Lee43943d82014-05-08 10:15:20 -0700816 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800817 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
818 */
819 public void onScrollStateChanged(AbsListView view, int scrollState);
820
821 /**
822 * Callback method to be invoked when the list or grid has been scrolled. This will be
823 * called after the scroll has completed
824 * @param view The view whose scroll state is being reported
825 * @param firstVisibleItem the index of the first visible cell (ignore if
826 * visibleItemCount == 0)
827 * @param visibleItemCount the number of visible cells
Joshua Baxterdf66bfa2018-06-04 13:30:17 -0700828 * @param totalItemCount the number of items in the list adapter
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 */
830 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
831 int totalItemCount);
832 }
833
Dianne Hackborne2136772010-11-04 15:08:59 -0700834 /**
835 * The top-level view of a list item can implement this interface to allow
836 * itself to modify the bounds of the selection shown for that item.
837 */
838 public interface SelectionBoundsAdjuster {
839 /**
840 * Called to allow the list item to adjust the bounds shown for
841 * its selection.
842 *
843 * @param bounds On call, this contains the bounds the list has
844 * selected for the item (that is the bounds of the entire view). The
845 * values can be modified as desired.
846 */
847 public void adjustListItemSelectionBounds(Rect bounds);
848 }
849
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800850 public AbsListView(Context context) {
851 super(context);
852 initAbsListView();
853
Alan Viverette39bed692013-08-07 15:47:04 -0700854 mOwnerThread = Thread.currentThread();
855
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 setVerticalScrollBarEnabled(true);
857 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700858 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 a.recycle();
860 }
861
862 public AbsListView(Context context, AttributeSet attrs) {
863 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
864 }
865
Alan Viverette617feb92013-09-09 18:09:13 -0700866 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
867 this(context, attrs, defStyleAttr, 0);
868 }
869
870 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
871 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872 initAbsListView();
873
Alan Viverette39bed692013-08-07 15:47:04 -0700874 mOwnerThread = Thread.currentThread();
875
Alan Viverette617feb92013-09-09 18:09:13 -0700876 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700877 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800878 saveAttributeDataForStyleable(context, R.styleable.AbsListView, attrs, a, defStyleAttr,
879 defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880
Alan Viverette7eceda32015-06-01 10:47:29 -0700881 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
882 if (selector != null) {
883 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800884 }
885
Alan Viverette7eceda32015-06-01 10:47:29 -0700886 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887
Alan Viverette7eceda32015-06-01 10:47:29 -0700888 setStackFromBottom(a.getBoolean(
889 R.styleable.AbsListView_stackFromBottom, false));
890 setScrollingCacheEnabled(a.getBoolean(
891 R.styleable.AbsListView_scrollingCache, true));
892 setTextFilterEnabled(a.getBoolean(
893 R.styleable.AbsListView_textFilterEnabled, false));
894 setTranscriptMode(a.getInt(
895 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
896 setCacheColorHint(a.getColor(
897 R.styleable.AbsListView_cacheColorHint, 0));
898 setSmoothScrollbarEnabled(a.getBoolean(
899 R.styleable.AbsListView_smoothScrollbar, true));
900 setChoiceMode(a.getInt(
901 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902
Alan Viverette7eceda32015-06-01 10:47:29 -0700903 setFastScrollEnabled(a.getBoolean(
904 R.styleable.AbsListView_fastScrollEnabled, false));
905 setFastScrollStyle(a.getResourceId(
906 R.styleable.AbsListView_fastScrollStyle, 0));
907 setFastScrollAlwaysVisible(a.getBoolean(
908 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700909
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910 a.recycle();
Adam Powell2fe301d2016-08-15 16:34:37 -0700911
912 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
913 setRevealOnFocusHint(false);
914 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 }
916
Romain Guyd6a463a2009-05-21 23:10:10 -0700917 private void initAbsListView() {
918 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700919 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700920 setFocusableInTouchMode(true);
921 setWillNotDraw(false);
922 setAlwaysDrawnWithCacheEnabled(false);
923 setScrollingCacheEnabled(true);
924
Romain Guy4296fc42009-07-06 11:48:52 -0700925 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
926 mTouchSlop = configuration.getScaledTouchSlop();
Aaron Whytef8306522017-03-22 16:30:58 -0700927 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
Romain Guy4296fc42009-07-06 11:48:52 -0700928 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
929 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700930 mOverscrollDistance = configuration.getScaledOverscrollDistance();
931 mOverflingDistance = configuration.getScaledOverflingDistance();
932
Romain Guyd6a463a2009-05-21 23:10:10 -0700933 mDensityScale = getContext().getResources().getDisplayMetrics().density;
934 }
Romain Guy0a637162009-05-29 14:43:54 -0700935
Romain Guyd6a463a2009-05-21 23:10:10 -0700936 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700937 * {@inheritDoc}
938 */
939 @Override
940 public void setAdapter(ListAdapter adapter) {
941 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800942 mAdapterHasStableIds = mAdapter.hasStableIds();
943 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700944 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700945 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700946 }
947 }
Sumir Kataria37b85672017-07-11 15:35:30 -0700948 clearChoices();
Adam Powellf343e1b2010-08-13 18:27:04 -0700949 }
950
951 /**
952 * Returns the number of items currently selected. This will only be valid
953 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
954 *
955 * <p>To determine the specific items that are currently selected, use one of
956 * the <code>getChecked*</code> methods.
957 *
958 * @return The number of items currently selected
959 *
960 * @see #getCheckedItemPosition()
961 * @see #getCheckedItemPositions()
962 * @see #getCheckedItemIds()
963 */
964 public int getCheckedItemCount() {
965 return mCheckedItemCount;
966 }
967
968 /**
969 * Returns the checked state of the specified position. The result is only
970 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
971 * or {@link #CHOICE_MODE_MULTIPLE}.
972 *
973 * @param position The item whose checked state to return
974 * @return The item's checked state or <code>false</code> if choice mode
975 * is invalid
976 *
977 * @see #setChoiceMode(int)
978 */
979 public boolean isItemChecked(int position) {
980 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
981 return mCheckStates.get(position);
982 }
983
984 return false;
985 }
986
987 /**
988 * Returns the currently checked item. The result is only valid if the choice
989 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
990 *
991 * @return The position of the currently checked item or
992 * {@link #INVALID_POSITION} if nothing is selected
993 *
994 * @see #setChoiceMode(int)
995 */
996 public int getCheckedItemPosition() {
997 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
998 return mCheckStates.keyAt(0);
999 }
1000
1001 return INVALID_POSITION;
1002 }
1003
1004 /**
1005 * Returns the set of checked items in the list. The result is only valid if
1006 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
1007 *
1008 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +02001009 * get(int position) where position is a checked position in the
1010 * list and false otherwise, or <code>null</code> if the choice
1011 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -07001012 */
1013 public SparseBooleanArray getCheckedItemPositions() {
1014 if (mChoiceMode != CHOICE_MODE_NONE) {
1015 return mCheckStates;
1016 }
1017 return null;
1018 }
1019
1020 /**
1021 * Returns the set of checked items ids. The result is only valid if the
1022 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
1023 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
1024 *
1025 * @return A new array which contains the id of each checked item in the
1026 * list.
1027 */
1028 public long[] getCheckedItemIds() {
1029 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
1030 return new long[0];
1031 }
1032
Adam Powell14c08042011-10-06 19:46:18 -07001033 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -07001034 final int count = idStates.size();
1035 final long[] ids = new long[count];
1036
1037 for (int i = 0; i < count; i++) {
1038 ids[i] = idStates.keyAt(i);
1039 }
1040
1041 return ids;
1042 }
1043
1044 /**
1045 * Clear any choices previously set
1046 */
1047 public void clearChoices() {
1048 if (mCheckStates != null) {
1049 mCheckStates.clear();
1050 }
1051 if (mCheckedIdStates != null) {
1052 mCheckedIdStates.clear();
1053 }
1054 mCheckedItemCount = 0;
1055 }
1056
1057 /**
1058 * Sets the checked state of the specified position. The is only valid if
1059 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1060 * {@link #CHOICE_MODE_MULTIPLE}.
1061 *
1062 * @param position The item whose checked state is to be checked
1063 * @param value The new checked state for the item
1064 */
1065 public void setItemChecked(int position, boolean value) {
1066 if (mChoiceMode == CHOICE_MODE_NONE) {
1067 return;
1068 }
1069
1070 // Start selection mode if needed. We don't need to if we're unchecking something.
1071 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001072 if (mMultiChoiceModeCallback == null ||
1073 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1074 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1075 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1076 "supplied. Call setMultiChoiceModeListener to set a callback.");
1077 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001078 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1079 }
1080
Siyamed Sinir135554e2016-01-22 18:40:42 -08001081 final boolean itemCheckChanged;
Adam Powellf343e1b2010-08-13 18:27:04 -07001082 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1083 boolean oldValue = mCheckStates.get(position);
1084 mCheckStates.put(position, value);
1085 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1086 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001087 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001088 } else {
1089 mCheckedIdStates.delete(mAdapter.getItemId(position));
1090 }
1091 }
Siyamed Sinir135554e2016-01-22 18:40:42 -08001092 itemCheckChanged = oldValue != value;
1093 if (itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001094 if (value) {
1095 mCheckedItemCount++;
1096 } else {
1097 mCheckedItemCount--;
1098 }
1099 }
1100 if (mChoiceActionMode != null) {
1101 final long id = mAdapter.getItemId(position);
1102 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1103 position, id, value);
1104 }
1105 } else {
1106 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1107 // Clear all values if we're checking something, or unchecking the currently
1108 // selected item
Siyamed Sinir135554e2016-01-22 18:40:42 -08001109 itemCheckChanged = isItemChecked(position) != value;
Adam Powellf343e1b2010-08-13 18:27:04 -07001110 if (value || isItemChecked(position)) {
1111 mCheckStates.clear();
1112 if (updateIds) {
1113 mCheckedIdStates.clear();
1114 }
1115 }
1116 // this may end up selecting the value we just cleared but this way
1117 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1118 if (value) {
1119 mCheckStates.put(position, true);
1120 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001121 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001122 }
1123 mCheckedItemCount = 1;
1124 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1125 mCheckedItemCount = 0;
1126 }
1127 }
1128
Siyamed Sinir135554e2016-01-22 18:40:42 -08001129 // Do not generate a data change while we are in the layout phase or data has not changed
1130 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001131 mDataChanged = true;
1132 rememberSyncState();
1133 requestLayout();
1134 }
1135 }
1136
1137 @Override
1138 public boolean performItemClick(View view, int position, long id) {
1139 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001140 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001141
1142 if (mChoiceMode != CHOICE_MODE_NONE) {
1143 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001144 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001145
1146 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1147 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001148 boolean checked = !mCheckStates.get(position, false);
1149 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001150 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001151 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001152 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001153 } else {
1154 mCheckedIdStates.delete(mAdapter.getItemId(position));
1155 }
1156 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001157 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001158 mCheckedItemCount++;
1159 } else {
1160 mCheckedItemCount--;
1161 }
1162 if (mChoiceActionMode != null) {
1163 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001164 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001165 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001166 }
Adam Powell29382d92012-02-23 11:03:22 -08001167 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001168 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001169 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001170 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001171 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001172 mCheckStates.put(position, true);
1173 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1174 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001175 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001176 }
1177 mCheckedItemCount = 1;
1178 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1179 mCheckedItemCount = 0;
1180 }
Adam Powell29382d92012-02-23 11:03:22 -08001181 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001182 }
1183
Adam Powell29382d92012-02-23 11:03:22 -08001184 if (checkedStateChanged) {
1185 updateOnScreenCheckedViews();
1186 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001187 }
1188
Adam Powellbf5f2b32010-10-24 16:45:44 -07001189 if (dispatchItemClick) {
1190 handled |= super.performItemClick(view, position, id);
1191 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001192
1193 return handled;
1194 }
1195
1196 /**
Adam Powell29382d92012-02-23 11:03:22 -08001197 * Perform a quick, in-place update of the checked or activated state
1198 * on all visible item views. This should only be called when a valid
1199 * choice mode is active.
1200 */
1201 private void updateOnScreenCheckedViews() {
1202 final int firstPos = mFirstPosition;
1203 final int count = getChildCount();
1204 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1205 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1206 for (int i = 0; i < count; i++) {
1207 final View child = getChildAt(i);
1208 final int position = firstPos + i;
1209
1210 if (child instanceof Checkable) {
1211 ((Checkable) child).setChecked(mCheckStates.get(position));
1212 } else if (useActivated) {
1213 child.setActivated(mCheckStates.get(position));
1214 }
1215 }
1216 }
1217
1218 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001219 * @see #setChoiceMode(int)
1220 *
1221 * @return The current choice mode
1222 */
Ashley Rose55f9f922019-01-28 19:29:36 -05001223 @InspectableProperty(enumMapping = {
Ashley Rose73abdd3c2019-03-15 17:32:56 -04001224 @EnumEntry(value = CHOICE_MODE_NONE, name = "none"),
1225 @EnumEntry(value = CHOICE_MODE_SINGLE, name = "singleChoice"),
1226 @InspectableProperty.EnumEntry(value = CHOICE_MODE_MULTIPLE, name = "multipleChoice"),
1227 @EnumEntry(value = CHOICE_MODE_MULTIPLE_MODAL, name = "multipleChoiceModal")
Ashley Rose55f9f922019-01-28 19:29:36 -05001228 })
Adam Powellf343e1b2010-08-13 18:27:04 -07001229 public int getChoiceMode() {
1230 return mChoiceMode;
1231 }
1232
1233 /**
1234 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1235 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1236 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1237 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1238 *
1239 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1240 * {@link #CHOICE_MODE_MULTIPLE}
1241 */
1242 public void setChoiceMode(int choiceMode) {
1243 mChoiceMode = choiceMode;
1244 if (mChoiceActionMode != null) {
1245 mChoiceActionMode.finish();
1246 mChoiceActionMode = null;
1247 }
1248 if (mChoiceMode != CHOICE_MODE_NONE) {
1249 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001250 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001251 }
1252 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001253 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001254 }
1255 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1256 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1257 clearChoices();
1258 setLongClickable(true);
1259 }
1260 }
1261 }
1262
1263 /**
1264 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1265 * selection {@link ActionMode}. Only used when the choice mode is set to
1266 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1267 *
1268 * @param listener Listener that will manage the selection mode
1269 *
1270 * @see #setChoiceMode(int)
1271 */
1272 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1273 if (mMultiChoiceModeCallback == null) {
1274 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1275 }
1276 mMultiChoiceModeCallback.setWrapped(listener);
1277 }
1278
1279 /**
Adam Powell637d3372010-08-25 14:37:03 -07001280 * @return true if all list content currently fits within the view boundaries
1281 */
1282 private boolean contentFits() {
1283 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001284 if (childCount == 0) return true;
1285 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001286
Adam Powell4ce35412011-01-24 14:55:00 -08001287 return getChildAt(0).getTop() >= mListPadding.top &&
1288 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001289 }
1290
1291 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001292 * Specifies whether fast scrolling is enabled or disabled.
1293 * <p>
1294 * When fast scrolling is enabled, the user can quickly scroll through lists
1295 * by dragging the fast scroll thumb.
1296 * <p>
1297 * If the adapter backing this list implements {@link SectionIndexer}, the
1298 * fast scroller will display section header previews as the user scrolls.
1299 * Additionally, the user will be able to quickly jump between sections by
1300 * tapping along the length of the scroll bar.
1301 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 * @see SectionIndexer
1303 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001304 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 */
Alan Viverette39bed692013-08-07 15:47:04 -07001306 public void setFastScrollEnabled(final boolean enabled) {
1307 if (mFastScrollEnabled != enabled) {
1308 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001309
Alan Viverette39bed692013-08-07 15:47:04 -07001310 if (isOwnerThread()) {
1311 setFastScrollerEnabledUiThread(enabled);
1312 } else {
1313 post(new Runnable() {
1314 @Override
1315 public void run() {
1316 setFastScrollerEnabledUiThread(enabled);
1317 }
1318 });
1319 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001320 }
Alan Viverette39bed692013-08-07 15:47:04 -07001321 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001322
Alan Viverette39bed692013-08-07 15:47:04 -07001323 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001324 if (mFastScroll != null) {
1325 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001326 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001327 mFastScroll = new FastScroller(this, mFastScrollStyle);
1328 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001329 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001330
Alan Viveretteb9f27222013-09-06 19:39:47 -07001331 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001332
Alan Viverette8636ace2013-10-31 15:41:31 -07001333 if (mFastScroll != null) {
1334 mFastScroll.updateLayout();
1335 }
1336 }
1337
1338 /**
1339 * Specifies the style of the fast scroller decorations.
1340 *
1341 * @param styleResId style resource containing fast scroller properties
1342 * @see android.R.styleable#FastScroll
1343 */
1344 public void setFastScrollStyle(int styleResId) {
1345 if (mFastScroll == null) {
1346 mFastScrollStyle = styleResId;
1347 } else {
1348 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001349 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 }
Romain Guy0a637162009-05-29 14:43:54 -07001351
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001353 * Set whether or not the fast scroller should always be shown in place of
1354 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001355 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001356 * <p>
1357 * Fast scrollers shown in this way will not fade out and will be a
1358 * permanent fixture within the list. This is best combined with an inset
1359 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001360 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001361 * @param alwaysShow true if the fast scroller should always be displayed,
1362 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001363 * @see #setScrollBarStyle(int)
1364 * @see #setFastScrollEnabled(boolean)
1365 */
Alan Viverette39bed692013-08-07 15:47:04 -07001366 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1367 if (mFastScrollAlwaysVisible != alwaysShow) {
1368 if (alwaysShow && !mFastScrollEnabled) {
1369 setFastScrollEnabled(true);
1370 }
Adam Powell20232d02010-12-08 21:08:53 -08001371
Alan Viverette39bed692013-08-07 15:47:04 -07001372 mFastScrollAlwaysVisible = alwaysShow;
1373
1374 if (isOwnerThread()) {
1375 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1376 } else {
1377 post(new Runnable() {
1378 @Override
1379 public void run() {
1380 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1381 }
1382 });
1383 }
1384 }
1385 }
1386
1387 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001388 if (mFastScroll != null) {
1389 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001390 }
Alan Viverette39bed692013-08-07 15:47:04 -07001391 }
Adam Powell20232d02010-12-08 21:08:53 -08001392
Alan Viverette39bed692013-08-07 15:47:04 -07001393 /**
1394 * @return whether the current thread is the one that created the view
1395 */
1396 private boolean isOwnerThread() {
1397 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001398 }
1399
1400 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001401 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001402 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001403 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001404 * @see #setFastScrollAlwaysVisible(boolean)
1405 */
1406 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001407 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001408 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1409 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001410 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001411 }
Adam Powell20232d02010-12-08 21:08:53 -08001412 }
1413
1414 @Override
1415 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001416 if (mFastScroll != null && mFastScroll.isEnabled()) {
1417 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001418 }
1419 return super.getVerticalScrollbarWidth();
1420 }
1421
1422 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001423 * Returns true if the fast scroller is enabled.
1424 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425 * @see #setFastScrollEnabled(boolean)
1426 * @return true if fast scroll is enabled, false otherwise
1427 */
1428 @ViewDebug.ExportedProperty
Ashley Rose55f9f922019-01-28 19:29:36 -05001429 @InspectableProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001430 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001431 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001432 return mFastScrollEnabled;
1433 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001434 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001435 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001436 }
Romain Guy0a637162009-05-29 14:43:54 -07001437
Adam Powell20232d02010-12-08 21:08:53 -08001438 @Override
1439 public void setVerticalScrollbarPosition(int position) {
1440 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001441 if (mFastScroll != null) {
1442 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001443 }
1444 }
1445
Alan Viverette26bb2532013-08-09 10:40:50 -07001446 @Override
1447 public void setScrollBarStyle(int style) {
1448 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001449 if (mFastScroll != null) {
1450 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001451 }
1452 }
1453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001455 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001456 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457 */
1458 @Override
Mathew Inwood978c6e22018-08-21 15:58:55 +01001459 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001461 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462 }
1463
1464 /**
1465 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1466 * is computed based on the number of visible pixels in the visible items. This
1467 * however assumes that all list items have the same height. If you use a list in
1468 * which items have different heights, the scrollbar will change appearance as the
1469 * user scrolls through the list. To avoid this issue, you need to disable this
1470 * property.
1471 *
1472 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1473 * is based solely on the number of items in the adapter and the position of the
1474 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001475 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 *
1477 * @param enabled Whether or not to enable smooth scrollbar.
1478 *
Romain Guy0a637162009-05-29 14:43:54 -07001479 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001480 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1481 */
1482 public void setSmoothScrollbarEnabled(boolean enabled) {
1483 mSmoothScrollbarEnabled = enabled;
1484 }
1485
1486 /**
1487 * Returns the current state of the fast scroll feature.
1488 *
1489 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1490 *
1491 * @see #setSmoothScrollbarEnabled(boolean)
1492 */
1493 @ViewDebug.ExportedProperty
Ashley Rose55f9f922019-01-28 19:29:36 -05001494 @InspectableProperty(name = "smoothScrollbar")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001495 public boolean isSmoothScrollbarEnabled() {
1496 return mSmoothScrollbarEnabled;
1497 }
1498
1499 /**
1500 * Set the listener that will receive notifications every time the list scrolls.
1501 *
1502 * @param l the scroll listener
1503 */
1504 public void setOnScrollListener(OnScrollListener l) {
1505 mOnScrollListener = l;
1506 invokeOnItemScrollListener();
1507 }
1508
1509 /**
1510 * Notify our scroll listener (if there is one) of a change in scroll state
1511 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001512 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001514 if (mFastScroll != null) {
1515 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516 }
1517 if (mOnScrollListener != null) {
1518 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1519 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001520 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 }
1522
Alan Viverettea54956a2015-01-07 16:05:02 -08001523 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001524 @Override
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001525 public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001526 // Since this class calls onScrollChanged even if the mFirstPosition and the
1527 // child count have not changed we will avoid sending duplicate accessibility
1528 // events.
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001529 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001530 final int firstVisiblePosition = getFirstVisiblePosition();
1531 final int lastVisiblePosition = getLastVisiblePosition();
1532 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1533 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001534 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001535 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001536 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1537 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001538 }
1539 }
Eugene Suslacb45ddf2017-05-31 10:57:16 -07001540 super.sendAccessibilityEventUnchecked(event);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001541 }
1542
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001543 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001544 public CharSequence getAccessibilityClassName() {
1545 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001546 }
1547
Alan Viverettea54956a2015-01-07 16:05:02 -08001548 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001549 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001550 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1551 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001552 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001553 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001554 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001555 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001556 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001557 }
Alan Viverette947a9692014-09-25 12:43:47 -07001558 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001559 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001560 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001561 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001562 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001563 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001564
1565 info.removeAction(AccessibilityAction.ACTION_CLICK);
1566 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001567 }
1568
Alan Viverette76769ae2014-02-12 16:38:10 -08001569 int getSelectionModeForAccessibility() {
1570 final int choiceMode = getChoiceMode();
1571 switch (choiceMode) {
1572 case CHOICE_MODE_NONE:
1573 return CollectionInfo.SELECTION_MODE_NONE;
1574 case CHOICE_MODE_SINGLE:
1575 return CollectionInfo.SELECTION_MODE_SINGLE;
1576 case CHOICE_MODE_MULTIPLE:
1577 case CHOICE_MODE_MULTIPLE_MODAL:
1578 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1579 default:
1580 return CollectionInfo.SELECTION_MODE_NONE;
1581 }
1582 }
1583
Alan Viverettea54956a2015-01-07 16:05:02 -08001584 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001585 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001586 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1587 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001588 return true;
1589 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001590 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001591 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1592 case R.id.accessibilityActionScrollDown: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001593 if (isEnabled() && canScrollDown()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001594 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1595 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1596 return true;
1597 }
1598 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001599 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1600 case R.id.accessibilityActionScrollUp: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001601 if (isEnabled() && canScrollUp()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001602 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1603 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1604 return true;
1605 }
1606 } return false;
1607 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001608 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001609 }
1610
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001611 /**
1612 * Indicates whether the children's drawing cache is used during a scroll.
1613 * By default, the drawing cache is enabled but this will consume more memory.
1614 *
1615 * @return true if the scrolling cache is enabled, false otherwise
1616 *
1617 * @see #setScrollingCacheEnabled(boolean)
1618 * @see View#setDrawingCacheEnabled(boolean)
1619 */
1620 @ViewDebug.ExportedProperty
Ashley Rose55f9f922019-01-28 19:29:36 -05001621 @InspectableProperty(name = "scrollingCache")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001622 public boolean isScrollingCacheEnabled() {
1623 return mScrollingCacheEnabled;
1624 }
1625
1626 /**
1627 * Enables or disables the children's drawing cache during a scroll.
1628 * By default, the drawing cache is enabled but this will use more memory.
1629 *
1630 * When the scrolling cache is enabled, the caches are kept after the
1631 * first scrolling. You can manually clear the cache by calling
1632 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1633 *
1634 * @param enabled true to enable the scroll cache, false otherwise
1635 *
1636 * @see #isScrollingCacheEnabled()
1637 * @see View#setDrawingCacheEnabled(boolean)
1638 */
1639 public void setScrollingCacheEnabled(boolean enabled) {
1640 if (mScrollingCacheEnabled && !enabled) {
1641 clearScrollingCache();
1642 }
1643 mScrollingCacheEnabled = enabled;
1644 }
1645
1646 /**
1647 * Enables or disables the type filter window. If enabled, typing when
1648 * this view has focus will filter the children to match the users input.
1649 * Note that the {@link Adapter} used by this view must implement the
1650 * {@link Filterable} interface.
1651 *
1652 * @param textFilterEnabled true to enable type filtering, false otherwise
1653 *
1654 * @see Filterable
1655 */
1656 public void setTextFilterEnabled(boolean textFilterEnabled) {
1657 mTextFilterEnabled = textFilterEnabled;
1658 }
1659
1660 /**
1661 * Indicates whether type filtering is enabled for this view
1662 *
1663 * @return true if type filtering is enabled, false otherwise
1664 *
1665 * @see #setTextFilterEnabled(boolean)
1666 * @see Filterable
1667 */
1668 @ViewDebug.ExportedProperty
Ashley Rose55f9f922019-01-28 19:29:36 -05001669 @InspectableProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001670 public boolean isTextFilterEnabled() {
1671 return mTextFilterEnabled;
1672 }
1673
1674 @Override
1675 public void getFocusedRect(Rect r) {
1676 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001677 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001678 // the focused rectangle of the selected view offset into the
1679 // coordinate space of this view.
1680 view.getFocusedRect(r);
1681 offsetDescendantRectToMyCoords(view, r);
1682 } else {
1683 // otherwise, just the norm
1684 super.getFocusedRect(r);
1685 }
1686 }
1687
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001688 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001689 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001690 com.android.internal.R.drawable.list_selector_background));
1691 }
1692
1693 /**
1694 * Indicates whether the content of this view is pinned to, or stacked from,
1695 * the bottom edge.
1696 *
1697 * @return true if the content is stacked from the bottom edge, false otherwise
1698 */
1699 @ViewDebug.ExportedProperty
Ashley Rose55f9f922019-01-28 19:29:36 -05001700 @InspectableProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001701 public boolean isStackFromBottom() {
1702 return mStackFromBottom;
1703 }
1704
1705 /**
1706 * When stack from bottom is set to true, the list fills its content starting from
1707 * the bottom of the view.
1708 *
1709 * @param stackFromBottom true to pin the view's content to the bottom edge,
1710 * false to pin the view's content to the top edge
1711 */
1712 public void setStackFromBottom(boolean stackFromBottom) {
1713 if (mStackFromBottom != stackFromBottom) {
1714 mStackFromBottom = stackFromBottom;
1715 requestLayoutIfNecessary();
1716 }
1717 }
1718
1719 void requestLayoutIfNecessary() {
1720 if (getChildCount() > 0) {
1721 resetList();
1722 requestLayout();
1723 invalidate();
1724 }
1725 }
1726
1727 static class SavedState extends BaseSavedState {
1728 long selectedId;
Mathew Inwood978c6e22018-08-21 15:58:55 +01001729 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001730 long firstId;
Mathew Inwood978c6e22018-08-21 15:58:55 +01001731 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001732 int viewTop;
1733 int position;
1734 int height;
1735 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001736 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001737 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001738 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001739 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001740
1741 /**
1742 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1743 */
1744 SavedState(Parcelable superState) {
1745 super(superState);
1746 }
1747
1748 /**
1749 * Constructor called from {@link #CREATOR}
1750 */
1751 private SavedState(Parcel in) {
1752 super(in);
1753 selectedId = in.readLong();
1754 firstId = in.readLong();
1755 viewTop = in.readInt();
1756 position = in.readInt();
1757 height = in.readInt();
1758 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001759 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001760 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001761 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001762 final int N = in.readInt();
1763 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001764 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001765 for (int i=0; i<N; i++) {
1766 final long key = in.readLong();
1767 final int value = in.readInt();
1768 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001769 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001770 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001771 }
1772
1773 @Override
1774 public void writeToParcel(Parcel out, int flags) {
1775 super.writeToParcel(out, flags);
1776 out.writeLong(selectedId);
1777 out.writeLong(firstId);
1778 out.writeInt(viewTop);
1779 out.writeInt(position);
1780 out.writeInt(height);
1781 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001782 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001783 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001784 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001785 final int N = checkIdState != null ? checkIdState.size() : 0;
1786 out.writeInt(N);
1787 for (int i=0; i<N; i++) {
1788 out.writeLong(checkIdState.keyAt(i));
1789 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001790 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001791 }
1792
1793 @Override
1794 public String toString() {
1795 return "AbsListView.SavedState{"
1796 + Integer.toHexString(System.identityHashCode(this))
1797 + " selectedId=" + selectedId
1798 + " firstId=" + firstId
1799 + " viewTop=" + viewTop
1800 + " position=" + position
1801 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001802 + " filter=" + filter
1803 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001804 }
1805
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -07001806 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001808 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001809 public SavedState createFromParcel(Parcel in) {
1810 return new SavedState(in);
1811 }
1812
Alan Viverette8fa327a2013-05-31 14:53:13 -07001813 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001814 public SavedState[] newArray(int size) {
1815 return new SavedState[size];
1816 }
1817 };
1818 }
1819
1820 @Override
1821 public Parcelable onSaveInstanceState() {
1822 /*
1823 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001824 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001825 * that happen early enough to keep from getting complaints
1826 * about having leaked the window.
1827 */
1828 dismissPopup();
1829
1830 Parcelable superState = super.onSaveInstanceState();
1831
1832 SavedState ss = new SavedState(superState);
1833
Dianne Hackborne181bd92012-09-25 14:15:15 -07001834 if (mPendingSync != null) {
1835 // Just keep what we last restored.
1836 ss.selectedId = mPendingSync.selectedId;
1837 ss.firstId = mPendingSync.firstId;
1838 ss.viewTop = mPendingSync.viewTop;
1839 ss.position = mPendingSync.position;
1840 ss.height = mPendingSync.height;
1841 ss.filter = mPendingSync.filter;
1842 ss.inActionMode = mPendingSync.inActionMode;
1843 ss.checkedItemCount = mPendingSync.checkedItemCount;
1844 ss.checkState = mPendingSync.checkState;
1845 ss.checkIdState = mPendingSync.checkIdState;
1846 return ss;
1847 }
1848
Dianne Hackborn99441c42010-12-15 11:02:55 -08001849 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001850 long selectedId = getSelectedItemId();
1851 ss.selectedId = selectedId;
1852 ss.height = getHeight();
1853
1854 if (selectedId >= 0) {
1855 // Remember the selection
1856 ss.viewTop = mSelectedTop;
1857 ss.position = getSelectedItemPosition();
1858 ss.firstId = INVALID_POSITION;
1859 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001860 if (haveChildren && mFirstPosition > 0) {
1861 // Remember the position of the first child.
1862 // We only do this if we are not currently at the top of
1863 // the list, for two reasons:
1864 // (1) The list may be in the process of becoming empty, in
1865 // which case mItemCount may not be 0, but if we try to
1866 // ask for any information about position 0 we will crash.
1867 // (2) Being "at the top" seems like a special case, anyway,
1868 // and the user wouldn't expect to end up somewhere else when
1869 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001870 View v = getChildAt(0);
1871 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001872 int firstPos = mFirstPosition;
1873 if (firstPos >= mItemCount) {
1874 firstPos = mItemCount - 1;
1875 }
1876 ss.position = firstPos;
1877 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001878 } else {
1879 ss.viewTop = 0;
1880 ss.firstId = INVALID_POSITION;
1881 ss.position = 0;
1882 }
1883 }
1884
1885 ss.filter = null;
1886 if (mFiltered) {
1887 final EditText textFilter = mTextFilter;
1888 if (textFilter != null) {
1889 Editable filterText = textFilter.getText();
1890 if (filterText != null) {
1891 ss.filter = filterText.toString();
1892 }
1893 }
1894 }
1895
Adam Powella0eeeac2010-11-05 11:55:05 -07001896 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1897
Adam Powell9a5cc282011-08-28 16:18:16 -07001898 if (mCheckStates != null) {
1899 ss.checkState = mCheckStates.clone();
1900 }
1901 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001902 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001903 final int count = mCheckedIdStates.size();
1904 for (int i = 0; i < count; i++) {
1905 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1906 }
1907 ss.checkIdState = idState;
1908 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001909 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001910
Adam Cohen335c3b62012-07-24 17:18:16 -07001911 if (mRemoteAdapter != null) {
1912 mRemoteAdapter.saveRemoteViewsCache();
1913 }
1914
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001915 return ss;
1916 }
1917
1918 @Override
1919 public void onRestoreInstanceState(Parcelable state) {
1920 SavedState ss = (SavedState) state;
1921
1922 super.onRestoreInstanceState(ss.getSuperState());
1923 mDataChanged = true;
1924
1925 mSyncHeight = ss.height;
1926
1927 if (ss.selectedId >= 0) {
1928 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001929 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001930 mSyncRowId = ss.selectedId;
1931 mSyncPosition = ss.position;
1932 mSpecificTop = ss.viewTop;
1933 mSyncMode = SYNC_SELECTED_POSITION;
1934 } else if (ss.firstId >= 0) {
1935 setSelectedPositionInt(INVALID_POSITION);
1936 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1937 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001938 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001939 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001940 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001941 mSyncRowId = ss.firstId;
1942 mSyncPosition = ss.position;
1943 mSpecificTop = ss.viewTop;
1944 mSyncMode = SYNC_FIRST_POSITION;
1945 }
1946
1947 setFilterText(ss.filter);
1948
Adam Powellf343e1b2010-08-13 18:27:04 -07001949 if (ss.checkState != null) {
1950 mCheckStates = ss.checkState;
1951 }
1952
1953 if (ss.checkIdState != null) {
1954 mCheckedIdStates = ss.checkIdState;
1955 }
1956
Adam Powell2614c6c2010-11-04 17:54:45 -07001957 mCheckedItemCount = ss.checkedItemCount;
1958
Adam Powella0eeeac2010-11-05 11:55:05 -07001959 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1960 mMultiChoiceModeCallback != null) {
1961 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1962 }
1963
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001964 requestLayout();
1965 }
1966
1967 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001968 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1969 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001970 }
1971
1972 /**
1973 * Sets the initial value for the text filter.
1974 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001975 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001976 * @see #setTextFilterEnabled
1977 */
1978 public void setFilterText(String filterText) {
1979 // TODO: Should we check for acceptFilter()?
1980 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1981 createTextFilter(false);
1982 // This is going to call our listener onTextChanged, but we might not
1983 // be ready to bring up a window yet
1984 mTextFilter.setText(filterText);
1985 mTextFilter.setSelection(filterText.length());
1986 if (mAdapter instanceof Filterable) {
1987 // if mPopup is non-null, then onTextChanged will do the filtering
1988 if (mPopup == null) {
1989 Filter f = ((Filterable) mAdapter).getFilter();
1990 f.filter(filterText);
1991 }
1992 // Set filtered to true so we will display the filter window when our main
1993 // window is ready
1994 mFiltered = true;
1995 mDataSetObserver.clearSavedState();
1996 }
1997 }
1998 }
1999
2000 /**
Romain Guy0a637162009-05-29 14:43:54 -07002001 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002002 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002003 */
2004 public CharSequence getTextFilter() {
2005 if (mTextFilterEnabled && mTextFilter != null) {
2006 return mTextFilter.getText();
2007 }
2008 return null;
2009 }
Romain Guy0a637162009-05-29 14:43:54 -07002010
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002011 @Override
2012 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
2013 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
2014 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07002015 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07002016 // Data may have changed while we were detached and it's valid
2017 // to change focus while detached. Refresh so we don't die.
2018 mDataChanged = true;
2019 mOldItemCount = mItemCount;
2020 mItemCount = mAdapter.getCount();
2021 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002022 resurrectSelection();
2023 }
2024 }
2025
2026 @Override
2027 public void requestLayout() {
2028 if (!mBlockLayoutRequests && !mInLayout) {
2029 super.requestLayout();
2030 }
2031 }
2032
2033 /**
2034 * The list is empty. Clear everything out.
2035 */
2036 void resetList() {
2037 removeAllViewsInLayout();
2038 mFirstPosition = 0;
2039 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07002040 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002041 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07002042 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002043 mOldSelectedPosition = INVALID_POSITION;
2044 mOldSelectedRowId = INVALID_ROW_ID;
2045 setSelectedPositionInt(INVALID_POSITION);
2046 setNextSelectedPositionInt(INVALID_POSITION);
2047 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002048 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002049 mSelectorRect.setEmpty();
2050 invalidate();
2051 }
2052
2053 @Override
2054 protected int computeVerticalScrollExtent() {
2055 final int count = getChildCount();
2056 if (count > 0) {
2057 if (mSmoothScrollbarEnabled) {
2058 int extent = count * 100;
2059
2060 View view = getChildAt(0);
2061 final int top = view.getTop();
2062 int height = view.getHeight();
2063 if (height > 0) {
2064 extent += (top * 100) / height;
2065 }
2066
2067 view = getChildAt(count - 1);
2068 final int bottom = view.getBottom();
2069 height = view.getHeight();
2070 if (height > 0) {
2071 extent -= ((bottom - getHeight()) * 100) / height;
2072 }
2073
2074 return extent;
2075 } else {
2076 return 1;
2077 }
2078 }
2079 return 0;
2080 }
2081
2082 @Override
2083 protected int computeVerticalScrollOffset() {
2084 final int firstPosition = mFirstPosition;
2085 final int childCount = getChildCount();
2086 if (firstPosition >= 0 && childCount > 0) {
2087 if (mSmoothScrollbarEnabled) {
2088 final View view = getChildAt(0);
2089 final int top = view.getTop();
2090 int height = view.getHeight();
2091 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002092 return Math.max(firstPosition * 100 - (top * 100) / height +
2093 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002094 }
2095 } else {
2096 int index;
2097 final int count = mItemCount;
2098 if (firstPosition == 0) {
2099 index = 0;
2100 } else if (firstPosition + childCount == count) {
2101 index = count;
2102 } else {
2103 index = firstPosition + childCount / 2;
2104 }
2105 return (int) (firstPosition + childCount * (index / (float) count));
2106 }
2107 }
2108 return 0;
2109 }
2110
2111 @Override
2112 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002113 int result;
2114 if (mSmoothScrollbarEnabled) {
2115 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002116 if (mScrollY != 0) {
2117 // Compensate for overscroll
2118 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2119 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002120 } else {
2121 result = mItemCount;
2122 }
2123 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002124 }
2125
2126 @Override
2127 protected float getTopFadingEdgeStrength() {
2128 final int count = getChildCount();
2129 final float fadeEdge = super.getTopFadingEdgeStrength();
2130 if (count == 0) {
2131 return fadeEdge;
2132 } else {
2133 if (mFirstPosition > 0) {
2134 return 1.0f;
2135 }
2136
2137 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002138 final float fadeLength = getVerticalFadingEdgeLength();
2139 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002140 }
2141 }
2142
2143 @Override
2144 protected float getBottomFadingEdgeStrength() {
2145 final int count = getChildCount();
2146 final float fadeEdge = super.getBottomFadingEdgeStrength();
2147 if (count == 0) {
2148 return fadeEdge;
2149 } else {
2150 if (mFirstPosition + count - 1 < mItemCount - 1) {
2151 return 1.0f;
2152 }
2153
2154 final int bottom = getChildAt(count - 1).getBottom();
2155 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002156 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002157 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002158 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002159 }
2160 }
2161
2162 @Override
2163 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2164 if (mSelector == null) {
2165 useDefaultSelector();
2166 }
2167 final Rect listPadding = mListPadding;
2168 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2169 listPadding.top = mSelectionTopPadding + mPaddingTop;
2170 listPadding.right = mSelectionRightPadding + mPaddingRight;
2171 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002172
2173 // Check if our previous measured size was at a point where we should scroll later.
2174 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2175 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002176 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002177 final View lastChild = getChildAt(childCount - 1);
2178 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002179 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002180 lastBottom <= listBottom;
2181 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002182 }
2183
Romain Guyd6a463a2009-05-21 23:10:10 -07002184 /**
2185 * Subclasses should NOT override this method but
2186 * {@link #layoutChildren()} instead.
2187 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002188 @Override
2189 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2190 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002191
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002192 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002193
Alan Viverette4b95cc72014-01-14 16:54:02 -08002194 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002195 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002196 for (int i = 0; i < childCount; i++) {
2197 getChildAt(i).forceLayout();
2198 }
2199 mRecycler.markChildrenDirty();
2200 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002201
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002202 layoutChildren();
Adam Powell637d3372010-08-25 14:37:03 -07002203
2204 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002205
2206 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2207 if (mFastScroll != null) {
2208 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2209 }
Phil Weavera9d976f2016-11-01 09:55:24 -07002210 mInLayout = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002211 }
2212
2213 /**
2214 * @hide
2215 */
2216 @Override
2217 protected boolean setFrame(int left, int top, int right, int bottom) {
2218 final boolean changed = super.setFrame(left, top, right, bottom);
2219
Romain Guyd6a463a2009-05-21 23:10:10 -07002220 if (changed) {
2221 // Reposition the popup when the frame has changed. This includes
2222 // translating the widget, not just changing its dimension. The
2223 // filter popup needs to follow the widget.
2224 final boolean visible = getWindowVisibility() == View.VISIBLE;
2225 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2226 positionPopup();
2227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002228 }
2229
2230 return changed;
2231 }
2232
Romain Guyd6a463a2009-05-21 23:10:10 -07002233 /**
2234 * Subclasses must override this method to layout their children.
2235 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002236 protected void layoutChildren() {
2237 }
2238
Alan Viverette5d565fa2013-10-30 11:09:03 -07002239 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002240 * @param focusedView view that holds accessibility focus
2241 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002242 * child contains accessibility focus
2243 */
Alan Viverette3e141622014-02-18 17:05:13 -08002244 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002245 ViewParent viewParent = focusedView.getParent();
2246 while ((viewParent instanceof View) && (viewParent != this)) {
2247 focusedView = (View) viewParent;
2248 viewParent = viewParent.getParent();
2249 }
2250
2251 if (!(viewParent instanceof View)) {
2252 return null;
2253 }
2254
2255 return focusedView;
2256 }
2257
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002258 void updateScrollIndicators() {
2259 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002260 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002261 }
2262
2263 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002264 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002265 }
2266 }
2267
Mathew Inwood978c6e22018-08-21 15:58:55 +01002268 @UnsupportedAppUsage
Alan Viverette947a9692014-09-25 12:43:47 -07002269 private boolean canScrollUp() {
2270 boolean canScrollUp;
2271 // 0th element is not visible
2272 canScrollUp = mFirstPosition > 0;
2273
2274 // ... Or top of 0th element is not visible
2275 if (!canScrollUp) {
2276 if (getChildCount() > 0) {
2277 View child = getChildAt(0);
2278 canScrollUp = child.getTop() < mListPadding.top;
2279 }
2280 }
2281
2282 return canScrollUp;
2283 }
2284
Mathew Inwood978c6e22018-08-21 15:58:55 +01002285 @UnsupportedAppUsage
Alan Viverette947a9692014-09-25 12:43:47 -07002286 private boolean canScrollDown() {
2287 boolean canScrollDown;
2288 int count = getChildCount();
2289
2290 // Last item is not visible
2291 canScrollDown = (mFirstPosition + count) < mItemCount;
2292
2293 // ... Or bottom of the last element is not visible
2294 if (!canScrollDown && count > 0) {
2295 View child = getChildAt(count - 1);
2296 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2297 }
2298
2299 return canScrollDown;
2300 }
2301
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002302 @Override
2303 @ViewDebug.ExportedProperty
2304 public View getSelectedView() {
2305 if (mItemCount > 0 && mSelectedPosition >= 0) {
2306 return getChildAt(mSelectedPosition - mFirstPosition);
2307 } else {
2308 return null;
2309 }
2310 }
2311
2312 /**
2313 * List padding is the maximum of the normal view's padding and the padding of the selector.
2314 *
2315 * @see android.view.View#getPaddingTop()
2316 * @see #getSelector()
2317 *
2318 * @return The top list padding.
2319 */
2320 public int getListPaddingTop() {
2321 return mListPadding.top;
2322 }
2323
2324 /**
2325 * List padding is the maximum of the normal view's padding and the padding of the selector.
2326 *
2327 * @see android.view.View#getPaddingBottom()
2328 * @see #getSelector()
2329 *
2330 * @return The bottom list padding.
2331 */
2332 public int getListPaddingBottom() {
2333 return mListPadding.bottom;
2334 }
2335
2336 /**
2337 * List padding is the maximum of the normal view's padding and the padding of the selector.
2338 *
2339 * @see android.view.View#getPaddingLeft()
2340 * @see #getSelector()
2341 *
2342 * @return The left list padding.
2343 */
2344 public int getListPaddingLeft() {
2345 return mListPadding.left;
2346 }
2347
2348 /**
2349 * List padding is the maximum of the normal view's padding and the padding of the selector.
2350 *
2351 * @see android.view.View#getPaddingRight()
2352 * @see #getSelector()
2353 *
2354 * @return The right list padding.
2355 */
2356 public int getListPaddingRight() {
2357 return mListPadding.right;
2358 }
2359
2360 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002361 * Gets a view and have it show the data associated with the specified
2362 * position. This is called when we have already discovered that the view
2363 * is not available for reuse in the recycle bin. The only choices left are
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002364 * converting an old view or making a new one.
2365 *
Alan Viverette26489e12016-07-07 16:39:27 -04002366 * @param position the position to display
2367 * @param outMetadata an array of at least 1 boolean where the first entry
2368 * will be set {@code true} if the view is currently
2369 * attached to the window, {@code false} otherwise (e.g.
2370 * newly-inflated or remained scrap for multiple layout
2371 * passes)
Mindy Pereira4e30d892010-11-24 15:32:39 -08002372 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002373 * @return A view displaying the data associated with the specified position
2374 */
Alan Viverette26489e12016-07-07 16:39:27 -04002375 View obtainView(int position, boolean[] outMetadata) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002376 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2377
Alan Viverette26489e12016-07-07 16:39:27 -04002378 outMetadata[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002379
Alan Viverette59511502013-12-09 13:49:25 -08002380 // Check whether we have a transient state view. Attempt to re-bind the
2381 // data and discard the view if we fail.
2382 final View transientView = mRecycler.getTransientStateView(position);
2383 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002384 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2385
2386 // If the view type hasn't changed, attempt to re-bind the data.
2387 if (params.viewType == mAdapter.getItemViewType(position)) {
2388 final View updatedView = mAdapter.getView(position, transientView, this);
2389
2390 // If we failed to re-bind the data, scrap the obtained view.
2391 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002392 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002393 mRecycler.addScrapView(updatedView, position);
2394 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002395 }
2396
Alan Viverette26489e12016-07-07 16:39:27 -04002397 outMetadata[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002398
2399 // Finish the temporary detach started in addScrapView().
2400 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002401 return transientView;
2402 }
2403
2404 final View scrapView = mRecycler.getScrapView(position);
2405 final View child = mAdapter.getView(position, scrapView, this);
2406 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002407 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002408 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002409 mRecycler.addScrapView(scrapView, position);
Alan Viverette26489e12016-07-07 16:39:27 -04002410 } else if (child.isTemporarilyDetached()) {
2411 outMetadata[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002412
Alan Viverette26489e12016-07-07 16:39:27 -04002413 // Finish the temporary detach started in addScrapView().
2414 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002415 }
Alan Viverette59511502013-12-09 13:49:25 -08002416 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002417
Alan Viverette59511502013-12-09 13:49:25 -08002418 if (mCacheColorHint != 0) {
2419 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2420 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002421
Alan Viverette59511502013-12-09 13:49:25 -08002422 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2423 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002424 }
2425
Alan Viverettee6be9c782014-02-26 18:16:36 -08002426 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002427
alanvc1d7e772012-05-08 14:47:24 -07002428 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2429 if (mAccessibilityDelegate == null) {
2430 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2431 }
alanvb72fe7a2012-08-27 16:44:25 -07002432 if (child.getAccessibilityDelegate() == null) {
2433 child.setAccessibilityDelegate(mAccessibilityDelegate);
2434 }
alanvc1d7e772012-05-08 14:47:24 -07002435 }
2436
Romain Guy5fade8c2013-07-10 16:36:18 -07002437 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2438
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002439 return child;
2440 }
2441
Alan Viverettee6be9c782014-02-26 18:16:36 -08002442 private void setItemViewLayoutParams(View child, int position) {
2443 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2444 LayoutParams lp;
2445 if (vlp == null) {
2446 lp = (LayoutParams) generateDefaultLayoutParams();
2447 } else if (!checkLayoutParams(vlp)) {
2448 lp = (LayoutParams) generateLayoutParams(vlp);
2449 } else {
2450 lp = (LayoutParams) vlp;
2451 }
2452
2453 if (mAdapterHasStableIds) {
2454 lp.itemId = mAdapter.getItemId(position);
2455 }
2456 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002457 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002458 if (lp != vlp) {
2459 child.setLayoutParams(lp);
2460 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002461 }
2462
alanvc1d7e772012-05-08 14:47:24 -07002463 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2464 @Override
2465 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2466 super.onInitializeAccessibilityNodeInfo(host, info);
2467
2468 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002469 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002470 }
2471
2472 @Override
2473 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002474 if (super.performAccessibilityAction(host, action, arguments)) {
2475 return true;
2476 }
2477
alanvc1d7e772012-05-08 14:47:24 -07002478 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002479 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002480 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002481 return false;
2482 }
2483
Alan Viverette92539d52015-09-14 10:49:25 -04002484 if (position >= mAdapter.getCount()) {
2485 // The position is no longer valid, likely due to a data set
2486 // change. We could fail here for all data set changes, since
2487 // there is a chance that the data bound to the view may no
2488 // longer exist at the same position within the adapter, but
2489 // it's more consistent with the standard touch interaction to
2490 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002491 return false;
2492 }
2493
Alan Viverette92539d52015-09-14 10:49:25 -04002494 final boolean isItemEnabled;
2495 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2496 if (lp instanceof AbsListView.LayoutParams) {
2497 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2498 } else {
2499 isItemEnabled = false;
2500 }
2501
2502 if (!isEnabled() || !isItemEnabled) {
2503 // Cannot perform actions on disabled items.
2504 return false;
2505 }
alanvc1d7e772012-05-08 14:47:24 -07002506
2507 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002508 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2509 if (getSelectedItemPosition() == position) {
2510 setSelection(INVALID_POSITION);
2511 return true;
2512 }
2513 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002514 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002515 if (getSelectedItemPosition() != position) {
2516 setSelection(position);
2517 return true;
2518 }
2519 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002520 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002521 if (isItemClickable(host)) {
2522 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002523 return performItemClick(host, position, id);
2524 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002525 } return false;
2526 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2527 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002528 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002529 return performLongPress(host, position, id);
2530 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002531 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002532 }
2533
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002534 return false;
alanvc1d7e772012-05-08 14:47:24 -07002535 }
2536 }
2537
Alan Viverette5b2081d2013-08-28 10:43:07 -07002538 /**
2539 * Initializes an {@link AccessibilityNodeInfo} with information about a
2540 * particular item in the list.
2541 *
2542 * @param view View representing the list item.
2543 * @param position Position of the list item within the adapter.
2544 * @param info Node info to populate.
2545 */
2546 public void onInitializeAccessibilityNodeInfoForItem(
2547 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002548 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002549 // The item doesn't exist, so there's not much we can do here.
2550 return;
2551 }
2552
Qasid Ahmad Sadiq8e5db402019-08-09 22:55:35 -07002553 boolean isItemEnabled = view.isEnabled() && isEnabled();
Alan Viverette92539d52015-09-14 10:49:25 -04002554 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2555 if (lp instanceof AbsListView.LayoutParams) {
Qasid Ahmad Sadiq8e5db402019-08-09 22:55:35 -07002556 isItemEnabled &= ((AbsListView.LayoutParams) lp).isEnabled;
Alan Viverette92539d52015-09-14 10:49:25 -04002557 }
2558
Qasid Ahmad Sadiq08de63e2019-06-11 18:58:21 -07002559 info.setEnabled(isItemEnabled);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002560
2561 if (position == getSelectedItemPosition()) {
2562 info.setSelected(true);
Qasid Ahmad Sadiq08de63e2019-06-11 18:58:21 -07002563 addAccessibilityActionIfEnabled(info, isItemEnabled,
2564 AccessibilityAction.ACTION_CLEAR_SELECTION);
2565 } else {
2566 addAccessibilityActionIfEnabled(info, isItemEnabled,
2567 AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002568 }
2569
Alan Viverette92539d52015-09-14 10:49:25 -04002570 if (isItemClickable(view)) {
Qasid Ahmad Sadiq08de63e2019-06-11 18:58:21 -07002571 addAccessibilityActionIfEnabled(info, isItemEnabled, AccessibilityAction.ACTION_CLICK);
yingleiwd5b89922019-09-26 15:11:33 -07002572 // A disabled item is a separator which should not be clickable.
2573 info.setClickable(isItemEnabled);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002574 }
2575
2576 if (isLongClickable()) {
Qasid Ahmad Sadiq08de63e2019-06-11 18:58:21 -07002577 addAccessibilityActionIfEnabled(info, isItemEnabled,
2578 AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002579 info.setLongClickable(true);
2580 }
2581 }
2582
Qasid Ahmad Sadiq08de63e2019-06-11 18:58:21 -07002583
2584 private void addAccessibilityActionIfEnabled(AccessibilityNodeInfo info, boolean enabled,
2585 AccessibilityAction action) {
2586 if (enabled) {
2587 info.addAction(action);
2588 }
2589 }
2590
Alan Viverette92539d52015-09-14 10:49:25 -04002591 private boolean isItemClickable(View view) {
Adam Powell0f552f42017-02-03 11:50:42 -08002592 return !view.hasExplicitFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002593 }
2594
Alan Viverettede399392014-05-01 17:20:55 -07002595 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002596 * Positions the selector in a way that mimics touch.
2597 */
2598 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002599 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002600 }
2601
2602 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002603 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002604 */
2605 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002606 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002607 final Rect bounds = mSelectorRect;
2608 final float x = bounds.exactCenterX();
2609 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002610 positionSelector(position, sel, true, x, y);
2611 } else {
2612 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002613 }
2614 }
2615
Dianne Hackborn079e2352010-10-18 17:02:43 -07002616 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002617 positionSelector(position, sel, false, -1, -1);
2618 }
2619
Mathew Inwood978c6e22018-08-21 15:58:55 +01002620 @UnsupportedAppUsage
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002621 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2622 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002623 if (position != INVALID_POSITION) {
2624 mSelectorPosition = position;
2625 }
2626
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002627 final Rect selectorRect = mSelectorRect;
2628 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002629 if (sel instanceof SelectionBoundsAdjuster) {
2630 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2631 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002632
2633 // Adjust for selection padding.
2634 selectorRect.left -= mSelectionLeftPadding;
2635 selectorRect.top -= mSelectionTopPadding;
2636 selectorRect.right += mSelectionRightPadding;
2637 selectorRect.bottom += mSelectionBottomPadding;
2638
Alan Viverettea19ab342015-05-18 13:20:52 -07002639 // Update the child enabled state prior to updating the selector.
2640 final boolean isChildViewEnabled = sel.isEnabled();
2641 if (mIsChildViewEnabled != isChildViewEnabled) {
2642 mIsChildViewEnabled = isChildViewEnabled;
2643 }
2644
2645 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002646 final Drawable selector = mSelector;
2647 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002648 if (positionChanged) {
2649 // Wipe out the current selector state so that we can start
2650 // over in the new position with a fresh state.
2651 selector.setVisible(false, false);
2652 selector.setState(StateSet.NOTHING);
2653 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002654 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002655 if (positionChanged) {
2656 if (getVisibility() == VISIBLE) {
2657 selector.setVisible(true, false);
2658 }
Chet Haase2167b112014-12-19 16:37:18 -08002659 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002660 }
2661 if (manageHotspot) {
2662 selector.setHotspot(x, y);
2663 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002664 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002665 }
2666
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002667 @Override
2668 protected void dispatchDraw(Canvas canvas) {
2669 int saveCount = 0;
2670 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2671 if (clipToPadding) {
2672 saveCount = canvas.save();
2673 final int scrollX = mScrollX;
2674 final int scrollY = mScrollY;
2675 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2676 scrollX + mRight - mLeft - mPaddingRight,
2677 scrollY + mBottom - mTop - mPaddingBottom);
2678 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2679 }
2680
2681 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2682 if (!drawSelectorOnTop) {
2683 drawSelector(canvas);
2684 }
2685
2686 super.dispatchDraw(canvas);
2687
2688 if (drawSelectorOnTop) {
2689 drawSelector(canvas);
2690 }
2691
2692 if (clipToPadding) {
2693 canvas.restoreToCount(saveCount);
2694 mGroupFlags |= CLIP_TO_PADDING_MASK;
2695 }
2696 }
2697
2698 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002699 protected boolean isPaddingOffsetRequired() {
2700 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2701 }
2702
2703 @Override
2704 protected int getLeftPaddingOffset() {
2705 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2706 }
2707
2708 @Override
2709 protected int getTopPaddingOffset() {
2710 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2711 }
2712
2713 @Override
2714 protected int getRightPaddingOffset() {
2715 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2716 }
2717
2718 @Override
2719 protected int getBottomPaddingOffset() {
2720 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2721 }
2722
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002723 /**
2724 * @hide
2725 */
2726 @Override
2727 protected void internalSetPadding(int left, int top, int right, int bottom) {
2728 super.internalSetPadding(left, top, right, bottom);
2729 if (isLayoutRequested()) {
2730 handleBoundsChange();
2731 }
2732 }
2733
Adam Powell20232d02010-12-08 21:08:53 -08002734 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002735 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002736 handleBoundsChange();
Alan Viverette8636ace2013-10-31 15:41:31 -07002737 if (mFastScroll != null) {
2738 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002739 }
2740 }
2741
2742 /**
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002743 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2744 * and force layouts all children that don't have exact measure specs.
2745 * <p>
2746 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2747 * fail to relayout them properly to accommodate for new bounds.
2748 */
2749 void handleBoundsChange() {
Phil Weavera9d976f2016-11-01 09:55:24 -07002750 if (mInLayout) {
2751 return;
2752 }
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002753 final int childCount = getChildCount();
2754 if (childCount > 0) {
2755 mDataChanged = true;
2756 rememberSyncState();
2757 for (int i = 0; i < childCount; i++) {
2758 final View child = getChildAt(i);
2759 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2760 // force layout child unless it has exact specs
2761 if (lp == null || lp.width < 1 || lp.height < 1) {
2762 child.forceLayout();
2763 }
2764 }
2765 }
2766 }
2767
2768 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002769 * @return True if the current touch mode requires that we draw the selector in the pressed
2770 * state.
2771 */
2772 boolean touchModeDrawsInPressedState() {
2773 // FIXME use isPressed for this
2774 switch (mTouchMode) {
2775 case TOUCH_MODE_TAP:
2776 case TOUCH_MODE_DONE_WAITING:
2777 return true;
2778 default:
2779 return false;
2780 }
2781 }
2782
2783 /**
2784 * Indicates whether this view is in a state where the selector should be drawn. This will
2785 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2786 * the pressed state for an item.
2787 *
2788 * @return True if the selector should be shown
2789 */
2790 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002791 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002792 }
2793
2794 private void drawSelector(Canvas canvas) {
Evan Rosky8e5bd812018-01-22 09:36:41 -08002795 if (shouldDrawSelector()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002796 final Drawable selector = mSelector;
2797 selector.setBounds(mSelectorRect);
2798 selector.draw(canvas);
2799 }
2800 }
2801
2802 /**
Evan Rosky8e5bd812018-01-22 09:36:41 -08002803 * @hide
2804 */
2805 @TestApi
2806 public final boolean shouldDrawSelector() {
2807 return !mSelectorRect.isEmpty();
2808 }
2809
2810 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002811 * Controls whether the selection highlight drawable should be drawn on top of the item or
2812 * behind it.
2813 *
2814 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2815 * is false.
2816 *
2817 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2818 */
2819 public void setDrawSelectorOnTop(boolean onTop) {
2820 mDrawSelectorOnTop = onTop;
2821 }
2822
2823 /**
Ashley Rose55f9f922019-01-28 19:29:36 -05002824 * Returns whether the selection highlight drawable should be drawn on top of the item or
2825 * behind it.
2826 *
2827 * @return true if selector is drawn on top, false otherwise
2828 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2829 */
2830 @InspectableProperty
Ashley Rose433cb1d2019-02-28 18:41:26 -05002831 public boolean isDrawSelectorOnTop() {
Ashley Rose55f9f922019-01-28 19:29:36 -05002832 return mDrawSelectorOnTop;
2833 }
2834
2835 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002836 * Set a Drawable that should be used to highlight the currently selected item.
2837 *
2838 * @param resID A Drawable resource to use as the selection highlight.
2839 *
2840 * @attr ref android.R.styleable#AbsListView_listSelector
2841 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002842 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002843 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002844 }
2845
2846 public void setSelector(Drawable sel) {
2847 if (mSelector != null) {
2848 mSelector.setCallback(null);
2849 unscheduleDrawable(mSelector);
2850 }
2851 mSelector = sel;
2852 Rect padding = new Rect();
2853 sel.getPadding(padding);
2854 mSelectionLeftPadding = padding.left;
2855 mSelectionTopPadding = padding.top;
2856 mSelectionRightPadding = padding.right;
2857 mSelectionBottomPadding = padding.bottom;
2858 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002859 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002860 }
2861
2862 /**
2863 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2864 * selection in the list.
2865 *
2866 * @return the drawable used to display the selector
2867 */
Ashley Rose55f9f922019-01-28 19:29:36 -05002868 @InspectableProperty(name = "listSelector")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002869 public Drawable getSelector() {
2870 return mSelector;
2871 }
2872
2873 /**
2874 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2875 * this is a long press.
2876 */
2877 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002878 if (!isEnabled() || !isClickable()) {
2879 return;
2880 }
2881
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002882 Drawable selector = mSelector;
2883 Rect selectorRect = mSelectorRect;
2884 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002885 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002886
2887 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2888
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002889 if (v != null) {
Adam Powell0f552f42017-02-03 11:50:42 -08002890 if (v.hasExplicitFocusable()) return;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002891 v.setPressed(true);
2892 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002893 setPressed(true);
2894
2895 final boolean longClickable = isLongClickable();
2896 Drawable d = selector.getCurrent();
2897 if (d != null && d instanceof TransitionDrawable) {
2898 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002899 ((TransitionDrawable) d).startTransition(
2900 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002901 } else {
2902 ((TransitionDrawable) d).resetTransition();
2903 }
2904 }
2905 if (longClickable && !mDataChanged) {
2906 if (mPendingCheckForKeyLongPress == null) {
2907 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2908 }
2909 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2910 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2911 }
2912 }
2913 }
2914
2915 public void setScrollIndicators(View up, View down) {
2916 mScrollUp = up;
2917 mScrollDown = down;
2918 }
2919
Mathew Inwood978c6e22018-08-21 15:58:55 +01002920 @UnsupportedAppUsage
Dianne Hackborn079e2352010-10-18 17:02:43 -07002921 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002922 final Drawable selector = mSelector;
2923 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002924 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002925 if (selector.setState(getDrawableStateForSelector())) {
2926 invalidateDrawable(selector);
2927 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002928 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002929 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002930 }
2931 }
2932 }
2933
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002934 @Override
2935 protected void drawableStateChanged() {
2936 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002937 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002938 }
2939
Alan Viverettef723c832015-02-03 16:31:46 -08002940 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002941 // If the child view is enabled then do the default behavior.
2942 if (mIsChildViewEnabled) {
2943 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002944 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002945 }
2946
2947 // The selector uses this View's drawable state. The selected child view
2948 // is disabled, so we need to remove the enabled state from the drawable
2949 // states.
2950 final int enabledState = ENABLED_STATE_SET[0];
2951
Alan Viverettef723c832015-02-03 16:31:46 -08002952 // If we don't have any extra space, it will return one of the static
2953 // state arrays, and clearing the enabled state on those arrays is a
2954 // bad thing! If we specify we need extra space, it will create+copy
2955 // into a new array that is safely mutable.
2956 final int[] state = onCreateDrawableState(1);
2957
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002958 int enabledPos = -1;
2959 for (int i = state.length - 1; i >= 0; i--) {
2960 if (state[i] == enabledState) {
2961 enabledPos = i;
2962 break;
2963 }
2964 }
2965
2966 // Remove the enabled state
2967 if (enabledPos >= 0) {
2968 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2969 state.length - enabledPos - 1);
2970 }
Romain Guy0a637162009-05-29 14:43:54 -07002971
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002972 return state;
2973 }
2974
2975 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -05002976 public boolean verifyDrawable(@NonNull Drawable dr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002977 return mSelector == dr || super.verifyDrawable(dr);
2978 }
2979
2980 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002981 public void jumpDrawablesToCurrentState() {
2982 super.jumpDrawablesToCurrentState();
2983 if (mSelector != null) mSelector.jumpToCurrentState();
2984 }
2985
2986 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002987 protected void onAttachedToWindow() {
2988 super.onAttachedToWindow();
2989
2990 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002991 treeObserver.addOnTouchModeChangeListener(this);
2992 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2993 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002994 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002995
Romain Guy82afc7b2010-05-13 11:52:37 -07002996 if (mAdapter != null && mDataSetObserver == null) {
2997 mDataSetObserver = new AdapterDataSetObserver();
2998 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002999
3000 // Data may have changed while we were detached. Refresh.
3001 mDataChanged = true;
3002 mOldItemCount = mItemCount;
3003 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07003004 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003005 }
3006
3007 @Override
3008 protected void onDetachedFromWindow() {
3009 super.onDetachedFromWindow();
3010
Alan Viverette462c2172014-02-24 12:24:11 -08003011 mIsDetaching = true;
3012
Romain Guy1f7f3c32009-07-22 11:25:42 -07003013 // Dismiss the popup in case onSaveInstanceState() was not invoked
3014 dismissPopup();
3015
Romain Guy21875052010-01-06 18:48:08 -08003016 // Detach any view left in the scrap heap
3017 mRecycler.clear();
3018
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003019 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08003020 treeObserver.removeOnTouchModeChangeListener(this);
3021 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07003022 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08003023 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003024 }
Romain Guy82afc7b2010-05-13 11:52:37 -07003025
Adam Powellbd1dd0d2013-04-09 17:46:15 -07003026 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07003027 mAdapter.unregisterDataSetObserver(mDataSetObserver);
3028 mDataSetObserver = null;
3029 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003030
3031 if (mScrollStrictSpan != null) {
3032 mScrollStrictSpan.finish();
3033 mScrollStrictSpan = null;
3034 }
3035
3036 if (mFlingStrictSpan != null) {
3037 mFlingStrictSpan.finish();
3038 mFlingStrictSpan = null;
3039 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003040
3041 if (mFlingRunnable != null) {
3042 removeCallbacks(mFlingRunnable);
3043 }
3044
3045 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08003046 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003047 }
3048
3049 if (mClearScrollingCache != null) {
3050 removeCallbacks(mClearScrollingCache);
3051 }
3052
3053 if (mPerformClick != null) {
3054 removeCallbacks(mPerformClick);
3055 }
3056
3057 if (mTouchModeReset != null) {
3058 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09003059 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003060 }
Alan Viverette462c2172014-02-24 12:24:11 -08003061
3062 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003063 }
3064
3065 @Override
3066 public void onWindowFocusChanged(boolean hasWindowFocus) {
3067 super.onWindowFocusChanged(hasWindowFocus);
3068
3069 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3070
3071 if (!hasWindowFocus) {
3072 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08003073 if (mFlingRunnable != null) {
3074 removeCallbacks(mFlingRunnable);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003075 // let the fling runnable report its new state which
Mark Wagner670dd812010-01-13 16:17:47 -08003076 // should be idle
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003077 mFlingRunnable.mSuppressIdleStateChangeCall = false;
Mark Wagner670dd812010-01-13 16:17:47 -08003078 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003079 if (mPositionScroller != null) {
3080 mPositionScroller.stop();
3081 }
Adam Powell45803472010-01-25 15:10:44 -08003082 if (mScrollY != 0) {
3083 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003084 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003085 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08003086 invalidate();
3087 }
Mark Wagner670dd812010-01-13 16:17:47 -08003088 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003089 // Always hide the type filter
3090 dismissPopup();
3091
3092 if (touchMode == TOUCH_MODE_OFF) {
3093 // Remember the last selected element
3094 mResurrectToPosition = mSelectedPosition;
3095 }
3096 } else {
Adam Powell97566042010-03-09 15:34:09 -08003097 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003098 // Show the type filter only if a filter is in effect
3099 showPopup();
3100 }
3101
3102 // If we changed touch mode since the last time we had focus
3103 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3104 // If we come back in trackball mode, we bring the selection back
3105 if (touchMode == TOUCH_MODE_OFF) {
3106 // This will trigger a layout
3107 resurrectSelection();
3108
3109 // If we come back in touch mode, then we want to hide the selector
3110 } else {
3111 hideSelector();
3112 mLayoutMode = LAYOUT_NORMAL;
3113 layoutChildren();
3114 }
3115 }
3116 }
3117
3118 mLastTouchMode = touchMode;
3119 }
3120
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003121 @Override
3122 public void onRtlPropertiesChanged(int layoutDirection) {
3123 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003124 if (mFastScroll != null) {
3125 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003126 }
3127 }
3128
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003129 /**
3130 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3131 * methods knows the view, position and ID of the item that received the
3132 * long press.
3133 *
3134 * @param view The view that received the long press.
3135 * @param position The position of the item that received the long press.
3136 * @param id The ID of the item that received the long press.
3137 * @return The extra information that should be returned by
3138 * {@link #getContextMenuInfo()}.
3139 */
3140 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3141 return new AdapterContextMenuInfo(view, position, id);
3142 }
3143
Adam Powell14874662013-07-18 19:42:41 -07003144 @Override
3145 public void onCancelPendingInputEvents() {
3146 super.onCancelPendingInputEvents();
3147 if (mPerformClick != null) {
3148 removeCallbacks(mPerformClick);
3149 }
3150 if (mPendingCheckForTap != null) {
3151 removeCallbacks(mPendingCheckForTap);
3152 }
3153 if (mPendingCheckForLongPress != null) {
3154 removeCallbacks(mPendingCheckForLongPress);
3155 }
3156 if (mPendingCheckForKeyLongPress != null) {
3157 removeCallbacks(mPendingCheckForKeyLongPress);
3158 }
3159 }
3160
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003161 /**
3162 * A base class for Runnables that will check that their view is still attached to
3163 * the original window as when the Runnable was created.
3164 *
3165 */
3166 private class WindowRunnnable {
3167 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003169 public void rememberWindowAttachCount() {
3170 mOriginalAttachCount = getWindowAttachCount();
3171 }
Romain Guy0a637162009-05-29 14:43:54 -07003172
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003173 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003174 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003175 }
3176 }
Romain Guy0a637162009-05-29 14:43:54 -07003177
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003178 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003179 int mClickMotionPosition;
3180
Alan Viverette8fa327a2013-05-31 14:53:13 -07003181 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003182 public void run() {
3183 // The data has changed since we posted this action in the event queue,
3184 // bail out before bad things happen
3185 if (mDataChanged) return;
3186
Adam Powell005c0a42010-03-30 16:26:36 -07003187 final ListAdapter adapter = mAdapter;
3188 final int motionPosition = mClickMotionPosition;
3189 if (adapter != null && mItemCount > 0 &&
3190 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003191 motionPosition < adapter.getCount() && sameWindow() &&
3192 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003193 final View view = getChildAt(motionPosition - mFirstPosition);
3194 // If there is no view, something bad happened (the view scrolled off the
3195 // screen, etc.) and we should cancel the click
3196 if (view != null) {
3197 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3198 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003199 }
3200 }
3201 }
3202
3203 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003204 private static final int INVALID_COORD = -1;
3205 private float mX = INVALID_COORD;
3206 private float mY = INVALID_COORD;
3207
3208 private void setCoords(float x, float y) {
3209 mX = x;
3210 mY = y;
3211 }
3212
Alan Viverette8fa327a2013-05-31 14:53:13 -07003213 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003214 public void run() {
3215 final int motionPosition = mMotionPosition;
3216 final View child = getChildAt(motionPosition - mFirstPosition);
3217 if (child != null) {
3218 final int longPressPosition = mMotionPosition;
3219 final long longPressId = mAdapter.getItemId(mMotionPosition);
3220
3221 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003222 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003223 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3224 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3225 } else {
3226 handled = performLongPress(child, longPressPosition, longPressId);
3227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003228 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003229
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003230 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003231 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003232 mTouchMode = TOUCH_MODE_REST;
3233 setPressed(false);
3234 child.setPressed(false);
3235 } else {
3236 mTouchMode = TOUCH_MODE_DONE_WAITING;
3237 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003238 }
3239 }
3240 }
Romain Guy0a637162009-05-29 14:43:54 -07003241
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003242 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003243 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003244 public void run() {
3245 if (isPressed() && mSelectedPosition >= 0) {
3246 int index = mSelectedPosition - mFirstPosition;
3247 View v = getChildAt(index);
3248
3249 if (!mDataChanged) {
3250 boolean handled = false;
3251 if (sameWindow()) {
3252 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3253 }
3254 if (handled) {
3255 setPressed(false);
3256 v.setPressed(false);
3257 }
3258 } else {
3259 setPressed(false);
3260 if (v != null) v.setPressed(false);
3261 }
3262 }
3263 }
3264 }
3265
Mady Mellore5561982015-04-14 15:06:40 -07003266 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003267 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003268 final View child = getChildAt(mMotionPosition - mFirstPosition);
3269 if (child != null) {
3270 final int longPressPosition = mMotionPosition;
3271 final long longPressId = mAdapter.getItemId(mMotionPosition);
3272 if (performLongPress(child, longPressPosition, longPressId)) {
3273 mTouchMode = TOUCH_MODE_REST;
3274 setPressed(false);
3275 child.setPressed(false);
3276 return true;
3277 }
3278 }
3279 }
3280 return false;
3281 }
3282
Mathew Inwood978c6e22018-08-21 15:58:55 +01003283 @UnsupportedAppUsage
Adam Powell8350f7d2010-07-28 14:27:28 -07003284 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003285 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003286 return performLongPress(
3287 child,
3288 longPressPosition,
3289 longPressId,
3290 CheckForLongPress.INVALID_COORD,
3291 CheckForLongPress.INVALID_COORD);
3292 }
3293
Mathew Inwood978c6e22018-08-21 15:58:55 +01003294 @UnsupportedAppUsage
Oren Blasberged391262015-09-01 12:12:51 -07003295 boolean performLongPress(final View child,
3296 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003297 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3298 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003299 if (mChoiceActionMode == null &&
3300 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003301 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003302 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003303 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003304 return true;
3305 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003306
Adam Powellf343e1b2010-08-13 18:27:04 -07003307 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003308 if (mOnItemLongClickListener != null) {
3309 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3310 longPressPosition, longPressId);
3311 }
3312 if (!handled) {
3313 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003314 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3315 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3316 } else {
3317 handled = super.showContextMenuForChild(AbsListView.this);
3318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003319 }
3320 if (handled) {
3321 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3322 }
3323 return handled;
3324 }
3325
3326 @Override
3327 protected ContextMenuInfo getContextMenuInfo() {
3328 return mContextMenuInfo;
3329 }
3330
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003331 @Override
3332 public boolean showContextMenu() {
3333 return showContextMenuInternal(0, 0, false);
3334 }
3335
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003336 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003337 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003338 return showContextMenuInternal(x, y, true);
3339 }
3340
3341 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003342 final int position = pointToPosition((int)x, (int)y);
3343 if (position != INVALID_POSITION) {
3344 final long id = mAdapter.getItemId(position);
3345 View child = getChildAt(position - mFirstPosition);
3346 if (child != null) {
3347 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003348 if (useOffsets) {
3349 return super.showContextMenuForChild(this, x, y);
3350 } else {
3351 return super.showContextMenuForChild(this);
3352 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003353 }
3354 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003355 if (useOffsets) {
3356 return super.showContextMenu(x, y);
3357 } else {
3358 return super.showContextMenu();
3359 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003360 }
3361
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362 @Override
3363 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003364 if (isShowingContextMenuWithCoords()) {
3365 return false;
3366 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003367 return showContextMenuForChildInternal(originalView, 0, 0, false);
3368 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003369
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003370 @Override
3371 public boolean showContextMenuForChild(View originalView, float x, float y) {
3372 return showContextMenuForChildInternal(originalView,x, y, true);
3373 }
3374
3375 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3376 boolean useOffsets) {
3377 final int longPressPosition = getPositionForView(originalView);
3378 if (longPressPosition < 0) {
3379 return false;
3380 }
3381
3382 final long longPressId = mAdapter.getItemId(longPressPosition);
3383 boolean handled = false;
3384
3385 if (mOnItemLongClickListener != null) {
3386 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3387 longPressPosition, longPressId);
3388 }
3389
3390 if (!handled) {
3391 final View child = getChildAt(longPressPosition - mFirstPosition);
3392 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3393
3394 if (useOffsets) {
3395 handled = super.showContextMenuForChild(originalView, x, y);
3396 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003397 handled = super.showContextMenuForChild(originalView);
3398 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003399 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003400
3401 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003402 }
3403
3404 @Override
Romain Guydf016072009-08-17 12:51:30 -07003405 public boolean onKeyDown(int keyCode, KeyEvent event) {
3406 return false;
3407 }
3408
3409 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003410 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003411 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003412 if (!isEnabled()) {
3413 return true;
3414 }
Romain Guydf016072009-08-17 12:51:30 -07003415 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003416 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003417 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003419 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003420 if (view != null) {
3421 performItemClick(view, mSelectedPosition, mSelectedRowId);
3422 view.setPressed(false);
3423 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003424 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003425 return true;
3426 }
3427 }
3428 return super.onKeyUp(keyCode, event);
3429 }
3430
3431 @Override
3432 protected void dispatchSetPressed(boolean pressed) {
3433 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3434 // get the selector in the right state, but we don't want to press each child.
3435 }
3436
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003437 @Override
3438 public void dispatchDrawableHotspotChanged(float x, float y) {
3439 // Don't dispatch hotspot changes to children. We'll manually handle
3440 // calling drawableHotspotChanged on the correct child.
3441 }
3442
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003443 /**
3444 * Maps a point to a position in the list.
3445 *
3446 * @param x X in local coordinate
3447 * @param y Y in local coordinate
3448 * @return The position of the item which contains the specified point, or
3449 * {@link #INVALID_POSITION} if the point does not intersect an item.
3450 */
3451 public int pointToPosition(int x, int y) {
3452 Rect frame = mTouchFrame;
3453 if (frame == null) {
3454 mTouchFrame = new Rect();
3455 frame = mTouchFrame;
3456 }
3457
3458 final int count = getChildCount();
3459 for (int i = count - 1; i >= 0; i--) {
3460 final View child = getChildAt(i);
3461 if (child.getVisibility() == View.VISIBLE) {
3462 child.getHitRect(frame);
3463 if (frame.contains(x, y)) {
3464 return mFirstPosition + i;
3465 }
3466 }
3467 }
3468 return INVALID_POSITION;
3469 }
3470
3471
3472 /**
3473 * Maps a point to a the rowId of the item which intersects that point.
3474 *
3475 * @param x X in local coordinate
3476 * @param y Y in local coordinate
3477 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3478 * if the point does not intersect an item.
3479 */
3480 public long pointToRowId(int x, int y) {
3481 int position = pointToPosition(x, y);
3482 if (position >= 0) {
3483 return mAdapter.getItemId(position);
3484 }
3485 return INVALID_ROW_ID;
3486 }
3487
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003488 private final class CheckForTap implements Runnable {
3489 float x;
3490 float y;
3491
Alan Viverette8fa327a2013-05-31 14:53:13 -07003492 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003493 public void run() {
3494 if (mTouchMode == TOUCH_MODE_DOWN) {
3495 mTouchMode = TOUCH_MODE_TAP;
3496 final View child = getChildAt(mMotionPosition - mFirstPosition);
Adam Powell0f552f42017-02-03 11:50:42 -08003497 if (child != null && !child.hasExplicitFocusable()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003498 mLayoutMode = LAYOUT_NORMAL;
3499
3500 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003501 final float[] point = mTmpPoint;
3502 point[0] = x;
3503 point[1] = y;
3504 transformPointToViewLocal(point, child);
3505 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003506 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003507 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003508 layoutChildren();
3509 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003510 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003511
3512 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3513 final boolean longClickable = isLongClickable();
3514
3515 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003516 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003517 if (d != null && d instanceof TransitionDrawable) {
3518 if (longClickable) {
3519 ((TransitionDrawable) d).startTransition(longPressTimeout);
3520 } else {
3521 ((TransitionDrawable) d).resetTransition();
3522 }
3523 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003524 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003525 }
3526
3527 if (longClickable) {
3528 if (mPendingCheckForLongPress == null) {
3529 mPendingCheckForLongPress = new CheckForLongPress();
3530 }
Oren Blasberged391262015-09-01 12:12:51 -07003531 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003532 mPendingCheckForLongPress.rememberWindowAttachCount();
3533 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3534 } else {
3535 mTouchMode = TOUCH_MODE_DONE_WAITING;
3536 }
3537 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003538 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003539 }
3540 }
3541 }
3542 }
3543 }
3544
Adam Powellc501db9f2014-05-08 12:50:10 -07003545 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003546 // Check if we have moved far enough that it looks more like a
3547 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003548 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003549 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003550 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003551 if ((overscroll || distance > mTouchSlop) &&
3552 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003553 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003554 if (overscroll) {
3555 mTouchMode = TOUCH_MODE_OVERSCROLL;
3556 mMotionCorrection = 0;
3557 } else {
3558 mTouchMode = TOUCH_MODE_SCROLL;
3559 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3560 }
Alan Viverette74ded292013-06-03 15:34:11 -07003561 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003562 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003563 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003564 if (motionView != null) {
3565 motionView.setPressed(false);
3566 }
3567 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3568 // Time to start stealing events! Once we've stolen them, don't let anyone
3569 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003570 final ViewParent parent = getParent();
3571 if (parent != null) {
3572 parent.requestDisallowInterceptTouchEvent(true);
3573 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003574 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003575 return true;
3576 }
3577
3578 return false;
3579 }
3580
Adam Powellc501db9f2014-05-08 12:50:10 -07003581 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003582 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003583 int scrollOffsetCorrection = 0;
3584 int scrollConsumedCorrection = 0;
3585 if (mLastY == Integer.MIN_VALUE) {
3586 rawDeltaY -= mMotionCorrection;
3587 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003588 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3589 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003590 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003591 scrollOffsetCorrection = -mScrollOffset[1];
3592 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003593 if (vtev != null) {
3594 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003595 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003596 }
3597 }
Yorke Lee43943d82014-05-08 10:15:20 -07003598 final int deltaY = rawDeltaY;
3599 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003600 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003601 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003602
3603 if (mTouchMode == TOUCH_MODE_SCROLL) {
3604 if (PROFILE_SCROLLING) {
3605 if (!mScrollProfilingStarted) {
3606 Debug.startMethodTracing("AbsListViewScroll");
3607 mScrollProfilingStarted = true;
3608 }
3609 }
3610
3611 if (mScrollStrictSpan == null) {
3612 // If it's non-null, we're already in a scroll.
3613 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3614 }
3615
3616 if (y != mLastY) {
3617 // We may be here after stopping a fling and continuing to scroll.
3618 // If so, we haven't disallowed intercepting touch events yet.
3619 // Make sure that we do so in case we're in a parent that can intercept.
3620 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3621 Math.abs(rawDeltaY) > mTouchSlop) {
3622 final ViewParent parent = getParent();
3623 if (parent != null) {
3624 parent.requestDisallowInterceptTouchEvent(true);
3625 }
3626 }
3627
3628 final int motionIndex;
3629 if (mMotionPosition >= 0) {
3630 motionIndex = mMotionPosition - mFirstPosition;
3631 } else {
3632 // If we don't have a motion position that we can reliably track,
3633 // pick something in the middle to make a best guess at things below.
3634 motionIndex = getChildCount() / 2;
3635 }
3636
3637 int motionViewPrevTop = 0;
3638 View motionView = this.getChildAt(motionIndex);
3639 if (motionView != null) {
3640 motionViewPrevTop = motionView.getTop();
3641 }
3642
3643 // No need to do all this work if we're not going to move anyway
3644 boolean atEdge = false;
3645 if (incrementalDeltaY != 0) {
3646 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3647 }
3648
3649 // Check to see if we have bumped into the scroll limit
3650 motionView = this.getChildAt(motionIndex);
3651 if (motionView != null) {
3652 // Check if the top of the motion view is where it is
3653 // supposed to be
3654 final int motionViewRealTop = motionView.getTop();
3655 if (atEdge) {
3656 // Apply overscroll
3657
3658 int overscroll = -incrementalDeltaY -
3659 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003660 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3661 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003662 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003663 if (vtev != null) {
3664 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003665 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003666 }
Adam Powell96d62af2014-05-02 10:04:38 -07003667 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003668 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3669 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3670
3671 if (atOverscrollEdge && mVelocityTracker != null) {
3672 // Don't allow overfling if we're at the edge
3673 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003674 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003675
Adam Powell96d62af2014-05-02 10:04:38 -07003676 final int overscrollMode = getOverScrollMode();
3677 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3678 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3679 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003680 if (!atOverscrollEdge) {
3681 mDirection = 0; // Reset when entering overscroll.
3682 mTouchMode = TOUCH_MODE_OVERSCROLL;
3683 }
3684 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003685 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003686 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003687 if (!mEdgeGlowBottom.isFinished()) {
3688 mEdgeGlowBottom.onRelease();
3689 }
Doris Liuf36c0612015-06-04 11:11:14 -07003690 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003691 } else if (incrementalDeltaY < 0) {
3692 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3693 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003694 if (!mEdgeGlowTop.isFinished()) {
3695 mEdgeGlowTop.onRelease();
3696 }
Doris Liuf36c0612015-06-04 11:11:14 -07003697 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003698 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003699 }
3700 }
3701 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003702 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003703 }
Yorke Lee43943d82014-05-08 10:15:20 -07003704 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003705 }
3706 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3707 if (y != mLastY) {
3708 final int oldScroll = mScrollY;
3709 final int newScroll = oldScroll - incrementalDeltaY;
3710 int newDirection = y > mLastY ? 1 : -1;
3711
3712 if (mDirection == 0) {
3713 mDirection = newDirection;
3714 }
3715
3716 int overScrollDistance = -incrementalDeltaY;
3717 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3718 overScrollDistance = -oldScroll;
3719 incrementalDeltaY += overScrollDistance;
3720 } else {
3721 incrementalDeltaY = 0;
3722 }
3723
3724 if (overScrollDistance != 0) {
3725 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3726 0, mOverscrollDistance, true);
3727 final int overscrollMode = getOverScrollMode();
3728 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3729 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3730 !contentFits())) {
3731 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003732 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3733 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003734 if (!mEdgeGlowBottom.isFinished()) {
3735 mEdgeGlowBottom.onRelease();
3736 }
Doris Liuf36c0612015-06-04 11:11:14 -07003737 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003738 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003739 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3740 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003741 if (!mEdgeGlowTop.isFinished()) {
3742 mEdgeGlowTop.onRelease();
3743 }
Doris Liuf36c0612015-06-04 11:11:14 -07003744 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003745 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003746 }
3747 }
3748
3749 if (incrementalDeltaY != 0) {
3750 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003751 if (mScrollY != 0) {
3752 mScrollY = 0;
3753 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003754 }
3755
Romain Guy9d849a22012-03-14 16:41:42 -07003756 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3757
Jeff Brown78f6e632011-09-09 17:15:31 -07003758 mTouchMode = TOUCH_MODE_SCROLL;
3759
3760 // We did not scroll the full amount. Treat this essentially like the
3761 // start of a new touch scroll
3762 final int motionPosition = findClosestMotionRow(y);
3763
3764 mMotionCorrection = 0;
3765 View motionView = getChildAt(motionPosition - mFirstPosition);
3766 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003767 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003768 mMotionPosition = motionPosition;
3769 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003770 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003771 mDirection = newDirection;
3772 }
3773 }
3774 }
3775
Doris Liuf36c0612015-06-04 11:11:14 -07003776 private void invalidateTopGlow() {
Yigit Boyarb6218472019-02-06 10:07:06 -08003777 if (!shouldDisplayEdgeEffects()) {
Doris Liuf36c0612015-06-04 11:11:14 -07003778 return;
3779 }
3780 final boolean clipToPadding = getClipToPadding();
3781 final int top = clipToPadding ? mPaddingTop : 0;
3782 final int left = clipToPadding ? mPaddingLeft : 0;
3783 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3784 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3785 }
3786
3787 private void invalidateBottomGlow() {
Yigit Boyarb6218472019-02-06 10:07:06 -08003788 if (!shouldDisplayEdgeEffects()) {
Doris Liuf36c0612015-06-04 11:11:14 -07003789 return;
3790 }
3791 final boolean clipToPadding = getClipToPadding();
3792 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3793 final int left = clipToPadding ? mPaddingLeft : 0;
3794 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3795 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3796 }
3797
Alan Viverette8fa327a2013-05-31 14:53:13 -07003798 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003799 public void onTouchModeChanged(boolean isInTouchMode) {
3800 if (isInTouchMode) {
3801 // Get rid of the selection when we enter touch mode
3802 hideSelector();
3803 // Layout, but only if we already have done so previously.
3804 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3805 // state.)
3806 if (getHeight() > 0 && getChildCount() > 0) {
3807 // We do not lose focus initiating a touch (since AbsListView is focusable in
3808 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003809 layoutChildren();
3810 }
Jeff Brown1e209462011-07-14 22:19:19 -07003811 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003812 } else {
3813 int touchMode = mTouchMode;
3814 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3815 if (mFlingRunnable != null) {
3816 mFlingRunnable.endFling();
3817 }
Adam Powell40322522011-01-12 21:58:20 -08003818 if (mPositionScroller != null) {
3819 mPositionScroller.stop();
3820 }
Adam Powell637d3372010-08-25 14:37:03 -07003821
3822 if (mScrollY != 0) {
3823 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003824 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003825 finishGlows();
3826 invalidate();
3827 }
3828 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003829 }
3830 }
3831
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003832 /** @hide */
3833 @Override
3834 protected boolean handleScrollBarDragging(MotionEvent event) {
3835 // Doesn't support normal scroll bar dragging. Use FastScroller.
3836 return false;
3837 }
3838
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003839 @Override
3840 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003841 if (!isEnabled()) {
3842 // A disabled view that is clickable still consumes the touch
3843 // events, it just doesn't respond to them.
3844 return isClickable() || isLongClickable();
3845 }
3846
Adam Powell1fa179ef2012-04-12 15:01:40 -07003847 if (mPositionScroller != null) {
3848 mPositionScroller.stop();
3849 }
3850
Alan Viverette462c2172014-02-24 12:24:11 -08003851 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003852 // Something isn't right.
3853 // Since we rely on being attached to get data set change notifications,
3854 // don't risk doing anything where we might try to resync and find things
3855 // in a bogus state.
3856 return false;
3857 }
3858
Adam Powell96d62af2014-05-02 10:04:38 -07003859 startNestedScroll(SCROLL_AXIS_VERTICAL);
3860
Alan Viverettefb99ba82015-05-01 10:10:15 -07003861 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3862 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003863 }
Romain Guy82f34952009-05-24 18:40:45 -07003864
Michael Jurka13451a42011-08-22 15:54:21 -07003865 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003866 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003867
Alan Viverette8fa327a2013-05-31 14:53:13 -07003868 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003869 if (actionMasked == MotionEvent.ACTION_DOWN) {
3870 mNestedYOffset = 0;
3871 }
3872 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003873 switch (actionMasked) {
3874 case MotionEvent.ACTION_DOWN: {
3875 onTouchDown(ev);
3876 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003877 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003878
Alan Viverette8fa327a2013-05-31 14:53:13 -07003879 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003880 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003881 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003882 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003883
3884 case MotionEvent.ACTION_UP: {
3885 onTouchUp(ev);
3886 break;
3887 }
3888
3889 case MotionEvent.ACTION_CANCEL: {
3890 onTouchCancel();
3891 break;
3892 }
3893
3894 case MotionEvent.ACTION_POINTER_UP: {
3895 onSecondaryPointerUp(ev);
3896 final int x = mMotionX;
3897 final int y = mMotionY;
3898 final int motionPosition = pointToPosition(x, y);
3899 if (motionPosition >= 0) {
3900 // Remember where the motion event started
3901 final View child = getChildAt(motionPosition - mFirstPosition);
3902 mMotionViewOriginalTop = child.getTop();
3903 mMotionPosition = motionPosition;
3904 }
3905 mLastY = y;
3906 break;
3907 }
3908
3909 case MotionEvent.ACTION_POINTER_DOWN: {
3910 // New pointers take over dragging duties
3911 final int index = ev.getActionIndex();
3912 final int id = ev.getPointerId(index);
3913 final int x = (int) ev.getX(index);
3914 final int y = (int) ev.getY(index);
3915 mMotionCorrection = 0;
3916 mActivePointerId = id;
3917 mMotionX = x;
3918 mMotionY = y;
3919 final int motionPosition = pointToPosition(x, y);
3920 if (motionPosition >= 0) {
3921 // Remember where the motion event started
3922 final View child = getChildAt(motionPosition - mFirstPosition);
3923 mMotionViewOriginalTop = child.getTop();
3924 mMotionPosition = motionPosition;
3925 }
3926 mLastY = y;
3927 break;
3928 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003929 }
3930
Adam Powell96d62af2014-05-02 10:04:38 -07003931 if (mVelocityTracker != null) {
3932 mVelocityTracker.addMovement(vtev);
3933 }
3934 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003935 return true;
3936 }
Romain Guy0a637162009-05-29 14:43:54 -07003937
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003938 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003939 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003940 mActivePointerId = ev.getPointerId(0);
Evan Rosky837ae0d2017-10-26 12:50:33 -07003941 hideSelector();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003942
3943 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3944 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003945 mFlingRunnable.endFling();
3946 if (mPositionScroller != null) {
3947 mPositionScroller.stop();
3948 }
3949 mTouchMode = TOUCH_MODE_OVERSCROLL;
3950 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003951 mMotionY = (int) ev.getY();
3952 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003953 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003954 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003955 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003956 final int x = (int) ev.getX();
3957 final int y = (int) ev.getY();
3958 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003959
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003960 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003961 if (mTouchMode == TOUCH_MODE_FLING) {
3962 // Stopped a fling. It is a scroll.
3963 createScrollingCache();
3964 mTouchMode = TOUCH_MODE_SCROLL;
3965 mMotionCorrection = 0;
3966 motionPosition = findMotionRow(y);
3967 mFlingRunnable.flywheelTouch();
3968 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3969 // User clicked on an actual view (and was not stopping a
3970 // fling). It might be a click or a scroll. Assume it is a
3971 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003972 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003973
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003974 // FIXME Debounce
3975 if (mPendingCheckForTap == null) {
3976 mPendingCheckForTap = new CheckForTap();
3977 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003978
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003979 mPendingCheckForTap.x = ev.getX();
3980 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003981 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003982 }
3983 }
3984
3985 if (motionPosition >= 0) {
3986 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003987 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003988 mMotionViewOriginalTop = v.getTop();
3989 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003990
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003991 mMotionX = x;
3992 mMotionY = y;
3993 mMotionPosition = motionPosition;
3994 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003995 }
3996
Alan Viveretteb339cc52013-08-12 13:29:15 -07003997 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003998 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003999 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004000 }
4001 }
4002
Adam Powell96d62af2014-05-02 10:04:38 -07004003 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05004004 if (mHasPerformedLongPress) {
4005 // Consume all move events following a successful long press.
4006 return;
4007 }
4008
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004009 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4010 if (pointerIndex == -1) {
4011 pointerIndex = 0;
4012 mActivePointerId = ev.getPointerId(pointerIndex);
4013 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004014
4015 if (mDataChanged) {
4016 // Re-sync everything if data has been changed
4017 // since the scroll operation can query the adapter.
4018 layoutChildren();
4019 }
4020
Alan Viverette8fa327a2013-05-31 14:53:13 -07004021 final int y = (int) ev.getY(pointerIndex);
4022
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004023 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004024 case TOUCH_MODE_DOWN:
4025 case TOUCH_MODE_TAP:
4026 case TOUCH_MODE_DONE_WAITING:
4027 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07004028 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07004029 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07004030 break;
4031 }
4032 // Otherwise, check containment within list bounds. If we're
4033 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08004034 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07004035 final float x = ev.getX(pointerIndex);
4036 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07004037 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004038 if (motionView != null) {
4039 motionView.setPressed(false);
4040 }
4041 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4042 mPendingCheckForTap : mPendingCheckForLongPress);
4043 mTouchMode = TOUCH_MODE_DONE_WAITING;
4044 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08004045 } else if (motionView != null) {
4046 // Still within bounds, update the hotspot.
4047 final float[] point = mTmpPoint;
4048 point[0] = x;
4049 point[1] = y;
4050 transformPointToViewLocal(point, motionView);
4051 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07004052 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07004053 break;
4054 case TOUCH_MODE_SCROLL:
4055 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07004056 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07004057 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004058 }
4059 }
4060
4061 private void onTouchUp(MotionEvent ev) {
4062 switch (mTouchMode) {
4063 case TOUCH_MODE_DOWN:
4064 case TOUCH_MODE_TAP:
4065 case TOUCH_MODE_DONE_WAITING:
4066 final int motionPosition = mMotionPosition;
4067 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07004068 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004069 if (mTouchMode != TOUCH_MODE_DOWN) {
4070 child.setPressed(false);
4071 }
4072
Alan Viverette74ded292013-06-03 15:34:11 -07004073 final float x = ev.getX();
4074 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
Adam Powell0f552f42017-02-03 11:50:42 -08004075 if (inList && !child.hasExplicitFocusable()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004076 if (mPerformClick == null) {
4077 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004078 }
Alan Viverette74ded292013-06-03 15:34:11 -07004079
4080 final AbsListView.PerformClick performClick = mPerformClick;
4081 performClick.mClickMotionPosition = motionPosition;
4082 performClick.rememberWindowAttachCount();
4083
4084 mResurrectToPosition = motionPosition;
4085
4086 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4087 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4088 mPendingCheckForTap : mPendingCheckForLongPress);
4089 mLayoutMode = LAYOUT_NORMAL;
4090 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4091 mTouchMode = TOUCH_MODE_TAP;
4092 setSelectedPositionInt(mMotionPosition);
4093 layoutChildren();
4094 child.setPressed(true);
4095 positionSelector(mMotionPosition, child);
4096 setPressed(true);
4097 if (mSelector != null) {
4098 Drawable d = mSelector.getCurrent();
4099 if (d != null && d instanceof TransitionDrawable) {
4100 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004101 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004102 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004103 }
Alan Viverette74ded292013-06-03 15:34:11 -07004104 if (mTouchModeReset != null) {
4105 removeCallbacks(mTouchModeReset);
4106 }
4107 mTouchModeReset = new Runnable() {
4108 @Override
4109 public void run() {
4110 mTouchModeReset = null;
4111 mTouchMode = TOUCH_MODE_REST;
4112 child.setPressed(false);
4113 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004114 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004115 performClick.run();
4116 }
4117 }
4118 };
4119 postDelayed(mTouchModeReset,
4120 ViewConfiguration.getPressedStateDuration());
4121 } else {
4122 mTouchMode = TOUCH_MODE_REST;
4123 updateSelectorState();
4124 }
4125 return;
4126 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4127 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004128 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004129 }
4130 }
4131 mTouchMode = TOUCH_MODE_REST;
4132 updateSelectorState();
4133 break;
4134 case TOUCH_MODE_SCROLL:
4135 final int childCount = getChildCount();
4136 if (childCount > 0) {
4137 final int firstChildTop = getChildAt(0).getTop();
4138 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4139 final int contentTop = mListPadding.top;
4140 final int contentBottom = getHeight() - mListPadding.bottom;
4141 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4142 mFirstPosition + childCount < mItemCount &&
4143 lastChildBottom <= getHeight() - contentBottom) {
4144 mTouchMode = TOUCH_MODE_REST;
4145 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4146 } else {
4147 final VelocityTracker velocityTracker = mVelocityTracker;
4148 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4149
4150 final int initialVelocity = (int)
4151 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4152 // Fling if we have enough velocity and we aren't at a boundary.
4153 // Since we can potentially overfling more than we can overscroll, don't
4154 // allow the weird behavior where you can scroll to a boundary then
4155 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004156 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4157 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004158 !((mFirstPosition == 0 &&
4159 firstChildTop == contentTop - mOverscrollDistance) ||
4160 (mFirstPosition + childCount == mItemCount &&
4161 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004162 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4163 if (mFlingRunnable == null) {
4164 mFlingRunnable = new FlingRunnable();
4165 }
4166 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4167 mFlingRunnable.start(-initialVelocity);
4168 dispatchNestedFling(0, -initialVelocity, true);
4169 } else {
4170 mTouchMode = TOUCH_MODE_REST;
4171 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004172 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004173 } else {
4174 mTouchMode = TOUCH_MODE_REST;
4175 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4176 if (mFlingRunnable != null) {
4177 mFlingRunnable.endFling();
4178 }
4179 if (mPositionScroller != null) {
4180 mPositionScroller.stop();
4181 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004182 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004183 dispatchNestedFling(0, -initialVelocity, false);
4184 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004185 }
4186 }
4187 } else {
4188 mTouchMode = TOUCH_MODE_REST;
4189 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4190 }
4191 break;
4192
4193 case TOUCH_MODE_OVERSCROLL:
4194 if (mFlingRunnable == null) {
4195 mFlingRunnable = new FlingRunnable();
4196 }
4197 final VelocityTracker velocityTracker = mVelocityTracker;
4198 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4199 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4200
4201 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4202 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4203 mFlingRunnable.startOverfling(-initialVelocity);
4204 } else {
4205 mFlingRunnable.startSpringback();
4206 }
4207
4208 break;
4209 }
4210
4211 setPressed(false);
4212
Yigit Boyarb6218472019-02-06 10:07:06 -08004213 if (shouldDisplayEdgeEffects()) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004214 mEdgeGlowTop.onRelease();
4215 mEdgeGlowBottom.onRelease();
4216 }
4217
4218 // Need to redraw since we probably aren't drawing the selector anymore
4219 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004220 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004221 recycleVelocityTracker();
4222
4223 mActivePointerId = INVALID_POINTER;
4224
4225 if (PROFILE_SCROLLING) {
4226 if (mScrollProfilingStarted) {
4227 Debug.stopMethodTracing();
4228 mScrollProfilingStarted = false;
4229 }
4230 }
4231
4232 if (mScrollStrictSpan != null) {
4233 mScrollStrictSpan.finish();
4234 mScrollStrictSpan = null;
4235 }
4236 }
4237
Yigit Boyarb6218472019-02-06 10:07:06 -08004238 private boolean shouldDisplayEdgeEffects() {
4239 return getOverScrollMode() != OVER_SCROLL_NEVER;
4240 }
4241
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004242 private void onTouchCancel() {
4243 switch (mTouchMode) {
4244 case TOUCH_MODE_OVERSCROLL:
4245 if (mFlingRunnable == null) {
4246 mFlingRunnable = new FlingRunnable();
4247 }
4248 mFlingRunnable.startSpringback();
4249 break;
4250
4251 case TOUCH_MODE_OVERFLING:
4252 // Do nothing - let it play out.
4253 break;
4254
4255 default:
4256 mTouchMode = TOUCH_MODE_REST;
4257 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004258 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004259 if (motionView != null) {
4260 motionView.setPressed(false);
4261 }
4262 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004263 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004264 recycleVelocityTracker();
4265 }
4266
Yigit Boyarb6218472019-02-06 10:07:06 -08004267 if (shouldDisplayEdgeEffects()) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004268 mEdgeGlowTop.onRelease();
4269 mEdgeGlowBottom.onRelease();
4270 }
4271 mActivePointerId = INVALID_POINTER;
4272 }
4273
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004274 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004275 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4276 if (mScrollY != scrollY) {
4277 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4278 mScrollY = scrollY;
4279 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004280
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004281 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004282 }
Adam Powell637d3372010-08-25 14:37:03 -07004283 }
4284
4285 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004286 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004287 switch (event.getAction()) {
4288 case MotionEvent.ACTION_SCROLL:
4289 final float axisValue;
4290 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4291 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4292 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4293 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4294 } else {
4295 axisValue = 0;
4296 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004297
Aaron Whytef8306522017-03-22 16:30:58 -07004298 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -04004299 if (delta != 0) {
4300 if (!trackMotionScroll(delta, delta)) {
4301 return true;
4302 }
4303 }
4304 break;
4305 case MotionEvent.ACTION_BUTTON_PRESS:
4306 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004307 int actionButton = event.getActionButton();
4308 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4309 || actionButton == MotionEvent.BUTTON_SECONDARY)
4310 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4311 if (performStylusButtonPressAction(event)) {
4312 removeCallbacks(mPendingCheckForLongPress);
4313 removeCallbacks(mPendingCheckForTap);
4314 }
4315 }
Ned Burns20ad0732016-08-18 14:22:57 -04004316 }
4317 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004318 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004319
Jeff Brown33bbfd22011-02-24 20:55:35 -08004320 return super.onGenericMotionEvent(event);
4321 }
4322
Adam Powell4884c642014-08-07 13:52:53 -07004323 /**
4324 * Initiate a fling with the given velocity.
4325 *
4326 * <p>Applications can use this method to manually initiate a fling as if the user
4327 * initiated it via touch interaction.</p>
4328 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004329 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4330 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004331 */
4332 public void fling(int velocityY) {
4333 if (mFlingRunnable == null) {
4334 mFlingRunnable = new FlingRunnable();
4335 }
4336 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004337 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004338 }
4339
Jeff Brown33bbfd22011-02-24 20:55:35 -08004340 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004341 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4342 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4343 }
4344
4345 @Override
4346 public void onNestedScrollAccepted(View child, View target, int axes) {
4347 super.onNestedScrollAccepted(child, target, axes);
4348 startNestedScroll(SCROLL_AXIS_VERTICAL);
4349 }
4350
4351 @Override
4352 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4353 int dxUnconsumed, int dyUnconsumed) {
4354 final int motionIndex = getChildCount() / 2;
4355 final View motionView = getChildAt(motionIndex);
4356 final int oldTop = motionView != null ? motionView.getTop() : 0;
4357 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4358 int myUnconsumed = dyUnconsumed;
4359 int myConsumed = 0;
4360 if (motionView != null) {
4361 myConsumed = motionView.getTop() - oldTop;
4362 myUnconsumed -= myConsumed;
4363 }
4364 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4365 }
4366 }
4367
4368 @Override
4369 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4370 final int childCount = getChildCount();
4371 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4372 Math.abs(velocityY) > mMinimumVelocity) {
4373 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4374 if (mFlingRunnable == null) {
4375 mFlingRunnable = new FlingRunnable();
4376 }
Adam Powell9413b242014-08-06 17:34:24 -07004377 if (!dispatchNestedPreFling(0, velocityY)) {
4378 mFlingRunnable.start((int) velocityY);
4379 }
Adam Powell96d62af2014-05-02 10:04:38 -07004380 return true;
4381 }
4382 return dispatchNestedFling(velocityX, velocityY, consumed);
4383 }
4384
4385 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004386 public void draw(Canvas canvas) {
4387 super.draw(canvas);
Yigit Boyarb6218472019-02-06 10:07:06 -08004388 if (shouldDisplayEdgeEffects()) {
Adam Powell637d3372010-08-25 14:37:03 -07004389 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004390 final boolean clipToPadding = getClipToPadding();
4391 final int width;
4392 final int height;
4393 final int translateX;
4394 final int translateY;
4395
4396 if (clipToPadding) {
4397 width = getWidth() - mPaddingLeft - mPaddingRight;
4398 height = getHeight() - mPaddingTop - mPaddingBottom;
4399 translateX = mPaddingLeft;
4400 translateY = mPaddingTop;
4401 } else {
4402 width = getWidth();
4403 height = getHeight();
4404 translateX = 0;
4405 translateY = 0;
4406 }
Vu Thanh Cong3af8c962016-11-10 22:40:24 +09004407 mEdgeGlowTop.setSize(width, height);
4408 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004409 if (!mEdgeGlowTop.isFinished()) {
4410 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004411 canvas.clipRect(translateX, translateY,
4412 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4413 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4414 canvas.translate(translateX, edgeY);
Adam Powell637d3372010-08-25 14:37:03 -07004415 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004416 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004417 }
4418 canvas.restoreToCount(restoreCount);
4419 }
4420 if (!mEdgeGlowBottom.isFinished()) {
4421 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004422 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4423 translateX + width, translateY + height);
4424 final int edgeX = -width + translateX;
4425 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4426 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004427 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004428 canvas.rotate(180, width, 0);
Adam Powell637d3372010-08-25 14:37:03 -07004429 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004430 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004431 }
4432 canvas.restoreToCount(restoreCount);
4433 }
4434 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004435 }
4436
Michael Jurka13451a42011-08-22 15:54:21 -07004437 private void initOrResetVelocityTracker() {
4438 if (mVelocityTracker == null) {
4439 mVelocityTracker = VelocityTracker.obtain();
4440 } else {
4441 mVelocityTracker.clear();
4442 }
4443 }
4444
4445 private void initVelocityTrackerIfNotExists() {
4446 if (mVelocityTracker == null) {
4447 mVelocityTracker = VelocityTracker.obtain();
4448 }
4449 }
4450
4451 private void recycleVelocityTracker() {
4452 if (mVelocityTracker != null) {
4453 mVelocityTracker.recycle();
4454 mVelocityTracker = null;
4455 }
4456 }
4457
4458 @Override
4459 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4460 if (disallowIntercept) {
4461 recycleVelocityTracker();
4462 }
4463 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4464 }
4465
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004466 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004467 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004468 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004469 return true;
4470 }
4471
4472 return super.onInterceptHoverEvent(event);
4473 }
4474
4475 @Override
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08004476 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4477 if (mFastScroll != null) {
4478 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4479 if (pointerIcon != null) {
4480 return pointerIcon;
4481 }
4482 }
4483 return super.onResolvePointerIcon(event, pointerIndex);
4484 }
4485
4486 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004487 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004488 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004489 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004490
Adam Powell1fa179ef2012-04-12 15:01:40 -07004491 if (mPositionScroller != null) {
4492 mPositionScroller.stop();
4493 }
4494
Alan Viverette462c2172014-02-24 12:24:11 -08004495 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004496 // Something isn't right.
4497 // Since we rely on being attached to get data set change notifications,
4498 // don't risk doing anything where we might try to resync and find things
4499 // in a bogus state.
4500 return false;
4501 }
4502
Alan Viverette8636ace2013-10-31 15:41:31 -07004503 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004504 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004505 }
Romain Guy0a637162009-05-29 14:43:54 -07004506
Adam Powell744beff2014-09-22 09:47:48 -07004507 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004508 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004509 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004510 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4511 mMotionCorrection = 0;
4512 return true;
4513 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004514
Adam Powell4cd47702010-02-25 11:21:14 -08004515 final int x = (int) ev.getX();
4516 final int y = (int) ev.getY();
4517 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004519 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004520 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004521 // User clicked on an actual view (and was not stopping a fling).
4522 // Remember where the motion event started
4523 v = getChildAt(motionPosition - mFirstPosition);
4524 mMotionViewOriginalTop = v.getTop();
4525 mMotionX = x;
4526 mMotionY = y;
4527 mMotionPosition = motionPosition;
4528 mTouchMode = TOUCH_MODE_DOWN;
4529 clearScrollingCache();
4530 }
4531 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004532 initOrResetVelocityTracker();
4533 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004534 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004535 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004536 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004537 return true;
4538 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004539 break;
4540 }
4541
4542 case MotionEvent.ACTION_MOVE: {
4543 switch (mTouchMode) {
4544 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004545 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4546 if (pointerIndex == -1) {
4547 pointerIndex = 0;
4548 mActivePointerId = ev.getPointerId(pointerIndex);
4549 }
Adam Powell4cd47702010-02-25 11:21:14 -08004550 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004551 initVelocityTrackerIfNotExists();
4552 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004553 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004554 return true;
4555 }
4556 break;
4557 }
4558 break;
4559 }
4560
Michael Jurka13451a42011-08-22 15:54:21 -07004561 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004562 case MotionEvent.ACTION_UP: {
4563 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004564 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004565 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004566 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004567 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004568 break;
4569 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004570
Adam Powell4cd47702010-02-25 11:21:14 -08004571 case MotionEvent.ACTION_POINTER_UP: {
4572 onSecondaryPointerUp(ev);
4573 break;
4574 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004575 }
4576
4577 return false;
4578 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004579
Adam Powell4cd47702010-02-25 11:21:14 -08004580 private void onSecondaryPointerUp(MotionEvent ev) {
4581 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4582 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4583 final int pointerId = ev.getPointerId(pointerIndex);
4584 if (pointerId == mActivePointerId) {
4585 // This was our active pointer going up. Choose a new
4586 // active pointer and adjust accordingly.
4587 // TODO: Make this decision more intelligent.
4588 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4589 mMotionX = (int) ev.getX(newPointerIndex);
4590 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004591 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004592 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004593 }
4594 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004595
4596 /**
4597 * {@inheritDoc}
4598 */
4599 @Override
4600 public void addTouchables(ArrayList<View> views) {
4601 final int count = getChildCount();
4602 final int firstPosition = mFirstPosition;
4603 final ListAdapter adapter = mAdapter;
4604
4605 if (adapter == null) {
4606 return;
4607 }
4608
4609 for (int i = 0; i < count; i++) {
4610 final View child = getChildAt(i);
4611 if (adapter.isEnabled(firstPosition + i)) {
4612 views.add(child);
4613 }
4614 child.addTouchables(views);
4615 }
4616 }
4617
4618 /**
4619 * Fires an "on scroll state changed" event to the registered
4620 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4621 * is fired only if the specified state is different from the previously known state.
4622 *
4623 * @param newState The new scroll state.
4624 */
Filip Pavlisab802912019-02-07 16:06:06 +00004625 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769710)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004626 void reportScrollStateChange(int newState) {
4627 if (newState != mLastScrollState) {
4628 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004629 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004630 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004631 }
4632 }
4633 }
4634
4635 /**
4636 * Responsible for fling behavior. Use {@link #start(int)} to
4637 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4638 * A FlingRunnable will keep re-posting itself until the fling is done.
4639 *
4640 */
4641 private class FlingRunnable implements Runnable {
4642 /**
4643 * Tracks the decay of a fling scroll
4644 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01004645 @UnsupportedAppUsage
Adam Powell637d3372010-08-25 14:37:03 -07004646 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004647
4648 /**
4649 * Y value reported by mScroller on the previous fling
4650 */
4651 private int mLastFlingY;
4652
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004653 /**
4654 * If true, {@link #endFling()} will not report scroll state change to
4655 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4656 */
4657 private boolean mSuppressIdleStateChangeCall;
4658
Gilles Debunned348bb42010-11-15 12:19:35 -08004659 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004660 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004661 public void run() {
4662 final int activeId = mActivePointerId;
4663 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004664 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004665 if (vt == null || activeId == INVALID_POINTER) {
4666 return;
4667 }
4668
4669 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4670 final float yvel = -vt.getYVelocity(activeId);
4671
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004672 if (Math.abs(yvel) >= mMinimumVelocity
4673 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004674 // Keep the fling alive a little longer
4675 postDelayed(this, FLYWHEEL_TIMEOUT);
4676 } else {
4677 endFling();
4678 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004679 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004680 }
4681 }
4682 };
4683
4684 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4685
Adam Powell79ac3392010-01-28 21:22:20 -08004686 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004687 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004688 }
4689
Louis Pullen-Freilichf0955ac2019-02-06 19:29:34 +00004690 // Use AbsListView#fling(int) instead
4691 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Adam Powell79ac3392010-01-28 21:22:20 -08004692 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004693 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4694 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004695 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004696 mScroller.fling(0, initialY, 0, initialVelocity,
4697 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4698 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004699 mSuppressIdleStateChangeCall = false;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004700 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004701
4702 if (PROFILE_FLINGING) {
4703 if (!mFlingProfilingStarted) {
4704 Debug.startMethodTracing("AbsListViewFling");
4705 mFlingProfilingStarted = true;
4706 }
4707 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004708
4709 if (mFlingStrictSpan == null) {
4710 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4711 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004712 }
Adam Powell45803472010-01-25 15:10:44 -08004713
Adam Powell637d3372010-08-25 14:37:03 -07004714 void startSpringback() {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004715 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004716 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4717 mTouchMode = TOUCH_MODE_OVERFLING;
4718 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004719 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004720 } else {
4721 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004722 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004723 }
4724 }
4725
4726 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004727 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004728 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4729 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004730 mTouchMode = TOUCH_MODE_OVERFLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004731 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004732 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004733 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004734 }
4735
4736 void edgeReached(int delta) {
4737 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4738 final int overscrollMode = getOverScrollMode();
4739 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4740 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4741 mTouchMode = TOUCH_MODE_OVERFLING;
4742 final int vel = (int) mScroller.getCurrVelocity();
4743 if (delta > 0) {
4744 mEdgeGlowTop.onAbsorb(vel);
4745 } else {
4746 mEdgeGlowBottom.onAbsorb(vel);
4747 }
Adam Powell40322522011-01-12 21:58:20 -08004748 } else {
4749 mTouchMode = TOUCH_MODE_REST;
4750 if (mPositionScroller != null) {
4751 mPositionScroller.stop();
4752 }
Adam Powell637d3372010-08-25 14:37:03 -07004753 }
4754 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004755 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004756 }
4757
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004758 void startScroll(int distance, int duration, boolean linear,
4759 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004760 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4761 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004762 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004763 mScroller.startScroll(0, initialY, 0, distance, duration);
4764 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004765 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004766 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004767 }
4768
Louis Pullen-Freilichf0955ac2019-02-06 19:29:34 +00004769 // To interrupt a fling early you should use smoothScrollBy(0,0) instead
4770 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Gilles Debunned348bb42010-11-15 12:19:35 -08004771 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004772 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004773
Adam Powell79ac3392010-01-28 21:22:20 -08004774 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004775 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004776
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004777 if (!mSuppressIdleStateChangeCall) {
4778 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4779 }
Romain Guy21317d12010-10-12 13:32:31 -07004780 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004781 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004782
4783 if (mFlingStrictSpan != null) {
4784 mFlingStrictSpan.finish();
4785 mFlingStrictSpan = null;
4786 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004787 }
4788
4789 void flywheelTouch() {
4790 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004791 }
4792
Alan Viverette8fa327a2013-05-31 14:53:13 -07004793 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004794 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004795 switch (mTouchMode) {
4796 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004797 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004798 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004799
Gilles Debunned348bb42010-11-15 12:19:35 -08004800 case TOUCH_MODE_SCROLL:
4801 if (mScroller.isFinished()) {
4802 return;
4803 }
4804 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004805 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004806 if (mDataChanged) {
4807 layoutChildren();
4808 }
4809
Adam Powell79ac3392010-01-28 21:22:20 -08004810 if (mItemCount == 0 || getChildCount() == 0) {
4811 endFling();
4812 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004813 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004814
Adam Powell637d3372010-08-25 14:37:03 -07004815 final OverScroller scroller = mScroller;
4816 boolean more = scroller.computeScrollOffset();
4817 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004818
Adam Powell637d3372010-08-25 14:37:03 -07004819 // Flip sign to convert finger direction to list items direction
4820 // (e.g. finger moving down means list is moving towards the top)
4821 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004822
Adam Powell637d3372010-08-25 14:37:03 -07004823 // Pretend that each frame of a fling scroll is a touch scroll
4824 if (delta > 0) {
4825 // List is moving towards the top. Use first view as mMotionPosition
4826 mMotionPosition = mFirstPosition;
4827 final View firstView = getChildAt(0);
4828 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004829
Adam Powell637d3372010-08-25 14:37:03 -07004830 // Don't fling more than 1 screen
4831 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4832 } else {
4833 // List is moving towards the bottom. Use last view as mMotionPosition
4834 int offsetToLast = getChildCount() - 1;
4835 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004836
Adam Powell637d3372010-08-25 14:37:03 -07004837 final View lastView = getChildAt(offsetToLast);
4838 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004839
Adam Powell637d3372010-08-25 14:37:03 -07004840 // Don't fling more than 1 screen
4841 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4842 }
Adam Powell79ac3392010-01-28 21:22:20 -08004843
Adam Powell637d3372010-08-25 14:37:03 -07004844 // Check to see if we have bumped into the scroll limit
4845 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4846 int oldTop = 0;
4847 if (motionView != null) {
4848 oldTop = motionView.getTop();
4849 }
Adam Powell9d32d242010-03-29 16:02:07 -07004850
Adam Powell637d3372010-08-25 14:37:03 -07004851 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004852 final boolean atEdge = trackMotionScroll(delta, delta);
4853 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004854 if (atEnd) {
4855 if (motionView != null) {
4856 // Tweak the scroll for how far we overshot
4857 int overshoot = -(delta - (motionView.getTop() - oldTop));
4858 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4859 0, mOverflingDistance, false);
4860 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004861 if (more) {
4862 edgeReached(delta);
4863 }
Adam Powell637d3372010-08-25 14:37:03 -07004864 break;
4865 }
4866
4867 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004868 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004869 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004870 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004871 } else {
4872 endFling();
4873
4874 if (PROFILE_FLINGING) {
4875 if (mFlingProfilingStarted) {
4876 Debug.stopMethodTracing();
4877 mFlingProfilingStarted = false;
4878 }
4879
4880 if (mFlingStrictSpan != null) {
4881 mFlingStrictSpan.finish();
4882 mFlingStrictSpan = null;
4883 }
Adam Powell79ac3392010-01-28 21:22:20 -08004884 }
4885 }
Adam Powell637d3372010-08-25 14:37:03 -07004886 break;
4887 }
4888
4889 case TOUCH_MODE_OVERFLING: {
4890 final OverScroller scroller = mScroller;
4891 if (scroller.computeScrollOffset()) {
4892 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004893 final int currY = scroller.getCurrY();
4894 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004895 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4896 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004897 final boolean crossDown = scrollY <= 0 && currY > 0;
4898 final boolean crossUp = scrollY >= 0 && currY < 0;
4899 if (crossDown || crossUp) {
4900 int velocity = (int) scroller.getCurrVelocity();
4901 if (crossUp) velocity = -velocity;
4902
4903 // Don't flywheel from this; we're just continuing things.
4904 scroller.abortAnimation();
4905 start(velocity);
4906 } else {
4907 startSpringback();
4908 }
Adam Powell637d3372010-08-25 14:37:03 -07004909 } else {
4910 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004911 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004912 }
4913 } else {
4914 endFling();
4915 }
4916 break;
4917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004918 }
4919 }
4920 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004921
Adam Powell45803472010-01-25 15:10:44 -08004922 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004923 * The amount of friction applied to flings. The default value
4924 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004925 */
4926 public void setFriction(float friction) {
4927 if (mFlingRunnable == null) {
4928 mFlingRunnable = new FlingRunnable();
4929 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004930 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004931 }
Romain Guy21317d12010-10-12 13:32:31 -07004932
4933 /**
4934 * Sets a scale factor for the fling velocity. The initial scale
4935 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004936 *
Romain Guy21317d12010-10-12 13:32:31 -07004937 * @param scale The scale factor to multiply the velocity by.
4938 */
4939 public void setVelocityScale(float scale) {
4940 mVelocityScale = scale;
4941 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004942
Romain Guy4bede9e2010-10-11 19:36:59 -07004943 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004944 * Override this for better control over position scrolling.
4945 */
4946 AbsPositionScroller createPositionScroller() {
4947 return new PositionScroller();
4948 }
4949
4950 /**
Adam Powell45803472010-01-25 15:10:44 -08004951 * Smoothly scroll to the specified adapter position. The view will
4952 * scroll such that the indicated position is displayed.
4953 * @param position Scroll to this adapter position.
4954 */
4955 public void smoothScrollToPosition(int position) {
4956 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004957 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004958 }
4959 mPositionScroller.start(position);
4960 }
Erik322171b2010-10-13 15:46:00 -07004961
4962 /**
4963 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004964 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004965 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4966 * the first or last item beyond the boundaries of the list) it will get as close
4967 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4968 *
4969 * @param position Position to scroll to
4970 * @param offset Desired distance in pixels of <code>position</code> from the top
4971 * of the view when scrolling is finished
4972 * @param duration Number of milliseconds to use for the scroll
4973 */
4974 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4975 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004976 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004977 }
4978 mPositionScroller.startWithOffset(position, offset, duration);
4979 }
4980
Adam Powell45803472010-01-25 15:10:44 -08004981 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004982 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004983 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004984 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4985 * the first or last item beyond the boundaries of the list) it will get as close
4986 * as possible.
4987 *
4988 * @param position Position to scroll to
4989 * @param offset Desired distance in pixels of <code>position</code> from the top
4990 * of the view when scrolling is finished
4991 */
4992 public void smoothScrollToPositionFromTop(int position, int offset) {
4993 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004994 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004995 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004996 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004997 }
4998
4999 /**
Adam Powell45803472010-01-25 15:10:44 -08005000 * Smoothly scroll to the specified adapter position. The view will
5001 * scroll such that the indicated position is displayed, but it will
5002 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08005003 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08005004 *
Adam Powell45803472010-01-25 15:10:44 -08005005 * @param position Scroll to this adapter position.
5006 * @param boundPosition Do not scroll if it would move this adapter
5007 * position out of view.
5008 */
5009 public void smoothScrollToPosition(int position, int boundPosition) {
5010 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08005011 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08005012 }
5013 mPositionScroller.start(position, boundPosition);
5014 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005015
Adam Powell45803472010-01-25 15:10:44 -08005016 /**
5017 * Smoothly scroll by distance pixels over duration milliseconds.
5018 * @param distance Distance to scroll in pixels.
5019 * @param duration Duration of the scroll animation in milliseconds.
5020 */
5021 public void smoothScrollBy(int distance, int duration) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005022 smoothScrollBy(distance, duration, false, false);
Adam Powell0b8acd82012-04-25 20:29:23 -07005023 }
5024
Mathew Inwood978c6e22018-08-21 15:58:55 +01005025 @UnsupportedAppUsage
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005026 void smoothScrollBy(int distance, int duration, boolean linear,
5027 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08005028 if (mFlingRunnable == null) {
5029 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08005030 }
Adam Powell40322522011-01-12 21:58:20 -08005031
Marc Blank299acb52010-10-21 11:03:53 -07005032 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08005033 final int firstPos = mFirstPosition;
5034 final int childCount = getChildCount();
5035 final int lastPos = firstPos + childCount;
5036 final int topLimit = getPaddingTop();
5037 final int bottomLimit = getHeight() - getPaddingBottom();
5038
Adam Powell79303752011-01-13 22:06:49 -08005039 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08005040 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07005041 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08005042 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
5043 mFlingRunnable.endFling();
5044 if (mPositionScroller != null) {
5045 mPositionScroller.stop();
5046 }
5047 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08005048 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005049 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
Marc Blank299acb52010-10-21 11:03:53 -07005050 }
Adam Powell45803472010-01-25 15:10:44 -08005051 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005052
Winson Chung499cb9f2010-07-16 11:18:17 -07005053 /**
5054 * Allows RemoteViews to scroll relatively to a position.
5055 */
5056 void smoothScrollByOffset(int position) {
5057 int index = -1;
5058 if (position < 0) {
5059 index = getFirstVisiblePosition();
5060 } else if (position > 0) {
5061 index = getLastVisiblePosition();
5062 }
5063
5064 if (index > -1) {
5065 View child = getChildAt(index - getFirstVisiblePosition());
5066 if (child != null) {
5067 Rect visibleRect = new Rect();
5068 if (child.getGlobalVisibleRect(visibleRect)) {
5069 // the child is partially visible
5070 int childRectArea = child.getWidth() * child.getHeight();
5071 int visibleRectArea = visibleRect.width() * visibleRect.height();
5072 float visibleArea = (visibleRectArea / (float) childRectArea);
5073 final float visibleThreshold = 0.75f;
5074 if ((position < 0) && (visibleArea < visibleThreshold)) {
5075 // the top index is not perceivably visible so offset
5076 // to account for showing that top index as well
5077 ++index;
5078 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
5079 // the bottom index is not perceivably visible so offset
5080 // to account for showing that bottom index as well
5081 --index;
5082 }
5083 }
5084 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5085 }
5086 }
5087 }
5088
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005089 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005090 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005091 setChildrenDrawnWithCacheEnabled(true);
5092 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08005093 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005094 }
5095 }
5096
5097 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005098 if (!isHardwareAccelerated()) {
5099 if (mClearScrollingCache == null) {
5100 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07005101 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07005102 public void run() {
5103 if (mCachingStarted) {
5104 mCachingStarted = mCachingActive = false;
5105 setChildrenDrawnWithCacheEnabled(false);
5106 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5107 setChildrenDrawingCacheEnabled(false);
5108 }
5109 if (!isAlwaysDrawnWithCacheEnabled()) {
5110 invalidate();
5111 }
Romain Guy6dfed242009-05-11 18:25:05 -07005112 }
5113 }
Romain Guy9d849a22012-03-14 16:41:42 -07005114 };
5115 }
5116 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005117 }
5118 }
5119
5120 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005121 * Scrolls the list items within the view by a specified number of pixels.
5122 *
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005123 * <p>The actual amount of scroll is capped by the list content viewport height
5124 * which is the list height minus top and bottom paddings minus one pixel.</p>
5125 *
Alan Viverette2f3317a2013-08-06 18:19:48 -07005126 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005127 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005128 */
Alan Viveretteba299062013-09-03 16:01:51 -07005129 public void scrollListBy(int y) {
5130 trackMotionScroll(-y, -y);
5131 }
5132
5133 /**
5134 * Check if the items in the list can be scrolled in a certain direction.
5135 *
5136 * @param direction Negative to check scrolling up, positive to check
5137 * scrolling down.
5138 * @return true if the list can be scrolled in the specified direction,
5139 * false otherwise.
5140 * @see #scrollListBy(int)
5141 */
5142 public boolean canScrollList(int direction) {
5143 final int childCount = getChildCount();
5144 if (childCount == 0) {
5145 return false;
5146 }
5147
5148 final int firstPosition = mFirstPosition;
5149 final Rect listPadding = mListPadding;
5150 if (direction > 0) {
5151 final int lastBottom = getChildAt(childCount - 1).getBottom();
5152 final int lastPosition = firstPosition + childCount;
5153 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5154 } else {
5155 final int firstTop = getChildAt(0).getTop();
5156 return firstPosition > 0 || firstTop < listPadding.top;
5157 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005158 }
5159
5160 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005161 * Track a motion scroll
5162 *
5163 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5164 * began. Positive numbers mean the user's finger is moving down the screen.
5165 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005166 * @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 -08005167 */
Rahul Ravikumar6933d7e2019-02-07 13:59:18 -08005168 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051739)
Adam Powell45803472010-01-25 15:10:44 -08005169 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005170 final int childCount = getChildCount();
5171 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005172 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005173 }
5174
5175 final int firstTop = getChildAt(0).getTop();
5176 final int lastBottom = getChildAt(childCount - 1).getBottom();
5177
5178 final Rect listPadding = mListPadding;
5179
Adam Powellbdccc2d2010-12-14 17:34:27 -08005180 // "effective padding" In this case is the amount of padding that affects
5181 // how much space should not be filled by items. If we don't clip to padding
5182 // there is no effective padding.
5183 int effectivePaddingTop = 0;
5184 int effectivePaddingBottom = 0;
5185 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5186 effectivePaddingTop = listPadding.top;
5187 effectivePaddingBottom = listPadding.bottom;
5188 }
5189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005190 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005191 final int spaceAbove = effectivePaddingTop - firstTop;
5192 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005193 final int spaceBelow = lastBottom - end;
5194
5195 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5196 if (deltaY < 0) {
5197 deltaY = Math.max(-(height - 1), deltaY);
5198 } else {
5199 deltaY = Math.min(height - 1, deltaY);
5200 }
5201
5202 if (incrementalDeltaY < 0) {
5203 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5204 } else {
5205 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5206 }
5207
Adam Powell45803472010-01-25 15:10:44 -08005208 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005209
Adam Powell637d3372010-08-25 14:37:03 -07005210 // Update our guesses for where the first and last views are
5211 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005212 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005213 } else {
5214 mFirstPositionDistanceGuess += incrementalDeltaY;
5215 }
5216 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005217 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005218 } else {
5219 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005220 }
Adam Powell45803472010-01-25 15:10:44 -08005221
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005222 final boolean cannotScrollDown = (firstPosition == 0 &&
5223 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5224 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5225 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005226
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005227 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005228 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005229 }
5230
5231 final boolean down = incrementalDeltaY < 0;
5232
Adam Powell029cfbd2010-03-08 19:03:54 -08005233 final boolean inTouchMode = isInTouchMode();
5234 if (inTouchMode) {
5235 hideSelector();
5236 }
Adam Powell45803472010-01-25 15:10:44 -08005237
5238 final int headerViewsCount = getHeaderViewsCount();
5239 final int footerViewsStart = mItemCount - getFooterViewsCount();
5240
5241 int start = 0;
5242 int count = 0;
5243
5244 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005245 int top = -incrementalDeltaY;
5246 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5247 top += listPadding.top;
5248 }
Adam Powell45803472010-01-25 15:10:44 -08005249 for (int i = 0; i < childCount; i++) {
5250 final View child = getChildAt(i);
5251 if (child.getBottom() >= top) {
5252 break;
5253 } else {
5254 count++;
5255 int position = firstPosition + i;
5256 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005257 // The view will be rebound to new data, clear any
5258 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005259 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005260 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005261 }
5262 }
5263 }
5264 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005265 int bottom = getHeight() - incrementalDeltaY;
5266 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5267 bottom -= listPadding.bottom;
5268 }
Adam Powell45803472010-01-25 15:10:44 -08005269 for (int i = childCount - 1; i >= 0; i--) {
5270 final View child = getChildAt(i);
5271 if (child.getTop() <= bottom) {
5272 break;
5273 } else {
5274 start = i;
5275 count++;
5276 int position = firstPosition + i;
5277 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005278 // The view will be rebound to new data, clear any
5279 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005280 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005281 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005282 }
5283 }
5284 }
5285 }
5286
5287 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5288
5289 mBlockLayoutRequests = true;
5290
5291 if (count > 0) {
5292 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005293 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005294 }
Adam Powell539ee872012-02-03 19:00:49 -08005295
Romain Guy9d849a22012-03-14 16:41:42 -07005296 // invalidate before moving the children to avoid unnecessary invalidate
5297 // calls to bubble up from the children all the way to the top
5298 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005299 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005300 }
5301
Adam Powell45803472010-01-25 15:10:44 -08005302 offsetChildrenTopAndBottom(incrementalDeltaY);
5303
5304 if (down) {
5305 mFirstPosition += count;
5306 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005307
Adam Powell45803472010-01-25 15:10:44 -08005308 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5309 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5310 fillGap(down);
5311 }
5312
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005313 mRecycler.fullyDetachScrapViews();
Evan Rosky837ae0d2017-10-26 12:50:33 -07005314 boolean selectorOnScreen = false;
Adam Powell029cfbd2010-03-08 19:03:54 -08005315 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005316 final int childIndex = mSelectedPosition - mFirstPosition;
5317 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005318 positionSelector(mSelectedPosition, getChildAt(childIndex));
Evan Rosky837ae0d2017-10-26 12:50:33 -07005319 selectorOnScreen = true;
Adam Powell2a20ddd2010-03-11 18:09:59 -08005320 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005321 } else if (mSelectorPosition != INVALID_POSITION) {
5322 final int childIndex = mSelectorPosition - mFirstPosition;
5323 if (childIndex >= 0 && childIndex < getChildCount()) {
Evan Rosky837ae0d2017-10-26 12:50:33 -07005324 positionSelector(mSelectorPosition, getChildAt(childIndex));
5325 selectorOnScreen = true;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005326 }
Evan Rosky837ae0d2017-10-26 12:50:33 -07005327 }
5328 if (!selectorOnScreen) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005329 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005330 }
5331
Adam Powell45803472010-01-25 15:10:44 -08005332 mBlockLayoutRequests = false;
5333
5334 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005335
Adam Powell45803472010-01-25 15:10:44 -08005336 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005337 }
5338
5339 /**
5340 * Returns the number of header views in the list. Header views are special views
5341 * at the top of the list that should not be recycled during a layout.
5342 *
5343 * @return The number of header views, 0 in the default implementation.
5344 */
5345 int getHeaderViewsCount() {
5346 return 0;
5347 }
5348
5349 /**
5350 * Returns the number of footer views in the list. Footer views are special views
5351 * at the bottom of the list that should not be recycled during a layout.
5352 *
5353 * @return The number of footer views, 0 in the default implementation.
5354 */
5355 int getFooterViewsCount() {
5356 return 0;
5357 }
5358
5359 /**
5360 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5361 * remain on screen are shifted and the other ones are discarded. The role of this
5362 * method is to fill the gap thus created by performing a partial layout in the
5363 * empty space.
5364 *
5365 * @param down true if the scroll is going down, false if it is going up
5366 */
5367 abstract void fillGap(boolean down);
5368
5369 void hideSelector() {
5370 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005371 if (mLayoutMode != LAYOUT_SPECIFIC) {
5372 mResurrectToPosition = mSelectedPosition;
5373 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005374 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5375 mResurrectToPosition = mNextSelectedPosition;
5376 }
5377 setSelectedPositionInt(INVALID_POSITION);
5378 setNextSelectedPositionInt(INVALID_POSITION);
5379 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005380 }
5381 }
5382
5383 /**
5384 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5385 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5386 * of items available in the adapter
5387 */
5388 int reconcileSelectedPosition() {
5389 int position = mSelectedPosition;
5390 if (position < 0) {
5391 position = mResurrectToPosition;
5392 }
5393 position = Math.max(0, position);
5394 position = Math.min(position, mItemCount - 1);
5395 return position;
5396 }
5397
5398 /**
5399 * Find the row closest to y. This row will be used as the motion row when scrolling
5400 *
5401 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005402 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005403 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01005404 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005405 abstract int findMotionRow(int y);
5406
5407 /**
Adam Powell637d3372010-08-25 14:37:03 -07005408 * Find the row closest to y. This row will be used as the motion row when scrolling.
5409 *
5410 * @param y Where the user touched
5411 * @return The position of the first (or only) item in the row closest to y
5412 */
5413 int findClosestMotionRow(int y) {
5414 final int childCount = getChildCount();
5415 if (childCount == 0) {
5416 return INVALID_POSITION;
5417 }
5418
5419 final int motionRow = findMotionRow(y);
5420 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5421 }
5422
5423 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005424 * Causes all the views to be rebuilt and redrawn.
5425 */
5426 public void invalidateViews() {
5427 mDataChanged = true;
5428 rememberSyncState();
5429 requestLayout();
5430 invalidate();
5431 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005432
Jeff Brown4e6319b2010-12-13 10:36:51 -08005433 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005434 * If there is a selection returns false.
5435 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005436 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01005437 @UnsupportedAppUsage
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005438 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005439 if (mSelectedPosition < 0 && resurrectSelection()) {
5440 updateSelectorState();
5441 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005442 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005443 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005444 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005445
5446 /**
5447 * Makes the item at the supplied position selected.
5448 *
5449 * @param position the position of the new selection
5450 */
5451 abstract void setSelectionInt(int position);
5452
5453 /**
5454 * Attempt to bring the selection back if the user is switching from touch
5455 * to trackball mode
5456 * @return Whether selection was set to something.
5457 */
5458 boolean resurrectSelection() {
5459 final int childCount = getChildCount();
5460
5461 if (childCount <= 0) {
5462 return false;
5463 }
5464
5465 int selectedTop = 0;
5466 int selectedPos;
5467 int childrenTop = mListPadding.top;
5468 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5469 final int firstPosition = mFirstPosition;
5470 final int toPosition = mResurrectToPosition;
5471 boolean down = true;
5472
5473 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5474 selectedPos = toPosition;
5475
5476 final View selected = getChildAt(selectedPos - mFirstPosition);
5477 selectedTop = selected.getTop();
5478 int selectedBottom = selected.getBottom();
5479
5480 // We are scrolled, don't get in the fade
5481 if (selectedTop < childrenTop) {
5482 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5483 } else if (selectedBottom > childrenBottom) {
5484 selectedTop = childrenBottom - selected.getMeasuredHeight()
5485 - getVerticalFadingEdgeLength();
5486 }
5487 } else {
5488 if (toPosition < firstPosition) {
5489 // Default to selecting whatever is first
5490 selectedPos = firstPosition;
5491 for (int i = 0; i < childCount; i++) {
5492 final View v = getChildAt(i);
5493 final int top = v.getTop();
5494
5495 if (i == 0) {
5496 // Remember the position of the first item
5497 selectedTop = top;
5498 // See if we are scrolled at all
5499 if (firstPosition > 0 || top < childrenTop) {
5500 // If we are scrolled, don't select anything that is
5501 // in the fade region
5502 childrenTop += getVerticalFadingEdgeLength();
5503 }
5504 }
5505 if (top >= childrenTop) {
5506 // Found a view whose top is fully visisble
5507 selectedPos = firstPosition + i;
5508 selectedTop = top;
5509 break;
5510 }
5511 }
5512 } else {
5513 final int itemCount = mItemCount;
5514 down = false;
5515 selectedPos = firstPosition + childCount - 1;
5516
5517 for (int i = childCount - 1; i >= 0; i--) {
5518 final View v = getChildAt(i);
5519 final int top = v.getTop();
5520 final int bottom = v.getBottom();
5521
5522 if (i == childCount - 1) {
5523 selectedTop = top;
5524 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5525 childrenBottom -= getVerticalFadingEdgeLength();
5526 }
5527 }
5528
5529 if (bottom <= childrenBottom) {
5530 selectedPos = firstPosition + i;
5531 selectedTop = top;
5532 break;
5533 }
5534 }
5535 }
5536 }
5537
5538 mResurrectToPosition = INVALID_POSITION;
5539 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005540 if (mPositionScroller != null) {
5541 mPositionScroller.stop();
5542 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005543 mTouchMode = TOUCH_MODE_REST;
5544 clearScrollingCache();
5545 mSpecificTop = selectedTop;
5546 selectedPos = lookForSelectablePosition(selectedPos, down);
5547 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5548 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005549 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005550 setSelectionInt(selectedPos);
5551 invokeOnItemScrollListener();
5552 } else {
5553 selectedPos = INVALID_POSITION;
5554 }
5555 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5556
5557 return selectedPos >= 0;
5558 }
5559
Adam Powell14c08042011-10-06 19:46:18 -07005560 void confirmCheckedPositionsById() {
5561 // Clear out the positional check states, we'll rebuild it below from IDs.
5562 mCheckStates.clear();
5563
5564 boolean checkedCountChanged = false;
5565 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5566 final long id = mCheckedIdStates.keyAt(checkedIndex);
5567 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5568
5569 final long lastPosId = mAdapter.getItemId(lastPos);
5570 if (id != lastPosId) {
5571 // Look around to see if the ID is nearby. If not, uncheck it.
5572 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5573 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5574 boolean found = false;
5575 for (int searchPos = start; searchPos < end; searchPos++) {
5576 final long searchId = mAdapter.getItemId(searchPos);
5577 if (id == searchId) {
5578 found = true;
5579 mCheckStates.put(searchPos, true);
5580 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5581 break;
5582 }
5583 }
5584
5585 if (!found) {
5586 mCheckedIdStates.delete(id);
5587 checkedIndex--;
5588 mCheckedItemCount--;
5589 checkedCountChanged = true;
5590 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5591 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5592 lastPos, id, false);
5593 }
5594 }
5595 } else {
5596 mCheckStates.put(lastPos, true);
5597 }
5598 }
5599
5600 if (checkedCountChanged && mChoiceActionMode != null) {
5601 mChoiceActionMode.invalidate();
5602 }
5603 }
5604
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005605 @Override
5606 protected void handleDataChanged() {
5607 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005608 int lastHandledItemCount = mLastHandledItemCount;
5609 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005610
5611 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5612 confirmCheckedPositionsById();
5613 }
5614
Adam Powell539ee872012-02-03 19:00:49 -08005615 // TODO: In the future we can recycle these views based on stable ID instead.
5616 mRecycler.clearTransientStateViews();
5617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005618 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005619 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005620 int selectablePos;
5621
5622 // Find the row we are supposed to sync to
5623 if (mNeedSync) {
5624 // Update this first, since setNextSelectedPositionInt inspects it
5625 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005626 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005627
Adam Powell07852792010-11-10 16:57:05 -08005628 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005629 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5630 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005631 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5632 if (mForceTranscriptScroll) {
5633 mForceTranscriptScroll = false;
5634 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5635 return;
5636 }
Adam Powell07852792010-11-10 16:57:05 -08005637 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005638 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005639 final View lastChild = getChildAt(childCount - 1);
5640 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005641 if (mFirstPosition + childCount >= lastHandledItemCount &&
5642 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005643 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5644 return;
5645 }
5646 // Something new came in and we didn't scroll; give the user a clue that
5647 // there's something new.
5648 awakenScrollBars();
5649 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005650
5651 switch (mSyncMode) {
5652 case SYNC_SELECTED_POSITION:
5653 if (isInTouchMode()) {
5654 // We saved our state when not in touch mode. (We know this because
5655 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5656 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5657 // adjusting if the available range changed) and return.
5658 mLayoutMode = LAYOUT_SYNC;
5659 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5660
5661 return;
5662 } else {
5663 // See if we can find a position in the new data with the same
5664 // id as the old selection. This will change mSyncPosition.
5665 newPos = findSyncPosition();
5666 if (newPos >= 0) {
5667 // Found it. Now verify that new selection is still selectable
5668 selectablePos = lookForSelectablePosition(newPos, true);
5669 if (selectablePos == newPos) {
5670 // Same row id is selected
5671 mSyncPosition = newPos;
5672
5673 if (mSyncHeight == getHeight()) {
5674 // If we are at the same height as when we saved state, try
5675 // to restore the scroll position too.
5676 mLayoutMode = LAYOUT_SYNC;
5677 } else {
5678 // We are not the same height as when the selection was saved, so
5679 // don't try to restore the exact position
5680 mLayoutMode = LAYOUT_SET_SELECTION;
5681 }
5682
5683 // Restore selection
5684 setNextSelectedPositionInt(newPos);
5685 return;
5686 }
5687 }
5688 }
5689 break;
5690 case SYNC_FIRST_POSITION:
5691 // Leave mSyncPosition as it is -- just pin to available range
5692 mLayoutMode = LAYOUT_SYNC;
5693 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5694
5695 return;
5696 }
5697 }
5698
5699 if (!isInTouchMode()) {
5700 // We couldn't find matching data -- try to use the same position
5701 newPos = getSelectedItemPosition();
5702
5703 // Pin position to the available range
5704 if (newPos >= count) {
5705 newPos = count - 1;
5706 }
5707 if (newPos < 0) {
5708 newPos = 0;
5709 }
5710
5711 // Make sure we select something selectable -- first look down
5712 selectablePos = lookForSelectablePosition(newPos, true);
5713
5714 if (selectablePos >= 0) {
5715 setNextSelectedPositionInt(selectablePos);
5716 return;
5717 } else {
5718 // Looking down didn't work -- try looking up
5719 selectablePos = lookForSelectablePosition(newPos, false);
5720 if (selectablePos >= 0) {
5721 setNextSelectedPositionInt(selectablePos);
5722 return;
5723 }
5724 }
5725 } else {
5726
5727 // We already know where we want to resurrect the selection
5728 if (mResurrectToPosition >= 0) {
5729 return;
5730 }
5731 }
5732
5733 }
5734
5735 // Nothing is selected. Give up and reset everything.
5736 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5737 mSelectedPosition = INVALID_POSITION;
5738 mSelectedRowId = INVALID_ROW_ID;
5739 mNextSelectedPosition = INVALID_POSITION;
5740 mNextSelectedRowId = INVALID_ROW_ID;
5741 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005742 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005743 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005744 checkSelectionChanged();
5745 }
5746
Romain Guy43c9cdf2010-01-27 13:53:55 -08005747 @Override
5748 protected void onDisplayHint(int hint) {
5749 super.onDisplayHint(hint);
5750 switch (hint) {
5751 case INVISIBLE:
5752 if (mPopup != null && mPopup.isShowing()) {
5753 dismissPopup();
5754 }
5755 break;
5756 case VISIBLE:
5757 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5758 showPopup();
5759 }
5760 break;
5761 }
Romain Guy24562482010-02-01 14:56:19 -08005762 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005763 }
5764
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005765 /**
5766 * Removes the filter window
5767 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005768 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005769 if (mPopup != null) {
5770 mPopup.dismiss();
5771 }
5772 }
5773
5774 /**
5775 * Shows the filter window
5776 */
5777 private void showPopup() {
5778 // Make sure we have a window before showing the popup
5779 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005780 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005781 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005782 // Make sure we get focus if we are showing the popup
5783 checkFocus();
5784 }
5785 }
5786
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005787 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005788 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5789 final int[] xy = new int[2];
5790 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005791 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005792 // TODO: And the gravity should be defined in the theme as well
5793 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005794 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005795 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5796 xy[0], bottomGap);
5797 } else {
5798 mPopup.update(xy[0], bottomGap, -1, -1);
5799 }
5800 }
5801
5802 /**
5803 * What is the distance between the source and destination rectangles given the direction of
5804 * focus navigation between them? The direction basically helps figure out more quickly what is
5805 * self evident by the relationship between the rects...
5806 *
5807 * @param source the source rectangle
5808 * @param dest the destination rectangle
5809 * @param direction the direction
5810 * @return the distance between the rectangles
5811 */
5812 static int getDistance(Rect source, Rect dest, int direction) {
5813 int sX, sY; // source x, y
5814 int dX, dY; // dest x, y
5815 switch (direction) {
5816 case View.FOCUS_RIGHT:
5817 sX = source.right;
5818 sY = source.top + source.height() / 2;
5819 dX = dest.left;
5820 dY = dest.top + dest.height() / 2;
5821 break;
5822 case View.FOCUS_DOWN:
5823 sX = source.left + source.width() / 2;
5824 sY = source.bottom;
5825 dX = dest.left + dest.width() / 2;
5826 dY = dest.top;
5827 break;
5828 case View.FOCUS_LEFT:
5829 sX = source.left;
5830 sY = source.top + source.height() / 2;
5831 dX = dest.right;
5832 dY = dest.top + dest.height() / 2;
5833 break;
5834 case View.FOCUS_UP:
5835 sX = source.left + source.width() / 2;
5836 sY = source.top;
5837 dX = dest.left + dest.width() / 2;
5838 dY = dest.bottom;
5839 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005840 case View.FOCUS_FORWARD:
5841 case View.FOCUS_BACKWARD:
5842 sX = source.right + source.width() / 2;
5843 sY = source.top + source.height() / 2;
5844 dX = dest.left + dest.width() / 2;
5845 dY = dest.top + dest.height() / 2;
5846 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005847 default:
5848 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005849 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5850 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005851 }
5852 int deltaX = dX - sX;
5853 int deltaY = dY - sY;
5854 return deltaY * deltaY + deltaX * deltaX;
5855 }
5856
5857 @Override
5858 protected boolean isInFilterMode() {
5859 return mFiltered;
5860 }
5861
5862 /**
5863 * Sends a key to the text filter window
5864 *
5865 * @param keyCode The keycode for the event
5866 * @param event The actual key event
5867 *
5868 * @return True if the text filter handled the event, false otherwise.
5869 */
5870 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005871 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005872 return false;
5873 }
5874
5875 boolean handled = false;
5876 boolean okToSend = true;
5877 switch (keyCode) {
5878 case KeyEvent.KEYCODE_DPAD_UP:
5879 case KeyEvent.KEYCODE_DPAD_DOWN:
5880 case KeyEvent.KEYCODE_DPAD_LEFT:
5881 case KeyEvent.KEYCODE_DPAD_RIGHT:
5882 case KeyEvent.KEYCODE_DPAD_CENTER:
5883 case KeyEvent.KEYCODE_ENTER:
5884 okToSend = false;
5885 break;
5886 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005887 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005888 if (event.getAction() == KeyEvent.ACTION_DOWN
5889 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005890 KeyEvent.DispatcherState state = getKeyDispatcherState();
5891 if (state != null) {
5892 state.startTracking(event, this);
5893 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005894 handled = true;
5895 } else if (event.getAction() == KeyEvent.ACTION_UP
5896 && event.isTracking() && !event.isCanceled()) {
5897 handled = true;
5898 mTextFilter.setText("");
5899 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005900 }
5901 okToSend = false;
5902 break;
5903 case KeyEvent.KEYCODE_SPACE:
5904 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005905 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005906 break;
5907 }
5908
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005909 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005910 createTextFilter(true);
5911
5912 KeyEvent forwardEvent = event;
5913 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005914 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005915 }
5916
5917 int action = event.getAction();
5918 switch (action) {
5919 case KeyEvent.ACTION_DOWN:
5920 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5921 break;
5922
5923 case KeyEvent.ACTION_UP:
5924 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5925 break;
5926
5927 case KeyEvent.ACTION_MULTIPLE:
5928 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5929 break;
5930 }
5931 }
5932 return handled;
5933 }
5934
5935 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005936 * Return an InputConnection for editing of the filter text.
5937 */
5938 @Override
5939 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005940 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005941 if (mPublicInputConnection == null) {
5942 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005943 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005944 }
Romain Guyf6991302013-06-05 17:19:01 -07005945 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005946 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5947 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005948 }
5949 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005950 }
Romain Guy0a637162009-05-29 14:43:54 -07005951
Romain Guyf6991302013-06-05 17:19:01 -07005952 private class InputConnectionWrapper implements InputConnection {
5953 private final EditorInfo mOutAttrs;
5954 private InputConnection mTarget;
5955
5956 public InputConnectionWrapper(EditorInfo outAttrs) {
5957 mOutAttrs = outAttrs;
5958 }
5959
5960 private InputConnection getTarget() {
5961 if (mTarget == null) {
5962 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5963 }
5964 return mTarget;
5965 }
5966
5967 @Override
5968 public boolean reportFullscreenMode(boolean enabled) {
5969 // Use our own input connection, since it is
5970 // the "real" one the IME is talking with.
5971 return mDefInputConnection.reportFullscreenMode(enabled);
5972 }
5973
5974 @Override
5975 public boolean performEditorAction(int editorAction) {
5976 // The editor is off in its own window; we need to be
5977 // the one that does this.
5978 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005979 InputMethodManager imm =
5980 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005981 if (imm != null) {
5982 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5983 }
5984 return true;
5985 }
5986 return false;
5987 }
5988
5989 @Override
5990 public boolean sendKeyEvent(KeyEvent event) {
5991 // Use our own input connection, since the filter
5992 // text view may not be shown in a window so has
5993 // no ViewAncestor to dispatch events with.
5994 return mDefInputConnection.sendKeyEvent(event);
5995 }
5996
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005997 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005998 public CharSequence getTextBeforeCursor(int n, int flags) {
5999 if (mTarget == null) return "";
6000 return mTarget.getTextBeforeCursor(n, flags);
6001 }
6002
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006003 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006004 public CharSequence getTextAfterCursor(int n, int flags) {
6005 if (mTarget == null) return "";
6006 return mTarget.getTextAfterCursor(n, flags);
6007 }
6008
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006009 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006010 public CharSequence getSelectedText(int flags) {
6011 if (mTarget == null) return "";
6012 return mTarget.getSelectedText(flags);
6013 }
6014
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006015 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006016 public int getCursorCapsMode(int reqModes) {
6017 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
6018 return mTarget.getCursorCapsMode(reqModes);
6019 }
6020
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006021 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006022 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
6023 return getTarget().getExtractedText(request, flags);
6024 }
6025
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006026 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006027 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
6028 return getTarget().deleteSurroundingText(beforeLength, afterLength);
6029 }
6030
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006031 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08006032 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
6033 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
6034 }
6035
6036 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006037 public boolean setComposingText(CharSequence text, int newCursorPosition) {
6038 return getTarget().setComposingText(text, newCursorPosition);
6039 }
6040
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006041 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006042 public boolean setComposingRegion(int start, int end) {
6043 return getTarget().setComposingRegion(start, end);
6044 }
6045
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006046 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006047 public boolean finishComposingText() {
6048 return mTarget == null || mTarget.finishComposingText();
6049 }
6050
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006051 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006052 public boolean commitText(CharSequence text, int newCursorPosition) {
6053 return getTarget().commitText(text, newCursorPosition);
6054 }
6055
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006056 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006057 public boolean commitCompletion(CompletionInfo text) {
6058 return getTarget().commitCompletion(text);
6059 }
6060
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006061 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006062 public boolean commitCorrection(CorrectionInfo correctionInfo) {
6063 return getTarget().commitCorrection(correctionInfo);
6064 }
6065
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006066 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006067 public boolean setSelection(int start, int end) {
6068 return getTarget().setSelection(start, end);
6069 }
6070
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006071 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006072 public boolean performContextMenuAction(int id) {
6073 return getTarget().performContextMenuAction(id);
6074 }
6075
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006076 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006077 public boolean beginBatchEdit() {
6078 return getTarget().beginBatchEdit();
6079 }
6080
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006081 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006082 public boolean endBatchEdit() {
6083 return getTarget().endBatchEdit();
6084 }
6085
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006086 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006087 public boolean clearMetaKeyStates(int states) {
6088 return getTarget().clearMetaKeyStates(states);
6089 }
6090
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006091 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006092 public boolean performPrivateCommand(String action, Bundle data) {
6093 return getTarget().performPrivateCommand(action, data);
6094 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09006095
6096 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07006097 public boolean requestCursorUpdates(int cursorUpdateMode) {
6098 return getTarget().requestCursorUpdates(cursorUpdateMode);
6099 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08006100
6101 @Override
6102 public Handler getHandler() {
6103 return getTarget().getHandler();
6104 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07006105
6106 @Override
6107 public void closeConnection() {
6108 getTarget().closeConnection();
6109 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07006110
6111 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07006112 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6113 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07006114 }
Romain Guyf6991302013-06-05 17:19:01 -07006115 }
6116
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006117 /**
6118 * For filtering we proxy an input connection to an internal text editor,
6119 * and this allows the proxying to happen.
6120 */
6121 @Override
6122 public boolean checkInputConnectionProxy(View view) {
6123 return view == mTextFilter;
6124 }
Romain Guy0a637162009-05-29 14:43:54 -07006125
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006126 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006127 * Creates the window for the text filter and populates it with an EditText field;
6128 *
6129 * @param animateEntrance true if the window should appear with an animation
6130 */
6131 private void createTextFilter(boolean animateEntrance) {
6132 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006133 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006134 p.setFocusable(false);
6135 p.setTouchable(false);
6136 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006137 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006138 p.setWidth(LayoutParams.WRAP_CONTENT);
6139 p.setHeight(LayoutParams.WRAP_CONTENT);
6140 p.setBackgroundDrawable(null);
6141 mPopup = p;
6142 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006143 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006144 }
6145 if (animateEntrance) {
6146 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6147 } else {
6148 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6149 }
6150 }
6151
Romain Guyf6991302013-06-05 17:19:01 -07006152 private EditText getTextFilterInput() {
6153 if (mTextFilter == null) {
6154 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6155 mTextFilter = (EditText) layoutInflater.inflate(
6156 com.android.internal.R.layout.typing_filter, null);
6157 // For some reason setting this as the "real" input type changes
6158 // the text view in some way that it doesn't work, and I don't
6159 // want to figure out why this is.
6160 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6161 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6162 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6163 mTextFilter.addTextChangedListener(this);
6164 }
6165 return mTextFilter;
6166 }
6167
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006168 /**
6169 * Clear the text filter.
6170 */
6171 public void clearTextFilter() {
6172 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006173 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006174 mFiltered = false;
6175 if (mPopup != null && mPopup.isShowing()) {
6176 dismissPopup();
6177 }
6178 }
6179 }
6180
6181 /**
6182 * Returns if the ListView currently has a text filter.
6183 */
6184 public boolean hasTextFilter() {
6185 return mFiltered;
6186 }
6187
Alan Viverette8fa327a2013-05-31 14:53:13 -07006188 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006189 public void onGlobalLayout() {
6190 if (isShown()) {
6191 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006192 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006193 showPopup();
6194 }
6195 } else {
6196 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006197 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006198 dismissPopup();
6199 }
6200 }
6201
6202 }
6203
6204 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006205 * For our text watcher that is associated with the text filter. Does
6206 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006207 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006208 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006209 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6210 }
6211
6212 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006213 * For our text watcher that is associated with the text filter. Performs
6214 * the actual filtering as the text changes, and takes care of hiding and
6215 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006216 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006217 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006218 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006219 if (isTextFilterEnabled()) {
6220 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006221 int length = s.length();
6222 boolean showing = mPopup.isShowing();
6223 if (!showing && length > 0) {
6224 // Show the filter popup if necessary
6225 showPopup();
6226 mFiltered = true;
6227 } else if (showing && length == 0) {
6228 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006229 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006230 mFiltered = false;
6231 }
6232 if (mAdapter instanceof Filterable) {
6233 Filter f = ((Filterable) mAdapter).getFilter();
6234 // Filter should not be null when we reach this part
6235 if (f != null) {
6236 f.filter(s, this);
6237 } else {
6238 throw new IllegalStateException("You cannot call onTextChanged with a non "
6239 + "filterable adapter");
6240 }
6241 }
6242 }
6243 }
6244
6245 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006246 * For our text watcher that is associated with the text filter. Does
6247 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006248 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006249 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006250 public void afterTextChanged(Editable s) {
6251 }
6252
Alan Viverette8fa327a2013-05-31 14:53:13 -07006253 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006254 public void onFilterComplete(int count) {
6255 if (mSelectedPosition < 0 && count > 0) {
6256 mResurrectToPosition = INVALID_POSITION;
6257 resurrectSelection();
6258 }
6259 }
6260
6261 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006262 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6263 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6264 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6265 }
6266
6267 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006268 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6269 return new LayoutParams(p);
6270 }
6271
6272 @Override
6273 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6274 return new AbsListView.LayoutParams(getContext(), attrs);
6275 }
6276
6277 @Override
6278 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6279 return p instanceof AbsListView.LayoutParams;
6280 }
6281
6282 /**
6283 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6284 * to the bottom to show new items.
6285 *
6286 * @param mode the transcript mode to set
6287 *
6288 * @see #TRANSCRIPT_MODE_DISABLED
6289 * @see #TRANSCRIPT_MODE_NORMAL
6290 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6291 */
6292 public void setTranscriptMode(int mode) {
6293 mTranscriptMode = mode;
6294 }
6295
6296 /**
6297 * Returns the current transcript mode.
6298 *
6299 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6300 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6301 */
Ashley Rose55f9f922019-01-28 19:29:36 -05006302 @InspectableProperty(enumMapping = {
Ashley Rose73abdd3c2019-03-15 17:32:56 -04006303 @EnumEntry(value = TRANSCRIPT_MODE_DISABLED, name = "disabled"),
6304 @EnumEntry(value = TRANSCRIPT_MODE_NORMAL, name = "normal"),
6305 @EnumEntry(value = TRANSCRIPT_MODE_ALWAYS_SCROLL, name = "alwaysScroll")
Ashley Rose55f9f922019-01-28 19:29:36 -05006306 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006307 public int getTranscriptMode() {
6308 return mTranscriptMode;
6309 }
6310
6311 @Override
6312 public int getSolidColor() {
6313 return mCacheColorHint;
6314 }
6315
6316 /**
6317 * 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 -07006318 * on top of a solid, single-color, opaque background.
6319 *
6320 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6321 * single color. This hint will not affect any existing background drawable set on this view (
6322 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006323 *
6324 * @param color The background color
6325 */
Tor Norbye80756e32015-03-02 09:39:27 -08006326 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006327 if (color != mCacheColorHint) {
6328 mCacheColorHint = color;
6329 int count = getChildCount();
6330 for (int i = 0; i < count; i++) {
6331 getChildAt(i).setDrawingCacheBackgroundColor(color);
6332 }
6333 mRecycler.setCacheColorHint(color);
6334 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006335 }
6336
6337 /**
6338 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6339 * on top of a solid, single-color, opaque background
6340 *
6341 * @return The cache color hint
6342 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006343 @ViewDebug.ExportedProperty(category = "drawing")
Ashley Rose55f9f922019-01-28 19:29:36 -05006344 @InspectableProperty
Tor Norbye80756e32015-03-02 09:39:27 -08006345 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006346 public int getCacheColorHint() {
6347 return mCacheColorHint;
6348 }
6349
6350 /**
6351 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6352 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6353 * internal view recycler.
6354 *
6355 * @param views A list into which to put the reclaimed views
6356 */
6357 public void reclaimViews(List<View> views) {
6358 int childCount = getChildCount();
6359 RecyclerListener listener = mRecycler.mRecyclerListener;
6360
6361 // Reclaim views on screen
6362 for (int i = 0; i < childCount; i++) {
6363 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006364 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006365 // Don't reclaim header or footer views, or views that should be ignored
6366 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6367 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006368 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006369 if (listener != null) {
6370 // Pretend they went through the scrap heap
6371 listener.onMovedToScrapHeap(child);
6372 }
6373 }
6374 }
6375 mRecycler.reclaimScrapViews(views);
6376 removeAllViewsInLayout();
6377 }
6378
Adam Powell637d3372010-08-25 14:37:03 -07006379 private void finishGlows() {
Yigit Boyarb6218472019-02-06 10:07:06 -08006380 if (shouldDisplayEdgeEffects()) {
Adam Powell637d3372010-08-25 14:37:03 -07006381 mEdgeGlowTop.finish();
6382 mEdgeGlowBottom.finish();
6383 }
6384 }
6385
Romain Guy13922e02009-05-12 17:56:14 -07006386 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006387 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6388 * through the specified intent.
6389 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6390 */
6391 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -08006392 setRemoteViewsAdapter(intent, false);
6393 }
6394
6395 /** @hide **/
6396 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6397 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6398 }
6399
6400 /** @hide **/
6401 @Override
6402 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006403 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6404 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006405 if (mRemoteAdapter != null) {
6406 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6407 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6408 mRemoteAdapter.getRemoteViewsServiceIntent());
6409 if (fcNew.equals(fcOld)) {
6410 return;
6411 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006412 }
Adam Cohen2148d432011-07-28 14:59:54 -07006413 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006414 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08006415 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07006416 if (mRemoteAdapter.isDataReady()) {
6417 setAdapter(mRemoteAdapter);
6418 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006419 }
6420
6421 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006422 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006423 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006424 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006425 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006426 * @hide
6427 */
6428 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6429 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6430 // service handling the specified intent.
6431 if (mRemoteAdapter != null) {
6432 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6433 }
6434 }
6435
6436 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006437 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6438 * connected yet.
6439 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006440 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006441 public void deferNotifyDataSetChanged() {
6442 mDeferNotifyDataSetChanged = true;
6443 }
6444
6445 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006446 * Called back when the adapter connects to the RemoteViewsService.
6447 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006448 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006449 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006450 if (mRemoteAdapter != mAdapter) {
6451 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006452 if (mDeferNotifyDataSetChanged) {
6453 mRemoteAdapter.notifyDataSetChanged();
6454 mDeferNotifyDataSetChanged = false;
6455 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006456 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006457 } else if (mRemoteAdapter != null) {
6458 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006459 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006460 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006461 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006462 }
6463
6464 /**
6465 * Called back when the adapter disconnects from the RemoteViewsService.
6466 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006467 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006468 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006469 // If the remote adapter disconnects, we keep it around
6470 // since the currently displayed items are still cached.
6471 // Further, we want the service to eventually reconnect
6472 // when necessary, as triggered by this view requesting
6473 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006474 }
6475
6476 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006477 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6478 * being displayed by the AbsListView.
6479 */
6480 void setVisibleRangeHint(int start, int end) {
6481 if (mRemoteAdapter != null) {
6482 mRemoteAdapter.setVisibleRangeHint(start, end);
6483 }
6484 }
6485
6486 /**
Yigit Boyarb6218472019-02-06 10:07:06 -08006487 * Sets the edge effect color for both top and bottom edge effects.
6488 *
6489 * @param color The color for the edge effects.
6490 * @see #setTopEdgeEffectColor(int)
6491 * @see #setBottomEdgeEffectColor(int)
6492 * @see #getTopEdgeEffectColor()
6493 * @see #getBottomEdgeEffectColor()
6494 */
6495 public void setEdgeEffectColor(@ColorInt int color) {
6496 setTopEdgeEffectColor(color);
6497 setBottomEdgeEffectColor(color);
6498 }
6499
6500 /**
6501 * Sets the bottom edge effect color.
6502 *
6503 * @param color The color for the bottom edge effect.
6504 * @see #setTopEdgeEffectColor(int)
6505 * @see #setEdgeEffectColor(int)
6506 * @see #getTopEdgeEffectColor()
6507 * @see #getBottomEdgeEffectColor()
6508 */
6509 public void setBottomEdgeEffectColor(@ColorInt int color) {
6510 mEdgeGlowBottom.setColor(color);
6511 invalidateBottomGlow();
6512 }
6513
6514 /**
6515 * Sets the top edge effect color.
6516 *
6517 * @param color The color for the top edge effect.
6518 * @see #setBottomEdgeEffectColor(int)
6519 * @see #setEdgeEffectColor(int)
6520 * @see #getTopEdgeEffectColor()
6521 * @see #getBottomEdgeEffectColor()
6522 */
6523 public void setTopEdgeEffectColor(@ColorInt int color) {
6524 mEdgeGlowTop.setColor(color);
6525 invalidateTopGlow();
6526 }
6527
6528 /**
6529 * Returns the top edge effect color.
6530 *
6531 * @return The top edge effect color.
6532 * @see #setEdgeEffectColor(int)
6533 * @see #setTopEdgeEffectColor(int)
6534 * @see #setBottomEdgeEffectColor(int)
6535 * @see #getBottomEdgeEffectColor()
6536 */
6537 @ColorInt
6538 public int getTopEdgeEffectColor() {
6539 return mEdgeGlowTop.getColor();
6540 }
6541
6542 /**
6543 * Returns the bottom edge effect color.
6544 *
6545 * @return The bottom edge effect color.
6546 * @see #setEdgeEffectColor(int)
6547 * @see #setTopEdgeEffectColor(int)
6548 * @see #setBottomEdgeEffectColor(int)
6549 * @see #getTopEdgeEffectColor()
6550 */
6551 @ColorInt
6552 public int getBottomEdgeEffectColor() {
6553 return mEdgeGlowBottom.getColor();
6554 }
6555
6556 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006557 * Sets the recycler listener to be notified whenever a View is set aside in
6558 * the recycler for later reuse. This listener can be used to free resources
6559 * associated to the View.
6560 *
6561 * @param listener The recycler listener to be notified of views set aside
6562 * in the recycler.
6563 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006564 * @see android.widget.AbsListView.RecyclerListener
6565 */
6566 public void setRecyclerListener(RecyclerListener listener) {
6567 mRecycler.mRecyclerListener = listener;
6568 }
6569
Adam Powellb1f498a2011-01-18 20:43:23 -08006570 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6571 @Override
6572 public void onChanged() {
6573 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006574 if (mFastScroll != null) {
6575 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006576 }
6577 }
6578
6579 @Override
6580 public void onInvalidated() {
6581 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006582 if (mFastScroll != null) {
6583 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006584 }
6585 }
6586 }
6587
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006588 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006589 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6590 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6591 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6592 * selects and deselects list items.
6593 */
6594 public interface MultiChoiceModeListener extends ActionMode.Callback {
6595 /**
6596 * Called when an item is checked or unchecked during selection mode.
6597 *
6598 * @param mode The {@link ActionMode} providing the selection mode
6599 * @param position Adapter position of the item that was checked or unchecked
6600 * @param id Adapter ID of the item that was checked or unchecked
6601 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6602 * if the item is now unchecked.
6603 */
6604 public void onItemCheckedStateChanged(ActionMode mode,
6605 int position, long id, boolean checked);
6606 }
6607
6608 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6609 private MultiChoiceModeListener mWrapped;
6610
6611 public void setWrapped(MultiChoiceModeListener wrapped) {
6612 mWrapped = wrapped;
6613 }
6614
Adam Powella7981702012-08-24 12:43:41 -07006615 public boolean hasWrappedCallback() {
6616 return mWrapped != null;
6617 }
6618
Alan Viverette8fa327a2013-05-31 14:53:13 -07006619 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006620 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6621 if (mWrapped.onCreateActionMode(mode, menu)) {
6622 // Initialize checked graphic state?
6623 setLongClickable(false);
6624 return true;
6625 }
6626 return false;
6627 }
6628
Alan Viverette8fa327a2013-05-31 14:53:13 -07006629 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006630 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6631 return mWrapped.onPrepareActionMode(mode, menu);
6632 }
6633
Alan Viverette8fa327a2013-05-31 14:53:13 -07006634 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006635 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6636 return mWrapped.onActionItemClicked(mode, item);
6637 }
6638
Alan Viverette8fa327a2013-05-31 14:53:13 -07006639 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006640 public void onDestroyActionMode(ActionMode mode) {
6641 mWrapped.onDestroyActionMode(mode);
6642 mChoiceActionMode = null;
6643
6644 // Ending selection mode means deselecting everything.
6645 clearChoices();
6646
6647 mDataChanged = true;
6648 rememberSyncState();
6649 requestLayout();
6650
6651 setLongClickable(true);
6652 }
6653
Alan Viverette8fa327a2013-05-31 14:53:13 -07006654 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006655 public void onItemCheckedStateChanged(ActionMode mode,
6656 int position, long id, boolean checked) {
6657 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6658
6659 // If there are no items selected we no longer need the selection mode.
6660 if (getCheckedItemCount() == 0) {
6661 mode.finish();
6662 }
6663 }
6664 }
6665
6666 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006667 * AbsListView extends LayoutParams to provide a place to hold the view type.
6668 */
6669 public static class LayoutParams extends ViewGroup.LayoutParams {
6670 /**
6671 * View type for this view, as returned by
6672 * {@link android.widget.Adapter#getItemViewType(int) }
6673 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006674 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006675 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6676 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6677 })
Mathew Inwood31755f92018-12-20 13:53:36 +00006678 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006679 int viewType;
6680
The Android Open Source Project4df24232009-03-05 14:34:35 -08006681 /**
6682 * When this boolean is set, the view has been added to the AbsListView
6683 * at least once. It is used to know whether headers/footers have already
6684 * been added to the list view and whether they should be treated as
6685 * recycled views or not.
6686 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006687 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006688 boolean recycledHeaderFooter;
6689
Romain Guy0bf88592010-03-02 13:38:44 -08006690 /**
6691 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6692 * to obtain children views to measure itself. When doing so, the children
6693 * are not attached to the window, but put in the recycler which assumes
6694 * they've been attached before. Setting this flag will force the reused
6695 * view to be attached to the window rather than just attached to the
6696 * parent.
6697 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006698 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006699 boolean forceAdd;
6700
Dianne Hackborn079e2352010-10-18 17:02:43 -07006701 /**
6702 * The position the view was removed from when pulled out of the
6703 * scrap heap.
6704 * @hide
6705 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01006706 @UnsupportedAppUsage
Dianne Hackborn079e2352010-10-18 17:02:43 -07006707 int scrappedFromPosition;
6708
Adam Powell539ee872012-02-03 19:00:49 -08006709 /**
6710 * The ID the view represents
6711 */
6712 long itemId = -1;
6713
Alan Viverette92539d52015-09-14 10:49:25 -04006714 /** Whether the adapter considers the item enabled. */
6715 boolean isEnabled;
6716
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006717 public LayoutParams(Context c, AttributeSet attrs) {
6718 super(c, attrs);
6719 }
6720
6721 public LayoutParams(int w, int h) {
6722 super(w, h);
6723 }
6724
6725 public LayoutParams(int w, int h, int viewType) {
6726 super(w, h);
6727 this.viewType = viewType;
6728 }
6729
6730 public LayoutParams(ViewGroup.LayoutParams source) {
6731 super(source);
6732 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006733
6734 /** @hide */
6735 @Override
6736 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6737 super.encodeProperties(encoder);
6738
6739 encoder.addProperty("list:viewType", viewType);
6740 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6741 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006742 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006743 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006744 }
6745
6746 /**
6747 * A RecyclerListener is used to receive a notification whenever a View is placed
6748 * inside the RecycleBin's scrap heap. This listener is used to free resources
6749 * associated to Views placed in the RecycleBin.
6750 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006751 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6752 */
6753 public static interface RecyclerListener {
6754 /**
6755 * Indicates that the specified View was moved into the recycler's scrap heap.
6756 * The view is not displayed on screen any more and any expensive resource
6757 * associated with the view should be discarded.
6758 *
6759 * @param view
6760 */
6761 void onMovedToScrapHeap(View view);
6762 }
6763
6764 /**
6765 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6766 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6767 * start of a layout. By construction, they are displaying current information. At the end of
6768 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6769 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6770 *
6771 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6772 * @see android.widget.AbsListView.RecyclerListener
6773 */
6774 class RecycleBin {
Mathew Inwood978c6e22018-08-21 15:58:55 +01006775 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006776 private RecyclerListener mRecyclerListener;
6777
6778 /**
6779 * The position of the first view stored in mActiveViews.
6780 */
6781 private int mFirstActivePosition;
6782
6783 /**
6784 * Views that were on screen at the start of layout. This array is populated at the start of
6785 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6786 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6787 * view store in mFirstActivePosition.
6788 */
6789 private View[] mActiveViews = new View[0];
6790
6791 /**
6792 * Unsorted views that can be used by the adapter as a convert view.
6793 */
6794 private ArrayList<View>[] mScrapViews;
6795
6796 private int mViewTypeCount;
6797
6798 private ArrayList<View> mCurrentScrap;
6799
Adam Powell539ee872012-02-03 19:00:49 -08006800 private ArrayList<View> mSkippedScrap;
6801
6802 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006803 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006804
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006805 public void setViewTypeCount(int viewTypeCount) {
6806 if (viewTypeCount < 1) {
6807 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6808 }
6809 //noinspection unchecked
6810 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6811 for (int i = 0; i < viewTypeCount; i++) {
6812 scrapViews[i] = new ArrayList<View>();
6813 }
6814 mViewTypeCount = viewTypeCount;
6815 mCurrentScrap = scrapViews[0];
6816 mScrapViews = scrapViews;
6817 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006818
Adam Powellf3c2eda2010-03-16 17:31:01 -07006819 public void markChildrenDirty() {
6820 if (mViewTypeCount == 1) {
6821 final ArrayList<View> scrap = mCurrentScrap;
6822 final int scrapCount = scrap.size();
6823 for (int i = 0; i < scrapCount; i++) {
6824 scrap.get(i).forceLayout();
6825 }
6826 } else {
6827 final int typeCount = mViewTypeCount;
6828 for (int i = 0; i < typeCount; i++) {
6829 final ArrayList<View> scrap = mScrapViews[i];
6830 final int scrapCount = scrap.size();
6831 for (int j = 0; j < scrapCount; j++) {
6832 scrap.get(j).forceLayout();
6833 }
6834 }
6835 }
Adam Powell539ee872012-02-03 19:00:49 -08006836 if (mTransientStateViews != null) {
6837 final int count = mTransientStateViews.size();
6838 for (int i = 0; i < count; i++) {
6839 mTransientStateViews.valueAt(i).forceLayout();
6840 }
6841 }
Chet Haase72871322013-02-26 16:12:13 -07006842 if (mTransientStateViewsById != null) {
6843 final int count = mTransientStateViewsById.size();
6844 for (int i = 0; i < count; i++) {
6845 mTransientStateViewsById.valueAt(i).forceLayout();
6846 }
6847 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006848 }
Romain Guy0a637162009-05-29 14:43:54 -07006849
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006850 public boolean shouldRecycleViewType(int viewType) {
6851 return viewType >= 0;
6852 }
6853
6854 /**
6855 * Clears the scrap heap.
6856 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01006857 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006858 void clear() {
6859 if (mViewTypeCount == 1) {
6860 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006861 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006862 } else {
6863 final int typeCount = mViewTypeCount;
6864 for (int i = 0; i < typeCount; i++) {
6865 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006866 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006867 }
6868 }
Alan Viverette59511502013-12-09 13:49:25 -08006869
6870 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006871 }
6872
6873 /**
6874 * Fill ActiveViews with all of the children of the AbsListView.
6875 *
6876 * @param childCount The minimum number of views mActiveViews should hold
6877 * @param firstActivePosition The position of the first view that will be stored in
6878 * mActiveViews
6879 */
6880 void fillActiveViews(int childCount, int firstActivePosition) {
6881 if (mActiveViews.length < childCount) {
6882 mActiveViews = new View[childCount];
6883 }
6884 mFirstActivePosition = firstActivePosition;
6885
Romain Guyf6991302013-06-05 17:19:01 -07006886 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006887 final View[] activeViews = mActiveViews;
6888 for (int i = 0; i < childCount; i++) {
6889 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006890 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006891 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006892 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006893 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6894 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006895 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006896 // Remember the position so that setupChild() doesn't reset state.
6897 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006898 }
6899 }
6900 }
6901
6902 /**
6903 * Get the view corresponding to the specified position. The view will be removed from
6904 * mActiveViews if it is found.
6905 *
6906 * @param position The position to look up in mActiveViews
6907 * @return The view if it is found, null otherwise
6908 */
6909 View getActiveView(int position) {
6910 int index = position - mFirstActivePosition;
6911 final View[] activeViews = mActiveViews;
6912 if (index >=0 && index < activeViews.length) {
6913 final View match = activeViews[index];
6914 activeViews[index] = null;
6915 return match;
6916 }
6917 return null;
6918 }
6919
Adam Powell539ee872012-02-03 19:00:49 -08006920 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006921 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6922 long id = mAdapter.getItemId(position);
6923 View result = mTransientStateViewsById.get(id);
6924 mTransientStateViewsById.remove(id);
6925 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006926 }
Chet Haase72871322013-02-26 16:12:13 -07006927 if (mTransientStateViews != null) {
6928 final int index = mTransientStateViews.indexOfKey(position);
6929 if (index >= 0) {
6930 View result = mTransientStateViews.valueAt(index);
6931 mTransientStateViews.removeAt(index);
6932 return result;
6933 }
Adam Powell539ee872012-02-03 19:00:49 -08006934 }
Chet Haase72871322013-02-26 16:12:13 -07006935 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006936 }
6937
6938 /**
Alan Viverette59511502013-12-09 13:49:25 -08006939 * Dumps and fully detaches any currently saved views with transient
6940 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006941 */
6942 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006943 final SparseArray<View> viewsByPos = mTransientStateViews;
6944 if (viewsByPos != null) {
6945 final int N = viewsByPos.size();
6946 for (int i = 0; i < N; i++) {
6947 removeDetachedView(viewsByPos.valueAt(i), false);
6948 }
6949 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006950 }
Alan Viverette59511502013-12-09 13:49:25 -08006951
6952 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6953 if (viewsById != null) {
6954 final int N = viewsById.size();
6955 for (int i = 0; i < N; i++) {
6956 removeDetachedView(viewsById.valueAt(i), false);
6957 }
6958 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006959 }
Adam Powell539ee872012-02-03 19:00:49 -08006960 }
6961
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006962 /**
6963 * @return A view from the ScrapViews collection. These are unordered.
6964 */
6965 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006966 final int whichScrap = mAdapter.getItemViewType(position);
6967 if (whichScrap < 0) {
6968 return null;
6969 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006970 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006971 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006972 } else if (whichScrap < mScrapViews.length) {
6973 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006974 }
6975 return null;
6976 }
6977
6978 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006979 * Puts a view into the list of scrap views.
6980 * <p>
6981 * If the list data hasn't changed or the adapter has stable IDs, views
6982 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006983 *
6984 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006985 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006986 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006987 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006988 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006989 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006990 // Can't recycle, but we don't know anything about the view.
6991 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006992 return;
6993 }
6994
Adam Powell539ee872012-02-03 19:00:49 -08006995 lp.scrappedFromPosition = position;
6996
Alan Viverette1e51cc72013-09-27 14:32:20 -07006997 // Remove but don't scrap header or footer views, or views that
6998 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006999 final int viewType = lp.viewType;
7000 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07007001 // Can't recycle. If it's not a header or footer, which have
7002 // special handling and should be ignored, then skip the scrap
7003 // heap and we'll fully detach the view later.
7004 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7005 getSkippedScrap().add(scrap);
7006 }
Alan Viveretted44696c2013-07-18 10:37:15 -07007007 return;
7008 }
7009
7010 scrap.dispatchStartTemporaryDetach();
7011
Svetoslavd4bdd6b2013-10-31 17:25:01 -07007012 // The the accessibility state of the view may change while temporary
7013 // detached and we do not allow detached views to fire accessibility
7014 // events. So we are announcing that the subtree changed giving a chance
7015 // to clients holding on to a view in this subtree to refresh it.
Eugene Susla72c510f2018-01-23 21:12:11 +00007016 notifyViewAccessibilityStateChangedIfNeeded(
Svetoslavd4bdd6b2013-10-31 17:25:01 -07007017 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
7018
Alan Viveretted44696c2013-07-18 10:37:15 -07007019 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08007020 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07007021 if (scrapHasTransientState) {
7022 if (mAdapter != null && mAdapterHasStableIds) {
7023 // If the adapter has stable IDs, we can reuse the view for
7024 // the same data.
7025 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07007026 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07007027 }
7028 mTransientStateViewsById.put(lp.itemId, scrap);
7029 } else if (!mDataChanged) {
7030 // If the data hasn't changed, we can reuse the views at
7031 // their old positions.
7032 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07007033 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07007034 }
7035 mTransientStateViews.put(position, scrap);
7036 } else {
7037 // Otherwise, we'll have to remove the view and start over.
Phil Weaverec66fb82017-03-23 12:21:53 -07007038 clearScrapForRebind(scrap);
Alan Viverette8bbae342015-06-25 14:49:29 -07007039 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08007040 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007041 } else {
Phil Weaverec66fb82017-03-23 12:21:53 -07007042 clearScrapForRebind(scrap);
Alan Viveretted44696c2013-07-18 10:37:15 -07007043 if (mViewTypeCount == 1) {
7044 mCurrentScrap.add(scrap);
7045 } else {
7046 mScrapViews[viewType].add(scrap);
7047 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007048
Alan Viveretted44696c2013-07-18 10:37:15 -07007049 if (mRecyclerListener != null) {
7050 mRecyclerListener.onMovedToScrapHeap(scrap);
7051 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007052 }
7053 }
7054
Alan Viverette8bbae342015-06-25 14:49:29 -07007055 private ArrayList<View> getSkippedScrap() {
7056 if (mSkippedScrap == null) {
7057 mSkippedScrap = new ArrayList<>();
7058 }
7059 return mSkippedScrap;
7060 }
7061
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007062 /**
Adam Powell539ee872012-02-03 19:00:49 -08007063 * Finish the removal of any views that skipped the scrap heap.
7064 */
7065 void removeSkippedScrap() {
7066 if (mSkippedScrap == null) {
7067 return;
7068 }
7069 final int count = mSkippedScrap.size();
7070 for (int i = 0; i < count; i++) {
7071 removeDetachedView(mSkippedScrap.get(i), false);
7072 }
7073 mSkippedScrap.clear();
7074 }
7075
7076 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007077 * Move all views remaining in mActiveViews to mScrapViews.
7078 */
7079 void scrapActiveViews() {
7080 final View[] activeViews = mActiveViews;
7081 final boolean hasListener = mRecyclerListener != null;
7082 final boolean multipleScraps = mViewTypeCount > 1;
7083
7084 ArrayList<View> scrapViews = mCurrentScrap;
7085 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08007086 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007087 final View victim = activeViews[i];
7088 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07007089 final AbsListView.LayoutParams lp
7090 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08007091 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007092
7093 activeViews[i] = null;
7094
Alan Viverette59511502013-12-09 13:49:25 -08007095 if (victim.hasTransientState()) {
7096 // Store views with transient state for later use.
7097 victim.dispatchStartTemporaryDetach();
7098
7099 if (mAdapter != null && mAdapterHasStableIds) {
7100 if (mTransientStateViewsById == null) {
7101 mTransientStateViewsById = new LongSparseArray<View>();
7102 }
7103 long id = mAdapter.getItemId(mFirstActivePosition + i);
7104 mTransientStateViewsById.put(id, victim);
7105 } else if (!mDataChanged) {
7106 if (mTransientStateViews == null) {
7107 mTransientStateViews = new SparseArray<View>();
7108 }
7109 mTransientStateViews.put(mFirstActivePosition + i, victim);
7110 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7111 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08007112 removeDetachedView(victim, false);
7113 }
Alan Viverette59511502013-12-09 13:49:25 -08007114 } else if (!shouldRecycleViewType(whichScrap)) {
7115 // Discard non-recyclable views except headers/footers.
7116 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7117 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08007118 }
Alan Viverette59511502013-12-09 13:49:25 -08007119 } else {
7120 // Store everything else on the appropriate scrap heap.
7121 if (multipleScraps) {
7122 scrapViews = mScrapViews[whichScrap];
7123 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007124
Alan Viverette59511502013-12-09 13:49:25 -08007125 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007126 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08007127 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007128
Alan Viverette59511502013-12-09 13:49:25 -08007129 if (hasListener) {
7130 mRecyclerListener.onMovedToScrapHeap(victim);
7131 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007132 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007133 }
7134 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007135 pruneScrapViews();
7136 }
7137
7138 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007139 * At the end of a layout pass, all temp detached views should either be re-attached or
7140 * completely detached. This method ensures that any remaining view in the scrap list is
7141 * fully detached.
7142 */
7143 void fullyDetachScrapViews() {
7144 final int viewTypeCount = mViewTypeCount;
7145 final ArrayList<View>[] scrapViews = mScrapViews;
7146 for (int i = 0; i < viewTypeCount; ++i) {
7147 final ArrayList<View> scrapPile = scrapViews[i];
7148 for (int j = scrapPile.size() - 1; j >= 0; j--) {
7149 final View view = scrapPile.get(j);
7150 if (view.isTemporarilyDetached()) {
7151 removeDetachedView(view, false);
7152 }
7153 }
7154 }
7155 }
7156
7157 /**
Alan Viverette59511502013-12-09 13:49:25 -08007158 * Makes sure that the size of mScrapViews does not exceed the size of
7159 * mActiveViews, which can happen if an adapter does not recycle its
7160 * views. Removes cached transient state views that no longer have
7161 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007162 */
7163 private void pruneScrapViews() {
7164 final int maxViews = mActiveViews.length;
7165 final int viewTypeCount = mViewTypeCount;
7166 final ArrayList<View>[] scrapViews = mScrapViews;
7167 for (int i = 0; i < viewTypeCount; ++i) {
7168 final ArrayList<View> scrapPile = scrapViews[i];
7169 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007170 while (size > maxViews) {
7171 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007172 }
7173 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07007174
Alan Viverette59511502013-12-09 13:49:25 -08007175 final SparseArray<View> transViewsByPos = mTransientStateViews;
7176 if (transViewsByPos != null) {
7177 for (int i = 0; i < transViewsByPos.size(); i++) {
7178 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007179 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007180 removeDetachedView(v, false);
7181 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007182 i--;
7183 }
7184 }
7185 }
Alan Viverette59511502013-12-09 13:49:25 -08007186
7187 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7188 if (transViewsById != null) {
7189 for (int i = 0; i < transViewsById.size(); i++) {
7190 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007191 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007192 removeDetachedView(v, false);
7193 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007194 i--;
7195 }
7196 }
7197 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007198 }
7199
7200 /**
7201 * Puts all views in the scrap heap into the supplied list.
7202 */
7203 void reclaimScrapViews(List<View> views) {
7204 if (mViewTypeCount == 1) {
7205 views.addAll(mCurrentScrap);
7206 } else {
7207 final int viewTypeCount = mViewTypeCount;
7208 final ArrayList<View>[] scrapViews = mScrapViews;
7209 for (int i = 0; i < viewTypeCount; ++i) {
7210 final ArrayList<View> scrapPile = scrapViews[i];
7211 views.addAll(scrapPile);
7212 }
7213 }
7214 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007215
7216 /**
7217 * Updates the cache color hint of all known views.
7218 *
7219 * @param color The new cache color hint.
7220 */
7221 void setCacheColorHint(int color) {
7222 if (mViewTypeCount == 1) {
7223 final ArrayList<View> scrap = mCurrentScrap;
7224 final int scrapCount = scrap.size();
7225 for (int i = 0; i < scrapCount; i++) {
7226 scrap.get(i).setDrawingCacheBackgroundColor(color);
7227 }
7228 } else {
7229 final int typeCount = mViewTypeCount;
7230 for (int i = 0; i < typeCount; i++) {
7231 final ArrayList<View> scrap = mScrapViews[i];
7232 final int scrapCount = scrap.size();
7233 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007234 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007235 }
7236 }
7237 }
7238 // Just in case this is called during a layout pass
7239 final View[] activeViews = mActiveViews;
7240 final int count = activeViews.length;
7241 for (int i = 0; i < count; ++i) {
7242 final View victim = activeViews[i];
7243 if (victim != null) {
7244 victim.setDrawingCacheBackgroundColor(color);
7245 }
7246 }
7247 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007248
Alan Viverette3e141622014-02-18 17:05:13 -08007249 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7250 final int size = scrapViews.size();
7251 if (size > 0) {
7252 // See if we still have a view for this position or ID.
Phil Weavere28c03b2017-04-24 13:23:10 -07007253 // Traverse backwards to find the most recently used scrap view
7254 for (int i = size - 1; i >= 0; i--) {
Alan Viverette3e141622014-02-18 17:05:13 -08007255 final View view = scrapViews.get(i);
7256 final AbsListView.LayoutParams params =
7257 (AbsListView.LayoutParams) view.getLayoutParams();
7258
7259 if (mAdapterHasStableIds) {
7260 final long id = mAdapter.getItemId(position);
7261 if (id == params.itemId) {
7262 return scrapViews.remove(i);
7263 }
7264 } else if (params.scrappedFromPosition == position) {
7265 final View scrap = scrapViews.remove(i);
Phil Weaverec66fb82017-03-23 12:21:53 -07007266 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007267 return scrap;
7268 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007269 }
Alan Viverette3e141622014-02-18 17:05:13 -08007270 final View scrap = scrapViews.remove(size - 1);
Phil Weaverec66fb82017-03-23 12:21:53 -07007271 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007272 return scrap;
7273 } else {
7274 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007275 }
Alan Viverette3e141622014-02-18 17:05:13 -08007276 }
7277
7278 private void clearScrap(final ArrayList<View> scrap) {
7279 final int scrapCount = scrap.size();
7280 for (int j = 0; j < scrapCount; j++) {
7281 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7282 }
7283 }
7284
Phil Weaverec66fb82017-03-23 12:21:53 -07007285 private void clearScrapForRebind(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007286 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007287 view.setAccessibilityDelegate(null);
7288 }
7289
7290 private void removeDetachedView(View child, boolean animate) {
7291 child.setAccessibilityDelegate(null);
7292 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007293 }
7294 }
Alan Viverette441b4372014-02-12 13:30:20 -08007295
7296 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007297 * Returns the height of the view for the specified position.
7298 *
7299 * @param position the item position
7300 * @return view height in pixels
7301 */
7302 int getHeightForPosition(int position) {
7303 final int firstVisiblePosition = getFirstVisiblePosition();
7304 final int childCount = getChildCount();
7305 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007306 if (index >= 0 && index < childCount) {
7307 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007308 final View view = getChildAt(index);
7309 return view.getHeight();
7310 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007311 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007312 final View view = obtainView(position, mIsScrap);
7313 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7314 final int height = view.getMeasuredHeight();
7315 mRecycler.addScrapView(view, position);
7316 return height;
7317 }
7318 }
7319
7320 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007321 * Sets the selected item and positions the selection y pixels from the top edge
7322 * of the ListView. (If in touch mode, the item will not be selected but it will
7323 * still be positioned appropriately.)
7324 *
7325 * @param position Index (starting at 0) of the data item to be selected.
7326 * @param y The distance from the top edge of the ListView (plus padding) that the
7327 * item will be positioned.
7328 */
7329 public void setSelectionFromTop(int position, int y) {
7330 if (mAdapter == null) {
7331 return;
7332 }
7333
7334 if (!isInTouchMode()) {
7335 position = lookForSelectablePosition(position, true);
7336 if (position >= 0) {
7337 setNextSelectedPositionInt(position);
7338 }
7339 } else {
7340 mResurrectToPosition = position;
7341 }
7342
7343 if (position >= 0) {
7344 mLayoutMode = LAYOUT_SPECIFIC;
7345 mSpecificTop = mListPadding.top + y;
7346
7347 if (mNeedSync) {
7348 mSyncPosition = position;
7349 mSyncRowId = mAdapter.getItemId(position);
7350 }
7351
7352 if (mPositionScroller != null) {
7353 mPositionScroller.stop();
7354 }
7355 requestLayout();
7356 }
7357 }
7358
Siva Velusamy94a6d152015-05-05 15:07:00 -07007359 /** @hide */
7360 @Override
7361 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7362 super.encodeProperties(encoder);
7363
7364 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7365 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7366 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7367 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7368 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7369 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7370
7371 View selectedView = getSelectedView();
7372 if (selectedView != null) {
7373 encoder.addPropertyKey("selectedView");
7374 selectedView.encode(encoder);
7375 }
7376 }
7377
Alan Viveretted22db212014-02-13 17:47:38 -08007378 /**
7379 * Abstract positon scroller used to handle smooth scrolling.
7380 */
7381 static abstract class AbsPositionScroller {
7382 public abstract void start(int position);
7383 public abstract void start(int position, int boundPosition);
7384 public abstract void startWithOffset(int position, int offset);
7385 public abstract void startWithOffset(int position, int offset, int duration);
7386 public abstract void stop();
7387 }
7388
7389 /**
7390 * Default position scroller that simulates a fling.
7391 */
7392 class PositionScroller extends AbsPositionScroller implements Runnable {
7393 private static final int SCROLL_DURATION = 200;
7394
7395 private static final int MOVE_DOWN_POS = 1;
7396 private static final int MOVE_UP_POS = 2;
7397 private static final int MOVE_DOWN_BOUND = 3;
7398 private static final int MOVE_UP_BOUND = 4;
7399 private static final int MOVE_OFFSET = 5;
7400
7401 private int mMode;
7402 private int mTargetPos;
7403 private int mBoundPos;
7404 private int mLastSeenPos;
7405 private int mScrollDuration;
7406 private final int mExtraScroll;
7407
7408 private int mOffsetFromTop;
7409
7410 PositionScroller() {
7411 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7412 }
7413
7414 @Override
7415 public void start(final int position) {
7416 stop();
7417
7418 if (mDataChanged) {
7419 // Wait until we're back in a stable state to try this.
7420 mPositionScrollAfterLayout = new Runnable() {
7421 @Override public void run() {
7422 start(position);
7423 }
7424 };
7425 return;
7426 }
7427
7428 final int childCount = getChildCount();
7429 if (childCount == 0) {
7430 // Can't scroll without children.
7431 return;
7432 }
7433
7434 final int firstPos = mFirstPosition;
7435 final int lastPos = firstPos + childCount - 1;
7436
7437 int viewTravelCount;
7438 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7439 if (clampedPosition < firstPos) {
7440 viewTravelCount = firstPos - clampedPosition + 1;
7441 mMode = MOVE_UP_POS;
7442 } else if (clampedPosition > lastPos) {
7443 viewTravelCount = clampedPosition - lastPos + 1;
7444 mMode = MOVE_DOWN_POS;
7445 } else {
7446 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7447 return;
7448 }
7449
7450 if (viewTravelCount > 0) {
7451 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7452 } else {
7453 mScrollDuration = SCROLL_DURATION;
7454 }
7455 mTargetPos = clampedPosition;
7456 mBoundPos = INVALID_POSITION;
7457 mLastSeenPos = INVALID_POSITION;
7458
7459 postOnAnimation(this);
7460 }
7461
7462 @Override
7463 public void start(final int position, final int boundPosition) {
7464 stop();
7465
7466 if (boundPosition == INVALID_POSITION) {
7467 start(position);
7468 return;
7469 }
7470
7471 if (mDataChanged) {
7472 // Wait until we're back in a stable state to try this.
7473 mPositionScrollAfterLayout = new Runnable() {
7474 @Override public void run() {
7475 start(position, boundPosition);
7476 }
7477 };
7478 return;
7479 }
7480
7481 final int childCount = getChildCount();
7482 if (childCount == 0) {
7483 // Can't scroll without children.
7484 return;
7485 }
7486
7487 final int firstPos = mFirstPosition;
7488 final int lastPos = firstPos + childCount - 1;
7489
7490 int viewTravelCount;
7491 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7492 if (clampedPosition < firstPos) {
7493 final int boundPosFromLast = lastPos - boundPosition;
7494 if (boundPosFromLast < 1) {
7495 // Moving would shift our bound position off the screen. Abort.
7496 return;
7497 }
7498
7499 final int posTravel = firstPos - clampedPosition + 1;
7500 final int boundTravel = boundPosFromLast - 1;
7501 if (boundTravel < posTravel) {
7502 viewTravelCount = boundTravel;
7503 mMode = MOVE_UP_BOUND;
7504 } else {
7505 viewTravelCount = posTravel;
7506 mMode = MOVE_UP_POS;
7507 }
7508 } else if (clampedPosition > lastPos) {
7509 final int boundPosFromFirst = boundPosition - firstPos;
7510 if (boundPosFromFirst < 1) {
7511 // Moving would shift our bound position off the screen. Abort.
7512 return;
7513 }
7514
7515 final int posTravel = clampedPosition - lastPos + 1;
7516 final int boundTravel = boundPosFromFirst - 1;
7517 if (boundTravel < posTravel) {
7518 viewTravelCount = boundTravel;
7519 mMode = MOVE_DOWN_BOUND;
7520 } else {
7521 viewTravelCount = posTravel;
7522 mMode = MOVE_DOWN_POS;
7523 }
7524 } else {
7525 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7526 return;
7527 }
7528
7529 if (viewTravelCount > 0) {
7530 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7531 } else {
7532 mScrollDuration = SCROLL_DURATION;
7533 }
7534 mTargetPos = clampedPosition;
7535 mBoundPos = boundPosition;
7536 mLastSeenPos = INVALID_POSITION;
7537
7538 postOnAnimation(this);
7539 }
7540
7541 @Override
7542 public void startWithOffset(int position, int offset) {
7543 startWithOffset(position, offset, SCROLL_DURATION);
7544 }
7545
7546 @Override
7547 public void startWithOffset(final int position, int offset, final int duration) {
7548 stop();
7549
7550 if (mDataChanged) {
7551 // Wait until we're back in a stable state to try this.
7552 final int postOffset = offset;
7553 mPositionScrollAfterLayout = new Runnable() {
7554 @Override public void run() {
7555 startWithOffset(position, postOffset, duration);
7556 }
7557 };
7558 return;
7559 }
7560
7561 final int childCount = getChildCount();
7562 if (childCount == 0) {
7563 // Can't scroll without children.
7564 return;
7565 }
7566
7567 offset += getPaddingTop();
7568
7569 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7570 mOffsetFromTop = offset;
7571 mBoundPos = INVALID_POSITION;
7572 mLastSeenPos = INVALID_POSITION;
7573 mMode = MOVE_OFFSET;
7574
7575 final int firstPos = mFirstPosition;
7576 final int lastPos = firstPos + childCount - 1;
7577
7578 int viewTravelCount;
7579 if (mTargetPos < firstPos) {
7580 viewTravelCount = firstPos - mTargetPos;
7581 } else if (mTargetPos > lastPos) {
7582 viewTravelCount = mTargetPos - lastPos;
7583 } else {
7584 // On-screen, just scroll.
7585 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007586 smoothScrollBy(targetTop - offset, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007587 return;
7588 }
7589
7590 // Estimate how many screens we should travel
7591 final float screenTravelCount = (float) viewTravelCount / childCount;
7592 mScrollDuration = screenTravelCount < 1 ?
7593 duration : (int) (duration / screenTravelCount);
7594 mLastSeenPos = INVALID_POSITION;
7595
7596 postOnAnimation(this);
7597 }
7598
7599 /**
7600 * Scroll such that targetPos is in the visible padded region without scrolling
7601 * boundPos out of view. Assumes targetPos is onscreen.
7602 */
7603 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7604 final int firstPos = mFirstPosition;
7605 final int childCount = getChildCount();
7606 final int lastPos = firstPos + childCount - 1;
7607 final int paddedTop = mListPadding.top;
7608 final int paddedBottom = getHeight() - mListPadding.bottom;
7609
7610 if (targetPos < firstPos || targetPos > lastPos) {
7611 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7612 " not visible [" + firstPos + ", " + lastPos + "]");
7613 }
7614 if (boundPos < firstPos || boundPos > lastPos) {
7615 // boundPos doesn't matter, it's already offscreen.
7616 boundPos = INVALID_POSITION;
7617 }
7618
7619 final View targetChild = getChildAt(targetPos - firstPos);
7620 final int targetTop = targetChild.getTop();
7621 final int targetBottom = targetChild.getBottom();
7622 int scrollBy = 0;
7623
7624 if (targetBottom > paddedBottom) {
7625 scrollBy = targetBottom - paddedBottom;
7626 }
7627 if (targetTop < paddedTop) {
7628 scrollBy = targetTop - paddedTop;
7629 }
7630
7631 if (scrollBy == 0) {
7632 return;
7633 }
7634
7635 if (boundPos >= 0) {
7636 final View boundChild = getChildAt(boundPos - firstPos);
7637 final int boundTop = boundChild.getTop();
7638 final int boundBottom = boundChild.getBottom();
7639 final int absScroll = Math.abs(scrollBy);
7640
7641 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7642 // Don't scroll the bound view off the bottom of the screen.
7643 scrollBy = Math.max(0, boundBottom - paddedBottom);
7644 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7645 // Don't scroll the bound view off the top of the screen.
7646 scrollBy = Math.min(0, boundTop - paddedTop);
7647 }
7648 }
7649
7650 smoothScrollBy(scrollBy, duration);
7651 }
7652
7653 @Override
7654 public void stop() {
7655 removeCallbacks(this);
7656 }
7657
7658 @Override
7659 public void run() {
7660 final int listHeight = getHeight();
7661 final int firstPos = mFirstPosition;
7662
7663 switch (mMode) {
7664 case MOVE_DOWN_POS: {
7665 final int lastViewIndex = getChildCount() - 1;
7666 final int lastPos = firstPos + lastViewIndex;
7667
7668 if (lastViewIndex < 0) {
7669 return;
7670 }
7671
7672 if (lastPos == mLastSeenPos) {
7673 // No new views, let things keep going.
7674 postOnAnimation(this);
7675 return;
7676 }
7677
7678 final View lastView = getChildAt(lastViewIndex);
7679 final int lastViewHeight = lastView.getHeight();
7680 final int lastViewTop = lastView.getTop();
7681 final int lastViewPixelsShowing = listHeight - lastViewTop;
7682 final int extraScroll = lastPos < mItemCount - 1 ?
7683 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7684
7685 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007686 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007687
7688 mLastSeenPos = lastPos;
7689 if (lastPos < mTargetPos) {
7690 postOnAnimation(this);
7691 }
7692 break;
7693 }
7694
7695 case MOVE_DOWN_BOUND: {
7696 final int nextViewIndex = 1;
7697 final int childCount = getChildCount();
7698
7699 if (firstPos == mBoundPos || childCount <= nextViewIndex
7700 || firstPos + childCount >= mItemCount) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007701 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007702 return;
7703 }
7704 final int nextPos = firstPos + nextViewIndex;
7705
7706 if (nextPos == mLastSeenPos) {
7707 // No new views, let things keep going.
7708 postOnAnimation(this);
7709 return;
7710 }
7711
7712 final View nextView = getChildAt(nextViewIndex);
7713 final int nextViewHeight = nextView.getHeight();
7714 final int nextViewTop = nextView.getTop();
7715 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7716 if (nextPos < mBoundPos) {
7717 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007718 mScrollDuration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007719
7720 mLastSeenPos = nextPos;
7721
7722 postOnAnimation(this);
7723 } else {
7724 if (nextViewTop > extraScroll) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007725 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7726 } else {
7727 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007728 }
7729 }
7730 break;
7731 }
7732
7733 case MOVE_UP_POS: {
7734 if (firstPos == mLastSeenPos) {
7735 // No new views, let things keep going.
7736 postOnAnimation(this);
7737 return;
7738 }
7739
7740 final View firstView = getChildAt(0);
7741 if (firstView == null) {
7742 return;
7743 }
7744 final int firstViewTop = firstView.getTop();
7745 final int extraScroll = firstPos > 0 ?
7746 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7747
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007748 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7749 firstPos > mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007750
7751 mLastSeenPos = firstPos;
7752
7753 if (firstPos > mTargetPos) {
7754 postOnAnimation(this);
7755 }
7756 break;
7757 }
7758
7759 case MOVE_UP_BOUND: {
7760 final int lastViewIndex = getChildCount() - 2;
7761 if (lastViewIndex < 0) {
7762 return;
7763 }
7764 final int lastPos = firstPos + lastViewIndex;
7765
7766 if (lastPos == mLastSeenPos) {
7767 // No new views, let things keep going.
7768 postOnAnimation(this);
7769 return;
7770 }
7771
7772 final View lastView = getChildAt(lastViewIndex);
7773 final int lastViewHeight = lastView.getHeight();
7774 final int lastViewTop = lastView.getTop();
7775 final int lastViewPixelsShowing = listHeight - lastViewTop;
7776 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7777 mLastSeenPos = lastPos;
7778 if (lastPos > mBoundPos) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007779 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7780 true);
Alan Viveretted22db212014-02-13 17:47:38 -08007781 postOnAnimation(this);
7782 } else {
7783 final int bottom = listHeight - extraScroll;
7784 final int lastViewBottom = lastViewTop + lastViewHeight;
7785 if (bottom > lastViewBottom) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007786 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7787 } else {
7788 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007789 }
7790 }
7791 break;
7792 }
7793
7794 case MOVE_OFFSET: {
7795 if (mLastSeenPos == firstPos) {
7796 // No new views, let things keep going.
7797 postOnAnimation(this);
7798 return;
7799 }
7800
7801 mLastSeenPos = firstPos;
7802
7803 final int childCount = getChildCount();
cnx42126c15a12019-02-18 14:36:03 +08007804
7805 if (childCount <= 0) {
7806 return;
7807 }
7808
Alan Viveretted22db212014-02-13 17:47:38 -08007809 final int position = mTargetPos;
7810 final int lastPos = firstPos + childCount - 1;
7811
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007812 // Account for the visible "portion" of the first / last child when we estimate
7813 // how many screens we should travel to reach our target
7814 final View firstChild = getChildAt(0);
7815 final int firstChildHeight = firstChild.getHeight();
7816 final View lastChild = getChildAt(childCount - 1);
7817 final int lastChildHeight = lastChild.getHeight();
7818 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7819 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7820 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7821 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7822 / lastChildHeight;
7823
7824 float viewTravelCount = 0;
Alan Viveretted22db212014-02-13 17:47:38 -08007825 if (position < firstPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007826 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
Alan Viveretted22db212014-02-13 17:47:38 -08007827 } else if (position > lastPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007828 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
Alan Viveretted22db212014-02-13 17:47:38 -08007829 }
7830
7831 // Estimate how many screens we should travel
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007832 final float screenTravelCount = viewTravelCount / childCount;
Alan Viveretted22db212014-02-13 17:47:38 -08007833
7834 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7835 if (position < firstPos) {
7836 final int distance = (int) (-getHeight() * modifier);
7837 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007838 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007839 postOnAnimation(this);
7840 } else if (position > lastPos) {
7841 final int distance = (int) (getHeight() * modifier);
7842 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007843 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007844 postOnAnimation(this);
7845 } else {
7846 // On-screen, just scroll.
7847 final int targetTop = getChildAt(position - firstPos).getTop();
7848 final int distance = targetTop - mOffsetFromTop;
7849 final int duration = (int) (mScrollDuration *
7850 ((float) Math.abs(distance) / getHeight()));
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007851 smoothScrollBy(distance, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007852 }
7853 break;
7854 }
7855
7856 default:
7857 break;
7858 }
7859 }
7860 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007861}