blob: f1bfadee5f36f14ba8a075b896b8c72e4ff79d61 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Tor Norbye80756e32015-03-02 09:39:27 -080019import android.annotation.ColorInt;
Tor Norbye7b9c9122013-05-30 16:48:33 -070020import android.annotation.DrawableRes;
Siva Velusamy94a6d152015-05-05 15:07:00 -070021import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070023import android.content.Intent;
Adam Powell2fe301d2016-08-15 16:34:37 -070024import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.res.TypedArray;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070030import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.os.Debug;
Yohei Yukawa612cce92016-02-11 17:47:33 -080032import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.os.Parcel;
34import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080035import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070036import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070038import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.text.TextUtils;
40import android.text.TextWatcher;
41import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080042import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070043import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080044import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070045import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070046import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070047import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070048import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.view.Gravity;
50import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080051import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.view.KeyEvent;
53import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070054import android.view.Menu;
55import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056import android.view.MotionEvent;
57import android.view.VelocityTracker;
58import android.view.View;
59import android.view.ViewConfiguration;
60import android.view.ViewDebug;
61import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070062import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070063import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070065import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070066import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070067import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070068import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080069import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070070import android.view.animation.Interpolator;
71import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070072import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070073import android.view.inputmethod.CompletionInfo;
74import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070075import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070076import android.view.inputmethod.ExtractedText;
77import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070078import android.view.inputmethod.InputConnection;
Yohei Yukawa152944f2016-06-10 19:04:34 -070079import android.view.inputmethod.InputContentInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070081import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082
Adam Cohen335c3b62012-07-24 17:18:16 -070083import com.android.internal.R;
84
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085import java.util.ArrayList;
86import java.util.List;
87
88/**
Romain Guyd6a463a2009-05-21 23:10:10 -070089 * Base class that can be used to implement virtualized lists of items. A list does
90 * not have a spatial definition here. For instance, subclases of this class can
91 * 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 -080092 *
93 * @attr ref android.R.styleable#AbsListView_listSelector
94 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
95 * @attr ref android.R.styleable#AbsListView_stackFromBottom
96 * @attr ref android.R.styleable#AbsListView_scrollingCache
97 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
98 * @attr ref android.R.styleable#AbsListView_transcriptMode
99 * @attr ref android.R.styleable#AbsListView_cacheColorHint
100 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
101 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -0700102 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 */
104public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
105 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700106 ViewTreeObserver.OnTouchModeChangeListener,
107 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108
Romain Guy9d849a22012-03-14 16:41:42 -0700109 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800110 private static final String TAG = "AbsListView";
111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 /**
113 * Disables the transcript mode.
114 *
115 * @see #setTranscriptMode(int)
116 */
117 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700118
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 /**
120 * The list will automatically scroll to the bottom when a data set change
121 * notification is received and only if the last item is already visible
122 * on screen.
123 *
124 * @see #setTranscriptMode(int)
125 */
126 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700127
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 /**
129 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700130 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 *
132 * @see #setTranscriptMode(int)
133 */
134 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
135
136 /**
137 * Indicates that we are not in the middle of a touch gesture
138 */
139 static final int TOUCH_MODE_REST = -1;
140
141 /**
142 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
143 * scroll gesture.
144 */
145 static final int TOUCH_MODE_DOWN = 0;
146
147 /**
148 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
149 * is a longpress
150 */
151 static final int TOUCH_MODE_TAP = 1;
152
153 /**
154 * Indicates we have waited for everything we can wait for, but the user's finger is still down
155 */
156 static final int TOUCH_MODE_DONE_WAITING = 2;
157
158 /**
159 * Indicates the touch gesture is a scroll
160 */
161 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 /**
164 * Indicates the view is in the process of being flung
165 */
166 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700167
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 /**
Adam Powell637d3372010-08-25 14:37:03 -0700169 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
170 */
171 static final int TOUCH_MODE_OVERSCROLL = 5;
172
173 /**
174 * Indicates the view is being flung outside of normal content bounds
175 * and will spring back.
176 */
177 static final int TOUCH_MODE_OVERFLING = 6;
178
179 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 * Regular layout - usually an unsolicited layout from the view system
181 */
182 static final int LAYOUT_NORMAL = 0;
183
184 /**
185 * Show the first item
186 */
187 static final int LAYOUT_FORCE_TOP = 1;
188
189 /**
190 * Force the selected item to be on somewhere on the screen
191 */
192 static final int LAYOUT_SET_SELECTION = 2;
193
194 /**
195 * Show the last item
196 */
197 static final int LAYOUT_FORCE_BOTTOM = 3;
198
199 /**
200 * Make a mSelectedItem appear in a specific location and build the rest of
201 * the views from there. The top is specified by mSpecificTop.
202 */
203 static final int LAYOUT_SPECIFIC = 4;
204
205 /**
206 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
207 * at mSpecificTop
208 */
209 static final int LAYOUT_SYNC = 5;
210
211 /**
212 * Layout as a result of using the navigation keys
213 */
214 static final int LAYOUT_MOVE_SELECTION = 6;
215
216 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700217 * Normal list that does not indicate choices
218 */
219 public static final int CHOICE_MODE_NONE = 0;
220
221 /**
222 * The list allows up to one choice
223 */
224 public static final int CHOICE_MODE_SINGLE = 1;
225
226 /**
227 * The list allows multiple choices
228 */
229 public static final int CHOICE_MODE_MULTIPLE = 2;
230
231 /**
232 * The list allows multiple choices in a modal selection mode
233 */
234 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
235
236 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700237 * The thread that created this view.
238 */
239 private final Thread mOwnerThread;
240
241 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700242 * Controls if/how the user may choose/check items in the list
243 */
244 int mChoiceMode = CHOICE_MODE_NONE;
245
246 /**
247 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
248 */
249 ActionMode mChoiceActionMode;
250
251 /**
252 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
253 * a few extra actions around what application code does.
254 */
255 MultiChoiceModeWrapper mMultiChoiceModeCallback;
256
257 /**
258 * Running count of how many items are currently checked
259 */
260 int mCheckedItemCount;
261
262 /**
263 * Running state of which positions are currently checked
264 */
265 SparseBooleanArray mCheckStates;
266
267 /**
Adam Powell14c08042011-10-06 19:46:18 -0700268 * Running state of which IDs are currently checked.
269 * If there is a value for a given key, the checked state for that ID is true
270 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700271 */
Adam Powell14c08042011-10-06 19:46:18 -0700272 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700273
274 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 * Controls how the next layout will happen
276 */
277 int mLayoutMode = LAYOUT_NORMAL;
278
279 /**
280 * Should be used by subclasses to listen to changes in the dataset
281 */
282 AdapterDataSetObserver mDataSetObserver;
283
284 /**
285 * The adapter containing the data to be displayed by this view
286 */
287 ListAdapter mAdapter;
288
289 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700290 * The remote adapter containing the data to be displayed by this view to be set
291 */
292 private RemoteViewsAdapter mRemoteAdapter;
293
294 /**
Adam Powell539ee872012-02-03 19:00:49 -0800295 * If mAdapter != null, whenever this is true the adapter has stable IDs.
296 */
297 boolean mAdapterHasStableIds;
298
299 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700300 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
301 */
302 private boolean mDeferNotifyDataSetChanged = false;
303
304 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 * Indicates whether the list selector should be drawn on top of the children or behind
306 */
307 boolean mDrawSelectorOnTop = false;
308
309 /**
310 * The drawable used to draw the selector
311 */
312 Drawable mSelector;
313
314 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700315 * The current position of the selector in the list.
316 */
317 int mSelectorPosition = INVALID_POSITION;
318
319 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 * Defines the selector's location and dimension at drawing time
321 */
322 Rect mSelectorRect = new Rect();
323
324 /**
325 * The data set used to store unused views that should be reused during the next layout
326 * to avoid creating new ones
327 */
328 final RecycleBin mRecycler = new RecycleBin();
329
330 /**
331 * The selection's left padding
332 */
333 int mSelectionLeftPadding = 0;
334
335 /**
336 * The selection's top padding
337 */
338 int mSelectionTopPadding = 0;
339
340 /**
341 * The selection's right padding
342 */
343 int mSelectionRightPadding = 0;
344
345 /**
346 * The selection's bottom padding
347 */
348 int mSelectionBottomPadding = 0;
349
350 /**
351 * This view's padding
352 */
353 Rect mListPadding = new Rect();
354
355 /**
356 * Subclasses must retain their measure spec from onMeasure() into this member
357 */
358 int mWidthMeasureSpec = 0;
359
360 /**
361 * The top scroll indicator
362 */
363 View mScrollUp;
364
365 /**
366 * The down scroll indicator
367 */
368 View mScrollDown;
369
370 /**
371 * When the view is scrolling, this flag is set to true to indicate subclasses that
372 * the drawing cache was enabled on the children
373 */
374 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800375 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376
377 /**
378 * The position of the view that received the down motion event
379 */
380 int mMotionPosition;
381
382 /**
383 * The offset to the top of the mMotionPosition view when the down motion event was received
384 */
385 int mMotionViewOriginalTop;
386
387 /**
388 * The desired offset to the top of the mMotionPosition view after a scroll
389 */
390 int mMotionViewNewTop;
391
392 /**
393 * The X value associated with the the down motion event
394 */
395 int mMotionX;
396
397 /**
398 * The Y value associated with the the down motion event
399 */
400 int mMotionY;
401
402 /**
403 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
404 * TOUCH_MODE_DONE_WAITING
405 */
406 int mTouchMode = TOUCH_MODE_REST;
407
408 /**
409 * Y value from on the previous motion event (if any)
410 */
411 int mLastY;
412
413 /**
414 * How far the finger moved before we started scrolling
415 */
416 int mMotionCorrection;
417
418 /**
419 * Determines speed during touch scrolling
420 */
421 private VelocityTracker mVelocityTracker;
422
423 /**
424 * Handles one frame of a fling
425 */
426 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800427
Adam Powell45803472010-01-25 15:10:44 -0800428 /**
429 * Handles scrolling between positions within the list.
430 */
Alan Viveretted22db212014-02-13 17:47:38 -0800431 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432
433 /**
434 * The offset in pixels form the top of the AdapterView to the top
435 * of the currently selected view. Used to save and restore state.
436 */
437 int mSelectedTop = 0;
438
439 /**
440 * Indicates whether the list is stacked from the bottom edge or
441 * the top edge.
442 */
443 boolean mStackFromBottom;
444
445 /**
446 * When set to true, the list automatically discards the children's
447 * bitmap cache after scrolling.
448 */
449 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700450
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 /**
452 * Whether or not to enable the fast scroll feature on this list
453 */
454 boolean mFastScrollEnabled;
455
456 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700457 * Whether or not to always show the fast scroll feature on this list
458 */
459 boolean mFastScrollAlwaysVisible;
460
461 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 * Optional callback to notify client when scroll position has changed
463 */
464 private OnScrollListener mOnScrollListener;
465
466 /**
467 * Keeps track of our accessory window
468 */
469 PopupWindow mPopup;
470
471 /**
472 * Used with type filter window
473 */
474 EditText mTextFilter;
475
476 /**
477 * Indicates whether to use pixels-based or position-based scrollbar
478 * properties.
479 */
480 private boolean mSmoothScrollbarEnabled = true;
481
482 /**
483 * Indicates that this view supports filtering
484 */
485 private boolean mTextFilterEnabled;
486
487 /**
488 * Indicates that this view is currently displaying a filtered view of the data
489 */
490 private boolean mFiltered;
491
492 /**
493 * Rectangle used for hit testing children
494 */
495 private Rect mTouchFrame;
496
497 /**
498 * The position to resurrect the selected position to.
499 */
500 int mResurrectToPosition = INVALID_POSITION;
501
502 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800503
Adam Powell0b8bb422010-02-08 14:30:45 -0800504 /**
Adam Powell637d3372010-08-25 14:37:03 -0700505 * Maximum distance to record overscroll
506 */
507 int mOverscrollMax;
508
509 /**
510 * Content height divided by this is the overscroll limit.
511 */
512 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
513
514 /**
Adam Powell14c08042011-10-06 19:46:18 -0700515 * How many positions in either direction we will search to try to
516 * find a checked item with a stable ID that moved position across
517 * a data set change. If the item isn't found it will be unselected.
518 */
519 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
520
521 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 * Used to request a layout when we changed touch mode
523 */
524 private static final int TOUCH_MODE_UNKNOWN = -1;
525 private static final int TOUCH_MODE_ON = 0;
526 private static final int TOUCH_MODE_OFF = 1;
527
528 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
529
530 private static final boolean PROFILE_SCROLLING = false;
531 private boolean mScrollProfilingStarted = false;
532
533 private static final boolean PROFILE_FLINGING = false;
534 private boolean mFlingProfilingStarted = false;
535
536 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800537 * The StrictMode "critical time span" objects to catch animation
538 * stutters. Non-null when a time-sensitive animation is
539 * in-flight. Must call finish() on them when done animating.
540 * These are no-ops on user builds.
541 */
542 private StrictMode.Span mScrollStrictSpan = null;
543 private StrictMode.Span mFlingStrictSpan = null;
544
545 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 * The last CheckForLongPress runnable we posted, if any
547 */
548 private CheckForLongPress mPendingCheckForLongPress;
549
550 /**
551 * The last CheckForTap runnable we posted, if any
552 */
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700553 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 /**
556 * The last CheckForKeyLongPress runnable we posted, if any
557 */
558 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
559
560 /**
561 * Acts upon click
562 */
563 private AbsListView.PerformClick mPerformClick;
564
565 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800566 * Delayed action for touch mode.
567 */
568 private Runnable mTouchModeReset;
569
570 /**
Alan Viverette66df60f2016-01-28 14:56:07 -0500571 * Whether the most recent touch event stream resulted in a successful
572 * long-press action. This is reset on TOUCH_DOWN.
573 */
574 private boolean mHasPerformedLongPress;
575
576 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 * This view is in transcript mode -- it shows the bottom of the list when the data
578 * changes
579 */
580 private int mTranscriptMode;
581
582 /**
583 * Indicates that this list is always drawn on top of a solid, single-color, opaque
584 * background
585 */
586 private int mCacheColorHint;
587
588 /**
589 * The select child's view (from the adapter's getView) is enabled.
590 */
591 private boolean mIsChildViewEnabled;
592
593 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800594 * The cached drawable state for the selector. Accounts for child enabled
595 * state, but otherwise identical to the view's own drawable state.
596 */
597 private int[] mSelectorState;
598
599 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 * The last scroll state reported to clients through {@link OnScrollListener}.
601 */
602 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
603
604 /**
605 * Helper object that renders and controls the fast scroll thumb.
606 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700607 private FastScroller mFastScroll;
608
609 /**
610 * Temporary holder for fast scroller style until a FastScroller object
611 * is created.
612 */
613 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614
Romain Guyd6a463a2009-05-21 23:10:10 -0700615 private boolean mGlobalLayoutListenerAddedFilter;
616
617 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 private float mDensityScale;
619
Ned Burns20ad0732016-08-18 14:22:57 -0400620 private float mScrollFactor;
621
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700622 private InputConnection mDefInputConnection;
623 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700624
625 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700626 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700627 private int mMinimumVelocity;
628 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700629 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800630
Romain Guy21875052010-01-06 18:48:08 -0800631 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800632
Adam Powell96d62af2014-05-02 10:04:38 -0700633 private final int[] mScrollOffset = new int[2];
634 private final int[] mScrollConsumed = new int[2];
635
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800636 private final float[] mTmpPoint = new float[2];
637
Adam Powell744beff2014-09-22 09:47:48 -0700638 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
639 // In the future it would be nice to be able to give this to the VelocityTracker
640 // directly, or alternatively put a VT into absolute-positioning mode that only
641 // reads the raw screen-coordinate x/y values.
642 private int mNestedYOffset = 0;
643
Romain Guy24562482010-02-01 14:56:19 -0800644 // True when the popup should be hidden because of a call to
645 // dispatchDisplayHint()
646 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800647
Adam Powell4cd47702010-02-25 11:21:14 -0800648 /**
649 * ID of the active pointer. This is used to retain consistency during
650 * drags/flings if multiple pointers are used.
651 */
652 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800653
Adam Powell4cd47702010-02-25 11:21:14 -0800654 /**
655 * Sentinel value for no current active pointer.
656 * Used by {@link #mActivePointerId}.
657 */
658 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700659
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800660 /**
Adam Powell637d3372010-08-25 14:37:03 -0700661 * Maximum distance to overscroll by during edge effects
662 */
663 int mOverscrollDistance;
664
665 /**
666 * Maximum distance to overfling during edge effects
667 */
668 int mOverflingDistance;
669
670 // These two EdgeGlows are always set and used together.
671 // Checking one for null is as good as checking both.
672
673 /**
674 * Tracks the state of the top edge glow.
675 */
Adam Powell89935e42011-08-31 14:26:12 -0700676 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700677
678 /**
679 * Tracks the state of the bottom edge glow.
680 */
Adam Powell89935e42011-08-31 14:26:12 -0700681 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700682
683 /**
684 * An estimate of how many pixels are between the top of the list and
685 * the top of the first position in the adapter, based on the last time
686 * we saw it. Used to hint where to draw edge glows.
687 */
688 private int mFirstPositionDistanceGuess;
689
690 /**
691 * An estimate of how many pixels are between the bottom of the list and
692 * the bottom of the last position in the adapter, based on the last time
693 * we saw it. Used to hint where to draw edge glows.
694 */
695 private int mLastPositionDistanceGuess;
696
697 /**
698 * Used for determining when to cancel out of overscroll.
699 */
700 private int mDirection = 0;
701
702 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800703 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
704 * the bottom correctly on resizes.
705 */
706 private boolean mForceTranscriptScroll;
707
alanvc1d7e772012-05-08 14:47:24 -0700708 /**
709 * Used for interacting with list items from an accessibility service.
710 */
711 private ListItemAccessibilityDelegate mAccessibilityDelegate;
712
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700713 private int mLastAccessibilityScrollEventFromIndex;
714 private int mLastAccessibilityScrollEventToIndex;
715
Adam Powellda13dba2010-12-05 13:47:23 -0800716 /**
Adam Powellee78b172011-08-16 16:39:20 -0700717 * Track the item count from the last time we handled a data change.
718 */
719 private int mLastHandledItemCount;
720
721 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700722 * Used for smooth scrolling at a consistent rate
723 */
724 static final Interpolator sLinearInterpolator = new LinearInterpolator();
725
726 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700727 * The saved state that we will be restoring from when we next sync.
728 * Kept here so that if we happen to be asked to save our state before
729 * the sync happens, we can return this existing data rather than losing
730 * it.
731 */
732 private SavedState mPendingSync;
733
734 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800735 * Whether the view is in the process of detaching from its window.
736 */
737 private boolean mIsDetaching;
738
739 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 * Interface definition for a callback to be invoked when the list or grid
741 * has been scrolled.
742 */
743 public interface OnScrollListener {
744
745 /**
746 * The view is not scrolling. Note navigating the list using the trackball counts as
747 * being in the idle state since these transitions are not animated.
748 */
749 public static int SCROLL_STATE_IDLE = 0;
750
751 /**
752 * The user is scrolling using touch, and their finger is still on the screen
753 */
754 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
755
756 /**
757 * The user had previously been scrolling using touch and had performed a fling. The
758 * animation is now coasting to a stop
759 */
760 public static int SCROLL_STATE_FLING = 2;
761
762 /**
763 * Callback method to be invoked while the list view or grid view is being scrolled. If the
764 * view is being scrolled, this method will be called before the next frame of the scroll is
765 * rendered. In particular, it will be called before any calls to
766 * {@link Adapter#getView(int, View, ViewGroup)}.
767 *
768 * @param view The view whose scroll state is being reported
769 *
Yorke Lee43943d82014-05-08 10:15:20 -0700770 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800771 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
772 */
773 public void onScrollStateChanged(AbsListView view, int scrollState);
774
775 /**
776 * Callback method to be invoked when the list or grid has been scrolled. This will be
777 * called after the scroll has completed
778 * @param view The view whose scroll state is being reported
779 * @param firstVisibleItem the index of the first visible cell (ignore if
780 * visibleItemCount == 0)
781 * @param visibleItemCount the number of visible cells
782 * @param totalItemCount the number of items in the list adaptor
783 */
784 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
785 int totalItemCount);
786 }
787
Dianne Hackborne2136772010-11-04 15:08:59 -0700788 /**
789 * The top-level view of a list item can implement this interface to allow
790 * itself to modify the bounds of the selection shown for that item.
791 */
792 public interface SelectionBoundsAdjuster {
793 /**
794 * Called to allow the list item to adjust the bounds shown for
795 * its selection.
796 *
797 * @param bounds On call, this contains the bounds the list has
798 * selected for the item (that is the bounds of the entire view). The
799 * values can be modified as desired.
800 */
801 public void adjustListItemSelectionBounds(Rect bounds);
802 }
803
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 public AbsListView(Context context) {
805 super(context);
806 initAbsListView();
807
Alan Viverette39bed692013-08-07 15:47:04 -0700808 mOwnerThread = Thread.currentThread();
809
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 setVerticalScrollBarEnabled(true);
811 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700812 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813 a.recycle();
814 }
815
816 public AbsListView(Context context, AttributeSet attrs) {
817 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
818 }
819
Alan Viverette617feb92013-09-09 18:09:13 -0700820 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
821 this(context, attrs, defStyleAttr, 0);
822 }
823
824 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
825 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826 initAbsListView();
827
Alan Viverette39bed692013-08-07 15:47:04 -0700828 mOwnerThread = Thread.currentThread();
829
Alan Viverette617feb92013-09-09 18:09:13 -0700830 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700831 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832
Alan Viverette7eceda32015-06-01 10:47:29 -0700833 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
834 if (selector != null) {
835 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800836 }
837
Alan Viverette7eceda32015-06-01 10:47:29 -0700838 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839
Alan Viverette7eceda32015-06-01 10:47:29 -0700840 setStackFromBottom(a.getBoolean(
841 R.styleable.AbsListView_stackFromBottom, false));
842 setScrollingCacheEnabled(a.getBoolean(
843 R.styleable.AbsListView_scrollingCache, true));
844 setTextFilterEnabled(a.getBoolean(
845 R.styleable.AbsListView_textFilterEnabled, false));
846 setTranscriptMode(a.getInt(
847 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
848 setCacheColorHint(a.getColor(
849 R.styleable.AbsListView_cacheColorHint, 0));
850 setSmoothScrollbarEnabled(a.getBoolean(
851 R.styleable.AbsListView_smoothScrollbar, true));
852 setChoiceMode(a.getInt(
853 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854
Alan Viverette7eceda32015-06-01 10:47:29 -0700855 setFastScrollEnabled(a.getBoolean(
856 R.styleable.AbsListView_fastScrollEnabled, false));
857 setFastScrollStyle(a.getResourceId(
858 R.styleable.AbsListView_fastScrollStyle, 0));
859 setFastScrollAlwaysVisible(a.getBoolean(
860 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700861
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862 a.recycle();
Adam Powell2fe301d2016-08-15 16:34:37 -0700863
864 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
865 setRevealOnFocusHint(false);
866 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868
Romain Guyd6a463a2009-05-21 23:10:10 -0700869 private void initAbsListView() {
870 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700871 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700872 setFocusableInTouchMode(true);
873 setWillNotDraw(false);
874 setAlwaysDrawnWithCacheEnabled(false);
875 setScrollingCacheEnabled(true);
876
Romain Guy4296fc42009-07-06 11:48:52 -0700877 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
878 mTouchSlop = configuration.getScaledTouchSlop();
Ned Burns20ad0732016-08-18 14:22:57 -0400879 mScrollFactor = configuration.getScaledScrollFactor();
Romain Guy4296fc42009-07-06 11:48:52 -0700880 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
881 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700882 mOverscrollDistance = configuration.getScaledOverscrollDistance();
883 mOverflingDistance = configuration.getScaledOverflingDistance();
884
Romain Guyd6a463a2009-05-21 23:10:10 -0700885 mDensityScale = getContext().getResources().getDisplayMetrics().density;
886 }
Romain Guy0a637162009-05-29 14:43:54 -0700887
Adam Powell637d3372010-08-25 14:37:03 -0700888 @Override
889 public void setOverScrollMode(int mode) {
890 if (mode != OVER_SCROLL_NEVER) {
891 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800892 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700893 mEdgeGlowTop = new EdgeEffect(context);
894 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700895 }
896 } else {
897 mEdgeGlowTop = null;
898 mEdgeGlowBottom = null;
899 }
900 super.setOverScrollMode(mode);
901 }
902
Romain Guyd6a463a2009-05-21 23:10:10 -0700903 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700904 * {@inheritDoc}
905 */
906 @Override
907 public void setAdapter(ListAdapter adapter) {
908 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800909 mAdapterHasStableIds = mAdapter.hasStableIds();
910 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700911 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700912 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700913 }
914 }
915
916 if (mCheckStates != null) {
917 mCheckStates.clear();
918 }
919
920 if (mCheckedIdStates != null) {
921 mCheckedIdStates.clear();
922 }
923 }
924
925 /**
926 * Returns the number of items currently selected. This will only be valid
927 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
928 *
929 * <p>To determine the specific items that are currently selected, use one of
930 * the <code>getChecked*</code> methods.
931 *
932 * @return The number of items currently selected
933 *
934 * @see #getCheckedItemPosition()
935 * @see #getCheckedItemPositions()
936 * @see #getCheckedItemIds()
937 */
938 public int getCheckedItemCount() {
939 return mCheckedItemCount;
940 }
941
942 /**
943 * Returns the checked state of the specified position. The result is only
944 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
945 * or {@link #CHOICE_MODE_MULTIPLE}.
946 *
947 * @param position The item whose checked state to return
948 * @return The item's checked state or <code>false</code> if choice mode
949 * is invalid
950 *
951 * @see #setChoiceMode(int)
952 */
953 public boolean isItemChecked(int position) {
954 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
955 return mCheckStates.get(position);
956 }
957
958 return false;
959 }
960
961 /**
962 * Returns the currently checked item. The result is only valid if the choice
963 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
964 *
965 * @return The position of the currently checked item or
966 * {@link #INVALID_POSITION} if nothing is selected
967 *
968 * @see #setChoiceMode(int)
969 */
970 public int getCheckedItemPosition() {
971 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
972 return mCheckStates.keyAt(0);
973 }
974
975 return INVALID_POSITION;
976 }
977
978 /**
979 * Returns the set of checked items in the list. The result is only valid if
980 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
981 *
982 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200983 * get(int position) where position is a checked position in the
984 * list and false otherwise, or <code>null</code> if the choice
985 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700986 */
987 public SparseBooleanArray getCheckedItemPositions() {
988 if (mChoiceMode != CHOICE_MODE_NONE) {
989 return mCheckStates;
990 }
991 return null;
992 }
993
994 /**
995 * Returns the set of checked items ids. The result is only valid if the
996 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
997 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
998 *
999 * @return A new array which contains the id of each checked item in the
1000 * list.
1001 */
1002 public long[] getCheckedItemIds() {
1003 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
1004 return new long[0];
1005 }
1006
Adam Powell14c08042011-10-06 19:46:18 -07001007 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -07001008 final int count = idStates.size();
1009 final long[] ids = new long[count];
1010
1011 for (int i = 0; i < count; i++) {
1012 ids[i] = idStates.keyAt(i);
1013 }
1014
1015 return ids;
1016 }
1017
1018 /**
1019 * Clear any choices previously set
1020 */
1021 public void clearChoices() {
1022 if (mCheckStates != null) {
1023 mCheckStates.clear();
1024 }
1025 if (mCheckedIdStates != null) {
1026 mCheckedIdStates.clear();
1027 }
1028 mCheckedItemCount = 0;
1029 }
1030
1031 /**
1032 * Sets the checked state of the specified position. The is only valid if
1033 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1034 * {@link #CHOICE_MODE_MULTIPLE}.
1035 *
1036 * @param position The item whose checked state is to be checked
1037 * @param value The new checked state for the item
1038 */
1039 public void setItemChecked(int position, boolean value) {
1040 if (mChoiceMode == CHOICE_MODE_NONE) {
1041 return;
1042 }
1043
1044 // Start selection mode if needed. We don't need to if we're unchecking something.
1045 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001046 if (mMultiChoiceModeCallback == null ||
1047 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1048 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1049 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1050 "supplied. Call setMultiChoiceModeListener to set a callback.");
1051 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001052 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1053 }
1054
Siyamed Sinir135554e2016-01-22 18:40:42 -08001055 final boolean itemCheckChanged;
Adam Powellf343e1b2010-08-13 18:27:04 -07001056 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1057 boolean oldValue = mCheckStates.get(position);
1058 mCheckStates.put(position, value);
1059 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1060 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001061 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001062 } else {
1063 mCheckedIdStates.delete(mAdapter.getItemId(position));
1064 }
1065 }
Siyamed Sinir135554e2016-01-22 18:40:42 -08001066 itemCheckChanged = oldValue != value;
1067 if (itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001068 if (value) {
1069 mCheckedItemCount++;
1070 } else {
1071 mCheckedItemCount--;
1072 }
1073 }
1074 if (mChoiceActionMode != null) {
1075 final long id = mAdapter.getItemId(position);
1076 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1077 position, id, value);
1078 }
1079 } else {
1080 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1081 // Clear all values if we're checking something, or unchecking the currently
1082 // selected item
Siyamed Sinir135554e2016-01-22 18:40:42 -08001083 itemCheckChanged = isItemChecked(position) != value;
Adam Powellf343e1b2010-08-13 18:27:04 -07001084 if (value || isItemChecked(position)) {
1085 mCheckStates.clear();
1086 if (updateIds) {
1087 mCheckedIdStates.clear();
1088 }
1089 }
1090 // this may end up selecting the value we just cleared but this way
1091 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1092 if (value) {
1093 mCheckStates.put(position, true);
1094 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001095 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001096 }
1097 mCheckedItemCount = 1;
1098 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1099 mCheckedItemCount = 0;
1100 }
1101 }
1102
Siyamed Sinir135554e2016-01-22 18:40:42 -08001103 // Do not generate a data change while we are in the layout phase or data has not changed
1104 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001105 mDataChanged = true;
1106 rememberSyncState();
1107 requestLayout();
1108 }
1109 }
1110
1111 @Override
1112 public boolean performItemClick(View view, int position, long id) {
1113 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001114 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001115
1116 if (mChoiceMode != CHOICE_MODE_NONE) {
1117 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001118 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001119
1120 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1121 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001122 boolean checked = !mCheckStates.get(position, false);
1123 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001124 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001125 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001126 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001127 } else {
1128 mCheckedIdStates.delete(mAdapter.getItemId(position));
1129 }
1130 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001131 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001132 mCheckedItemCount++;
1133 } else {
1134 mCheckedItemCount--;
1135 }
1136 if (mChoiceActionMode != null) {
1137 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001138 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001139 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001140 }
Adam Powell29382d92012-02-23 11:03:22 -08001141 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001142 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001143 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001144 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001145 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001146 mCheckStates.put(position, true);
1147 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1148 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001149 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001150 }
1151 mCheckedItemCount = 1;
1152 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1153 mCheckedItemCount = 0;
1154 }
Adam Powell29382d92012-02-23 11:03:22 -08001155 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001156 }
1157
Adam Powell29382d92012-02-23 11:03:22 -08001158 if (checkedStateChanged) {
1159 updateOnScreenCheckedViews();
1160 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001161 }
1162
Adam Powellbf5f2b32010-10-24 16:45:44 -07001163 if (dispatchItemClick) {
1164 handled |= super.performItemClick(view, position, id);
1165 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001166
1167 return handled;
1168 }
1169
1170 /**
Adam Powell29382d92012-02-23 11:03:22 -08001171 * Perform a quick, in-place update of the checked or activated state
1172 * on all visible item views. This should only be called when a valid
1173 * choice mode is active.
1174 */
1175 private void updateOnScreenCheckedViews() {
1176 final int firstPos = mFirstPosition;
1177 final int count = getChildCount();
1178 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1179 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1180 for (int i = 0; i < count; i++) {
1181 final View child = getChildAt(i);
1182 final int position = firstPos + i;
1183
1184 if (child instanceof Checkable) {
1185 ((Checkable) child).setChecked(mCheckStates.get(position));
1186 } else if (useActivated) {
1187 child.setActivated(mCheckStates.get(position));
1188 }
1189 }
1190 }
1191
1192 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001193 * @see #setChoiceMode(int)
1194 *
1195 * @return The current choice mode
1196 */
1197 public int getChoiceMode() {
1198 return mChoiceMode;
1199 }
1200
1201 /**
1202 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1203 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1204 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1205 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1206 *
1207 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1208 * {@link #CHOICE_MODE_MULTIPLE}
1209 */
1210 public void setChoiceMode(int choiceMode) {
1211 mChoiceMode = choiceMode;
1212 if (mChoiceActionMode != null) {
1213 mChoiceActionMode.finish();
1214 mChoiceActionMode = null;
1215 }
1216 if (mChoiceMode != CHOICE_MODE_NONE) {
1217 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001218 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001219 }
1220 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001221 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001222 }
1223 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1224 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1225 clearChoices();
1226 setLongClickable(true);
1227 }
1228 }
1229 }
1230
1231 /**
1232 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1233 * selection {@link ActionMode}. Only used when the choice mode is set to
1234 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1235 *
1236 * @param listener Listener that will manage the selection mode
1237 *
1238 * @see #setChoiceMode(int)
1239 */
1240 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1241 if (mMultiChoiceModeCallback == null) {
1242 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1243 }
1244 mMultiChoiceModeCallback.setWrapped(listener);
1245 }
1246
1247 /**
Adam Powell637d3372010-08-25 14:37:03 -07001248 * @return true if all list content currently fits within the view boundaries
1249 */
1250 private boolean contentFits() {
1251 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001252 if (childCount == 0) return true;
1253 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001254
Adam Powell4ce35412011-01-24 14:55:00 -08001255 return getChildAt(0).getTop() >= mListPadding.top &&
1256 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001257 }
1258
1259 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001260 * Specifies whether fast scrolling is enabled or disabled.
1261 * <p>
1262 * When fast scrolling is enabled, the user can quickly scroll through lists
1263 * by dragging the fast scroll thumb.
1264 * <p>
1265 * If the adapter backing this list implements {@link SectionIndexer}, the
1266 * fast scroller will display section header previews as the user scrolls.
1267 * Additionally, the user will be able to quickly jump between sections by
1268 * tapping along the length of the scroll bar.
1269 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 * @see SectionIndexer
1271 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001272 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 */
Alan Viverette39bed692013-08-07 15:47:04 -07001274 public void setFastScrollEnabled(final boolean enabled) {
1275 if (mFastScrollEnabled != enabled) {
1276 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001277
Alan Viverette39bed692013-08-07 15:47:04 -07001278 if (isOwnerThread()) {
1279 setFastScrollerEnabledUiThread(enabled);
1280 } else {
1281 post(new Runnable() {
1282 @Override
1283 public void run() {
1284 setFastScrollerEnabledUiThread(enabled);
1285 }
1286 });
1287 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001288 }
Alan Viverette39bed692013-08-07 15:47:04 -07001289 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001290
Alan Viverette39bed692013-08-07 15:47:04 -07001291 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001292 if (mFastScroll != null) {
1293 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001294 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001295 mFastScroll = new FastScroller(this, mFastScrollStyle);
1296 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001298
Alan Viveretteb9f27222013-09-06 19:39:47 -07001299 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001300
Alan Viverette8636ace2013-10-31 15:41:31 -07001301 if (mFastScroll != null) {
1302 mFastScroll.updateLayout();
1303 }
1304 }
1305
1306 /**
1307 * Specifies the style of the fast scroller decorations.
1308 *
1309 * @param styleResId style resource containing fast scroller properties
1310 * @see android.R.styleable#FastScroll
1311 */
1312 public void setFastScrollStyle(int styleResId) {
1313 if (mFastScroll == null) {
1314 mFastScrollStyle = styleResId;
1315 } else {
1316 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001317 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 }
Romain Guy0a637162009-05-29 14:43:54 -07001319
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001320 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001321 * Set whether or not the fast scroller should always be shown in place of
1322 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001323 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001324 * <p>
1325 * Fast scrollers shown in this way will not fade out and will be a
1326 * permanent fixture within the list. This is best combined with an inset
1327 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001328 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001329 * @param alwaysShow true if the fast scroller should always be displayed,
1330 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001331 * @see #setScrollBarStyle(int)
1332 * @see #setFastScrollEnabled(boolean)
1333 */
Alan Viverette39bed692013-08-07 15:47:04 -07001334 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1335 if (mFastScrollAlwaysVisible != alwaysShow) {
1336 if (alwaysShow && !mFastScrollEnabled) {
1337 setFastScrollEnabled(true);
1338 }
Adam Powell20232d02010-12-08 21:08:53 -08001339
Alan Viverette39bed692013-08-07 15:47:04 -07001340 mFastScrollAlwaysVisible = alwaysShow;
1341
1342 if (isOwnerThread()) {
1343 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1344 } else {
1345 post(new Runnable() {
1346 @Override
1347 public void run() {
1348 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1349 }
1350 });
1351 }
1352 }
1353 }
1354
1355 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001356 if (mFastScroll != null) {
1357 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001358 }
Alan Viverette39bed692013-08-07 15:47:04 -07001359 }
Adam Powell20232d02010-12-08 21:08:53 -08001360
Alan Viverette39bed692013-08-07 15:47:04 -07001361 /**
1362 * @return whether the current thread is the one that created the view
1363 */
1364 private boolean isOwnerThread() {
1365 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001366 }
1367
1368 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001369 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001370 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001371 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001372 * @see #setFastScrollAlwaysVisible(boolean)
1373 */
1374 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001375 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001376 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1377 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001378 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001379 }
Adam Powell20232d02010-12-08 21:08:53 -08001380 }
1381
1382 @Override
1383 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001384 if (mFastScroll != null && mFastScroll.isEnabled()) {
1385 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001386 }
1387 return super.getVerticalScrollbarWidth();
1388 }
1389
1390 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001391 * Returns true if the fast scroller is enabled.
1392 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393 * @see #setFastScrollEnabled(boolean)
1394 * @return true if fast scroll is enabled, false otherwise
1395 */
1396 @ViewDebug.ExportedProperty
1397 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001398 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001399 return mFastScrollEnabled;
1400 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001401 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001402 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001403 }
Romain Guy0a637162009-05-29 14:43:54 -07001404
Adam Powell20232d02010-12-08 21:08:53 -08001405 @Override
1406 public void setVerticalScrollbarPosition(int position) {
1407 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001408 if (mFastScroll != null) {
1409 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001410 }
1411 }
1412
Alan Viverette26bb2532013-08-09 10:40:50 -07001413 @Override
1414 public void setScrollBarStyle(int style) {
1415 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001416 if (mFastScroll != null) {
1417 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001418 }
1419 }
1420
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001422 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001423 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001424 */
1425 @Override
1426 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001427 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001428 }
1429
1430 /**
1431 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1432 * is computed based on the number of visible pixels in the visible items. This
1433 * however assumes that all list items have the same height. If you use a list in
1434 * which items have different heights, the scrollbar will change appearance as the
1435 * user scrolls through the list. To avoid this issue, you need to disable this
1436 * property.
1437 *
1438 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1439 * is based solely on the number of items in the adapter and the position of the
1440 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001441 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 *
1443 * @param enabled Whether or not to enable smooth scrollbar.
1444 *
Romain Guy0a637162009-05-29 14:43:54 -07001445 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001446 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1447 */
1448 public void setSmoothScrollbarEnabled(boolean enabled) {
1449 mSmoothScrollbarEnabled = enabled;
1450 }
1451
1452 /**
1453 * Returns the current state of the fast scroll feature.
1454 *
1455 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1456 *
1457 * @see #setSmoothScrollbarEnabled(boolean)
1458 */
1459 @ViewDebug.ExportedProperty
1460 public boolean isSmoothScrollbarEnabled() {
1461 return mSmoothScrollbarEnabled;
1462 }
1463
1464 /**
1465 * Set the listener that will receive notifications every time the list scrolls.
1466 *
1467 * @param l the scroll listener
1468 */
1469 public void setOnScrollListener(OnScrollListener l) {
1470 mOnScrollListener = l;
1471 invokeOnItemScrollListener();
1472 }
1473
1474 /**
1475 * Notify our scroll listener (if there is one) of a change in scroll state
1476 */
1477 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001478 if (mFastScroll != null) {
1479 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001480 }
1481 if (mOnScrollListener != null) {
1482 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1483 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001484 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485 }
1486
Alan Viverettea54956a2015-01-07 16:05:02 -08001487 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001488 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001489 public void sendAccessibilityEventInternal(int eventType) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001490 // Since this class calls onScrollChanged even if the mFirstPosition and the
1491 // child count have not changed we will avoid sending duplicate accessibility
1492 // events.
1493 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001494 final int firstVisiblePosition = getFirstVisiblePosition();
1495 final int lastVisiblePosition = getLastVisiblePosition();
1496 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1497 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001498 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001499 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001500 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1501 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001502 }
1503 }
Alan Viverettea54956a2015-01-07 16:05:02 -08001504 super.sendAccessibilityEventInternal(eventType);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001505 }
1506
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001507 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001508 public CharSequence getAccessibilityClassName() {
1509 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001510 }
1511
Alan Viverettea54956a2015-01-07 16:05:02 -08001512 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001513 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001514 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1515 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001516 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001517 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001518 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001519 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001520 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001521 }
Alan Viverette947a9692014-09-25 12:43:47 -07001522 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001523 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001524 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001525 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001526 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001527 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001528
1529 info.removeAction(AccessibilityAction.ACTION_CLICK);
1530 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001531 }
1532
Alan Viverette76769ae2014-02-12 16:38:10 -08001533 int getSelectionModeForAccessibility() {
1534 final int choiceMode = getChoiceMode();
1535 switch (choiceMode) {
1536 case CHOICE_MODE_NONE:
1537 return CollectionInfo.SELECTION_MODE_NONE;
1538 case CHOICE_MODE_SINGLE:
1539 return CollectionInfo.SELECTION_MODE_SINGLE;
1540 case CHOICE_MODE_MULTIPLE:
1541 case CHOICE_MODE_MULTIPLE_MODAL:
1542 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1543 default:
1544 return CollectionInfo.SELECTION_MODE_NONE;
1545 }
1546 }
1547
Alan Viverettea54956a2015-01-07 16:05:02 -08001548 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001549 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001550 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1551 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001552 return true;
1553 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001554 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001555 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1556 case R.id.accessibilityActionScrollDown: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001557 if (isEnabled() && canScrollDown()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001558 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1559 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1560 return true;
1561 }
1562 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001563 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1564 case R.id.accessibilityActionScrollUp: {
Alan Viverette47be54b2016-08-05 16:48:19 -04001565 if (isEnabled() && canScrollUp()) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001566 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1567 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1568 return true;
1569 }
1570 } return false;
1571 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001572 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001573 }
1574
Svetoslav5b578da2013-05-08 14:23:32 -07001575 /** @hide */
1576 @Override
1577 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1578 if (accessibilityId == getAccessibilityViewId()) {
1579 return this;
1580 }
Svetoslav5b578da2013-05-08 14:23:32 -07001581 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1582 }
1583
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001584 /**
1585 * Indicates whether the children's drawing cache is used during a scroll.
1586 * By default, the drawing cache is enabled but this will consume more memory.
1587 *
1588 * @return true if the scrolling cache is enabled, false otherwise
1589 *
1590 * @see #setScrollingCacheEnabled(boolean)
1591 * @see View#setDrawingCacheEnabled(boolean)
1592 */
1593 @ViewDebug.ExportedProperty
1594 public boolean isScrollingCacheEnabled() {
1595 return mScrollingCacheEnabled;
1596 }
1597
1598 /**
1599 * Enables or disables the children's drawing cache during a scroll.
1600 * By default, the drawing cache is enabled but this will use more memory.
1601 *
1602 * When the scrolling cache is enabled, the caches are kept after the
1603 * first scrolling. You can manually clear the cache by calling
1604 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1605 *
1606 * @param enabled true to enable the scroll cache, false otherwise
1607 *
1608 * @see #isScrollingCacheEnabled()
1609 * @see View#setDrawingCacheEnabled(boolean)
1610 */
1611 public void setScrollingCacheEnabled(boolean enabled) {
1612 if (mScrollingCacheEnabled && !enabled) {
1613 clearScrollingCache();
1614 }
1615 mScrollingCacheEnabled = enabled;
1616 }
1617
1618 /**
1619 * Enables or disables the type filter window. If enabled, typing when
1620 * this view has focus will filter the children to match the users input.
1621 * Note that the {@link Adapter} used by this view must implement the
1622 * {@link Filterable} interface.
1623 *
1624 * @param textFilterEnabled true to enable type filtering, false otherwise
1625 *
1626 * @see Filterable
1627 */
1628 public void setTextFilterEnabled(boolean textFilterEnabled) {
1629 mTextFilterEnabled = textFilterEnabled;
1630 }
1631
1632 /**
1633 * Indicates whether type filtering is enabled for this view
1634 *
1635 * @return true if type filtering is enabled, false otherwise
1636 *
1637 * @see #setTextFilterEnabled(boolean)
1638 * @see Filterable
1639 */
1640 @ViewDebug.ExportedProperty
1641 public boolean isTextFilterEnabled() {
1642 return mTextFilterEnabled;
1643 }
1644
1645 @Override
1646 public void getFocusedRect(Rect r) {
1647 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001648 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001649 // the focused rectangle of the selected view offset into the
1650 // coordinate space of this view.
1651 view.getFocusedRect(r);
1652 offsetDescendantRectToMyCoords(view, r);
1653 } else {
1654 // otherwise, just the norm
1655 super.getFocusedRect(r);
1656 }
1657 }
1658
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001659 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001660 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001661 com.android.internal.R.drawable.list_selector_background));
1662 }
1663
1664 /**
1665 * Indicates whether the content of this view is pinned to, or stacked from,
1666 * the bottom edge.
1667 *
1668 * @return true if the content is stacked from the bottom edge, false otherwise
1669 */
1670 @ViewDebug.ExportedProperty
1671 public boolean isStackFromBottom() {
1672 return mStackFromBottom;
1673 }
1674
1675 /**
1676 * When stack from bottom is set to true, the list fills its content starting from
1677 * the bottom of the view.
1678 *
1679 * @param stackFromBottom true to pin the view's content to the bottom edge,
1680 * false to pin the view's content to the top edge
1681 */
1682 public void setStackFromBottom(boolean stackFromBottom) {
1683 if (mStackFromBottom != stackFromBottom) {
1684 mStackFromBottom = stackFromBottom;
1685 requestLayoutIfNecessary();
1686 }
1687 }
1688
1689 void requestLayoutIfNecessary() {
1690 if (getChildCount() > 0) {
1691 resetList();
1692 requestLayout();
1693 invalidate();
1694 }
1695 }
1696
1697 static class SavedState extends BaseSavedState {
1698 long selectedId;
1699 long firstId;
1700 int viewTop;
1701 int position;
1702 int height;
1703 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001704 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001705 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001706 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001707 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001708
1709 /**
1710 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1711 */
1712 SavedState(Parcelable superState) {
1713 super(superState);
1714 }
1715
1716 /**
1717 * Constructor called from {@link #CREATOR}
1718 */
1719 private SavedState(Parcel in) {
1720 super(in);
1721 selectedId = in.readLong();
1722 firstId = in.readLong();
1723 viewTop = in.readInt();
1724 position = in.readInt();
1725 height = in.readInt();
1726 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001727 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001728 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001729 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001730 final int N = in.readInt();
1731 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001732 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001733 for (int i=0; i<N; i++) {
1734 final long key = in.readLong();
1735 final int value = in.readInt();
1736 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001737 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001738 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001739 }
1740
1741 @Override
1742 public void writeToParcel(Parcel out, int flags) {
1743 super.writeToParcel(out, flags);
1744 out.writeLong(selectedId);
1745 out.writeLong(firstId);
1746 out.writeInt(viewTop);
1747 out.writeInt(position);
1748 out.writeInt(height);
1749 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001750 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001751 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001752 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001753 final int N = checkIdState != null ? checkIdState.size() : 0;
1754 out.writeInt(N);
1755 for (int i=0; i<N; i++) {
1756 out.writeLong(checkIdState.keyAt(i));
1757 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001758 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001759 }
1760
1761 @Override
1762 public String toString() {
1763 return "AbsListView.SavedState{"
1764 + Integer.toHexString(System.identityHashCode(this))
1765 + " selectedId=" + selectedId
1766 + " firstId=" + firstId
1767 + " viewTop=" + viewTop
1768 + " position=" + position
1769 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001770 + " filter=" + filter
1771 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001772 }
1773
1774 public static final Parcelable.Creator<SavedState> CREATOR
1775 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001776 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001777 public SavedState createFromParcel(Parcel in) {
1778 return new SavedState(in);
1779 }
1780
Alan Viverette8fa327a2013-05-31 14:53:13 -07001781 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001782 public SavedState[] newArray(int size) {
1783 return new SavedState[size];
1784 }
1785 };
1786 }
1787
1788 @Override
1789 public Parcelable onSaveInstanceState() {
1790 /*
1791 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001792 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001793 * that happen early enough to keep from getting complaints
1794 * about having leaked the window.
1795 */
1796 dismissPopup();
1797
1798 Parcelable superState = super.onSaveInstanceState();
1799
1800 SavedState ss = new SavedState(superState);
1801
Dianne Hackborne181bd92012-09-25 14:15:15 -07001802 if (mPendingSync != null) {
1803 // Just keep what we last restored.
1804 ss.selectedId = mPendingSync.selectedId;
1805 ss.firstId = mPendingSync.firstId;
1806 ss.viewTop = mPendingSync.viewTop;
1807 ss.position = mPendingSync.position;
1808 ss.height = mPendingSync.height;
1809 ss.filter = mPendingSync.filter;
1810 ss.inActionMode = mPendingSync.inActionMode;
1811 ss.checkedItemCount = mPendingSync.checkedItemCount;
1812 ss.checkState = mPendingSync.checkState;
1813 ss.checkIdState = mPendingSync.checkIdState;
1814 return ss;
1815 }
1816
Dianne Hackborn99441c42010-12-15 11:02:55 -08001817 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001818 long selectedId = getSelectedItemId();
1819 ss.selectedId = selectedId;
1820 ss.height = getHeight();
1821
1822 if (selectedId >= 0) {
1823 // Remember the selection
1824 ss.viewTop = mSelectedTop;
1825 ss.position = getSelectedItemPosition();
1826 ss.firstId = INVALID_POSITION;
1827 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001828 if (haveChildren && mFirstPosition > 0) {
1829 // Remember the position of the first child.
1830 // We only do this if we are not currently at the top of
1831 // the list, for two reasons:
1832 // (1) The list may be in the process of becoming empty, in
1833 // which case mItemCount may not be 0, but if we try to
1834 // ask for any information about position 0 we will crash.
1835 // (2) Being "at the top" seems like a special case, anyway,
1836 // and the user wouldn't expect to end up somewhere else when
1837 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001838 View v = getChildAt(0);
1839 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001840 int firstPos = mFirstPosition;
1841 if (firstPos >= mItemCount) {
1842 firstPos = mItemCount - 1;
1843 }
1844 ss.position = firstPos;
1845 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001846 } else {
1847 ss.viewTop = 0;
1848 ss.firstId = INVALID_POSITION;
1849 ss.position = 0;
1850 }
1851 }
1852
1853 ss.filter = null;
1854 if (mFiltered) {
1855 final EditText textFilter = mTextFilter;
1856 if (textFilter != null) {
1857 Editable filterText = textFilter.getText();
1858 if (filterText != null) {
1859 ss.filter = filterText.toString();
1860 }
1861 }
1862 }
1863
Adam Powella0eeeac2010-11-05 11:55:05 -07001864 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1865
Adam Powell9a5cc282011-08-28 16:18:16 -07001866 if (mCheckStates != null) {
1867 ss.checkState = mCheckStates.clone();
1868 }
1869 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001870 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001871 final int count = mCheckedIdStates.size();
1872 for (int i = 0; i < count; i++) {
1873 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1874 }
1875 ss.checkIdState = idState;
1876 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001877 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001878
Adam Cohen335c3b62012-07-24 17:18:16 -07001879 if (mRemoteAdapter != null) {
1880 mRemoteAdapter.saveRemoteViewsCache();
1881 }
1882
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001883 return ss;
1884 }
1885
1886 @Override
1887 public void onRestoreInstanceState(Parcelable state) {
1888 SavedState ss = (SavedState) state;
1889
1890 super.onRestoreInstanceState(ss.getSuperState());
1891 mDataChanged = true;
1892
1893 mSyncHeight = ss.height;
1894
1895 if (ss.selectedId >= 0) {
1896 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001897 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001898 mSyncRowId = ss.selectedId;
1899 mSyncPosition = ss.position;
1900 mSpecificTop = ss.viewTop;
1901 mSyncMode = SYNC_SELECTED_POSITION;
1902 } else if (ss.firstId >= 0) {
1903 setSelectedPositionInt(INVALID_POSITION);
1904 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1905 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001906 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001907 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001908 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001909 mSyncRowId = ss.firstId;
1910 mSyncPosition = ss.position;
1911 mSpecificTop = ss.viewTop;
1912 mSyncMode = SYNC_FIRST_POSITION;
1913 }
1914
1915 setFilterText(ss.filter);
1916
Adam Powellf343e1b2010-08-13 18:27:04 -07001917 if (ss.checkState != null) {
1918 mCheckStates = ss.checkState;
1919 }
1920
1921 if (ss.checkIdState != null) {
1922 mCheckedIdStates = ss.checkIdState;
1923 }
1924
Adam Powell2614c6c2010-11-04 17:54:45 -07001925 mCheckedItemCount = ss.checkedItemCount;
1926
Adam Powella0eeeac2010-11-05 11:55:05 -07001927 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1928 mMultiChoiceModeCallback != null) {
1929 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1930 }
1931
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001932 requestLayout();
1933 }
1934
1935 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001936 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1937 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001938 }
1939
1940 /**
1941 * Sets the initial value for the text filter.
1942 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001943 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001944 * @see #setTextFilterEnabled
1945 */
1946 public void setFilterText(String filterText) {
1947 // TODO: Should we check for acceptFilter()?
1948 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1949 createTextFilter(false);
1950 // This is going to call our listener onTextChanged, but we might not
1951 // be ready to bring up a window yet
1952 mTextFilter.setText(filterText);
1953 mTextFilter.setSelection(filterText.length());
1954 if (mAdapter instanceof Filterable) {
1955 // if mPopup is non-null, then onTextChanged will do the filtering
1956 if (mPopup == null) {
1957 Filter f = ((Filterable) mAdapter).getFilter();
1958 f.filter(filterText);
1959 }
1960 // Set filtered to true so we will display the filter window when our main
1961 // window is ready
1962 mFiltered = true;
1963 mDataSetObserver.clearSavedState();
1964 }
1965 }
1966 }
1967
1968 /**
Romain Guy0a637162009-05-29 14:43:54 -07001969 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001970 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001971 */
1972 public CharSequence getTextFilter() {
1973 if (mTextFilterEnabled && mTextFilter != null) {
1974 return mTextFilter.getText();
1975 }
1976 return null;
1977 }
Romain Guy0a637162009-05-29 14:43:54 -07001978
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001979 @Override
1980 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1981 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1982 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001983 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001984 // Data may have changed while we were detached and it's valid
1985 // to change focus while detached. Refresh so we don't die.
1986 mDataChanged = true;
1987 mOldItemCount = mItemCount;
1988 mItemCount = mAdapter.getCount();
1989 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001990 resurrectSelection();
1991 }
1992 }
1993
1994 @Override
1995 public void requestLayout() {
1996 if (!mBlockLayoutRequests && !mInLayout) {
1997 super.requestLayout();
1998 }
1999 }
2000
2001 /**
2002 * The list is empty. Clear everything out.
2003 */
2004 void resetList() {
2005 removeAllViewsInLayout();
2006 mFirstPosition = 0;
2007 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07002008 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002009 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07002010 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002011 mOldSelectedPosition = INVALID_POSITION;
2012 mOldSelectedRowId = INVALID_ROW_ID;
2013 setSelectedPositionInt(INVALID_POSITION);
2014 setNextSelectedPositionInt(INVALID_POSITION);
2015 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002016 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002017 mSelectorRect.setEmpty();
2018 invalidate();
2019 }
2020
2021 @Override
2022 protected int computeVerticalScrollExtent() {
2023 final int count = getChildCount();
2024 if (count > 0) {
2025 if (mSmoothScrollbarEnabled) {
2026 int extent = count * 100;
2027
2028 View view = getChildAt(0);
2029 final int top = view.getTop();
2030 int height = view.getHeight();
2031 if (height > 0) {
2032 extent += (top * 100) / height;
2033 }
2034
2035 view = getChildAt(count - 1);
2036 final int bottom = view.getBottom();
2037 height = view.getHeight();
2038 if (height > 0) {
2039 extent -= ((bottom - getHeight()) * 100) / height;
2040 }
2041
2042 return extent;
2043 } else {
2044 return 1;
2045 }
2046 }
2047 return 0;
2048 }
2049
2050 @Override
2051 protected int computeVerticalScrollOffset() {
2052 final int firstPosition = mFirstPosition;
2053 final int childCount = getChildCount();
2054 if (firstPosition >= 0 && childCount > 0) {
2055 if (mSmoothScrollbarEnabled) {
2056 final View view = getChildAt(0);
2057 final int top = view.getTop();
2058 int height = view.getHeight();
2059 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002060 return Math.max(firstPosition * 100 - (top * 100) / height +
2061 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002062 }
2063 } else {
2064 int index;
2065 final int count = mItemCount;
2066 if (firstPosition == 0) {
2067 index = 0;
2068 } else if (firstPosition + childCount == count) {
2069 index = count;
2070 } else {
2071 index = firstPosition + childCount / 2;
2072 }
2073 return (int) (firstPosition + childCount * (index / (float) count));
2074 }
2075 }
2076 return 0;
2077 }
2078
2079 @Override
2080 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002081 int result;
2082 if (mSmoothScrollbarEnabled) {
2083 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002084 if (mScrollY != 0) {
2085 // Compensate for overscroll
2086 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2087 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002088 } else {
2089 result = mItemCount;
2090 }
2091 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002092 }
2093
2094 @Override
2095 protected float getTopFadingEdgeStrength() {
2096 final int count = getChildCount();
2097 final float fadeEdge = super.getTopFadingEdgeStrength();
2098 if (count == 0) {
2099 return fadeEdge;
2100 } else {
2101 if (mFirstPosition > 0) {
2102 return 1.0f;
2103 }
2104
2105 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002106 final float fadeLength = getVerticalFadingEdgeLength();
2107 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002108 }
2109 }
2110
2111 @Override
2112 protected float getBottomFadingEdgeStrength() {
2113 final int count = getChildCount();
2114 final float fadeEdge = super.getBottomFadingEdgeStrength();
2115 if (count == 0) {
2116 return fadeEdge;
2117 } else {
2118 if (mFirstPosition + count - 1 < mItemCount - 1) {
2119 return 1.0f;
2120 }
2121
2122 final int bottom = getChildAt(count - 1).getBottom();
2123 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002124 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002125 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002126 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002127 }
2128 }
2129
2130 @Override
2131 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2132 if (mSelector == null) {
2133 useDefaultSelector();
2134 }
2135 final Rect listPadding = mListPadding;
2136 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2137 listPadding.top = mSelectionTopPadding + mPaddingTop;
2138 listPadding.right = mSelectionRightPadding + mPaddingRight;
2139 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002140
2141 // Check if our previous measured size was at a point where we should scroll later.
2142 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2143 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002144 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002145 final View lastChild = getChildAt(childCount - 1);
2146 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002147 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002148 lastBottom <= listBottom;
2149 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002150 }
2151
Romain Guyd6a463a2009-05-21 23:10:10 -07002152 /**
2153 * Subclasses should NOT override this method but
2154 * {@link #layoutChildren()} instead.
2155 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002156 @Override
2157 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2158 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002159
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002160 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002161
Alan Viverette4b95cc72014-01-14 16:54:02 -08002162 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002163 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002164 for (int i = 0; i < childCount; i++) {
2165 getChildAt(i).forceLayout();
2166 }
2167 mRecycler.markChildrenDirty();
2168 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002170 layoutChildren();
Phil Weaver866ded52016-11-01 23:38:44 +00002171 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002172
2173 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002174
2175 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2176 if (mFastScroll != null) {
2177 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2178 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002179 }
2180
2181 /**
2182 * @hide
2183 */
2184 @Override
2185 protected boolean setFrame(int left, int top, int right, int bottom) {
2186 final boolean changed = super.setFrame(left, top, right, bottom);
2187
Romain Guyd6a463a2009-05-21 23:10:10 -07002188 if (changed) {
2189 // Reposition the popup when the frame has changed. This includes
2190 // translating the widget, not just changing its dimension. The
2191 // filter popup needs to follow the widget.
2192 final boolean visible = getWindowVisibility() == View.VISIBLE;
2193 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2194 positionPopup();
2195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002196 }
2197
2198 return changed;
2199 }
2200
Romain Guyd6a463a2009-05-21 23:10:10 -07002201 /**
2202 * Subclasses must override this method to layout their children.
2203 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002204 protected void layoutChildren() {
2205 }
2206
Alan Viverette5d565fa2013-10-30 11:09:03 -07002207 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002208 * @param focusedView view that holds accessibility focus
2209 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002210 * child contains accessibility focus
2211 */
Alan Viverette3e141622014-02-18 17:05:13 -08002212 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002213 ViewParent viewParent = focusedView.getParent();
2214 while ((viewParent instanceof View) && (viewParent != this)) {
2215 focusedView = (View) viewParent;
2216 viewParent = viewParent.getParent();
2217 }
2218
2219 if (!(viewParent instanceof View)) {
2220 return null;
2221 }
2222
2223 return focusedView;
2224 }
2225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002226 void updateScrollIndicators() {
2227 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002228 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002229 }
2230
2231 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002232 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002233 }
2234 }
2235
Alan Viverette947a9692014-09-25 12:43:47 -07002236 private boolean canScrollUp() {
2237 boolean canScrollUp;
2238 // 0th element is not visible
2239 canScrollUp = mFirstPosition > 0;
2240
2241 // ... Or top of 0th element is not visible
2242 if (!canScrollUp) {
2243 if (getChildCount() > 0) {
2244 View child = getChildAt(0);
2245 canScrollUp = child.getTop() < mListPadding.top;
2246 }
2247 }
2248
2249 return canScrollUp;
2250 }
2251
2252 private boolean canScrollDown() {
2253 boolean canScrollDown;
2254 int count = getChildCount();
2255
2256 // Last item is not visible
2257 canScrollDown = (mFirstPosition + count) < mItemCount;
2258
2259 // ... Or bottom of the last element is not visible
2260 if (!canScrollDown && count > 0) {
2261 View child = getChildAt(count - 1);
2262 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2263 }
2264
2265 return canScrollDown;
2266 }
2267
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002268 @Override
2269 @ViewDebug.ExportedProperty
2270 public View getSelectedView() {
2271 if (mItemCount > 0 && mSelectedPosition >= 0) {
2272 return getChildAt(mSelectedPosition - mFirstPosition);
2273 } else {
2274 return null;
2275 }
2276 }
2277
2278 /**
2279 * List padding is the maximum of the normal view's padding and the padding of the selector.
2280 *
2281 * @see android.view.View#getPaddingTop()
2282 * @see #getSelector()
2283 *
2284 * @return The top list padding.
2285 */
2286 public int getListPaddingTop() {
2287 return mListPadding.top;
2288 }
2289
2290 /**
2291 * List padding is the maximum of the normal view's padding and the padding of the selector.
2292 *
2293 * @see android.view.View#getPaddingBottom()
2294 * @see #getSelector()
2295 *
2296 * @return The bottom list padding.
2297 */
2298 public int getListPaddingBottom() {
2299 return mListPadding.bottom;
2300 }
2301
2302 /**
2303 * List padding is the maximum of the normal view's padding and the padding of the selector.
2304 *
2305 * @see android.view.View#getPaddingLeft()
2306 * @see #getSelector()
2307 *
2308 * @return The left list padding.
2309 */
2310 public int getListPaddingLeft() {
2311 return mListPadding.left;
2312 }
2313
2314 /**
2315 * List padding is the maximum of the normal view's padding and the padding of the selector.
2316 *
2317 * @see android.view.View#getPaddingRight()
2318 * @see #getSelector()
2319 *
2320 * @return The right list padding.
2321 */
2322 public int getListPaddingRight() {
2323 return mListPadding.right;
2324 }
2325
2326 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002327 * Gets a view and have it show the data associated with the specified
2328 * position. This is called when we have already discovered that the view
2329 * is not available for reuse in the recycle bin. The only choices left are
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002330 * converting an old view or making a new one.
2331 *
Alan Viverette26489e12016-07-07 16:39:27 -04002332 * @param position the position to display
2333 * @param outMetadata an array of at least 1 boolean where the first entry
2334 * will be set {@code true} if the view is currently
2335 * attached to the window, {@code false} otherwise (e.g.
2336 * newly-inflated or remained scrap for multiple layout
2337 * passes)
Mindy Pereira4e30d892010-11-24 15:32:39 -08002338 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002339 * @return A view displaying the data associated with the specified position
2340 */
Alan Viverette26489e12016-07-07 16:39:27 -04002341 View obtainView(int position, boolean[] outMetadata) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002342 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2343
Alan Viverette26489e12016-07-07 16:39:27 -04002344 outMetadata[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002345
Alan Viverette59511502013-12-09 13:49:25 -08002346 // Check whether we have a transient state view. Attempt to re-bind the
2347 // data and discard the view if we fail.
2348 final View transientView = mRecycler.getTransientStateView(position);
2349 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002350 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2351
2352 // If the view type hasn't changed, attempt to re-bind the data.
2353 if (params.viewType == mAdapter.getItemViewType(position)) {
2354 final View updatedView = mAdapter.getView(position, transientView, this);
2355
2356 // If we failed to re-bind the data, scrap the obtained view.
2357 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002358 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002359 mRecycler.addScrapView(updatedView, position);
2360 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002361 }
2362
Alan Viverette26489e12016-07-07 16:39:27 -04002363 outMetadata[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002364
2365 // Finish the temporary detach started in addScrapView().
2366 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002367 return transientView;
2368 }
2369
2370 final View scrapView = mRecycler.getScrapView(position);
2371 final View child = mAdapter.getView(position, scrapView, this);
2372 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002373 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002374 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002375 mRecycler.addScrapView(scrapView, position);
Alan Viverette26489e12016-07-07 16:39:27 -04002376 } else if (child.isTemporarilyDetached()) {
2377 outMetadata[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002378
Alan Viverette26489e12016-07-07 16:39:27 -04002379 // Finish the temporary detach started in addScrapView().
2380 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002381 }
Alan Viverette59511502013-12-09 13:49:25 -08002382 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002383
Alan Viverette59511502013-12-09 13:49:25 -08002384 if (mCacheColorHint != 0) {
2385 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2386 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002387
Alan Viverette59511502013-12-09 13:49:25 -08002388 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2389 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002390 }
2391
Alan Viverettee6be9c782014-02-26 18:16:36 -08002392 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002393
alanvc1d7e772012-05-08 14:47:24 -07002394 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2395 if (mAccessibilityDelegate == null) {
2396 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2397 }
alanvb72fe7a2012-08-27 16:44:25 -07002398 if (child.getAccessibilityDelegate() == null) {
2399 child.setAccessibilityDelegate(mAccessibilityDelegate);
2400 }
alanvc1d7e772012-05-08 14:47:24 -07002401 }
2402
Romain Guy5fade8c2013-07-10 16:36:18 -07002403 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2404
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002405 return child;
2406 }
2407
Alan Viverettee6be9c782014-02-26 18:16:36 -08002408 private void setItemViewLayoutParams(View child, int position) {
2409 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2410 LayoutParams lp;
2411 if (vlp == null) {
2412 lp = (LayoutParams) generateDefaultLayoutParams();
2413 } else if (!checkLayoutParams(vlp)) {
2414 lp = (LayoutParams) generateLayoutParams(vlp);
2415 } else {
2416 lp = (LayoutParams) vlp;
2417 }
2418
2419 if (mAdapterHasStableIds) {
2420 lp.itemId = mAdapter.getItemId(position);
2421 }
2422 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002423 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002424 if (lp != vlp) {
2425 child.setLayoutParams(lp);
2426 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002427 }
2428
alanvc1d7e772012-05-08 14:47:24 -07002429 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2430 @Override
2431 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2432 super.onInitializeAccessibilityNodeInfo(host, info);
2433
2434 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002435 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002436 }
2437
2438 @Override
2439 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002440 if (super.performAccessibilityAction(host, action, arguments)) {
2441 return true;
2442 }
2443
alanvc1d7e772012-05-08 14:47:24 -07002444 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002445 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002446 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002447 return false;
2448 }
2449
Alan Viverette92539d52015-09-14 10:49:25 -04002450 if (position >= mAdapter.getCount()) {
2451 // The position is no longer valid, likely due to a data set
2452 // change. We could fail here for all data set changes, since
2453 // there is a chance that the data bound to the view may no
2454 // longer exist at the same position within the adapter, but
2455 // it's more consistent with the standard touch interaction to
2456 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002457 return false;
2458 }
2459
Alan Viverette92539d52015-09-14 10:49:25 -04002460 final boolean isItemEnabled;
2461 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2462 if (lp instanceof AbsListView.LayoutParams) {
2463 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2464 } else {
2465 isItemEnabled = false;
2466 }
2467
2468 if (!isEnabled() || !isItemEnabled) {
2469 // Cannot perform actions on disabled items.
2470 return false;
2471 }
alanvc1d7e772012-05-08 14:47:24 -07002472
2473 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002474 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2475 if (getSelectedItemPosition() == position) {
2476 setSelection(INVALID_POSITION);
2477 return true;
2478 }
2479 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002480 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002481 if (getSelectedItemPosition() != position) {
2482 setSelection(position);
2483 return true;
2484 }
2485 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002486 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002487 if (isItemClickable(host)) {
2488 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002489 return performItemClick(host, position, id);
2490 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002491 } return false;
2492 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2493 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002494 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002495 return performLongPress(host, position, id);
2496 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002497 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002498 }
2499
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002500 return false;
alanvc1d7e772012-05-08 14:47:24 -07002501 }
2502 }
2503
Alan Viverette5b2081d2013-08-28 10:43:07 -07002504 /**
2505 * Initializes an {@link AccessibilityNodeInfo} with information about a
2506 * particular item in the list.
2507 *
2508 * @param view View representing the list item.
2509 * @param position Position of the list item within the adapter.
2510 * @param info Node info to populate.
2511 */
2512 public void onInitializeAccessibilityNodeInfoForItem(
2513 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002514 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002515 // The item doesn't exist, so there's not much we can do here.
2516 return;
2517 }
2518
Alan Viverette92539d52015-09-14 10:49:25 -04002519 final boolean isItemEnabled;
2520 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2521 if (lp instanceof AbsListView.LayoutParams) {
2522 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2523 } else {
2524 isItemEnabled = false;
2525 }
2526
2527 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002528 info.setEnabled(false);
2529 return;
2530 }
2531
2532 if (position == getSelectedItemPosition()) {
2533 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002534 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002535 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002536 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002537 }
2538
Alan Viverette92539d52015-09-14 10:49:25 -04002539 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002540 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002541 info.setClickable(true);
2542 }
2543
2544 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002545 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002546 info.setLongClickable(true);
2547 }
2548 }
2549
Alan Viverette92539d52015-09-14 10:49:25 -04002550 private boolean isItemClickable(View view) {
2551 return !view.hasFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002552 }
2553
Alan Viverettede399392014-05-01 17:20:55 -07002554 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002555 * Positions the selector in a way that mimics touch.
2556 */
2557 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002558 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002559 }
2560
2561 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002562 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002563 */
2564 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002565 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002566 final Rect bounds = mSelectorRect;
2567 final float x = bounds.exactCenterX();
2568 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002569 positionSelector(position, sel, true, x, y);
2570 } else {
2571 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002572 }
2573 }
2574
Dianne Hackborn079e2352010-10-18 17:02:43 -07002575 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002576 positionSelector(position, sel, false, -1, -1);
2577 }
2578
2579 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2580 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002581 if (position != INVALID_POSITION) {
2582 mSelectorPosition = position;
2583 }
2584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002585 final Rect selectorRect = mSelectorRect;
2586 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002587 if (sel instanceof SelectionBoundsAdjuster) {
2588 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2589 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002590
2591 // Adjust for selection padding.
2592 selectorRect.left -= mSelectionLeftPadding;
2593 selectorRect.top -= mSelectionTopPadding;
2594 selectorRect.right += mSelectionRightPadding;
2595 selectorRect.bottom += mSelectionBottomPadding;
2596
Alan Viverettea19ab342015-05-18 13:20:52 -07002597 // Update the child enabled state prior to updating the selector.
2598 final boolean isChildViewEnabled = sel.isEnabled();
2599 if (mIsChildViewEnabled != isChildViewEnabled) {
2600 mIsChildViewEnabled = isChildViewEnabled;
2601 }
2602
2603 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002604 final Drawable selector = mSelector;
2605 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002606 if (positionChanged) {
2607 // Wipe out the current selector state so that we can start
2608 // over in the new position with a fresh state.
2609 selector.setVisible(false, false);
2610 selector.setState(StateSet.NOTHING);
2611 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002612 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002613 if (positionChanged) {
2614 if (getVisibility() == VISIBLE) {
2615 selector.setVisible(true, false);
2616 }
Chet Haase2167b112014-12-19 16:37:18 -08002617 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002618 }
2619 if (manageHotspot) {
2620 selector.setHotspot(x, y);
2621 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002622 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002623 }
2624
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002625 @Override
2626 protected void dispatchDraw(Canvas canvas) {
2627 int saveCount = 0;
2628 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2629 if (clipToPadding) {
2630 saveCount = canvas.save();
2631 final int scrollX = mScrollX;
2632 final int scrollY = mScrollY;
2633 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2634 scrollX + mRight - mLeft - mPaddingRight,
2635 scrollY + mBottom - mTop - mPaddingBottom);
2636 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2637 }
2638
2639 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2640 if (!drawSelectorOnTop) {
2641 drawSelector(canvas);
2642 }
2643
2644 super.dispatchDraw(canvas);
2645
2646 if (drawSelectorOnTop) {
2647 drawSelector(canvas);
2648 }
2649
2650 if (clipToPadding) {
2651 canvas.restoreToCount(saveCount);
2652 mGroupFlags |= CLIP_TO_PADDING_MASK;
2653 }
2654 }
2655
2656 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002657 protected boolean isPaddingOffsetRequired() {
2658 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2659 }
2660
2661 @Override
2662 protected int getLeftPaddingOffset() {
2663 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2664 }
2665
2666 @Override
2667 protected int getTopPaddingOffset() {
2668 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2669 }
2670
2671 @Override
2672 protected int getRightPaddingOffset() {
2673 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2674 }
2675
2676 @Override
2677 protected int getBottomPaddingOffset() {
2678 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2679 }
2680
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002681 /**
2682 * @hide
2683 */
2684 @Override
2685 protected void internalSetPadding(int left, int top, int right, int bottom) {
2686 super.internalSetPadding(left, top, right, bottom);
2687 if (isLayoutRequested()) {
2688 handleBoundsChange();
2689 }
2690 }
2691
Adam Powell20232d02010-12-08 21:08:53 -08002692 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002693 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002694 handleBoundsChange();
Alan Viverette8636ace2013-10-31 15:41:31 -07002695 if (mFastScroll != null) {
2696 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002697 }
2698 }
2699
2700 /**
Yigit Boyar51b5caf2016-05-27 15:18:54 -07002701 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2702 * and force layouts all children that don't have exact measure specs.
2703 * <p>
2704 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2705 * fail to relayout them properly to accommodate for new bounds.
2706 */
2707 void handleBoundsChange() {
2708 final int childCount = getChildCount();
2709 if (childCount > 0) {
2710 mDataChanged = true;
2711 rememberSyncState();
2712 for (int i = 0; i < childCount; i++) {
2713 final View child = getChildAt(i);
2714 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2715 // force layout child unless it has exact specs
2716 if (lp == null || lp.width < 1 || lp.height < 1) {
2717 child.forceLayout();
2718 }
2719 }
2720 }
2721 }
2722
2723 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002724 * @return True if the current touch mode requires that we draw the selector in the pressed
2725 * state.
2726 */
2727 boolean touchModeDrawsInPressedState() {
2728 // FIXME use isPressed for this
2729 switch (mTouchMode) {
2730 case TOUCH_MODE_TAP:
2731 case TOUCH_MODE_DONE_WAITING:
2732 return true;
2733 default:
2734 return false;
2735 }
2736 }
2737
2738 /**
2739 * Indicates whether this view is in a state where the selector should be drawn. This will
2740 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2741 * the pressed state for an item.
2742 *
2743 * @return True if the selector should be shown
2744 */
2745 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002746 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002747 }
2748
2749 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002750 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002751 final Drawable selector = mSelector;
2752 selector.setBounds(mSelectorRect);
2753 selector.draw(canvas);
2754 }
2755 }
2756
2757 /**
2758 * Controls whether the selection highlight drawable should be drawn on top of the item or
2759 * behind it.
2760 *
2761 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2762 * is false.
2763 *
2764 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2765 */
2766 public void setDrawSelectorOnTop(boolean onTop) {
2767 mDrawSelectorOnTop = onTop;
2768 }
2769
2770 /**
2771 * Set a Drawable that should be used to highlight the currently selected item.
2772 *
2773 * @param resID A Drawable resource to use as the selection highlight.
2774 *
2775 * @attr ref android.R.styleable#AbsListView_listSelector
2776 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002777 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002778 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002779 }
2780
2781 public void setSelector(Drawable sel) {
2782 if (mSelector != null) {
2783 mSelector.setCallback(null);
2784 unscheduleDrawable(mSelector);
2785 }
2786 mSelector = sel;
2787 Rect padding = new Rect();
2788 sel.getPadding(padding);
2789 mSelectionLeftPadding = padding.left;
2790 mSelectionTopPadding = padding.top;
2791 mSelectionRightPadding = padding.right;
2792 mSelectionBottomPadding = padding.bottom;
2793 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002794 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002795 }
2796
2797 /**
2798 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2799 * selection in the list.
2800 *
2801 * @return the drawable used to display the selector
2802 */
2803 public Drawable getSelector() {
2804 return mSelector;
2805 }
2806
2807 /**
2808 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2809 * this is a long press.
2810 */
2811 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002812 if (!isEnabled() || !isClickable()) {
2813 return;
2814 }
2815
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002816 Drawable selector = mSelector;
2817 Rect selectorRect = mSelectorRect;
2818 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002819 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002820
2821 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2822
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002823 if (v != null) {
2824 if (v.hasFocusable()) return;
2825 v.setPressed(true);
2826 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002827 setPressed(true);
2828
2829 final boolean longClickable = isLongClickable();
2830 Drawable d = selector.getCurrent();
2831 if (d != null && d instanceof TransitionDrawable) {
2832 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002833 ((TransitionDrawable) d).startTransition(
2834 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002835 } else {
2836 ((TransitionDrawable) d).resetTransition();
2837 }
2838 }
2839 if (longClickable && !mDataChanged) {
2840 if (mPendingCheckForKeyLongPress == null) {
2841 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2842 }
2843 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2844 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2845 }
2846 }
2847 }
2848
2849 public void setScrollIndicators(View up, View down) {
2850 mScrollUp = up;
2851 mScrollDown = down;
2852 }
2853
Dianne Hackborn079e2352010-10-18 17:02:43 -07002854 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002855 final Drawable selector = mSelector;
2856 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002857 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002858 if (selector.setState(getDrawableStateForSelector())) {
2859 invalidateDrawable(selector);
2860 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002861 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002862 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002863 }
2864 }
2865 }
2866
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002867 @Override
2868 protected void drawableStateChanged() {
2869 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002870 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002871 }
2872
Alan Viverettef723c832015-02-03 16:31:46 -08002873 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002874 // If the child view is enabled then do the default behavior.
2875 if (mIsChildViewEnabled) {
2876 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002877 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002878 }
2879
2880 // The selector uses this View's drawable state. The selected child view
2881 // is disabled, so we need to remove the enabled state from the drawable
2882 // states.
2883 final int enabledState = ENABLED_STATE_SET[0];
2884
Alan Viverettef723c832015-02-03 16:31:46 -08002885 // If we don't have any extra space, it will return one of the static
2886 // state arrays, and clearing the enabled state on those arrays is a
2887 // bad thing! If we specify we need extra space, it will create+copy
2888 // into a new array that is safely mutable.
2889 final int[] state = onCreateDrawableState(1);
2890
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002891 int enabledPos = -1;
2892 for (int i = state.length - 1; i >= 0; i--) {
2893 if (state[i] == enabledState) {
2894 enabledPos = i;
2895 break;
2896 }
2897 }
2898
2899 // Remove the enabled state
2900 if (enabledPos >= 0) {
2901 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2902 state.length - enabledPos - 1);
2903 }
Romain Guy0a637162009-05-29 14:43:54 -07002904
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002905 return state;
2906 }
2907
2908 @Override
Alan Viverettef6d87ec2016-03-11 10:09:14 -05002909 public boolean verifyDrawable(@NonNull Drawable dr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002910 return mSelector == dr || super.verifyDrawable(dr);
2911 }
2912
2913 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002914 public void jumpDrawablesToCurrentState() {
2915 super.jumpDrawablesToCurrentState();
2916 if (mSelector != null) mSelector.jumpToCurrentState();
2917 }
2918
2919 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002920 protected void onAttachedToWindow() {
2921 super.onAttachedToWindow();
2922
2923 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002924 treeObserver.addOnTouchModeChangeListener(this);
2925 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2926 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002928
Romain Guy82afc7b2010-05-13 11:52:37 -07002929 if (mAdapter != null && mDataSetObserver == null) {
2930 mDataSetObserver = new AdapterDataSetObserver();
2931 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002932
2933 // Data may have changed while we were detached. Refresh.
2934 mDataChanged = true;
2935 mOldItemCount = mItemCount;
2936 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002937 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002938 }
2939
2940 @Override
2941 protected void onDetachedFromWindow() {
2942 super.onDetachedFromWindow();
2943
Alan Viverette462c2172014-02-24 12:24:11 -08002944 mIsDetaching = true;
2945
Romain Guy1f7f3c32009-07-22 11:25:42 -07002946 // Dismiss the popup in case onSaveInstanceState() was not invoked
2947 dismissPopup();
2948
Romain Guy21875052010-01-06 18:48:08 -08002949 // Detach any view left in the scrap heap
2950 mRecycler.clear();
2951
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002952 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002953 treeObserver.removeOnTouchModeChangeListener(this);
2954 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002955 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002956 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002957 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002958
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002959 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002960 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2961 mDataSetObserver = null;
2962 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002963
2964 if (mScrollStrictSpan != null) {
2965 mScrollStrictSpan.finish();
2966 mScrollStrictSpan = null;
2967 }
2968
2969 if (mFlingStrictSpan != null) {
2970 mFlingStrictSpan.finish();
2971 mFlingStrictSpan = null;
2972 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002973
2974 if (mFlingRunnable != null) {
2975 removeCallbacks(mFlingRunnable);
2976 }
2977
2978 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002979 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002980 }
2981
2982 if (mClearScrollingCache != null) {
2983 removeCallbacks(mClearScrollingCache);
2984 }
2985
2986 if (mPerformClick != null) {
2987 removeCallbacks(mPerformClick);
2988 }
2989
2990 if (mTouchModeReset != null) {
2991 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002992 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002993 }
Alan Viverette462c2172014-02-24 12:24:11 -08002994
2995 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002996 }
2997
2998 @Override
2999 public void onWindowFocusChanged(boolean hasWindowFocus) {
3000 super.onWindowFocusChanged(hasWindowFocus);
3001
3002 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3003
3004 if (!hasWindowFocus) {
3005 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08003006 if (mFlingRunnable != null) {
3007 removeCallbacks(mFlingRunnable);
3008 // let the fling runnable report it's new state which
3009 // should be idle
3010 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003011 if (mPositionScroller != null) {
3012 mPositionScroller.stop();
3013 }
Adam Powell45803472010-01-25 15:10:44 -08003014 if (mScrollY != 0) {
3015 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003016 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003017 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08003018 invalidate();
3019 }
Mark Wagner670dd812010-01-13 16:17:47 -08003020 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003021 // Always hide the type filter
3022 dismissPopup();
3023
3024 if (touchMode == TOUCH_MODE_OFF) {
3025 // Remember the last selected element
3026 mResurrectToPosition = mSelectedPosition;
3027 }
3028 } else {
Adam Powell97566042010-03-09 15:34:09 -08003029 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003030 // Show the type filter only if a filter is in effect
3031 showPopup();
3032 }
3033
3034 // If we changed touch mode since the last time we had focus
3035 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3036 // If we come back in trackball mode, we bring the selection back
3037 if (touchMode == TOUCH_MODE_OFF) {
3038 // This will trigger a layout
3039 resurrectSelection();
3040
3041 // If we come back in touch mode, then we want to hide the selector
3042 } else {
3043 hideSelector();
3044 mLayoutMode = LAYOUT_NORMAL;
3045 layoutChildren();
3046 }
3047 }
3048 }
3049
3050 mLastTouchMode = touchMode;
3051 }
3052
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003053 @Override
3054 public void onRtlPropertiesChanged(int layoutDirection) {
3055 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003056 if (mFastScroll != null) {
3057 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003058 }
3059 }
3060
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003061 /**
3062 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3063 * methods knows the view, position and ID of the item that received the
3064 * long press.
3065 *
3066 * @param view The view that received the long press.
3067 * @param position The position of the item that received the long press.
3068 * @param id The ID of the item that received the long press.
3069 * @return The extra information that should be returned by
3070 * {@link #getContextMenuInfo()}.
3071 */
3072 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3073 return new AdapterContextMenuInfo(view, position, id);
3074 }
3075
Adam Powell14874662013-07-18 19:42:41 -07003076 @Override
3077 public void onCancelPendingInputEvents() {
3078 super.onCancelPendingInputEvents();
3079 if (mPerformClick != null) {
3080 removeCallbacks(mPerformClick);
3081 }
3082 if (mPendingCheckForTap != null) {
3083 removeCallbacks(mPendingCheckForTap);
3084 }
3085 if (mPendingCheckForLongPress != null) {
3086 removeCallbacks(mPendingCheckForLongPress);
3087 }
3088 if (mPendingCheckForKeyLongPress != null) {
3089 removeCallbacks(mPendingCheckForKeyLongPress);
3090 }
3091 }
3092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003093 /**
3094 * A base class for Runnables that will check that their view is still attached to
3095 * the original window as when the Runnable was created.
3096 *
3097 */
3098 private class WindowRunnnable {
3099 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003100
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003101 public void rememberWindowAttachCount() {
3102 mOriginalAttachCount = getWindowAttachCount();
3103 }
Romain Guy0a637162009-05-29 14:43:54 -07003104
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003105 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003106 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003107 }
3108 }
Romain Guy0a637162009-05-29 14:43:54 -07003109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003110 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003111 int mClickMotionPosition;
3112
Alan Viverette8fa327a2013-05-31 14:53:13 -07003113 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003114 public void run() {
3115 // The data has changed since we posted this action in the event queue,
3116 // bail out before bad things happen
3117 if (mDataChanged) return;
3118
Adam Powell005c0a42010-03-30 16:26:36 -07003119 final ListAdapter adapter = mAdapter;
3120 final int motionPosition = mClickMotionPosition;
3121 if (adapter != null && mItemCount > 0 &&
3122 motionPosition != INVALID_POSITION &&
Yigit Boyar418d0cf2016-03-01 16:09:58 -08003123 motionPosition < adapter.getCount() && sameWindow() &&
3124 adapter.isEnabled(motionPosition)) {
Romain Guy7890fe22011-01-18 20:24:18 -08003125 final View view = getChildAt(motionPosition - mFirstPosition);
3126 // If there is no view, something bad happened (the view scrolled off the
3127 // screen, etc.) and we should cancel the click
3128 if (view != null) {
3129 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3130 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003131 }
3132 }
3133 }
3134
3135 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003136 private static final int INVALID_COORD = -1;
3137 private float mX = INVALID_COORD;
3138 private float mY = INVALID_COORD;
3139
3140 private void setCoords(float x, float y) {
3141 mX = x;
3142 mY = y;
3143 }
3144
Alan Viverette8fa327a2013-05-31 14:53:13 -07003145 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003146 public void run() {
3147 final int motionPosition = mMotionPosition;
3148 final View child = getChildAt(motionPosition - mFirstPosition);
3149 if (child != null) {
3150 final int longPressPosition = mMotionPosition;
3151 final long longPressId = mAdapter.getItemId(mMotionPosition);
3152
3153 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003154 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003155 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3156 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3157 } else {
3158 handled = performLongPress(child, longPressPosition, longPressId);
3159 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003160 }
Alan Viverette66df60f2016-01-28 14:56:07 -05003161
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003162 if (handled) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003163 mHasPerformedLongPress = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003164 mTouchMode = TOUCH_MODE_REST;
3165 setPressed(false);
3166 child.setPressed(false);
3167 } else {
3168 mTouchMode = TOUCH_MODE_DONE_WAITING;
3169 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003170 }
3171 }
3172 }
Romain Guy0a637162009-05-29 14:43:54 -07003173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003174 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003175 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003176 public void run() {
3177 if (isPressed() && mSelectedPosition >= 0) {
3178 int index = mSelectedPosition - mFirstPosition;
3179 View v = getChildAt(index);
3180
3181 if (!mDataChanged) {
3182 boolean handled = false;
3183 if (sameWindow()) {
3184 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3185 }
3186 if (handled) {
3187 setPressed(false);
3188 v.setPressed(false);
3189 }
3190 } else {
3191 setPressed(false);
3192 if (v != null) v.setPressed(false);
3193 }
3194 }
3195 }
3196 }
3197
Mady Mellore5561982015-04-14 15:06:40 -07003198 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003199 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003200 final View child = getChildAt(mMotionPosition - mFirstPosition);
3201 if (child != null) {
3202 final int longPressPosition = mMotionPosition;
3203 final long longPressId = mAdapter.getItemId(mMotionPosition);
3204 if (performLongPress(child, longPressPosition, longPressId)) {
3205 mTouchMode = TOUCH_MODE_REST;
3206 setPressed(false);
3207 child.setPressed(false);
3208 return true;
3209 }
3210 }
3211 }
3212 return false;
3213 }
3214
Adam Powell8350f7d2010-07-28 14:27:28 -07003215 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003216 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003217 return performLongPress(
3218 child,
3219 longPressPosition,
3220 longPressId,
3221 CheckForLongPress.INVALID_COORD,
3222 CheckForLongPress.INVALID_COORD);
3223 }
3224
3225 boolean performLongPress(final View child,
3226 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003227 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3228 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003229 if (mChoiceActionMode == null &&
3230 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003231 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003232 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003233 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003234 return true;
3235 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003236
Adam Powellf343e1b2010-08-13 18:27:04 -07003237 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003238 if (mOnItemLongClickListener != null) {
3239 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3240 longPressPosition, longPressId);
3241 }
3242 if (!handled) {
3243 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003244 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3245 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3246 } else {
3247 handled = super.showContextMenuForChild(AbsListView.this);
3248 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003249 }
3250 if (handled) {
3251 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3252 }
3253 return handled;
3254 }
3255
3256 @Override
3257 protected ContextMenuInfo getContextMenuInfo() {
3258 return mContextMenuInfo;
3259 }
3260
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003261 @Override
3262 public boolean showContextMenu() {
3263 return showContextMenuInternal(0, 0, false);
3264 }
3265
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003266 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003267 public boolean showContextMenu(float x, float y) {
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003268 return showContextMenuInternal(x, y, true);
3269 }
3270
3271 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003272 final int position = pointToPosition((int)x, (int)y);
3273 if (position != INVALID_POSITION) {
3274 final long id = mAdapter.getItemId(position);
3275 View child = getChildAt(position - mFirstPosition);
3276 if (child != null) {
3277 mContextMenuInfo = createContextMenuInfo(child, position, id);
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003278 if (useOffsets) {
3279 return super.showContextMenuForChild(this, x, y);
3280 } else {
3281 return super.showContextMenuForChild(this);
3282 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003283 }
3284 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003285 if (useOffsets) {
3286 return super.showContextMenu(x, y);
3287 } else {
3288 return super.showContextMenu();
3289 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003290 }
3291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003292 @Override
3293 public boolean showContextMenuForChild(View originalView) {
Adam Powell2af189a2016-02-05 15:52:02 -08003294 if (isShowingContextMenuWithCoords()) {
3295 return false;
3296 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003297 return showContextMenuForChildInternal(originalView, 0, 0, false);
3298 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003299
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003300 @Override
3301 public boolean showContextMenuForChild(View originalView, float x, float y) {
3302 return showContextMenuForChildInternal(originalView,x, y, true);
3303 }
3304
3305 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3306 boolean useOffsets) {
3307 final int longPressPosition = getPositionForView(originalView);
3308 if (longPressPosition < 0) {
3309 return false;
3310 }
3311
3312 final long longPressId = mAdapter.getItemId(longPressPosition);
3313 boolean handled = false;
3314
3315 if (mOnItemLongClickListener != null) {
3316 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3317 longPressPosition, longPressId);
3318 }
3319
3320 if (!handled) {
3321 final View child = getChildAt(longPressPosition - mFirstPosition);
3322 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3323
3324 if (useOffsets) {
3325 handled = super.showContextMenuForChild(originalView, x, y);
3326 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003327 handled = super.showContextMenuForChild(originalView);
3328 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003329 }
Alan Viverette62bbd1a2016-01-21 14:47:30 -05003330
3331 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003332 }
3333
3334 @Override
Romain Guydf016072009-08-17 12:51:30 -07003335 public boolean onKeyDown(int keyCode, KeyEvent event) {
3336 return false;
3337 }
3338
3339 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003340 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003341 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003342 if (!isEnabled()) {
3343 return true;
3344 }
Romain Guydf016072009-08-17 12:51:30 -07003345 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003346 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003347 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003348
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003349 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003350 if (view != null) {
3351 performItemClick(view, mSelectedPosition, mSelectedRowId);
3352 view.setPressed(false);
3353 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003354 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003355 return true;
3356 }
3357 }
3358 return super.onKeyUp(keyCode, event);
3359 }
3360
3361 @Override
3362 protected void dispatchSetPressed(boolean pressed) {
3363 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3364 // get the selector in the right state, but we don't want to press each child.
3365 }
3366
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003367 @Override
3368 public void dispatchDrawableHotspotChanged(float x, float y) {
3369 // Don't dispatch hotspot changes to children. We'll manually handle
3370 // calling drawableHotspotChanged on the correct child.
3371 }
3372
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003373 /**
3374 * Maps a point to a position in the list.
3375 *
3376 * @param x X in local coordinate
3377 * @param y Y in local coordinate
3378 * @return The position of the item which contains the specified point, or
3379 * {@link #INVALID_POSITION} if the point does not intersect an item.
3380 */
3381 public int pointToPosition(int x, int y) {
3382 Rect frame = mTouchFrame;
3383 if (frame == null) {
3384 mTouchFrame = new Rect();
3385 frame = mTouchFrame;
3386 }
3387
3388 final int count = getChildCount();
3389 for (int i = count - 1; i >= 0; i--) {
3390 final View child = getChildAt(i);
3391 if (child.getVisibility() == View.VISIBLE) {
3392 child.getHitRect(frame);
3393 if (frame.contains(x, y)) {
3394 return mFirstPosition + i;
3395 }
3396 }
3397 }
3398 return INVALID_POSITION;
3399 }
3400
3401
3402 /**
3403 * Maps a point to a the rowId of the item which intersects that point.
3404 *
3405 * @param x X in local coordinate
3406 * @param y Y in local coordinate
3407 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3408 * if the point does not intersect an item.
3409 */
3410 public long pointToRowId(int x, int y) {
3411 int position = pointToPosition(x, y);
3412 if (position >= 0) {
3413 return mAdapter.getItemId(position);
3414 }
3415 return INVALID_ROW_ID;
3416 }
3417
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003418 private final class CheckForTap implements Runnable {
3419 float x;
3420 float y;
3421
Alan Viverette8fa327a2013-05-31 14:53:13 -07003422 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003423 public void run() {
3424 if (mTouchMode == TOUCH_MODE_DOWN) {
3425 mTouchMode = TOUCH_MODE_TAP;
3426 final View child = getChildAt(mMotionPosition - mFirstPosition);
3427 if (child != null && !child.hasFocusable()) {
3428 mLayoutMode = LAYOUT_NORMAL;
3429
3430 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003431 final float[] point = mTmpPoint;
3432 point[0] = x;
3433 point[1] = y;
3434 transformPointToViewLocal(point, child);
3435 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003436 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003437 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003438 layoutChildren();
3439 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003440 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003441
3442 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3443 final boolean longClickable = isLongClickable();
3444
3445 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003446 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003447 if (d != null && d instanceof TransitionDrawable) {
3448 if (longClickable) {
3449 ((TransitionDrawable) d).startTransition(longPressTimeout);
3450 } else {
3451 ((TransitionDrawable) d).resetTransition();
3452 }
3453 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003454 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003455 }
3456
3457 if (longClickable) {
3458 if (mPendingCheckForLongPress == null) {
3459 mPendingCheckForLongPress = new CheckForLongPress();
3460 }
Oren Blasberged391262015-09-01 12:12:51 -07003461 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003462 mPendingCheckForLongPress.rememberWindowAttachCount();
3463 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3464 } else {
3465 mTouchMode = TOUCH_MODE_DONE_WAITING;
3466 }
3467 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003468 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003469 }
3470 }
3471 }
3472 }
3473 }
3474
Adam Powellc501db9f2014-05-08 12:50:10 -07003475 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003476 // Check if we have moved far enough that it looks more like a
3477 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003478 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003479 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003480 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003481 if ((overscroll || distance > mTouchSlop) &&
3482 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003483 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003484 if (overscroll) {
3485 mTouchMode = TOUCH_MODE_OVERSCROLL;
3486 mMotionCorrection = 0;
3487 } else {
3488 mTouchMode = TOUCH_MODE_SCROLL;
3489 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3490 }
Alan Viverette74ded292013-06-03 15:34:11 -07003491 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003492 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003493 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003494 if (motionView != null) {
3495 motionView.setPressed(false);
3496 }
3497 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3498 // Time to start stealing events! Once we've stolen them, don't let anyone
3499 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003500 final ViewParent parent = getParent();
3501 if (parent != null) {
3502 parent.requestDisallowInterceptTouchEvent(true);
3503 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003504 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003505 return true;
3506 }
3507
3508 return false;
3509 }
3510
Adam Powellc501db9f2014-05-08 12:50:10 -07003511 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003512 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003513 int scrollOffsetCorrection = 0;
3514 int scrollConsumedCorrection = 0;
3515 if (mLastY == Integer.MIN_VALUE) {
3516 rawDeltaY -= mMotionCorrection;
3517 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003518 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3519 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003520 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003521 scrollOffsetCorrection = -mScrollOffset[1];
3522 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003523 if (vtev != null) {
3524 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003525 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003526 }
3527 }
Yorke Lee43943d82014-05-08 10:15:20 -07003528 final int deltaY = rawDeltaY;
3529 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003530 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003531 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003532
3533 if (mTouchMode == TOUCH_MODE_SCROLL) {
3534 if (PROFILE_SCROLLING) {
3535 if (!mScrollProfilingStarted) {
3536 Debug.startMethodTracing("AbsListViewScroll");
3537 mScrollProfilingStarted = true;
3538 }
3539 }
3540
3541 if (mScrollStrictSpan == null) {
3542 // If it's non-null, we're already in a scroll.
3543 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3544 }
3545
3546 if (y != mLastY) {
3547 // We may be here after stopping a fling and continuing to scroll.
3548 // If so, we haven't disallowed intercepting touch events yet.
3549 // Make sure that we do so in case we're in a parent that can intercept.
3550 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3551 Math.abs(rawDeltaY) > mTouchSlop) {
3552 final ViewParent parent = getParent();
3553 if (parent != null) {
3554 parent.requestDisallowInterceptTouchEvent(true);
3555 }
3556 }
3557
3558 final int motionIndex;
3559 if (mMotionPosition >= 0) {
3560 motionIndex = mMotionPosition - mFirstPosition;
3561 } else {
3562 // If we don't have a motion position that we can reliably track,
3563 // pick something in the middle to make a best guess at things below.
3564 motionIndex = getChildCount() / 2;
3565 }
3566
3567 int motionViewPrevTop = 0;
3568 View motionView = this.getChildAt(motionIndex);
3569 if (motionView != null) {
3570 motionViewPrevTop = motionView.getTop();
3571 }
3572
3573 // No need to do all this work if we're not going to move anyway
3574 boolean atEdge = false;
3575 if (incrementalDeltaY != 0) {
3576 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3577 }
3578
3579 // Check to see if we have bumped into the scroll limit
3580 motionView = this.getChildAt(motionIndex);
3581 if (motionView != null) {
3582 // Check if the top of the motion view is where it is
3583 // supposed to be
3584 final int motionViewRealTop = motionView.getTop();
3585 if (atEdge) {
3586 // Apply overscroll
3587
3588 int overscroll = -incrementalDeltaY -
3589 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003590 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3591 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003592 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003593 if (vtev != null) {
3594 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003595 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003596 }
Adam Powell96d62af2014-05-02 10:04:38 -07003597 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003598 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3599 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3600
3601 if (atOverscrollEdge && mVelocityTracker != null) {
3602 // Don't allow overfling if we're at the edge
3603 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003604 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003605
Adam Powell96d62af2014-05-02 10:04:38 -07003606 final int overscrollMode = getOverScrollMode();
3607 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3608 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3609 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003610 if (!atOverscrollEdge) {
3611 mDirection = 0; // Reset when entering overscroll.
3612 mTouchMode = TOUCH_MODE_OVERSCROLL;
3613 }
3614 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003615 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003616 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003617 if (!mEdgeGlowBottom.isFinished()) {
3618 mEdgeGlowBottom.onRelease();
3619 }
Doris Liuf36c0612015-06-04 11:11:14 -07003620 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003621 } else if (incrementalDeltaY < 0) {
3622 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3623 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003624 if (!mEdgeGlowTop.isFinished()) {
3625 mEdgeGlowTop.onRelease();
3626 }
Doris Liuf36c0612015-06-04 11:11:14 -07003627 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003628 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003629 }
3630 }
3631 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003632 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003633 }
Yorke Lee43943d82014-05-08 10:15:20 -07003634 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003635 }
3636 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3637 if (y != mLastY) {
3638 final int oldScroll = mScrollY;
3639 final int newScroll = oldScroll - incrementalDeltaY;
3640 int newDirection = y > mLastY ? 1 : -1;
3641
3642 if (mDirection == 0) {
3643 mDirection = newDirection;
3644 }
3645
3646 int overScrollDistance = -incrementalDeltaY;
3647 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3648 overScrollDistance = -oldScroll;
3649 incrementalDeltaY += overScrollDistance;
3650 } else {
3651 incrementalDeltaY = 0;
3652 }
3653
3654 if (overScrollDistance != 0) {
3655 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3656 0, mOverscrollDistance, true);
3657 final int overscrollMode = getOverScrollMode();
3658 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3659 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3660 !contentFits())) {
3661 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003662 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3663 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003664 if (!mEdgeGlowBottom.isFinished()) {
3665 mEdgeGlowBottom.onRelease();
3666 }
Doris Liuf36c0612015-06-04 11:11:14 -07003667 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003668 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003669 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3670 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003671 if (!mEdgeGlowTop.isFinished()) {
3672 mEdgeGlowTop.onRelease();
3673 }
Doris Liuf36c0612015-06-04 11:11:14 -07003674 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003675 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003676 }
3677 }
3678
3679 if (incrementalDeltaY != 0) {
3680 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003681 if (mScrollY != 0) {
3682 mScrollY = 0;
3683 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003684 }
3685
Romain Guy9d849a22012-03-14 16:41:42 -07003686 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3687
Jeff Brown78f6e632011-09-09 17:15:31 -07003688 mTouchMode = TOUCH_MODE_SCROLL;
3689
3690 // We did not scroll the full amount. Treat this essentially like the
3691 // start of a new touch scroll
3692 final int motionPosition = findClosestMotionRow(y);
3693
3694 mMotionCorrection = 0;
3695 View motionView = getChildAt(motionPosition - mFirstPosition);
3696 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003697 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003698 mMotionPosition = motionPosition;
3699 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003700 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003701 mDirection = newDirection;
3702 }
3703 }
3704 }
3705
Doris Liuf36c0612015-06-04 11:11:14 -07003706 private void invalidateTopGlow() {
3707 if (mEdgeGlowTop == null) {
3708 return;
3709 }
3710 final boolean clipToPadding = getClipToPadding();
3711 final int top = clipToPadding ? mPaddingTop : 0;
3712 final int left = clipToPadding ? mPaddingLeft : 0;
3713 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3714 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3715 }
3716
3717 private void invalidateBottomGlow() {
3718 if (mEdgeGlowBottom == null) {
3719 return;
3720 }
3721 final boolean clipToPadding = getClipToPadding();
3722 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3723 final int left = clipToPadding ? mPaddingLeft : 0;
3724 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3725 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3726 }
3727
Alan Viverette8fa327a2013-05-31 14:53:13 -07003728 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003729 public void onTouchModeChanged(boolean isInTouchMode) {
3730 if (isInTouchMode) {
3731 // Get rid of the selection when we enter touch mode
3732 hideSelector();
3733 // Layout, but only if we already have done so previously.
3734 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3735 // state.)
3736 if (getHeight() > 0 && getChildCount() > 0) {
3737 // We do not lose focus initiating a touch (since AbsListView is focusable in
3738 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003739 layoutChildren();
3740 }
Jeff Brown1e209462011-07-14 22:19:19 -07003741 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003742 } else {
3743 int touchMode = mTouchMode;
3744 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3745 if (mFlingRunnable != null) {
3746 mFlingRunnable.endFling();
3747 }
Adam Powell40322522011-01-12 21:58:20 -08003748 if (mPositionScroller != null) {
3749 mPositionScroller.stop();
3750 }
Adam Powell637d3372010-08-25 14:37:03 -07003751
3752 if (mScrollY != 0) {
3753 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003754 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003755 finishGlows();
3756 invalidate();
3757 }
3758 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003759 }
3760 }
3761
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +09003762 /** @hide */
3763 @Override
3764 protected boolean handleScrollBarDragging(MotionEvent event) {
3765 // Doesn't support normal scroll bar dragging. Use FastScroller.
3766 return false;
3767 }
3768
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003769 @Override
3770 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003771 if (!isEnabled()) {
3772 // A disabled view that is clickable still consumes the touch
3773 // events, it just doesn't respond to them.
3774 return isClickable() || isLongClickable();
3775 }
3776
Adam Powell1fa179ef2012-04-12 15:01:40 -07003777 if (mPositionScroller != null) {
3778 mPositionScroller.stop();
3779 }
3780
Alan Viverette462c2172014-02-24 12:24:11 -08003781 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003782 // Something isn't right.
3783 // Since we rely on being attached to get data set change notifications,
3784 // don't risk doing anything where we might try to resync and find things
3785 // in a bogus state.
3786 return false;
3787 }
3788
Adam Powell96d62af2014-05-02 10:04:38 -07003789 startNestedScroll(SCROLL_AXIS_VERTICAL);
3790
Alan Viverettefb99ba82015-05-01 10:10:15 -07003791 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3792 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003793 }
Romain Guy82f34952009-05-24 18:40:45 -07003794
Michael Jurka13451a42011-08-22 15:54:21 -07003795 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003796 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003797
Alan Viverette8fa327a2013-05-31 14:53:13 -07003798 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003799 if (actionMasked == MotionEvent.ACTION_DOWN) {
3800 mNestedYOffset = 0;
3801 }
3802 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003803 switch (actionMasked) {
3804 case MotionEvent.ACTION_DOWN: {
3805 onTouchDown(ev);
3806 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003807 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003808
Alan Viverette8fa327a2013-05-31 14:53:13 -07003809 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003810 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003811 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003812 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003813
3814 case MotionEvent.ACTION_UP: {
3815 onTouchUp(ev);
3816 break;
3817 }
3818
3819 case MotionEvent.ACTION_CANCEL: {
3820 onTouchCancel();
3821 break;
3822 }
3823
3824 case MotionEvent.ACTION_POINTER_UP: {
3825 onSecondaryPointerUp(ev);
3826 final int x = mMotionX;
3827 final int y = mMotionY;
3828 final int motionPosition = pointToPosition(x, y);
3829 if (motionPosition >= 0) {
3830 // Remember where the motion event started
3831 final View child = getChildAt(motionPosition - mFirstPosition);
3832 mMotionViewOriginalTop = child.getTop();
3833 mMotionPosition = motionPosition;
3834 }
3835 mLastY = y;
3836 break;
3837 }
3838
3839 case MotionEvent.ACTION_POINTER_DOWN: {
3840 // New pointers take over dragging duties
3841 final int index = ev.getActionIndex();
3842 final int id = ev.getPointerId(index);
3843 final int x = (int) ev.getX(index);
3844 final int y = (int) ev.getY(index);
3845 mMotionCorrection = 0;
3846 mActivePointerId = id;
3847 mMotionX = x;
3848 mMotionY = y;
3849 final int motionPosition = pointToPosition(x, y);
3850 if (motionPosition >= 0) {
3851 // Remember where the motion event started
3852 final View child = getChildAt(motionPosition - mFirstPosition);
3853 mMotionViewOriginalTop = child.getTop();
3854 mMotionPosition = motionPosition;
3855 }
3856 mLastY = y;
3857 break;
3858 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003859 }
3860
Adam Powell96d62af2014-05-02 10:04:38 -07003861 if (mVelocityTracker != null) {
3862 mVelocityTracker.addMovement(vtev);
3863 }
3864 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003865 return true;
3866 }
Romain Guy0a637162009-05-29 14:43:54 -07003867
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003868 private void onTouchDown(MotionEvent ev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003869 mHasPerformedLongPress = false;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003870 mActivePointerId = ev.getPointerId(0);
3871
3872 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3873 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003874 mFlingRunnable.endFling();
3875 if (mPositionScroller != null) {
3876 mPositionScroller.stop();
3877 }
3878 mTouchMode = TOUCH_MODE_OVERSCROLL;
3879 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003880 mMotionY = (int) ev.getY();
3881 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003882 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003883 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003884 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003885 final int x = (int) ev.getX();
3886 final int y = (int) ev.getY();
3887 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003888
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003889 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003890 if (mTouchMode == TOUCH_MODE_FLING) {
3891 // Stopped a fling. It is a scroll.
3892 createScrollingCache();
3893 mTouchMode = TOUCH_MODE_SCROLL;
3894 mMotionCorrection = 0;
3895 motionPosition = findMotionRow(y);
3896 mFlingRunnable.flywheelTouch();
3897 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3898 // User clicked on an actual view (and was not stopping a
3899 // fling). It might be a click or a scroll. Assume it is a
3900 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003901 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003902
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003903 // FIXME Debounce
3904 if (mPendingCheckForTap == null) {
3905 mPendingCheckForTap = new CheckForTap();
3906 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003907
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003908 mPendingCheckForTap.x = ev.getX();
3909 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003910 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003911 }
3912 }
3913
3914 if (motionPosition >= 0) {
3915 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003916 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003917 mMotionViewOriginalTop = v.getTop();
3918 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003919
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003920 mMotionX = x;
3921 mMotionY = y;
3922 mMotionPosition = motionPosition;
3923 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003924 }
3925
Alan Viveretteb339cc52013-08-12 13:29:15 -07003926 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003927 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003928 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003929 }
3930 }
3931
Adam Powell96d62af2014-05-02 10:04:38 -07003932 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverette66df60f2016-01-28 14:56:07 -05003933 if (mHasPerformedLongPress) {
3934 // Consume all move events following a successful long press.
3935 return;
3936 }
3937
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003938 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3939 if (pointerIndex == -1) {
3940 pointerIndex = 0;
3941 mActivePointerId = ev.getPointerId(pointerIndex);
3942 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003943
3944 if (mDataChanged) {
3945 // Re-sync everything if data has been changed
3946 // since the scroll operation can query the adapter.
3947 layoutChildren();
3948 }
3949
Alan Viverette8fa327a2013-05-31 14:53:13 -07003950 final int y = (int) ev.getY(pointerIndex);
3951
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003952 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003953 case TOUCH_MODE_DOWN:
3954 case TOUCH_MODE_TAP:
3955 case TOUCH_MODE_DONE_WAITING:
3956 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003957 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003958 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003959 break;
3960 }
3961 // Otherwise, check containment within list bounds. If we're
3962 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003963 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003964 final float x = ev.getX(pointerIndex);
3965 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003966 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003967 if (motionView != null) {
3968 motionView.setPressed(false);
3969 }
3970 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3971 mPendingCheckForTap : mPendingCheckForLongPress);
3972 mTouchMode = TOUCH_MODE_DONE_WAITING;
3973 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003974 } else if (motionView != null) {
3975 // Still within bounds, update the hotspot.
3976 final float[] point = mTmpPoint;
3977 point[0] = x;
3978 point[1] = y;
3979 transformPointToViewLocal(point, motionView);
3980 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003981 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003982 break;
3983 case TOUCH_MODE_SCROLL:
3984 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003985 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003986 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003987 }
3988 }
3989
3990 private void onTouchUp(MotionEvent ev) {
3991 switch (mTouchMode) {
3992 case TOUCH_MODE_DOWN:
3993 case TOUCH_MODE_TAP:
3994 case TOUCH_MODE_DONE_WAITING:
3995 final int motionPosition = mMotionPosition;
3996 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003997 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003998 if (mTouchMode != TOUCH_MODE_DOWN) {
3999 child.setPressed(false);
4000 }
4001
Alan Viverette74ded292013-06-03 15:34:11 -07004002 final float x = ev.getX();
4003 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
4004 if (inList && !child.hasFocusable()) {
4005 if (mPerformClick == null) {
4006 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004007 }
Alan Viverette74ded292013-06-03 15:34:11 -07004008
4009 final AbsListView.PerformClick performClick = mPerformClick;
4010 performClick.mClickMotionPosition = motionPosition;
4011 performClick.rememberWindowAttachCount();
4012
4013 mResurrectToPosition = motionPosition;
4014
4015 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4016 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4017 mPendingCheckForTap : mPendingCheckForLongPress);
4018 mLayoutMode = LAYOUT_NORMAL;
4019 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4020 mTouchMode = TOUCH_MODE_TAP;
4021 setSelectedPositionInt(mMotionPosition);
4022 layoutChildren();
4023 child.setPressed(true);
4024 positionSelector(mMotionPosition, child);
4025 setPressed(true);
4026 if (mSelector != null) {
4027 Drawable d = mSelector.getCurrent();
4028 if (d != null && d instanceof TransitionDrawable) {
4029 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004030 }
Alan Viverettec80ad992014-05-19 15:46:17 -07004031 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004032 }
Alan Viverette74ded292013-06-03 15:34:11 -07004033 if (mTouchModeReset != null) {
4034 removeCallbacks(mTouchModeReset);
4035 }
4036 mTouchModeReset = new Runnable() {
4037 @Override
4038 public void run() {
4039 mTouchModeReset = null;
4040 mTouchMode = TOUCH_MODE_REST;
4041 child.setPressed(false);
4042 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08004043 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07004044 performClick.run();
4045 }
4046 }
4047 };
4048 postDelayed(mTouchModeReset,
4049 ViewConfiguration.getPressedStateDuration());
4050 } else {
4051 mTouchMode = TOUCH_MODE_REST;
4052 updateSelectorState();
4053 }
4054 return;
4055 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4056 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004057 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004058 }
4059 }
4060 mTouchMode = TOUCH_MODE_REST;
4061 updateSelectorState();
4062 break;
4063 case TOUCH_MODE_SCROLL:
4064 final int childCount = getChildCount();
4065 if (childCount > 0) {
4066 final int firstChildTop = getChildAt(0).getTop();
4067 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4068 final int contentTop = mListPadding.top;
4069 final int contentBottom = getHeight() - mListPadding.bottom;
4070 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4071 mFirstPosition + childCount < mItemCount &&
4072 lastChildBottom <= getHeight() - contentBottom) {
4073 mTouchMode = TOUCH_MODE_REST;
4074 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4075 } else {
4076 final VelocityTracker velocityTracker = mVelocityTracker;
4077 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4078
4079 final int initialVelocity = (int)
4080 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4081 // Fling if we have enough velocity and we aren't at a boundary.
4082 // Since we can potentially overfling more than we can overscroll, don't
4083 // allow the weird behavior where you can scroll to a boundary then
4084 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07004085 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4086 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004087 !((mFirstPosition == 0 &&
4088 firstChildTop == contentTop - mOverscrollDistance) ||
4089 (mFirstPosition + childCount == mItemCount &&
4090 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07004091 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4092 if (mFlingRunnable == null) {
4093 mFlingRunnable = new FlingRunnable();
4094 }
4095 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4096 mFlingRunnable.start(-initialVelocity);
4097 dispatchNestedFling(0, -initialVelocity, true);
4098 } else {
4099 mTouchMode = TOUCH_MODE_REST;
4100 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004101 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004102 } else {
4103 mTouchMode = TOUCH_MODE_REST;
4104 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4105 if (mFlingRunnable != null) {
4106 mFlingRunnable.endFling();
4107 }
4108 if (mPositionScroller != null) {
4109 mPositionScroller.stop();
4110 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004111 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004112 dispatchNestedFling(0, -initialVelocity, false);
4113 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004114 }
4115 }
4116 } else {
4117 mTouchMode = TOUCH_MODE_REST;
4118 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4119 }
4120 break;
4121
4122 case TOUCH_MODE_OVERSCROLL:
4123 if (mFlingRunnable == null) {
4124 mFlingRunnable = new FlingRunnable();
4125 }
4126 final VelocityTracker velocityTracker = mVelocityTracker;
4127 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4128 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4129
4130 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4131 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4132 mFlingRunnable.startOverfling(-initialVelocity);
4133 } else {
4134 mFlingRunnable.startSpringback();
4135 }
4136
4137 break;
4138 }
4139
4140 setPressed(false);
4141
4142 if (mEdgeGlowTop != null) {
4143 mEdgeGlowTop.onRelease();
4144 mEdgeGlowBottom.onRelease();
4145 }
4146
4147 // Need to redraw since we probably aren't drawing the selector anymore
4148 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004149 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004150 recycleVelocityTracker();
4151
4152 mActivePointerId = INVALID_POINTER;
4153
4154 if (PROFILE_SCROLLING) {
4155 if (mScrollProfilingStarted) {
4156 Debug.stopMethodTracing();
4157 mScrollProfilingStarted = false;
4158 }
4159 }
4160
4161 if (mScrollStrictSpan != null) {
4162 mScrollStrictSpan.finish();
4163 mScrollStrictSpan = null;
4164 }
4165 }
4166
4167 private void onTouchCancel() {
4168 switch (mTouchMode) {
4169 case TOUCH_MODE_OVERSCROLL:
4170 if (mFlingRunnable == null) {
4171 mFlingRunnable = new FlingRunnable();
4172 }
4173 mFlingRunnable.startSpringback();
4174 break;
4175
4176 case TOUCH_MODE_OVERFLING:
4177 // Do nothing - let it play out.
4178 break;
4179
4180 default:
4181 mTouchMode = TOUCH_MODE_REST;
4182 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004183 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004184 if (motionView != null) {
4185 motionView.setPressed(false);
4186 }
4187 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004188 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004189 recycleVelocityTracker();
4190 }
4191
4192 if (mEdgeGlowTop != null) {
4193 mEdgeGlowTop.onRelease();
4194 mEdgeGlowBottom.onRelease();
4195 }
4196 mActivePointerId = INVALID_POINTER;
4197 }
4198
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004199 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004200 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4201 if (mScrollY != scrollY) {
4202 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4203 mScrollY = scrollY;
4204 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004205
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004206 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004207 }
Adam Powell637d3372010-08-25 14:37:03 -07004208 }
4209
4210 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004211 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -04004212 switch (event.getAction()) {
4213 case MotionEvent.ACTION_SCROLL:
4214 final float axisValue;
4215 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4216 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4217 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4218 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4219 } else {
4220 axisValue = 0;
4221 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004222
Ned Burns20ad0732016-08-18 14:22:57 -04004223 final int delta = Math.round(axisValue * mScrollFactor);
4224 if (delta != 0) {
4225 if (!trackMotionScroll(delta, delta)) {
4226 return true;
4227 }
4228 }
4229 break;
4230 case MotionEvent.ACTION_BUTTON_PRESS:
4231 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004232 int actionButton = event.getActionButton();
4233 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4234 || actionButton == MotionEvent.BUTTON_SECONDARY)
4235 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4236 if (performStylusButtonPressAction(event)) {
4237 removeCallbacks(mPendingCheckForLongPress);
4238 removeCallbacks(mPendingCheckForTap);
4239 }
4240 }
Ned Burns20ad0732016-08-18 14:22:57 -04004241 }
4242 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004243 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004244
Jeff Brown33bbfd22011-02-24 20:55:35 -08004245 return super.onGenericMotionEvent(event);
4246 }
4247
Adam Powell4884c642014-08-07 13:52:53 -07004248 /**
4249 * Initiate a fling with the given velocity.
4250 *
4251 * <p>Applications can use this method to manually initiate a fling as if the user
4252 * initiated it via touch interaction.</p>
4253 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004254 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4255 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004256 */
4257 public void fling(int velocityY) {
4258 if (mFlingRunnable == null) {
4259 mFlingRunnable = new FlingRunnable();
4260 }
4261 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004262 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004263 }
4264
Jeff Brown33bbfd22011-02-24 20:55:35 -08004265 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004266 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4267 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4268 }
4269
4270 @Override
4271 public void onNestedScrollAccepted(View child, View target, int axes) {
4272 super.onNestedScrollAccepted(child, target, axes);
4273 startNestedScroll(SCROLL_AXIS_VERTICAL);
4274 }
4275
4276 @Override
4277 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4278 int dxUnconsumed, int dyUnconsumed) {
4279 final int motionIndex = getChildCount() / 2;
4280 final View motionView = getChildAt(motionIndex);
4281 final int oldTop = motionView != null ? motionView.getTop() : 0;
4282 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4283 int myUnconsumed = dyUnconsumed;
4284 int myConsumed = 0;
4285 if (motionView != null) {
4286 myConsumed = motionView.getTop() - oldTop;
4287 myUnconsumed -= myConsumed;
4288 }
4289 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4290 }
4291 }
4292
4293 @Override
4294 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4295 final int childCount = getChildCount();
4296 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4297 Math.abs(velocityY) > mMinimumVelocity) {
4298 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4299 if (mFlingRunnable == null) {
4300 mFlingRunnable = new FlingRunnable();
4301 }
Adam Powell9413b242014-08-06 17:34:24 -07004302 if (!dispatchNestedPreFling(0, velocityY)) {
4303 mFlingRunnable.start((int) velocityY);
4304 }
Adam Powell96d62af2014-05-02 10:04:38 -07004305 return true;
4306 }
4307 return dispatchNestedFling(velocityX, velocityY, consumed);
4308 }
4309
4310 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004311 public void draw(Canvas canvas) {
4312 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004313 if (mEdgeGlowTop != null) {
4314 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004315 final boolean clipToPadding = getClipToPadding();
4316 final int width;
4317 final int height;
4318 final int translateX;
4319 final int translateY;
4320
4321 if (clipToPadding) {
4322 width = getWidth() - mPaddingLeft - mPaddingRight;
4323 height = getHeight() - mPaddingTop - mPaddingBottom;
4324 translateX = mPaddingLeft;
4325 translateY = mPaddingTop;
4326 } else {
4327 width = getWidth();
4328 height = getHeight();
4329 translateX = 0;
4330 translateY = 0;
4331 }
Adam Powell637d3372010-08-25 14:37:03 -07004332 if (!mEdgeGlowTop.isFinished()) {
4333 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004334 canvas.clipRect(translateX, translateY,
4335 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4336 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4337 canvas.translate(translateX, edgeY);
4338 mEdgeGlowTop.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004339 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004340 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004341 }
4342 canvas.restoreToCount(restoreCount);
4343 }
4344 if (!mEdgeGlowBottom.isFinished()) {
4345 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004346 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4347 translateX + width, translateY + height);
4348 final int edgeX = -width + translateX;
4349 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4350 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004351 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004352 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08004353 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004354 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004355 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004356 }
4357 canvas.restoreToCount(restoreCount);
4358 }
4359 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004360 }
4361
Michael Jurka13451a42011-08-22 15:54:21 -07004362 private void initOrResetVelocityTracker() {
4363 if (mVelocityTracker == null) {
4364 mVelocityTracker = VelocityTracker.obtain();
4365 } else {
4366 mVelocityTracker.clear();
4367 }
4368 }
4369
4370 private void initVelocityTrackerIfNotExists() {
4371 if (mVelocityTracker == null) {
4372 mVelocityTracker = VelocityTracker.obtain();
4373 }
4374 }
4375
4376 private void recycleVelocityTracker() {
4377 if (mVelocityTracker != null) {
4378 mVelocityTracker.recycle();
4379 mVelocityTracker = null;
4380 }
4381 }
4382
4383 @Override
4384 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4385 if (disallowIntercept) {
4386 recycleVelocityTracker();
4387 }
4388 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4389 }
4390
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004391 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004392 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004393 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004394 return true;
4395 }
4396
4397 return super.onInterceptHoverEvent(event);
4398 }
4399
4400 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004401 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004402 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004403 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004404
Adam Powell1fa179ef2012-04-12 15:01:40 -07004405 if (mPositionScroller != null) {
4406 mPositionScroller.stop();
4407 }
4408
Alan Viverette462c2172014-02-24 12:24:11 -08004409 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004410 // Something isn't right.
4411 // Since we rely on being attached to get data set change notifications,
4412 // don't risk doing anything where we might try to resync and find things
4413 // in a bogus state.
4414 return false;
4415 }
4416
Alan Viverette8636ace2013-10-31 15:41:31 -07004417 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004418 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004419 }
Romain Guy0a637162009-05-29 14:43:54 -07004420
Adam Powell744beff2014-09-22 09:47:48 -07004421 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004422 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004423 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004424 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4425 mMotionCorrection = 0;
4426 return true;
4427 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004428
Adam Powell4cd47702010-02-25 11:21:14 -08004429 final int x = (int) ev.getX();
4430 final int y = (int) ev.getY();
4431 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004432
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004433 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004434 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004435 // User clicked on an actual view (and was not stopping a fling).
4436 // Remember where the motion event started
4437 v = getChildAt(motionPosition - mFirstPosition);
4438 mMotionViewOriginalTop = v.getTop();
4439 mMotionX = x;
4440 mMotionY = y;
4441 mMotionPosition = motionPosition;
4442 mTouchMode = TOUCH_MODE_DOWN;
4443 clearScrollingCache();
4444 }
4445 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004446 initOrResetVelocityTracker();
4447 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004448 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004449 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004450 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004451 return true;
4452 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004453 break;
4454 }
4455
4456 case MotionEvent.ACTION_MOVE: {
4457 switch (mTouchMode) {
4458 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004459 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4460 if (pointerIndex == -1) {
4461 pointerIndex = 0;
4462 mActivePointerId = ev.getPointerId(pointerIndex);
4463 }
Adam Powell4cd47702010-02-25 11:21:14 -08004464 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004465 initVelocityTrackerIfNotExists();
4466 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004467 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004468 return true;
4469 }
4470 break;
4471 }
4472 break;
4473 }
4474
Michael Jurka13451a42011-08-22 15:54:21 -07004475 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004476 case MotionEvent.ACTION_UP: {
4477 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004478 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004479 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004480 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004481 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004482 break;
4483 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004484
Adam Powell4cd47702010-02-25 11:21:14 -08004485 case MotionEvent.ACTION_POINTER_UP: {
4486 onSecondaryPointerUp(ev);
4487 break;
4488 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004489 }
4490
4491 return false;
4492 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004493
Adam Powell4cd47702010-02-25 11:21:14 -08004494 private void onSecondaryPointerUp(MotionEvent ev) {
4495 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4496 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4497 final int pointerId = ev.getPointerId(pointerIndex);
4498 if (pointerId == mActivePointerId) {
4499 // This was our active pointer going up. Choose a new
4500 // active pointer and adjust accordingly.
4501 // TODO: Make this decision more intelligent.
4502 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4503 mMotionX = (int) ev.getX(newPointerIndex);
4504 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004505 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004506 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004507 }
4508 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004509
4510 /**
4511 * {@inheritDoc}
4512 */
4513 @Override
4514 public void addTouchables(ArrayList<View> views) {
4515 final int count = getChildCount();
4516 final int firstPosition = mFirstPosition;
4517 final ListAdapter adapter = mAdapter;
4518
4519 if (adapter == null) {
4520 return;
4521 }
4522
4523 for (int i = 0; i < count; i++) {
4524 final View child = getChildAt(i);
4525 if (adapter.isEnabled(firstPosition + i)) {
4526 views.add(child);
4527 }
4528 child.addTouchables(views);
4529 }
4530 }
4531
4532 /**
4533 * Fires an "on scroll state changed" event to the registered
4534 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4535 * is fired only if the specified state is different from the previously known state.
4536 *
4537 * @param newState The new scroll state.
4538 */
4539 void reportScrollStateChange(int newState) {
4540 if (newState != mLastScrollState) {
4541 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004542 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004543 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004544 }
4545 }
4546 }
4547
4548 /**
4549 * Responsible for fling behavior. Use {@link #start(int)} to
4550 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4551 * A FlingRunnable will keep re-posting itself until the fling is done.
4552 *
4553 */
4554 private class FlingRunnable implements Runnable {
4555 /**
4556 * Tracks the decay of a fling scroll
4557 */
Adam Powell637d3372010-08-25 14:37:03 -07004558 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004559
4560 /**
4561 * Y value reported by mScroller on the previous fling
4562 */
4563 private int mLastFlingY;
4564
Gilles Debunned348bb42010-11-15 12:19:35 -08004565 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004566 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004567 public void run() {
4568 final int activeId = mActivePointerId;
4569 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004570 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004571 if (vt == null || activeId == INVALID_POINTER) {
4572 return;
4573 }
4574
4575 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4576 final float yvel = -vt.getYVelocity(activeId);
4577
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004578 if (Math.abs(yvel) >= mMinimumVelocity
4579 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004580 // Keep the fling alive a little longer
4581 postDelayed(this, FLYWHEEL_TIMEOUT);
4582 } else {
4583 endFling();
4584 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004585 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004586 }
4587 }
4588 };
4589
4590 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4591
Adam Powell79ac3392010-01-28 21:22:20 -08004592 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004593 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004594 }
4595
Adam Powell79ac3392010-01-28 21:22:20 -08004596 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004597 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4598 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004599 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004600 mScroller.fling(0, initialY, 0, initialVelocity,
4601 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4602 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004603 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004604
4605 if (PROFILE_FLINGING) {
4606 if (!mFlingProfilingStarted) {
4607 Debug.startMethodTracing("AbsListViewFling");
4608 mFlingProfilingStarted = true;
4609 }
4610 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004611
4612 if (mFlingStrictSpan == null) {
4613 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4614 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004615 }
Adam Powell45803472010-01-25 15:10:44 -08004616
Adam Powell637d3372010-08-25 14:37:03 -07004617 void startSpringback() {
4618 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4619 mTouchMode = TOUCH_MODE_OVERFLING;
4620 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004621 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004622 } else {
4623 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004624 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004625 }
4626 }
4627
4628 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004629 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004630 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4631 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004632 mTouchMode = TOUCH_MODE_OVERFLING;
4633 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004634 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004635 }
4636
4637 void edgeReached(int delta) {
4638 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4639 final int overscrollMode = getOverScrollMode();
4640 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4641 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4642 mTouchMode = TOUCH_MODE_OVERFLING;
4643 final int vel = (int) mScroller.getCurrVelocity();
4644 if (delta > 0) {
4645 mEdgeGlowTop.onAbsorb(vel);
4646 } else {
4647 mEdgeGlowBottom.onAbsorb(vel);
4648 }
Adam Powell40322522011-01-12 21:58:20 -08004649 } else {
4650 mTouchMode = TOUCH_MODE_REST;
4651 if (mPositionScroller != null) {
4652 mPositionScroller.stop();
4653 }
Adam Powell637d3372010-08-25 14:37:03 -07004654 }
4655 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004656 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004657 }
4658
Adam Powell0b8acd82012-04-25 20:29:23 -07004659 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004660 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4661 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004662 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004663 mScroller.startScroll(0, initialY, 0, distance, duration);
4664 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004665 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004666 }
4667
Gilles Debunned348bb42010-11-15 12:19:35 -08004668 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004669 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004670
Adam Powell79ac3392010-01-28 21:22:20 -08004671 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004672 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004673
4674 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4675 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004676 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004677
4678 if (mFlingStrictSpan != null) {
4679 mFlingStrictSpan.finish();
4680 mFlingStrictSpan = null;
4681 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004682 }
4683
4684 void flywheelTouch() {
4685 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004686 }
4687
Alan Viverette8fa327a2013-05-31 14:53:13 -07004688 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004689 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004690 switch (mTouchMode) {
4691 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004692 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004693 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004694
Gilles Debunned348bb42010-11-15 12:19:35 -08004695 case TOUCH_MODE_SCROLL:
4696 if (mScroller.isFinished()) {
4697 return;
4698 }
4699 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004700 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004701 if (mDataChanged) {
4702 layoutChildren();
4703 }
4704
Adam Powell79ac3392010-01-28 21:22:20 -08004705 if (mItemCount == 0 || getChildCount() == 0) {
4706 endFling();
4707 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004708 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004709
Adam Powell637d3372010-08-25 14:37:03 -07004710 final OverScroller scroller = mScroller;
4711 boolean more = scroller.computeScrollOffset();
4712 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004713
Adam Powell637d3372010-08-25 14:37:03 -07004714 // Flip sign to convert finger direction to list items direction
4715 // (e.g. finger moving down means list is moving towards the top)
4716 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004717
Adam Powell637d3372010-08-25 14:37:03 -07004718 // Pretend that each frame of a fling scroll is a touch scroll
4719 if (delta > 0) {
4720 // List is moving towards the top. Use first view as mMotionPosition
4721 mMotionPosition = mFirstPosition;
4722 final View firstView = getChildAt(0);
4723 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004724
Adam Powell637d3372010-08-25 14:37:03 -07004725 // Don't fling more than 1 screen
4726 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4727 } else {
4728 // List is moving towards the bottom. Use last view as mMotionPosition
4729 int offsetToLast = getChildCount() - 1;
4730 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004731
Adam Powell637d3372010-08-25 14:37:03 -07004732 final View lastView = getChildAt(offsetToLast);
4733 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004734
Adam Powell637d3372010-08-25 14:37:03 -07004735 // Don't fling more than 1 screen
4736 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4737 }
Adam Powell79ac3392010-01-28 21:22:20 -08004738
Adam Powell637d3372010-08-25 14:37:03 -07004739 // Check to see if we have bumped into the scroll limit
4740 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4741 int oldTop = 0;
4742 if (motionView != null) {
4743 oldTop = motionView.getTop();
4744 }
Adam Powell9d32d242010-03-29 16:02:07 -07004745
Adam Powell637d3372010-08-25 14:37:03 -07004746 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004747 final boolean atEdge = trackMotionScroll(delta, delta);
4748 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004749 if (atEnd) {
4750 if (motionView != null) {
4751 // Tweak the scroll for how far we overshot
4752 int overshoot = -(delta - (motionView.getTop() - oldTop));
4753 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4754 0, mOverflingDistance, false);
4755 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004756 if (more) {
4757 edgeReached(delta);
4758 }
Adam Powell637d3372010-08-25 14:37:03 -07004759 break;
4760 }
4761
4762 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004763 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004764 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004765 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004766 } else {
4767 endFling();
4768
4769 if (PROFILE_FLINGING) {
4770 if (mFlingProfilingStarted) {
4771 Debug.stopMethodTracing();
4772 mFlingProfilingStarted = false;
4773 }
4774
4775 if (mFlingStrictSpan != null) {
4776 mFlingStrictSpan.finish();
4777 mFlingStrictSpan = null;
4778 }
Adam Powell79ac3392010-01-28 21:22:20 -08004779 }
4780 }
Adam Powell637d3372010-08-25 14:37:03 -07004781 break;
4782 }
4783
4784 case TOUCH_MODE_OVERFLING: {
4785 final OverScroller scroller = mScroller;
4786 if (scroller.computeScrollOffset()) {
4787 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004788 final int currY = scroller.getCurrY();
4789 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004790 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4791 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004792 final boolean crossDown = scrollY <= 0 && currY > 0;
4793 final boolean crossUp = scrollY >= 0 && currY < 0;
4794 if (crossDown || crossUp) {
4795 int velocity = (int) scroller.getCurrVelocity();
4796 if (crossUp) velocity = -velocity;
4797
4798 // Don't flywheel from this; we're just continuing things.
4799 scroller.abortAnimation();
4800 start(velocity);
4801 } else {
4802 startSpringback();
4803 }
Adam Powell637d3372010-08-25 14:37:03 -07004804 } else {
4805 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004806 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004807 }
4808 } else {
4809 endFling();
4810 }
4811 break;
4812 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004813 }
4814 }
4815 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004816
Adam Powell45803472010-01-25 15:10:44 -08004817 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004818 * The amount of friction applied to flings. The default value
4819 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004820 */
4821 public void setFriction(float friction) {
4822 if (mFlingRunnable == null) {
4823 mFlingRunnable = new FlingRunnable();
4824 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004825 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004826 }
Romain Guy21317d12010-10-12 13:32:31 -07004827
4828 /**
4829 * Sets a scale factor for the fling velocity. The initial scale
4830 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004831 *
Romain Guy21317d12010-10-12 13:32:31 -07004832 * @param scale The scale factor to multiply the velocity by.
4833 */
4834 public void setVelocityScale(float scale) {
4835 mVelocityScale = scale;
4836 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004837
Romain Guy4bede9e2010-10-11 19:36:59 -07004838 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004839 * Override this for better control over position scrolling.
4840 */
4841 AbsPositionScroller createPositionScroller() {
4842 return new PositionScroller();
4843 }
4844
4845 /**
Adam Powell45803472010-01-25 15:10:44 -08004846 * Smoothly scroll to the specified adapter position. The view will
4847 * scroll such that the indicated position is displayed.
4848 * @param position Scroll to this adapter position.
4849 */
4850 public void smoothScrollToPosition(int position) {
4851 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004852 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004853 }
4854 mPositionScroller.start(position);
4855 }
Erik322171b2010-10-13 15:46:00 -07004856
4857 /**
4858 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004859 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004860 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4861 * the first or last item beyond the boundaries of the list) it will get as close
4862 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4863 *
4864 * @param position Position to scroll to
4865 * @param offset Desired distance in pixels of <code>position</code> from the top
4866 * of the view when scrolling is finished
4867 * @param duration Number of milliseconds to use for the scroll
4868 */
4869 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4870 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004871 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004872 }
4873 mPositionScroller.startWithOffset(position, offset, duration);
4874 }
4875
Adam Powell45803472010-01-25 15:10:44 -08004876 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004877 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004878 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004879 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4880 * the first or last item beyond the boundaries of the list) it will get as close
4881 * as possible.
4882 *
4883 * @param position Position to scroll to
4884 * @param offset Desired distance in pixels of <code>position</code> from the top
4885 * of the view when scrolling is finished
4886 */
4887 public void smoothScrollToPositionFromTop(int position, int offset) {
4888 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004889 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004890 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004891 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004892 }
4893
4894 /**
Adam Powell45803472010-01-25 15:10:44 -08004895 * Smoothly scroll to the specified adapter position. The view will
4896 * scroll such that the indicated position is displayed, but it will
4897 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004898 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004899 *
Adam Powell45803472010-01-25 15:10:44 -08004900 * @param position Scroll to this adapter position.
4901 * @param boundPosition Do not scroll if it would move this adapter
4902 * position out of view.
4903 */
4904 public void smoothScrollToPosition(int position, int boundPosition) {
4905 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004906 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004907 }
4908 mPositionScroller.start(position, boundPosition);
4909 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004910
Adam Powell45803472010-01-25 15:10:44 -08004911 /**
4912 * Smoothly scroll by distance pixels over duration milliseconds.
4913 * @param distance Distance to scroll in pixels.
4914 * @param duration Duration of the scroll animation in milliseconds.
4915 */
4916 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004917 smoothScrollBy(distance, duration, false);
4918 }
4919
4920 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004921 if (mFlingRunnable == null) {
4922 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004923 }
Adam Powell40322522011-01-12 21:58:20 -08004924
Marc Blank299acb52010-10-21 11:03:53 -07004925 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004926 final int firstPos = mFirstPosition;
4927 final int childCount = getChildCount();
4928 final int lastPos = firstPos + childCount;
4929 final int topLimit = getPaddingTop();
4930 final int bottomLimit = getHeight() - getPaddingBottom();
4931
Adam Powell79303752011-01-13 22:06:49 -08004932 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004933 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004934 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004935 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4936 mFlingRunnable.endFling();
4937 if (mPositionScroller != null) {
4938 mPositionScroller.stop();
4939 }
4940 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004941 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004942 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004943 }
Adam Powell45803472010-01-25 15:10:44 -08004944 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004945
Winson Chung499cb9f2010-07-16 11:18:17 -07004946 /**
4947 * Allows RemoteViews to scroll relatively to a position.
4948 */
4949 void smoothScrollByOffset(int position) {
4950 int index = -1;
4951 if (position < 0) {
4952 index = getFirstVisiblePosition();
4953 } else if (position > 0) {
4954 index = getLastVisiblePosition();
4955 }
4956
4957 if (index > -1) {
4958 View child = getChildAt(index - getFirstVisiblePosition());
4959 if (child != null) {
4960 Rect visibleRect = new Rect();
4961 if (child.getGlobalVisibleRect(visibleRect)) {
4962 // the child is partially visible
4963 int childRectArea = child.getWidth() * child.getHeight();
4964 int visibleRectArea = visibleRect.width() * visibleRect.height();
4965 float visibleArea = (visibleRectArea / (float) childRectArea);
4966 final float visibleThreshold = 0.75f;
4967 if ((position < 0) && (visibleArea < visibleThreshold)) {
4968 // the top index is not perceivably visible so offset
4969 // to account for showing that top index as well
4970 ++index;
4971 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4972 // the bottom index is not perceivably visible so offset
4973 // to account for showing that bottom index as well
4974 --index;
4975 }
4976 }
4977 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4978 }
4979 }
4980 }
4981
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004982 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004983 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004984 setChildrenDrawnWithCacheEnabled(true);
4985 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004986 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004987 }
4988 }
4989
4990 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004991 if (!isHardwareAccelerated()) {
4992 if (mClearScrollingCache == null) {
4993 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004994 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07004995 public void run() {
4996 if (mCachingStarted) {
4997 mCachingStarted = mCachingActive = false;
4998 setChildrenDrawnWithCacheEnabled(false);
4999 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5000 setChildrenDrawingCacheEnabled(false);
5001 }
5002 if (!isAlwaysDrawnWithCacheEnabled()) {
5003 invalidate();
5004 }
Romain Guy6dfed242009-05-11 18:25:05 -07005005 }
5006 }
Romain Guy9d849a22012-03-14 16:41:42 -07005007 };
5008 }
5009 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005010 }
5011 }
5012
5013 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07005014 * Scrolls the list items within the view by a specified number of pixels.
5015 *
5016 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07005017 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07005018 */
Alan Viveretteba299062013-09-03 16:01:51 -07005019 public void scrollListBy(int y) {
5020 trackMotionScroll(-y, -y);
5021 }
5022
5023 /**
5024 * Check if the items in the list can be scrolled in a certain direction.
5025 *
5026 * @param direction Negative to check scrolling up, positive to check
5027 * scrolling down.
5028 * @return true if the list can be scrolled in the specified direction,
5029 * false otherwise.
5030 * @see #scrollListBy(int)
5031 */
5032 public boolean canScrollList(int direction) {
5033 final int childCount = getChildCount();
5034 if (childCount == 0) {
5035 return false;
5036 }
5037
5038 final int firstPosition = mFirstPosition;
5039 final Rect listPadding = mListPadding;
5040 if (direction > 0) {
5041 final int lastBottom = getChildAt(childCount - 1).getBottom();
5042 final int lastPosition = firstPosition + childCount;
5043 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5044 } else {
5045 final int firstTop = getChildAt(0).getTop();
5046 return firstPosition > 0 || firstTop < listPadding.top;
5047 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07005048 }
5049
5050 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005051 * Track a motion scroll
5052 *
5053 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5054 * began. Positive numbers mean the user's finger is moving down the screen.
5055 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08005056 * @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 -08005057 */
Adam Powell45803472010-01-25 15:10:44 -08005058 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005059 final int childCount = getChildCount();
5060 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08005061 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005062 }
5063
5064 final int firstTop = getChildAt(0).getTop();
5065 final int lastBottom = getChildAt(childCount - 1).getBottom();
5066
5067 final Rect listPadding = mListPadding;
5068
Adam Powellbdccc2d2010-12-14 17:34:27 -08005069 // "effective padding" In this case is the amount of padding that affects
5070 // how much space should not be filled by items. If we don't clip to padding
5071 // there is no effective padding.
5072 int effectivePaddingTop = 0;
5073 int effectivePaddingBottom = 0;
5074 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5075 effectivePaddingTop = listPadding.top;
5076 effectivePaddingBottom = listPadding.bottom;
5077 }
5078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005079 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005080 final int spaceAbove = effectivePaddingTop - firstTop;
5081 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005082 final int spaceBelow = lastBottom - end;
5083
5084 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5085 if (deltaY < 0) {
5086 deltaY = Math.max(-(height - 1), deltaY);
5087 } else {
5088 deltaY = Math.min(height - 1, deltaY);
5089 }
5090
5091 if (incrementalDeltaY < 0) {
5092 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5093 } else {
5094 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5095 }
5096
Adam Powell45803472010-01-25 15:10:44 -08005097 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005098
Adam Powell637d3372010-08-25 14:37:03 -07005099 // Update our guesses for where the first and last views are
5100 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005101 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005102 } else {
5103 mFirstPositionDistanceGuess += incrementalDeltaY;
5104 }
5105 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005106 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005107 } else {
5108 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005109 }
Adam Powell45803472010-01-25 15:10:44 -08005110
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005111 final boolean cannotScrollDown = (firstPosition == 0 &&
5112 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5113 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5114 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005115
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005116 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005117 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005118 }
5119
5120 final boolean down = incrementalDeltaY < 0;
5121
Adam Powell029cfbd2010-03-08 19:03:54 -08005122 final boolean inTouchMode = isInTouchMode();
5123 if (inTouchMode) {
5124 hideSelector();
5125 }
Adam Powell45803472010-01-25 15:10:44 -08005126
5127 final int headerViewsCount = getHeaderViewsCount();
5128 final int footerViewsStart = mItemCount - getFooterViewsCount();
5129
5130 int start = 0;
5131 int count = 0;
5132
5133 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005134 int top = -incrementalDeltaY;
5135 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5136 top += listPadding.top;
5137 }
Adam Powell45803472010-01-25 15:10:44 -08005138 for (int i = 0; i < childCount; i++) {
5139 final View child = getChildAt(i);
5140 if (child.getBottom() >= top) {
5141 break;
5142 } else {
5143 count++;
5144 int position = firstPosition + i;
5145 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005146 // The view will be rebound to new data, clear any
5147 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005148 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005149 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005150 }
5151 }
5152 }
5153 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005154 int bottom = getHeight() - incrementalDeltaY;
5155 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5156 bottom -= listPadding.bottom;
5157 }
Adam Powell45803472010-01-25 15:10:44 -08005158 for (int i = childCount - 1; i >= 0; i--) {
5159 final View child = getChildAt(i);
5160 if (child.getTop() <= bottom) {
5161 break;
5162 } else {
5163 start = i;
5164 count++;
5165 int position = firstPosition + i;
5166 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005167 // The view will be rebound to new data, clear any
5168 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005169 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005170 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005171 }
5172 }
5173 }
5174 }
5175
5176 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5177
5178 mBlockLayoutRequests = true;
5179
5180 if (count > 0) {
5181 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005182 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005183 }
Adam Powell539ee872012-02-03 19:00:49 -08005184
Romain Guy9d849a22012-03-14 16:41:42 -07005185 // invalidate before moving the children to avoid unnecessary invalidate
5186 // calls to bubble up from the children all the way to the top
5187 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005188 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005189 }
5190
Adam Powell45803472010-01-25 15:10:44 -08005191 offsetChildrenTopAndBottom(incrementalDeltaY);
5192
5193 if (down) {
5194 mFirstPosition += count;
5195 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005196
Adam Powell45803472010-01-25 15:10:44 -08005197 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5198 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5199 fillGap(down);
5200 }
5201
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07005202 mRecycler.fullyDetachScrapViews();
Adam Powell029cfbd2010-03-08 19:03:54 -08005203 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005204 final int childIndex = mSelectedPosition - mFirstPosition;
5205 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005206 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08005207 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005208 } else if (mSelectorPosition != INVALID_POSITION) {
5209 final int childIndex = mSelectorPosition - mFirstPosition;
5210 if (childIndex >= 0 && childIndex < getChildCount()) {
5211 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5212 }
5213 } else {
5214 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005215 }
5216
Adam Powell45803472010-01-25 15:10:44 -08005217 mBlockLayoutRequests = false;
5218
5219 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005220
Adam Powell45803472010-01-25 15:10:44 -08005221 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005222 }
5223
5224 /**
5225 * Returns the number of header views in the list. Header views are special views
5226 * at the top of the list that should not be recycled during a layout.
5227 *
5228 * @return The number of header views, 0 in the default implementation.
5229 */
5230 int getHeaderViewsCount() {
5231 return 0;
5232 }
5233
5234 /**
5235 * Returns the number of footer views in the list. Footer views are special views
5236 * at the bottom of the list that should not be recycled during a layout.
5237 *
5238 * @return The number of footer views, 0 in the default implementation.
5239 */
5240 int getFooterViewsCount() {
5241 return 0;
5242 }
5243
5244 /**
5245 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5246 * remain on screen are shifted and the other ones are discarded. The role of this
5247 * method is to fill the gap thus created by performing a partial layout in the
5248 * empty space.
5249 *
5250 * @param down true if the scroll is going down, false if it is going up
5251 */
5252 abstract void fillGap(boolean down);
5253
5254 void hideSelector() {
5255 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005256 if (mLayoutMode != LAYOUT_SPECIFIC) {
5257 mResurrectToPosition = mSelectedPosition;
5258 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005259 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5260 mResurrectToPosition = mNextSelectedPosition;
5261 }
5262 setSelectedPositionInt(INVALID_POSITION);
5263 setNextSelectedPositionInt(INVALID_POSITION);
5264 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005265 }
5266 }
5267
5268 /**
5269 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5270 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5271 * of items available in the adapter
5272 */
5273 int reconcileSelectedPosition() {
5274 int position = mSelectedPosition;
5275 if (position < 0) {
5276 position = mResurrectToPosition;
5277 }
5278 position = Math.max(0, position);
5279 position = Math.min(position, mItemCount - 1);
5280 return position;
5281 }
5282
5283 /**
5284 * Find the row closest to y. This row will be used as the motion row when scrolling
5285 *
5286 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005287 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005288 */
5289 abstract int findMotionRow(int y);
5290
5291 /**
Adam Powell637d3372010-08-25 14:37:03 -07005292 * Find the row closest to y. This row will be used as the motion row when scrolling.
5293 *
5294 * @param y Where the user touched
5295 * @return The position of the first (or only) item in the row closest to y
5296 */
5297 int findClosestMotionRow(int y) {
5298 final int childCount = getChildCount();
5299 if (childCount == 0) {
5300 return INVALID_POSITION;
5301 }
5302
5303 final int motionRow = findMotionRow(y);
5304 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5305 }
5306
5307 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005308 * Causes all the views to be rebuilt and redrawn.
5309 */
5310 public void invalidateViews() {
5311 mDataChanged = true;
5312 rememberSyncState();
5313 requestLayout();
5314 invalidate();
5315 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005316
Jeff Brown4e6319b2010-12-13 10:36:51 -08005317 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005318 * If there is a selection returns false.
5319 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005320 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005321 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005322 if (mSelectedPosition < 0 && resurrectSelection()) {
5323 updateSelectorState();
5324 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005325 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005326 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005327 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005328
5329 /**
5330 * Makes the item at the supplied position selected.
5331 *
5332 * @param position the position of the new selection
5333 */
5334 abstract void setSelectionInt(int position);
5335
5336 /**
5337 * Attempt to bring the selection back if the user is switching from touch
5338 * to trackball mode
5339 * @return Whether selection was set to something.
5340 */
5341 boolean resurrectSelection() {
5342 final int childCount = getChildCount();
5343
5344 if (childCount <= 0) {
5345 return false;
5346 }
5347
5348 int selectedTop = 0;
5349 int selectedPos;
5350 int childrenTop = mListPadding.top;
5351 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5352 final int firstPosition = mFirstPosition;
5353 final int toPosition = mResurrectToPosition;
5354 boolean down = true;
5355
5356 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5357 selectedPos = toPosition;
5358
5359 final View selected = getChildAt(selectedPos - mFirstPosition);
5360 selectedTop = selected.getTop();
5361 int selectedBottom = selected.getBottom();
5362
5363 // We are scrolled, don't get in the fade
5364 if (selectedTop < childrenTop) {
5365 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5366 } else if (selectedBottom > childrenBottom) {
5367 selectedTop = childrenBottom - selected.getMeasuredHeight()
5368 - getVerticalFadingEdgeLength();
5369 }
5370 } else {
5371 if (toPosition < firstPosition) {
5372 // Default to selecting whatever is first
5373 selectedPos = firstPosition;
5374 for (int i = 0; i < childCount; i++) {
5375 final View v = getChildAt(i);
5376 final int top = v.getTop();
5377
5378 if (i == 0) {
5379 // Remember the position of the first item
5380 selectedTop = top;
5381 // See if we are scrolled at all
5382 if (firstPosition > 0 || top < childrenTop) {
5383 // If we are scrolled, don't select anything that is
5384 // in the fade region
5385 childrenTop += getVerticalFadingEdgeLength();
5386 }
5387 }
5388 if (top >= childrenTop) {
5389 // Found a view whose top is fully visisble
5390 selectedPos = firstPosition + i;
5391 selectedTop = top;
5392 break;
5393 }
5394 }
5395 } else {
5396 final int itemCount = mItemCount;
5397 down = false;
5398 selectedPos = firstPosition + childCount - 1;
5399
5400 for (int i = childCount - 1; i >= 0; i--) {
5401 final View v = getChildAt(i);
5402 final int top = v.getTop();
5403 final int bottom = v.getBottom();
5404
5405 if (i == childCount - 1) {
5406 selectedTop = top;
5407 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5408 childrenBottom -= getVerticalFadingEdgeLength();
5409 }
5410 }
5411
5412 if (bottom <= childrenBottom) {
5413 selectedPos = firstPosition + i;
5414 selectedTop = top;
5415 break;
5416 }
5417 }
5418 }
5419 }
5420
5421 mResurrectToPosition = INVALID_POSITION;
5422 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005423 if (mPositionScroller != null) {
5424 mPositionScroller.stop();
5425 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005426 mTouchMode = TOUCH_MODE_REST;
5427 clearScrollingCache();
5428 mSpecificTop = selectedTop;
5429 selectedPos = lookForSelectablePosition(selectedPos, down);
5430 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5431 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005432 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005433 setSelectionInt(selectedPos);
5434 invokeOnItemScrollListener();
5435 } else {
5436 selectedPos = INVALID_POSITION;
5437 }
5438 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5439
5440 return selectedPos >= 0;
5441 }
5442
Adam Powell14c08042011-10-06 19:46:18 -07005443 void confirmCheckedPositionsById() {
5444 // Clear out the positional check states, we'll rebuild it below from IDs.
5445 mCheckStates.clear();
5446
5447 boolean checkedCountChanged = false;
5448 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5449 final long id = mCheckedIdStates.keyAt(checkedIndex);
5450 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5451
5452 final long lastPosId = mAdapter.getItemId(lastPos);
5453 if (id != lastPosId) {
5454 // Look around to see if the ID is nearby. If not, uncheck it.
5455 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5456 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5457 boolean found = false;
5458 for (int searchPos = start; searchPos < end; searchPos++) {
5459 final long searchId = mAdapter.getItemId(searchPos);
5460 if (id == searchId) {
5461 found = true;
5462 mCheckStates.put(searchPos, true);
5463 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5464 break;
5465 }
5466 }
5467
5468 if (!found) {
5469 mCheckedIdStates.delete(id);
5470 checkedIndex--;
5471 mCheckedItemCount--;
5472 checkedCountChanged = true;
5473 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5474 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5475 lastPos, id, false);
5476 }
5477 }
5478 } else {
5479 mCheckStates.put(lastPos, true);
5480 }
5481 }
5482
5483 if (checkedCountChanged && mChoiceActionMode != null) {
5484 mChoiceActionMode.invalidate();
5485 }
5486 }
5487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005488 @Override
5489 protected void handleDataChanged() {
5490 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005491 int lastHandledItemCount = mLastHandledItemCount;
5492 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005493
5494 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5495 confirmCheckedPositionsById();
5496 }
5497
Adam Powell539ee872012-02-03 19:00:49 -08005498 // TODO: In the future we can recycle these views based on stable ID instead.
5499 mRecycler.clearTransientStateViews();
5500
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005501 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005502 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005503 int selectablePos;
5504
5505 // Find the row we are supposed to sync to
5506 if (mNeedSync) {
5507 // Update this first, since setNextSelectedPositionInt inspects it
5508 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005509 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005510
Adam Powell07852792010-11-10 16:57:05 -08005511 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005512 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5513 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005514 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5515 if (mForceTranscriptScroll) {
5516 mForceTranscriptScroll = false;
5517 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5518 return;
5519 }
Adam Powell07852792010-11-10 16:57:05 -08005520 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005521 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005522 final View lastChild = getChildAt(childCount - 1);
5523 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005524 if (mFirstPosition + childCount >= lastHandledItemCount &&
5525 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005526 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5527 return;
5528 }
5529 // Something new came in and we didn't scroll; give the user a clue that
5530 // there's something new.
5531 awakenScrollBars();
5532 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005533
5534 switch (mSyncMode) {
5535 case SYNC_SELECTED_POSITION:
5536 if (isInTouchMode()) {
5537 // We saved our state when not in touch mode. (We know this because
5538 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5539 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5540 // adjusting if the available range changed) and return.
5541 mLayoutMode = LAYOUT_SYNC;
5542 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5543
5544 return;
5545 } else {
5546 // See if we can find a position in the new data with the same
5547 // id as the old selection. This will change mSyncPosition.
5548 newPos = findSyncPosition();
5549 if (newPos >= 0) {
5550 // Found it. Now verify that new selection is still selectable
5551 selectablePos = lookForSelectablePosition(newPos, true);
5552 if (selectablePos == newPos) {
5553 // Same row id is selected
5554 mSyncPosition = newPos;
5555
5556 if (mSyncHeight == getHeight()) {
5557 // If we are at the same height as when we saved state, try
5558 // to restore the scroll position too.
5559 mLayoutMode = LAYOUT_SYNC;
5560 } else {
5561 // We are not the same height as when the selection was saved, so
5562 // don't try to restore the exact position
5563 mLayoutMode = LAYOUT_SET_SELECTION;
5564 }
5565
5566 // Restore selection
5567 setNextSelectedPositionInt(newPos);
5568 return;
5569 }
5570 }
5571 }
5572 break;
5573 case SYNC_FIRST_POSITION:
5574 // Leave mSyncPosition as it is -- just pin to available range
5575 mLayoutMode = LAYOUT_SYNC;
5576 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5577
5578 return;
5579 }
5580 }
5581
5582 if (!isInTouchMode()) {
5583 // We couldn't find matching data -- try to use the same position
5584 newPos = getSelectedItemPosition();
5585
5586 // Pin position to the available range
5587 if (newPos >= count) {
5588 newPos = count - 1;
5589 }
5590 if (newPos < 0) {
5591 newPos = 0;
5592 }
5593
5594 // Make sure we select something selectable -- first look down
5595 selectablePos = lookForSelectablePosition(newPos, true);
5596
5597 if (selectablePos >= 0) {
5598 setNextSelectedPositionInt(selectablePos);
5599 return;
5600 } else {
5601 // Looking down didn't work -- try looking up
5602 selectablePos = lookForSelectablePosition(newPos, false);
5603 if (selectablePos >= 0) {
5604 setNextSelectedPositionInt(selectablePos);
5605 return;
5606 }
5607 }
5608 } else {
5609
5610 // We already know where we want to resurrect the selection
5611 if (mResurrectToPosition >= 0) {
5612 return;
5613 }
5614 }
5615
5616 }
5617
5618 // Nothing is selected. Give up and reset everything.
5619 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5620 mSelectedPosition = INVALID_POSITION;
5621 mSelectedRowId = INVALID_ROW_ID;
5622 mNextSelectedPosition = INVALID_POSITION;
5623 mNextSelectedRowId = INVALID_ROW_ID;
5624 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005625 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005626 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005627 checkSelectionChanged();
5628 }
5629
Romain Guy43c9cdf2010-01-27 13:53:55 -08005630 @Override
5631 protected void onDisplayHint(int hint) {
5632 super.onDisplayHint(hint);
5633 switch (hint) {
5634 case INVISIBLE:
5635 if (mPopup != null && mPopup.isShowing()) {
5636 dismissPopup();
5637 }
5638 break;
5639 case VISIBLE:
5640 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5641 showPopup();
5642 }
5643 break;
5644 }
Romain Guy24562482010-02-01 14:56:19 -08005645 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005646 }
5647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005648 /**
5649 * Removes the filter window
5650 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005651 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005652 if (mPopup != null) {
5653 mPopup.dismiss();
5654 }
5655 }
5656
5657 /**
5658 * Shows the filter window
5659 */
5660 private void showPopup() {
5661 // Make sure we have a window before showing the popup
5662 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005663 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005664 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005665 // Make sure we get focus if we are showing the popup
5666 checkFocus();
5667 }
5668 }
5669
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005670 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005671 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5672 final int[] xy = new int[2];
5673 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005674 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005675 // TODO: And the gravity should be defined in the theme as well
5676 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005677 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005678 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5679 xy[0], bottomGap);
5680 } else {
5681 mPopup.update(xy[0], bottomGap, -1, -1);
5682 }
5683 }
5684
5685 /**
5686 * What is the distance between the source and destination rectangles given the direction of
5687 * focus navigation between them? The direction basically helps figure out more quickly what is
5688 * self evident by the relationship between the rects...
5689 *
5690 * @param source the source rectangle
5691 * @param dest the destination rectangle
5692 * @param direction the direction
5693 * @return the distance between the rectangles
5694 */
5695 static int getDistance(Rect source, Rect dest, int direction) {
5696 int sX, sY; // source x, y
5697 int dX, dY; // dest x, y
5698 switch (direction) {
5699 case View.FOCUS_RIGHT:
5700 sX = source.right;
5701 sY = source.top + source.height() / 2;
5702 dX = dest.left;
5703 dY = dest.top + dest.height() / 2;
5704 break;
5705 case View.FOCUS_DOWN:
5706 sX = source.left + source.width() / 2;
5707 sY = source.bottom;
5708 dX = dest.left + dest.width() / 2;
5709 dY = dest.top;
5710 break;
5711 case View.FOCUS_LEFT:
5712 sX = source.left;
5713 sY = source.top + source.height() / 2;
5714 dX = dest.right;
5715 dY = dest.top + dest.height() / 2;
5716 break;
5717 case View.FOCUS_UP:
5718 sX = source.left + source.width() / 2;
5719 sY = source.top;
5720 dX = dest.left + dest.width() / 2;
5721 dY = dest.bottom;
5722 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005723 case View.FOCUS_FORWARD:
5724 case View.FOCUS_BACKWARD:
5725 sX = source.right + source.width() / 2;
5726 sY = source.top + source.height() / 2;
5727 dX = dest.left + dest.width() / 2;
5728 dY = dest.top + dest.height() / 2;
5729 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005730 default:
5731 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005732 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5733 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005734 }
5735 int deltaX = dX - sX;
5736 int deltaY = dY - sY;
5737 return deltaY * deltaY + deltaX * deltaX;
5738 }
5739
5740 @Override
5741 protected boolean isInFilterMode() {
5742 return mFiltered;
5743 }
5744
5745 /**
5746 * Sends a key to the text filter window
5747 *
5748 * @param keyCode The keycode for the event
5749 * @param event The actual key event
5750 *
5751 * @return True if the text filter handled the event, false otherwise.
5752 */
5753 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005754 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005755 return false;
5756 }
5757
5758 boolean handled = false;
5759 boolean okToSend = true;
5760 switch (keyCode) {
5761 case KeyEvent.KEYCODE_DPAD_UP:
5762 case KeyEvent.KEYCODE_DPAD_DOWN:
5763 case KeyEvent.KEYCODE_DPAD_LEFT:
5764 case KeyEvent.KEYCODE_DPAD_RIGHT:
5765 case KeyEvent.KEYCODE_DPAD_CENTER:
5766 case KeyEvent.KEYCODE_ENTER:
5767 okToSend = false;
5768 break;
5769 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005770 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005771 if (event.getAction() == KeyEvent.ACTION_DOWN
5772 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005773 KeyEvent.DispatcherState state = getKeyDispatcherState();
5774 if (state != null) {
5775 state.startTracking(event, this);
5776 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005777 handled = true;
5778 } else if (event.getAction() == KeyEvent.ACTION_UP
5779 && event.isTracking() && !event.isCanceled()) {
5780 handled = true;
5781 mTextFilter.setText("");
5782 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005783 }
5784 okToSend = false;
5785 break;
5786 case KeyEvent.KEYCODE_SPACE:
5787 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005788 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005789 break;
5790 }
5791
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005792 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005793 createTextFilter(true);
5794
5795 KeyEvent forwardEvent = event;
5796 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005797 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005798 }
5799
5800 int action = event.getAction();
5801 switch (action) {
5802 case KeyEvent.ACTION_DOWN:
5803 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5804 break;
5805
5806 case KeyEvent.ACTION_UP:
5807 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5808 break;
5809
5810 case KeyEvent.ACTION_MULTIPLE:
5811 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5812 break;
5813 }
5814 }
5815 return handled;
5816 }
5817
5818 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005819 * Return an InputConnection for editing of the filter text.
5820 */
5821 @Override
5822 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005823 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005824 if (mPublicInputConnection == null) {
5825 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005826 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005827 }
Romain Guyf6991302013-06-05 17:19:01 -07005828 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005829 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5830 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005831 }
5832 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005833 }
Romain Guy0a637162009-05-29 14:43:54 -07005834
Romain Guyf6991302013-06-05 17:19:01 -07005835 private class InputConnectionWrapper implements InputConnection {
5836 private final EditorInfo mOutAttrs;
5837 private InputConnection mTarget;
5838
5839 public InputConnectionWrapper(EditorInfo outAttrs) {
5840 mOutAttrs = outAttrs;
5841 }
5842
5843 private InputConnection getTarget() {
5844 if (mTarget == null) {
5845 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5846 }
5847 return mTarget;
5848 }
5849
5850 @Override
5851 public boolean reportFullscreenMode(boolean enabled) {
5852 // Use our own input connection, since it is
5853 // the "real" one the IME is talking with.
5854 return mDefInputConnection.reportFullscreenMode(enabled);
5855 }
5856
5857 @Override
5858 public boolean performEditorAction(int editorAction) {
5859 // The editor is off in its own window; we need to be
5860 // the one that does this.
5861 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005862 InputMethodManager imm =
5863 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005864 if (imm != null) {
5865 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5866 }
5867 return true;
5868 }
5869 return false;
5870 }
5871
5872 @Override
5873 public boolean sendKeyEvent(KeyEvent event) {
5874 // Use our own input connection, since the filter
5875 // text view may not be shown in a window so has
5876 // no ViewAncestor to dispatch events with.
5877 return mDefInputConnection.sendKeyEvent(event);
5878 }
5879
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005880 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005881 public CharSequence getTextBeforeCursor(int n, int flags) {
5882 if (mTarget == null) return "";
5883 return mTarget.getTextBeforeCursor(n, flags);
5884 }
5885
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005886 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005887 public CharSequence getTextAfterCursor(int n, int flags) {
5888 if (mTarget == null) return "";
5889 return mTarget.getTextAfterCursor(n, flags);
5890 }
5891
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005892 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005893 public CharSequence getSelectedText(int flags) {
5894 if (mTarget == null) return "";
5895 return mTarget.getSelectedText(flags);
5896 }
5897
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005898 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005899 public int getCursorCapsMode(int reqModes) {
5900 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5901 return mTarget.getCursorCapsMode(reqModes);
5902 }
5903
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005904 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005905 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5906 return getTarget().getExtractedText(request, flags);
5907 }
5908
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005909 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005910 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5911 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5912 }
5913
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005914 @Override
Yohei Yukawac89e22a2016-01-13 22:48:14 -08005915 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5916 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5917 }
5918
5919 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005920 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5921 return getTarget().setComposingText(text, newCursorPosition);
5922 }
5923
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005924 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005925 public boolean setComposingRegion(int start, int end) {
5926 return getTarget().setComposingRegion(start, end);
5927 }
5928
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005929 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005930 public boolean finishComposingText() {
5931 return mTarget == null || mTarget.finishComposingText();
5932 }
5933
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005934 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005935 public boolean commitText(CharSequence text, int newCursorPosition) {
5936 return getTarget().commitText(text, newCursorPosition);
5937 }
5938
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005939 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005940 public boolean commitCompletion(CompletionInfo text) {
5941 return getTarget().commitCompletion(text);
5942 }
5943
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005944 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005945 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5946 return getTarget().commitCorrection(correctionInfo);
5947 }
5948
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005949 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005950 public boolean setSelection(int start, int end) {
5951 return getTarget().setSelection(start, end);
5952 }
5953
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005954 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005955 public boolean performContextMenuAction(int id) {
5956 return getTarget().performContextMenuAction(id);
5957 }
5958
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005959 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005960 public boolean beginBatchEdit() {
5961 return getTarget().beginBatchEdit();
5962 }
5963
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005964 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005965 public boolean endBatchEdit() {
5966 return getTarget().endBatchEdit();
5967 }
5968
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005969 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005970 public boolean clearMetaKeyStates(int states) {
5971 return getTarget().clearMetaKeyStates(states);
5972 }
5973
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005974 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005975 public boolean performPrivateCommand(String action, Bundle data) {
5976 return getTarget().performPrivateCommand(action, data);
5977 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09005978
5979 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07005980 public boolean requestCursorUpdates(int cursorUpdateMode) {
5981 return getTarget().requestCursorUpdates(cursorUpdateMode);
5982 }
Yohei Yukawa612cce92016-02-11 17:47:33 -08005983
5984 @Override
5985 public Handler getHandler() {
5986 return getTarget().getHandler();
5987 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -07005988
5989 @Override
5990 public void closeConnection() {
5991 getTarget().closeConnection();
5992 }
Yohei Yukawa152944f2016-06-10 19:04:34 -07005993
5994 @Override
Yohei Yukawa45700fa2016-06-23 17:12:59 -07005995 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
5996 return getTarget().commitContent(inputContentInfo, flags, opts);
Yohei Yukawa152944f2016-06-10 19:04:34 -07005997 }
Romain Guyf6991302013-06-05 17:19:01 -07005998 }
5999
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006000 /**
6001 * For filtering we proxy an input connection to an internal text editor,
6002 * and this allows the proxying to happen.
6003 */
6004 @Override
6005 public boolean checkInputConnectionProxy(View view) {
6006 return view == mTextFilter;
6007 }
Romain Guy0a637162009-05-29 14:43:54 -07006008
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006009 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006010 * Creates the window for the text filter and populates it with an EditText field;
6011 *
6012 * @param animateEntrance true if the window should appear with an animation
6013 */
6014 private void createTextFilter(boolean animateEntrance) {
6015 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07006016 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006017 p.setFocusable(false);
6018 p.setTouchable(false);
6019 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07006020 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006021 p.setWidth(LayoutParams.WRAP_CONTENT);
6022 p.setHeight(LayoutParams.WRAP_CONTENT);
6023 p.setBackgroundDrawable(null);
6024 mPopup = p;
6025 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07006026 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006027 }
6028 if (animateEntrance) {
6029 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6030 } else {
6031 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6032 }
6033 }
6034
Romain Guyf6991302013-06-05 17:19:01 -07006035 private EditText getTextFilterInput() {
6036 if (mTextFilter == null) {
6037 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6038 mTextFilter = (EditText) layoutInflater.inflate(
6039 com.android.internal.R.layout.typing_filter, null);
6040 // For some reason setting this as the "real" input type changes
6041 // the text view in some way that it doesn't work, and I don't
6042 // want to figure out why this is.
6043 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6044 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6045 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6046 mTextFilter.addTextChangedListener(this);
6047 }
6048 return mTextFilter;
6049 }
6050
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006051 /**
6052 * Clear the text filter.
6053 */
6054 public void clearTextFilter() {
6055 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07006056 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006057 mFiltered = false;
6058 if (mPopup != null && mPopup.isShowing()) {
6059 dismissPopup();
6060 }
6061 }
6062 }
6063
6064 /**
6065 * Returns if the ListView currently has a text filter.
6066 */
6067 public boolean hasTextFilter() {
6068 return mFiltered;
6069 }
6070
Alan Viverette8fa327a2013-05-31 14:53:13 -07006071 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006072 public void onGlobalLayout() {
6073 if (isShown()) {
6074 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08006075 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006076 showPopup();
6077 }
6078 } else {
6079 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08006080 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006081 dismissPopup();
6082 }
6083 }
6084
6085 }
6086
6087 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006088 * For our text watcher that is associated with the text filter. Does
6089 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006090 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006091 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006092 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6093 }
6094
6095 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006096 * For our text watcher that is associated with the text filter. Performs
6097 * the actual filtering as the text changes, and takes care of hiding and
6098 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006099 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006100 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006101 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006102 if (isTextFilterEnabled()) {
6103 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006104 int length = s.length();
6105 boolean showing = mPopup.isShowing();
6106 if (!showing && length > 0) {
6107 // Show the filter popup if necessary
6108 showPopup();
6109 mFiltered = true;
6110 } else if (showing && length == 0) {
6111 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006112 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006113 mFiltered = false;
6114 }
6115 if (mAdapter instanceof Filterable) {
6116 Filter f = ((Filterable) mAdapter).getFilter();
6117 // Filter should not be null when we reach this part
6118 if (f != null) {
6119 f.filter(s, this);
6120 } else {
6121 throw new IllegalStateException("You cannot call onTextChanged with a non "
6122 + "filterable adapter");
6123 }
6124 }
6125 }
6126 }
6127
6128 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006129 * For our text watcher that is associated with the text filter. Does
6130 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006131 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006132 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006133 public void afterTextChanged(Editable s) {
6134 }
6135
Alan Viverette8fa327a2013-05-31 14:53:13 -07006136 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006137 public void onFilterComplete(int count) {
6138 if (mSelectedPosition < 0 && count > 0) {
6139 mResurrectToPosition = INVALID_POSITION;
6140 resurrectSelection();
6141 }
6142 }
6143
6144 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006145 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6146 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6147 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6148 }
6149
6150 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006151 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6152 return new LayoutParams(p);
6153 }
6154
6155 @Override
6156 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6157 return new AbsListView.LayoutParams(getContext(), attrs);
6158 }
6159
6160 @Override
6161 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6162 return p instanceof AbsListView.LayoutParams;
6163 }
6164
6165 /**
6166 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6167 * to the bottom to show new items.
6168 *
6169 * @param mode the transcript mode to set
6170 *
6171 * @see #TRANSCRIPT_MODE_DISABLED
6172 * @see #TRANSCRIPT_MODE_NORMAL
6173 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6174 */
6175 public void setTranscriptMode(int mode) {
6176 mTranscriptMode = mode;
6177 }
6178
6179 /**
6180 * Returns the current transcript mode.
6181 *
6182 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6183 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6184 */
6185 public int getTranscriptMode() {
6186 return mTranscriptMode;
6187 }
6188
6189 @Override
6190 public int getSolidColor() {
6191 return mCacheColorHint;
6192 }
6193
6194 /**
6195 * 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 -07006196 * on top of a solid, single-color, opaque background.
6197 *
6198 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6199 * single color. This hint will not affect any existing background drawable set on this view (
6200 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006201 *
6202 * @param color The background color
6203 */
Tor Norbye80756e32015-03-02 09:39:27 -08006204 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006205 if (color != mCacheColorHint) {
6206 mCacheColorHint = color;
6207 int count = getChildCount();
6208 for (int i = 0; i < count; i++) {
6209 getChildAt(i).setDrawingCacheBackgroundColor(color);
6210 }
6211 mRecycler.setCacheColorHint(color);
6212 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006213 }
6214
6215 /**
6216 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6217 * on top of a solid, single-color, opaque background
6218 *
6219 * @return The cache color hint
6220 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006221 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006222 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006223 public int getCacheColorHint() {
6224 return mCacheColorHint;
6225 }
6226
6227 /**
6228 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6229 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6230 * internal view recycler.
6231 *
6232 * @param views A list into which to put the reclaimed views
6233 */
6234 public void reclaimViews(List<View> views) {
6235 int childCount = getChildCount();
6236 RecyclerListener listener = mRecycler.mRecyclerListener;
6237
6238 // Reclaim views on screen
6239 for (int i = 0; i < childCount; i++) {
6240 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006241 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006242 // Don't reclaim header or footer views, or views that should be ignored
6243 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6244 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006245 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006246 if (listener != null) {
6247 // Pretend they went through the scrap heap
6248 listener.onMovedToScrapHeap(child);
6249 }
6250 }
6251 }
6252 mRecycler.reclaimScrapViews(views);
6253 removeAllViewsInLayout();
6254 }
6255
Adam Powell637d3372010-08-25 14:37:03 -07006256 private void finishGlows() {
6257 if (mEdgeGlowTop != null) {
6258 mEdgeGlowTop.finish();
6259 mEdgeGlowBottom.finish();
6260 }
6261 }
6262
Romain Guy13922e02009-05-12 17:56:14 -07006263 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006264 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6265 * through the specified intent.
6266 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6267 */
6268 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006269 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6270 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006271 if (mRemoteAdapter != null) {
6272 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6273 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6274 mRemoteAdapter.getRemoteViewsServiceIntent());
6275 if (fcNew.equals(fcOld)) {
6276 return;
6277 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006278 }
Adam Cohen2148d432011-07-28 14:59:54 -07006279 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006280 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07006281 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07006282 if (mRemoteAdapter.isDataReady()) {
6283 setAdapter(mRemoteAdapter);
6284 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006285 }
6286
6287 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006288 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006289 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006290 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006291 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006292 * @hide
6293 */
6294 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6295 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6296 // service handling the specified intent.
6297 if (mRemoteAdapter != null) {
6298 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6299 }
6300 }
6301
6302 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006303 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6304 * connected yet.
6305 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006306 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006307 public void deferNotifyDataSetChanged() {
6308 mDeferNotifyDataSetChanged = true;
6309 }
6310
6311 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006312 * Called back when the adapter connects to the RemoteViewsService.
6313 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006314 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006315 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006316 if (mRemoteAdapter != mAdapter) {
6317 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006318 if (mDeferNotifyDataSetChanged) {
6319 mRemoteAdapter.notifyDataSetChanged();
6320 mDeferNotifyDataSetChanged = false;
6321 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006322 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006323 } else if (mRemoteAdapter != null) {
6324 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006325 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006326 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006327 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006328 }
6329
6330 /**
6331 * Called back when the adapter disconnects from the RemoteViewsService.
6332 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006333 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006334 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006335 // If the remote adapter disconnects, we keep it around
6336 // since the currently displayed items are still cached.
6337 // Further, we want the service to eventually reconnect
6338 // when necessary, as triggered by this view requesting
6339 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006340 }
6341
6342 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006343 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6344 * being displayed by the AbsListView.
6345 */
6346 void setVisibleRangeHint(int start, int end) {
6347 if (mRemoteAdapter != null) {
6348 mRemoteAdapter.setVisibleRangeHint(start, end);
6349 }
6350 }
6351
6352 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006353 * Sets the recycler listener to be notified whenever a View is set aside in
6354 * the recycler for later reuse. This listener can be used to free resources
6355 * associated to the View.
6356 *
6357 * @param listener The recycler listener to be notified of views set aside
6358 * in the recycler.
6359 *
6360 * @see android.widget.AbsListView.RecycleBin
6361 * @see android.widget.AbsListView.RecyclerListener
6362 */
6363 public void setRecyclerListener(RecyclerListener listener) {
6364 mRecycler.mRecyclerListener = listener;
6365 }
6366
Adam Powellb1f498a2011-01-18 20:43:23 -08006367 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6368 @Override
6369 public void onChanged() {
6370 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006371 if (mFastScroll != null) {
6372 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006373 }
6374 }
6375
6376 @Override
6377 public void onInvalidated() {
6378 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006379 if (mFastScroll != null) {
6380 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006381 }
6382 }
6383 }
6384
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006385 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006386 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6387 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6388 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6389 * selects and deselects list items.
6390 */
6391 public interface MultiChoiceModeListener extends ActionMode.Callback {
6392 /**
6393 * Called when an item is checked or unchecked during selection mode.
6394 *
6395 * @param mode The {@link ActionMode} providing the selection mode
6396 * @param position Adapter position of the item that was checked or unchecked
6397 * @param id Adapter ID of the item that was checked or unchecked
6398 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6399 * if the item is now unchecked.
6400 */
6401 public void onItemCheckedStateChanged(ActionMode mode,
6402 int position, long id, boolean checked);
6403 }
6404
6405 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6406 private MultiChoiceModeListener mWrapped;
6407
6408 public void setWrapped(MultiChoiceModeListener wrapped) {
6409 mWrapped = wrapped;
6410 }
6411
Adam Powella7981702012-08-24 12:43:41 -07006412 public boolean hasWrappedCallback() {
6413 return mWrapped != null;
6414 }
6415
Alan Viverette8fa327a2013-05-31 14:53:13 -07006416 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006417 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6418 if (mWrapped.onCreateActionMode(mode, menu)) {
6419 // Initialize checked graphic state?
6420 setLongClickable(false);
6421 return true;
6422 }
6423 return false;
6424 }
6425
Alan Viverette8fa327a2013-05-31 14:53:13 -07006426 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006427 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6428 return mWrapped.onPrepareActionMode(mode, menu);
6429 }
6430
Alan Viverette8fa327a2013-05-31 14:53:13 -07006431 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006432 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6433 return mWrapped.onActionItemClicked(mode, item);
6434 }
6435
Alan Viverette8fa327a2013-05-31 14:53:13 -07006436 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006437 public void onDestroyActionMode(ActionMode mode) {
6438 mWrapped.onDestroyActionMode(mode);
6439 mChoiceActionMode = null;
6440
6441 // Ending selection mode means deselecting everything.
6442 clearChoices();
6443
6444 mDataChanged = true;
6445 rememberSyncState();
6446 requestLayout();
6447
6448 setLongClickable(true);
6449 }
6450
Alan Viverette8fa327a2013-05-31 14:53:13 -07006451 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006452 public void onItemCheckedStateChanged(ActionMode mode,
6453 int position, long id, boolean checked) {
6454 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6455
6456 // If there are no items selected we no longer need the selection mode.
6457 if (getCheckedItemCount() == 0) {
6458 mode.finish();
6459 }
6460 }
6461 }
6462
6463 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006464 * AbsListView extends LayoutParams to provide a place to hold the view type.
6465 */
6466 public static class LayoutParams extends ViewGroup.LayoutParams {
6467 /**
6468 * View type for this view, as returned by
6469 * {@link android.widget.Adapter#getItemViewType(int) }
6470 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006471 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006472 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6473 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6474 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006475 int viewType;
6476
The Android Open Source Project4df24232009-03-05 14:34:35 -08006477 /**
6478 * When this boolean is set, the view has been added to the AbsListView
6479 * at least once. It is used to know whether headers/footers have already
6480 * been added to the list view and whether they should be treated as
6481 * recycled views or not.
6482 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006483 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006484 boolean recycledHeaderFooter;
6485
Romain Guy0bf88592010-03-02 13:38:44 -08006486 /**
6487 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6488 * to obtain children views to measure itself. When doing so, the children
6489 * are not attached to the window, but put in the recycler which assumes
6490 * they've been attached before. Setting this flag will force the reused
6491 * view to be attached to the window rather than just attached to the
6492 * parent.
6493 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006494 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006495 boolean forceAdd;
6496
Dianne Hackborn079e2352010-10-18 17:02:43 -07006497 /**
6498 * The position the view was removed from when pulled out of the
6499 * scrap heap.
6500 * @hide
6501 */
6502 int scrappedFromPosition;
6503
Adam Powell539ee872012-02-03 19:00:49 -08006504 /**
6505 * The ID the view represents
6506 */
6507 long itemId = -1;
6508
Alan Viverette92539d52015-09-14 10:49:25 -04006509 /** Whether the adapter considers the item enabled. */
6510 boolean isEnabled;
6511
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006512 public LayoutParams(Context c, AttributeSet attrs) {
6513 super(c, attrs);
6514 }
6515
6516 public LayoutParams(int w, int h) {
6517 super(w, h);
6518 }
6519
6520 public LayoutParams(int w, int h, int viewType) {
6521 super(w, h);
6522 this.viewType = viewType;
6523 }
6524
6525 public LayoutParams(ViewGroup.LayoutParams source) {
6526 super(source);
6527 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006528
6529 /** @hide */
6530 @Override
6531 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6532 super.encodeProperties(encoder);
6533
6534 encoder.addProperty("list:viewType", viewType);
6535 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6536 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006537 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006538 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006539 }
6540
6541 /**
6542 * A RecyclerListener is used to receive a notification whenever a View is placed
6543 * inside the RecycleBin's scrap heap. This listener is used to free resources
6544 * associated to Views placed in the RecycleBin.
6545 *
6546 * @see android.widget.AbsListView.RecycleBin
6547 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6548 */
6549 public static interface RecyclerListener {
6550 /**
6551 * Indicates that the specified View was moved into the recycler's scrap heap.
6552 * The view is not displayed on screen any more and any expensive resource
6553 * associated with the view should be discarded.
6554 *
6555 * @param view
6556 */
6557 void onMovedToScrapHeap(View view);
6558 }
6559
6560 /**
6561 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6562 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6563 * start of a layout. By construction, they are displaying current information. At the end of
6564 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6565 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6566 *
6567 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6568 * @see android.widget.AbsListView.RecyclerListener
6569 */
6570 class RecycleBin {
6571 private RecyclerListener mRecyclerListener;
6572
6573 /**
6574 * The position of the first view stored in mActiveViews.
6575 */
6576 private int mFirstActivePosition;
6577
6578 /**
6579 * Views that were on screen at the start of layout. This array is populated at the start of
6580 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6581 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6582 * view store in mFirstActivePosition.
6583 */
6584 private View[] mActiveViews = new View[0];
6585
6586 /**
6587 * Unsorted views that can be used by the adapter as a convert view.
6588 */
6589 private ArrayList<View>[] mScrapViews;
6590
6591 private int mViewTypeCount;
6592
6593 private ArrayList<View> mCurrentScrap;
6594
Adam Powell539ee872012-02-03 19:00:49 -08006595 private ArrayList<View> mSkippedScrap;
6596
6597 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006598 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006599
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006600 public void setViewTypeCount(int viewTypeCount) {
6601 if (viewTypeCount < 1) {
6602 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6603 }
6604 //noinspection unchecked
6605 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6606 for (int i = 0; i < viewTypeCount; i++) {
6607 scrapViews[i] = new ArrayList<View>();
6608 }
6609 mViewTypeCount = viewTypeCount;
6610 mCurrentScrap = scrapViews[0];
6611 mScrapViews = scrapViews;
6612 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006613
Adam Powellf3c2eda2010-03-16 17:31:01 -07006614 public void markChildrenDirty() {
6615 if (mViewTypeCount == 1) {
6616 final ArrayList<View> scrap = mCurrentScrap;
6617 final int scrapCount = scrap.size();
6618 for (int i = 0; i < scrapCount; i++) {
6619 scrap.get(i).forceLayout();
6620 }
6621 } else {
6622 final int typeCount = mViewTypeCount;
6623 for (int i = 0; i < typeCount; i++) {
6624 final ArrayList<View> scrap = mScrapViews[i];
6625 final int scrapCount = scrap.size();
6626 for (int j = 0; j < scrapCount; j++) {
6627 scrap.get(j).forceLayout();
6628 }
6629 }
6630 }
Adam Powell539ee872012-02-03 19:00:49 -08006631 if (mTransientStateViews != null) {
6632 final int count = mTransientStateViews.size();
6633 for (int i = 0; i < count; i++) {
6634 mTransientStateViews.valueAt(i).forceLayout();
6635 }
6636 }
Chet Haase72871322013-02-26 16:12:13 -07006637 if (mTransientStateViewsById != null) {
6638 final int count = mTransientStateViewsById.size();
6639 for (int i = 0; i < count; i++) {
6640 mTransientStateViewsById.valueAt(i).forceLayout();
6641 }
6642 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006643 }
Romain Guy0a637162009-05-29 14:43:54 -07006644
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006645 public boolean shouldRecycleViewType(int viewType) {
6646 return viewType >= 0;
6647 }
6648
6649 /**
6650 * Clears the scrap heap.
6651 */
6652 void clear() {
6653 if (mViewTypeCount == 1) {
6654 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006655 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006656 } else {
6657 final int typeCount = mViewTypeCount;
6658 for (int i = 0; i < typeCount; i++) {
6659 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006660 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006661 }
6662 }
Alan Viverette59511502013-12-09 13:49:25 -08006663
6664 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006665 }
6666
6667 /**
6668 * Fill ActiveViews with all of the children of the AbsListView.
6669 *
6670 * @param childCount The minimum number of views mActiveViews should hold
6671 * @param firstActivePosition The position of the first view that will be stored in
6672 * mActiveViews
6673 */
6674 void fillActiveViews(int childCount, int firstActivePosition) {
6675 if (mActiveViews.length < childCount) {
6676 mActiveViews = new View[childCount];
6677 }
6678 mFirstActivePosition = firstActivePosition;
6679
Romain Guyf6991302013-06-05 17:19:01 -07006680 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006681 final View[] activeViews = mActiveViews;
6682 for (int i = 0; i < childCount; i++) {
6683 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006684 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006685 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006686 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006687 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6688 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006689 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006690 // Remember the position so that setupChild() doesn't reset state.
6691 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006692 }
6693 }
6694 }
6695
6696 /**
6697 * Get the view corresponding to the specified position. The view will be removed from
6698 * mActiveViews if it is found.
6699 *
6700 * @param position The position to look up in mActiveViews
6701 * @return The view if it is found, null otherwise
6702 */
6703 View getActiveView(int position) {
6704 int index = position - mFirstActivePosition;
6705 final View[] activeViews = mActiveViews;
6706 if (index >=0 && index < activeViews.length) {
6707 final View match = activeViews[index];
6708 activeViews[index] = null;
6709 return match;
6710 }
6711 return null;
6712 }
6713
Adam Powell539ee872012-02-03 19:00:49 -08006714 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006715 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6716 long id = mAdapter.getItemId(position);
6717 View result = mTransientStateViewsById.get(id);
6718 mTransientStateViewsById.remove(id);
6719 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006720 }
Chet Haase72871322013-02-26 16:12:13 -07006721 if (mTransientStateViews != null) {
6722 final int index = mTransientStateViews.indexOfKey(position);
6723 if (index >= 0) {
6724 View result = mTransientStateViews.valueAt(index);
6725 mTransientStateViews.removeAt(index);
6726 return result;
6727 }
Adam Powell539ee872012-02-03 19:00:49 -08006728 }
Chet Haase72871322013-02-26 16:12:13 -07006729 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006730 }
6731
6732 /**
Alan Viverette59511502013-12-09 13:49:25 -08006733 * Dumps and fully detaches any currently saved views with transient
6734 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006735 */
6736 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006737 final SparseArray<View> viewsByPos = mTransientStateViews;
6738 if (viewsByPos != null) {
6739 final int N = viewsByPos.size();
6740 for (int i = 0; i < N; i++) {
6741 removeDetachedView(viewsByPos.valueAt(i), false);
6742 }
6743 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006744 }
Alan Viverette59511502013-12-09 13:49:25 -08006745
6746 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6747 if (viewsById != null) {
6748 final int N = viewsById.size();
6749 for (int i = 0; i < N; i++) {
6750 removeDetachedView(viewsById.valueAt(i), false);
6751 }
6752 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006753 }
Adam Powell539ee872012-02-03 19:00:49 -08006754 }
6755
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006756 /**
6757 * @return A view from the ScrapViews collection. These are unordered.
6758 */
6759 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006760 final int whichScrap = mAdapter.getItemViewType(position);
6761 if (whichScrap < 0) {
6762 return null;
6763 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006764 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006765 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006766 } else if (whichScrap < mScrapViews.length) {
6767 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006768 }
6769 return null;
6770 }
6771
6772 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006773 * Puts a view into the list of scrap views.
6774 * <p>
6775 * If the list data hasn't changed or the adapter has stable IDs, views
6776 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006777 *
6778 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006779 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006780 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006781 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006782 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006783 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006784 // Can't recycle, but we don't know anything about the view.
6785 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006786 return;
6787 }
6788
Adam Powell539ee872012-02-03 19:00:49 -08006789 lp.scrappedFromPosition = position;
6790
Alan Viverette1e51cc72013-09-27 14:32:20 -07006791 // Remove but don't scrap header or footer views, or views that
6792 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006793 final int viewType = lp.viewType;
6794 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006795 // Can't recycle. If it's not a header or footer, which have
6796 // special handling and should be ignored, then skip the scrap
6797 // heap and we'll fully detach the view later.
6798 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6799 getSkippedScrap().add(scrap);
6800 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006801 return;
6802 }
6803
6804 scrap.dispatchStartTemporaryDetach();
6805
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006806 // The the accessibility state of the view may change while temporary
6807 // detached and we do not allow detached views to fire accessibility
6808 // events. So we are announcing that the subtree changed giving a chance
6809 // to clients holding on to a view in this subtree to refresh it.
6810 notifyViewAccessibilityStateChangedIfNeeded(
6811 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6812
Alan Viveretted44696c2013-07-18 10:37:15 -07006813 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006814 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006815 if (scrapHasTransientState) {
6816 if (mAdapter != null && mAdapterHasStableIds) {
6817 // If the adapter has stable IDs, we can reuse the view for
6818 // the same data.
6819 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006820 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006821 }
6822 mTransientStateViewsById.put(lp.itemId, scrap);
6823 } else if (!mDataChanged) {
6824 // If the data hasn't changed, we can reuse the views at
6825 // their old positions.
6826 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006827 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006828 }
6829 mTransientStateViews.put(position, scrap);
6830 } else {
6831 // Otherwise, we'll have to remove the view and start over.
Alan Viverette8bbae342015-06-25 14:49:29 -07006832 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006833 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006834 } else {
Alan Viveretted44696c2013-07-18 10:37:15 -07006835 if (mViewTypeCount == 1) {
6836 mCurrentScrap.add(scrap);
6837 } else {
6838 mScrapViews[viewType].add(scrap);
6839 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006840
Alan Viveretted44696c2013-07-18 10:37:15 -07006841 if (mRecyclerListener != null) {
6842 mRecyclerListener.onMovedToScrapHeap(scrap);
6843 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006844 }
6845 }
6846
Alan Viverette8bbae342015-06-25 14:49:29 -07006847 private ArrayList<View> getSkippedScrap() {
6848 if (mSkippedScrap == null) {
6849 mSkippedScrap = new ArrayList<>();
6850 }
6851 return mSkippedScrap;
6852 }
6853
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006854 /**
Adam Powell539ee872012-02-03 19:00:49 -08006855 * Finish the removal of any views that skipped the scrap heap.
6856 */
6857 void removeSkippedScrap() {
6858 if (mSkippedScrap == null) {
6859 return;
6860 }
6861 final int count = mSkippedScrap.size();
6862 for (int i = 0; i < count; i++) {
6863 removeDetachedView(mSkippedScrap.get(i), false);
6864 }
6865 mSkippedScrap.clear();
6866 }
6867
6868 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006869 * Move all views remaining in mActiveViews to mScrapViews.
6870 */
6871 void scrapActiveViews() {
6872 final View[] activeViews = mActiveViews;
6873 final boolean hasListener = mRecyclerListener != null;
6874 final boolean multipleScraps = mViewTypeCount > 1;
6875
6876 ArrayList<View> scrapViews = mCurrentScrap;
6877 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006878 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006879 final View victim = activeViews[i];
6880 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006881 final AbsListView.LayoutParams lp
6882 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006883 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006884
6885 activeViews[i] = null;
6886
Alan Viverette59511502013-12-09 13:49:25 -08006887 if (victim.hasTransientState()) {
6888 // Store views with transient state for later use.
6889 victim.dispatchStartTemporaryDetach();
6890
6891 if (mAdapter != null && mAdapterHasStableIds) {
6892 if (mTransientStateViewsById == null) {
6893 mTransientStateViewsById = new LongSparseArray<View>();
6894 }
6895 long id = mAdapter.getItemId(mFirstActivePosition + i);
6896 mTransientStateViewsById.put(id, victim);
6897 } else if (!mDataChanged) {
6898 if (mTransientStateViews == null) {
6899 mTransientStateViews = new SparseArray<View>();
6900 }
6901 mTransientStateViews.put(mFirstActivePosition + i, victim);
6902 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6903 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006904 removeDetachedView(victim, false);
6905 }
Alan Viverette59511502013-12-09 13:49:25 -08006906 } else if (!shouldRecycleViewType(whichScrap)) {
6907 // Discard non-recyclable views except headers/footers.
6908 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6909 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006910 }
Alan Viverette59511502013-12-09 13:49:25 -08006911 } else {
6912 // Store everything else on the appropriate scrap heap.
6913 if (multipleScraps) {
6914 scrapViews = mScrapViews[whichScrap];
6915 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006916
Alan Viverette59511502013-12-09 13:49:25 -08006917 lp.scrappedFromPosition = mFirstActivePosition + i;
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006918 removeDetachedView(victim, false);
Alan Viverette59511502013-12-09 13:49:25 -08006919 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006920
Alan Viverette59511502013-12-09 13:49:25 -08006921 if (hasListener) {
6922 mRecyclerListener.onMovedToScrapHeap(victim);
6923 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006924 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006925 }
6926 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006927 pruneScrapViews();
6928 }
6929
6930 /**
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006931 * At the end of a layout pass, all temp detached views should either be re-attached or
6932 * completely detached. This method ensures that any remaining view in the scrap list is
6933 * fully detached.
6934 */
6935 void fullyDetachScrapViews() {
6936 final int viewTypeCount = mViewTypeCount;
6937 final ArrayList<View>[] scrapViews = mScrapViews;
6938 for (int i = 0; i < viewTypeCount; ++i) {
6939 final ArrayList<View> scrapPile = scrapViews[i];
6940 for (int j = scrapPile.size() - 1; j >= 0; j--) {
6941 final View view = scrapPile.get(j);
6942 if (view.isTemporarilyDetached()) {
6943 removeDetachedView(view, false);
6944 }
6945 }
6946 }
6947 }
6948
6949 /**
Alan Viverette59511502013-12-09 13:49:25 -08006950 * Makes sure that the size of mScrapViews does not exceed the size of
6951 * mActiveViews, which can happen if an adapter does not recycle its
6952 * views. Removes cached transient state views that no longer have
6953 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006954 */
6955 private void pruneScrapViews() {
6956 final int maxViews = mActiveViews.length;
6957 final int viewTypeCount = mViewTypeCount;
6958 final ArrayList<View>[] scrapViews = mScrapViews;
6959 for (int i = 0; i < viewTypeCount; ++i) {
6960 final ArrayList<View> scrapPile = scrapViews[i];
6961 int size = scrapPile.size();
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07006962 while (size > maxViews) {
6963 scrapPile.remove(--size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006964 }
6965 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006966
Alan Viverette59511502013-12-09 13:49:25 -08006967 final SparseArray<View> transViewsByPos = mTransientStateViews;
6968 if (transViewsByPos != null) {
6969 for (int i = 0; i < transViewsByPos.size(); i++) {
6970 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07006971 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08006972 removeDetachedView(v, false);
6973 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07006974 i--;
6975 }
6976 }
6977 }
Alan Viverette59511502013-12-09 13:49:25 -08006978
6979 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
6980 if (transViewsById != null) {
6981 for (int i = 0; i < transViewsById.size(); i++) {
6982 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07006983 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08006984 removeDetachedView(v, false);
6985 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07006986 i--;
6987 }
6988 }
6989 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006990 }
6991
6992 /**
6993 * Puts all views in the scrap heap into the supplied list.
6994 */
6995 void reclaimScrapViews(List<View> views) {
6996 if (mViewTypeCount == 1) {
6997 views.addAll(mCurrentScrap);
6998 } else {
6999 final int viewTypeCount = mViewTypeCount;
7000 final ArrayList<View>[] scrapViews = mScrapViews;
7001 for (int i = 0; i < viewTypeCount; ++i) {
7002 final ArrayList<View> scrapPile = scrapViews[i];
7003 views.addAll(scrapPile);
7004 }
7005 }
7006 }
Romain Guy52e2ef82010-01-14 12:11:48 -08007007
7008 /**
7009 * Updates the cache color hint of all known views.
7010 *
7011 * @param color The new cache color hint.
7012 */
7013 void setCacheColorHint(int color) {
7014 if (mViewTypeCount == 1) {
7015 final ArrayList<View> scrap = mCurrentScrap;
7016 final int scrapCount = scrap.size();
7017 for (int i = 0; i < scrapCount; i++) {
7018 scrap.get(i).setDrawingCacheBackgroundColor(color);
7019 }
7020 } else {
7021 final int typeCount = mViewTypeCount;
7022 for (int i = 0; i < typeCount; i++) {
7023 final ArrayList<View> scrap = mScrapViews[i];
7024 final int scrapCount = scrap.size();
7025 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07007026 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08007027 }
7028 }
7029 }
7030 // Just in case this is called during a layout pass
7031 final View[] activeViews = mActiveViews;
7032 final int count = activeViews.length;
7033 for (int i = 0; i < count; ++i) {
7034 final View victim = activeViews[i];
7035 if (victim != null) {
7036 victim.setDrawingCacheBackgroundColor(color);
7037 }
7038 }
7039 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007040
Alan Viverette3e141622014-02-18 17:05:13 -08007041 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7042 final int size = scrapViews.size();
7043 if (size > 0) {
7044 // See if we still have a view for this position or ID.
7045 for (int i = 0; i < size; i++) {
7046 final View view = scrapViews.get(i);
7047 final AbsListView.LayoutParams params =
7048 (AbsListView.LayoutParams) view.getLayoutParams();
7049
7050 if (mAdapterHasStableIds) {
7051 final long id = mAdapter.getItemId(position);
7052 if (id == params.itemId) {
7053 return scrapViews.remove(i);
7054 }
7055 } else if (params.scrappedFromPosition == position) {
7056 final View scrap = scrapViews.remove(i);
7057 clearAccessibilityFromScrap(scrap);
7058 return scrap;
7059 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07007060 }
Alan Viverette3e141622014-02-18 17:05:13 -08007061 final View scrap = scrapViews.remove(size - 1);
7062 clearAccessibilityFromScrap(scrap);
7063 return scrap;
7064 } else {
7065 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07007066 }
Alan Viverette3e141622014-02-18 17:05:13 -08007067 }
7068
7069 private void clearScrap(final ArrayList<View> scrap) {
7070 final int scrapCount = scrap.size();
7071 for (int j = 0; j < scrapCount; j++) {
7072 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7073 }
7074 }
7075
7076 private void clearAccessibilityFromScrap(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07007077 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08007078 view.setAccessibilityDelegate(null);
7079 }
7080
7081 private void removeDetachedView(View child, boolean animate) {
7082 child.setAccessibilityDelegate(null);
7083 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07007084 }
7085 }
Alan Viverette441b4372014-02-12 13:30:20 -08007086
7087 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007088 * Returns the height of the view for the specified position.
7089 *
7090 * @param position the item position
7091 * @return view height in pixels
7092 */
7093 int getHeightForPosition(int position) {
7094 final int firstVisiblePosition = getFirstVisiblePosition();
7095 final int childCount = getChildCount();
7096 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08007097 if (index >= 0 && index < childCount) {
7098 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08007099 final View view = getChildAt(index);
7100 return view.getHeight();
7101 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08007102 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08007103 final View view = obtainView(position, mIsScrap);
7104 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7105 final int height = view.getMeasuredHeight();
7106 mRecycler.addScrapView(view, position);
7107 return height;
7108 }
7109 }
7110
7111 /**
Alan Viverette441b4372014-02-12 13:30:20 -08007112 * Sets the selected item and positions the selection y pixels from the top edge
7113 * of the ListView. (If in touch mode, the item will not be selected but it will
7114 * still be positioned appropriately.)
7115 *
7116 * @param position Index (starting at 0) of the data item to be selected.
7117 * @param y The distance from the top edge of the ListView (plus padding) that the
7118 * item will be positioned.
7119 */
7120 public void setSelectionFromTop(int position, int y) {
7121 if (mAdapter == null) {
7122 return;
7123 }
7124
7125 if (!isInTouchMode()) {
7126 position = lookForSelectablePosition(position, true);
7127 if (position >= 0) {
7128 setNextSelectedPositionInt(position);
7129 }
7130 } else {
7131 mResurrectToPosition = position;
7132 }
7133
7134 if (position >= 0) {
7135 mLayoutMode = LAYOUT_SPECIFIC;
7136 mSpecificTop = mListPadding.top + y;
7137
7138 if (mNeedSync) {
7139 mSyncPosition = position;
7140 mSyncRowId = mAdapter.getItemId(position);
7141 }
7142
7143 if (mPositionScroller != null) {
7144 mPositionScroller.stop();
7145 }
7146 requestLayout();
7147 }
7148 }
7149
Siva Velusamy94a6d152015-05-05 15:07:00 -07007150 /** @hide */
7151 @Override
7152 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7153 super.encodeProperties(encoder);
7154
7155 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7156 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7157 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7158 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7159 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7160 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7161
7162 View selectedView = getSelectedView();
7163 if (selectedView != null) {
7164 encoder.addPropertyKey("selectedView");
7165 selectedView.encode(encoder);
7166 }
7167 }
7168
Alan Viveretted22db212014-02-13 17:47:38 -08007169 /**
7170 * Abstract positon scroller used to handle smooth scrolling.
7171 */
7172 static abstract class AbsPositionScroller {
7173 public abstract void start(int position);
7174 public abstract void start(int position, int boundPosition);
7175 public abstract void startWithOffset(int position, int offset);
7176 public abstract void startWithOffset(int position, int offset, int duration);
7177 public abstract void stop();
7178 }
7179
7180 /**
7181 * Default position scroller that simulates a fling.
7182 */
7183 class PositionScroller extends AbsPositionScroller implements Runnable {
7184 private static final int SCROLL_DURATION = 200;
7185
7186 private static final int MOVE_DOWN_POS = 1;
7187 private static final int MOVE_UP_POS = 2;
7188 private static final int MOVE_DOWN_BOUND = 3;
7189 private static final int MOVE_UP_BOUND = 4;
7190 private static final int MOVE_OFFSET = 5;
7191
7192 private int mMode;
7193 private int mTargetPos;
7194 private int mBoundPos;
7195 private int mLastSeenPos;
7196 private int mScrollDuration;
7197 private final int mExtraScroll;
7198
7199 private int mOffsetFromTop;
7200
7201 PositionScroller() {
7202 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7203 }
7204
7205 @Override
7206 public void start(final int position) {
7207 stop();
7208
7209 if (mDataChanged) {
7210 // Wait until we're back in a stable state to try this.
7211 mPositionScrollAfterLayout = new Runnable() {
7212 @Override public void run() {
7213 start(position);
7214 }
7215 };
7216 return;
7217 }
7218
7219 final int childCount = getChildCount();
7220 if (childCount == 0) {
7221 // Can't scroll without children.
7222 return;
7223 }
7224
7225 final int firstPos = mFirstPosition;
7226 final int lastPos = firstPos + childCount - 1;
7227
7228 int viewTravelCount;
7229 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7230 if (clampedPosition < firstPos) {
7231 viewTravelCount = firstPos - clampedPosition + 1;
7232 mMode = MOVE_UP_POS;
7233 } else if (clampedPosition > lastPos) {
7234 viewTravelCount = clampedPosition - lastPos + 1;
7235 mMode = MOVE_DOWN_POS;
7236 } else {
7237 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7238 return;
7239 }
7240
7241 if (viewTravelCount > 0) {
7242 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7243 } else {
7244 mScrollDuration = SCROLL_DURATION;
7245 }
7246 mTargetPos = clampedPosition;
7247 mBoundPos = INVALID_POSITION;
7248 mLastSeenPos = INVALID_POSITION;
7249
7250 postOnAnimation(this);
7251 }
7252
7253 @Override
7254 public void start(final int position, final int boundPosition) {
7255 stop();
7256
7257 if (boundPosition == INVALID_POSITION) {
7258 start(position);
7259 return;
7260 }
7261
7262 if (mDataChanged) {
7263 // Wait until we're back in a stable state to try this.
7264 mPositionScrollAfterLayout = new Runnable() {
7265 @Override public void run() {
7266 start(position, boundPosition);
7267 }
7268 };
7269 return;
7270 }
7271
7272 final int childCount = getChildCount();
7273 if (childCount == 0) {
7274 // Can't scroll without children.
7275 return;
7276 }
7277
7278 final int firstPos = mFirstPosition;
7279 final int lastPos = firstPos + childCount - 1;
7280
7281 int viewTravelCount;
7282 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7283 if (clampedPosition < firstPos) {
7284 final int boundPosFromLast = lastPos - boundPosition;
7285 if (boundPosFromLast < 1) {
7286 // Moving would shift our bound position off the screen. Abort.
7287 return;
7288 }
7289
7290 final int posTravel = firstPos - clampedPosition + 1;
7291 final int boundTravel = boundPosFromLast - 1;
7292 if (boundTravel < posTravel) {
7293 viewTravelCount = boundTravel;
7294 mMode = MOVE_UP_BOUND;
7295 } else {
7296 viewTravelCount = posTravel;
7297 mMode = MOVE_UP_POS;
7298 }
7299 } else if (clampedPosition > lastPos) {
7300 final int boundPosFromFirst = boundPosition - firstPos;
7301 if (boundPosFromFirst < 1) {
7302 // Moving would shift our bound position off the screen. Abort.
7303 return;
7304 }
7305
7306 final int posTravel = clampedPosition - lastPos + 1;
7307 final int boundTravel = boundPosFromFirst - 1;
7308 if (boundTravel < posTravel) {
7309 viewTravelCount = boundTravel;
7310 mMode = MOVE_DOWN_BOUND;
7311 } else {
7312 viewTravelCount = posTravel;
7313 mMode = MOVE_DOWN_POS;
7314 }
7315 } else {
7316 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7317 return;
7318 }
7319
7320 if (viewTravelCount > 0) {
7321 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7322 } else {
7323 mScrollDuration = SCROLL_DURATION;
7324 }
7325 mTargetPos = clampedPosition;
7326 mBoundPos = boundPosition;
7327 mLastSeenPos = INVALID_POSITION;
7328
7329 postOnAnimation(this);
7330 }
7331
7332 @Override
7333 public void startWithOffset(int position, int offset) {
7334 startWithOffset(position, offset, SCROLL_DURATION);
7335 }
7336
7337 @Override
7338 public void startWithOffset(final int position, int offset, final int duration) {
7339 stop();
7340
7341 if (mDataChanged) {
7342 // Wait until we're back in a stable state to try this.
7343 final int postOffset = offset;
7344 mPositionScrollAfterLayout = new Runnable() {
7345 @Override public void run() {
7346 startWithOffset(position, postOffset, duration);
7347 }
7348 };
7349 return;
7350 }
7351
7352 final int childCount = getChildCount();
7353 if (childCount == 0) {
7354 // Can't scroll without children.
7355 return;
7356 }
7357
7358 offset += getPaddingTop();
7359
7360 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7361 mOffsetFromTop = offset;
7362 mBoundPos = INVALID_POSITION;
7363 mLastSeenPos = INVALID_POSITION;
7364 mMode = MOVE_OFFSET;
7365
7366 final int firstPos = mFirstPosition;
7367 final int lastPos = firstPos + childCount - 1;
7368
7369 int viewTravelCount;
7370 if (mTargetPos < firstPos) {
7371 viewTravelCount = firstPos - mTargetPos;
7372 } else if (mTargetPos > lastPos) {
7373 viewTravelCount = mTargetPos - lastPos;
7374 } else {
7375 // On-screen, just scroll.
7376 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
7377 smoothScrollBy(targetTop - offset, duration, true);
7378 return;
7379 }
7380
7381 // Estimate how many screens we should travel
7382 final float screenTravelCount = (float) viewTravelCount / childCount;
7383 mScrollDuration = screenTravelCount < 1 ?
7384 duration : (int) (duration / screenTravelCount);
7385 mLastSeenPos = INVALID_POSITION;
7386
7387 postOnAnimation(this);
7388 }
7389
7390 /**
7391 * Scroll such that targetPos is in the visible padded region without scrolling
7392 * boundPos out of view. Assumes targetPos is onscreen.
7393 */
7394 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7395 final int firstPos = mFirstPosition;
7396 final int childCount = getChildCount();
7397 final int lastPos = firstPos + childCount - 1;
7398 final int paddedTop = mListPadding.top;
7399 final int paddedBottom = getHeight() - mListPadding.bottom;
7400
7401 if (targetPos < firstPos || targetPos > lastPos) {
7402 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7403 " not visible [" + firstPos + ", " + lastPos + "]");
7404 }
7405 if (boundPos < firstPos || boundPos > lastPos) {
7406 // boundPos doesn't matter, it's already offscreen.
7407 boundPos = INVALID_POSITION;
7408 }
7409
7410 final View targetChild = getChildAt(targetPos - firstPos);
7411 final int targetTop = targetChild.getTop();
7412 final int targetBottom = targetChild.getBottom();
7413 int scrollBy = 0;
7414
7415 if (targetBottom > paddedBottom) {
7416 scrollBy = targetBottom - paddedBottom;
7417 }
7418 if (targetTop < paddedTop) {
7419 scrollBy = targetTop - paddedTop;
7420 }
7421
7422 if (scrollBy == 0) {
7423 return;
7424 }
7425
7426 if (boundPos >= 0) {
7427 final View boundChild = getChildAt(boundPos - firstPos);
7428 final int boundTop = boundChild.getTop();
7429 final int boundBottom = boundChild.getBottom();
7430 final int absScroll = Math.abs(scrollBy);
7431
7432 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7433 // Don't scroll the bound view off the bottom of the screen.
7434 scrollBy = Math.max(0, boundBottom - paddedBottom);
7435 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7436 // Don't scroll the bound view off the top of the screen.
7437 scrollBy = Math.min(0, boundTop - paddedTop);
7438 }
7439 }
7440
7441 smoothScrollBy(scrollBy, duration);
7442 }
7443
7444 @Override
7445 public void stop() {
7446 removeCallbacks(this);
7447 }
7448
7449 @Override
7450 public void run() {
7451 final int listHeight = getHeight();
7452 final int firstPos = mFirstPosition;
7453
7454 switch (mMode) {
7455 case MOVE_DOWN_POS: {
7456 final int lastViewIndex = getChildCount() - 1;
7457 final int lastPos = firstPos + lastViewIndex;
7458
7459 if (lastViewIndex < 0) {
7460 return;
7461 }
7462
7463 if (lastPos == mLastSeenPos) {
7464 // No new views, let things keep going.
7465 postOnAnimation(this);
7466 return;
7467 }
7468
7469 final View lastView = getChildAt(lastViewIndex);
7470 final int lastViewHeight = lastView.getHeight();
7471 final int lastViewTop = lastView.getTop();
7472 final int lastViewPixelsShowing = listHeight - lastViewTop;
7473 final int extraScroll = lastPos < mItemCount - 1 ?
7474 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7475
7476 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
7477 smoothScrollBy(scrollBy, mScrollDuration, true);
7478
7479 mLastSeenPos = lastPos;
7480 if (lastPos < mTargetPos) {
7481 postOnAnimation(this);
7482 }
7483 break;
7484 }
7485
7486 case MOVE_DOWN_BOUND: {
7487 final int nextViewIndex = 1;
7488 final int childCount = getChildCount();
7489
7490 if (firstPos == mBoundPos || childCount <= nextViewIndex
7491 || firstPos + childCount >= mItemCount) {
7492 return;
7493 }
7494 final int nextPos = firstPos + nextViewIndex;
7495
7496 if (nextPos == mLastSeenPos) {
7497 // No new views, let things keep going.
7498 postOnAnimation(this);
7499 return;
7500 }
7501
7502 final View nextView = getChildAt(nextViewIndex);
7503 final int nextViewHeight = nextView.getHeight();
7504 final int nextViewTop = nextView.getTop();
7505 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7506 if (nextPos < mBoundPos) {
7507 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
7508 mScrollDuration, true);
7509
7510 mLastSeenPos = nextPos;
7511
7512 postOnAnimation(this);
7513 } else {
7514 if (nextViewTop > extraScroll) {
7515 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
7516 }
7517 }
7518 break;
7519 }
7520
7521 case MOVE_UP_POS: {
7522 if (firstPos == mLastSeenPos) {
7523 // No new views, let things keep going.
7524 postOnAnimation(this);
7525 return;
7526 }
7527
7528 final View firstView = getChildAt(0);
7529 if (firstView == null) {
7530 return;
7531 }
7532 final int firstViewTop = firstView.getTop();
7533 final int extraScroll = firstPos > 0 ?
7534 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7535
7536 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
7537
7538 mLastSeenPos = firstPos;
7539
7540 if (firstPos > mTargetPos) {
7541 postOnAnimation(this);
7542 }
7543 break;
7544 }
7545
7546 case MOVE_UP_BOUND: {
7547 final int lastViewIndex = getChildCount() - 2;
7548 if (lastViewIndex < 0) {
7549 return;
7550 }
7551 final int lastPos = firstPos + lastViewIndex;
7552
7553 if (lastPos == mLastSeenPos) {
7554 // No new views, let things keep going.
7555 postOnAnimation(this);
7556 return;
7557 }
7558
7559 final View lastView = getChildAt(lastViewIndex);
7560 final int lastViewHeight = lastView.getHeight();
7561 final int lastViewTop = lastView.getTop();
7562 final int lastViewPixelsShowing = listHeight - lastViewTop;
7563 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7564 mLastSeenPos = lastPos;
7565 if (lastPos > mBoundPos) {
7566 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
7567 postOnAnimation(this);
7568 } else {
7569 final int bottom = listHeight - extraScroll;
7570 final int lastViewBottom = lastViewTop + lastViewHeight;
7571 if (bottom > lastViewBottom) {
7572 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
7573 }
7574 }
7575 break;
7576 }
7577
7578 case MOVE_OFFSET: {
7579 if (mLastSeenPos == firstPos) {
7580 // No new views, let things keep going.
7581 postOnAnimation(this);
7582 return;
7583 }
7584
7585 mLastSeenPos = firstPos;
7586
7587 final int childCount = getChildCount();
7588 final int position = mTargetPos;
7589 final int lastPos = firstPos + childCount - 1;
7590
7591 int viewTravelCount = 0;
7592 if (position < firstPos) {
7593 viewTravelCount = firstPos - position + 1;
7594 } else if (position > lastPos) {
7595 viewTravelCount = position - lastPos;
7596 }
7597
7598 // Estimate how many screens we should travel
7599 final float screenTravelCount = (float) viewTravelCount / childCount;
7600
7601 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7602 if (position < firstPos) {
7603 final int distance = (int) (-getHeight() * modifier);
7604 final int duration = (int) (mScrollDuration * modifier);
7605 smoothScrollBy(distance, duration, true);
7606 postOnAnimation(this);
7607 } else if (position > lastPos) {
7608 final int distance = (int) (getHeight() * modifier);
7609 final int duration = (int) (mScrollDuration * modifier);
7610 smoothScrollBy(distance, duration, true);
7611 postOnAnimation(this);
7612 } else {
7613 // On-screen, just scroll.
7614 final int targetTop = getChildAt(position - firstPos).getTop();
7615 final int distance = targetTop - mOffsetFromTop;
7616 final int duration = (int) (mScrollDuration *
7617 ((float) Math.abs(distance) / getHeight()));
7618 smoothScrollBy(distance, duration, true);
7619 }
7620 break;
7621 }
7622
7623 default:
7624 break;
7625 }
7626 }
7627 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007628}