blob: f01babe7b89440c2f57a74dab2b6d4bd40bba8aa [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;
Mathew Inwood978c6e22018-08-21 15:58:55 +010023import android.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;
86import android.view.inspector.InspectableProperty.EnumMap;
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 = {
1224 @EnumMap(value = CHOICE_MODE_NONE, name = "none"),
1225 @EnumMap(value = CHOICE_MODE_SINGLE, name = "singleChoice"),
1226 @EnumMap(value = CHOICE_MODE_MULTIPLE, name = "multipleChoice"),
1227 @EnumMap(value = CHOICE_MODE_MULTIPLE_MODAL, name = "multipleChoiceModal")
1228 })
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
1806 public static final Parcelable.Creator<SavedState> CREATOR
1807 = 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
Alan Viverette92539d52015-09-14 10:49:25 -04002553 final boolean isItemEnabled;
2554 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2555 if (lp instanceof AbsListView.LayoutParams) {
2556 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2557 } else {
2558 isItemEnabled = false;
2559 }
2560
2561 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002562 info.setEnabled(false);
2563 return;
2564 }
2565
2566 if (position == getSelectedItemPosition()) {
2567 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002568 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002569 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002570 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002571 }
2572
Alan Viverette92539d52015-09-14 10:49:25 -04002573 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002574 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002575 info.setClickable(true);
2576 }
2577
2578 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002579 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002580 info.setLongClickable(true);
2581 }
2582 }
2583
Alan Viverette92539d52015-09-14 10:49:25 -04002584 private boolean isItemClickable(View view) {
Adam Powell0f552f42017-02-03 11:50:42 -08002585 return !view.hasExplicitFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002586 }
2587
Alan Viverettede399392014-05-01 17:20:55 -07002588 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002589 * Positions the selector in a way that mimics touch.
2590 */
2591 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002592 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002593 }
2594
2595 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002596 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002597 */
2598 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002599 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002600 final Rect bounds = mSelectorRect;
2601 final float x = bounds.exactCenterX();
2602 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002603 positionSelector(position, sel, true, x, y);
2604 } else {
2605 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002606 }
2607 }
2608
Dianne Hackborn079e2352010-10-18 17:02:43 -07002609 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002610 positionSelector(position, sel, false, -1, -1);
2611 }
2612
Mathew Inwood978c6e22018-08-21 15:58:55 +01002613 @UnsupportedAppUsage
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002614 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2615 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002616 if (position != INVALID_POSITION) {
2617 mSelectorPosition = position;
2618 }
2619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002620 final Rect selectorRect = mSelectorRect;
2621 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002622 if (sel instanceof SelectionBoundsAdjuster) {
2623 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2624 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002625
2626 // Adjust for selection padding.
2627 selectorRect.left -= mSelectionLeftPadding;
2628 selectorRect.top -= mSelectionTopPadding;
2629 selectorRect.right += mSelectionRightPadding;
2630 selectorRect.bottom += mSelectionBottomPadding;
2631
Alan Viverettea19ab342015-05-18 13:20:52 -07002632 // Update the child enabled state prior to updating the selector.
2633 final boolean isChildViewEnabled = sel.isEnabled();
2634 if (mIsChildViewEnabled != isChildViewEnabled) {
2635 mIsChildViewEnabled = isChildViewEnabled;
2636 }
2637
2638 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002639 final Drawable selector = mSelector;
2640 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002641 if (positionChanged) {
2642 // Wipe out the current selector state so that we can start
2643 // over in the new position with a fresh state.
2644 selector.setVisible(false, false);
2645 selector.setState(StateSet.NOTHING);
2646 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002647 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002648 if (positionChanged) {
2649 if (getVisibility() == VISIBLE) {
2650 selector.setVisible(true, false);
2651 }
Chet Haase2167b112014-12-19 16:37:18 -08002652 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002653 }
2654 if (manageHotspot) {
2655 selector.setHotspot(x, y);
2656 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002657 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002658 }
2659
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002660 @Override
2661 protected void dispatchDraw(Canvas canvas) {
2662 int saveCount = 0;
2663 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2664 if (clipToPadding) {
2665 saveCount = canvas.save();
2666 final int scrollX = mScrollX;
2667 final int scrollY = mScrollY;
2668 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2669 scrollX + mRight - mLeft - mPaddingRight,
2670 scrollY + mBottom - mTop - mPaddingBottom);
2671 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2672 }
2673
2674 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2675 if (!drawSelectorOnTop) {
2676 drawSelector(canvas);
2677 }
2678
2679 super.dispatchDraw(canvas);
2680
2681 if (drawSelectorOnTop) {
2682 drawSelector(canvas);
2683 }
2684
2685 if (clipToPadding) {
2686 canvas.restoreToCount(saveCount);
2687 mGroupFlags |= CLIP_TO_PADDING_MASK;
2688 }
2689 }
2690
2691 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002692 protected boolean isPaddingOffsetRequired() {
2693 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2694 }
2695
2696 @Override
2697 protected int getLeftPaddingOffset() {
2698 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2699 }
2700
2701 @Override
2702 protected int getTopPaddingOffset() {
2703 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2704 }
2705
2706 @Override
2707 protected int getRightPaddingOffset() {
2708 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2709 }
2710
2711 @Override
2712 protected int getBottomPaddingOffset() {
2713 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2714 }
2715
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002716 /**
2717 * @hide
2718 */
2719 @Override
2720 protected void internalSetPadding(int left, int top, int right, int bottom) {
2721 super.internalSetPadding(left, top, right, bottom);
2722 if (isLayoutRequested()) {
2723 handleBoundsChange();
2724 }
2725 }
2726
Adam Powell20232d02010-12-08 21:08:53 -08002727 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002728 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002729 handleBoundsChange();
Alan Viverette8636ace2013-10-31 15:41:31 -07002730 if (mFastScroll != null) {
2731 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002732 }
2733 }
2734
2735 /**
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002736 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2737 * and force layouts all children that don't have exact measure specs.
2738 * <p>
2739 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2740 * fail to relayout them properly to accommodate for new bounds.
2741 */
2742 void handleBoundsChange() {
Phil Weavera9d976f2016-11-01 09:55:24 -07002743 if (mInLayout) {
2744 return;
2745 }
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002746 final int childCount = getChildCount();
2747 if (childCount > 0) {
2748 mDataChanged = true;
2749 rememberSyncState();
2750 for (int i = 0; i < childCount; i++) {
2751 final View child = getChildAt(i);
2752 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2753 // force layout child unless it has exact specs
2754 if (lp == null || lp.width < 1 || lp.height < 1) {
2755 child.forceLayout();
2756 }
2757 }
2758 }
2759 }
2760
2761 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002762 * @return True if the current touch mode requires that we draw the selector in the pressed
2763 * state.
2764 */
2765 boolean touchModeDrawsInPressedState() {
2766 // FIXME use isPressed for this
2767 switch (mTouchMode) {
2768 case TOUCH_MODE_TAP:
2769 case TOUCH_MODE_DONE_WAITING:
2770 return true;
2771 default:
2772 return false;
2773 }
2774 }
2775
2776 /**
2777 * Indicates whether this view is in a state where the selector should be drawn. This will
2778 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2779 * the pressed state for an item.
2780 *
2781 * @return True if the selector should be shown
2782 */
2783 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002784 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002785 }
2786
2787 private void drawSelector(Canvas canvas) {
Evan Rosky8e5bd812018-01-22 09:36:41 -08002788 if (shouldDrawSelector()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002789 final Drawable selector = mSelector;
2790 selector.setBounds(mSelectorRect);
2791 selector.draw(canvas);
2792 }
2793 }
2794
2795 /**
Evan Rosky8e5bd812018-01-22 09:36:41 -08002796 * @hide
2797 */
2798 @TestApi
2799 public final boolean shouldDrawSelector() {
2800 return !mSelectorRect.isEmpty();
2801 }
2802
2803 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002804 * Controls whether the selection highlight drawable should be drawn on top of the item or
2805 * behind it.
2806 *
2807 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2808 * is false.
2809 *
2810 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2811 */
2812 public void setDrawSelectorOnTop(boolean onTop) {
2813 mDrawSelectorOnTop = onTop;
2814 }
2815
2816 /**
Ashley Rose55f9f922019-01-28 19:29:36 -05002817 * Returns whether the selection highlight drawable should be drawn on top of the item or
2818 * behind it.
2819 *
2820 * @return true if selector is drawn on top, false otherwise
2821 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2822 */
2823 @InspectableProperty
2824 public boolean getDrawSelectorOnTop() {
2825 return mDrawSelectorOnTop;
2826 }
2827
2828 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002829 * Set a Drawable that should be used to highlight the currently selected item.
2830 *
2831 * @param resID A Drawable resource to use as the selection highlight.
2832 *
2833 * @attr ref android.R.styleable#AbsListView_listSelector
2834 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002835 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002836 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002837 }
2838
2839 public void setSelector(Drawable sel) {
2840 if (mSelector != null) {
2841 mSelector.setCallback(null);
2842 unscheduleDrawable(mSelector);
2843 }
2844 mSelector = sel;
2845 Rect padding = new Rect();
2846 sel.getPadding(padding);
2847 mSelectionLeftPadding = padding.left;
2848 mSelectionTopPadding = padding.top;
2849 mSelectionRightPadding = padding.right;
2850 mSelectionBottomPadding = padding.bottom;
2851 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002852 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002853 }
2854
2855 /**
2856 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2857 * selection in the list.
2858 *
2859 * @return the drawable used to display the selector
2860 */
Ashley Rose55f9f922019-01-28 19:29:36 -05002861 @InspectableProperty(name = "listSelector")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002862 public Drawable getSelector() {
2863 return mSelector;
2864 }
2865
2866 /**
2867 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2868 * this is a long press.
2869 */
2870 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002871 if (!isEnabled() || !isClickable()) {
2872 return;
2873 }
2874
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002875 Drawable selector = mSelector;
2876 Rect selectorRect = mSelectorRect;
2877 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002878 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002879
2880 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2881
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002882 if (v != null) {
Adam Powell0f552f42017-02-03 11:50:42 -08002883 if (v.hasExplicitFocusable()) return;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002884 v.setPressed(true);
2885 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002886 setPressed(true);
2887
2888 final boolean longClickable = isLongClickable();
2889 Drawable d = selector.getCurrent();
2890 if (d != null && d instanceof TransitionDrawable) {
2891 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002892 ((TransitionDrawable) d).startTransition(
2893 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002894 } else {
2895 ((TransitionDrawable) d).resetTransition();
2896 }
2897 }
2898 if (longClickable && !mDataChanged) {
2899 if (mPendingCheckForKeyLongPress == null) {
2900 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2901 }
2902 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2903 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2904 }
2905 }
2906 }
2907
2908 public void setScrollIndicators(View up, View down) {
2909 mScrollUp = up;
2910 mScrollDown = down;
2911 }
2912
Mathew Inwood978c6e22018-08-21 15:58:55 +01002913 @UnsupportedAppUsage
Dianne Hackborn079e2352010-10-18 17:02:43 -07002914 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002915 final Drawable selector = mSelector;
2916 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002917 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002918 if (selector.setState(getDrawableStateForSelector())) {
2919 invalidateDrawable(selector);
2920 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002921 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002922 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002923 }
2924 }
2925 }
2926
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 @Override
2928 protected void drawableStateChanged() {
2929 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002930 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002931 }
2932
Alan Viverettef723c832015-02-03 16:31:46 -08002933 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002934 // If the child view is enabled then do the default behavior.
2935 if (mIsChildViewEnabled) {
2936 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002937 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002938 }
2939
2940 // The selector uses this View's drawable state. The selected child view
2941 // is disabled, so we need to remove the enabled state from the drawable
2942 // states.
2943 final int enabledState = ENABLED_STATE_SET[0];
2944
Alan Viverettef723c832015-02-03 16:31:46 -08002945 // If we don't have any extra space, it will return one of the static
2946 // state arrays, and clearing the enabled state on those arrays is a
2947 // bad thing! If we specify we need extra space, it will create+copy
2948 // into a new array that is safely mutable.
2949 final int[] state = onCreateDrawableState(1);
2950
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002951 int enabledPos = -1;
2952 for (int i = state.length - 1; i >= 0; i--) {
2953 if (state[i] == enabledState) {
2954 enabledPos = i;
2955 break;
2956 }
2957 }
2958
2959 // Remove the enabled state
2960 if (enabledPos >= 0) {
2961 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2962 state.length - enabledPos - 1);
2963 }
Romain Guy0a637162009-05-29 14:43:54 -07002964
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002965 return state;
2966 }
2967
2968 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -05002969 public boolean verifyDrawable(@NonNull Drawable dr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002970 return mSelector == dr || super.verifyDrawable(dr);
2971 }
2972
2973 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002974 public void jumpDrawablesToCurrentState() {
2975 super.jumpDrawablesToCurrentState();
2976 if (mSelector != null) mSelector.jumpToCurrentState();
2977 }
2978
2979 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002980 protected void onAttachedToWindow() {
2981 super.onAttachedToWindow();
2982
2983 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002984 treeObserver.addOnTouchModeChangeListener(this);
2985 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2986 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002987 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002988
Romain Guy82afc7b2010-05-13 11:52:37 -07002989 if (mAdapter != null && mDataSetObserver == null) {
2990 mDataSetObserver = new AdapterDataSetObserver();
2991 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002992
2993 // Data may have changed while we were detached. Refresh.
2994 mDataChanged = true;
2995 mOldItemCount = mItemCount;
2996 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002997 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002998 }
2999
3000 @Override
3001 protected void onDetachedFromWindow() {
3002 super.onDetachedFromWindow();
3003
Alan Viverette462c2172014-02-24 12:24:11 -08003004 mIsDetaching = true;
3005
Romain Guy1f7f3c32009-07-22 11:25:42 -07003006 // Dismiss the popup in case onSaveInstanceState() was not invoked
3007 dismissPopup();
3008
Romain Guy21875052010-01-06 18:48:08 -08003009 // Detach any view left in the scrap heap
3010 mRecycler.clear();
3011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003012 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08003013 treeObserver.removeOnTouchModeChangeListener(this);
3014 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07003015 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08003016 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003017 }
Romain Guy82afc7b2010-05-13 11:52:37 -07003018
Adam Powellbd1dd0d2013-04-09 17:46:15 -07003019 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07003020 mAdapter.unregisterDataSetObserver(mDataSetObserver);
3021 mDataSetObserver = null;
3022 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003023
3024 if (mScrollStrictSpan != null) {
3025 mScrollStrictSpan.finish();
3026 mScrollStrictSpan = null;
3027 }
3028
3029 if (mFlingStrictSpan != null) {
3030 mFlingStrictSpan.finish();
3031 mFlingStrictSpan = null;
3032 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003033
3034 if (mFlingRunnable != null) {
3035 removeCallbacks(mFlingRunnable);
3036 }
3037
3038 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08003039 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003040 }
3041
3042 if (mClearScrollingCache != null) {
3043 removeCallbacks(mClearScrollingCache);
3044 }
3045
3046 if (mPerformClick != null) {
3047 removeCallbacks(mPerformClick);
3048 }
3049
3050 if (mTouchModeReset != null) {
3051 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09003052 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003053 }
Alan Viverette462c2172014-02-24 12:24:11 -08003054
3055 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003056 }
3057
3058 @Override
3059 public void onWindowFocusChanged(boolean hasWindowFocus) {
3060 super.onWindowFocusChanged(hasWindowFocus);
3061
3062 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3063
3064 if (!hasWindowFocus) {
3065 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08003066 if (mFlingRunnable != null) {
3067 removeCallbacks(mFlingRunnable);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003068 // let the fling runnable report its new state which
Mark Wagner670dd812010-01-13 16:17:47 -08003069 // should be idle
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04003070 mFlingRunnable.mSuppressIdleStateChangeCall = false;
Mark Wagner670dd812010-01-13 16:17:47 -08003071 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003072 if (mPositionScroller != null) {
3073 mPositionScroller.stop();
3074 }
Adam Powell45803472010-01-25 15:10:44 -08003075 if (mScrollY != 0) {
3076 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003077 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003078 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08003079 invalidate();
3080 }
Mark Wagner670dd812010-01-13 16:17:47 -08003081 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003082 // Always hide the type filter
3083 dismissPopup();
3084
3085 if (touchMode == TOUCH_MODE_OFF) {
3086 // Remember the last selected element
3087 mResurrectToPosition = mSelectedPosition;
3088 }
3089 } else {
Adam Powell97566042010-03-09 15:34:09 -08003090 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003091 // Show the type filter only if a filter is in effect
3092 showPopup();
3093 }
3094
3095 // If we changed touch mode since the last time we had focus
3096 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3097 // If we come back in trackball mode, we bring the selection back
3098 if (touchMode == TOUCH_MODE_OFF) {
3099 // This will trigger a layout
3100 resurrectSelection();
3101
3102 // If we come back in touch mode, then we want to hide the selector
3103 } else {
3104 hideSelector();
3105 mLayoutMode = LAYOUT_NORMAL;
3106 layoutChildren();
3107 }
3108 }
3109 }
3110
3111 mLastTouchMode = touchMode;
3112 }
3113
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003114 @Override
3115 public void onRtlPropertiesChanged(int layoutDirection) {
3116 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003117 if (mFastScroll != null) {
3118 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003119 }
3120 }
3121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003122 /**
3123 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3124 * methods knows the view, position and ID of the item that received the
3125 * long press.
3126 *
3127 * @param view The view that received the long press.
3128 * @param position The position of the item that received the long press.
3129 * @param id The ID of the item that received the long press.
3130 * @return The extra information that should be returned by
3131 * {@link #getContextMenuInfo()}.
3132 */
3133 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3134 return new AdapterContextMenuInfo(view, position, id);
3135 }
3136
Adam Powell14874662013-07-18 19:42:41 -07003137 @Override
3138 public void onCancelPendingInputEvents() {
3139 super.onCancelPendingInputEvents();
3140 if (mPerformClick != null) {
3141 removeCallbacks(mPerformClick);
3142 }
3143 if (mPendingCheckForTap != null) {
3144 removeCallbacks(mPendingCheckForTap);
3145 }
3146 if (mPendingCheckForLongPress != null) {
3147 removeCallbacks(mPendingCheckForLongPress);
3148 }
3149 if (mPendingCheckForKeyLongPress != null) {
3150 removeCallbacks(mPendingCheckForKeyLongPress);
3151 }
3152 }
3153
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003154 /**
3155 * A base class for Runnables that will check that their view is still attached to
3156 * the original window as when the Runnable was created.
3157 *
3158 */
3159 private class WindowRunnnable {
3160 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003161
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003162 public void rememberWindowAttachCount() {
3163 mOriginalAttachCount = getWindowAttachCount();
3164 }
Romain Guy0a637162009-05-29 14:43:54 -07003165
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003166 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003167 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003168 }
3169 }
Romain Guy0a637162009-05-29 14:43:54 -07003170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003171 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003172 int mClickMotionPosition;
3173
Alan Viverette8fa327a2013-05-31 14:53:13 -07003174 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003175 public void run() {
3176 // The data has changed since we posted this action in the event queue,
3177 // bail out before bad things happen
3178 if (mDataChanged) return;
3179
Adam Powell005c0a42010-03-30 16:26:36 -07003180 final ListAdapter adapter = mAdapter;
3181 final int motionPosition = mClickMotionPosition;
3182 if (adapter != null && mItemCount > 0 &&
3183 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003184 motionPosition < adapter.getCount() && sameWindow() &&
3185 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003186 final View view = getChildAt(motionPosition - mFirstPosition);
3187 // If there is no view, something bad happened (the view scrolled off the
3188 // screen, etc.) and we should cancel the click
3189 if (view != null) {
3190 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3191 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003192 }
3193 }
3194 }
3195
3196 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003197 private static final int INVALID_COORD = -1;
3198 private float mX = INVALID_COORD;
3199 private float mY = INVALID_COORD;
3200
3201 private void setCoords(float x, float y) {
3202 mX = x;
3203 mY = y;
3204 }
3205
Alan Viverette8fa327a2013-05-31 14:53:13 -07003206 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003207 public void run() {
3208 final int motionPosition = mMotionPosition;
3209 final View child = getChildAt(motionPosition - mFirstPosition);
3210 if (child != null) {
3211 final int longPressPosition = mMotionPosition;
3212 final long longPressId = mAdapter.getItemId(mMotionPosition);
3213
3214 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003215 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003216 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3217 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3218 } else {
3219 handled = performLongPress(child, longPressPosition, longPressId);
3220 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003221 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003222
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003223 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003224 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003225 mTouchMode = TOUCH_MODE_REST;
3226 setPressed(false);
3227 child.setPressed(false);
3228 } else {
3229 mTouchMode = TOUCH_MODE_DONE_WAITING;
3230 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003231 }
3232 }
3233 }
Romain Guy0a637162009-05-29 14:43:54 -07003234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003235 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003236 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003237 public void run() {
3238 if (isPressed() && mSelectedPosition >= 0) {
3239 int index = mSelectedPosition - mFirstPosition;
3240 View v = getChildAt(index);
3241
3242 if (!mDataChanged) {
3243 boolean handled = false;
3244 if (sameWindow()) {
3245 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3246 }
3247 if (handled) {
3248 setPressed(false);
3249 v.setPressed(false);
3250 }
3251 } else {
3252 setPressed(false);
3253 if (v != null) v.setPressed(false);
3254 }
3255 }
3256 }
3257 }
3258
Mady Mellore5561982015-04-14 15:06:40 -07003259 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003260 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003261 final View child = getChildAt(mMotionPosition - mFirstPosition);
3262 if (child != null) {
3263 final int longPressPosition = mMotionPosition;
3264 final long longPressId = mAdapter.getItemId(mMotionPosition);
3265 if (performLongPress(child, longPressPosition, longPressId)) {
3266 mTouchMode = TOUCH_MODE_REST;
3267 setPressed(false);
3268 child.setPressed(false);
3269 return true;
3270 }
3271 }
3272 }
3273 return false;
3274 }
3275
Mathew Inwood978c6e22018-08-21 15:58:55 +01003276 @UnsupportedAppUsage
Adam Powell8350f7d2010-07-28 14:27:28 -07003277 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003278 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003279 return performLongPress(
3280 child,
3281 longPressPosition,
3282 longPressId,
3283 CheckForLongPress.INVALID_COORD,
3284 CheckForLongPress.INVALID_COORD);
3285 }
3286
Mathew Inwood978c6e22018-08-21 15:58:55 +01003287 @UnsupportedAppUsage
Oren Blasberged391262015-09-01 12:12:51 -07003288 boolean performLongPress(final View child,
3289 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003290 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3291 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003292 if (mChoiceActionMode == null &&
3293 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003294 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003295 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003296 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003297 return true;
3298 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003299
Adam Powellf343e1b2010-08-13 18:27:04 -07003300 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003301 if (mOnItemLongClickListener != null) {
3302 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3303 longPressPosition, longPressId);
3304 }
3305 if (!handled) {
3306 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003307 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3308 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3309 } else {
3310 handled = super.showContextMenuForChild(AbsListView.this);
3311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003312 }
3313 if (handled) {
3314 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3315 }
3316 return handled;
3317 }
3318
3319 @Override
3320 protected ContextMenuInfo getContextMenuInfo() {
3321 return mContextMenuInfo;
3322 }
3323
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003324 @Override
3325 public boolean showContextMenu() {
3326 return showContextMenuInternal(0, 0, false);
3327 }
3328
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003329 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003330 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003331 return showContextMenuInternal(x, y, true);
3332 }
3333
3334 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003335 final int position = pointToPosition((int)x, (int)y);
3336 if (position != INVALID_POSITION) {
3337 final long id = mAdapter.getItemId(position);
3338 View child = getChildAt(position - mFirstPosition);
3339 if (child != null) {
3340 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003341 if (useOffsets) {
3342 return super.showContextMenuForChild(this, x, y);
3343 } else {
3344 return super.showContextMenuForChild(this);
3345 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003346 }
3347 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003348 if (useOffsets) {
3349 return super.showContextMenu(x, y);
3350 } else {
3351 return super.showContextMenu();
3352 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003353 }
3354
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003355 @Override
3356 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003357 if (isShowingContextMenuWithCoords()) {
3358 return false;
3359 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003360 return showContextMenuForChildInternal(originalView, 0, 0, false);
3361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003363 @Override
3364 public boolean showContextMenuForChild(View originalView, float x, float y) {
3365 return showContextMenuForChildInternal(originalView,x, y, true);
3366 }
3367
3368 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3369 boolean useOffsets) {
3370 final int longPressPosition = getPositionForView(originalView);
3371 if (longPressPosition < 0) {
3372 return false;
3373 }
3374
3375 final long longPressId = mAdapter.getItemId(longPressPosition);
3376 boolean handled = false;
3377
3378 if (mOnItemLongClickListener != null) {
3379 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3380 longPressPosition, longPressId);
3381 }
3382
3383 if (!handled) {
3384 final View child = getChildAt(longPressPosition - mFirstPosition);
3385 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3386
3387 if (useOffsets) {
3388 handled = super.showContextMenuForChild(originalView, x, y);
3389 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003390 handled = super.showContextMenuForChild(originalView);
3391 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003392 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003393
3394 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003395 }
3396
3397 @Override
Romain Guydf016072009-08-17 12:51:30 -07003398 public boolean onKeyDown(int keyCode, KeyEvent event) {
3399 return false;
3400 }
3401
3402 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003403 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003404 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003405 if (!isEnabled()) {
3406 return true;
3407 }
Romain Guydf016072009-08-17 12:51:30 -07003408 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003409 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003410 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003411
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003412 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003413 if (view != null) {
3414 performItemClick(view, mSelectedPosition, mSelectedRowId);
3415 view.setPressed(false);
3416 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003417 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003418 return true;
3419 }
3420 }
3421 return super.onKeyUp(keyCode, event);
3422 }
3423
3424 @Override
3425 protected void dispatchSetPressed(boolean pressed) {
3426 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3427 // get the selector in the right state, but we don't want to press each child.
3428 }
3429
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003430 @Override
3431 public void dispatchDrawableHotspotChanged(float x, float y) {
3432 // Don't dispatch hotspot changes to children. We'll manually handle
3433 // calling drawableHotspotChanged on the correct child.
3434 }
3435
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003436 /**
3437 * Maps a point to a position in the list.
3438 *
3439 * @param x X in local coordinate
3440 * @param y Y in local coordinate
3441 * @return The position of the item which contains the specified point, or
3442 * {@link #INVALID_POSITION} if the point does not intersect an item.
3443 */
3444 public int pointToPosition(int x, int y) {
3445 Rect frame = mTouchFrame;
3446 if (frame == null) {
3447 mTouchFrame = new Rect();
3448 frame = mTouchFrame;
3449 }
3450
3451 final int count = getChildCount();
3452 for (int i = count - 1; i >= 0; i--) {
3453 final View child = getChildAt(i);
3454 if (child.getVisibility() == View.VISIBLE) {
3455 child.getHitRect(frame);
3456 if (frame.contains(x, y)) {
3457 return mFirstPosition + i;
3458 }
3459 }
3460 }
3461 return INVALID_POSITION;
3462 }
3463
3464
3465 /**
3466 * Maps a point to a the rowId of the item which intersects that point.
3467 *
3468 * @param x X in local coordinate
3469 * @param y Y in local coordinate
3470 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3471 * if the point does not intersect an item.
3472 */
3473 public long pointToRowId(int x, int y) {
3474 int position = pointToPosition(x, y);
3475 if (position >= 0) {
3476 return mAdapter.getItemId(position);
3477 }
3478 return INVALID_ROW_ID;
3479 }
3480
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003481 private final class CheckForTap implements Runnable {
3482 float x;
3483 float y;
3484
Alan Viverette8fa327a2013-05-31 14:53:13 -07003485 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003486 public void run() {
3487 if (mTouchMode == TOUCH_MODE_DOWN) {
3488 mTouchMode = TOUCH_MODE_TAP;
3489 final View child = getChildAt(mMotionPosition - mFirstPosition);
Adam Powell0f552f42017-02-03 11:50:42 -08003490 if (child != null && !child.hasExplicitFocusable()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003491 mLayoutMode = LAYOUT_NORMAL;
3492
3493 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003494 final float[] point = mTmpPoint;
3495 point[0] = x;
3496 point[1] = y;
3497 transformPointToViewLocal(point, child);
3498 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003499 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003500 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003501 layoutChildren();
3502 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003503 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003504
3505 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3506 final boolean longClickable = isLongClickable();
3507
3508 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003509 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003510 if (d != null && d instanceof TransitionDrawable) {
3511 if (longClickable) {
3512 ((TransitionDrawable) d).startTransition(longPressTimeout);
3513 } else {
3514 ((TransitionDrawable) d).resetTransition();
3515 }
3516 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003517 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003518 }
3519
3520 if (longClickable) {
3521 if (mPendingCheckForLongPress == null) {
3522 mPendingCheckForLongPress = new CheckForLongPress();
3523 }
Oren Blasberged391262015-09-01 12:12:51 -07003524 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003525 mPendingCheckForLongPress.rememberWindowAttachCount();
3526 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3527 } else {
3528 mTouchMode = TOUCH_MODE_DONE_WAITING;
3529 }
3530 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003531 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003532 }
3533 }
3534 }
3535 }
3536 }
3537
Adam Powellc501db9f2014-05-08 12:50:10 -07003538 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003539 // Check if we have moved far enough that it looks more like a
3540 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003541 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003542 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003543 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003544 if ((overscroll || distance > mTouchSlop) &&
3545 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003546 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003547 if (overscroll) {
3548 mTouchMode = TOUCH_MODE_OVERSCROLL;
3549 mMotionCorrection = 0;
3550 } else {
3551 mTouchMode = TOUCH_MODE_SCROLL;
3552 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3553 }
Alan Viverette74ded292013-06-03 15:34:11 -07003554 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003555 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003556 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003557 if (motionView != null) {
3558 motionView.setPressed(false);
3559 }
3560 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3561 // Time to start stealing events! Once we've stolen them, don't let anyone
3562 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003563 final ViewParent parent = getParent();
3564 if (parent != null) {
3565 parent.requestDisallowInterceptTouchEvent(true);
3566 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003567 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003568 return true;
3569 }
3570
3571 return false;
3572 }
3573
Adam Powellc501db9f2014-05-08 12:50:10 -07003574 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003575 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003576 int scrollOffsetCorrection = 0;
3577 int scrollConsumedCorrection = 0;
3578 if (mLastY == Integer.MIN_VALUE) {
3579 rawDeltaY -= mMotionCorrection;
3580 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003581 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3582 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003583 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003584 scrollOffsetCorrection = -mScrollOffset[1];
3585 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003586 if (vtev != null) {
3587 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003588 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003589 }
3590 }
Yorke Lee43943d82014-05-08 10:15:20 -07003591 final int deltaY = rawDeltaY;
3592 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003593 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003594 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003595
3596 if (mTouchMode == TOUCH_MODE_SCROLL) {
3597 if (PROFILE_SCROLLING) {
3598 if (!mScrollProfilingStarted) {
3599 Debug.startMethodTracing("AbsListViewScroll");
3600 mScrollProfilingStarted = true;
3601 }
3602 }
3603
3604 if (mScrollStrictSpan == null) {
3605 // If it's non-null, we're already in a scroll.
3606 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3607 }
3608
3609 if (y != mLastY) {
3610 // We may be here after stopping a fling and continuing to scroll.
3611 // If so, we haven't disallowed intercepting touch events yet.
3612 // Make sure that we do so in case we're in a parent that can intercept.
3613 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3614 Math.abs(rawDeltaY) > mTouchSlop) {
3615 final ViewParent parent = getParent();
3616 if (parent != null) {
3617 parent.requestDisallowInterceptTouchEvent(true);
3618 }
3619 }
3620
3621 final int motionIndex;
3622 if (mMotionPosition >= 0) {
3623 motionIndex = mMotionPosition - mFirstPosition;
3624 } else {
3625 // If we don't have a motion position that we can reliably track,
3626 // pick something in the middle to make a best guess at things below.
3627 motionIndex = getChildCount() / 2;
3628 }
3629
3630 int motionViewPrevTop = 0;
3631 View motionView = this.getChildAt(motionIndex);
3632 if (motionView != null) {
3633 motionViewPrevTop = motionView.getTop();
3634 }
3635
3636 // No need to do all this work if we're not going to move anyway
3637 boolean atEdge = false;
3638 if (incrementalDeltaY != 0) {
3639 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3640 }
3641
3642 // Check to see if we have bumped into the scroll limit
3643 motionView = this.getChildAt(motionIndex);
3644 if (motionView != null) {
3645 // Check if the top of the motion view is where it is
3646 // supposed to be
3647 final int motionViewRealTop = motionView.getTop();
3648 if (atEdge) {
3649 // Apply overscroll
3650
3651 int overscroll = -incrementalDeltaY -
3652 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003653 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3654 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003655 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003656 if (vtev != null) {
3657 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003658 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003659 }
Adam Powell96d62af2014-05-02 10:04:38 -07003660 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003661 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3662 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3663
3664 if (atOverscrollEdge && mVelocityTracker != null) {
3665 // Don't allow overfling if we're at the edge
3666 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003667 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003668
Adam Powell96d62af2014-05-02 10:04:38 -07003669 final int overscrollMode = getOverScrollMode();
3670 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3671 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3672 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003673 if (!atOverscrollEdge) {
3674 mDirection = 0; // Reset when entering overscroll.
3675 mTouchMode = TOUCH_MODE_OVERSCROLL;
3676 }
3677 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003678 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003679 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003680 if (!mEdgeGlowBottom.isFinished()) {
3681 mEdgeGlowBottom.onRelease();
3682 }
Doris Liuf36c0612015-06-04 11:11:14 -07003683 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003684 } else if (incrementalDeltaY < 0) {
3685 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3686 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003687 if (!mEdgeGlowTop.isFinished()) {
3688 mEdgeGlowTop.onRelease();
3689 }
Doris Liuf36c0612015-06-04 11:11:14 -07003690 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003691 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003692 }
3693 }
3694 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003695 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003696 }
Yorke Lee43943d82014-05-08 10:15:20 -07003697 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003698 }
3699 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3700 if (y != mLastY) {
3701 final int oldScroll = mScrollY;
3702 final int newScroll = oldScroll - incrementalDeltaY;
3703 int newDirection = y > mLastY ? 1 : -1;
3704
3705 if (mDirection == 0) {
3706 mDirection = newDirection;
3707 }
3708
3709 int overScrollDistance = -incrementalDeltaY;
3710 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3711 overScrollDistance = -oldScroll;
3712 incrementalDeltaY += overScrollDistance;
3713 } else {
3714 incrementalDeltaY = 0;
3715 }
3716
3717 if (overScrollDistance != 0) {
3718 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3719 0, mOverscrollDistance, true);
3720 final int overscrollMode = getOverScrollMode();
3721 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3722 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3723 !contentFits())) {
3724 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003725 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3726 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003727 if (!mEdgeGlowBottom.isFinished()) {
3728 mEdgeGlowBottom.onRelease();
3729 }
Doris Liuf36c0612015-06-04 11:11:14 -07003730 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003731 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003732 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3733 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003734 if (!mEdgeGlowTop.isFinished()) {
3735 mEdgeGlowTop.onRelease();
3736 }
Doris Liuf36c0612015-06-04 11:11:14 -07003737 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003738 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003739 }
3740 }
3741
3742 if (incrementalDeltaY != 0) {
3743 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003744 if (mScrollY != 0) {
3745 mScrollY = 0;
3746 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003747 }
3748
Romain Guy9d849a22012-03-14 16:41:42 -07003749 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3750
Jeff Brown78f6e632011-09-09 17:15:31 -07003751 mTouchMode = TOUCH_MODE_SCROLL;
3752
3753 // We did not scroll the full amount. Treat this essentially like the
3754 // start of a new touch scroll
3755 final int motionPosition = findClosestMotionRow(y);
3756
3757 mMotionCorrection = 0;
3758 View motionView = getChildAt(motionPosition - mFirstPosition);
3759 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003760 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003761 mMotionPosition = motionPosition;
3762 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003763 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003764 mDirection = newDirection;
3765 }
3766 }
3767 }
3768
Doris Liuf36c0612015-06-04 11:11:14 -07003769 private void invalidateTopGlow() {
Yigit Boyarb6218472019-02-06 10:07:06 -08003770 if (!shouldDisplayEdgeEffects()) {
Doris Liuf36c0612015-06-04 11:11:14 -07003771 return;
3772 }
3773 final boolean clipToPadding = getClipToPadding();
3774 final int top = clipToPadding ? mPaddingTop : 0;
3775 final int left = clipToPadding ? mPaddingLeft : 0;
3776 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3777 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3778 }
3779
3780 private void invalidateBottomGlow() {
Yigit Boyarb6218472019-02-06 10:07:06 -08003781 if (!shouldDisplayEdgeEffects()) {
Doris Liuf36c0612015-06-04 11:11:14 -07003782 return;
3783 }
3784 final boolean clipToPadding = getClipToPadding();
3785 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3786 final int left = clipToPadding ? mPaddingLeft : 0;
3787 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3788 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3789 }
3790
Alan Viverette8fa327a2013-05-31 14:53:13 -07003791 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003792 public void onTouchModeChanged(boolean isInTouchMode) {
3793 if (isInTouchMode) {
3794 // Get rid of the selection when we enter touch mode
3795 hideSelector();
3796 // Layout, but only if we already have done so previously.
3797 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3798 // state.)
3799 if (getHeight() > 0 && getChildCount() > 0) {
3800 // We do not lose focus initiating a touch (since AbsListView is focusable in
3801 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003802 layoutChildren();
3803 }
Jeff Brown1e209462011-07-14 22:19:19 -07003804 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003805 } else {
3806 int touchMode = mTouchMode;
3807 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3808 if (mFlingRunnable != null) {
3809 mFlingRunnable.endFling();
3810 }
Adam Powell40322522011-01-12 21:58:20 -08003811 if (mPositionScroller != null) {
3812 mPositionScroller.stop();
3813 }
Adam Powell637d3372010-08-25 14:37:03 -07003814
3815 if (mScrollY != 0) {
3816 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003817 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003818 finishGlows();
3819 invalidate();
3820 }
3821 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003822 }
3823 }
3824
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003825 /** @hide */
3826 @Override
3827 protected boolean handleScrollBarDragging(MotionEvent event) {
3828 // Doesn't support normal scroll bar dragging. Use FastScroller.
3829 return false;
3830 }
3831
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003832 @Override
3833 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003834 if (!isEnabled()) {
3835 // A disabled view that is clickable still consumes the touch
3836 // events, it just doesn't respond to them.
3837 return isClickable() || isLongClickable();
3838 }
3839
Adam Powell1fa179ef2012-04-12 15:01:40 -07003840 if (mPositionScroller != null) {
3841 mPositionScroller.stop();
3842 }
3843
Alan Viverette462c2172014-02-24 12:24:11 -08003844 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003845 // Something isn't right.
3846 // Since we rely on being attached to get data set change notifications,
3847 // don't risk doing anything where we might try to resync and find things
3848 // in a bogus state.
3849 return false;
3850 }
3851
Adam Powell96d62af2014-05-02 10:04:38 -07003852 startNestedScroll(SCROLL_AXIS_VERTICAL);
3853
Alan Viverettefb99ba82015-05-01 10:10:15 -07003854 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3855 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003856 }
Romain Guy82f34952009-05-24 18:40:45 -07003857
Michael Jurka13451a42011-08-22 15:54:21 -07003858 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003859 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003860
Alan Viverette8fa327a2013-05-31 14:53:13 -07003861 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003862 if (actionMasked == MotionEvent.ACTION_DOWN) {
3863 mNestedYOffset = 0;
3864 }
3865 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003866 switch (actionMasked) {
3867 case MotionEvent.ACTION_DOWN: {
3868 onTouchDown(ev);
3869 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003870 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003871
Alan Viverette8fa327a2013-05-31 14:53:13 -07003872 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003873 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003874 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003875 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003876
3877 case MotionEvent.ACTION_UP: {
3878 onTouchUp(ev);
3879 break;
3880 }
3881
3882 case MotionEvent.ACTION_CANCEL: {
3883 onTouchCancel();
3884 break;
3885 }
3886
3887 case MotionEvent.ACTION_POINTER_UP: {
3888 onSecondaryPointerUp(ev);
3889 final int x = mMotionX;
3890 final int y = mMotionY;
3891 final int motionPosition = pointToPosition(x, y);
3892 if (motionPosition >= 0) {
3893 // Remember where the motion event started
3894 final View child = getChildAt(motionPosition - mFirstPosition);
3895 mMotionViewOriginalTop = child.getTop();
3896 mMotionPosition = motionPosition;
3897 }
3898 mLastY = y;
3899 break;
3900 }
3901
3902 case MotionEvent.ACTION_POINTER_DOWN: {
3903 // New pointers take over dragging duties
3904 final int index = ev.getActionIndex();
3905 final int id = ev.getPointerId(index);
3906 final int x = (int) ev.getX(index);
3907 final int y = (int) ev.getY(index);
3908 mMotionCorrection = 0;
3909 mActivePointerId = id;
3910 mMotionX = x;
3911 mMotionY = y;
3912 final int motionPosition = pointToPosition(x, y);
3913 if (motionPosition >= 0) {
3914 // Remember where the motion event started
3915 final View child = getChildAt(motionPosition - mFirstPosition);
3916 mMotionViewOriginalTop = child.getTop();
3917 mMotionPosition = motionPosition;
3918 }
3919 mLastY = y;
3920 break;
3921 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003922 }
3923
Adam Powell96d62af2014-05-02 10:04:38 -07003924 if (mVelocityTracker != null) {
3925 mVelocityTracker.addMovement(vtev);
3926 }
3927 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003928 return true;
3929 }
Romain Guy0a637162009-05-29 14:43:54 -07003930
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003931 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003932 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003933 mActivePointerId = ev.getPointerId(0);
Evan Rosky837ae0d2017-10-26 12:50:33 -07003934 hideSelector();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003935
3936 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3937 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003938 mFlingRunnable.endFling();
3939 if (mPositionScroller != null) {
3940 mPositionScroller.stop();
3941 }
3942 mTouchMode = TOUCH_MODE_OVERSCROLL;
3943 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003944 mMotionY = (int) ev.getY();
3945 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003946 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003947 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003948 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003949 final int x = (int) ev.getX();
3950 final int y = (int) ev.getY();
3951 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003952
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003953 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003954 if (mTouchMode == TOUCH_MODE_FLING) {
3955 // Stopped a fling. It is a scroll.
3956 createScrollingCache();
3957 mTouchMode = TOUCH_MODE_SCROLL;
3958 mMotionCorrection = 0;
3959 motionPosition = findMotionRow(y);
3960 mFlingRunnable.flywheelTouch();
3961 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3962 // User clicked on an actual view (and was not stopping a
3963 // fling). It might be a click or a scroll. Assume it is a
3964 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003965 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003966
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003967 // FIXME Debounce
3968 if (mPendingCheckForTap == null) {
3969 mPendingCheckForTap = new CheckForTap();
3970 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003971
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003972 mPendingCheckForTap.x = ev.getX();
3973 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003974 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003975 }
3976 }
3977
3978 if (motionPosition >= 0) {
3979 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003980 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003981 mMotionViewOriginalTop = v.getTop();
3982 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003983
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003984 mMotionX = x;
3985 mMotionY = y;
3986 mMotionPosition = motionPosition;
3987 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003988 }
3989
Alan Viveretteb339cc52013-08-12 13:29:15 -07003990 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003991 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003992 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003993 }
3994 }
3995
Adam Powell96d62af2014-05-02 10:04:38 -07003996 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003997 if (mHasPerformedLongPress) {
3998 // Consume all move events following a successful long press.
3999 return;
4000 }
4001
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004002 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4003 if (pointerIndex == -1) {
4004 pointerIndex = 0;
4005 mActivePointerId = ev.getPointerId(pointerIndex);
4006 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004007
4008 if (mDataChanged) {
4009 // Re-sync everything if data has been changed
4010 // since the scroll operation can query the adapter.
4011 layoutChildren();
4012 }
4013
Alan Viverette8fa327a2013-05-31 14:53:13 -07004014 final int y = (int) ev.getY(pointerIndex);
4015
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004016 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004017 case TOUCH_MODE_DOWN:
4018 case TOUCH_MODE_TAP:
4019 case TOUCH_MODE_DONE_WAITING:
4020 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07004021 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07004022 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07004023 break;
4024 }
4025 // Otherwise, check containment within list bounds. If we're
4026 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08004027 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07004028 final float x = ev.getX(pointerIndex);
4029 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07004030 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004031 if (motionView != null) {
4032 motionView.setPressed(false);
4033 }
4034 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4035 mPendingCheckForTap : mPendingCheckForLongPress);
4036 mTouchMode = TOUCH_MODE_DONE_WAITING;
4037 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08004038 } else if (motionView != null) {
4039 // Still within bounds, update the hotspot.
4040 final float[] point = mTmpPoint;
4041 point[0] = x;
4042 point[1] = y;
4043 transformPointToViewLocal(point, motionView);
4044 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07004045 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07004046 break;
4047 case TOUCH_MODE_SCROLL:
4048 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07004049 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07004050 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004051 }
4052 }
4053
4054 private void onTouchUp(MotionEvent ev) {
4055 switch (mTouchMode) {
4056 case TOUCH_MODE_DOWN:
4057 case TOUCH_MODE_TAP:
4058 case TOUCH_MODE_DONE_WAITING:
4059 final int motionPosition = mMotionPosition;
4060 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07004061 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004062 if (mTouchMode != TOUCH_MODE_DOWN) {
4063 child.setPressed(false);
4064 }
4065
Alan Viverette74ded292013-06-03 15:34:11 -07004066 final float x = ev.getX();
4067 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
Adam Powell0f552f42017-02-03 11:50:42 -08004068 if (inList && !child.hasExplicitFocusable()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004069 if (mPerformClick == null) {
4070 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004071 }
Alan Viverette74ded292013-06-03 15:34:11 -07004072
4073 final AbsListView.PerformClick performClick = mPerformClick;
4074 performClick.mClickMotionPosition = motionPosition;
4075 performClick.rememberWindowAttachCount();
4076
4077 mResurrectToPosition = motionPosition;
4078
4079 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4080 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4081 mPendingCheckForTap : mPendingCheckForLongPress);
4082 mLayoutMode = LAYOUT_NORMAL;
4083 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4084 mTouchMode = TOUCH_MODE_TAP;
4085 setSelectedPositionInt(mMotionPosition);
4086 layoutChildren();
4087 child.setPressed(true);
4088 positionSelector(mMotionPosition, child);
4089 setPressed(true);
4090 if (mSelector != null) {
4091 Drawable d = mSelector.getCurrent();
4092 if (d != null && d instanceof TransitionDrawable) {
4093 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004094 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004095 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004096 }
Alan Viverette74ded292013-06-03 15:34:11 -07004097 if (mTouchModeReset != null) {
4098 removeCallbacks(mTouchModeReset);
4099 }
4100 mTouchModeReset = new Runnable() {
4101 @Override
4102 public void run() {
4103 mTouchModeReset = null;
4104 mTouchMode = TOUCH_MODE_REST;
4105 child.setPressed(false);
4106 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004107 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004108 performClick.run();
4109 }
4110 }
4111 };
4112 postDelayed(mTouchModeReset,
4113 ViewConfiguration.getPressedStateDuration());
4114 } else {
4115 mTouchMode = TOUCH_MODE_REST;
4116 updateSelectorState();
4117 }
4118 return;
4119 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4120 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004121 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004122 }
4123 }
4124 mTouchMode = TOUCH_MODE_REST;
4125 updateSelectorState();
4126 break;
4127 case TOUCH_MODE_SCROLL:
4128 final int childCount = getChildCount();
4129 if (childCount > 0) {
4130 final int firstChildTop = getChildAt(0).getTop();
4131 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4132 final int contentTop = mListPadding.top;
4133 final int contentBottom = getHeight() - mListPadding.bottom;
4134 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4135 mFirstPosition + childCount < mItemCount &&
4136 lastChildBottom <= getHeight() - contentBottom) {
4137 mTouchMode = TOUCH_MODE_REST;
4138 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4139 } else {
4140 final VelocityTracker velocityTracker = mVelocityTracker;
4141 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4142
4143 final int initialVelocity = (int)
4144 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4145 // Fling if we have enough velocity and we aren't at a boundary.
4146 // Since we can potentially overfling more than we can overscroll, don't
4147 // allow the weird behavior where you can scroll to a boundary then
4148 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004149 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4150 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004151 !((mFirstPosition == 0 &&
4152 firstChildTop == contentTop - mOverscrollDistance) ||
4153 (mFirstPosition + childCount == mItemCount &&
4154 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004155 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4156 if (mFlingRunnable == null) {
4157 mFlingRunnable = new FlingRunnable();
4158 }
4159 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4160 mFlingRunnable.start(-initialVelocity);
4161 dispatchNestedFling(0, -initialVelocity, true);
4162 } else {
4163 mTouchMode = TOUCH_MODE_REST;
4164 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004165 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004166 } else {
4167 mTouchMode = TOUCH_MODE_REST;
4168 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4169 if (mFlingRunnable != null) {
4170 mFlingRunnable.endFling();
4171 }
4172 if (mPositionScroller != null) {
4173 mPositionScroller.stop();
4174 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004175 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004176 dispatchNestedFling(0, -initialVelocity, false);
4177 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004178 }
4179 }
4180 } else {
4181 mTouchMode = TOUCH_MODE_REST;
4182 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4183 }
4184 break;
4185
4186 case TOUCH_MODE_OVERSCROLL:
4187 if (mFlingRunnable == null) {
4188 mFlingRunnable = new FlingRunnable();
4189 }
4190 final VelocityTracker velocityTracker = mVelocityTracker;
4191 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4192 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4193
4194 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4195 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4196 mFlingRunnable.startOverfling(-initialVelocity);
4197 } else {
4198 mFlingRunnable.startSpringback();
4199 }
4200
4201 break;
4202 }
4203
4204 setPressed(false);
4205
Yigit Boyarb6218472019-02-06 10:07:06 -08004206 if (shouldDisplayEdgeEffects()) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004207 mEdgeGlowTop.onRelease();
4208 mEdgeGlowBottom.onRelease();
4209 }
4210
4211 // Need to redraw since we probably aren't drawing the selector anymore
4212 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004213 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004214 recycleVelocityTracker();
4215
4216 mActivePointerId = INVALID_POINTER;
4217
4218 if (PROFILE_SCROLLING) {
4219 if (mScrollProfilingStarted) {
4220 Debug.stopMethodTracing();
4221 mScrollProfilingStarted = false;
4222 }
4223 }
4224
4225 if (mScrollStrictSpan != null) {
4226 mScrollStrictSpan.finish();
4227 mScrollStrictSpan = null;
4228 }
4229 }
4230
Yigit Boyarb6218472019-02-06 10:07:06 -08004231 private boolean shouldDisplayEdgeEffects() {
4232 return getOverScrollMode() != OVER_SCROLL_NEVER;
4233 }
4234
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004235 private void onTouchCancel() {
4236 switch (mTouchMode) {
4237 case TOUCH_MODE_OVERSCROLL:
4238 if (mFlingRunnable == null) {
4239 mFlingRunnable = new FlingRunnable();
4240 }
4241 mFlingRunnable.startSpringback();
4242 break;
4243
4244 case TOUCH_MODE_OVERFLING:
4245 // Do nothing - let it play out.
4246 break;
4247
4248 default:
4249 mTouchMode = TOUCH_MODE_REST;
4250 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004251 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004252 if (motionView != null) {
4253 motionView.setPressed(false);
4254 }
4255 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004256 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004257 recycleVelocityTracker();
4258 }
4259
Yigit Boyarb6218472019-02-06 10:07:06 -08004260 if (shouldDisplayEdgeEffects()) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004261 mEdgeGlowTop.onRelease();
4262 mEdgeGlowBottom.onRelease();
4263 }
4264 mActivePointerId = INVALID_POINTER;
4265 }
4266
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004267 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004268 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4269 if (mScrollY != scrollY) {
4270 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4271 mScrollY = scrollY;
4272 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004273
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004274 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004275 }
Adam Powell637d3372010-08-25 14:37:03 -07004276 }
4277
4278 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004279 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004280 switch (event.getAction()) {
4281 case MotionEvent.ACTION_SCROLL:
4282 final float axisValue;
4283 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4284 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4285 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4286 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4287 } else {
4288 axisValue = 0;
4289 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004290
Aaron Whytef8306522017-03-22 16:30:58 -07004291 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -04004292 if (delta != 0) {
4293 if (!trackMotionScroll(delta, delta)) {
4294 return true;
4295 }
4296 }
4297 break;
4298 case MotionEvent.ACTION_BUTTON_PRESS:
4299 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004300 int actionButton = event.getActionButton();
4301 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4302 || actionButton == MotionEvent.BUTTON_SECONDARY)
4303 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4304 if (performStylusButtonPressAction(event)) {
4305 removeCallbacks(mPendingCheckForLongPress);
4306 removeCallbacks(mPendingCheckForTap);
4307 }
4308 }
Ned Burns20ad0732016-08-18 14:22:57 -04004309 }
4310 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004311 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004312
Jeff Brown33bbfd22011-02-24 20:55:35 -08004313 return super.onGenericMotionEvent(event);
4314 }
4315
Adam Powell4884c642014-08-07 13:52:53 -07004316 /**
4317 * Initiate a fling with the given velocity.
4318 *
4319 * <p>Applications can use this method to manually initiate a fling as if the user
4320 * initiated it via touch interaction.</p>
4321 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004322 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4323 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004324 */
4325 public void fling(int velocityY) {
4326 if (mFlingRunnable == null) {
4327 mFlingRunnable = new FlingRunnable();
4328 }
4329 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004330 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004331 }
4332
Jeff Brown33bbfd22011-02-24 20:55:35 -08004333 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004334 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4335 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4336 }
4337
4338 @Override
4339 public void onNestedScrollAccepted(View child, View target, int axes) {
4340 super.onNestedScrollAccepted(child, target, axes);
4341 startNestedScroll(SCROLL_AXIS_VERTICAL);
4342 }
4343
4344 @Override
4345 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4346 int dxUnconsumed, int dyUnconsumed) {
4347 final int motionIndex = getChildCount() / 2;
4348 final View motionView = getChildAt(motionIndex);
4349 final int oldTop = motionView != null ? motionView.getTop() : 0;
4350 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4351 int myUnconsumed = dyUnconsumed;
4352 int myConsumed = 0;
4353 if (motionView != null) {
4354 myConsumed = motionView.getTop() - oldTop;
4355 myUnconsumed -= myConsumed;
4356 }
4357 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4358 }
4359 }
4360
4361 @Override
4362 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4363 final int childCount = getChildCount();
4364 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4365 Math.abs(velocityY) > mMinimumVelocity) {
4366 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4367 if (mFlingRunnable == null) {
4368 mFlingRunnable = new FlingRunnable();
4369 }
Adam Powell9413b242014-08-06 17:34:24 -07004370 if (!dispatchNestedPreFling(0, velocityY)) {
4371 mFlingRunnable.start((int) velocityY);
4372 }
Adam Powell96d62af2014-05-02 10:04:38 -07004373 return true;
4374 }
4375 return dispatchNestedFling(velocityX, velocityY, consumed);
4376 }
4377
4378 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004379 public void draw(Canvas canvas) {
4380 super.draw(canvas);
Yigit Boyarb6218472019-02-06 10:07:06 -08004381 if (shouldDisplayEdgeEffects()) {
Adam Powell637d3372010-08-25 14:37:03 -07004382 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004383 final boolean clipToPadding = getClipToPadding();
4384 final int width;
4385 final int height;
4386 final int translateX;
4387 final int translateY;
4388
4389 if (clipToPadding) {
4390 width = getWidth() - mPaddingLeft - mPaddingRight;
4391 height = getHeight() - mPaddingTop - mPaddingBottom;
4392 translateX = mPaddingLeft;
4393 translateY = mPaddingTop;
4394 } else {
4395 width = getWidth();
4396 height = getHeight();
4397 translateX = 0;
4398 translateY = 0;
4399 }
Vu Thanh Cong3af8c962016-11-10 22:40:24 +09004400 mEdgeGlowTop.setSize(width, height);
4401 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004402 if (!mEdgeGlowTop.isFinished()) {
4403 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004404 canvas.clipRect(translateX, translateY,
4405 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4406 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4407 canvas.translate(translateX, edgeY);
Adam Powell637d3372010-08-25 14:37:03 -07004408 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004409 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004410 }
4411 canvas.restoreToCount(restoreCount);
4412 }
4413 if (!mEdgeGlowBottom.isFinished()) {
4414 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004415 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4416 translateX + width, translateY + height);
4417 final int edgeX = -width + translateX;
4418 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4419 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004420 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004421 canvas.rotate(180, width, 0);
Adam Powell637d3372010-08-25 14:37:03 -07004422 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004423 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004424 }
4425 canvas.restoreToCount(restoreCount);
4426 }
4427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004428 }
4429
Michael Jurka13451a42011-08-22 15:54:21 -07004430 private void initOrResetVelocityTracker() {
4431 if (mVelocityTracker == null) {
4432 mVelocityTracker = VelocityTracker.obtain();
4433 } else {
4434 mVelocityTracker.clear();
4435 }
4436 }
4437
4438 private void initVelocityTrackerIfNotExists() {
4439 if (mVelocityTracker == null) {
4440 mVelocityTracker = VelocityTracker.obtain();
4441 }
4442 }
4443
4444 private void recycleVelocityTracker() {
4445 if (mVelocityTracker != null) {
4446 mVelocityTracker.recycle();
4447 mVelocityTracker = null;
4448 }
4449 }
4450
4451 @Override
4452 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4453 if (disallowIntercept) {
4454 recycleVelocityTracker();
4455 }
4456 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4457 }
4458
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004459 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004460 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004461 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004462 return true;
4463 }
4464
4465 return super.onInterceptHoverEvent(event);
4466 }
4467
4468 @Override
Vladislav Kaznacheev11372fa2017-02-16 09:37:56 -08004469 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4470 if (mFastScroll != null) {
4471 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4472 if (pointerIcon != null) {
4473 return pointerIcon;
4474 }
4475 }
4476 return super.onResolvePointerIcon(event, pointerIndex);
4477 }
4478
4479 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004480 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004481 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004482 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004483
Adam Powell1fa179ef2012-04-12 15:01:40 -07004484 if (mPositionScroller != null) {
4485 mPositionScroller.stop();
4486 }
4487
Alan Viverette462c2172014-02-24 12:24:11 -08004488 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004489 // Something isn't right.
4490 // Since we rely on being attached to get data set change notifications,
4491 // don't risk doing anything where we might try to resync and find things
4492 // in a bogus state.
4493 return false;
4494 }
4495
Alan Viverette8636ace2013-10-31 15:41:31 -07004496 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004497 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004498 }
Romain Guy0a637162009-05-29 14:43:54 -07004499
Adam Powell744beff2014-09-22 09:47:48 -07004500 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004501 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004502 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004503 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4504 mMotionCorrection = 0;
4505 return true;
4506 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004507
Adam Powell4cd47702010-02-25 11:21:14 -08004508 final int x = (int) ev.getX();
4509 final int y = (int) ev.getY();
4510 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004511
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004512 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004513 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004514 // User clicked on an actual view (and was not stopping a fling).
4515 // Remember where the motion event started
4516 v = getChildAt(motionPosition - mFirstPosition);
4517 mMotionViewOriginalTop = v.getTop();
4518 mMotionX = x;
4519 mMotionY = y;
4520 mMotionPosition = motionPosition;
4521 mTouchMode = TOUCH_MODE_DOWN;
4522 clearScrollingCache();
4523 }
4524 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004525 initOrResetVelocityTracker();
4526 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004527 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004528 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004529 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004530 return true;
4531 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004532 break;
4533 }
4534
4535 case MotionEvent.ACTION_MOVE: {
4536 switch (mTouchMode) {
4537 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004538 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4539 if (pointerIndex == -1) {
4540 pointerIndex = 0;
4541 mActivePointerId = ev.getPointerId(pointerIndex);
4542 }
Adam Powell4cd47702010-02-25 11:21:14 -08004543 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004544 initVelocityTrackerIfNotExists();
4545 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004546 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004547 return true;
4548 }
4549 break;
4550 }
4551 break;
4552 }
4553
Michael Jurka13451a42011-08-22 15:54:21 -07004554 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004555 case MotionEvent.ACTION_UP: {
4556 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004557 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004558 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004559 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004560 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004561 break;
4562 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004563
Adam Powell4cd47702010-02-25 11:21:14 -08004564 case MotionEvent.ACTION_POINTER_UP: {
4565 onSecondaryPointerUp(ev);
4566 break;
4567 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004568 }
4569
4570 return false;
4571 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004572
Adam Powell4cd47702010-02-25 11:21:14 -08004573 private void onSecondaryPointerUp(MotionEvent ev) {
4574 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4575 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4576 final int pointerId = ev.getPointerId(pointerIndex);
4577 if (pointerId == mActivePointerId) {
4578 // This was our active pointer going up. Choose a new
4579 // active pointer and adjust accordingly.
4580 // TODO: Make this decision more intelligent.
4581 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4582 mMotionX = (int) ev.getX(newPointerIndex);
4583 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004584 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004585 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004586 }
4587 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004588
4589 /**
4590 * {@inheritDoc}
4591 */
4592 @Override
4593 public void addTouchables(ArrayList<View> views) {
4594 final int count = getChildCount();
4595 final int firstPosition = mFirstPosition;
4596 final ListAdapter adapter = mAdapter;
4597
4598 if (adapter == null) {
4599 return;
4600 }
4601
4602 for (int i = 0; i < count; i++) {
4603 final View child = getChildAt(i);
4604 if (adapter.isEnabled(firstPosition + i)) {
4605 views.add(child);
4606 }
4607 child.addTouchables(views);
4608 }
4609 }
4610
4611 /**
4612 * Fires an "on scroll state changed" event to the registered
4613 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4614 * is fired only if the specified state is different from the previously known state.
4615 *
4616 * @param newState The new scroll state.
4617 */
Filip Pavlisab802912019-02-07 16:06:06 +00004618 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769710)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004619 void reportScrollStateChange(int newState) {
4620 if (newState != mLastScrollState) {
4621 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004622 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004623 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004624 }
4625 }
4626 }
4627
4628 /**
4629 * Responsible for fling behavior. Use {@link #start(int)} to
4630 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4631 * A FlingRunnable will keep re-posting itself until the fling is done.
4632 *
4633 */
4634 private class FlingRunnable implements Runnable {
4635 /**
4636 * Tracks the decay of a fling scroll
4637 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01004638 @UnsupportedAppUsage
Adam Powell637d3372010-08-25 14:37:03 -07004639 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004640
4641 /**
4642 * Y value reported by mScroller on the previous fling
4643 */
4644 private int mLastFlingY;
4645
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004646 /**
4647 * If true, {@link #endFling()} will not report scroll state change to
4648 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4649 */
4650 private boolean mSuppressIdleStateChangeCall;
4651
Gilles Debunned348bb42010-11-15 12:19:35 -08004652 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004653 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004654 public void run() {
4655 final int activeId = mActivePointerId;
4656 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004657 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004658 if (vt == null || activeId == INVALID_POINTER) {
4659 return;
4660 }
4661
4662 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4663 final float yvel = -vt.getYVelocity(activeId);
4664
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004665 if (Math.abs(yvel) >= mMinimumVelocity
4666 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004667 // Keep the fling alive a little longer
4668 postDelayed(this, FLYWHEEL_TIMEOUT);
4669 } else {
4670 endFling();
4671 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004672 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004673 }
4674 }
4675 };
4676
4677 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4678
Adam Powell79ac3392010-01-28 21:22:20 -08004679 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004680 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004681 }
4682
Louis Pullen-Freilichf0955ac2019-02-06 19:29:34 +00004683 // Use AbsListView#fling(int) instead
4684 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Adam Powell79ac3392010-01-28 21:22:20 -08004685 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004686 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4687 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004688 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004689 mScroller.fling(0, initialY, 0, initialVelocity,
4690 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4691 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004692 mSuppressIdleStateChangeCall = false;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004693 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004694
4695 if (PROFILE_FLINGING) {
4696 if (!mFlingProfilingStarted) {
4697 Debug.startMethodTracing("AbsListViewFling");
4698 mFlingProfilingStarted = true;
4699 }
4700 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004701
4702 if (mFlingStrictSpan == null) {
4703 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4704 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004705 }
Adam Powell45803472010-01-25 15:10:44 -08004706
Adam Powell637d3372010-08-25 14:37:03 -07004707 void startSpringback() {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004708 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004709 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4710 mTouchMode = TOUCH_MODE_OVERFLING;
4711 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004712 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004713 } else {
4714 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004715 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004716 }
4717 }
4718
4719 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004720 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004721 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4722 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004723 mTouchMode = TOUCH_MODE_OVERFLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004724 mSuppressIdleStateChangeCall = false;
Adam Powell637d3372010-08-25 14:37:03 -07004725 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004726 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004727 }
4728
4729 void edgeReached(int delta) {
4730 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4731 final int overscrollMode = getOverScrollMode();
4732 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4733 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4734 mTouchMode = TOUCH_MODE_OVERFLING;
4735 final int vel = (int) mScroller.getCurrVelocity();
4736 if (delta > 0) {
4737 mEdgeGlowTop.onAbsorb(vel);
4738 } else {
4739 mEdgeGlowBottom.onAbsorb(vel);
4740 }
Adam Powell40322522011-01-12 21:58:20 -08004741 } else {
4742 mTouchMode = TOUCH_MODE_REST;
4743 if (mPositionScroller != null) {
4744 mPositionScroller.stop();
4745 }
Adam Powell637d3372010-08-25 14:37:03 -07004746 }
4747 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004748 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004749 }
4750
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004751 void startScroll(int distance, int duration, boolean linear,
4752 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08004753 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4754 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004755 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004756 mScroller.startScroll(0, initialY, 0, distance, duration);
4757 mTouchMode = TOUCH_MODE_FLING;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004758 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004759 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004760 }
4761
Louis Pullen-Freilichf0955ac2019-02-06 19:29:34 +00004762 // To interrupt a fling early you should use smoothScrollBy(0,0) instead
4763 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
Gilles Debunned348bb42010-11-15 12:19:35 -08004764 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004765 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004766
Adam Powell79ac3392010-01-28 21:22:20 -08004767 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004768 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004769
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04004770 if (!mSuppressIdleStateChangeCall) {
4771 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4772 }
Romain Guy21317d12010-10-12 13:32:31 -07004773 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004774 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004775
4776 if (mFlingStrictSpan != null) {
4777 mFlingStrictSpan.finish();
4778 mFlingStrictSpan = null;
4779 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004780 }
4781
4782 void flywheelTouch() {
4783 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004784 }
4785
Alan Viverette8fa327a2013-05-31 14:53:13 -07004786 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004787 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004788 switch (mTouchMode) {
4789 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004790 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004791 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004792
Gilles Debunned348bb42010-11-15 12:19:35 -08004793 case TOUCH_MODE_SCROLL:
4794 if (mScroller.isFinished()) {
4795 return;
4796 }
4797 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004798 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004799 if (mDataChanged) {
4800 layoutChildren();
4801 }
4802
Adam Powell79ac3392010-01-28 21:22:20 -08004803 if (mItemCount == 0 || getChildCount() == 0) {
4804 endFling();
4805 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004806 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004807
Adam Powell637d3372010-08-25 14:37:03 -07004808 final OverScroller scroller = mScroller;
4809 boolean more = scroller.computeScrollOffset();
4810 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004811
Adam Powell637d3372010-08-25 14:37:03 -07004812 // Flip sign to convert finger direction to list items direction
4813 // (e.g. finger moving down means list is moving towards the top)
4814 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004815
Adam Powell637d3372010-08-25 14:37:03 -07004816 // Pretend that each frame of a fling scroll is a touch scroll
4817 if (delta > 0) {
4818 // List is moving towards the top. Use first view as mMotionPosition
4819 mMotionPosition = mFirstPosition;
4820 final View firstView = getChildAt(0);
4821 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004822
Adam Powell637d3372010-08-25 14:37:03 -07004823 // Don't fling more than 1 screen
4824 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4825 } else {
4826 // List is moving towards the bottom. Use last view as mMotionPosition
4827 int offsetToLast = getChildCount() - 1;
4828 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004829
Adam Powell637d3372010-08-25 14:37:03 -07004830 final View lastView = getChildAt(offsetToLast);
4831 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004832
Adam Powell637d3372010-08-25 14:37:03 -07004833 // Don't fling more than 1 screen
4834 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4835 }
Adam Powell79ac3392010-01-28 21:22:20 -08004836
Adam Powell637d3372010-08-25 14:37:03 -07004837 // Check to see if we have bumped into the scroll limit
4838 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4839 int oldTop = 0;
4840 if (motionView != null) {
4841 oldTop = motionView.getTop();
4842 }
Adam Powell9d32d242010-03-29 16:02:07 -07004843
Adam Powell637d3372010-08-25 14:37:03 -07004844 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004845 final boolean atEdge = trackMotionScroll(delta, delta);
4846 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004847 if (atEnd) {
4848 if (motionView != null) {
4849 // Tweak the scroll for how far we overshot
4850 int overshoot = -(delta - (motionView.getTop() - oldTop));
4851 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4852 0, mOverflingDistance, false);
4853 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004854 if (more) {
4855 edgeReached(delta);
4856 }
Adam Powell637d3372010-08-25 14:37:03 -07004857 break;
4858 }
4859
4860 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004861 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004862 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004863 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004864 } else {
4865 endFling();
4866
4867 if (PROFILE_FLINGING) {
4868 if (mFlingProfilingStarted) {
4869 Debug.stopMethodTracing();
4870 mFlingProfilingStarted = false;
4871 }
4872
4873 if (mFlingStrictSpan != null) {
4874 mFlingStrictSpan.finish();
4875 mFlingStrictSpan = null;
4876 }
Adam Powell79ac3392010-01-28 21:22:20 -08004877 }
4878 }
Adam Powell637d3372010-08-25 14:37:03 -07004879 break;
4880 }
4881
4882 case TOUCH_MODE_OVERFLING: {
4883 final OverScroller scroller = mScroller;
4884 if (scroller.computeScrollOffset()) {
4885 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004886 final int currY = scroller.getCurrY();
4887 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004888 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4889 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004890 final boolean crossDown = scrollY <= 0 && currY > 0;
4891 final boolean crossUp = scrollY >= 0 && currY < 0;
4892 if (crossDown || crossUp) {
4893 int velocity = (int) scroller.getCurrVelocity();
4894 if (crossUp) velocity = -velocity;
4895
4896 // Don't flywheel from this; we're just continuing things.
4897 scroller.abortAnimation();
4898 start(velocity);
4899 } else {
4900 startSpringback();
4901 }
Adam Powell637d3372010-08-25 14:37:03 -07004902 } else {
4903 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004904 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004905 }
4906 } else {
4907 endFling();
4908 }
4909 break;
4910 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004911 }
4912 }
4913 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004914
Adam Powell45803472010-01-25 15:10:44 -08004915 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004916 * The amount of friction applied to flings. The default value
4917 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004918 */
4919 public void setFriction(float friction) {
4920 if (mFlingRunnable == null) {
4921 mFlingRunnable = new FlingRunnable();
4922 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004923 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004924 }
Romain Guy21317d12010-10-12 13:32:31 -07004925
4926 /**
4927 * Sets a scale factor for the fling velocity. The initial scale
4928 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004929 *
Romain Guy21317d12010-10-12 13:32:31 -07004930 * @param scale The scale factor to multiply the velocity by.
4931 */
4932 public void setVelocityScale(float scale) {
4933 mVelocityScale = scale;
4934 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004935
Romain Guy4bede9e2010-10-11 19:36:59 -07004936 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004937 * Override this for better control over position scrolling.
4938 */
4939 AbsPositionScroller createPositionScroller() {
4940 return new PositionScroller();
4941 }
4942
4943 /**
Adam Powell45803472010-01-25 15:10:44 -08004944 * Smoothly scroll to the specified adapter position. The view will
4945 * scroll such that the indicated position is displayed.
4946 * @param position Scroll to this adapter position.
4947 */
4948 public void smoothScrollToPosition(int position) {
4949 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004950 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004951 }
4952 mPositionScroller.start(position);
4953 }
Erik322171b2010-10-13 15:46:00 -07004954
4955 /**
4956 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004957 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004958 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4959 * the first or last item beyond the boundaries of the list) it will get as close
4960 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4961 *
4962 * @param position Position to scroll to
4963 * @param offset Desired distance in pixels of <code>position</code> from the top
4964 * of the view when scrolling is finished
4965 * @param duration Number of milliseconds to use for the scroll
4966 */
4967 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4968 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004969 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004970 }
4971 mPositionScroller.startWithOffset(position, offset, duration);
4972 }
4973
Adam Powell45803472010-01-25 15:10:44 -08004974 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004975 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004976 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004977 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4978 * the first or last item beyond the boundaries of the list) it will get as close
4979 * as possible.
4980 *
4981 * @param position Position to scroll to
4982 * @param offset Desired distance in pixels of <code>position</code> from the top
4983 * of the view when scrolling is finished
4984 */
4985 public void smoothScrollToPositionFromTop(int position, int offset) {
4986 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004987 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004988 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004989 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004990 }
4991
4992 /**
Adam Powell45803472010-01-25 15:10:44 -08004993 * Smoothly scroll to the specified adapter position. The view will
4994 * scroll such that the indicated position is displayed, but it will
4995 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004996 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004997 *
Adam Powell45803472010-01-25 15:10:44 -08004998 * @param position Scroll to this adapter position.
4999 * @param boundPosition Do not scroll if it would move this adapter
5000 * position out of view.
5001 */
5002 public void smoothScrollToPosition(int position, int boundPosition) {
5003 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08005004 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08005005 }
5006 mPositionScroller.start(position, boundPosition);
5007 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005008
Adam Powell45803472010-01-25 15:10:44 -08005009 /**
5010 * Smoothly scroll by distance pixels over duration milliseconds.
5011 * @param distance Distance to scroll in pixels.
5012 * @param duration Duration of the scroll animation in milliseconds.
5013 */
5014 public void smoothScrollBy(int distance, int duration) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005015 smoothScrollBy(distance, duration, false, false);
Adam Powell0b8acd82012-04-25 20:29:23 -07005016 }
5017
Mathew Inwood978c6e22018-08-21 15:58:55 +01005018 @UnsupportedAppUsage
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005019 void smoothScrollBy(int distance, int duration, boolean linear,
5020 boolean suppressEndFlingStateChangeCall) {
Adam Powell45803472010-01-25 15:10:44 -08005021 if (mFlingRunnable == null) {
5022 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08005023 }
Adam Powell40322522011-01-12 21:58:20 -08005024
Marc Blank299acb52010-10-21 11:03:53 -07005025 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08005026 final int firstPos = mFirstPosition;
5027 final int childCount = getChildCount();
5028 final int lastPos = firstPos + childCount;
5029 final int topLimit = getPaddingTop();
5030 final int bottomLimit = getHeight() - getPaddingBottom();
5031
Adam Powell79303752011-01-13 22:06:49 -08005032 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08005033 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07005034 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08005035 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
5036 mFlingRunnable.endFling();
5037 if (mPositionScroller != null) {
5038 mPositionScroller.stop();
5039 }
5040 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08005041 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005042 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
Marc Blank299acb52010-10-21 11:03:53 -07005043 }
Adam Powell45803472010-01-25 15:10:44 -08005044 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005045
Winson Chung499cb9f2010-07-16 11:18:17 -07005046 /**
5047 * Allows RemoteViews to scroll relatively to a position.
5048 */
5049 void smoothScrollByOffset(int position) {
5050 int index = -1;
5051 if (position < 0) {
5052 index = getFirstVisiblePosition();
5053 } else if (position > 0) {
5054 index = getLastVisiblePosition();
5055 }
5056
5057 if (index > -1) {
5058 View child = getChildAt(index - getFirstVisiblePosition());
5059 if (child != null) {
5060 Rect visibleRect = new Rect();
5061 if (child.getGlobalVisibleRect(visibleRect)) {
5062 // the child is partially visible
5063 int childRectArea = child.getWidth() * child.getHeight();
5064 int visibleRectArea = visibleRect.width() * visibleRect.height();
5065 float visibleArea = (visibleRectArea / (float) childRectArea);
5066 final float visibleThreshold = 0.75f;
5067 if ((position < 0) && (visibleArea < visibleThreshold)) {
5068 // the top index is not perceivably visible so offset
5069 // to account for showing that top index as well
5070 ++index;
5071 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
5072 // the bottom index is not perceivably visible so offset
5073 // to account for showing that bottom index as well
5074 --index;
5075 }
5076 }
5077 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5078 }
5079 }
5080 }
5081
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005082 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005083 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005084 setChildrenDrawnWithCacheEnabled(true);
5085 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08005086 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005087 }
5088 }
5089
5090 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07005091 if (!isHardwareAccelerated()) {
5092 if (mClearScrollingCache == null) {
5093 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07005094 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07005095 public void run() {
5096 if (mCachingStarted) {
5097 mCachingStarted = mCachingActive = false;
5098 setChildrenDrawnWithCacheEnabled(false);
5099 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5100 setChildrenDrawingCacheEnabled(false);
5101 }
5102 if (!isAlwaysDrawnWithCacheEnabled()) {
5103 invalidate();
5104 }
Romain Guy6dfed242009-05-11 18:25:05 -07005105 }
5106 }
Romain Guy9d849a22012-03-14 16:41:42 -07005107 };
5108 }
5109 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005110 }
5111 }
5112
5113 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005114 * Scrolls the list items within the view by a specified number of pixels.
5115 *
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04005116 * <p>The actual amount of scroll is capped by the list content viewport height
5117 * which is the list height minus top and bottom paddings minus one pixel.</p>
5118 *
Alan Viverette2f3317a2013-08-06 18:19:48 -07005119 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005120 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005121 */
Alan Viveretteba299062013-09-03 16:01:51 -07005122 public void scrollListBy(int y) {
5123 trackMotionScroll(-y, -y);
5124 }
5125
5126 /**
5127 * Check if the items in the list can be scrolled in a certain direction.
5128 *
5129 * @param direction Negative to check scrolling up, positive to check
5130 * scrolling down.
5131 * @return true if the list can be scrolled in the specified direction,
5132 * false otherwise.
5133 * @see #scrollListBy(int)
5134 */
5135 public boolean canScrollList(int direction) {
5136 final int childCount = getChildCount();
5137 if (childCount == 0) {
5138 return false;
5139 }
5140
5141 final int firstPosition = mFirstPosition;
5142 final Rect listPadding = mListPadding;
5143 if (direction > 0) {
5144 final int lastBottom = getChildAt(childCount - 1).getBottom();
5145 final int lastPosition = firstPosition + childCount;
5146 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5147 } else {
5148 final int firstTop = getChildAt(0).getTop();
5149 return firstPosition > 0 || firstTop < listPadding.top;
5150 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005151 }
5152
5153 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005154 * Track a motion scroll
5155 *
5156 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5157 * began. Positive numbers mean the user's finger is moving down the screen.
5158 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005159 * @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 -08005160 */
Rahul Ravikumar6933d7e2019-02-07 13:59:18 -08005161 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051739)
Adam Powell45803472010-01-25 15:10:44 -08005162 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005163 final int childCount = getChildCount();
5164 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005165 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005166 }
5167
5168 final int firstTop = getChildAt(0).getTop();
5169 final int lastBottom = getChildAt(childCount - 1).getBottom();
5170
5171 final Rect listPadding = mListPadding;
5172
Adam Powellbdccc2d2010-12-14 17:34:27 -08005173 // "effective padding" In this case is the amount of padding that affects
5174 // how much space should not be filled by items. If we don't clip to padding
5175 // there is no effective padding.
5176 int effectivePaddingTop = 0;
5177 int effectivePaddingBottom = 0;
5178 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5179 effectivePaddingTop = listPadding.top;
5180 effectivePaddingBottom = listPadding.bottom;
5181 }
5182
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005183 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005184 final int spaceAbove = effectivePaddingTop - firstTop;
5185 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005186 final int spaceBelow = lastBottom - end;
5187
5188 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5189 if (deltaY < 0) {
5190 deltaY = Math.max(-(height - 1), deltaY);
5191 } else {
5192 deltaY = Math.min(height - 1, deltaY);
5193 }
5194
5195 if (incrementalDeltaY < 0) {
5196 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5197 } else {
5198 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5199 }
5200
Adam Powell45803472010-01-25 15:10:44 -08005201 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005202
Adam Powell637d3372010-08-25 14:37:03 -07005203 // Update our guesses for where the first and last views are
5204 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005205 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005206 } else {
5207 mFirstPositionDistanceGuess += incrementalDeltaY;
5208 }
5209 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005210 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005211 } else {
5212 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005213 }
Adam Powell45803472010-01-25 15:10:44 -08005214
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005215 final boolean cannotScrollDown = (firstPosition == 0 &&
5216 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5217 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5218 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005219
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005220 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005221 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005222 }
5223
5224 final boolean down = incrementalDeltaY < 0;
5225
Adam Powell029cfbd2010-03-08 19:03:54 -08005226 final boolean inTouchMode = isInTouchMode();
5227 if (inTouchMode) {
5228 hideSelector();
5229 }
Adam Powell45803472010-01-25 15:10:44 -08005230
5231 final int headerViewsCount = getHeaderViewsCount();
5232 final int footerViewsStart = mItemCount - getFooterViewsCount();
5233
5234 int start = 0;
5235 int count = 0;
5236
5237 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005238 int top = -incrementalDeltaY;
5239 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5240 top += listPadding.top;
5241 }
Adam Powell45803472010-01-25 15:10:44 -08005242 for (int i = 0; i < childCount; i++) {
5243 final View child = getChildAt(i);
5244 if (child.getBottom() >= top) {
5245 break;
5246 } else {
5247 count++;
5248 int position = firstPosition + i;
5249 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005250 // The view will be rebound to new data, clear any
5251 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005252 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005253 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005254 }
5255 }
5256 }
5257 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005258 int bottom = getHeight() - incrementalDeltaY;
5259 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5260 bottom -= listPadding.bottom;
5261 }
Adam Powell45803472010-01-25 15:10:44 -08005262 for (int i = childCount - 1; i >= 0; i--) {
5263 final View child = getChildAt(i);
5264 if (child.getTop() <= bottom) {
5265 break;
5266 } else {
5267 start = i;
5268 count++;
5269 int position = firstPosition + i;
5270 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005271 // The view will be rebound to new data, clear any
5272 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005273 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005274 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005275 }
5276 }
5277 }
5278 }
5279
5280 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5281
5282 mBlockLayoutRequests = true;
5283
5284 if (count > 0) {
5285 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005286 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005287 }
Adam Powell539ee872012-02-03 19:00:49 -08005288
Romain Guy9d849a22012-03-14 16:41:42 -07005289 // invalidate before moving the children to avoid unnecessary invalidate
5290 // calls to bubble up from the children all the way to the top
5291 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005292 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005293 }
5294
Adam Powell45803472010-01-25 15:10:44 -08005295 offsetChildrenTopAndBottom(incrementalDeltaY);
5296
5297 if (down) {
5298 mFirstPosition += count;
5299 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005300
Adam Powell45803472010-01-25 15:10:44 -08005301 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5302 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5303 fillGap(down);
5304 }
5305
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005306 mRecycler.fullyDetachScrapViews();
Evan Rosky837ae0d2017-10-26 12:50:33 -07005307 boolean selectorOnScreen = false;
Adam Powell029cfbd2010-03-08 19:03:54 -08005308 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005309 final int childIndex = mSelectedPosition - mFirstPosition;
5310 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005311 positionSelector(mSelectedPosition, getChildAt(childIndex));
Evan Rosky837ae0d2017-10-26 12:50:33 -07005312 selectorOnScreen = true;
Adam Powell2a20ddd2010-03-11 18:09:59 -08005313 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005314 } else if (mSelectorPosition != INVALID_POSITION) {
5315 final int childIndex = mSelectorPosition - mFirstPosition;
5316 if (childIndex >= 0 && childIndex < getChildCount()) {
Evan Rosky837ae0d2017-10-26 12:50:33 -07005317 positionSelector(mSelectorPosition, getChildAt(childIndex));
5318 selectorOnScreen = true;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005319 }
Evan Rosky837ae0d2017-10-26 12:50:33 -07005320 }
5321 if (!selectorOnScreen) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005322 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005323 }
5324
Adam Powell45803472010-01-25 15:10:44 -08005325 mBlockLayoutRequests = false;
5326
5327 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005328
Adam Powell45803472010-01-25 15:10:44 -08005329 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005330 }
5331
5332 /**
5333 * Returns the number of header views in the list. Header views are special views
5334 * at the top of the list that should not be recycled during a layout.
5335 *
5336 * @return The number of header views, 0 in the default implementation.
5337 */
5338 int getHeaderViewsCount() {
5339 return 0;
5340 }
5341
5342 /**
5343 * Returns the number of footer views in the list. Footer views are special views
5344 * at the bottom of the list that should not be recycled during a layout.
5345 *
5346 * @return The number of footer views, 0 in the default implementation.
5347 */
5348 int getFooterViewsCount() {
5349 return 0;
5350 }
5351
5352 /**
5353 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5354 * remain on screen are shifted and the other ones are discarded. The role of this
5355 * method is to fill the gap thus created by performing a partial layout in the
5356 * empty space.
5357 *
5358 * @param down true if the scroll is going down, false if it is going up
5359 */
5360 abstract void fillGap(boolean down);
5361
5362 void hideSelector() {
5363 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005364 if (mLayoutMode != LAYOUT_SPECIFIC) {
5365 mResurrectToPosition = mSelectedPosition;
5366 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005367 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5368 mResurrectToPosition = mNextSelectedPosition;
5369 }
5370 setSelectedPositionInt(INVALID_POSITION);
5371 setNextSelectedPositionInt(INVALID_POSITION);
5372 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005373 }
5374 }
5375
5376 /**
5377 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5378 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5379 * of items available in the adapter
5380 */
5381 int reconcileSelectedPosition() {
5382 int position = mSelectedPosition;
5383 if (position < 0) {
5384 position = mResurrectToPosition;
5385 }
5386 position = Math.max(0, position);
5387 position = Math.min(position, mItemCount - 1);
5388 return position;
5389 }
5390
5391 /**
5392 * Find the row closest to y. This row will be used as the motion row when scrolling
5393 *
5394 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005395 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005396 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01005397 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005398 abstract int findMotionRow(int y);
5399
5400 /**
Adam Powell637d3372010-08-25 14:37:03 -07005401 * Find the row closest to y. This row will be used as the motion row when scrolling.
5402 *
5403 * @param y Where the user touched
5404 * @return The position of the first (or only) item in the row closest to y
5405 */
5406 int findClosestMotionRow(int y) {
5407 final int childCount = getChildCount();
5408 if (childCount == 0) {
5409 return INVALID_POSITION;
5410 }
5411
5412 final int motionRow = findMotionRow(y);
5413 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5414 }
5415
5416 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005417 * Causes all the views to be rebuilt and redrawn.
5418 */
5419 public void invalidateViews() {
5420 mDataChanged = true;
5421 rememberSyncState();
5422 requestLayout();
5423 invalidate();
5424 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005425
Jeff Brown4e6319b2010-12-13 10:36:51 -08005426 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005427 * If there is a selection returns false.
5428 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005429 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01005430 @UnsupportedAppUsage
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005431 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005432 if (mSelectedPosition < 0 && resurrectSelection()) {
5433 updateSelectorState();
5434 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005435 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005436 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005437 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005438
5439 /**
5440 * Makes the item at the supplied position selected.
5441 *
5442 * @param position the position of the new selection
5443 */
5444 abstract void setSelectionInt(int position);
5445
5446 /**
5447 * Attempt to bring the selection back if the user is switching from touch
5448 * to trackball mode
5449 * @return Whether selection was set to something.
5450 */
5451 boolean resurrectSelection() {
5452 final int childCount = getChildCount();
5453
5454 if (childCount <= 0) {
5455 return false;
5456 }
5457
5458 int selectedTop = 0;
5459 int selectedPos;
5460 int childrenTop = mListPadding.top;
5461 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5462 final int firstPosition = mFirstPosition;
5463 final int toPosition = mResurrectToPosition;
5464 boolean down = true;
5465
5466 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5467 selectedPos = toPosition;
5468
5469 final View selected = getChildAt(selectedPos - mFirstPosition);
5470 selectedTop = selected.getTop();
5471 int selectedBottom = selected.getBottom();
5472
5473 // We are scrolled, don't get in the fade
5474 if (selectedTop < childrenTop) {
5475 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5476 } else if (selectedBottom > childrenBottom) {
5477 selectedTop = childrenBottom - selected.getMeasuredHeight()
5478 - getVerticalFadingEdgeLength();
5479 }
5480 } else {
5481 if (toPosition < firstPosition) {
5482 // Default to selecting whatever is first
5483 selectedPos = firstPosition;
5484 for (int i = 0; i < childCount; i++) {
5485 final View v = getChildAt(i);
5486 final int top = v.getTop();
5487
5488 if (i == 0) {
5489 // Remember the position of the first item
5490 selectedTop = top;
5491 // See if we are scrolled at all
5492 if (firstPosition > 0 || top < childrenTop) {
5493 // If we are scrolled, don't select anything that is
5494 // in the fade region
5495 childrenTop += getVerticalFadingEdgeLength();
5496 }
5497 }
5498 if (top >= childrenTop) {
5499 // Found a view whose top is fully visisble
5500 selectedPos = firstPosition + i;
5501 selectedTop = top;
5502 break;
5503 }
5504 }
5505 } else {
5506 final int itemCount = mItemCount;
5507 down = false;
5508 selectedPos = firstPosition + childCount - 1;
5509
5510 for (int i = childCount - 1; i >= 0; i--) {
5511 final View v = getChildAt(i);
5512 final int top = v.getTop();
5513 final int bottom = v.getBottom();
5514
5515 if (i == childCount - 1) {
5516 selectedTop = top;
5517 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5518 childrenBottom -= getVerticalFadingEdgeLength();
5519 }
5520 }
5521
5522 if (bottom <= childrenBottom) {
5523 selectedPos = firstPosition + i;
5524 selectedTop = top;
5525 break;
5526 }
5527 }
5528 }
5529 }
5530
5531 mResurrectToPosition = INVALID_POSITION;
5532 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005533 if (mPositionScroller != null) {
5534 mPositionScroller.stop();
5535 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005536 mTouchMode = TOUCH_MODE_REST;
5537 clearScrollingCache();
5538 mSpecificTop = selectedTop;
5539 selectedPos = lookForSelectablePosition(selectedPos, down);
5540 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5541 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005542 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005543 setSelectionInt(selectedPos);
5544 invokeOnItemScrollListener();
5545 } else {
5546 selectedPos = INVALID_POSITION;
5547 }
5548 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5549
5550 return selectedPos >= 0;
5551 }
5552
Adam Powell14c08042011-10-06 19:46:18 -07005553 void confirmCheckedPositionsById() {
5554 // Clear out the positional check states, we'll rebuild it below from IDs.
5555 mCheckStates.clear();
5556
5557 boolean checkedCountChanged = false;
5558 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5559 final long id = mCheckedIdStates.keyAt(checkedIndex);
5560 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5561
5562 final long lastPosId = mAdapter.getItemId(lastPos);
5563 if (id != lastPosId) {
5564 // Look around to see if the ID is nearby. If not, uncheck it.
5565 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5566 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5567 boolean found = false;
5568 for (int searchPos = start; searchPos < end; searchPos++) {
5569 final long searchId = mAdapter.getItemId(searchPos);
5570 if (id == searchId) {
5571 found = true;
5572 mCheckStates.put(searchPos, true);
5573 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5574 break;
5575 }
5576 }
5577
5578 if (!found) {
5579 mCheckedIdStates.delete(id);
5580 checkedIndex--;
5581 mCheckedItemCount--;
5582 checkedCountChanged = true;
5583 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5584 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5585 lastPos, id, false);
5586 }
5587 }
5588 } else {
5589 mCheckStates.put(lastPos, true);
5590 }
5591 }
5592
5593 if (checkedCountChanged && mChoiceActionMode != null) {
5594 mChoiceActionMode.invalidate();
5595 }
5596 }
5597
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005598 @Override
5599 protected void handleDataChanged() {
5600 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005601 int lastHandledItemCount = mLastHandledItemCount;
5602 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005603
5604 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5605 confirmCheckedPositionsById();
5606 }
5607
Adam Powell539ee872012-02-03 19:00:49 -08005608 // TODO: In the future we can recycle these views based on stable ID instead.
5609 mRecycler.clearTransientStateViews();
5610
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005611 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005612 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005613 int selectablePos;
5614
5615 // Find the row we are supposed to sync to
5616 if (mNeedSync) {
5617 // Update this first, since setNextSelectedPositionInt inspects it
5618 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005619 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005620
Adam Powell07852792010-11-10 16:57:05 -08005621 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005622 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5623 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005624 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5625 if (mForceTranscriptScroll) {
5626 mForceTranscriptScroll = false;
5627 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5628 return;
5629 }
Adam Powell07852792010-11-10 16:57:05 -08005630 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005631 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005632 final View lastChild = getChildAt(childCount - 1);
5633 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005634 if (mFirstPosition + childCount >= lastHandledItemCount &&
5635 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005636 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5637 return;
5638 }
5639 // Something new came in and we didn't scroll; give the user a clue that
5640 // there's something new.
5641 awakenScrollBars();
5642 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005643
5644 switch (mSyncMode) {
5645 case SYNC_SELECTED_POSITION:
5646 if (isInTouchMode()) {
5647 // We saved our state when not in touch mode. (We know this because
5648 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5649 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5650 // adjusting if the available range changed) and return.
5651 mLayoutMode = LAYOUT_SYNC;
5652 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5653
5654 return;
5655 } else {
5656 // See if we can find a position in the new data with the same
5657 // id as the old selection. This will change mSyncPosition.
5658 newPos = findSyncPosition();
5659 if (newPos >= 0) {
5660 // Found it. Now verify that new selection is still selectable
5661 selectablePos = lookForSelectablePosition(newPos, true);
5662 if (selectablePos == newPos) {
5663 // Same row id is selected
5664 mSyncPosition = newPos;
5665
5666 if (mSyncHeight == getHeight()) {
5667 // If we are at the same height as when we saved state, try
5668 // to restore the scroll position too.
5669 mLayoutMode = LAYOUT_SYNC;
5670 } else {
5671 // We are not the same height as when the selection was saved, so
5672 // don't try to restore the exact position
5673 mLayoutMode = LAYOUT_SET_SELECTION;
5674 }
5675
5676 // Restore selection
5677 setNextSelectedPositionInt(newPos);
5678 return;
5679 }
5680 }
5681 }
5682 break;
5683 case SYNC_FIRST_POSITION:
5684 // Leave mSyncPosition as it is -- just pin to available range
5685 mLayoutMode = LAYOUT_SYNC;
5686 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5687
5688 return;
5689 }
5690 }
5691
5692 if (!isInTouchMode()) {
5693 // We couldn't find matching data -- try to use the same position
5694 newPos = getSelectedItemPosition();
5695
5696 // Pin position to the available range
5697 if (newPos >= count) {
5698 newPos = count - 1;
5699 }
5700 if (newPos < 0) {
5701 newPos = 0;
5702 }
5703
5704 // Make sure we select something selectable -- first look down
5705 selectablePos = lookForSelectablePosition(newPos, true);
5706
5707 if (selectablePos >= 0) {
5708 setNextSelectedPositionInt(selectablePos);
5709 return;
5710 } else {
5711 // Looking down didn't work -- try looking up
5712 selectablePos = lookForSelectablePosition(newPos, false);
5713 if (selectablePos >= 0) {
5714 setNextSelectedPositionInt(selectablePos);
5715 return;
5716 }
5717 }
5718 } else {
5719
5720 // We already know where we want to resurrect the selection
5721 if (mResurrectToPosition >= 0) {
5722 return;
5723 }
5724 }
5725
5726 }
5727
5728 // Nothing is selected. Give up and reset everything.
5729 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5730 mSelectedPosition = INVALID_POSITION;
5731 mSelectedRowId = INVALID_ROW_ID;
5732 mNextSelectedPosition = INVALID_POSITION;
5733 mNextSelectedRowId = INVALID_ROW_ID;
5734 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005735 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005736 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005737 checkSelectionChanged();
5738 }
5739
Romain Guy43c9cdf2010-01-27 13:53:55 -08005740 @Override
5741 protected void onDisplayHint(int hint) {
5742 super.onDisplayHint(hint);
5743 switch (hint) {
5744 case INVISIBLE:
5745 if (mPopup != null && mPopup.isShowing()) {
5746 dismissPopup();
5747 }
5748 break;
5749 case VISIBLE:
5750 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5751 showPopup();
5752 }
5753 break;
5754 }
Romain Guy24562482010-02-01 14:56:19 -08005755 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005756 }
5757
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005758 /**
5759 * Removes the filter window
5760 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005761 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005762 if (mPopup != null) {
5763 mPopup.dismiss();
5764 }
5765 }
5766
5767 /**
5768 * Shows the filter window
5769 */
5770 private void showPopup() {
5771 // Make sure we have a window before showing the popup
5772 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005773 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005774 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005775 // Make sure we get focus if we are showing the popup
5776 checkFocus();
5777 }
5778 }
5779
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005780 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005781 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5782 final int[] xy = new int[2];
5783 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005784 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005785 // TODO: And the gravity should be defined in the theme as well
5786 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005787 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005788 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5789 xy[0], bottomGap);
5790 } else {
5791 mPopup.update(xy[0], bottomGap, -1, -1);
5792 }
5793 }
5794
5795 /**
5796 * What is the distance between the source and destination rectangles given the direction of
5797 * focus navigation between them? The direction basically helps figure out more quickly what is
5798 * self evident by the relationship between the rects...
5799 *
5800 * @param source the source rectangle
5801 * @param dest the destination rectangle
5802 * @param direction the direction
5803 * @return the distance between the rectangles
5804 */
5805 static int getDistance(Rect source, Rect dest, int direction) {
5806 int sX, sY; // source x, y
5807 int dX, dY; // dest x, y
5808 switch (direction) {
5809 case View.FOCUS_RIGHT:
5810 sX = source.right;
5811 sY = source.top + source.height() / 2;
5812 dX = dest.left;
5813 dY = dest.top + dest.height() / 2;
5814 break;
5815 case View.FOCUS_DOWN:
5816 sX = source.left + source.width() / 2;
5817 sY = source.bottom;
5818 dX = dest.left + dest.width() / 2;
5819 dY = dest.top;
5820 break;
5821 case View.FOCUS_LEFT:
5822 sX = source.left;
5823 sY = source.top + source.height() / 2;
5824 dX = dest.right;
5825 dY = dest.top + dest.height() / 2;
5826 break;
5827 case View.FOCUS_UP:
5828 sX = source.left + source.width() / 2;
5829 sY = source.top;
5830 dX = dest.left + dest.width() / 2;
5831 dY = dest.bottom;
5832 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005833 case View.FOCUS_FORWARD:
5834 case View.FOCUS_BACKWARD:
5835 sX = source.right + source.width() / 2;
5836 sY = source.top + source.height() / 2;
5837 dX = dest.left + dest.width() / 2;
5838 dY = dest.top + dest.height() / 2;
5839 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005840 default:
5841 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005842 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5843 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005844 }
5845 int deltaX = dX - sX;
5846 int deltaY = dY - sY;
5847 return deltaY * deltaY + deltaX * deltaX;
5848 }
5849
5850 @Override
5851 protected boolean isInFilterMode() {
5852 return mFiltered;
5853 }
5854
5855 /**
5856 * Sends a key to the text filter window
5857 *
5858 * @param keyCode The keycode for the event
5859 * @param event The actual key event
5860 *
5861 * @return True if the text filter handled the event, false otherwise.
5862 */
5863 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005864 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005865 return false;
5866 }
5867
5868 boolean handled = false;
5869 boolean okToSend = true;
5870 switch (keyCode) {
5871 case KeyEvent.KEYCODE_DPAD_UP:
5872 case KeyEvent.KEYCODE_DPAD_DOWN:
5873 case KeyEvent.KEYCODE_DPAD_LEFT:
5874 case KeyEvent.KEYCODE_DPAD_RIGHT:
5875 case KeyEvent.KEYCODE_DPAD_CENTER:
5876 case KeyEvent.KEYCODE_ENTER:
5877 okToSend = false;
5878 break;
5879 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005880 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005881 if (event.getAction() == KeyEvent.ACTION_DOWN
5882 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005883 KeyEvent.DispatcherState state = getKeyDispatcherState();
5884 if (state != null) {
5885 state.startTracking(event, this);
5886 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005887 handled = true;
5888 } else if (event.getAction() == KeyEvent.ACTION_UP
5889 && event.isTracking() && !event.isCanceled()) {
5890 handled = true;
5891 mTextFilter.setText("");
5892 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005893 }
5894 okToSend = false;
5895 break;
5896 case KeyEvent.KEYCODE_SPACE:
5897 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005898 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005899 break;
5900 }
5901
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005902 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005903 createTextFilter(true);
5904
5905 KeyEvent forwardEvent = event;
5906 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005907 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005908 }
5909
5910 int action = event.getAction();
5911 switch (action) {
5912 case KeyEvent.ACTION_DOWN:
5913 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5914 break;
5915
5916 case KeyEvent.ACTION_UP:
5917 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5918 break;
5919
5920 case KeyEvent.ACTION_MULTIPLE:
5921 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5922 break;
5923 }
5924 }
5925 return handled;
5926 }
5927
5928 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005929 * Return an InputConnection for editing of the filter text.
5930 */
5931 @Override
5932 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005933 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005934 if (mPublicInputConnection == null) {
5935 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005936 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005937 }
Romain Guyf6991302013-06-05 17:19:01 -07005938 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005939 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5940 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005941 }
5942 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005943 }
Romain Guy0a637162009-05-29 14:43:54 -07005944
Romain Guyf6991302013-06-05 17:19:01 -07005945 private class InputConnectionWrapper implements InputConnection {
5946 private final EditorInfo mOutAttrs;
5947 private InputConnection mTarget;
5948
5949 public InputConnectionWrapper(EditorInfo outAttrs) {
5950 mOutAttrs = outAttrs;
5951 }
5952
5953 private InputConnection getTarget() {
5954 if (mTarget == null) {
5955 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5956 }
5957 return mTarget;
5958 }
5959
5960 @Override
5961 public boolean reportFullscreenMode(boolean enabled) {
5962 // Use our own input connection, since it is
5963 // the "real" one the IME is talking with.
5964 return mDefInputConnection.reportFullscreenMode(enabled);
5965 }
5966
5967 @Override
5968 public boolean performEditorAction(int editorAction) {
5969 // The editor is off in its own window; we need to be
5970 // the one that does this.
5971 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005972 InputMethodManager imm =
5973 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005974 if (imm != null) {
5975 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5976 }
5977 return true;
5978 }
5979 return false;
5980 }
5981
5982 @Override
5983 public boolean sendKeyEvent(KeyEvent event) {
5984 // Use our own input connection, since the filter
5985 // text view may not be shown in a window so has
5986 // no ViewAncestor to dispatch events with.
5987 return mDefInputConnection.sendKeyEvent(event);
5988 }
5989
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005990 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005991 public CharSequence getTextBeforeCursor(int n, int flags) {
5992 if (mTarget == null) return "";
5993 return mTarget.getTextBeforeCursor(n, flags);
5994 }
5995
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005996 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005997 public CharSequence getTextAfterCursor(int n, int flags) {
5998 if (mTarget == null) return "";
5999 return mTarget.getTextAfterCursor(n, flags);
6000 }
6001
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006002 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006003 public CharSequence getSelectedText(int flags) {
6004 if (mTarget == null) return "";
6005 return mTarget.getSelectedText(flags);
6006 }
6007
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006008 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006009 public int getCursorCapsMode(int reqModes) {
6010 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
6011 return mTarget.getCursorCapsMode(reqModes);
6012 }
6013
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006014 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006015 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
6016 return getTarget().getExtractedText(request, flags);
6017 }
6018
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006019 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006020 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
6021 return getTarget().deleteSurroundingText(beforeLength, afterLength);
6022 }
6023
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006024 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08006025 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
6026 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
6027 }
6028
6029 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006030 public boolean setComposingText(CharSequence text, int newCursorPosition) {
6031 return getTarget().setComposingText(text, newCursorPosition);
6032 }
6033
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006034 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006035 public boolean setComposingRegion(int start, int end) {
6036 return getTarget().setComposingRegion(start, end);
6037 }
6038
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006039 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006040 public boolean finishComposingText() {
6041 return mTarget == null || mTarget.finishComposingText();
6042 }
6043
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006044 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006045 public boolean commitText(CharSequence text, int newCursorPosition) {
6046 return getTarget().commitText(text, newCursorPosition);
6047 }
6048
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006049 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006050 public boolean commitCompletion(CompletionInfo text) {
6051 return getTarget().commitCompletion(text);
6052 }
6053
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006054 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006055 public boolean commitCorrection(CorrectionInfo correctionInfo) {
6056 return getTarget().commitCorrection(correctionInfo);
6057 }
6058
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006059 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006060 public boolean setSelection(int start, int end) {
6061 return getTarget().setSelection(start, end);
6062 }
6063
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006064 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006065 public boolean performContextMenuAction(int id) {
6066 return getTarget().performContextMenuAction(id);
6067 }
6068
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006069 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006070 public boolean beginBatchEdit() {
6071 return getTarget().beginBatchEdit();
6072 }
6073
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006074 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006075 public boolean endBatchEdit() {
6076 return getTarget().endBatchEdit();
6077 }
6078
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006079 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006080 public boolean clearMetaKeyStates(int states) {
6081 return getTarget().clearMetaKeyStates(states);
6082 }
6083
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006084 @Override
Romain Guyf6991302013-06-05 17:19:01 -07006085 public boolean performPrivateCommand(String action, Bundle data) {
6086 return getTarget().performPrivateCommand(action, data);
6087 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09006088
6089 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07006090 public boolean requestCursorUpdates(int cursorUpdateMode) {
6091 return getTarget().requestCursorUpdates(cursorUpdateMode);
6092 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08006093
6094 @Override
6095 public Handler getHandler() {
6096 return getTarget().getHandler();
6097 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07006098
6099 @Override
6100 public void closeConnection() {
6101 getTarget().closeConnection();
6102 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07006103
6104 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07006105 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6106 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07006107 }
Romain Guyf6991302013-06-05 17:19:01 -07006108 }
6109
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006110 /**
6111 * For filtering we proxy an input connection to an internal text editor,
6112 * and this allows the proxying to happen.
6113 */
6114 @Override
6115 public boolean checkInputConnectionProxy(View view) {
6116 return view == mTextFilter;
6117 }
Romain Guy0a637162009-05-29 14:43:54 -07006118
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006119 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006120 * Creates the window for the text filter and populates it with an EditText field;
6121 *
6122 * @param animateEntrance true if the window should appear with an animation
6123 */
6124 private void createTextFilter(boolean animateEntrance) {
6125 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006126 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006127 p.setFocusable(false);
6128 p.setTouchable(false);
6129 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006130 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006131 p.setWidth(LayoutParams.WRAP_CONTENT);
6132 p.setHeight(LayoutParams.WRAP_CONTENT);
6133 p.setBackgroundDrawable(null);
6134 mPopup = p;
6135 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006136 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006137 }
6138 if (animateEntrance) {
6139 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6140 } else {
6141 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6142 }
6143 }
6144
Romain Guyf6991302013-06-05 17:19:01 -07006145 private EditText getTextFilterInput() {
6146 if (mTextFilter == null) {
6147 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6148 mTextFilter = (EditText) layoutInflater.inflate(
6149 com.android.internal.R.layout.typing_filter, null);
6150 // For some reason setting this as the "real" input type changes
6151 // the text view in some way that it doesn't work, and I don't
6152 // want to figure out why this is.
6153 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6154 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6155 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6156 mTextFilter.addTextChangedListener(this);
6157 }
6158 return mTextFilter;
6159 }
6160
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006161 /**
6162 * Clear the text filter.
6163 */
6164 public void clearTextFilter() {
6165 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006166 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006167 mFiltered = false;
6168 if (mPopup != null && mPopup.isShowing()) {
6169 dismissPopup();
6170 }
6171 }
6172 }
6173
6174 /**
6175 * Returns if the ListView currently has a text filter.
6176 */
6177 public boolean hasTextFilter() {
6178 return mFiltered;
6179 }
6180
Alan Viverette8fa327a2013-05-31 14:53:13 -07006181 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006182 public void onGlobalLayout() {
6183 if (isShown()) {
6184 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006185 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006186 showPopup();
6187 }
6188 } else {
6189 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006190 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006191 dismissPopup();
6192 }
6193 }
6194
6195 }
6196
6197 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006198 * For our text watcher that is associated with the text filter. Does
6199 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006200 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006201 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006202 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6203 }
6204
6205 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006206 * For our text watcher that is associated with the text filter. Performs
6207 * the actual filtering as the text changes, and takes care of hiding and
6208 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006209 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006210 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006211 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006212 if (isTextFilterEnabled()) {
6213 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006214 int length = s.length();
6215 boolean showing = mPopup.isShowing();
6216 if (!showing && length > 0) {
6217 // Show the filter popup if necessary
6218 showPopup();
6219 mFiltered = true;
6220 } else if (showing && length == 0) {
6221 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006222 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006223 mFiltered = false;
6224 }
6225 if (mAdapter instanceof Filterable) {
6226 Filter f = ((Filterable) mAdapter).getFilter();
6227 // Filter should not be null when we reach this part
6228 if (f != null) {
6229 f.filter(s, this);
6230 } else {
6231 throw new IllegalStateException("You cannot call onTextChanged with a non "
6232 + "filterable adapter");
6233 }
6234 }
6235 }
6236 }
6237
6238 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006239 * For our text watcher that is associated with the text filter. Does
6240 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006241 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006242 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006243 public void afterTextChanged(Editable s) {
6244 }
6245
Alan Viverette8fa327a2013-05-31 14:53:13 -07006246 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006247 public void onFilterComplete(int count) {
6248 if (mSelectedPosition < 0 && count > 0) {
6249 mResurrectToPosition = INVALID_POSITION;
6250 resurrectSelection();
6251 }
6252 }
6253
6254 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006255 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6256 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6257 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6258 }
6259
6260 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006261 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6262 return new LayoutParams(p);
6263 }
6264
6265 @Override
6266 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6267 return new AbsListView.LayoutParams(getContext(), attrs);
6268 }
6269
6270 @Override
6271 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6272 return p instanceof AbsListView.LayoutParams;
6273 }
6274
6275 /**
6276 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6277 * to the bottom to show new items.
6278 *
6279 * @param mode the transcript mode to set
6280 *
6281 * @see #TRANSCRIPT_MODE_DISABLED
6282 * @see #TRANSCRIPT_MODE_NORMAL
6283 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6284 */
6285 public void setTranscriptMode(int mode) {
6286 mTranscriptMode = mode;
6287 }
6288
6289 /**
6290 * Returns the current transcript mode.
6291 *
6292 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6293 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6294 */
Ashley Rose55f9f922019-01-28 19:29:36 -05006295 @InspectableProperty(enumMapping = {
6296 @EnumMap(value = TRANSCRIPT_MODE_DISABLED, name = "disabled"),
6297 @EnumMap(value = TRANSCRIPT_MODE_NORMAL, name = "normal"),
6298 @EnumMap(value = TRANSCRIPT_MODE_ALWAYS_SCROLL, name = "alwaysScroll")
6299 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006300 public int getTranscriptMode() {
6301 return mTranscriptMode;
6302 }
6303
6304 @Override
6305 public int getSolidColor() {
6306 return mCacheColorHint;
6307 }
6308
6309 /**
6310 * 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 -07006311 * on top of a solid, single-color, opaque background.
6312 *
6313 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6314 * single color. This hint will not affect any existing background drawable set on this view (
6315 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006316 *
6317 * @param color The background color
6318 */
Tor Norbye80756e32015-03-02 09:39:27 -08006319 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006320 if (color != mCacheColorHint) {
6321 mCacheColorHint = color;
6322 int count = getChildCount();
6323 for (int i = 0; i < count; i++) {
6324 getChildAt(i).setDrawingCacheBackgroundColor(color);
6325 }
6326 mRecycler.setCacheColorHint(color);
6327 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006328 }
6329
6330 /**
6331 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6332 * on top of a solid, single-color, opaque background
6333 *
6334 * @return The cache color hint
6335 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006336 @ViewDebug.ExportedProperty(category = "drawing")
Ashley Rose55f9f922019-01-28 19:29:36 -05006337 @InspectableProperty
Tor Norbye80756e32015-03-02 09:39:27 -08006338 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006339 public int getCacheColorHint() {
6340 return mCacheColorHint;
6341 }
6342
6343 /**
6344 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6345 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6346 * internal view recycler.
6347 *
6348 * @param views A list into which to put the reclaimed views
6349 */
6350 public void reclaimViews(List<View> views) {
6351 int childCount = getChildCount();
6352 RecyclerListener listener = mRecycler.mRecyclerListener;
6353
6354 // Reclaim views on screen
6355 for (int i = 0; i < childCount; i++) {
6356 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006357 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006358 // Don't reclaim header or footer views, or views that should be ignored
6359 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6360 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006361 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006362 if (listener != null) {
6363 // Pretend they went through the scrap heap
6364 listener.onMovedToScrapHeap(child);
6365 }
6366 }
6367 }
6368 mRecycler.reclaimScrapViews(views);
6369 removeAllViewsInLayout();
6370 }
6371
Adam Powell637d3372010-08-25 14:37:03 -07006372 private void finishGlows() {
Yigit Boyarb6218472019-02-06 10:07:06 -08006373 if (shouldDisplayEdgeEffects()) {
Adam Powell637d3372010-08-25 14:37:03 -07006374 mEdgeGlowTop.finish();
6375 mEdgeGlowBottom.finish();
6376 }
6377 }
6378
Romain Guy13922e02009-05-12 17:56:14 -07006379 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006380 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6381 * through the specified intent.
6382 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6383 */
6384 public void setRemoteViewsAdapter(Intent intent) {
Sunny Goyal5c022632016-02-17 16:30:41 -08006385 setRemoteViewsAdapter(intent, false);
6386 }
6387
6388 /** @hide **/
6389 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6390 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6391 }
6392
6393 /** @hide **/
6394 @Override
6395 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006396 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6397 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006398 if (mRemoteAdapter != null) {
6399 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6400 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6401 mRemoteAdapter.getRemoteViewsServiceIntent());
6402 if (fcNew.equals(fcOld)) {
6403 return;
6404 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006405 }
Adam Cohen2148d432011-07-28 14:59:54 -07006406 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006407 // Otherwise, create a new RemoteViewsAdapter for binding
Sunny Goyal5c022632016-02-17 16:30:41 -08006408 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
Adam Cohen335c3b62012-07-24 17:18:16 -07006409 if (mRemoteAdapter.isDataReady()) {
6410 setAdapter(mRemoteAdapter);
6411 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006412 }
6413
6414 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006415 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006416 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006417 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006418 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006419 * @hide
6420 */
6421 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6422 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6423 // service handling the specified intent.
6424 if (mRemoteAdapter != null) {
6425 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6426 }
6427 }
6428
6429 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006430 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6431 * connected yet.
6432 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006433 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006434 public void deferNotifyDataSetChanged() {
6435 mDeferNotifyDataSetChanged = true;
6436 }
6437
6438 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006439 * Called back when the adapter connects to the RemoteViewsService.
6440 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006441 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006442 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006443 if (mRemoteAdapter != mAdapter) {
6444 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006445 if (mDeferNotifyDataSetChanged) {
6446 mRemoteAdapter.notifyDataSetChanged();
6447 mDeferNotifyDataSetChanged = false;
6448 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006449 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006450 } else if (mRemoteAdapter != null) {
6451 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006452 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006453 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006454 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006455 }
6456
6457 /**
6458 * Called back when the adapter disconnects from the RemoteViewsService.
6459 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006460 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006461 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006462 // If the remote adapter disconnects, we keep it around
6463 // since the currently displayed items are still cached.
6464 // Further, we want the service to eventually reconnect
6465 // when necessary, as triggered by this view requesting
6466 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006467 }
6468
6469 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006470 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6471 * being displayed by the AbsListView.
6472 */
6473 void setVisibleRangeHint(int start, int end) {
6474 if (mRemoteAdapter != null) {
6475 mRemoteAdapter.setVisibleRangeHint(start, end);
6476 }
6477 }
6478
6479 /**
Yigit Boyarb6218472019-02-06 10:07:06 -08006480 * Sets the edge effect color for both top and bottom edge effects.
6481 *
6482 * @param color The color for the edge effects.
6483 * @see #setTopEdgeEffectColor(int)
6484 * @see #setBottomEdgeEffectColor(int)
6485 * @see #getTopEdgeEffectColor()
6486 * @see #getBottomEdgeEffectColor()
6487 */
6488 public void setEdgeEffectColor(@ColorInt int color) {
6489 setTopEdgeEffectColor(color);
6490 setBottomEdgeEffectColor(color);
6491 }
6492
6493 /**
6494 * Sets the bottom edge effect color.
6495 *
6496 * @param color The color for the bottom edge effect.
6497 * @see #setTopEdgeEffectColor(int)
6498 * @see #setEdgeEffectColor(int)
6499 * @see #getTopEdgeEffectColor()
6500 * @see #getBottomEdgeEffectColor()
6501 */
6502 public void setBottomEdgeEffectColor(@ColorInt int color) {
6503 mEdgeGlowBottom.setColor(color);
6504 invalidateBottomGlow();
6505 }
6506
6507 /**
6508 * Sets the top edge effect color.
6509 *
6510 * @param color The color for the top edge effect.
6511 * @see #setBottomEdgeEffectColor(int)
6512 * @see #setEdgeEffectColor(int)
6513 * @see #getTopEdgeEffectColor()
6514 * @see #getBottomEdgeEffectColor()
6515 */
6516 public void setTopEdgeEffectColor(@ColorInt int color) {
6517 mEdgeGlowTop.setColor(color);
6518 invalidateTopGlow();
6519 }
6520
6521 /**
6522 * Returns the top edge effect color.
6523 *
6524 * @return The top edge effect color.
6525 * @see #setEdgeEffectColor(int)
6526 * @see #setTopEdgeEffectColor(int)
6527 * @see #setBottomEdgeEffectColor(int)
6528 * @see #getBottomEdgeEffectColor()
6529 */
6530 @ColorInt
6531 public int getTopEdgeEffectColor() {
6532 return mEdgeGlowTop.getColor();
6533 }
6534
6535 /**
6536 * Returns the bottom edge effect color.
6537 *
6538 * @return The bottom edge effect color.
6539 * @see #setEdgeEffectColor(int)
6540 * @see #setTopEdgeEffectColor(int)
6541 * @see #setBottomEdgeEffectColor(int)
6542 * @see #getTopEdgeEffectColor()
6543 */
6544 @ColorInt
6545 public int getBottomEdgeEffectColor() {
6546 return mEdgeGlowBottom.getColor();
6547 }
6548
6549 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006550 * Sets the recycler listener to be notified whenever a View is set aside in
6551 * the recycler for later reuse. This listener can be used to free resources
6552 * associated to the View.
6553 *
6554 * @param listener The recycler listener to be notified of views set aside
6555 * in the recycler.
6556 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006557 * @see android.widget.AbsListView.RecyclerListener
6558 */
6559 public void setRecyclerListener(RecyclerListener listener) {
6560 mRecycler.mRecyclerListener = listener;
6561 }
6562
Adam Powellb1f498a2011-01-18 20:43:23 -08006563 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6564 @Override
6565 public void onChanged() {
6566 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006567 if (mFastScroll != null) {
6568 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006569 }
6570 }
6571
6572 @Override
6573 public void onInvalidated() {
6574 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006575 if (mFastScroll != null) {
6576 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006577 }
6578 }
6579 }
6580
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006581 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006582 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6583 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6584 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6585 * selects and deselects list items.
6586 */
6587 public interface MultiChoiceModeListener extends ActionMode.Callback {
6588 /**
6589 * Called when an item is checked or unchecked during selection mode.
6590 *
6591 * @param mode The {@link ActionMode} providing the selection mode
6592 * @param position Adapter position of the item that was checked or unchecked
6593 * @param id Adapter ID of the item that was checked or unchecked
6594 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6595 * if the item is now unchecked.
6596 */
6597 public void onItemCheckedStateChanged(ActionMode mode,
6598 int position, long id, boolean checked);
6599 }
6600
6601 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6602 private MultiChoiceModeListener mWrapped;
6603
6604 public void setWrapped(MultiChoiceModeListener wrapped) {
6605 mWrapped = wrapped;
6606 }
6607
Adam Powella7981702012-08-24 12:43:41 -07006608 public boolean hasWrappedCallback() {
6609 return mWrapped != null;
6610 }
6611
Alan Viverette8fa327a2013-05-31 14:53:13 -07006612 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006613 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6614 if (mWrapped.onCreateActionMode(mode, menu)) {
6615 // Initialize checked graphic state?
6616 setLongClickable(false);
6617 return true;
6618 }
6619 return false;
6620 }
6621
Alan Viverette8fa327a2013-05-31 14:53:13 -07006622 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006623 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6624 return mWrapped.onPrepareActionMode(mode, menu);
6625 }
6626
Alan Viverette8fa327a2013-05-31 14:53:13 -07006627 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006628 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6629 return mWrapped.onActionItemClicked(mode, item);
6630 }
6631
Alan Viverette8fa327a2013-05-31 14:53:13 -07006632 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006633 public void onDestroyActionMode(ActionMode mode) {
6634 mWrapped.onDestroyActionMode(mode);
6635 mChoiceActionMode = null;
6636
6637 // Ending selection mode means deselecting everything.
6638 clearChoices();
6639
6640 mDataChanged = true;
6641 rememberSyncState();
6642 requestLayout();
6643
6644 setLongClickable(true);
6645 }
6646
Alan Viverette8fa327a2013-05-31 14:53:13 -07006647 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006648 public void onItemCheckedStateChanged(ActionMode mode,
6649 int position, long id, boolean checked) {
6650 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6651
6652 // If there are no items selected we no longer need the selection mode.
6653 if (getCheckedItemCount() == 0) {
6654 mode.finish();
6655 }
6656 }
6657 }
6658
6659 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006660 * AbsListView extends LayoutParams to provide a place to hold the view type.
6661 */
6662 public static class LayoutParams extends ViewGroup.LayoutParams {
6663 /**
6664 * View type for this view, as returned by
6665 * {@link android.widget.Adapter#getItemViewType(int) }
6666 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006667 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006668 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6669 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6670 })
Mathew Inwood31755f92018-12-20 13:53:36 +00006671 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006672 int viewType;
6673
The Android Open Source Project4df24232009-03-05 14:34:35 -08006674 /**
6675 * When this boolean is set, the view has been added to the AbsListView
6676 * at least once. It is used to know whether headers/footers have already
6677 * been added to the list view and whether they should be treated as
6678 * recycled views or not.
6679 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006680 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006681 boolean recycledHeaderFooter;
6682
Romain Guy0bf88592010-03-02 13:38:44 -08006683 /**
6684 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6685 * to obtain children views to measure itself. When doing so, the children
6686 * are not attached to the window, but put in the recycler which assumes
6687 * they've been attached before. Setting this flag will force the reused
6688 * view to be attached to the window rather than just attached to the
6689 * parent.
6690 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006691 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006692 boolean forceAdd;
6693
Dianne Hackborn079e2352010-10-18 17:02:43 -07006694 /**
6695 * The position the view was removed from when pulled out of the
6696 * scrap heap.
6697 * @hide
6698 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01006699 @UnsupportedAppUsage
Dianne Hackborn079e2352010-10-18 17:02:43 -07006700 int scrappedFromPosition;
6701
Adam Powell539ee872012-02-03 19:00:49 -08006702 /**
6703 * The ID the view represents
6704 */
6705 long itemId = -1;
6706
Alan Viverette92539d52015-09-14 10:49:25 -04006707 /** Whether the adapter considers the item enabled. */
6708 boolean isEnabled;
6709
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006710 public LayoutParams(Context c, AttributeSet attrs) {
6711 super(c, attrs);
6712 }
6713
6714 public LayoutParams(int w, int h) {
6715 super(w, h);
6716 }
6717
6718 public LayoutParams(int w, int h, int viewType) {
6719 super(w, h);
6720 this.viewType = viewType;
6721 }
6722
6723 public LayoutParams(ViewGroup.LayoutParams source) {
6724 super(source);
6725 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006726
6727 /** @hide */
6728 @Override
6729 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6730 super.encodeProperties(encoder);
6731
6732 encoder.addProperty("list:viewType", viewType);
6733 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6734 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006735 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006736 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006737 }
6738
6739 /**
6740 * A RecyclerListener is used to receive a notification whenever a View is placed
6741 * inside the RecycleBin's scrap heap. This listener is used to free resources
6742 * associated to Views placed in the RecycleBin.
6743 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006744 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6745 */
6746 public static interface RecyclerListener {
6747 /**
6748 * Indicates that the specified View was moved into the recycler's scrap heap.
6749 * The view is not displayed on screen any more and any expensive resource
6750 * associated with the view should be discarded.
6751 *
6752 * @param view
6753 */
6754 void onMovedToScrapHeap(View view);
6755 }
6756
6757 /**
6758 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6759 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6760 * start of a layout. By construction, they are displaying current information. At the end of
6761 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6762 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6763 *
6764 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6765 * @see android.widget.AbsListView.RecyclerListener
6766 */
6767 class RecycleBin {
Mathew Inwood978c6e22018-08-21 15:58:55 +01006768 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006769 private RecyclerListener mRecyclerListener;
6770
6771 /**
6772 * The position of the first view stored in mActiveViews.
6773 */
6774 private int mFirstActivePosition;
6775
6776 /**
6777 * Views that were on screen at the start of layout. This array is populated at the start of
6778 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6779 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6780 * view store in mFirstActivePosition.
6781 */
6782 private View[] mActiveViews = new View[0];
6783
6784 /**
6785 * Unsorted views that can be used by the adapter as a convert view.
6786 */
6787 private ArrayList<View>[] mScrapViews;
6788
6789 private int mViewTypeCount;
6790
6791 private ArrayList<View> mCurrentScrap;
6792
Adam Powell539ee872012-02-03 19:00:49 -08006793 private ArrayList<View> mSkippedScrap;
6794
6795 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006796 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006798 public void setViewTypeCount(int viewTypeCount) {
6799 if (viewTypeCount < 1) {
6800 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6801 }
6802 //noinspection unchecked
6803 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6804 for (int i = 0; i < viewTypeCount; i++) {
6805 scrapViews[i] = new ArrayList<View>();
6806 }
6807 mViewTypeCount = viewTypeCount;
6808 mCurrentScrap = scrapViews[0];
6809 mScrapViews = scrapViews;
6810 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006811
Adam Powellf3c2eda2010-03-16 17:31:01 -07006812 public void markChildrenDirty() {
6813 if (mViewTypeCount == 1) {
6814 final ArrayList<View> scrap = mCurrentScrap;
6815 final int scrapCount = scrap.size();
6816 for (int i = 0; i < scrapCount; i++) {
6817 scrap.get(i).forceLayout();
6818 }
6819 } else {
6820 final int typeCount = mViewTypeCount;
6821 for (int i = 0; i < typeCount; i++) {
6822 final ArrayList<View> scrap = mScrapViews[i];
6823 final int scrapCount = scrap.size();
6824 for (int j = 0; j < scrapCount; j++) {
6825 scrap.get(j).forceLayout();
6826 }
6827 }
6828 }
Adam Powell539ee872012-02-03 19:00:49 -08006829 if (mTransientStateViews != null) {
6830 final int count = mTransientStateViews.size();
6831 for (int i = 0; i < count; i++) {
6832 mTransientStateViews.valueAt(i).forceLayout();
6833 }
6834 }
Chet Haase72871322013-02-26 16:12:13 -07006835 if (mTransientStateViewsById != null) {
6836 final int count = mTransientStateViewsById.size();
6837 for (int i = 0; i < count; i++) {
6838 mTransientStateViewsById.valueAt(i).forceLayout();
6839 }
6840 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006841 }
Romain Guy0a637162009-05-29 14:43:54 -07006842
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006843 public boolean shouldRecycleViewType(int viewType) {
6844 return viewType >= 0;
6845 }
6846
6847 /**
6848 * Clears the scrap heap.
6849 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01006850 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006851 void clear() {
6852 if (mViewTypeCount == 1) {
6853 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006854 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006855 } else {
6856 final int typeCount = mViewTypeCount;
6857 for (int i = 0; i < typeCount; i++) {
6858 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006859 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006860 }
6861 }
Alan Viverette59511502013-12-09 13:49:25 -08006862
6863 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006864 }
6865
6866 /**
6867 * Fill ActiveViews with all of the children of the AbsListView.
6868 *
6869 * @param childCount The minimum number of views mActiveViews should hold
6870 * @param firstActivePosition The position of the first view that will be stored in
6871 * mActiveViews
6872 */
6873 void fillActiveViews(int childCount, int firstActivePosition) {
6874 if (mActiveViews.length < childCount) {
6875 mActiveViews = new View[childCount];
6876 }
6877 mFirstActivePosition = firstActivePosition;
6878
Romain Guyf6991302013-06-05 17:19:01 -07006879 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006880 final View[] activeViews = mActiveViews;
6881 for (int i = 0; i < childCount; i++) {
6882 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006883 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006884 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006885 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006886 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6887 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006888 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006889 // Remember the position so that setupChild() doesn't reset state.
6890 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006891 }
6892 }
6893 }
6894
6895 /**
6896 * Get the view corresponding to the specified position. The view will be removed from
6897 * mActiveViews if it is found.
6898 *
6899 * @param position The position to look up in mActiveViews
6900 * @return The view if it is found, null otherwise
6901 */
6902 View getActiveView(int position) {
6903 int index = position - mFirstActivePosition;
6904 final View[] activeViews = mActiveViews;
6905 if (index >=0 && index < activeViews.length) {
6906 final View match = activeViews[index];
6907 activeViews[index] = null;
6908 return match;
6909 }
6910 return null;
6911 }
6912
Adam Powell539ee872012-02-03 19:00:49 -08006913 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006914 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6915 long id = mAdapter.getItemId(position);
6916 View result = mTransientStateViewsById.get(id);
6917 mTransientStateViewsById.remove(id);
6918 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006919 }
Chet Haase72871322013-02-26 16:12:13 -07006920 if (mTransientStateViews != null) {
6921 final int index = mTransientStateViews.indexOfKey(position);
6922 if (index >= 0) {
6923 View result = mTransientStateViews.valueAt(index);
6924 mTransientStateViews.removeAt(index);
6925 return result;
6926 }
Adam Powell539ee872012-02-03 19:00:49 -08006927 }
Chet Haase72871322013-02-26 16:12:13 -07006928 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006929 }
6930
6931 /**
Alan Viverette59511502013-12-09 13:49:25 -08006932 * Dumps and fully detaches any currently saved views with transient
6933 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006934 */
6935 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006936 final SparseArray<View> viewsByPos = mTransientStateViews;
6937 if (viewsByPos != null) {
6938 final int N = viewsByPos.size();
6939 for (int i = 0; i < N; i++) {
6940 removeDetachedView(viewsByPos.valueAt(i), false);
6941 }
6942 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006943 }
Alan Viverette59511502013-12-09 13:49:25 -08006944
6945 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6946 if (viewsById != null) {
6947 final int N = viewsById.size();
6948 for (int i = 0; i < N; i++) {
6949 removeDetachedView(viewsById.valueAt(i), false);
6950 }
6951 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006952 }
Adam Powell539ee872012-02-03 19:00:49 -08006953 }
6954
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006955 /**
6956 * @return A view from the ScrapViews collection. These are unordered.
6957 */
6958 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006959 final int whichScrap = mAdapter.getItemViewType(position);
6960 if (whichScrap < 0) {
6961 return null;
6962 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006963 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006964 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006965 } else if (whichScrap < mScrapViews.length) {
6966 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006967 }
6968 return null;
6969 }
6970
6971 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006972 * Puts a view into the list of scrap views.
6973 * <p>
6974 * If the list data hasn't changed or the adapter has stable IDs, views
6975 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006976 *
6977 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006978 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006979 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006980 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006981 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006982 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006983 // Can't recycle, but we don't know anything about the view.
6984 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006985 return;
6986 }
6987
Adam Powell539ee872012-02-03 19:00:49 -08006988 lp.scrappedFromPosition = position;
6989
Alan Viverette1e51cc72013-09-27 14:32:20 -07006990 // Remove but don't scrap header or footer views, or views that
6991 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006992 final int viewType = lp.viewType;
6993 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006994 // Can't recycle. If it's not a header or footer, which have
6995 // special handling and should be ignored, then skip the scrap
6996 // heap and we'll fully detach the view later.
6997 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6998 getSkippedScrap().add(scrap);
6999 }
Alan Viveretted44696c2013-07-18 10:37:15 -07007000 return;
7001 }
7002
7003 scrap.dispatchStartTemporaryDetach();
7004
Svetoslavd4bdd6b2013-10-31 17:25:01 -07007005 // The the accessibility state of the view may change while temporary
7006 // detached and we do not allow detached views to fire accessibility
7007 // events. So we are announcing that the subtree changed giving a chance
7008 // to clients holding on to a view in this subtree to refresh it.
Eugene Susla72c510f2018-01-23 21:12:11 +00007009 notifyViewAccessibilityStateChangedIfNeeded(
Svetoslavd4bdd6b2013-10-31 17:25:01 -07007010 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
7011
Alan Viveretted44696c2013-07-18 10:37:15 -07007012 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08007013 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07007014 if (scrapHasTransientState) {
7015 if (mAdapter != null && mAdapterHasStableIds) {
7016 // If the adapter has stable IDs, we can reuse the view for
7017 // the same data.
7018 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07007019 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07007020 }
7021 mTransientStateViewsById.put(lp.itemId, scrap);
7022 } else if (!mDataChanged) {
7023 // If the data hasn't changed, we can reuse the views at
7024 // their old positions.
7025 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07007026 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07007027 }
7028 mTransientStateViews.put(position, scrap);
7029 } else {
7030 // Otherwise, we'll have to remove the view and start over.
Phil Weaverec66fb82017-03-23 12:21:53 -07007031 clearScrapForRebind(scrap);
Alan Viverette8bbae342015-06-25 14:49:29 -07007032 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08007033 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007034 } else {
Phil Weaverec66fb82017-03-23 12:21:53 -07007035 clearScrapForRebind(scrap);
Alan Viveretted44696c2013-07-18 10:37:15 -07007036 if (mViewTypeCount == 1) {
7037 mCurrentScrap.add(scrap);
7038 } else {
7039 mScrapViews[viewType].add(scrap);
7040 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007041
Alan Viveretted44696c2013-07-18 10:37:15 -07007042 if (mRecyclerListener != null) {
7043 mRecyclerListener.onMovedToScrapHeap(scrap);
7044 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007045 }
7046 }
7047
Alan Viverette8bbae342015-06-25 14:49:29 -07007048 private ArrayList<View> getSkippedScrap() {
7049 if (mSkippedScrap == null) {
7050 mSkippedScrap = new ArrayList<>();
7051 }
7052 return mSkippedScrap;
7053 }
7054
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007055 /**
Adam Powell539ee872012-02-03 19:00:49 -08007056 * Finish the removal of any views that skipped the scrap heap.
7057 */
7058 void removeSkippedScrap() {
7059 if (mSkippedScrap == null) {
7060 return;
7061 }
7062 final int count = mSkippedScrap.size();
7063 for (int i = 0; i < count; i++) {
7064 removeDetachedView(mSkippedScrap.get(i), false);
7065 }
7066 mSkippedScrap.clear();
7067 }
7068
7069 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007070 * Move all views remaining in mActiveViews to mScrapViews.
7071 */
7072 void scrapActiveViews() {
7073 final View[] activeViews = mActiveViews;
7074 final boolean hasListener = mRecyclerListener != null;
7075 final boolean multipleScraps = mViewTypeCount > 1;
7076
7077 ArrayList<View> scrapViews = mCurrentScrap;
7078 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08007079 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007080 final View victim = activeViews[i];
7081 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07007082 final AbsListView.LayoutParams lp
7083 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08007084 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007085
7086 activeViews[i] = null;
7087
Alan Viverette59511502013-12-09 13:49:25 -08007088 if (victim.hasTransientState()) {
7089 // Store views with transient state for later use.
7090 victim.dispatchStartTemporaryDetach();
7091
7092 if (mAdapter != null && mAdapterHasStableIds) {
7093 if (mTransientStateViewsById == null) {
7094 mTransientStateViewsById = new LongSparseArray<View>();
7095 }
7096 long id = mAdapter.getItemId(mFirstActivePosition + i);
7097 mTransientStateViewsById.put(id, victim);
7098 } else if (!mDataChanged) {
7099 if (mTransientStateViews == null) {
7100 mTransientStateViews = new SparseArray<View>();
7101 }
7102 mTransientStateViews.put(mFirstActivePosition + i, victim);
7103 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7104 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08007105 removeDetachedView(victim, false);
7106 }
Alan Viverette59511502013-12-09 13:49:25 -08007107 } else if (!shouldRecycleViewType(whichScrap)) {
7108 // Discard non-recyclable views except headers/footers.
7109 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
7110 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08007111 }
Alan Viverette59511502013-12-09 13:49:25 -08007112 } else {
7113 // Store everything else on the appropriate scrap heap.
7114 if (multipleScraps) {
7115 scrapViews = mScrapViews[whichScrap];
7116 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007117
Alan Viverette59511502013-12-09 13:49:25 -08007118 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007119 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08007120 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007121
Alan Viverette59511502013-12-09 13:49:25 -08007122 if (hasListener) {
7123 mRecyclerListener.onMovedToScrapHeap(victim);
7124 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007125 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007126 }
7127 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007128 pruneScrapViews();
7129 }
7130
7131 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007132 * At the end of a layout pass, all temp detached views should either be re-attached or
7133 * completely detached. This method ensures that any remaining view in the scrap list is
7134 * fully detached.
7135 */
7136 void fullyDetachScrapViews() {
7137 final int viewTypeCount = mViewTypeCount;
7138 final ArrayList<View>[] scrapViews = mScrapViews;
7139 for (int i = 0; i < viewTypeCount; ++i) {
7140 final ArrayList<View> scrapPile = scrapViews[i];
7141 for (int j = scrapPile.size() - 1; j >= 0; j--) {
7142 final View view = scrapPile.get(j);
7143 if (view.isTemporarilyDetached()) {
7144 removeDetachedView(view, false);
7145 }
7146 }
7147 }
7148 }
7149
7150 /**
Alan Viverette59511502013-12-09 13:49:25 -08007151 * Makes sure that the size of mScrapViews does not exceed the size of
7152 * mActiveViews, which can happen if an adapter does not recycle its
7153 * views. Removes cached transient state views that no longer have
7154 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007155 */
7156 private void pruneScrapViews() {
7157 final int maxViews = mActiveViews.length;
7158 final int viewTypeCount = mViewTypeCount;
7159 final ArrayList<View>[] scrapViews = mScrapViews;
7160 for (int i = 0; i < viewTypeCount; ++i) {
7161 final ArrayList<View> scrapPile = scrapViews[i];
7162 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07007163 while (size > maxViews) {
7164 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007165 }
7166 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07007167
Alan Viverette59511502013-12-09 13:49:25 -08007168 final SparseArray<View> transViewsByPos = mTransientStateViews;
7169 if (transViewsByPos != null) {
7170 for (int i = 0; i < transViewsByPos.size(); i++) {
7171 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007172 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007173 removeDetachedView(v, false);
7174 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07007175 i--;
7176 }
7177 }
7178 }
Alan Viverette59511502013-12-09 13:49:25 -08007179
7180 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7181 if (transViewsById != null) {
7182 for (int i = 0; i < transViewsById.size(); i++) {
7183 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007184 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08007185 removeDetachedView(v, false);
7186 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07007187 i--;
7188 }
7189 }
7190 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007191 }
7192
7193 /**
7194 * Puts all views in the scrap heap into the supplied list.
7195 */
7196 void reclaimScrapViews(List<View> views) {
7197 if (mViewTypeCount == 1) {
7198 views.addAll(mCurrentScrap);
7199 } else {
7200 final int viewTypeCount = mViewTypeCount;
7201 final ArrayList<View>[] scrapViews = mScrapViews;
7202 for (int i = 0; i < viewTypeCount; ++i) {
7203 final ArrayList<View> scrapPile = scrapViews[i];
7204 views.addAll(scrapPile);
7205 }
7206 }
7207 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007208
7209 /**
7210 * Updates the cache color hint of all known views.
7211 *
7212 * @param color The new cache color hint.
7213 */
7214 void setCacheColorHint(int color) {
7215 if (mViewTypeCount == 1) {
7216 final ArrayList<View> scrap = mCurrentScrap;
7217 final int scrapCount = scrap.size();
7218 for (int i = 0; i < scrapCount; i++) {
7219 scrap.get(i).setDrawingCacheBackgroundColor(color);
7220 }
7221 } else {
7222 final int typeCount = mViewTypeCount;
7223 for (int i = 0; i < typeCount; i++) {
7224 final ArrayList<View> scrap = mScrapViews[i];
7225 final int scrapCount = scrap.size();
7226 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007227 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007228 }
7229 }
7230 }
7231 // Just in case this is called during a layout pass
7232 final View[] activeViews = mActiveViews;
7233 final int count = activeViews.length;
7234 for (int i = 0; i < count; ++i) {
7235 final View victim = activeViews[i];
7236 if (victim != null) {
7237 victim.setDrawingCacheBackgroundColor(color);
7238 }
7239 }
7240 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007241
Alan Viverette3e141622014-02-18 17:05:13 -08007242 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7243 final int size = scrapViews.size();
7244 if (size > 0) {
7245 // See if we still have a view for this position or ID.
Phil Weavere28c03b2017-04-24 13:23:10 -07007246 // Traverse backwards to find the most recently used scrap view
7247 for (int i = size - 1; i >= 0; i--) {
Alan Viverette3e141622014-02-18 17:05:13 -08007248 final View view = scrapViews.get(i);
7249 final AbsListView.LayoutParams params =
7250 (AbsListView.LayoutParams) view.getLayoutParams();
7251
7252 if (mAdapterHasStableIds) {
7253 final long id = mAdapter.getItemId(position);
7254 if (id == params.itemId) {
7255 return scrapViews.remove(i);
7256 }
7257 } else if (params.scrappedFromPosition == position) {
7258 final View scrap = scrapViews.remove(i);
Phil Weaverec66fb82017-03-23 12:21:53 -07007259 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007260 return scrap;
7261 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007262 }
Alan Viverette3e141622014-02-18 17:05:13 -08007263 final View scrap = scrapViews.remove(size - 1);
Phil Weaverec66fb82017-03-23 12:21:53 -07007264 clearScrapForRebind(scrap);
Alan Viverette3e141622014-02-18 17:05:13 -08007265 return scrap;
7266 } else {
7267 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007268 }
Alan Viverette3e141622014-02-18 17:05:13 -08007269 }
7270
7271 private void clearScrap(final ArrayList<View> scrap) {
7272 final int scrapCount = scrap.size();
7273 for (int j = 0; j < scrapCount; j++) {
7274 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7275 }
7276 }
7277
Phil Weaverec66fb82017-03-23 12:21:53 -07007278 private void clearScrapForRebind(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007279 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007280 view.setAccessibilityDelegate(null);
7281 }
7282
7283 private void removeDetachedView(View child, boolean animate) {
7284 child.setAccessibilityDelegate(null);
7285 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007286 }
7287 }
Alan Viverette441b4372014-02-12 13:30:20 -08007288
7289 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007290 * Returns the height of the view for the specified position.
7291 *
7292 * @param position the item position
7293 * @return view height in pixels
7294 */
7295 int getHeightForPosition(int position) {
7296 final int firstVisiblePosition = getFirstVisiblePosition();
7297 final int childCount = getChildCount();
7298 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007299 if (index >= 0 && index < childCount) {
7300 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007301 final View view = getChildAt(index);
7302 return view.getHeight();
7303 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007304 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007305 final View view = obtainView(position, mIsScrap);
7306 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7307 final int height = view.getMeasuredHeight();
7308 mRecycler.addScrapView(view, position);
7309 return height;
7310 }
7311 }
7312
7313 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007314 * Sets the selected item and positions the selection y pixels from the top edge
7315 * of the ListView. (If in touch mode, the item will not be selected but it will
7316 * still be positioned appropriately.)
7317 *
7318 * @param position Index (starting at 0) of the data item to be selected.
7319 * @param y The distance from the top edge of the ListView (plus padding) that the
7320 * item will be positioned.
7321 */
7322 public void setSelectionFromTop(int position, int y) {
7323 if (mAdapter == null) {
7324 return;
7325 }
7326
7327 if (!isInTouchMode()) {
7328 position = lookForSelectablePosition(position, true);
7329 if (position >= 0) {
7330 setNextSelectedPositionInt(position);
7331 }
7332 } else {
7333 mResurrectToPosition = position;
7334 }
7335
7336 if (position >= 0) {
7337 mLayoutMode = LAYOUT_SPECIFIC;
7338 mSpecificTop = mListPadding.top + y;
7339
7340 if (mNeedSync) {
7341 mSyncPosition = position;
7342 mSyncRowId = mAdapter.getItemId(position);
7343 }
7344
7345 if (mPositionScroller != null) {
7346 mPositionScroller.stop();
7347 }
7348 requestLayout();
7349 }
7350 }
7351
Siva Velusamy94a6d152015-05-05 15:07:00 -07007352 /** @hide */
7353 @Override
7354 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7355 super.encodeProperties(encoder);
7356
7357 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7358 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7359 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7360 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7361 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7362 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7363
7364 View selectedView = getSelectedView();
7365 if (selectedView != null) {
7366 encoder.addPropertyKey("selectedView");
7367 selectedView.encode(encoder);
7368 }
7369 }
7370
Alan Viveretted22db212014-02-13 17:47:38 -08007371 /**
7372 * Abstract positon scroller used to handle smooth scrolling.
7373 */
7374 static abstract class AbsPositionScroller {
7375 public abstract void start(int position);
7376 public abstract void start(int position, int boundPosition);
7377 public abstract void startWithOffset(int position, int offset);
7378 public abstract void startWithOffset(int position, int offset, int duration);
7379 public abstract void stop();
7380 }
7381
7382 /**
7383 * Default position scroller that simulates a fling.
7384 */
7385 class PositionScroller extends AbsPositionScroller implements Runnable {
7386 private static final int SCROLL_DURATION = 200;
7387
7388 private static final int MOVE_DOWN_POS = 1;
7389 private static final int MOVE_UP_POS = 2;
7390 private static final int MOVE_DOWN_BOUND = 3;
7391 private static final int MOVE_UP_BOUND = 4;
7392 private static final int MOVE_OFFSET = 5;
7393
7394 private int mMode;
7395 private int mTargetPos;
7396 private int mBoundPos;
7397 private int mLastSeenPos;
7398 private int mScrollDuration;
7399 private final int mExtraScroll;
7400
7401 private int mOffsetFromTop;
7402
7403 PositionScroller() {
7404 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7405 }
7406
7407 @Override
7408 public void start(final int position) {
7409 stop();
7410
7411 if (mDataChanged) {
7412 // Wait until we're back in a stable state to try this.
7413 mPositionScrollAfterLayout = new Runnable() {
7414 @Override public void run() {
7415 start(position);
7416 }
7417 };
7418 return;
7419 }
7420
7421 final int childCount = getChildCount();
7422 if (childCount == 0) {
7423 // Can't scroll without children.
7424 return;
7425 }
7426
7427 final int firstPos = mFirstPosition;
7428 final int lastPos = firstPos + childCount - 1;
7429
7430 int viewTravelCount;
7431 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7432 if (clampedPosition < firstPos) {
7433 viewTravelCount = firstPos - clampedPosition + 1;
7434 mMode = MOVE_UP_POS;
7435 } else if (clampedPosition > lastPos) {
7436 viewTravelCount = clampedPosition - lastPos + 1;
7437 mMode = MOVE_DOWN_POS;
7438 } else {
7439 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7440 return;
7441 }
7442
7443 if (viewTravelCount > 0) {
7444 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7445 } else {
7446 mScrollDuration = SCROLL_DURATION;
7447 }
7448 mTargetPos = clampedPosition;
7449 mBoundPos = INVALID_POSITION;
7450 mLastSeenPos = INVALID_POSITION;
7451
7452 postOnAnimation(this);
7453 }
7454
7455 @Override
7456 public void start(final int position, final int boundPosition) {
7457 stop();
7458
7459 if (boundPosition == INVALID_POSITION) {
7460 start(position);
7461 return;
7462 }
7463
7464 if (mDataChanged) {
7465 // Wait until we're back in a stable state to try this.
7466 mPositionScrollAfterLayout = new Runnable() {
7467 @Override public void run() {
7468 start(position, boundPosition);
7469 }
7470 };
7471 return;
7472 }
7473
7474 final int childCount = getChildCount();
7475 if (childCount == 0) {
7476 // Can't scroll without children.
7477 return;
7478 }
7479
7480 final int firstPos = mFirstPosition;
7481 final int lastPos = firstPos + childCount - 1;
7482
7483 int viewTravelCount;
7484 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7485 if (clampedPosition < firstPos) {
7486 final int boundPosFromLast = lastPos - boundPosition;
7487 if (boundPosFromLast < 1) {
7488 // Moving would shift our bound position off the screen. Abort.
7489 return;
7490 }
7491
7492 final int posTravel = firstPos - clampedPosition + 1;
7493 final int boundTravel = boundPosFromLast - 1;
7494 if (boundTravel < posTravel) {
7495 viewTravelCount = boundTravel;
7496 mMode = MOVE_UP_BOUND;
7497 } else {
7498 viewTravelCount = posTravel;
7499 mMode = MOVE_UP_POS;
7500 }
7501 } else if (clampedPosition > lastPos) {
7502 final int boundPosFromFirst = boundPosition - firstPos;
7503 if (boundPosFromFirst < 1) {
7504 // Moving would shift our bound position off the screen. Abort.
7505 return;
7506 }
7507
7508 final int posTravel = clampedPosition - lastPos + 1;
7509 final int boundTravel = boundPosFromFirst - 1;
7510 if (boundTravel < posTravel) {
7511 viewTravelCount = boundTravel;
7512 mMode = MOVE_DOWN_BOUND;
7513 } else {
7514 viewTravelCount = posTravel;
7515 mMode = MOVE_DOWN_POS;
7516 }
7517 } else {
7518 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7519 return;
7520 }
7521
7522 if (viewTravelCount > 0) {
7523 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7524 } else {
7525 mScrollDuration = SCROLL_DURATION;
7526 }
7527 mTargetPos = clampedPosition;
7528 mBoundPos = boundPosition;
7529 mLastSeenPos = INVALID_POSITION;
7530
7531 postOnAnimation(this);
7532 }
7533
7534 @Override
7535 public void startWithOffset(int position, int offset) {
7536 startWithOffset(position, offset, SCROLL_DURATION);
7537 }
7538
7539 @Override
7540 public void startWithOffset(final int position, int offset, final int duration) {
7541 stop();
7542
7543 if (mDataChanged) {
7544 // Wait until we're back in a stable state to try this.
7545 final int postOffset = offset;
7546 mPositionScrollAfterLayout = new Runnable() {
7547 @Override public void run() {
7548 startWithOffset(position, postOffset, duration);
7549 }
7550 };
7551 return;
7552 }
7553
7554 final int childCount = getChildCount();
7555 if (childCount == 0) {
7556 // Can't scroll without children.
7557 return;
7558 }
7559
7560 offset += getPaddingTop();
7561
7562 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7563 mOffsetFromTop = offset;
7564 mBoundPos = INVALID_POSITION;
7565 mLastSeenPos = INVALID_POSITION;
7566 mMode = MOVE_OFFSET;
7567
7568 final int firstPos = mFirstPosition;
7569 final int lastPos = firstPos + childCount - 1;
7570
7571 int viewTravelCount;
7572 if (mTargetPos < firstPos) {
7573 viewTravelCount = firstPos - mTargetPos;
7574 } else if (mTargetPos > lastPos) {
7575 viewTravelCount = mTargetPos - lastPos;
7576 } else {
7577 // On-screen, just scroll.
7578 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007579 smoothScrollBy(targetTop - offset, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007580 return;
7581 }
7582
7583 // Estimate how many screens we should travel
7584 final float screenTravelCount = (float) viewTravelCount / childCount;
7585 mScrollDuration = screenTravelCount < 1 ?
7586 duration : (int) (duration / screenTravelCount);
7587 mLastSeenPos = INVALID_POSITION;
7588
7589 postOnAnimation(this);
7590 }
7591
7592 /**
7593 * Scroll such that targetPos is in the visible padded region without scrolling
7594 * boundPos out of view. Assumes targetPos is onscreen.
7595 */
7596 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7597 final int firstPos = mFirstPosition;
7598 final int childCount = getChildCount();
7599 final int lastPos = firstPos + childCount - 1;
7600 final int paddedTop = mListPadding.top;
7601 final int paddedBottom = getHeight() - mListPadding.bottom;
7602
7603 if (targetPos < firstPos || targetPos > lastPos) {
7604 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7605 " not visible [" + firstPos + ", " + lastPos + "]");
7606 }
7607 if (boundPos < firstPos || boundPos > lastPos) {
7608 // boundPos doesn't matter, it's already offscreen.
7609 boundPos = INVALID_POSITION;
7610 }
7611
7612 final View targetChild = getChildAt(targetPos - firstPos);
7613 final int targetTop = targetChild.getTop();
7614 final int targetBottom = targetChild.getBottom();
7615 int scrollBy = 0;
7616
7617 if (targetBottom > paddedBottom) {
7618 scrollBy = targetBottom - paddedBottom;
7619 }
7620 if (targetTop < paddedTop) {
7621 scrollBy = targetTop - paddedTop;
7622 }
7623
7624 if (scrollBy == 0) {
7625 return;
7626 }
7627
7628 if (boundPos >= 0) {
7629 final View boundChild = getChildAt(boundPos - firstPos);
7630 final int boundTop = boundChild.getTop();
7631 final int boundBottom = boundChild.getBottom();
7632 final int absScroll = Math.abs(scrollBy);
7633
7634 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7635 // Don't scroll the bound view off the bottom of the screen.
7636 scrollBy = Math.max(0, boundBottom - paddedBottom);
7637 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7638 // Don't scroll the bound view off the top of the screen.
7639 scrollBy = Math.min(0, boundTop - paddedTop);
7640 }
7641 }
7642
7643 smoothScrollBy(scrollBy, duration);
7644 }
7645
7646 @Override
7647 public void stop() {
7648 removeCallbacks(this);
7649 }
7650
7651 @Override
7652 public void run() {
7653 final int listHeight = getHeight();
7654 final int firstPos = mFirstPosition;
7655
7656 switch (mMode) {
7657 case MOVE_DOWN_POS: {
7658 final int lastViewIndex = getChildCount() - 1;
7659 final int lastPos = firstPos + lastViewIndex;
7660
7661 if (lastViewIndex < 0) {
7662 return;
7663 }
7664
7665 if (lastPos == mLastSeenPos) {
7666 // No new views, let things keep going.
7667 postOnAnimation(this);
7668 return;
7669 }
7670
7671 final View lastView = getChildAt(lastViewIndex);
7672 final int lastViewHeight = lastView.getHeight();
7673 final int lastViewTop = lastView.getTop();
7674 final int lastViewPixelsShowing = listHeight - lastViewTop;
7675 final int extraScroll = lastPos < mItemCount - 1 ?
7676 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7677
7678 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007679 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007680
7681 mLastSeenPos = lastPos;
7682 if (lastPos < mTargetPos) {
7683 postOnAnimation(this);
7684 }
7685 break;
7686 }
7687
7688 case MOVE_DOWN_BOUND: {
7689 final int nextViewIndex = 1;
7690 final int childCount = getChildCount();
7691
7692 if (firstPos == mBoundPos || childCount <= nextViewIndex
7693 || firstPos + childCount >= mItemCount) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007694 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007695 return;
7696 }
7697 final int nextPos = firstPos + nextViewIndex;
7698
7699 if (nextPos == mLastSeenPos) {
7700 // No new views, let things keep going.
7701 postOnAnimation(this);
7702 return;
7703 }
7704
7705 final View nextView = getChildAt(nextViewIndex);
7706 final int nextViewHeight = nextView.getHeight();
7707 final int nextViewTop = nextView.getTop();
7708 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7709 if (nextPos < mBoundPos) {
7710 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007711 mScrollDuration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007712
7713 mLastSeenPos = nextPos;
7714
7715 postOnAnimation(this);
7716 } else {
7717 if (nextViewTop > extraScroll) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007718 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7719 } else {
7720 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007721 }
7722 }
7723 break;
7724 }
7725
7726 case MOVE_UP_POS: {
7727 if (firstPos == mLastSeenPos) {
7728 // No new views, let things keep going.
7729 postOnAnimation(this);
7730 return;
7731 }
7732
7733 final View firstView = getChildAt(0);
7734 if (firstView == null) {
7735 return;
7736 }
7737 final int firstViewTop = firstView.getTop();
7738 final int extraScroll = firstPos > 0 ?
7739 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7740
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007741 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7742 firstPos > mTargetPos);
Alan Viveretted22db212014-02-13 17:47:38 -08007743
7744 mLastSeenPos = firstPos;
7745
7746 if (firstPos > mTargetPos) {
7747 postOnAnimation(this);
7748 }
7749 break;
7750 }
7751
7752 case MOVE_UP_BOUND: {
7753 final int lastViewIndex = getChildCount() - 2;
7754 if (lastViewIndex < 0) {
7755 return;
7756 }
7757 final int lastPos = firstPos + lastViewIndex;
7758
7759 if (lastPos == mLastSeenPos) {
7760 // No new views, let things keep going.
7761 postOnAnimation(this);
7762 return;
7763 }
7764
7765 final View lastView = getChildAt(lastViewIndex);
7766 final int lastViewHeight = lastView.getHeight();
7767 final int lastViewTop = lastView.getTop();
7768 final int lastViewPixelsShowing = listHeight - lastViewTop;
7769 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7770 mLastSeenPos = lastPos;
7771 if (lastPos > mBoundPos) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007772 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7773 true);
Alan Viveretted22db212014-02-13 17:47:38 -08007774 postOnAnimation(this);
7775 } else {
7776 final int bottom = listHeight - extraScroll;
7777 final int lastViewBottom = lastViewTop + lastViewHeight;
7778 if (bottom > lastViewBottom) {
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007779 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7780 } else {
7781 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viveretted22db212014-02-13 17:47:38 -08007782 }
7783 }
7784 break;
7785 }
7786
7787 case MOVE_OFFSET: {
7788 if (mLastSeenPos == firstPos) {
7789 // No new views, let things keep going.
7790 postOnAnimation(this);
7791 return;
7792 }
7793
7794 mLastSeenPos = firstPos;
7795
7796 final int childCount = getChildCount();
7797 final int position = mTargetPos;
7798 final int lastPos = firstPos + childCount - 1;
7799
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007800 // Account for the visible "portion" of the first / last child when we estimate
7801 // how many screens we should travel to reach our target
7802 final View firstChild = getChildAt(0);
7803 final int firstChildHeight = firstChild.getHeight();
7804 final View lastChild = getChildAt(childCount - 1);
7805 final int lastChildHeight = lastChild.getHeight();
7806 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7807 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7808 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7809 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7810 / lastChildHeight;
7811
7812 float viewTravelCount = 0;
Alan Viveretted22db212014-02-13 17:47:38 -08007813 if (position < firstPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007814 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
Alan Viveretted22db212014-02-13 17:47:38 -08007815 } else if (position > lastPos) {
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007816 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
Alan Viveretted22db212014-02-13 17:47:38 -08007817 }
7818
7819 // Estimate how many screens we should travel
Kirill Grouchnikova8e64d92016-09-07 13:19:24 -07007820 final float screenTravelCount = viewTravelCount / childCount;
Alan Viveretted22db212014-02-13 17:47:38 -08007821
7822 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7823 if (position < firstPos) {
7824 final int distance = (int) (-getHeight() * modifier);
7825 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007826 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007827 postOnAnimation(this);
7828 } else if (position > lastPos) {
7829 final int distance = (int) (getHeight() * modifier);
7830 final int duration = (int) (mScrollDuration * modifier);
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007831 smoothScrollBy(distance, duration, true, true);
Alan Viveretted22db212014-02-13 17:47:38 -08007832 postOnAnimation(this);
7833 } else {
7834 // On-screen, just scroll.
7835 final int targetTop = getChildAt(position - firstPos).getTop();
7836 final int distance = targetTop - mOffsetFromTop;
7837 final int duration = (int) (mScrollDuration *
7838 ((float) Math.abs(distance) / getHeight()));
Kirill Grouchnikovb9cda5f2016-07-13 17:24:13 -04007839 smoothScrollBy(distance, duration, true, false);
Alan Viveretted22db212014-02-13 17:47:38 -08007840 }
7841 break;
7842 }
7843
7844 default:
7845 break;
7846 }
7847 }
7848 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007849}