blob: f050e49f0b05b651a5602750748af9f191b6100b [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070029import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.os.Parcel;
32import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080033import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070034import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070036import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.text.TextUtils;
38import android.text.TextWatcher;
39import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080040import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070041import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080042import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070043import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070044import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070045import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070046import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.view.Gravity;
48import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080049import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.view.KeyEvent;
51import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070052import android.view.Menu;
53import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054import android.view.MotionEvent;
55import android.view.VelocityTracker;
56import android.view.View;
57import android.view.ViewConfiguration;
58import android.view.ViewDebug;
59import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070060import android.view.ViewHierarchyEncoder;
Michael Jurka13451a42011-08-22 15:54:21 -070061import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070063import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070064import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070065import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070066import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette76769ae2014-02-12 16:38:10 -080067import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070068import android.view.animation.Interpolator;
69import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070070import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070071import android.view.inputmethod.CompletionInfo;
72import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070073import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070074import android.view.inputmethod.ExtractedText;
75import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070076import android.view.inputmethod.InputConnection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070078import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079
Adam Cohen335c3b62012-07-24 17:18:16 -070080import com.android.internal.R;
81
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082import java.util.ArrayList;
83import java.util.List;
84
85/**
Romain Guyd6a463a2009-05-21 23:10:10 -070086 * Base class that can be used to implement virtualized lists of items. A list does
87 * not have a spatial definition here. For instance, subclases of this class can
88 * 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 -080089 *
90 * @attr ref android.R.styleable#AbsListView_listSelector
91 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
92 * @attr ref android.R.styleable#AbsListView_stackFromBottom
93 * @attr ref android.R.styleable#AbsListView_scrollingCache
94 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
95 * @attr ref android.R.styleable#AbsListView_transcriptMode
96 * @attr ref android.R.styleable#AbsListView_cacheColorHint
97 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
98 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -070099 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100 */
101public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
102 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -0700103 ViewTreeObserver.OnTouchModeChangeListener,
104 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105
Romain Guy9d849a22012-03-14 16:41:42 -0700106 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800107 private static final String TAG = "AbsListView";
108
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 /**
110 * Disables the transcript mode.
111 *
112 * @see #setTranscriptMode(int)
113 */
114 public static final int TRANSCRIPT_MODE_DISABLED = 0;
Alan Viverettede399392014-05-01 17:20:55 -0700115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 /**
117 * The list will automatically scroll to the bottom when a data set change
118 * notification is received and only if the last item is already visible
119 * on screen.
120 *
121 * @see #setTranscriptMode(int)
122 */
123 public static final int TRANSCRIPT_MODE_NORMAL = 1;
Alan Viverettede399392014-05-01 17:20:55 -0700124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 /**
126 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700127 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 *
129 * @see #setTranscriptMode(int)
130 */
131 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
132
133 /**
134 * Indicates that we are not in the middle of a touch gesture
135 */
136 static final int TOUCH_MODE_REST = -1;
137
138 /**
139 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
140 * scroll gesture.
141 */
142 static final int TOUCH_MODE_DOWN = 0;
143
144 /**
145 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
146 * is a longpress
147 */
148 static final int TOUCH_MODE_TAP = 1;
149
150 /**
151 * Indicates we have waited for everything we can wait for, but the user's finger is still down
152 */
153 static final int TOUCH_MODE_DONE_WAITING = 2;
154
155 /**
156 * Indicates the touch gesture is a scroll
157 */
158 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800159
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 /**
161 * Indicates the view is in the process of being flung
162 */
163 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700164
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 /**
Adam Powell637d3372010-08-25 14:37:03 -0700166 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
167 */
168 static final int TOUCH_MODE_OVERSCROLL = 5;
169
170 /**
171 * Indicates the view is being flung outside of normal content bounds
172 * and will spring back.
173 */
174 static final int TOUCH_MODE_OVERFLING = 6;
175
176 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 * Regular layout - usually an unsolicited layout from the view system
178 */
179 static final int LAYOUT_NORMAL = 0;
180
181 /**
182 * Show the first item
183 */
184 static final int LAYOUT_FORCE_TOP = 1;
185
186 /**
187 * Force the selected item to be on somewhere on the screen
188 */
189 static final int LAYOUT_SET_SELECTION = 2;
190
191 /**
192 * Show the last item
193 */
194 static final int LAYOUT_FORCE_BOTTOM = 3;
195
196 /**
197 * Make a mSelectedItem appear in a specific location and build the rest of
198 * the views from there. The top is specified by mSpecificTop.
199 */
200 static final int LAYOUT_SPECIFIC = 4;
201
202 /**
203 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
204 * at mSpecificTop
205 */
206 static final int LAYOUT_SYNC = 5;
207
208 /**
209 * Layout as a result of using the navigation keys
210 */
211 static final int LAYOUT_MOVE_SELECTION = 6;
212
213 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700214 * Normal list that does not indicate choices
215 */
216 public static final int CHOICE_MODE_NONE = 0;
217
218 /**
219 * The list allows up to one choice
220 */
221 public static final int CHOICE_MODE_SINGLE = 1;
222
223 /**
224 * The list allows multiple choices
225 */
226 public static final int CHOICE_MODE_MULTIPLE = 2;
227
228 /**
229 * The list allows multiple choices in a modal selection mode
230 */
231 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
232
233 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700234 * The thread that created this view.
235 */
236 private final Thread mOwnerThread;
237
238 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700239 * Controls if/how the user may choose/check items in the list
240 */
241 int mChoiceMode = CHOICE_MODE_NONE;
242
243 /**
244 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
245 */
246 ActionMode mChoiceActionMode;
247
248 /**
249 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
250 * a few extra actions around what application code does.
251 */
252 MultiChoiceModeWrapper mMultiChoiceModeCallback;
253
254 /**
255 * Running count of how many items are currently checked
256 */
257 int mCheckedItemCount;
258
259 /**
260 * Running state of which positions are currently checked
261 */
262 SparseBooleanArray mCheckStates;
263
264 /**
Adam Powell14c08042011-10-06 19:46:18 -0700265 * Running state of which IDs are currently checked.
266 * If there is a value for a given key, the checked state for that ID is true
267 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700268 */
Adam Powell14c08042011-10-06 19:46:18 -0700269 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700270
271 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 * Controls how the next layout will happen
273 */
274 int mLayoutMode = LAYOUT_NORMAL;
275
276 /**
277 * Should be used by subclasses to listen to changes in the dataset
278 */
279 AdapterDataSetObserver mDataSetObserver;
280
281 /**
282 * The adapter containing the data to be displayed by this view
283 */
284 ListAdapter mAdapter;
285
286 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700287 * The remote adapter containing the data to be displayed by this view to be set
288 */
289 private RemoteViewsAdapter mRemoteAdapter;
290
291 /**
Adam Powell539ee872012-02-03 19:00:49 -0800292 * If mAdapter != null, whenever this is true the adapter has stable IDs.
293 */
294 boolean mAdapterHasStableIds;
295
296 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700297 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
298 */
299 private boolean mDeferNotifyDataSetChanged = false;
300
301 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 * Indicates whether the list selector should be drawn on top of the children or behind
303 */
304 boolean mDrawSelectorOnTop = false;
305
306 /**
307 * The drawable used to draw the selector
308 */
309 Drawable mSelector;
310
311 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700312 * The current position of the selector in the list.
313 */
314 int mSelectorPosition = INVALID_POSITION;
315
316 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317 * Defines the selector's location and dimension at drawing time
318 */
319 Rect mSelectorRect = new Rect();
320
321 /**
322 * The data set used to store unused views that should be reused during the next layout
323 * to avoid creating new ones
324 */
325 final RecycleBin mRecycler = new RecycleBin();
326
327 /**
328 * The selection's left padding
329 */
330 int mSelectionLeftPadding = 0;
331
332 /**
333 * The selection's top padding
334 */
335 int mSelectionTopPadding = 0;
336
337 /**
338 * The selection's right padding
339 */
340 int mSelectionRightPadding = 0;
341
342 /**
343 * The selection's bottom padding
344 */
345 int mSelectionBottomPadding = 0;
346
347 /**
348 * This view's padding
349 */
350 Rect mListPadding = new Rect();
351
352 /**
353 * Subclasses must retain their measure spec from onMeasure() into this member
354 */
355 int mWidthMeasureSpec = 0;
356
357 /**
358 * The top scroll indicator
359 */
360 View mScrollUp;
361
362 /**
363 * The down scroll indicator
364 */
365 View mScrollDown;
366
367 /**
368 * When the view is scrolling, this flag is set to true to indicate subclasses that
369 * the drawing cache was enabled on the children
370 */
371 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800372 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373
374 /**
375 * The position of the view that received the down motion event
376 */
377 int mMotionPosition;
378
379 /**
380 * The offset to the top of the mMotionPosition view when the down motion event was received
381 */
382 int mMotionViewOriginalTop;
383
384 /**
385 * The desired offset to the top of the mMotionPosition view after a scroll
386 */
387 int mMotionViewNewTop;
388
389 /**
390 * The X value associated with the the down motion event
391 */
392 int mMotionX;
393
394 /**
395 * The Y value associated with the the down motion event
396 */
397 int mMotionY;
398
399 /**
400 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
401 * TOUCH_MODE_DONE_WAITING
402 */
403 int mTouchMode = TOUCH_MODE_REST;
404
405 /**
406 * Y value from on the previous motion event (if any)
407 */
408 int mLastY;
409
410 /**
411 * How far the finger moved before we started scrolling
412 */
413 int mMotionCorrection;
414
415 /**
416 * Determines speed during touch scrolling
417 */
418 private VelocityTracker mVelocityTracker;
419
420 /**
421 * Handles one frame of a fling
422 */
423 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800424
Adam Powell45803472010-01-25 15:10:44 -0800425 /**
426 * Handles scrolling between positions within the list.
427 */
Alan Viveretted22db212014-02-13 17:47:38 -0800428 AbsPositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429
430 /**
431 * The offset in pixels form the top of the AdapterView to the top
432 * of the currently selected view. Used to save and restore state.
433 */
434 int mSelectedTop = 0;
435
436 /**
437 * Indicates whether the list is stacked from the bottom edge or
438 * the top edge.
439 */
440 boolean mStackFromBottom;
441
442 /**
443 * When set to true, the list automatically discards the children's
444 * bitmap cache after scrolling.
445 */
446 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 /**
449 * Whether or not to enable the fast scroll feature on this list
450 */
451 boolean mFastScrollEnabled;
452
453 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700454 * Whether or not to always show the fast scroll feature on this list
455 */
456 boolean mFastScrollAlwaysVisible;
457
458 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 * Optional callback to notify client when scroll position has changed
460 */
461 private OnScrollListener mOnScrollListener;
462
463 /**
464 * Keeps track of our accessory window
465 */
466 PopupWindow mPopup;
467
468 /**
469 * Used with type filter window
470 */
471 EditText mTextFilter;
472
473 /**
474 * Indicates whether to use pixels-based or position-based scrollbar
475 * properties.
476 */
477 private boolean mSmoothScrollbarEnabled = true;
478
479 /**
480 * Indicates that this view supports filtering
481 */
482 private boolean mTextFilterEnabled;
483
484 /**
485 * Indicates that this view is currently displaying a filtered view of the data
486 */
487 private boolean mFiltered;
488
489 /**
490 * Rectangle used for hit testing children
491 */
492 private Rect mTouchFrame;
493
494 /**
495 * The position to resurrect the selected position to.
496 */
497 int mResurrectToPosition = INVALID_POSITION;
498
499 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800500
Adam Powell0b8bb422010-02-08 14:30:45 -0800501 /**
Adam Powell637d3372010-08-25 14:37:03 -0700502 * Maximum distance to record overscroll
503 */
504 int mOverscrollMax;
505
506 /**
507 * Content height divided by this is the overscroll limit.
508 */
509 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
510
511 /**
Adam Powell14c08042011-10-06 19:46:18 -0700512 * How many positions in either direction we will search to try to
513 * find a checked item with a stable ID that moved position across
514 * a data set change. If the item isn't found it will be unselected.
515 */
516 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
517
518 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 * Used to request a layout when we changed touch mode
520 */
521 private static final int TOUCH_MODE_UNKNOWN = -1;
522 private static final int TOUCH_MODE_ON = 0;
523 private static final int TOUCH_MODE_OFF = 1;
524
525 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
526
527 private static final boolean PROFILE_SCROLLING = false;
528 private boolean mScrollProfilingStarted = false;
529
530 private static final boolean PROFILE_FLINGING = false;
531 private boolean mFlingProfilingStarted = false;
532
533 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800534 * The StrictMode "critical time span" objects to catch animation
535 * stutters. Non-null when a time-sensitive animation is
536 * in-flight. Must call finish() on them when done animating.
537 * These are no-ops on user builds.
538 */
539 private StrictMode.Span mScrollStrictSpan = null;
540 private StrictMode.Span mFlingStrictSpan = null;
541
542 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800543 * The last CheckForLongPress runnable we posted, if any
544 */
545 private CheckForLongPress mPendingCheckForLongPress;
546
547 /**
548 * The last CheckForTap runnable we posted, if any
549 */
Alan Viveretted1ca75b2014-04-27 18:13:34 -0700550 private CheckForTap mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700551
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 /**
553 * The last CheckForKeyLongPress runnable we posted, if any
554 */
555 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
556
557 /**
558 * Acts upon click
559 */
560 private AbsListView.PerformClick mPerformClick;
561
562 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800563 * Delayed action for touch mode.
564 */
565 private Runnable mTouchModeReset;
566
567 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 * This view is in transcript mode -- it shows the bottom of the list when the data
569 * changes
570 */
571 private int mTranscriptMode;
572
573 /**
574 * Indicates that this list is always drawn on top of a solid, single-color, opaque
575 * background
576 */
577 private int mCacheColorHint;
578
579 /**
580 * The select child's view (from the adapter's getView) is enabled.
581 */
582 private boolean mIsChildViewEnabled;
583
584 /**
Alan Viverettef723c832015-02-03 16:31:46 -0800585 * The cached drawable state for the selector. Accounts for child enabled
586 * state, but otherwise identical to the view's own drawable state.
587 */
588 private int[] mSelectorState;
589
590 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 * The last scroll state reported to clients through {@link OnScrollListener}.
592 */
593 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
594
595 /**
596 * Helper object that renders and controls the fast scroll thumb.
597 */
Alan Viverette8636ace2013-10-31 15:41:31 -0700598 private FastScroller mFastScroll;
599
600 /**
601 * Temporary holder for fast scroller style until a FastScroller object
602 * is created.
603 */
604 private int mFastScrollStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605
Romain Guyd6a463a2009-05-21 23:10:10 -0700606 private boolean mGlobalLayoutListenerAddedFilter;
607
608 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 private float mDensityScale;
610
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700611 private InputConnection mDefInputConnection;
612 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700613
614 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700615 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700616 private int mMinimumVelocity;
617 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700618 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800619
Romain Guy21875052010-01-06 18:48:08 -0800620 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800621
Adam Powell96d62af2014-05-02 10:04:38 -0700622 private final int[] mScrollOffset = new int[2];
623 private final int[] mScrollConsumed = new int[2];
624
Alan Viveretteb942b6f2014-12-08 10:37:39 -0800625 private final float[] mTmpPoint = new float[2];
626
Adam Powell744beff2014-09-22 09:47:48 -0700627 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
628 // In the future it would be nice to be able to give this to the VelocityTracker
629 // directly, or alternatively put a VT into absolute-positioning mode that only
630 // reads the raw screen-coordinate x/y values.
631 private int mNestedYOffset = 0;
632
Romain Guy24562482010-02-01 14:56:19 -0800633 // True when the popup should be hidden because of a call to
634 // dispatchDisplayHint()
635 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800636
Adam Powell4cd47702010-02-25 11:21:14 -0800637 /**
638 * ID of the active pointer. This is used to retain consistency during
639 * drags/flings if multiple pointers are used.
640 */
641 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800642
Adam Powell4cd47702010-02-25 11:21:14 -0800643 /**
644 * Sentinel value for no current active pointer.
645 * Used by {@link #mActivePointerId}.
646 */
647 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700648
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 /**
Adam Powell637d3372010-08-25 14:37:03 -0700650 * Maximum distance to overscroll by during edge effects
651 */
652 int mOverscrollDistance;
653
654 /**
655 * Maximum distance to overfling during edge effects
656 */
657 int mOverflingDistance;
658
659 // These two EdgeGlows are always set and used together.
660 // Checking one for null is as good as checking both.
661
662 /**
663 * Tracks the state of the top edge glow.
664 */
Adam Powell89935e42011-08-31 14:26:12 -0700665 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700666
667 /**
668 * Tracks the state of the bottom edge glow.
669 */
Adam Powell89935e42011-08-31 14:26:12 -0700670 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700671
672 /**
673 * An estimate of how many pixels are between the top of the list and
674 * the top of the first position in the adapter, based on the last time
675 * we saw it. Used to hint where to draw edge glows.
676 */
677 private int mFirstPositionDistanceGuess;
678
679 /**
680 * An estimate of how many pixels are between the bottom of the list and
681 * the bottom of the last position in the adapter, based on the last time
682 * we saw it. Used to hint where to draw edge glows.
683 */
684 private int mLastPositionDistanceGuess;
685
686 /**
687 * Used for determining when to cancel out of overscroll.
688 */
689 private int mDirection = 0;
690
691 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800692 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
693 * the bottom correctly on resizes.
694 */
695 private boolean mForceTranscriptScroll;
696
alanvc1d7e772012-05-08 14:47:24 -0700697 /**
698 * Used for interacting with list items from an accessibility service.
699 */
700 private ListItemAccessibilityDelegate mAccessibilityDelegate;
701
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700702 private int mLastAccessibilityScrollEventFromIndex;
703 private int mLastAccessibilityScrollEventToIndex;
704
Adam Powellda13dba2010-12-05 13:47:23 -0800705 /**
Adam Powellee78b172011-08-16 16:39:20 -0700706 * Track the item count from the last time we handled a data change.
707 */
708 private int mLastHandledItemCount;
709
710 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700711 * Used for smooth scrolling at a consistent rate
712 */
713 static final Interpolator sLinearInterpolator = new LinearInterpolator();
714
715 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700716 * The saved state that we will be restoring from when we next sync.
717 * Kept here so that if we happen to be asked to save our state before
718 * the sync happens, we can return this existing data rather than losing
719 * it.
720 */
721 private SavedState mPendingSync;
722
723 /**
Alan Viverette462c2172014-02-24 12:24:11 -0800724 * Whether the view is in the process of detaching from its window.
725 */
726 private boolean mIsDetaching;
727
728 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800729 * Interface definition for a callback to be invoked when the list or grid
730 * has been scrolled.
731 */
732 public interface OnScrollListener {
733
734 /**
735 * The view is not scrolling. Note navigating the list using the trackball counts as
736 * being in the idle state since these transitions are not animated.
737 */
738 public static int SCROLL_STATE_IDLE = 0;
739
740 /**
741 * The user is scrolling using touch, and their finger is still on the screen
742 */
743 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
744
745 /**
746 * The user had previously been scrolling using touch and had performed a fling. The
747 * animation is now coasting to a stop
748 */
749 public static int SCROLL_STATE_FLING = 2;
750
751 /**
752 * Callback method to be invoked while the list view or grid view is being scrolled. If the
753 * view is being scrolled, this method will be called before the next frame of the scroll is
754 * rendered. In particular, it will be called before any calls to
755 * {@link Adapter#getView(int, View, ViewGroup)}.
756 *
757 * @param view The view whose scroll state is being reported
758 *
Yorke Lee43943d82014-05-08 10:15:20 -0700759 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
761 */
762 public void onScrollStateChanged(AbsListView view, int scrollState);
763
764 /**
765 * Callback method to be invoked when the list or grid has been scrolled. This will be
766 * called after the scroll has completed
767 * @param view The view whose scroll state is being reported
768 * @param firstVisibleItem the index of the first visible cell (ignore if
769 * visibleItemCount == 0)
770 * @param visibleItemCount the number of visible cells
771 * @param totalItemCount the number of items in the list adaptor
772 */
773 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
774 int totalItemCount);
775 }
776
Dianne Hackborne2136772010-11-04 15:08:59 -0700777 /**
778 * The top-level view of a list item can implement this interface to allow
779 * itself to modify the bounds of the selection shown for that item.
780 */
781 public interface SelectionBoundsAdjuster {
782 /**
783 * Called to allow the list item to adjust the bounds shown for
784 * its selection.
785 *
786 * @param bounds On call, this contains the bounds the list has
787 * selected for the item (that is the bounds of the entire view). The
788 * values can be modified as desired.
789 */
790 public void adjustListItemSelectionBounds(Rect bounds);
791 }
792
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 public AbsListView(Context context) {
794 super(context);
795 initAbsListView();
796
Alan Viverette39bed692013-08-07 15:47:04 -0700797 mOwnerThread = Thread.currentThread();
798
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 setVerticalScrollBarEnabled(true);
800 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
Adam Powell287c03612014-06-23 12:32:35 -0700801 initializeScrollbarsInternal(a);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 a.recycle();
803 }
804
805 public AbsListView(Context context, AttributeSet attrs) {
806 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
807 }
808
Alan Viverette617feb92013-09-09 18:09:13 -0700809 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
810 this(context, attrs, defStyleAttr, 0);
811 }
812
813 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
814 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 initAbsListView();
816
Alan Viverette39bed692013-08-07 15:47:04 -0700817 mOwnerThread = Thread.currentThread();
818
Alan Viverette617feb92013-09-09 18:09:13 -0700819 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7eceda32015-06-01 10:47:29 -0700820 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821
Alan Viverette7eceda32015-06-01 10:47:29 -0700822 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
823 if (selector != null) {
824 setSelector(selector);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 }
826
Alan Viverette7eceda32015-06-01 10:47:29 -0700827 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828
Alan Viverette7eceda32015-06-01 10:47:29 -0700829 setStackFromBottom(a.getBoolean(
830 R.styleable.AbsListView_stackFromBottom, false));
831 setScrollingCacheEnabled(a.getBoolean(
832 R.styleable.AbsListView_scrollingCache, true));
833 setTextFilterEnabled(a.getBoolean(
834 R.styleable.AbsListView_textFilterEnabled, false));
835 setTranscriptMode(a.getInt(
836 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
837 setCacheColorHint(a.getColor(
838 R.styleable.AbsListView_cacheColorHint, 0));
839 setSmoothScrollbarEnabled(a.getBoolean(
840 R.styleable.AbsListView_smoothScrollbar, true));
841 setChoiceMode(a.getInt(
842 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800843
Alan Viverette7eceda32015-06-01 10:47:29 -0700844 setFastScrollEnabled(a.getBoolean(
845 R.styleable.AbsListView_fastScrollEnabled, false));
846 setFastScrollStyle(a.getResourceId(
847 R.styleable.AbsListView_fastScrollStyle, 0));
848 setFastScrollAlwaysVisible(a.getBoolean(
849 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 a.recycle();
852 }
853
Romain Guyd6a463a2009-05-21 23:10:10 -0700854 private void initAbsListView() {
855 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700856 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700857 setFocusableInTouchMode(true);
858 setWillNotDraw(false);
859 setAlwaysDrawnWithCacheEnabled(false);
860 setScrollingCacheEnabled(true);
861
Romain Guy4296fc42009-07-06 11:48:52 -0700862 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
863 mTouchSlop = configuration.getScaledTouchSlop();
864 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
865 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700866 mOverscrollDistance = configuration.getScaledOverscrollDistance();
867 mOverflingDistance = configuration.getScaledOverflingDistance();
868
Romain Guyd6a463a2009-05-21 23:10:10 -0700869 mDensityScale = getContext().getResources().getDisplayMetrics().density;
870 }
Romain Guy0a637162009-05-29 14:43:54 -0700871
Adam Powell637d3372010-08-25 14:37:03 -0700872 @Override
873 public void setOverScrollMode(int mode) {
874 if (mode != OVER_SCROLL_NEVER) {
875 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800876 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700877 mEdgeGlowTop = new EdgeEffect(context);
878 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700879 }
880 } else {
881 mEdgeGlowTop = null;
882 mEdgeGlowBottom = null;
883 }
884 super.setOverScrollMode(mode);
885 }
886
Romain Guyd6a463a2009-05-21 23:10:10 -0700887 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700888 * {@inheritDoc}
889 */
890 @Override
891 public void setAdapter(ListAdapter adapter) {
892 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800893 mAdapterHasStableIds = mAdapter.hasStableIds();
894 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700895 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700896 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700897 }
898 }
899
900 if (mCheckStates != null) {
901 mCheckStates.clear();
902 }
903
904 if (mCheckedIdStates != null) {
905 mCheckedIdStates.clear();
906 }
907 }
908
909 /**
910 * Returns the number of items currently selected. This will only be valid
911 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
912 *
913 * <p>To determine the specific items that are currently selected, use one of
914 * the <code>getChecked*</code> methods.
915 *
916 * @return The number of items currently selected
917 *
918 * @see #getCheckedItemPosition()
919 * @see #getCheckedItemPositions()
920 * @see #getCheckedItemIds()
921 */
922 public int getCheckedItemCount() {
923 return mCheckedItemCount;
924 }
925
926 /**
927 * Returns the checked state of the specified position. The result is only
928 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
929 * or {@link #CHOICE_MODE_MULTIPLE}.
930 *
931 * @param position The item whose checked state to return
932 * @return The item's checked state or <code>false</code> if choice mode
933 * is invalid
934 *
935 * @see #setChoiceMode(int)
936 */
937 public boolean isItemChecked(int position) {
938 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
939 return mCheckStates.get(position);
940 }
941
942 return false;
943 }
944
945 /**
946 * Returns the currently checked item. The result is only valid if the choice
947 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
948 *
949 * @return The position of the currently checked item or
950 * {@link #INVALID_POSITION} if nothing is selected
951 *
952 * @see #setChoiceMode(int)
953 */
954 public int getCheckedItemPosition() {
955 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
956 return mCheckStates.keyAt(0);
957 }
958
959 return INVALID_POSITION;
960 }
961
962 /**
963 * Returns the set of checked items in the list. The result is only valid if
964 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
965 *
966 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200967 * get(int position) where position is a checked position in the
968 * list and false otherwise, or <code>null</code> if the choice
969 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700970 */
971 public SparseBooleanArray getCheckedItemPositions() {
972 if (mChoiceMode != CHOICE_MODE_NONE) {
973 return mCheckStates;
974 }
975 return null;
976 }
977
978 /**
979 * Returns the set of checked items ids. The result is only valid if the
980 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
981 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
982 *
983 * @return A new array which contains the id of each checked item in the
984 * list.
985 */
986 public long[] getCheckedItemIds() {
987 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
988 return new long[0];
989 }
990
Adam Powell14c08042011-10-06 19:46:18 -0700991 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700992 final int count = idStates.size();
993 final long[] ids = new long[count];
994
995 for (int i = 0; i < count; i++) {
996 ids[i] = idStates.keyAt(i);
997 }
998
999 return ids;
1000 }
1001
1002 /**
1003 * Clear any choices previously set
1004 */
1005 public void clearChoices() {
1006 if (mCheckStates != null) {
1007 mCheckStates.clear();
1008 }
1009 if (mCheckedIdStates != null) {
1010 mCheckedIdStates.clear();
1011 }
1012 mCheckedItemCount = 0;
1013 }
1014
1015 /**
1016 * Sets the checked state of the specified position. The is only valid if
1017 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1018 * {@link #CHOICE_MODE_MULTIPLE}.
1019 *
1020 * @param position The item whose checked state is to be checked
1021 * @param value The new checked state for the item
1022 */
1023 public void setItemChecked(int position, boolean value) {
1024 if (mChoiceMode == CHOICE_MODE_NONE) {
1025 return;
1026 }
1027
1028 // Start selection mode if needed. We don't need to if we're unchecking something.
1029 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -07001030 if (mMultiChoiceModeCallback == null ||
1031 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1032 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1033 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1034 "supplied. Call setMultiChoiceModeListener to set a callback.");
1035 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001036 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1037 }
1038
1039 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1040 boolean oldValue = mCheckStates.get(position);
1041 mCheckStates.put(position, value);
1042 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1043 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001044 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001045 } else {
1046 mCheckedIdStates.delete(mAdapter.getItemId(position));
1047 }
1048 }
1049 if (oldValue != value) {
1050 if (value) {
1051 mCheckedItemCount++;
1052 } else {
1053 mCheckedItemCount--;
1054 }
1055 }
1056 if (mChoiceActionMode != null) {
1057 final long id = mAdapter.getItemId(position);
1058 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1059 position, id, value);
1060 }
1061 } else {
1062 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1063 // Clear all values if we're checking something, or unchecking the currently
1064 // selected item
1065 if (value || isItemChecked(position)) {
1066 mCheckStates.clear();
1067 if (updateIds) {
1068 mCheckedIdStates.clear();
1069 }
1070 }
1071 // this may end up selecting the value we just cleared but this way
1072 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1073 if (value) {
1074 mCheckStates.put(position, true);
1075 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001076 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001077 }
1078 mCheckedItemCount = 1;
1079 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1080 mCheckedItemCount = 0;
1081 }
1082 }
1083
1084 // Do not generate a data change while we are in the layout phase
1085 if (!mInLayout && !mBlockLayoutRequests) {
1086 mDataChanged = true;
1087 rememberSyncState();
1088 requestLayout();
1089 }
1090 }
1091
1092 @Override
1093 public boolean performItemClick(View view, int position, long id) {
1094 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001095 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001096
1097 if (mChoiceMode != CHOICE_MODE_NONE) {
1098 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001099 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001100
1101 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1102 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001103 boolean checked = !mCheckStates.get(position, false);
1104 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001105 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001106 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001107 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001108 } else {
1109 mCheckedIdStates.delete(mAdapter.getItemId(position));
1110 }
1111 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001112 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001113 mCheckedItemCount++;
1114 } else {
1115 mCheckedItemCount--;
1116 }
1117 if (mChoiceActionMode != null) {
1118 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001119 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001120 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001121 }
Adam Powell29382d92012-02-23 11:03:22 -08001122 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001123 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001124 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001125 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001126 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001127 mCheckStates.put(position, true);
1128 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1129 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001130 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001131 }
1132 mCheckedItemCount = 1;
1133 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1134 mCheckedItemCount = 0;
1135 }
Adam Powell29382d92012-02-23 11:03:22 -08001136 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001137 }
1138
Adam Powell29382d92012-02-23 11:03:22 -08001139 if (checkedStateChanged) {
1140 updateOnScreenCheckedViews();
1141 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001142 }
1143
Adam Powellbf5f2b32010-10-24 16:45:44 -07001144 if (dispatchItemClick) {
1145 handled |= super.performItemClick(view, position, id);
1146 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001147
1148 return handled;
1149 }
1150
1151 /**
Adam Powell29382d92012-02-23 11:03:22 -08001152 * Perform a quick, in-place update of the checked or activated state
1153 * on all visible item views. This should only be called when a valid
1154 * choice mode is active.
1155 */
1156 private void updateOnScreenCheckedViews() {
1157 final int firstPos = mFirstPosition;
1158 final int count = getChildCount();
1159 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1160 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1161 for (int i = 0; i < count; i++) {
1162 final View child = getChildAt(i);
1163 final int position = firstPos + i;
1164
1165 if (child instanceof Checkable) {
1166 ((Checkable) child).setChecked(mCheckStates.get(position));
1167 } else if (useActivated) {
1168 child.setActivated(mCheckStates.get(position));
1169 }
1170 }
1171 }
1172
1173 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001174 * @see #setChoiceMode(int)
1175 *
1176 * @return The current choice mode
1177 */
1178 public int getChoiceMode() {
1179 return mChoiceMode;
1180 }
1181
1182 /**
1183 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1184 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1185 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1186 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1187 *
1188 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1189 * {@link #CHOICE_MODE_MULTIPLE}
1190 */
1191 public void setChoiceMode(int choiceMode) {
1192 mChoiceMode = choiceMode;
1193 if (mChoiceActionMode != null) {
1194 mChoiceActionMode.finish();
1195 mChoiceActionMode = null;
1196 }
1197 if (mChoiceMode != CHOICE_MODE_NONE) {
1198 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001199 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001200 }
1201 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001202 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001203 }
1204 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1205 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1206 clearChoices();
1207 setLongClickable(true);
1208 }
1209 }
1210 }
1211
1212 /**
1213 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1214 * selection {@link ActionMode}. Only used when the choice mode is set to
1215 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1216 *
1217 * @param listener Listener that will manage the selection mode
1218 *
1219 * @see #setChoiceMode(int)
1220 */
1221 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1222 if (mMultiChoiceModeCallback == null) {
1223 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1224 }
1225 mMultiChoiceModeCallback.setWrapped(listener);
1226 }
1227
1228 /**
Adam Powell637d3372010-08-25 14:37:03 -07001229 * @return true if all list content currently fits within the view boundaries
1230 */
1231 private boolean contentFits() {
1232 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001233 if (childCount == 0) return true;
1234 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001235
Adam Powell4ce35412011-01-24 14:55:00 -08001236 return getChildAt(0).getTop() >= mListPadding.top &&
1237 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001238 }
1239
1240 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001241 * Specifies whether fast scrolling is enabled or disabled.
1242 * <p>
1243 * When fast scrolling is enabled, the user can quickly scroll through lists
1244 * by dragging the fast scroll thumb.
1245 * <p>
1246 * If the adapter backing this list implements {@link SectionIndexer}, the
1247 * fast scroller will display section header previews as the user scrolls.
1248 * Additionally, the user will be able to quickly jump between sections by
1249 * tapping along the length of the scroll bar.
1250 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 * @see SectionIndexer
1252 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001253 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 */
Alan Viverette39bed692013-08-07 15:47:04 -07001255 public void setFastScrollEnabled(final boolean enabled) {
1256 if (mFastScrollEnabled != enabled) {
1257 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001258
Alan Viverette39bed692013-08-07 15:47:04 -07001259 if (isOwnerThread()) {
1260 setFastScrollerEnabledUiThread(enabled);
1261 } else {
1262 post(new Runnable() {
1263 @Override
1264 public void run() {
1265 setFastScrollerEnabledUiThread(enabled);
1266 }
1267 });
1268 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001269 }
Alan Viverette39bed692013-08-07 15:47:04 -07001270 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001271
Alan Viverette39bed692013-08-07 15:47:04 -07001272 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001273 if (mFastScroll != null) {
1274 mFastScroll.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001275 } else if (enabled) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001276 mFastScroll = new FastScroller(this, mFastScrollStyle);
1277 mFastScroll.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001279
Alan Viveretteb9f27222013-09-06 19:39:47 -07001280 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001281
Alan Viverette8636ace2013-10-31 15:41:31 -07001282 if (mFastScroll != null) {
1283 mFastScroll.updateLayout();
1284 }
1285 }
1286
1287 /**
1288 * Specifies the style of the fast scroller decorations.
1289 *
1290 * @param styleResId style resource containing fast scroller properties
1291 * @see android.R.styleable#FastScroll
1292 */
1293 public void setFastScrollStyle(int styleResId) {
1294 if (mFastScroll == null) {
1295 mFastScrollStyle = styleResId;
1296 } else {
1297 mFastScroll.setStyle(styleResId);
Alan Viverette26bb2532013-08-09 10:40:50 -07001298 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001299 }
Romain Guy0a637162009-05-29 14:43:54 -07001300
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001301 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001302 * Set whether or not the fast scroller should always be shown in place of
1303 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001304 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001305 * <p>
1306 * Fast scrollers shown in this way will not fade out and will be a
1307 * permanent fixture within the list. This is best combined with an inset
1308 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001309 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001310 * @param alwaysShow true if the fast scroller should always be displayed,
1311 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001312 * @see #setScrollBarStyle(int)
1313 * @see #setFastScrollEnabled(boolean)
1314 */
Alan Viverette39bed692013-08-07 15:47:04 -07001315 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1316 if (mFastScrollAlwaysVisible != alwaysShow) {
1317 if (alwaysShow && !mFastScrollEnabled) {
1318 setFastScrollEnabled(true);
1319 }
Adam Powell20232d02010-12-08 21:08:53 -08001320
Alan Viverette39bed692013-08-07 15:47:04 -07001321 mFastScrollAlwaysVisible = alwaysShow;
1322
1323 if (isOwnerThread()) {
1324 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1325 } else {
1326 post(new Runnable() {
1327 @Override
1328 public void run() {
1329 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1330 }
1331 });
1332 }
1333 }
1334 }
1335
1336 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Alan Viverette8636ace2013-10-31 15:41:31 -07001337 if (mFastScroll != null) {
1338 mFastScroll.setAlwaysShow(alwaysShow);
Adam Powell20232d02010-12-08 21:08:53 -08001339 }
Alan Viverette39bed692013-08-07 15:47:04 -07001340 }
Adam Powell20232d02010-12-08 21:08:53 -08001341
Alan Viverette39bed692013-08-07 15:47:04 -07001342 /**
1343 * @return whether the current thread is the one that created the view
1344 */
1345 private boolean isOwnerThread() {
1346 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001347 }
1348
1349 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001350 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001351 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001352 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001353 * @see #setFastScrollAlwaysVisible(boolean)
1354 */
1355 public boolean isFastScrollAlwaysVisible() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001356 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001357 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1358 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001359 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001360 }
Adam Powell20232d02010-12-08 21:08:53 -08001361 }
1362
1363 @Override
1364 public int getVerticalScrollbarWidth() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001365 if (mFastScroll != null && mFastScroll.isEnabled()) {
1366 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
Adam Powell20232d02010-12-08 21:08:53 -08001367 }
1368 return super.getVerticalScrollbarWidth();
1369 }
1370
1371 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001372 * Returns true if the fast scroller is enabled.
1373 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001374 * @see #setFastScrollEnabled(boolean)
1375 * @return true if fast scroll is enabled, false otherwise
1376 */
1377 @ViewDebug.ExportedProperty
1378 public boolean isFastScrollEnabled() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001379 if (mFastScroll == null) {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001380 return mFastScrollEnabled;
1381 } else {
Alan Viverette8636ace2013-10-31 15:41:31 -07001382 return mFastScroll.isEnabled();
Alan Viveretteb9f27222013-09-06 19:39:47 -07001383 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 }
Romain Guy0a637162009-05-29 14:43:54 -07001385
Adam Powell20232d02010-12-08 21:08:53 -08001386 @Override
1387 public void setVerticalScrollbarPosition(int position) {
1388 super.setVerticalScrollbarPosition(position);
Alan Viverette8636ace2013-10-31 15:41:31 -07001389 if (mFastScroll != null) {
1390 mFastScroll.setScrollbarPosition(position);
Adam Powell20232d02010-12-08 21:08:53 -08001391 }
1392 }
1393
Alan Viverette26bb2532013-08-09 10:40:50 -07001394 @Override
1395 public void setScrollBarStyle(int style) {
1396 super.setScrollBarStyle(style);
Alan Viverette8636ace2013-10-31 15:41:31 -07001397 if (mFastScroll != null) {
1398 mFastScroll.setScrollBarStyle(style);
Alan Viverette26bb2532013-08-09 10:40:50 -07001399 }
1400 }
1401
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001402 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001403 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001404 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001405 */
1406 @Override
1407 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001408 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 }
1410
1411 /**
1412 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1413 * is computed based on the number of visible pixels in the visible items. This
1414 * however assumes that all list items have the same height. If you use a list in
1415 * which items have different heights, the scrollbar will change appearance as the
1416 * user scrolls through the list. To avoid this issue, you need to disable this
1417 * property.
1418 *
1419 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1420 * is based solely on the number of items in the adapter and the position of the
1421 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001422 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001423 *
1424 * @param enabled Whether or not to enable smooth scrollbar.
1425 *
Romain Guy0a637162009-05-29 14:43:54 -07001426 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1428 */
1429 public void setSmoothScrollbarEnabled(boolean enabled) {
1430 mSmoothScrollbarEnabled = enabled;
1431 }
1432
1433 /**
1434 * Returns the current state of the fast scroll feature.
1435 *
1436 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1437 *
1438 * @see #setSmoothScrollbarEnabled(boolean)
1439 */
1440 @ViewDebug.ExportedProperty
1441 public boolean isSmoothScrollbarEnabled() {
1442 return mSmoothScrollbarEnabled;
1443 }
1444
1445 /**
1446 * Set the listener that will receive notifications every time the list scrolls.
1447 *
1448 * @param l the scroll listener
1449 */
1450 public void setOnScrollListener(OnScrollListener l) {
1451 mOnScrollListener = l;
1452 invokeOnItemScrollListener();
1453 }
1454
1455 /**
1456 * Notify our scroll listener (if there is one) of a change in scroll state
1457 */
1458 void invokeOnItemScrollListener() {
Alan Viverette8636ace2013-10-31 15:41:31 -07001459 if (mFastScroll != null) {
1460 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001461 }
1462 if (mOnScrollListener != null) {
1463 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1464 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001465 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001466 }
1467
Alan Viverettea54956a2015-01-07 16:05:02 -08001468 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001469 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001470 public void sendAccessibilityEventInternal(int eventType) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001471 // Since this class calls onScrollChanged even if the mFirstPosition and the
1472 // child count have not changed we will avoid sending duplicate accessibility
1473 // events.
1474 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001475 final int firstVisiblePosition = getFirstVisiblePosition();
1476 final int lastVisiblePosition = getLastVisiblePosition();
1477 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1478 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001479 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001480 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001481 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1482 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001483 }
1484 }
Alan Viverettea54956a2015-01-07 16:05:02 -08001485 super.sendAccessibilityEventInternal(eventType);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001486 }
1487
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001488 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08001489 public CharSequence getAccessibilityClassName() {
1490 return AbsListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001491 }
1492
Alan Viverettea54956a2015-01-07 16:05:02 -08001493 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001494 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001495 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1496 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001497 if (isEnabled()) {
Alan Viverette947a9692014-09-25 12:43:47 -07001498 if (canScrollUp()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001499 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001500 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001501 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001502 }
Alan Viverette947a9692014-09-25 12:43:47 -07001503 if (canScrollDown()) {
Alan Viverette23f44322015-04-06 16:04:56 -07001504 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001505 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001506 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001507 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001508 }
Maxim Bogatov67986972015-05-27 11:15:23 -07001509
1510 info.removeAction(AccessibilityAction.ACTION_CLICK);
1511 info.setClickable(false);
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001512 }
1513
Alan Viverette76769ae2014-02-12 16:38:10 -08001514 int getSelectionModeForAccessibility() {
1515 final int choiceMode = getChoiceMode();
1516 switch (choiceMode) {
1517 case CHOICE_MODE_NONE:
1518 return CollectionInfo.SELECTION_MODE_NONE;
1519 case CHOICE_MODE_SINGLE:
1520 return CollectionInfo.SELECTION_MODE_SINGLE;
1521 case CHOICE_MODE_MULTIPLE:
1522 case CHOICE_MODE_MULTIPLE_MODAL:
1523 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1524 default:
1525 return CollectionInfo.SELECTION_MODE_NONE;
1526 }
1527 }
1528
Alan Viverettea54956a2015-01-07 16:05:02 -08001529 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001530 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001531 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1532 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001533 return true;
1534 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001535 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001536 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1537 case R.id.accessibilityActionScrollDown: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001538 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001539 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1540 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1541 return true;
1542 }
1543 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001544 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1545 case R.id.accessibilityActionScrollUp: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001546 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001547 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1548 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1549 return true;
1550 }
1551 } return false;
1552 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001553 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001554 }
1555
Svetoslav5b578da2013-05-08 14:23:32 -07001556 /** @hide */
1557 @Override
1558 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1559 if (accessibilityId == getAccessibilityViewId()) {
1560 return this;
1561 }
Svetoslav5b578da2013-05-08 14:23:32 -07001562 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1563 }
1564
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001565 /**
1566 * Indicates whether the children's drawing cache is used during a scroll.
1567 * By default, the drawing cache is enabled but this will consume more memory.
1568 *
1569 * @return true if the scrolling cache is enabled, false otherwise
1570 *
1571 * @see #setScrollingCacheEnabled(boolean)
1572 * @see View#setDrawingCacheEnabled(boolean)
1573 */
1574 @ViewDebug.ExportedProperty
1575 public boolean isScrollingCacheEnabled() {
1576 return mScrollingCacheEnabled;
1577 }
1578
1579 /**
1580 * Enables or disables the children's drawing cache during a scroll.
1581 * By default, the drawing cache is enabled but this will use more memory.
1582 *
1583 * When the scrolling cache is enabled, the caches are kept after the
1584 * first scrolling. You can manually clear the cache by calling
1585 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1586 *
1587 * @param enabled true to enable the scroll cache, false otherwise
1588 *
1589 * @see #isScrollingCacheEnabled()
1590 * @see View#setDrawingCacheEnabled(boolean)
1591 */
1592 public void setScrollingCacheEnabled(boolean enabled) {
1593 if (mScrollingCacheEnabled && !enabled) {
1594 clearScrollingCache();
1595 }
1596 mScrollingCacheEnabled = enabled;
1597 }
1598
1599 /**
1600 * Enables or disables the type filter window. If enabled, typing when
1601 * this view has focus will filter the children to match the users input.
1602 * Note that the {@link Adapter} used by this view must implement the
1603 * {@link Filterable} interface.
1604 *
1605 * @param textFilterEnabled true to enable type filtering, false otherwise
1606 *
1607 * @see Filterable
1608 */
1609 public void setTextFilterEnabled(boolean textFilterEnabled) {
1610 mTextFilterEnabled = textFilterEnabled;
1611 }
1612
1613 /**
1614 * Indicates whether type filtering is enabled for this view
1615 *
1616 * @return true if type filtering is enabled, false otherwise
1617 *
1618 * @see #setTextFilterEnabled(boolean)
1619 * @see Filterable
1620 */
1621 @ViewDebug.ExportedProperty
1622 public boolean isTextFilterEnabled() {
1623 return mTextFilterEnabled;
1624 }
1625
1626 @Override
1627 public void getFocusedRect(Rect r) {
1628 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001629 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001630 // the focused rectangle of the selected view offset into the
1631 // coordinate space of this view.
1632 view.getFocusedRect(r);
1633 offsetDescendantRectToMyCoords(view, r);
1634 } else {
1635 // otherwise, just the norm
1636 super.getFocusedRect(r);
1637 }
1638 }
1639
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001640 private void useDefaultSelector() {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001641 setSelector(getContext().getDrawable(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001642 com.android.internal.R.drawable.list_selector_background));
1643 }
1644
1645 /**
1646 * Indicates whether the content of this view is pinned to, or stacked from,
1647 * the bottom edge.
1648 *
1649 * @return true if the content is stacked from the bottom edge, false otherwise
1650 */
1651 @ViewDebug.ExportedProperty
1652 public boolean isStackFromBottom() {
1653 return mStackFromBottom;
1654 }
1655
1656 /**
1657 * When stack from bottom is set to true, the list fills its content starting from
1658 * the bottom of the view.
1659 *
1660 * @param stackFromBottom true to pin the view's content to the bottom edge,
1661 * false to pin the view's content to the top edge
1662 */
1663 public void setStackFromBottom(boolean stackFromBottom) {
1664 if (mStackFromBottom != stackFromBottom) {
1665 mStackFromBottom = stackFromBottom;
1666 requestLayoutIfNecessary();
1667 }
1668 }
1669
1670 void requestLayoutIfNecessary() {
1671 if (getChildCount() > 0) {
1672 resetList();
1673 requestLayout();
1674 invalidate();
1675 }
1676 }
1677
1678 static class SavedState extends BaseSavedState {
1679 long selectedId;
1680 long firstId;
1681 int viewTop;
1682 int position;
1683 int height;
1684 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001685 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001686 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001687 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001688 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001689
1690 /**
1691 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1692 */
1693 SavedState(Parcelable superState) {
1694 super(superState);
1695 }
1696
1697 /**
1698 * Constructor called from {@link #CREATOR}
1699 */
1700 private SavedState(Parcel in) {
1701 super(in);
1702 selectedId = in.readLong();
1703 firstId = in.readLong();
1704 viewTop = in.readInt();
1705 position = in.readInt();
1706 height = in.readInt();
1707 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001708 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001709 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001710 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001711 final int N = in.readInt();
1712 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001713 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001714 for (int i=0; i<N; i++) {
1715 final long key = in.readLong();
1716 final int value = in.readInt();
1717 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001718 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001719 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001720 }
1721
1722 @Override
1723 public void writeToParcel(Parcel out, int flags) {
1724 super.writeToParcel(out, flags);
1725 out.writeLong(selectedId);
1726 out.writeLong(firstId);
1727 out.writeInt(viewTop);
1728 out.writeInt(position);
1729 out.writeInt(height);
1730 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001731 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001732 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001733 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001734 final int N = checkIdState != null ? checkIdState.size() : 0;
1735 out.writeInt(N);
1736 for (int i=0; i<N; i++) {
1737 out.writeLong(checkIdState.keyAt(i));
1738 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001739 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001740 }
1741
1742 @Override
1743 public String toString() {
1744 return "AbsListView.SavedState{"
1745 + Integer.toHexString(System.identityHashCode(this))
1746 + " selectedId=" + selectedId
1747 + " firstId=" + firstId
1748 + " viewTop=" + viewTop
1749 + " position=" + position
1750 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001751 + " filter=" + filter
1752 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001753 }
1754
1755 public static final Parcelable.Creator<SavedState> CREATOR
1756 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001757 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001758 public SavedState createFromParcel(Parcel in) {
1759 return new SavedState(in);
1760 }
1761
Alan Viverette8fa327a2013-05-31 14:53:13 -07001762 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001763 public SavedState[] newArray(int size) {
1764 return new SavedState[size];
1765 }
1766 };
1767 }
1768
1769 @Override
1770 public Parcelable onSaveInstanceState() {
1771 /*
1772 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001773 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001774 * that happen early enough to keep from getting complaints
1775 * about having leaked the window.
1776 */
1777 dismissPopup();
1778
1779 Parcelable superState = super.onSaveInstanceState();
1780
1781 SavedState ss = new SavedState(superState);
1782
Dianne Hackborne181bd92012-09-25 14:15:15 -07001783 if (mPendingSync != null) {
1784 // Just keep what we last restored.
1785 ss.selectedId = mPendingSync.selectedId;
1786 ss.firstId = mPendingSync.firstId;
1787 ss.viewTop = mPendingSync.viewTop;
1788 ss.position = mPendingSync.position;
1789 ss.height = mPendingSync.height;
1790 ss.filter = mPendingSync.filter;
1791 ss.inActionMode = mPendingSync.inActionMode;
1792 ss.checkedItemCount = mPendingSync.checkedItemCount;
1793 ss.checkState = mPendingSync.checkState;
1794 ss.checkIdState = mPendingSync.checkIdState;
1795 return ss;
1796 }
1797
Dianne Hackborn99441c42010-12-15 11:02:55 -08001798 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001799 long selectedId = getSelectedItemId();
1800 ss.selectedId = selectedId;
1801 ss.height = getHeight();
1802
1803 if (selectedId >= 0) {
1804 // Remember the selection
1805 ss.viewTop = mSelectedTop;
1806 ss.position = getSelectedItemPosition();
1807 ss.firstId = INVALID_POSITION;
1808 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001809 if (haveChildren && mFirstPosition > 0) {
1810 // Remember the position of the first child.
1811 // We only do this if we are not currently at the top of
1812 // the list, for two reasons:
1813 // (1) The list may be in the process of becoming empty, in
1814 // which case mItemCount may not be 0, but if we try to
1815 // ask for any information about position 0 we will crash.
1816 // (2) Being "at the top" seems like a special case, anyway,
1817 // and the user wouldn't expect to end up somewhere else when
1818 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001819 View v = getChildAt(0);
1820 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001821 int firstPos = mFirstPosition;
1822 if (firstPos >= mItemCount) {
1823 firstPos = mItemCount - 1;
1824 }
1825 ss.position = firstPos;
1826 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001827 } else {
1828 ss.viewTop = 0;
1829 ss.firstId = INVALID_POSITION;
1830 ss.position = 0;
1831 }
1832 }
1833
1834 ss.filter = null;
1835 if (mFiltered) {
1836 final EditText textFilter = mTextFilter;
1837 if (textFilter != null) {
1838 Editable filterText = textFilter.getText();
1839 if (filterText != null) {
1840 ss.filter = filterText.toString();
1841 }
1842 }
1843 }
1844
Adam Powella0eeeac2010-11-05 11:55:05 -07001845 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1846
Adam Powell9a5cc282011-08-28 16:18:16 -07001847 if (mCheckStates != null) {
1848 ss.checkState = mCheckStates.clone();
1849 }
1850 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001851 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001852 final int count = mCheckedIdStates.size();
1853 for (int i = 0; i < count; i++) {
1854 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1855 }
1856 ss.checkIdState = idState;
1857 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001858 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001859
Adam Cohen335c3b62012-07-24 17:18:16 -07001860 if (mRemoteAdapter != null) {
1861 mRemoteAdapter.saveRemoteViewsCache();
1862 }
1863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001864 return ss;
1865 }
1866
1867 @Override
1868 public void onRestoreInstanceState(Parcelable state) {
1869 SavedState ss = (SavedState) state;
1870
1871 super.onRestoreInstanceState(ss.getSuperState());
1872 mDataChanged = true;
1873
1874 mSyncHeight = ss.height;
1875
1876 if (ss.selectedId >= 0) {
1877 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001878 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001879 mSyncRowId = ss.selectedId;
1880 mSyncPosition = ss.position;
1881 mSpecificTop = ss.viewTop;
1882 mSyncMode = SYNC_SELECTED_POSITION;
1883 } else if (ss.firstId >= 0) {
1884 setSelectedPositionInt(INVALID_POSITION);
1885 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1886 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001887 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001888 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001889 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001890 mSyncRowId = ss.firstId;
1891 mSyncPosition = ss.position;
1892 mSpecificTop = ss.viewTop;
1893 mSyncMode = SYNC_FIRST_POSITION;
1894 }
1895
1896 setFilterText(ss.filter);
1897
Adam Powellf343e1b2010-08-13 18:27:04 -07001898 if (ss.checkState != null) {
1899 mCheckStates = ss.checkState;
1900 }
1901
1902 if (ss.checkIdState != null) {
1903 mCheckedIdStates = ss.checkIdState;
1904 }
1905
Adam Powell2614c6c2010-11-04 17:54:45 -07001906 mCheckedItemCount = ss.checkedItemCount;
1907
Adam Powella0eeeac2010-11-05 11:55:05 -07001908 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1909 mMultiChoiceModeCallback != null) {
1910 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1911 }
1912
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001913 requestLayout();
1914 }
1915
1916 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001917 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1918 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001919 }
1920
1921 /**
1922 * Sets the initial value for the text filter.
1923 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001924 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001925 * @see #setTextFilterEnabled
1926 */
1927 public void setFilterText(String filterText) {
1928 // TODO: Should we check for acceptFilter()?
1929 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1930 createTextFilter(false);
1931 // This is going to call our listener onTextChanged, but we might not
1932 // be ready to bring up a window yet
1933 mTextFilter.setText(filterText);
1934 mTextFilter.setSelection(filterText.length());
1935 if (mAdapter instanceof Filterable) {
1936 // if mPopup is non-null, then onTextChanged will do the filtering
1937 if (mPopup == null) {
1938 Filter f = ((Filterable) mAdapter).getFilter();
1939 f.filter(filterText);
1940 }
1941 // Set filtered to true so we will display the filter window when our main
1942 // window is ready
1943 mFiltered = true;
1944 mDataSetObserver.clearSavedState();
1945 }
1946 }
1947 }
1948
1949 /**
Romain Guy0a637162009-05-29 14:43:54 -07001950 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001951 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001952 */
1953 public CharSequence getTextFilter() {
1954 if (mTextFilterEnabled && mTextFilter != null) {
1955 return mTextFilter.getText();
1956 }
1957 return null;
1958 }
Romain Guy0a637162009-05-29 14:43:54 -07001959
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001960 @Override
1961 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1962 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1963 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001964 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001965 // Data may have changed while we were detached and it's valid
1966 // to change focus while detached. Refresh so we don't die.
1967 mDataChanged = true;
1968 mOldItemCount = mItemCount;
1969 mItemCount = mAdapter.getCount();
1970 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001971 resurrectSelection();
1972 }
1973 }
1974
1975 @Override
1976 public void requestLayout() {
1977 if (!mBlockLayoutRequests && !mInLayout) {
1978 super.requestLayout();
1979 }
1980 }
1981
1982 /**
1983 * The list is empty. Clear everything out.
1984 */
1985 void resetList() {
1986 removeAllViewsInLayout();
1987 mFirstPosition = 0;
1988 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001989 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001990 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001991 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001992 mOldSelectedPosition = INVALID_POSITION;
1993 mOldSelectedRowId = INVALID_ROW_ID;
1994 setSelectedPositionInt(INVALID_POSITION);
1995 setNextSelectedPositionInt(INVALID_POSITION);
1996 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07001997 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001998 mSelectorRect.setEmpty();
1999 invalidate();
2000 }
2001
2002 @Override
2003 protected int computeVerticalScrollExtent() {
2004 final int count = getChildCount();
2005 if (count > 0) {
2006 if (mSmoothScrollbarEnabled) {
2007 int extent = count * 100;
2008
2009 View view = getChildAt(0);
2010 final int top = view.getTop();
2011 int height = view.getHeight();
2012 if (height > 0) {
2013 extent += (top * 100) / height;
2014 }
2015
2016 view = getChildAt(count - 1);
2017 final int bottom = view.getBottom();
2018 height = view.getHeight();
2019 if (height > 0) {
2020 extent -= ((bottom - getHeight()) * 100) / height;
2021 }
2022
2023 return extent;
2024 } else {
2025 return 1;
2026 }
2027 }
2028 return 0;
2029 }
2030
2031 @Override
2032 protected int computeVerticalScrollOffset() {
2033 final int firstPosition = mFirstPosition;
2034 final int childCount = getChildCount();
2035 if (firstPosition >= 0 && childCount > 0) {
2036 if (mSmoothScrollbarEnabled) {
2037 final View view = getChildAt(0);
2038 final int top = view.getTop();
2039 int height = view.getHeight();
2040 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08002041 return Math.max(firstPosition * 100 - (top * 100) / height +
2042 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002043 }
2044 } else {
2045 int index;
2046 final int count = mItemCount;
2047 if (firstPosition == 0) {
2048 index = 0;
2049 } else if (firstPosition + childCount == count) {
2050 index = count;
2051 } else {
2052 index = firstPosition + childCount / 2;
2053 }
2054 return (int) (firstPosition + childCount * (index / (float) count));
2055 }
2056 }
2057 return 0;
2058 }
2059
2060 @Override
2061 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002062 int result;
2063 if (mSmoothScrollbarEnabled) {
2064 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002065 if (mScrollY != 0) {
2066 // Compensate for overscroll
2067 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2068 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002069 } else {
2070 result = mItemCount;
2071 }
2072 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002073 }
2074
2075 @Override
2076 protected float getTopFadingEdgeStrength() {
2077 final int count = getChildCount();
2078 final float fadeEdge = super.getTopFadingEdgeStrength();
2079 if (count == 0) {
2080 return fadeEdge;
2081 } else {
2082 if (mFirstPosition > 0) {
2083 return 1.0f;
2084 }
2085
2086 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002087 final float fadeLength = getVerticalFadingEdgeLength();
2088 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002089 }
2090 }
2091
2092 @Override
2093 protected float getBottomFadingEdgeStrength() {
2094 final int count = getChildCount();
2095 final float fadeEdge = super.getBottomFadingEdgeStrength();
2096 if (count == 0) {
2097 return fadeEdge;
2098 } else {
2099 if (mFirstPosition + count - 1 < mItemCount - 1) {
2100 return 1.0f;
2101 }
2102
2103 final int bottom = getChildAt(count - 1).getBottom();
2104 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002105 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002106 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002107 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002108 }
2109 }
2110
2111 @Override
Adam Powellc55d5072014-12-18 15:22:26 -08002112 public int findDependentLayoutAxes(View child, int axisFilter) {
2113 return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
2114 }
2115
2116 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002117 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2118 if (mSelector == null) {
2119 useDefaultSelector();
2120 }
2121 final Rect listPadding = mListPadding;
2122 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2123 listPadding.top = mSelectionTopPadding + mPaddingTop;
2124 listPadding.right = mSelectionRightPadding + mPaddingRight;
2125 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002126
2127 // Check if our previous measured size was at a point where we should scroll later.
2128 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2129 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002130 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002131 final View lastChild = getChildAt(childCount - 1);
2132 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002133 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002134 lastBottom <= listBottom;
2135 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002136 }
2137
Romain Guyd6a463a2009-05-21 23:10:10 -07002138 /**
2139 * Subclasses should NOT override this method but
2140 * {@link #layoutChildren()} instead.
2141 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002142 @Override
2143 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2144 super.onLayout(changed, l, t, r, b);
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002146 mInLayout = true;
Alan Viveretted1ca75b2014-04-27 18:13:34 -07002147
Alan Viverette4b95cc72014-01-14 16:54:02 -08002148 final int childCount = getChildCount();
Adam Powellf3c2eda2010-03-16 17:31:01 -07002149 if (changed) {
Adam Powellf3c2eda2010-03-16 17:31:01 -07002150 for (int i = 0; i < childCount; i++) {
2151 getChildAt(i).forceLayout();
2152 }
2153 mRecycler.markChildrenDirty();
2154 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002155
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002156 layoutChildren();
2157 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002158
2159 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
Alan Viverette732ca462014-03-07 16:49:32 -08002160
2161 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2162 if (mFastScroll != null) {
2163 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2164 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002165 }
2166
2167 /**
2168 * @hide
2169 */
2170 @Override
2171 protected boolean setFrame(int left, int top, int right, int bottom) {
2172 final boolean changed = super.setFrame(left, top, right, bottom);
2173
Romain Guyd6a463a2009-05-21 23:10:10 -07002174 if (changed) {
2175 // Reposition the popup when the frame has changed. This includes
2176 // translating the widget, not just changing its dimension. The
2177 // filter popup needs to follow the widget.
2178 final boolean visible = getWindowVisibility() == View.VISIBLE;
2179 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2180 positionPopup();
2181 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002182 }
2183
2184 return changed;
2185 }
2186
Romain Guyd6a463a2009-05-21 23:10:10 -07002187 /**
2188 * Subclasses must override this method to layout their children.
2189 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002190 protected void layoutChildren() {
2191 }
2192
Alan Viverette5d565fa2013-10-30 11:09:03 -07002193 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002194 * @param focusedView view that holds accessibility focus
2195 * @return direct child that contains accessibility focus, or null if no
Alan Viverette5d565fa2013-10-30 11:09:03 -07002196 * child contains accessibility focus
2197 */
Alan Viverette3e141622014-02-18 17:05:13 -08002198 View getAccessibilityFocusedChild(View focusedView) {
Alan Viverette5d565fa2013-10-30 11:09:03 -07002199 ViewParent viewParent = focusedView.getParent();
2200 while ((viewParent instanceof View) && (viewParent != this)) {
2201 focusedView = (View) viewParent;
2202 viewParent = viewParent.getParent();
2203 }
2204
2205 if (!(viewParent instanceof View)) {
2206 return null;
2207 }
2208
2209 return focusedView;
2210 }
2211
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002212 void updateScrollIndicators() {
2213 if (mScrollUp != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002214 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002215 }
2216
2217 if (mScrollDown != null) {
Alan Viverette947a9692014-09-25 12:43:47 -07002218 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002219 }
2220 }
2221
Alan Viverette947a9692014-09-25 12:43:47 -07002222 private boolean canScrollUp() {
2223 boolean canScrollUp;
2224 // 0th element is not visible
2225 canScrollUp = mFirstPosition > 0;
2226
2227 // ... Or top of 0th element is not visible
2228 if (!canScrollUp) {
2229 if (getChildCount() > 0) {
2230 View child = getChildAt(0);
2231 canScrollUp = child.getTop() < mListPadding.top;
2232 }
2233 }
2234
2235 return canScrollUp;
2236 }
2237
2238 private boolean canScrollDown() {
2239 boolean canScrollDown;
2240 int count = getChildCount();
2241
2242 // Last item is not visible
2243 canScrollDown = (mFirstPosition + count) < mItemCount;
2244
2245 // ... Or bottom of the last element is not visible
2246 if (!canScrollDown && count > 0) {
2247 View child = getChildAt(count - 1);
2248 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2249 }
2250
2251 return canScrollDown;
2252 }
2253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002254 @Override
2255 @ViewDebug.ExportedProperty
2256 public View getSelectedView() {
2257 if (mItemCount > 0 && mSelectedPosition >= 0) {
2258 return getChildAt(mSelectedPosition - mFirstPosition);
2259 } else {
2260 return null;
2261 }
2262 }
2263
2264 /**
2265 * List padding is the maximum of the normal view's padding and the padding of the selector.
2266 *
2267 * @see android.view.View#getPaddingTop()
2268 * @see #getSelector()
2269 *
2270 * @return The top list padding.
2271 */
2272 public int getListPaddingTop() {
2273 return mListPadding.top;
2274 }
2275
2276 /**
2277 * List padding is the maximum of the normal view's padding and the padding of the selector.
2278 *
2279 * @see android.view.View#getPaddingBottom()
2280 * @see #getSelector()
2281 *
2282 * @return The bottom list padding.
2283 */
2284 public int getListPaddingBottom() {
2285 return mListPadding.bottom;
2286 }
2287
2288 /**
2289 * List padding is the maximum of the normal view's padding and the padding of the selector.
2290 *
2291 * @see android.view.View#getPaddingLeft()
2292 * @see #getSelector()
2293 *
2294 * @return The left list padding.
2295 */
2296 public int getListPaddingLeft() {
2297 return mListPadding.left;
2298 }
2299
2300 /**
2301 * List padding is the maximum of the normal view's padding and the padding of the selector.
2302 *
2303 * @see android.view.View#getPaddingRight()
2304 * @see #getSelector()
2305 *
2306 * @return The right list padding.
2307 */
2308 public int getListPaddingRight() {
2309 return mListPadding.right;
2310 }
2311
2312 /**
2313 * Get a view and have it show the data associated with the specified
2314 * position. This is called when we have already discovered that the view is
2315 * not available for reuse in the recycle bin. The only choices left are
2316 * converting an old view or making a new one.
2317 *
2318 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002319 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2320 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002321 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002322 * @return A view displaying the data associated with the specified position
2323 */
Romain Guy21875052010-01-06 18:48:08 -08002324 View obtainView(int position, boolean[] isScrap) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002325 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2326
Romain Guy21875052010-01-06 18:48:08 -08002327 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002328
Alan Viverette59511502013-12-09 13:49:25 -08002329 // Check whether we have a transient state view. Attempt to re-bind the
2330 // data and discard the view if we fail.
2331 final View transientView = mRecycler.getTransientStateView(position);
2332 if (transientView != null) {
Alan Viveretteff699572014-02-19 15:25:10 -08002333 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2334
2335 // If the view type hasn't changed, attempt to re-bind the data.
2336 if (params.viewType == mAdapter.getItemViewType(position)) {
2337 final View updatedView = mAdapter.getView(position, transientView, this);
2338
2339 // If we failed to re-bind the data, scrap the obtained view.
2340 if (updatedView != transientView) {
Alan Viverettee6be9c782014-02-26 18:16:36 -08002341 setItemViewLayoutParams(updatedView, position);
Alan Viveretteff699572014-02-19 15:25:10 -08002342 mRecycler.addScrapView(updatedView, position);
2343 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002344 }
2345
Alan Viverette59511502013-12-09 13:49:25 -08002346 isScrap[0] = true;
Alan Viverette6c413ce2015-06-03 10:35:44 -07002347
2348 // Finish the temporary detach started in addScrapView().
2349 transientView.dispatchFinishTemporaryDetach();
Alan Viverette59511502013-12-09 13:49:25 -08002350 return transientView;
2351 }
2352
2353 final View scrapView = mRecycler.getScrapView(position);
2354 final View child = mAdapter.getView(position, scrapView, this);
2355 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002356 if (child != scrapView) {
Alan Viverette59511502013-12-09 13:49:25 -08002357 // Failed to re-bind the data, return scrap to the heap.
Dianne Hackborn079e2352010-10-18 17:02:43 -07002358 mRecycler.addScrapView(scrapView, position);
Romain Guy21875052010-01-06 18:48:08 -08002359 } else {
Romain Guya440b002010-02-24 15:57:54 -08002360 isScrap[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002361
Alan Viverette6c413ce2015-06-03 10:35:44 -07002362 // Finish the temporary detach started in addScrapView().
Deepanshu Gupta5987e552015-05-13 21:25:57 -07002363 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002364 }
Alan Viverette59511502013-12-09 13:49:25 -08002365 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002366
Alan Viverette59511502013-12-09 13:49:25 -08002367 if (mCacheColorHint != 0) {
2368 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2369 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07002370
Alan Viverette59511502013-12-09 13:49:25 -08002371 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2372 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002373 }
2374
Alan Viverettee6be9c782014-02-26 18:16:36 -08002375 setItemViewLayoutParams(child, position);
Adam Powellaebd28f2012-02-22 10:31:16 -08002376
alanvc1d7e772012-05-08 14:47:24 -07002377 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2378 if (mAccessibilityDelegate == null) {
2379 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2380 }
alanvb72fe7a2012-08-27 16:44:25 -07002381 if (child.getAccessibilityDelegate() == null) {
2382 child.setAccessibilityDelegate(mAccessibilityDelegate);
2383 }
alanvc1d7e772012-05-08 14:47:24 -07002384 }
2385
Romain Guy5fade8c2013-07-10 16:36:18 -07002386 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2387
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002388 return child;
2389 }
2390
Alan Viverettee6be9c782014-02-26 18:16:36 -08002391 private void setItemViewLayoutParams(View child, int position) {
2392 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2393 LayoutParams lp;
2394 if (vlp == null) {
2395 lp = (LayoutParams) generateDefaultLayoutParams();
2396 } else if (!checkLayoutParams(vlp)) {
2397 lp = (LayoutParams) generateLayoutParams(vlp);
2398 } else {
2399 lp = (LayoutParams) vlp;
2400 }
2401
2402 if (mAdapterHasStableIds) {
2403 lp.itemId = mAdapter.getItemId(position);
2404 }
2405 lp.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002406 lp.isEnabled = mAdapter.isEnabled(position);
Adam Powelldbed9e52014-08-11 11:12:58 -07002407 if (lp != vlp) {
2408 child.setLayoutParams(lp);
2409 }
Alan Viverettee6be9c782014-02-26 18:16:36 -08002410 }
2411
alanvc1d7e772012-05-08 14:47:24 -07002412 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2413 @Override
2414 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2415 super.onInitializeAccessibilityNodeInfo(host, info);
2416
2417 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002418 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002419 }
2420
2421 @Override
2422 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002423 if (super.performAccessibilityAction(host, action, arguments)) {
2424 return true;
2425 }
2426
alanvc1d7e772012-05-08 14:47:24 -07002427 final int position = getPositionForView(host);
Alan Viverette92539d52015-09-14 10:49:25 -04002428 if (position == INVALID_POSITION || mAdapter == null) {
alanv9c3e0e62012-05-18 17:43:35 -07002429 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002430 return false;
2431 }
2432
Alan Viverette92539d52015-09-14 10:49:25 -04002433 if (position >= mAdapter.getCount()) {
2434 // The position is no longer valid, likely due to a data set
2435 // change. We could fail here for all data set changes, since
2436 // there is a chance that the data bound to the view may no
2437 // longer exist at the same position within the adapter, but
2438 // it's more consistent with the standard touch interaction to
2439 // click at whatever may have moved into that position.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002440 return false;
2441 }
2442
Alan Viverette92539d52015-09-14 10:49:25 -04002443 final boolean isItemEnabled;
2444 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2445 if (lp instanceof AbsListView.LayoutParams) {
2446 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2447 } else {
2448 isItemEnabled = false;
2449 }
2450
2451 if (!isEnabled() || !isItemEnabled) {
2452 // Cannot perform actions on disabled items.
2453 return false;
2454 }
alanvc1d7e772012-05-08 14:47:24 -07002455
2456 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002457 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2458 if (getSelectedItemPosition() == position) {
2459 setSelection(INVALID_POSITION);
2460 return true;
2461 }
2462 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002463 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002464 if (getSelectedItemPosition() != position) {
2465 setSelection(position);
2466 return true;
2467 }
2468 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002469 case AccessibilityNodeInfo.ACTION_CLICK: {
Alan Viverette92539d52015-09-14 10:49:25 -04002470 if (isItemClickable(host)) {
2471 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002472 return performItemClick(host, position, id);
2473 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002474 } return false;
2475 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2476 if (isLongClickable()) {
Alan Viverette92539d52015-09-14 10:49:25 -04002477 final long id = getItemIdAtPosition(position);
alanvc1d7e772012-05-08 14:47:24 -07002478 return performLongPress(host, position, id);
2479 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002480 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002481 }
2482
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002483 return false;
alanvc1d7e772012-05-08 14:47:24 -07002484 }
2485 }
2486
Alan Viverette5b2081d2013-08-28 10:43:07 -07002487 /**
2488 * Initializes an {@link AccessibilityNodeInfo} with information about a
2489 * particular item in the list.
2490 *
2491 * @param view View representing the list item.
2492 * @param position Position of the list item within the adapter.
2493 * @param info Node info to populate.
2494 */
2495 public void onInitializeAccessibilityNodeInfoForItem(
2496 View view, int position, AccessibilityNodeInfo info) {
Alan Viverette92539d52015-09-14 10:49:25 -04002497 if (position == INVALID_POSITION) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002498 // The item doesn't exist, so there's not much we can do here.
2499 return;
2500 }
2501
Alan Viverette92539d52015-09-14 10:49:25 -04002502 final boolean isItemEnabled;
2503 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2504 if (lp instanceof AbsListView.LayoutParams) {
2505 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2506 } else {
2507 isItemEnabled = false;
2508 }
2509
2510 if (!isEnabled() || !isItemEnabled) {
Alan Viverette5b2081d2013-08-28 10:43:07 -07002511 info.setEnabled(false);
2512 return;
2513 }
2514
2515 if (position == getSelectedItemPosition()) {
2516 info.setSelected(true);
Alan Viverette23f44322015-04-06 16:04:56 -07002517 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002518 } else {
Alan Viverette23f44322015-04-06 16:04:56 -07002519 info.addAction(AccessibilityAction.ACTION_SELECT);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002520 }
2521
Alan Viverette92539d52015-09-14 10:49:25 -04002522 if (isItemClickable(view)) {
Alan Viverette23f44322015-04-06 16:04:56 -07002523 info.addAction(AccessibilityAction.ACTION_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002524 info.setClickable(true);
2525 }
2526
2527 if (isLongClickable()) {
Alan Viverette23f44322015-04-06 16:04:56 -07002528 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002529 info.setLongClickable(true);
2530 }
2531 }
2532
Alan Viverette92539d52015-09-14 10:49:25 -04002533 private boolean isItemClickable(View view) {
2534 return !view.hasFocusable();
Maxim Bogatov67986972015-05-27 11:15:23 -07002535 }
2536
Alan Viverettede399392014-05-01 17:20:55 -07002537 /**
Alan Viveretted361a4f2014-06-30 16:47:40 -07002538 * Positions the selector in a way that mimics touch.
2539 */
2540 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002541 positionSelector(position, sel, true, x, y);
Alan Viveretted361a4f2014-06-30 16:47:40 -07002542 }
2543
2544 /**
Alan Viverette4d2f2482014-06-01 15:58:04 -07002545 * Positions the selector in a way that mimics keyboard focus.
Alan Viverettede399392014-05-01 17:20:55 -07002546 */
2547 void positionSelectorLikeFocus(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002548 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
Alan Viverettede399392014-05-01 17:20:55 -07002549 final Rect bounds = mSelectorRect;
2550 final float x = bounds.exactCenterX();
2551 final float y = bounds.exactCenterY();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002552 positionSelector(position, sel, true, x, y);
2553 } else {
2554 positionSelector(position, sel);
Alan Viverettede399392014-05-01 17:20:55 -07002555 }
2556 }
2557
Dianne Hackborn079e2352010-10-18 17:02:43 -07002558 void positionSelector(int position, View sel) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002559 positionSelector(position, sel, false, -1, -1);
2560 }
2561
2562 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2563 final boolean positionChanged = position != mSelectorPosition;
Dianne Hackborn079e2352010-10-18 17:02:43 -07002564 if (position != INVALID_POSITION) {
2565 mSelectorPosition = position;
2566 }
2567
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002568 final Rect selectorRect = mSelectorRect;
2569 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002570 if (sel instanceof SelectionBoundsAdjuster) {
2571 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2572 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002573
2574 // Adjust for selection padding.
2575 selectorRect.left -= mSelectionLeftPadding;
2576 selectorRect.top -= mSelectionTopPadding;
2577 selectorRect.right += mSelectionRightPadding;
2578 selectorRect.bottom += mSelectionBottomPadding;
2579
Alan Viverettea19ab342015-05-18 13:20:52 -07002580 // Update the child enabled state prior to updating the selector.
2581 final boolean isChildViewEnabled = sel.isEnabled();
2582 if (mIsChildViewEnabled != isChildViewEnabled) {
2583 mIsChildViewEnabled = isChildViewEnabled;
2584 }
2585
2586 // Update the selector drawable's state and position.
Alan Viverette4d2f2482014-06-01 15:58:04 -07002587 final Drawable selector = mSelector;
2588 if (selector != null) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002589 if (positionChanged) {
2590 // Wipe out the current selector state so that we can start
2591 // over in the new position with a fresh state.
2592 selector.setVisible(false, false);
2593 selector.setState(StateSet.NOTHING);
2594 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002595 selector.setBounds(selectorRect);
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002596 if (positionChanged) {
2597 if (getVisibility() == VISIBLE) {
2598 selector.setVisible(true, false);
2599 }
Chet Haase2167b112014-12-19 16:37:18 -08002600 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08002601 }
2602 if (manageHotspot) {
2603 selector.setHotspot(x, y);
2604 }
Alan Viverette4d2f2482014-06-01 15:58:04 -07002605 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002606 }
2607
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002608 @Override
2609 protected void dispatchDraw(Canvas canvas) {
2610 int saveCount = 0;
2611 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2612 if (clipToPadding) {
2613 saveCount = canvas.save();
2614 final int scrollX = mScrollX;
2615 final int scrollY = mScrollY;
2616 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2617 scrollX + mRight - mLeft - mPaddingRight,
2618 scrollY + mBottom - mTop - mPaddingBottom);
2619 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2620 }
2621
2622 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2623 if (!drawSelectorOnTop) {
2624 drawSelector(canvas);
2625 }
2626
2627 super.dispatchDraw(canvas);
2628
2629 if (drawSelectorOnTop) {
2630 drawSelector(canvas);
2631 }
2632
2633 if (clipToPadding) {
2634 canvas.restoreToCount(saveCount);
2635 mGroupFlags |= CLIP_TO_PADDING_MASK;
2636 }
2637 }
2638
2639 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002640 protected boolean isPaddingOffsetRequired() {
2641 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2642 }
2643
2644 @Override
2645 protected int getLeftPaddingOffset() {
2646 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2647 }
2648
2649 @Override
2650 protected int getTopPaddingOffset() {
2651 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2652 }
2653
2654 @Override
2655 protected int getRightPaddingOffset() {
2656 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2657 }
2658
2659 @Override
2660 protected int getBottomPaddingOffset() {
2661 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2662 }
2663
2664 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002665 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2666 if (getChildCount() > 0) {
2667 mDataChanged = true;
2668 rememberSyncState();
2669 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002670
Alan Viverette8636ace2013-10-31 15:41:31 -07002671 if (mFastScroll != null) {
2672 mFastScroll.onSizeChanged(w, h, oldw, oldh);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002673 }
2674 }
2675
2676 /**
2677 * @return True if the current touch mode requires that we draw the selector in the pressed
2678 * state.
2679 */
2680 boolean touchModeDrawsInPressedState() {
2681 // FIXME use isPressed for this
2682 switch (mTouchMode) {
2683 case TOUCH_MODE_TAP:
2684 case TOUCH_MODE_DONE_WAITING:
2685 return true;
2686 default:
2687 return false;
2688 }
2689 }
2690
2691 /**
2692 * Indicates whether this view is in a state where the selector should be drawn. This will
2693 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2694 * the pressed state for an item.
2695 *
2696 * @return True if the selector should be shown
2697 */
2698 boolean shouldShowSelector() {
Alan Viverettef7dee542014-10-30 11:26:29 -07002699 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002700 }
2701
2702 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002703 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002704 final Drawable selector = mSelector;
2705 selector.setBounds(mSelectorRect);
2706 selector.draw(canvas);
2707 }
2708 }
2709
2710 /**
2711 * Controls whether the selection highlight drawable should be drawn on top of the item or
2712 * behind it.
2713 *
2714 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2715 * is false.
2716 *
2717 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2718 */
2719 public void setDrawSelectorOnTop(boolean onTop) {
2720 mDrawSelectorOnTop = onTop;
2721 }
2722
2723 /**
2724 * Set a Drawable that should be used to highlight the currently selected item.
2725 *
2726 * @param resID A Drawable resource to use as the selection highlight.
2727 *
2728 * @attr ref android.R.styleable#AbsListView_listSelector
2729 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07002730 public void setSelector(@DrawableRes int resID) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002731 setSelector(getContext().getDrawable(resID));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002732 }
2733
2734 public void setSelector(Drawable sel) {
2735 if (mSelector != null) {
2736 mSelector.setCallback(null);
2737 unscheduleDrawable(mSelector);
2738 }
2739 mSelector = sel;
2740 Rect padding = new Rect();
2741 sel.getPadding(padding);
2742 mSelectionLeftPadding = padding.left;
2743 mSelectionTopPadding = padding.top;
2744 mSelectionRightPadding = padding.right;
2745 mSelectionBottomPadding = padding.bottom;
2746 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002747 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002748 }
2749
2750 /**
2751 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2752 * selection in the list.
2753 *
2754 * @return the drawable used to display the selector
2755 */
2756 public Drawable getSelector() {
2757 return mSelector;
2758 }
2759
2760 /**
2761 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2762 * this is a long press.
2763 */
2764 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002765 if (!isEnabled() || !isClickable()) {
2766 return;
2767 }
2768
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002769 Drawable selector = mSelector;
2770 Rect selectorRect = mSelectorRect;
2771 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002772 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002773
2774 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2775
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002776 if (v != null) {
2777 if (v.hasFocusable()) return;
2778 v.setPressed(true);
2779 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002780 setPressed(true);
2781
2782 final boolean longClickable = isLongClickable();
2783 Drawable d = selector.getCurrent();
2784 if (d != null && d instanceof TransitionDrawable) {
2785 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002786 ((TransitionDrawable) d).startTransition(
2787 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002788 } else {
2789 ((TransitionDrawable) d).resetTransition();
2790 }
2791 }
2792 if (longClickable && !mDataChanged) {
2793 if (mPendingCheckForKeyLongPress == null) {
2794 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2795 }
2796 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2797 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2798 }
2799 }
2800 }
2801
2802 public void setScrollIndicators(View up, View down) {
2803 mScrollUp = up;
2804 mScrollDown = down;
2805 }
2806
Dianne Hackborn079e2352010-10-18 17:02:43 -07002807 void updateSelectorState() {
Alan Viverettead0020f2015-09-04 10:10:42 -04002808 final Drawable selector = mSelector;
2809 if (selector != null && selector.isStateful()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002810 if (shouldShowSelector()) {
Alan Viverettead0020f2015-09-04 10:10:42 -04002811 if (selector.setState(getDrawableStateForSelector())) {
2812 invalidateDrawable(selector);
2813 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002814 } else {
Alan Viverettead0020f2015-09-04 10:10:42 -04002815 selector.setState(StateSet.NOTHING);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002816 }
2817 }
2818 }
2819
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002820 @Override
2821 protected void drawableStateChanged() {
2822 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002823 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002824 }
2825
Alan Viverettef723c832015-02-03 16:31:46 -08002826 private int[] getDrawableStateForSelector() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002827 // If the child view is enabled then do the default behavior.
2828 if (mIsChildViewEnabled) {
2829 // Common case
Alan Viverettef723c832015-02-03 16:31:46 -08002830 return super.getDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002831 }
2832
2833 // The selector uses this View's drawable state. The selected child view
2834 // is disabled, so we need to remove the enabled state from the drawable
2835 // states.
2836 final int enabledState = ENABLED_STATE_SET[0];
2837
Alan Viverettef723c832015-02-03 16:31:46 -08002838 // If we don't have any extra space, it will return one of the static
2839 // state arrays, and clearing the enabled state on those arrays is a
2840 // bad thing! If we specify we need extra space, it will create+copy
2841 // into a new array that is safely mutable.
2842 final int[] state = onCreateDrawableState(1);
2843
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002844 int enabledPos = -1;
2845 for (int i = state.length - 1; i >= 0; i--) {
2846 if (state[i] == enabledState) {
2847 enabledPos = i;
2848 break;
2849 }
2850 }
2851
2852 // Remove the enabled state
2853 if (enabledPos >= 0) {
2854 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2855 state.length - enabledPos - 1);
2856 }
Romain Guy0a637162009-05-29 14:43:54 -07002857
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002858 return state;
2859 }
2860
2861 @Override
2862 public boolean verifyDrawable(Drawable dr) {
2863 return mSelector == dr || super.verifyDrawable(dr);
2864 }
2865
2866 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002867 public void jumpDrawablesToCurrentState() {
2868 super.jumpDrawablesToCurrentState();
2869 if (mSelector != null) mSelector.jumpToCurrentState();
2870 }
2871
2872 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002873 protected void onAttachedToWindow() {
2874 super.onAttachedToWindow();
2875
2876 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002877 treeObserver.addOnTouchModeChangeListener(this);
2878 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2879 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002880 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002881
Romain Guy82afc7b2010-05-13 11:52:37 -07002882 if (mAdapter != null && mDataSetObserver == null) {
2883 mDataSetObserver = new AdapterDataSetObserver();
2884 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002885
2886 // Data may have changed while we were detached. Refresh.
2887 mDataChanged = true;
2888 mOldItemCount = mItemCount;
2889 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002890 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002891 }
2892
2893 @Override
2894 protected void onDetachedFromWindow() {
2895 super.onDetachedFromWindow();
2896
Alan Viverette462c2172014-02-24 12:24:11 -08002897 mIsDetaching = true;
2898
Romain Guy1f7f3c32009-07-22 11:25:42 -07002899 // Dismiss the popup in case onSaveInstanceState() was not invoked
2900 dismissPopup();
2901
Romain Guy21875052010-01-06 18:48:08 -08002902 // Detach any view left in the scrap heap
2903 mRecycler.clear();
2904
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002905 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002906 treeObserver.removeOnTouchModeChangeListener(this);
2907 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002908 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002909 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002910 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002911
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002912 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002913 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2914 mDataSetObserver = null;
2915 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002916
2917 if (mScrollStrictSpan != null) {
2918 mScrollStrictSpan.finish();
2919 mScrollStrictSpan = null;
2920 }
2921
2922 if (mFlingStrictSpan != null) {
2923 mFlingStrictSpan.finish();
2924 mFlingStrictSpan = null;
2925 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002926
2927 if (mFlingRunnable != null) {
2928 removeCallbacks(mFlingRunnable);
2929 }
2930
2931 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002932 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002933 }
2934
2935 if (mClearScrollingCache != null) {
2936 removeCallbacks(mClearScrollingCache);
2937 }
2938
2939 if (mPerformClick != null) {
2940 removeCallbacks(mPerformClick);
2941 }
2942
2943 if (mTouchModeReset != null) {
2944 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002945 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002946 }
Alan Viverette462c2172014-02-24 12:24:11 -08002947
2948 mIsDetaching = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002949 }
2950
2951 @Override
2952 public void onWindowFocusChanged(boolean hasWindowFocus) {
2953 super.onWindowFocusChanged(hasWindowFocus);
2954
2955 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2956
2957 if (!hasWindowFocus) {
2958 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002959 if (mFlingRunnable != null) {
2960 removeCallbacks(mFlingRunnable);
2961 // let the fling runnable report it's new state which
2962 // should be idle
2963 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002964 if (mPositionScroller != null) {
2965 mPositionScroller.stop();
2966 }
Adam Powell45803472010-01-25 15:10:44 -08002967 if (mScrollY != 0) {
2968 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002969 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002970 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002971 invalidate();
2972 }
Mark Wagner670dd812010-01-13 16:17:47 -08002973 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002974 // Always hide the type filter
2975 dismissPopup();
2976
2977 if (touchMode == TOUCH_MODE_OFF) {
2978 // Remember the last selected element
2979 mResurrectToPosition = mSelectedPosition;
2980 }
2981 } else {
Adam Powell97566042010-03-09 15:34:09 -08002982 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002983 // Show the type filter only if a filter is in effect
2984 showPopup();
2985 }
2986
2987 // If we changed touch mode since the last time we had focus
2988 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2989 // If we come back in trackball mode, we bring the selection back
2990 if (touchMode == TOUCH_MODE_OFF) {
2991 // This will trigger a layout
2992 resurrectSelection();
2993
2994 // If we come back in touch mode, then we want to hide the selector
2995 } else {
2996 hideSelector();
2997 mLayoutMode = LAYOUT_NORMAL;
2998 layoutChildren();
2999 }
3000 }
3001 }
3002
3003 mLastTouchMode = touchMode;
3004 }
3005
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003006 @Override
3007 public void onRtlPropertiesChanged(int layoutDirection) {
3008 super.onRtlPropertiesChanged(layoutDirection);
Alan Viverette8636ace2013-10-31 15:41:31 -07003009 if (mFastScroll != null) {
3010 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07003011 }
3012 }
3013
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003014 /**
3015 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3016 * methods knows the view, position and ID of the item that received the
3017 * long press.
3018 *
3019 * @param view The view that received the long press.
3020 * @param position The position of the item that received the long press.
3021 * @param id The ID of the item that received the long press.
3022 * @return The extra information that should be returned by
3023 * {@link #getContextMenuInfo()}.
3024 */
3025 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3026 return new AdapterContextMenuInfo(view, position, id);
3027 }
3028
Adam Powell14874662013-07-18 19:42:41 -07003029 @Override
3030 public void onCancelPendingInputEvents() {
3031 super.onCancelPendingInputEvents();
3032 if (mPerformClick != null) {
3033 removeCallbacks(mPerformClick);
3034 }
3035 if (mPendingCheckForTap != null) {
3036 removeCallbacks(mPendingCheckForTap);
3037 }
3038 if (mPendingCheckForLongPress != null) {
3039 removeCallbacks(mPendingCheckForLongPress);
3040 }
3041 if (mPendingCheckForKeyLongPress != null) {
3042 removeCallbacks(mPendingCheckForKeyLongPress);
3043 }
3044 }
3045
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003046 /**
3047 * A base class for Runnables that will check that their view is still attached to
3048 * the original window as when the Runnable was created.
3049 *
3050 */
3051 private class WindowRunnnable {
3052 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07003053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003054 public void rememberWindowAttachCount() {
3055 mOriginalAttachCount = getWindowAttachCount();
3056 }
Romain Guy0a637162009-05-29 14:43:54 -07003057
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003058 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07003059 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003060 }
3061 }
Romain Guy0a637162009-05-29 14:43:54 -07003062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003063 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003064 int mClickMotionPosition;
3065
Alan Viverette8fa327a2013-05-31 14:53:13 -07003066 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003067 public void run() {
3068 // The data has changed since we posted this action in the event queue,
3069 // bail out before bad things happen
3070 if (mDataChanged) return;
3071
Adam Powell005c0a42010-03-30 16:26:36 -07003072 final ListAdapter adapter = mAdapter;
3073 final int motionPosition = mClickMotionPosition;
3074 if (adapter != null && mItemCount > 0 &&
3075 motionPosition != INVALID_POSITION &&
3076 motionPosition < adapter.getCount() && sameWindow()) {
Romain Guy7890fe22011-01-18 20:24:18 -08003077 final View view = getChildAt(motionPosition - mFirstPosition);
3078 // If there is no view, something bad happened (the view scrolled off the
3079 // screen, etc.) and we should cancel the click
3080 if (view != null) {
3081 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3082 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003083 }
3084 }
3085 }
3086
3087 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Oren Blasberged391262015-09-01 12:12:51 -07003088 private static final int INVALID_COORD = -1;
3089 private float mX = INVALID_COORD;
3090 private float mY = INVALID_COORD;
3091
3092 private void setCoords(float x, float y) {
3093 mX = x;
3094 mY = y;
3095 }
3096
Alan Viverette8fa327a2013-05-31 14:53:13 -07003097 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003098 public void run() {
3099 final int motionPosition = mMotionPosition;
3100 final View child = getChildAt(motionPosition - mFirstPosition);
3101 if (child != null) {
3102 final int longPressPosition = mMotionPosition;
3103 final long longPressId = mAdapter.getItemId(mMotionPosition);
3104
3105 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07003106 if (sameWindow() && !mDataChanged) {
Oren Blasberged391262015-09-01 12:12:51 -07003107 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3108 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3109 } else {
3110 handled = performLongPress(child, longPressPosition, longPressId);
3111 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003112 }
3113 if (handled) {
3114 mTouchMode = TOUCH_MODE_REST;
3115 setPressed(false);
3116 child.setPressed(false);
3117 } else {
3118 mTouchMode = TOUCH_MODE_DONE_WAITING;
3119 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003120 }
3121 }
3122 }
Romain Guy0a637162009-05-29 14:43:54 -07003123
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003124 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003125 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003126 public void run() {
3127 if (isPressed() && mSelectedPosition >= 0) {
3128 int index = mSelectedPosition - mFirstPosition;
3129 View v = getChildAt(index);
3130
3131 if (!mDataChanged) {
3132 boolean handled = false;
3133 if (sameWindow()) {
3134 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3135 }
3136 if (handled) {
3137 setPressed(false);
3138 v.setPressed(false);
3139 }
3140 } else {
3141 setPressed(false);
3142 if (v != null) v.setPressed(false);
3143 }
3144 }
3145 }
3146 }
3147
Mady Mellore5561982015-04-14 15:06:40 -07003148 private boolean performStylusButtonPressAction(MotionEvent ev) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003149 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Mady Mellore5561982015-04-14 15:06:40 -07003150 final View child = getChildAt(mMotionPosition - mFirstPosition);
3151 if (child != null) {
3152 final int longPressPosition = mMotionPosition;
3153 final long longPressId = mAdapter.getItemId(mMotionPosition);
3154 if (performLongPress(child, longPressPosition, longPressId)) {
3155 mTouchMode = TOUCH_MODE_REST;
3156 setPressed(false);
3157 child.setPressed(false);
3158 return true;
3159 }
3160 }
3161 }
3162 return false;
3163 }
3164
Adam Powell8350f7d2010-07-28 14:27:28 -07003165 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003166 final int longPressPosition, final long longPressId) {
Oren Blasberged391262015-09-01 12:12:51 -07003167 return performLongPress(
3168 child,
3169 longPressPosition,
3170 longPressId,
3171 CheckForLongPress.INVALID_COORD,
3172 CheckForLongPress.INVALID_COORD);
3173 }
3174
3175 boolean performLongPress(final View child,
3176 final int longPressPosition, final long longPressId, float x, float y) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003177 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3178 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07003179 if (mChoiceActionMode == null &&
3180 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07003181 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07003182 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07003183 }
Adam Powellf343e1b2010-08-13 18:27:04 -07003184 return true;
3185 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003186
Adam Powellf343e1b2010-08-13 18:27:04 -07003187 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003188 if (mOnItemLongClickListener != null) {
3189 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3190 longPressPosition, longPressId);
3191 }
3192 if (!handled) {
3193 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
Oren Blasberged391262015-09-01 12:12:51 -07003194 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3195 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3196 } else {
3197 handled = super.showContextMenuForChild(AbsListView.this);
3198 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003199 }
3200 if (handled) {
3201 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3202 }
3203 return handled;
3204 }
3205
3206 @Override
3207 protected ContextMenuInfo getContextMenuInfo() {
3208 return mContextMenuInfo;
3209 }
3210
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003211 /** @hide */
3212 @Override
Oren Blasberged391262015-09-01 12:12:51 -07003213 public boolean showContextMenu(float x, float y) {
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003214 final int position = pointToPosition((int)x, (int)y);
3215 if (position != INVALID_POSITION) {
3216 final long id = mAdapter.getItemId(position);
3217 View child = getChildAt(position - mFirstPosition);
3218 if (child != null) {
3219 mContextMenuInfo = createContextMenuInfo(child, position, id);
Oren Blasberged391262015-09-01 12:12:51 -07003220 return super.showContextMenuForChild(AbsListView.this, x, y);
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003221 }
3222 }
Oren Blasberged391262015-09-01 12:12:51 -07003223 return super.showContextMenu(x, y);
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003224 }
3225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003226 @Override
3227 public boolean showContextMenuForChild(View originalView) {
3228 final int longPressPosition = getPositionForView(originalView);
3229 if (longPressPosition >= 0) {
3230 final long longPressId = mAdapter.getItemId(longPressPosition);
3231 boolean handled = false;
3232
3233 if (mOnItemLongClickListener != null) {
3234 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
3235 longPressPosition, longPressId);
3236 }
3237 if (!handled) {
3238 mContextMenuInfo = createContextMenuInfo(
3239 getChildAt(longPressPosition - mFirstPosition),
3240 longPressPosition, longPressId);
3241 handled = super.showContextMenuForChild(originalView);
3242 }
3243
3244 return handled;
3245 }
3246 return false;
3247 }
3248
3249 @Override
Romain Guydf016072009-08-17 12:51:30 -07003250 public boolean onKeyDown(int keyCode, KeyEvent event) {
3251 return false;
3252 }
3253
3254 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003255 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003256 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003257 if (!isEnabled()) {
3258 return true;
3259 }
Romain Guydf016072009-08-17 12:51:30 -07003260 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003261 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003262 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003263
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003264 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003265 if (view != null) {
3266 performItemClick(view, mSelectedPosition, mSelectedRowId);
3267 view.setPressed(false);
3268 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003269 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003270 return true;
3271 }
3272 }
3273 return super.onKeyUp(keyCode, event);
3274 }
3275
3276 @Override
3277 protected void dispatchSetPressed(boolean pressed) {
3278 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3279 // get the selector in the right state, but we don't want to press each child.
3280 }
3281
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003282 @Override
3283 public void dispatchDrawableHotspotChanged(float x, float y) {
3284 // Don't dispatch hotspot changes to children. We'll manually handle
3285 // calling drawableHotspotChanged on the correct child.
3286 }
3287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003288 /**
3289 * Maps a point to a position in the list.
3290 *
3291 * @param x X in local coordinate
3292 * @param y Y in local coordinate
3293 * @return The position of the item which contains the specified point, or
3294 * {@link #INVALID_POSITION} if the point does not intersect an item.
3295 */
3296 public int pointToPosition(int x, int y) {
3297 Rect frame = mTouchFrame;
3298 if (frame == null) {
3299 mTouchFrame = new Rect();
3300 frame = mTouchFrame;
3301 }
3302
3303 final int count = getChildCount();
3304 for (int i = count - 1; i >= 0; i--) {
3305 final View child = getChildAt(i);
3306 if (child.getVisibility() == View.VISIBLE) {
3307 child.getHitRect(frame);
3308 if (frame.contains(x, y)) {
3309 return mFirstPosition + i;
3310 }
3311 }
3312 }
3313 return INVALID_POSITION;
3314 }
3315
3316
3317 /**
3318 * Maps a point to a the rowId of the item which intersects that point.
3319 *
3320 * @param x X in local coordinate
3321 * @param y Y in local coordinate
3322 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3323 * if the point does not intersect an item.
3324 */
3325 public long pointToRowId(int x, int y) {
3326 int position = pointToPosition(x, y);
3327 if (position >= 0) {
3328 return mAdapter.getItemId(position);
3329 }
3330 return INVALID_ROW_ID;
3331 }
3332
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003333 private final class CheckForTap implements Runnable {
3334 float x;
3335 float y;
3336
Alan Viverette8fa327a2013-05-31 14:53:13 -07003337 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003338 public void run() {
3339 if (mTouchMode == TOUCH_MODE_DOWN) {
3340 mTouchMode = TOUCH_MODE_TAP;
3341 final View child = getChildAt(mMotionPosition - mFirstPosition);
3342 if (child != null && !child.hasFocusable()) {
3343 mLayoutMode = LAYOUT_NORMAL;
3344
3345 if (!mDataChanged) {
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003346 final float[] point = mTmpPoint;
3347 point[0] = x;
3348 point[1] = y;
3349 transformPointToViewLocal(point, child);
3350 child.drawableHotspotChanged(point[0], point[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003351 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003352 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003353 layoutChildren();
3354 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003355 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003356
3357 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3358 final boolean longClickable = isLongClickable();
3359
3360 if (mSelector != null) {
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003361 final Drawable d = mSelector.getCurrent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362 if (d != null && d instanceof TransitionDrawable) {
3363 if (longClickable) {
3364 ((TransitionDrawable) d).startTransition(longPressTimeout);
3365 } else {
3366 ((TransitionDrawable) d).resetTransition();
3367 }
3368 }
Alan Viverette8390fab2014-06-30 16:03:43 -07003369 mSelector.setHotspot(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003370 }
3371
3372 if (longClickable) {
3373 if (mPendingCheckForLongPress == null) {
3374 mPendingCheckForLongPress = new CheckForLongPress();
3375 }
Oren Blasberged391262015-09-01 12:12:51 -07003376 mPendingCheckForLongPress.setCoords(x, y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003377 mPendingCheckForLongPress.rememberWindowAttachCount();
3378 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3379 } else {
3380 mTouchMode = TOUCH_MODE_DONE_WAITING;
3381 }
3382 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003383 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003384 }
3385 }
3386 }
3387 }
3388 }
3389
Adam Powellc501db9f2014-05-08 12:50:10 -07003390 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003391 // Check if we have moved far enough that it looks more like a
3392 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003393 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003394 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003395 final boolean overscroll = mScrollY != 0;
Adam Powell96d62af2014-05-02 10:04:38 -07003396 if ((overscroll || distance > mTouchSlop) &&
3397 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003398 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003399 if (overscroll) {
3400 mTouchMode = TOUCH_MODE_OVERSCROLL;
3401 mMotionCorrection = 0;
3402 } else {
3403 mTouchMode = TOUCH_MODE_SCROLL;
3404 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3405 }
Alan Viverette74ded292013-06-03 15:34:11 -07003406 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003407 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003408 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003409 if (motionView != null) {
3410 motionView.setPressed(false);
3411 }
3412 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3413 // Time to start stealing events! Once we've stolen them, don't let anyone
3414 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003415 final ViewParent parent = getParent();
3416 if (parent != null) {
3417 parent.requestDisallowInterceptTouchEvent(true);
3418 }
Adam Powellc501db9f2014-05-08 12:50:10 -07003419 scrollIfNeeded(x, y, vtev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003420 return true;
3421 }
3422
3423 return false;
3424 }
3425
Adam Powellc501db9f2014-05-08 12:50:10 -07003426 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
Adam Powell96d62af2014-05-02 10:04:38 -07003427 int rawDeltaY = y - mMotionY;
Yorke Lee43943d82014-05-08 10:15:20 -07003428 int scrollOffsetCorrection = 0;
3429 int scrollConsumedCorrection = 0;
3430 if (mLastY == Integer.MIN_VALUE) {
3431 rawDeltaY -= mMotionCorrection;
3432 }
Brian Attwelle0e42172014-09-16 14:46:20 -07003433 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3434 mScrollConsumed, mScrollOffset)) {
Adam Powellaab726c2014-07-07 15:10:54 -07003435 rawDeltaY += mScrollConsumed[1];
Adam Powellfd1e93d2014-09-07 16:52:22 -07003436 scrollOffsetCorrection = -mScrollOffset[1];
3437 scrollConsumedCorrection = mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003438 if (vtev != null) {
3439 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003440 mNestedYOffset += mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -07003441 }
3442 }
Yorke Lee43943d82014-05-08 10:15:20 -07003443 final int deltaY = rawDeltaY;
3444 int incrementalDeltaY =
Yorke Leee2e19392014-05-12 11:14:12 -07003445 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
Adam Powell96d62af2014-05-02 10:04:38 -07003446 int lastYCorrection = 0;
Jeff Brown78f6e632011-09-09 17:15:31 -07003447
3448 if (mTouchMode == TOUCH_MODE_SCROLL) {
3449 if (PROFILE_SCROLLING) {
3450 if (!mScrollProfilingStarted) {
3451 Debug.startMethodTracing("AbsListViewScroll");
3452 mScrollProfilingStarted = true;
3453 }
3454 }
3455
3456 if (mScrollStrictSpan == null) {
3457 // If it's non-null, we're already in a scroll.
3458 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3459 }
3460
3461 if (y != mLastY) {
3462 // We may be here after stopping a fling and continuing to scroll.
3463 // If so, we haven't disallowed intercepting touch events yet.
3464 // Make sure that we do so in case we're in a parent that can intercept.
3465 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3466 Math.abs(rawDeltaY) > mTouchSlop) {
3467 final ViewParent parent = getParent();
3468 if (parent != null) {
3469 parent.requestDisallowInterceptTouchEvent(true);
3470 }
3471 }
3472
3473 final int motionIndex;
3474 if (mMotionPosition >= 0) {
3475 motionIndex = mMotionPosition - mFirstPosition;
3476 } else {
3477 // If we don't have a motion position that we can reliably track,
3478 // pick something in the middle to make a best guess at things below.
3479 motionIndex = getChildCount() / 2;
3480 }
3481
3482 int motionViewPrevTop = 0;
3483 View motionView = this.getChildAt(motionIndex);
3484 if (motionView != null) {
3485 motionViewPrevTop = motionView.getTop();
3486 }
3487
3488 // No need to do all this work if we're not going to move anyway
3489 boolean atEdge = false;
3490 if (incrementalDeltaY != 0) {
3491 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3492 }
3493
3494 // Check to see if we have bumped into the scroll limit
3495 motionView = this.getChildAt(motionIndex);
3496 if (motionView != null) {
3497 // Check if the top of the motion view is where it is
3498 // supposed to be
3499 final int motionViewRealTop = motionView.getTop();
3500 if (atEdge) {
3501 // Apply overscroll
3502
3503 int overscroll = -incrementalDeltaY -
3504 (motionViewRealTop - motionViewPrevTop);
Adam Powell96d62af2014-05-02 10:04:38 -07003505 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3506 mScrollOffset)) {
Adam Powell96d62af2014-05-02 10:04:38 -07003507 lastYCorrection -= mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003508 if (vtev != null) {
3509 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -07003510 mNestedYOffset += mScrollOffset[1];
Adam Powell11d00692014-05-05 13:28:22 -07003511 }
Adam Powell96d62af2014-05-02 10:04:38 -07003512 } else {
Adam Powellc501db9f2014-05-08 12:50:10 -07003513 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3514 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3515
3516 if (atOverscrollEdge && mVelocityTracker != null) {
3517 // Don't allow overfling if we're at the edge
3518 mVelocityTracker.clear();
Jeff Brown78f6e632011-09-09 17:15:31 -07003519 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003520
Adam Powell96d62af2014-05-02 10:04:38 -07003521 final int overscrollMode = getOverScrollMode();
3522 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3523 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3524 !contentFits())) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003525 if (!atOverscrollEdge) {
3526 mDirection = 0; // Reset when entering overscroll.
3527 mTouchMode = TOUCH_MODE_OVERSCROLL;
3528 }
3529 if (incrementalDeltaY > 0) {
Adam Powell2897a6f2014-05-12 22:20:45 -07003530 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
Adam Powellc501db9f2014-05-08 12:50:10 -07003531 (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003532 if (!mEdgeGlowBottom.isFinished()) {
3533 mEdgeGlowBottom.onRelease();
3534 }
Doris Liuf36c0612015-06-04 11:11:14 -07003535 invalidateTopGlow();
Adam Powellc501db9f2014-05-08 12:50:10 -07003536 } else if (incrementalDeltaY < 0) {
3537 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3538 1.f - (float) x / getWidth());
Adam Powell96d62af2014-05-02 10:04:38 -07003539 if (!mEdgeGlowTop.isFinished()) {
3540 mEdgeGlowTop.onRelease();
3541 }
Doris Liuf36c0612015-06-04 11:11:14 -07003542 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003543 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003544 }
3545 }
3546 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003547 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003548 }
Yorke Lee43943d82014-05-08 10:15:20 -07003549 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003550 }
3551 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3552 if (y != mLastY) {
3553 final int oldScroll = mScrollY;
3554 final int newScroll = oldScroll - incrementalDeltaY;
3555 int newDirection = y > mLastY ? 1 : -1;
3556
3557 if (mDirection == 0) {
3558 mDirection = newDirection;
3559 }
3560
3561 int overScrollDistance = -incrementalDeltaY;
3562 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3563 overScrollDistance = -oldScroll;
3564 incrementalDeltaY += overScrollDistance;
3565 } else {
3566 incrementalDeltaY = 0;
3567 }
3568
3569 if (overScrollDistance != 0) {
3570 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3571 0, mOverscrollDistance, true);
3572 final int overscrollMode = getOverScrollMode();
3573 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3574 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3575 !contentFits())) {
3576 if (rawDeltaY > 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003577 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3578 (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003579 if (!mEdgeGlowBottom.isFinished()) {
3580 mEdgeGlowBottom.onRelease();
3581 }
Doris Liuf36c0612015-06-04 11:11:14 -07003582 invalidateTopGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003583 } else if (rawDeltaY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -07003584 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3585 1.f - (float) x / getWidth());
Jeff Brown78f6e632011-09-09 17:15:31 -07003586 if (!mEdgeGlowTop.isFinished()) {
3587 mEdgeGlowTop.onRelease();
3588 }
Doris Liuf36c0612015-06-04 11:11:14 -07003589 invalidateBottomGlow();
Jeff Brown78f6e632011-09-09 17:15:31 -07003590 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003591 }
3592 }
3593
3594 if (incrementalDeltaY != 0) {
3595 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003596 if (mScrollY != 0) {
3597 mScrollY = 0;
3598 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003599 }
3600
Romain Guy9d849a22012-03-14 16:41:42 -07003601 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3602
Jeff Brown78f6e632011-09-09 17:15:31 -07003603 mTouchMode = TOUCH_MODE_SCROLL;
3604
3605 // We did not scroll the full amount. Treat this essentially like the
3606 // start of a new touch scroll
3607 final int motionPosition = findClosestMotionRow(y);
3608
3609 mMotionCorrection = 0;
3610 View motionView = getChildAt(motionPosition - mFirstPosition);
3611 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
Adam Powellfd1e93d2014-09-07 16:52:22 -07003612 mMotionY = y + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003613 mMotionPosition = motionPosition;
3614 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07003615 mLastY = y + lastYCorrection + scrollOffsetCorrection;
Jeff Brown78f6e632011-09-09 17:15:31 -07003616 mDirection = newDirection;
3617 }
3618 }
3619 }
3620
Doris Liuf36c0612015-06-04 11:11:14 -07003621 private void invalidateTopGlow() {
3622 if (mEdgeGlowTop == null) {
3623 return;
3624 }
3625 final boolean clipToPadding = getClipToPadding();
3626 final int top = clipToPadding ? mPaddingTop : 0;
3627 final int left = clipToPadding ? mPaddingLeft : 0;
3628 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3629 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3630 }
3631
3632 private void invalidateBottomGlow() {
3633 if (mEdgeGlowBottom == null) {
3634 return;
3635 }
3636 final boolean clipToPadding = getClipToPadding();
3637 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3638 final int left = clipToPadding ? mPaddingLeft : 0;
3639 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3640 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3641 }
3642
Alan Viverette8fa327a2013-05-31 14:53:13 -07003643 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003644 public void onTouchModeChanged(boolean isInTouchMode) {
3645 if (isInTouchMode) {
3646 // Get rid of the selection when we enter touch mode
3647 hideSelector();
3648 // Layout, but only if we already have done so previously.
3649 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3650 // state.)
3651 if (getHeight() > 0 && getChildCount() > 0) {
3652 // We do not lose focus initiating a touch (since AbsListView is focusable in
3653 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003654 layoutChildren();
3655 }
Jeff Brown1e209462011-07-14 22:19:19 -07003656 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003657 } else {
3658 int touchMode = mTouchMode;
3659 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3660 if (mFlingRunnable != null) {
3661 mFlingRunnable.endFling();
3662 }
Adam Powell40322522011-01-12 21:58:20 -08003663 if (mPositionScroller != null) {
3664 mPositionScroller.stop();
3665 }
Adam Powell637d3372010-08-25 14:37:03 -07003666
3667 if (mScrollY != 0) {
3668 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003669 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003670 finishGlows();
3671 invalidate();
3672 }
3673 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003674 }
3675 }
3676
3677 @Override
3678 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003679 if (!isEnabled()) {
3680 // A disabled view that is clickable still consumes the touch
3681 // events, it just doesn't respond to them.
3682 return isClickable() || isLongClickable();
3683 }
3684
Adam Powell1fa179ef2012-04-12 15:01:40 -07003685 if (mPositionScroller != null) {
3686 mPositionScroller.stop();
3687 }
3688
Alan Viverette462c2172014-02-24 12:24:11 -08003689 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003690 // Something isn't right.
3691 // Since we rely on being attached to get data set change notifications,
3692 // don't risk doing anything where we might try to resync and find things
3693 // in a bogus state.
3694 return false;
3695 }
3696
Adam Powell96d62af2014-05-02 10:04:38 -07003697 startNestedScroll(SCROLL_AXIS_VERTICAL);
3698
Alan Viverettefb99ba82015-05-01 10:10:15 -07003699 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3700 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003701 }
Romain Guy82f34952009-05-24 18:40:45 -07003702
Michael Jurka13451a42011-08-22 15:54:21 -07003703 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -07003704 final MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003705
Alan Viverette8fa327a2013-05-31 14:53:13 -07003706 final int actionMasked = ev.getActionMasked();
Adam Powell744beff2014-09-22 09:47:48 -07003707 if (actionMasked == MotionEvent.ACTION_DOWN) {
3708 mNestedYOffset = 0;
3709 }
3710 vtev.offsetLocation(0, mNestedYOffset);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003711 switch (actionMasked) {
3712 case MotionEvent.ACTION_DOWN: {
3713 onTouchDown(ev);
3714 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003715 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003716
Alan Viverette8fa327a2013-05-31 14:53:13 -07003717 case MotionEvent.ACTION_MOVE: {
Adam Powell96d62af2014-05-02 10:04:38 -07003718 onTouchMove(ev, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003719 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003720 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003721
3722 case MotionEvent.ACTION_UP: {
3723 onTouchUp(ev);
3724 break;
3725 }
3726
3727 case MotionEvent.ACTION_CANCEL: {
3728 onTouchCancel();
3729 break;
3730 }
3731
3732 case MotionEvent.ACTION_POINTER_UP: {
3733 onSecondaryPointerUp(ev);
3734 final int x = mMotionX;
3735 final int y = mMotionY;
3736 final int motionPosition = pointToPosition(x, y);
3737 if (motionPosition >= 0) {
3738 // Remember where the motion event started
3739 final View child = getChildAt(motionPosition - mFirstPosition);
3740 mMotionViewOriginalTop = child.getTop();
3741 mMotionPosition = motionPosition;
3742 }
3743 mLastY = y;
3744 break;
3745 }
3746
3747 case MotionEvent.ACTION_POINTER_DOWN: {
3748 // New pointers take over dragging duties
3749 final int index = ev.getActionIndex();
3750 final int id = ev.getPointerId(index);
3751 final int x = (int) ev.getX(index);
3752 final int y = (int) ev.getY(index);
3753 mMotionCorrection = 0;
3754 mActivePointerId = id;
3755 mMotionX = x;
3756 mMotionY = y;
3757 final int motionPosition = pointToPosition(x, y);
3758 if (motionPosition >= 0) {
3759 // Remember where the motion event started
3760 final View child = getChildAt(motionPosition - mFirstPosition);
3761 mMotionViewOriginalTop = child.getTop();
3762 mMotionPosition = motionPosition;
3763 }
3764 mLastY = y;
3765 break;
3766 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003767 }
3768
Adam Powell96d62af2014-05-02 10:04:38 -07003769 if (mVelocityTracker != null) {
3770 mVelocityTracker.addMovement(vtev);
3771 }
3772 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003773 return true;
3774 }
Romain Guy0a637162009-05-29 14:43:54 -07003775
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003776 private void onTouchDown(MotionEvent ev) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003777 mActivePointerId = ev.getPointerId(0);
3778
3779 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3780 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003781 mFlingRunnable.endFling();
3782 if (mPositionScroller != null) {
3783 mPositionScroller.stop();
3784 }
3785 mTouchMode = TOUCH_MODE_OVERSCROLL;
3786 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003787 mMotionY = (int) ev.getY();
3788 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003789 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003790 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003791 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003792 final int x = (int) ev.getX();
3793 final int y = (int) ev.getY();
3794 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003795
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003796 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003797 if (mTouchMode == TOUCH_MODE_FLING) {
3798 // Stopped a fling. It is a scroll.
3799 createScrollingCache();
3800 mTouchMode = TOUCH_MODE_SCROLL;
3801 mMotionCorrection = 0;
3802 motionPosition = findMotionRow(y);
3803 mFlingRunnable.flywheelTouch();
3804 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3805 // User clicked on an actual view (and was not stopping a
3806 // fling). It might be a click or a scroll. Assume it is a
3807 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003808 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003809
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003810 // FIXME Debounce
3811 if (mPendingCheckForTap == null) {
3812 mPendingCheckForTap = new CheckForTap();
3813 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003814
Alan Viveretted1ca75b2014-04-27 18:13:34 -07003815 mPendingCheckForTap.x = ev.getX();
3816 mPendingCheckForTap.y = ev.getY();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003817 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003818 }
3819 }
3820
3821 if (motionPosition >= 0) {
3822 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003823 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003824 mMotionViewOriginalTop = v.getTop();
3825 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003826
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003827 mMotionX = x;
3828 mMotionY = y;
3829 mMotionPosition = motionPosition;
3830 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003831 }
3832
Alan Viveretteb339cc52013-08-12 13:29:15 -07003833 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
Mady Mellor0d85d2a2015-06-16 17:08:27 -07003834 && performButtonActionOnTouchDown(ev)) {
Mady Mellore5561982015-04-14 15:06:40 -07003835 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003836 }
3837 }
3838
Adam Powell96d62af2014-05-02 10:04:38 -07003839 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003840 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3841 if (pointerIndex == -1) {
3842 pointerIndex = 0;
3843 mActivePointerId = ev.getPointerId(pointerIndex);
3844 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003845
3846 if (mDataChanged) {
3847 // Re-sync everything if data has been changed
3848 // since the scroll operation can query the adapter.
3849 layoutChildren();
3850 }
3851
Alan Viverette8fa327a2013-05-31 14:53:13 -07003852 final int y = (int) ev.getY(pointerIndex);
3853
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003854 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003855 case TOUCH_MODE_DOWN:
3856 case TOUCH_MODE_TAP:
3857 case TOUCH_MODE_DONE_WAITING:
3858 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003859 // scroll than a tap. If so, we'll enter scrolling mode.
Adam Powellc501db9f2014-05-08 12:50:10 -07003860 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003861 break;
3862 }
3863 // Otherwise, check containment within list bounds. If we're
3864 // outside bounds, cancel any active presses.
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003865 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003866 final float x = ev.getX(pointerIndex);
3867 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003868 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003869 if (motionView != null) {
3870 motionView.setPressed(false);
3871 }
3872 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3873 mPendingCheckForTap : mPendingCheckForLongPress);
3874 mTouchMode = TOUCH_MODE_DONE_WAITING;
3875 updateSelectorState();
Alan Viveretteb942b6f2014-12-08 10:37:39 -08003876 } else if (motionView != null) {
3877 // Still within bounds, update the hotspot.
3878 final float[] point = mTmpPoint;
3879 point[0] = x;
3880 point[1] = y;
3881 transformPointToViewLocal(point, motionView);
3882 motionView.drawableHotspotChanged(point[0], point[1]);
Alan Viverette74ded292013-06-03 15:34:11 -07003883 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003884 break;
3885 case TOUCH_MODE_SCROLL:
3886 case TOUCH_MODE_OVERSCROLL:
Adam Powellc501db9f2014-05-08 12:50:10 -07003887 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
Alan Viverette8fa327a2013-05-31 14:53:13 -07003888 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003889 }
3890 }
3891
3892 private void onTouchUp(MotionEvent ev) {
3893 switch (mTouchMode) {
3894 case TOUCH_MODE_DOWN:
3895 case TOUCH_MODE_TAP:
3896 case TOUCH_MODE_DONE_WAITING:
3897 final int motionPosition = mMotionPosition;
3898 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003899 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003900 if (mTouchMode != TOUCH_MODE_DOWN) {
3901 child.setPressed(false);
3902 }
3903
Alan Viverette74ded292013-06-03 15:34:11 -07003904 final float x = ev.getX();
3905 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3906 if (inList && !child.hasFocusable()) {
3907 if (mPerformClick == null) {
3908 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003909 }
Alan Viverette74ded292013-06-03 15:34:11 -07003910
3911 final AbsListView.PerformClick performClick = mPerformClick;
3912 performClick.mClickMotionPosition = motionPosition;
3913 performClick.rememberWindowAttachCount();
3914
3915 mResurrectToPosition = motionPosition;
3916
3917 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3918 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3919 mPendingCheckForTap : mPendingCheckForLongPress);
3920 mLayoutMode = LAYOUT_NORMAL;
3921 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3922 mTouchMode = TOUCH_MODE_TAP;
3923 setSelectedPositionInt(mMotionPosition);
3924 layoutChildren();
3925 child.setPressed(true);
3926 positionSelector(mMotionPosition, child);
3927 setPressed(true);
3928 if (mSelector != null) {
3929 Drawable d = mSelector.getCurrent();
3930 if (d != null && d instanceof TransitionDrawable) {
3931 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003932 }
Alan Viverettec80ad992014-05-19 15:46:17 -07003933 mSelector.setHotspot(x, ev.getY());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003934 }
Alan Viverette74ded292013-06-03 15:34:11 -07003935 if (mTouchModeReset != null) {
3936 removeCallbacks(mTouchModeReset);
3937 }
3938 mTouchModeReset = new Runnable() {
3939 @Override
3940 public void run() {
3941 mTouchModeReset = null;
3942 mTouchMode = TOUCH_MODE_REST;
3943 child.setPressed(false);
3944 setPressed(false);
Alan Viverette462c2172014-02-24 12:24:11 -08003945 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07003946 performClick.run();
3947 }
3948 }
3949 };
3950 postDelayed(mTouchModeReset,
3951 ViewConfiguration.getPressedStateDuration());
3952 } else {
3953 mTouchMode = TOUCH_MODE_REST;
3954 updateSelectorState();
3955 }
3956 return;
3957 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3958 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003959 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003960 }
3961 }
3962 mTouchMode = TOUCH_MODE_REST;
3963 updateSelectorState();
3964 break;
3965 case TOUCH_MODE_SCROLL:
3966 final int childCount = getChildCount();
3967 if (childCount > 0) {
3968 final int firstChildTop = getChildAt(0).getTop();
3969 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3970 final int contentTop = mListPadding.top;
3971 final int contentBottom = getHeight() - mListPadding.bottom;
3972 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
3973 mFirstPosition + childCount < mItemCount &&
3974 lastChildBottom <= getHeight() - contentBottom) {
3975 mTouchMode = TOUCH_MODE_REST;
3976 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3977 } else {
3978 final VelocityTracker velocityTracker = mVelocityTracker;
3979 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3980
3981 final int initialVelocity = (int)
3982 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
3983 // Fling if we have enough velocity and we aren't at a boundary.
3984 // Since we can potentially overfling more than we can overscroll, don't
3985 // allow the weird behavior where you can scroll to a boundary then
3986 // fling further.
Adam Powellaab726c2014-07-07 15:10:54 -07003987 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
3988 if (flingVelocity &&
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003989 !((mFirstPosition == 0 &&
3990 firstChildTop == contentTop - mOverscrollDistance) ||
3991 (mFirstPosition + childCount == mItemCount &&
3992 lastChildBottom == contentBottom + mOverscrollDistance))) {
Adam Powell9413b242014-08-06 17:34:24 -07003993 if (!dispatchNestedPreFling(0, -initialVelocity)) {
3994 if (mFlingRunnable == null) {
3995 mFlingRunnable = new FlingRunnable();
3996 }
3997 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3998 mFlingRunnable.start(-initialVelocity);
3999 dispatchNestedFling(0, -initialVelocity, true);
4000 } else {
4001 mTouchMode = TOUCH_MODE_REST;
4002 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004003 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004004 } else {
4005 mTouchMode = TOUCH_MODE_REST;
4006 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4007 if (mFlingRunnable != null) {
4008 mFlingRunnable.endFling();
4009 }
4010 if (mPositionScroller != null) {
4011 mPositionScroller.stop();
4012 }
Adam Powellfd1e93d2014-09-07 16:52:22 -07004013 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
Adam Powellaab726c2014-07-07 15:10:54 -07004014 dispatchNestedFling(0, -initialVelocity, false);
4015 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004016 }
4017 }
4018 } else {
4019 mTouchMode = TOUCH_MODE_REST;
4020 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4021 }
4022 break;
4023
4024 case TOUCH_MODE_OVERSCROLL:
4025 if (mFlingRunnable == null) {
4026 mFlingRunnable = new FlingRunnable();
4027 }
4028 final VelocityTracker velocityTracker = mVelocityTracker;
4029 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4030 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4031
4032 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4033 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4034 mFlingRunnable.startOverfling(-initialVelocity);
4035 } else {
4036 mFlingRunnable.startSpringback();
4037 }
4038
4039 break;
4040 }
4041
4042 setPressed(false);
4043
4044 if (mEdgeGlowTop != null) {
4045 mEdgeGlowTop.onRelease();
4046 mEdgeGlowBottom.onRelease();
4047 }
4048
4049 // Need to redraw since we probably aren't drawing the selector anymore
4050 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07004051 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004052 recycleVelocityTracker();
4053
4054 mActivePointerId = INVALID_POINTER;
4055
4056 if (PROFILE_SCROLLING) {
4057 if (mScrollProfilingStarted) {
4058 Debug.stopMethodTracing();
4059 mScrollProfilingStarted = false;
4060 }
4061 }
4062
4063 if (mScrollStrictSpan != null) {
4064 mScrollStrictSpan.finish();
4065 mScrollStrictSpan = null;
4066 }
4067 }
4068
4069 private void onTouchCancel() {
4070 switch (mTouchMode) {
4071 case TOUCH_MODE_OVERSCROLL:
4072 if (mFlingRunnable == null) {
4073 mFlingRunnable = new FlingRunnable();
4074 }
4075 mFlingRunnable.startSpringback();
4076 break;
4077
4078 case TOUCH_MODE_OVERFLING:
4079 // Do nothing - let it play out.
4080 break;
4081
4082 default:
4083 mTouchMode = TOUCH_MODE_REST;
4084 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07004085 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004086 if (motionView != null) {
4087 motionView.setPressed(false);
4088 }
4089 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07004090 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07004091 recycleVelocityTracker();
4092 }
4093
4094 if (mEdgeGlowTop != null) {
4095 mEdgeGlowTop.onRelease();
4096 mEdgeGlowBottom.onRelease();
4097 }
4098 mActivePointerId = INVALID_POINTER;
4099 }
4100
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004101 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004102 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4103 if (mScrollY != scrollY) {
4104 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4105 mScrollY = scrollY;
4106 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07004107
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004108 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07004109 }
Adam Powell637d3372010-08-25 14:37:03 -07004110 }
4111
4112 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08004113 public boolean onGenericMotionEvent(MotionEvent event) {
4114 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
4115 switch (event.getAction()) {
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004116 case MotionEvent.ACTION_SCROLL:
Jeff Brown33bbfd22011-02-24 20:55:35 -08004117 if (mTouchMode == TOUCH_MODE_REST) {
4118 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4119 if (vscroll != 0) {
4120 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08004121 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08004122 return true;
4123 }
4124 }
4125 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004126 break;
4127
4128 case MotionEvent.ACTION_BUTTON_PRESS:
4129 int actionButton = event.getActionButton();
4130 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4131 || actionButton == MotionEvent.BUTTON_SECONDARY)
4132 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4133 if (performStylusButtonPressAction(event)) {
4134 removeCallbacks(mPendingCheckForLongPress);
4135 removeCallbacks(mPendingCheckForTap);
4136 }
4137 }
4138 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -08004139 }
4140 }
Mady Mellor0d85d2a2015-06-16 17:08:27 -07004141
Jeff Brown33bbfd22011-02-24 20:55:35 -08004142 return super.onGenericMotionEvent(event);
4143 }
4144
Adam Powell4884c642014-08-07 13:52:53 -07004145 /**
4146 * Initiate a fling with the given velocity.
4147 *
4148 * <p>Applications can use this method to manually initiate a fling as if the user
4149 * initiated it via touch interaction.</p>
4150 *
Adam Powellfd1e93d2014-09-07 16:52:22 -07004151 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4152 * content, not velocity of a touch that initiated the fling.
Adam Powell4884c642014-08-07 13:52:53 -07004153 */
4154 public void fling(int velocityY) {
4155 if (mFlingRunnable == null) {
4156 mFlingRunnable = new FlingRunnable();
4157 }
4158 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powellfd1e93d2014-09-07 16:52:22 -07004159 mFlingRunnable.start(velocityY);
Adam Powell4884c642014-08-07 13:52:53 -07004160 }
4161
Jeff Brown33bbfd22011-02-24 20:55:35 -08004162 @Override
Adam Powell96d62af2014-05-02 10:04:38 -07004163 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4164 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4165 }
4166
4167 @Override
4168 public void onNestedScrollAccepted(View child, View target, int axes) {
4169 super.onNestedScrollAccepted(child, target, axes);
4170 startNestedScroll(SCROLL_AXIS_VERTICAL);
4171 }
4172
4173 @Override
4174 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4175 int dxUnconsumed, int dyUnconsumed) {
4176 final int motionIndex = getChildCount() / 2;
4177 final View motionView = getChildAt(motionIndex);
4178 final int oldTop = motionView != null ? motionView.getTop() : 0;
4179 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4180 int myUnconsumed = dyUnconsumed;
4181 int myConsumed = 0;
4182 if (motionView != null) {
4183 myConsumed = motionView.getTop() - oldTop;
4184 myUnconsumed -= myConsumed;
4185 }
4186 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4187 }
4188 }
4189
4190 @Override
4191 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4192 final int childCount = getChildCount();
4193 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4194 Math.abs(velocityY) > mMinimumVelocity) {
4195 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4196 if (mFlingRunnable == null) {
4197 mFlingRunnable = new FlingRunnable();
4198 }
Adam Powell9413b242014-08-06 17:34:24 -07004199 if (!dispatchNestedPreFling(0, velocityY)) {
4200 mFlingRunnable.start((int) velocityY);
4201 }
Adam Powell96d62af2014-05-02 10:04:38 -07004202 return true;
4203 }
4204 return dispatchNestedFling(velocityX, velocityY, consumed);
4205 }
4206
4207 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004208 public void draw(Canvas canvas) {
4209 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07004210 if (mEdgeGlowTop != null) {
4211 final int scrollY = mScrollY;
Doris Liuf36c0612015-06-04 11:11:14 -07004212 final boolean clipToPadding = getClipToPadding();
4213 final int width;
4214 final int height;
4215 final int translateX;
4216 final int translateY;
4217
4218 if (clipToPadding) {
4219 width = getWidth() - mPaddingLeft - mPaddingRight;
4220 height = getHeight() - mPaddingTop - mPaddingBottom;
4221 translateX = mPaddingLeft;
4222 translateY = mPaddingTop;
4223 } else {
4224 width = getWidth();
4225 height = getHeight();
4226 translateX = 0;
4227 translateY = 0;
4228 }
Adam Powell637d3372010-08-25 14:37:03 -07004229 if (!mEdgeGlowTop.isFinished()) {
4230 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004231 canvas.clipRect(translateX, translateY,
4232 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4233 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4234 canvas.translate(translateX, edgeY);
4235 mEdgeGlowTop.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004236 if (mEdgeGlowTop.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004237 invalidateTopGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004238 }
4239 canvas.restoreToCount(restoreCount);
4240 }
4241 if (!mEdgeGlowBottom.isFinished()) {
4242 final int restoreCount = canvas.save();
Doris Liuf36c0612015-06-04 11:11:14 -07004243 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4244 translateX + width, translateY + height);
4245 final int edgeX = -width + translateX;
4246 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4247 - (clipToPadding ? mPaddingBottom : 0);
Romain Guy9d849a22012-03-14 16:41:42 -07004248 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08004249 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08004250 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07004251 if (mEdgeGlowBottom.draw(canvas)) {
Doris Liuf36c0612015-06-04 11:11:14 -07004252 invalidateBottomGlow();
Adam Powell637d3372010-08-25 14:37:03 -07004253 }
4254 canvas.restoreToCount(restoreCount);
4255 }
4256 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004257 }
4258
Michael Jurka13451a42011-08-22 15:54:21 -07004259 private void initOrResetVelocityTracker() {
4260 if (mVelocityTracker == null) {
4261 mVelocityTracker = VelocityTracker.obtain();
4262 } else {
4263 mVelocityTracker.clear();
4264 }
4265 }
4266
4267 private void initVelocityTrackerIfNotExists() {
4268 if (mVelocityTracker == null) {
4269 mVelocityTracker = VelocityTracker.obtain();
4270 }
4271 }
4272
4273 private void recycleVelocityTracker() {
4274 if (mVelocityTracker != null) {
4275 mVelocityTracker.recycle();
4276 mVelocityTracker = null;
4277 }
4278 }
4279
4280 @Override
4281 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4282 if (disallowIntercept) {
4283 recycleVelocityTracker();
4284 }
4285 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4286 }
4287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004288 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07004289 public boolean onInterceptHoverEvent(MotionEvent event) {
Alan Viverette8636ace2013-10-31 15:41:31 -07004290 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
Alan Viverettea709b372013-07-25 14:43:24 -07004291 return true;
4292 }
4293
4294 return super.onInterceptHoverEvent(event);
4295 }
4296
4297 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004298 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Powell744beff2014-09-22 09:47:48 -07004299 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004300 View v;
Romain Guy0a637162009-05-29 14:43:54 -07004301
Adam Powell1fa179ef2012-04-12 15:01:40 -07004302 if (mPositionScroller != null) {
4303 mPositionScroller.stop();
4304 }
4305
Alan Viverette462c2172014-02-24 12:24:11 -08004306 if (mIsDetaching || !isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07004307 // Something isn't right.
4308 // Since we rely on being attached to get data set change notifications,
4309 // don't risk doing anything where we might try to resync and find things
4310 // in a bogus state.
4311 return false;
4312 }
4313
Alan Viverette8636ace2013-10-31 15:41:31 -07004314 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07004315 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004316 }
Romain Guy0a637162009-05-29 14:43:54 -07004317
Adam Powell744beff2014-09-22 09:47:48 -07004318 switch (actionMasked) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004319 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08004320 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07004321 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4322 mMotionCorrection = 0;
4323 return true;
4324 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004325
Adam Powell4cd47702010-02-25 11:21:14 -08004326 final int x = (int) ev.getX();
4327 final int y = (int) ev.getY();
4328 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08004329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004330 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08004331 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004332 // User clicked on an actual view (and was not stopping a fling).
4333 // Remember where the motion event started
4334 v = getChildAt(motionPosition - mFirstPosition);
4335 mMotionViewOriginalTop = v.getTop();
4336 mMotionX = x;
4337 mMotionY = y;
4338 mMotionPosition = motionPosition;
4339 mTouchMode = TOUCH_MODE_DOWN;
4340 clearScrollingCache();
4341 }
4342 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07004343 initOrResetVelocityTracker();
4344 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -07004345 mNestedYOffset = 0;
Adam Powell96d62af2014-05-02 10:04:38 -07004346 startNestedScroll(SCROLL_AXIS_VERTICAL);
Adam Powell79ac3392010-01-28 21:22:20 -08004347 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08004348 return true;
4349 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004350 break;
4351 }
4352
4353 case MotionEvent.ACTION_MOVE: {
4354 switch (mTouchMode) {
4355 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07004356 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4357 if (pointerIndex == -1) {
4358 pointerIndex = 0;
4359 mActivePointerId = ev.getPointerId(pointerIndex);
4360 }
Adam Powell4cd47702010-02-25 11:21:14 -08004361 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07004362 initVelocityTrackerIfNotExists();
4363 mVelocityTracker.addMovement(ev);
Adam Powellc501db9f2014-05-08 12:50:10 -07004364 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004365 return true;
4366 }
4367 break;
4368 }
4369 break;
4370 }
4371
Michael Jurka13451a42011-08-22 15:54:21 -07004372 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004373 case MotionEvent.ACTION_UP: {
4374 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08004375 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07004376 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004377 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell96d62af2014-05-02 10:04:38 -07004378 stopNestedScroll();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004379 break;
4380 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004381
Adam Powell4cd47702010-02-25 11:21:14 -08004382 case MotionEvent.ACTION_POINTER_UP: {
4383 onSecondaryPointerUp(ev);
4384 break;
4385 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004386 }
4387
4388 return false;
4389 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004390
Adam Powell4cd47702010-02-25 11:21:14 -08004391 private void onSecondaryPointerUp(MotionEvent ev) {
4392 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4393 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4394 final int pointerId = ev.getPointerId(pointerIndex);
4395 if (pointerId == mActivePointerId) {
4396 // This was our active pointer going up. Choose a new
4397 // active pointer and adjust accordingly.
4398 // TODO: Make this decision more intelligent.
4399 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4400 mMotionX = (int) ev.getX(newPointerIndex);
4401 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004402 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004403 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004404 }
4405 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004406
4407 /**
4408 * {@inheritDoc}
4409 */
4410 @Override
4411 public void addTouchables(ArrayList<View> views) {
4412 final int count = getChildCount();
4413 final int firstPosition = mFirstPosition;
4414 final ListAdapter adapter = mAdapter;
4415
4416 if (adapter == null) {
4417 return;
4418 }
4419
4420 for (int i = 0; i < count; i++) {
4421 final View child = getChildAt(i);
4422 if (adapter.isEnabled(firstPosition + i)) {
4423 views.add(child);
4424 }
4425 child.addTouchables(views);
4426 }
4427 }
4428
4429 /**
4430 * Fires an "on scroll state changed" event to the registered
4431 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4432 * is fired only if the specified state is different from the previously known state.
4433 *
4434 * @param newState The new scroll state.
4435 */
4436 void reportScrollStateChange(int newState) {
4437 if (newState != mLastScrollState) {
4438 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004439 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004440 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004441 }
4442 }
4443 }
4444
4445 /**
4446 * Responsible for fling behavior. Use {@link #start(int)} to
4447 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4448 * A FlingRunnable will keep re-posting itself until the fling is done.
4449 *
4450 */
4451 private class FlingRunnable implements Runnable {
4452 /**
4453 * Tracks the decay of a fling scroll
4454 */
Adam Powell637d3372010-08-25 14:37:03 -07004455 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004456
4457 /**
4458 * Y value reported by mScroller on the previous fling
4459 */
4460 private int mLastFlingY;
4461
Gilles Debunned348bb42010-11-15 12:19:35 -08004462 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004463 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004464 public void run() {
4465 final int activeId = mActivePointerId;
4466 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004467 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004468 if (vt == null || activeId == INVALID_POINTER) {
4469 return;
4470 }
4471
4472 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4473 final float yvel = -vt.getYVelocity(activeId);
4474
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004475 if (Math.abs(yvel) >= mMinimumVelocity
4476 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004477 // Keep the fling alive a little longer
4478 postDelayed(this, FLYWHEEL_TIMEOUT);
4479 } else {
4480 endFling();
4481 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004482 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004483 }
4484 }
4485 };
4486
4487 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4488
Adam Powell79ac3392010-01-28 21:22:20 -08004489 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004490 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004491 }
4492
Adam Powell79ac3392010-01-28 21:22:20 -08004493 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004494 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4495 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004496 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004497 mScroller.fling(0, initialY, 0, initialVelocity,
4498 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4499 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004500 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004501
4502 if (PROFILE_FLINGING) {
4503 if (!mFlingProfilingStarted) {
4504 Debug.startMethodTracing("AbsListViewFling");
4505 mFlingProfilingStarted = true;
4506 }
4507 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004508
4509 if (mFlingStrictSpan == null) {
4510 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4511 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004512 }
Adam Powell45803472010-01-25 15:10:44 -08004513
Adam Powell637d3372010-08-25 14:37:03 -07004514 void startSpringback() {
4515 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4516 mTouchMode = TOUCH_MODE_OVERFLING;
4517 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004518 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004519 } else {
4520 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004521 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004522 }
4523 }
4524
4525 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004526 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004527 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4528 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004529 mTouchMode = TOUCH_MODE_OVERFLING;
4530 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004531 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004532 }
4533
4534 void edgeReached(int delta) {
4535 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4536 final int overscrollMode = getOverScrollMode();
4537 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4538 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4539 mTouchMode = TOUCH_MODE_OVERFLING;
4540 final int vel = (int) mScroller.getCurrVelocity();
4541 if (delta > 0) {
4542 mEdgeGlowTop.onAbsorb(vel);
4543 } else {
4544 mEdgeGlowBottom.onAbsorb(vel);
4545 }
Adam Powell40322522011-01-12 21:58:20 -08004546 } else {
4547 mTouchMode = TOUCH_MODE_REST;
4548 if (mPositionScroller != null) {
4549 mPositionScroller.stop();
4550 }
Adam Powell637d3372010-08-25 14:37:03 -07004551 }
4552 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004553 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004554 }
4555
Adam Powell0b8acd82012-04-25 20:29:23 -07004556 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004557 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4558 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004559 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004560 mScroller.startScroll(0, initialY, 0, distance, duration);
4561 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004562 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004563 }
4564
Gilles Debunned348bb42010-11-15 12:19:35 -08004565 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004566 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004567
Adam Powell79ac3392010-01-28 21:22:20 -08004568 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004569 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004570
4571 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4572 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004573 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004574
4575 if (mFlingStrictSpan != null) {
4576 mFlingStrictSpan.finish();
4577 mFlingStrictSpan = null;
4578 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004579 }
4580
4581 void flywheelTouch() {
4582 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004583 }
4584
Alan Viverette8fa327a2013-05-31 14:53:13 -07004585 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004586 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004587 switch (mTouchMode) {
4588 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004589 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004590 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004591
Gilles Debunned348bb42010-11-15 12:19:35 -08004592 case TOUCH_MODE_SCROLL:
4593 if (mScroller.isFinished()) {
4594 return;
4595 }
4596 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004597 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004598 if (mDataChanged) {
4599 layoutChildren();
4600 }
4601
Adam Powell79ac3392010-01-28 21:22:20 -08004602 if (mItemCount == 0 || getChildCount() == 0) {
4603 endFling();
4604 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004605 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004606
Adam Powell637d3372010-08-25 14:37:03 -07004607 final OverScroller scroller = mScroller;
4608 boolean more = scroller.computeScrollOffset();
4609 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004610
Adam Powell637d3372010-08-25 14:37:03 -07004611 // Flip sign to convert finger direction to list items direction
4612 // (e.g. finger moving down means list is moving towards the top)
4613 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004614
Adam Powell637d3372010-08-25 14:37:03 -07004615 // Pretend that each frame of a fling scroll is a touch scroll
4616 if (delta > 0) {
4617 // List is moving towards the top. Use first view as mMotionPosition
4618 mMotionPosition = mFirstPosition;
4619 final View firstView = getChildAt(0);
4620 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004621
Adam Powell637d3372010-08-25 14:37:03 -07004622 // Don't fling more than 1 screen
4623 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4624 } else {
4625 // List is moving towards the bottom. Use last view as mMotionPosition
4626 int offsetToLast = getChildCount() - 1;
4627 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004628
Adam Powell637d3372010-08-25 14:37:03 -07004629 final View lastView = getChildAt(offsetToLast);
4630 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004631
Adam Powell637d3372010-08-25 14:37:03 -07004632 // Don't fling more than 1 screen
4633 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4634 }
Adam Powell79ac3392010-01-28 21:22:20 -08004635
Adam Powell637d3372010-08-25 14:37:03 -07004636 // Check to see if we have bumped into the scroll limit
4637 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4638 int oldTop = 0;
4639 if (motionView != null) {
4640 oldTop = motionView.getTop();
4641 }
Adam Powell9d32d242010-03-29 16:02:07 -07004642
Adam Powell637d3372010-08-25 14:37:03 -07004643 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004644 final boolean atEdge = trackMotionScroll(delta, delta);
4645 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004646 if (atEnd) {
4647 if (motionView != null) {
4648 // Tweak the scroll for how far we overshot
4649 int overshoot = -(delta - (motionView.getTop() - oldTop));
4650 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4651 0, mOverflingDistance, false);
4652 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004653 if (more) {
4654 edgeReached(delta);
4655 }
Adam Powell637d3372010-08-25 14:37:03 -07004656 break;
4657 }
4658
4659 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004660 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004661 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004662 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004663 } else {
4664 endFling();
4665
4666 if (PROFILE_FLINGING) {
4667 if (mFlingProfilingStarted) {
4668 Debug.stopMethodTracing();
4669 mFlingProfilingStarted = false;
4670 }
4671
4672 if (mFlingStrictSpan != null) {
4673 mFlingStrictSpan.finish();
4674 mFlingStrictSpan = null;
4675 }
Adam Powell79ac3392010-01-28 21:22:20 -08004676 }
4677 }
Adam Powell637d3372010-08-25 14:37:03 -07004678 break;
4679 }
4680
4681 case TOUCH_MODE_OVERFLING: {
4682 final OverScroller scroller = mScroller;
4683 if (scroller.computeScrollOffset()) {
4684 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004685 final int currY = scroller.getCurrY();
4686 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004687 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4688 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004689 final boolean crossDown = scrollY <= 0 && currY > 0;
4690 final boolean crossUp = scrollY >= 0 && currY < 0;
4691 if (crossDown || crossUp) {
4692 int velocity = (int) scroller.getCurrVelocity();
4693 if (crossUp) velocity = -velocity;
4694
4695 // Don't flywheel from this; we're just continuing things.
4696 scroller.abortAnimation();
4697 start(velocity);
4698 } else {
4699 startSpringback();
4700 }
Adam Powell637d3372010-08-25 14:37:03 -07004701 } else {
4702 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004703 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004704 }
4705 } else {
4706 endFling();
4707 }
4708 break;
4709 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004710 }
4711 }
4712 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004713
Adam Powell45803472010-01-25 15:10:44 -08004714 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004715 * The amount of friction applied to flings. The default value
4716 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004717 */
4718 public void setFriction(float friction) {
4719 if (mFlingRunnable == null) {
4720 mFlingRunnable = new FlingRunnable();
4721 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004722 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004723 }
Romain Guy21317d12010-10-12 13:32:31 -07004724
4725 /**
4726 * Sets a scale factor for the fling velocity. The initial scale
4727 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004728 *
Romain Guy21317d12010-10-12 13:32:31 -07004729 * @param scale The scale factor to multiply the velocity by.
4730 */
4731 public void setVelocityScale(float scale) {
4732 mVelocityScale = scale;
4733 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004734
Romain Guy4bede9e2010-10-11 19:36:59 -07004735 /**
Alan Viveretted22db212014-02-13 17:47:38 -08004736 * Override this for better control over position scrolling.
4737 */
4738 AbsPositionScroller createPositionScroller() {
4739 return new PositionScroller();
4740 }
4741
4742 /**
Adam Powell45803472010-01-25 15:10:44 -08004743 * Smoothly scroll to the specified adapter position. The view will
4744 * scroll such that the indicated position is displayed.
4745 * @param position Scroll to this adapter position.
4746 */
4747 public void smoothScrollToPosition(int position) {
4748 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004749 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004750 }
4751 mPositionScroller.start(position);
4752 }
Erik322171b2010-10-13 15:46:00 -07004753
4754 /**
4755 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004756 * such that the indicated position is displayed <code>offset</code> pixels below
Erik322171b2010-10-13 15:46:00 -07004757 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4758 * the first or last item beyond the boundaries of the list) it will get as close
4759 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4760 *
4761 * @param position Position to scroll to
4762 * @param offset Desired distance in pixels of <code>position</code> from the top
4763 * of the view when scrolling is finished
4764 * @param duration Number of milliseconds to use for the scroll
4765 */
4766 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4767 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004768 mPositionScroller = createPositionScroller();
Erik322171b2010-10-13 15:46:00 -07004769 }
4770 mPositionScroller.startWithOffset(position, offset, duration);
4771 }
4772
Adam Powell45803472010-01-25 15:10:44 -08004773 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004774 * Smoothly scroll to the specified adapter position. The view will scroll
Alan Viverette441b4372014-02-12 13:30:20 -08004775 * such that the indicated position is displayed <code>offset</code> pixels below
Adam Powelle44afae2010-07-01 10:10:35 -07004776 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4777 * the first or last item beyond the boundaries of the list) it will get as close
4778 * as possible.
4779 *
4780 * @param position Position to scroll to
4781 * @param offset Desired distance in pixels of <code>position</code> from the top
4782 * of the view when scrolling is finished
4783 */
4784 public void smoothScrollToPositionFromTop(int position, int offset) {
4785 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004786 mPositionScroller = createPositionScroller();
Adam Powelle44afae2010-07-01 10:10:35 -07004787 }
Alan Viverette3b415e42014-10-22 16:19:57 -07004788 mPositionScroller.startWithOffset(position, offset);
Adam Powelle44afae2010-07-01 10:10:35 -07004789 }
4790
4791 /**
Adam Powell45803472010-01-25 15:10:44 -08004792 * Smoothly scroll to the specified adapter position. The view will
4793 * scroll such that the indicated position is displayed, but it will
4794 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004795 * view.
Alan Viverettecb23bce2014-02-27 16:33:06 -08004796 *
Adam Powell45803472010-01-25 15:10:44 -08004797 * @param position Scroll to this adapter position.
4798 * @param boundPosition Do not scroll if it would move this adapter
4799 * position out of view.
4800 */
4801 public void smoothScrollToPosition(int position, int boundPosition) {
4802 if (mPositionScroller == null) {
Alan Viveretted22db212014-02-13 17:47:38 -08004803 mPositionScroller = createPositionScroller();
Adam Powell45803472010-01-25 15:10:44 -08004804 }
4805 mPositionScroller.start(position, boundPosition);
4806 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004807
Adam Powell45803472010-01-25 15:10:44 -08004808 /**
4809 * Smoothly scroll by distance pixels over duration milliseconds.
4810 * @param distance Distance to scroll in pixels.
4811 * @param duration Duration of the scroll animation in milliseconds.
4812 */
4813 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004814 smoothScrollBy(distance, duration, false);
4815 }
4816
4817 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004818 if (mFlingRunnable == null) {
4819 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004820 }
Adam Powell40322522011-01-12 21:58:20 -08004821
Marc Blank299acb52010-10-21 11:03:53 -07004822 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004823 final int firstPos = mFirstPosition;
4824 final int childCount = getChildCount();
4825 final int lastPos = firstPos + childCount;
4826 final int topLimit = getPaddingTop();
4827 final int bottomLimit = getHeight() - getPaddingBottom();
4828
Adam Powell79303752011-01-13 22:06:49 -08004829 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004830 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004831 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004832 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4833 mFlingRunnable.endFling();
4834 if (mPositionScroller != null) {
4835 mPositionScroller.stop();
4836 }
4837 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004838 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004839 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004840 }
Adam Powell45803472010-01-25 15:10:44 -08004841 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004842
Winson Chung499cb9f2010-07-16 11:18:17 -07004843 /**
4844 * Allows RemoteViews to scroll relatively to a position.
4845 */
4846 void smoothScrollByOffset(int position) {
4847 int index = -1;
4848 if (position < 0) {
4849 index = getFirstVisiblePosition();
4850 } else if (position > 0) {
4851 index = getLastVisiblePosition();
4852 }
4853
4854 if (index > -1) {
4855 View child = getChildAt(index - getFirstVisiblePosition());
4856 if (child != null) {
4857 Rect visibleRect = new Rect();
4858 if (child.getGlobalVisibleRect(visibleRect)) {
4859 // the child is partially visible
4860 int childRectArea = child.getWidth() * child.getHeight();
4861 int visibleRectArea = visibleRect.width() * visibleRect.height();
4862 float visibleArea = (visibleRectArea / (float) childRectArea);
4863 final float visibleThreshold = 0.75f;
4864 if ((position < 0) && (visibleArea < visibleThreshold)) {
4865 // the top index is not perceivably visible so offset
4866 // to account for showing that top index as well
4867 ++index;
4868 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4869 // the bottom index is not perceivably visible so offset
4870 // to account for showing that bottom index as well
4871 --index;
4872 }
4873 }
4874 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4875 }
4876 }
4877 }
4878
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004879 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004880 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004881 setChildrenDrawnWithCacheEnabled(true);
4882 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004883 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004884 }
4885 }
4886
4887 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004888 if (!isHardwareAccelerated()) {
4889 if (mClearScrollingCache == null) {
4890 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004891 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07004892 public void run() {
4893 if (mCachingStarted) {
4894 mCachingStarted = mCachingActive = false;
4895 setChildrenDrawnWithCacheEnabled(false);
4896 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4897 setChildrenDrawingCacheEnabled(false);
4898 }
4899 if (!isAlwaysDrawnWithCacheEnabled()) {
4900 invalidate();
4901 }
Romain Guy6dfed242009-05-11 18:25:05 -07004902 }
4903 }
Romain Guy9d849a22012-03-14 16:41:42 -07004904 };
4905 }
4906 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004907 }
4908 }
4909
4910 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07004911 * Scrolls the list items within the view by a specified number of pixels.
4912 *
4913 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07004914 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07004915 */
Alan Viveretteba299062013-09-03 16:01:51 -07004916 public void scrollListBy(int y) {
4917 trackMotionScroll(-y, -y);
4918 }
4919
4920 /**
4921 * Check if the items in the list can be scrolled in a certain direction.
4922 *
4923 * @param direction Negative to check scrolling up, positive to check
4924 * scrolling down.
4925 * @return true if the list can be scrolled in the specified direction,
4926 * false otherwise.
4927 * @see #scrollListBy(int)
4928 */
4929 public boolean canScrollList(int direction) {
4930 final int childCount = getChildCount();
4931 if (childCount == 0) {
4932 return false;
4933 }
4934
4935 final int firstPosition = mFirstPosition;
4936 final Rect listPadding = mListPadding;
4937 if (direction > 0) {
4938 final int lastBottom = getChildAt(childCount - 1).getBottom();
4939 final int lastPosition = firstPosition + childCount;
4940 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
4941 } else {
4942 final int firstTop = getChildAt(0).getTop();
4943 return firstPosition > 0 || firstTop < listPadding.top;
4944 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07004945 }
4946
4947 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004948 * Track a motion scroll
4949 *
4950 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4951 * began. Positive numbers mean the user's finger is moving down the screen.
4952 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08004953 * @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 -08004954 */
Adam Powell45803472010-01-25 15:10:44 -08004955 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004956 final int childCount = getChildCount();
4957 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08004958 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004959 }
4960
4961 final int firstTop = getChildAt(0).getTop();
4962 final int lastBottom = getChildAt(childCount - 1).getBottom();
4963
4964 final Rect listPadding = mListPadding;
4965
Adam Powellbdccc2d2010-12-14 17:34:27 -08004966 // "effective padding" In this case is the amount of padding that affects
4967 // how much space should not be filled by items. If we don't clip to padding
4968 // there is no effective padding.
4969 int effectivePaddingTop = 0;
4970 int effectivePaddingBottom = 0;
4971 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4972 effectivePaddingTop = listPadding.top;
4973 effectivePaddingBottom = listPadding.bottom;
4974 }
4975
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004976 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08004977 final int spaceAbove = effectivePaddingTop - firstTop;
4978 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004979 final int spaceBelow = lastBottom - end;
4980
4981 final int height = getHeight() - mPaddingBottom - mPaddingTop;
4982 if (deltaY < 0) {
4983 deltaY = Math.max(-(height - 1), deltaY);
4984 } else {
4985 deltaY = Math.min(height - 1, deltaY);
4986 }
4987
4988 if (incrementalDeltaY < 0) {
4989 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4990 } else {
4991 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4992 }
4993
Adam Powell45803472010-01-25 15:10:44 -08004994 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004995
Adam Powell637d3372010-08-25 14:37:03 -07004996 // Update our guesses for where the first and last views are
4997 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004998 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07004999 } else {
5000 mFirstPositionDistanceGuess += incrementalDeltaY;
5001 }
5002 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005003 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005004 } else {
5005 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005006 }
Adam Powell45803472010-01-25 15:10:44 -08005007
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005008 final boolean cannotScrollDown = (firstPosition == 0 &&
5009 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5010 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5011 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005012
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005013 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005014 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005015 }
5016
5017 final boolean down = incrementalDeltaY < 0;
5018
Adam Powell029cfbd2010-03-08 19:03:54 -08005019 final boolean inTouchMode = isInTouchMode();
5020 if (inTouchMode) {
5021 hideSelector();
5022 }
Adam Powell45803472010-01-25 15:10:44 -08005023
5024 final int headerViewsCount = getHeaderViewsCount();
5025 final int footerViewsStart = mItemCount - getFooterViewsCount();
5026
5027 int start = 0;
5028 int count = 0;
5029
5030 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005031 int top = -incrementalDeltaY;
5032 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5033 top += listPadding.top;
5034 }
Adam Powell45803472010-01-25 15:10:44 -08005035 for (int i = 0; i < childCount; i++) {
5036 final View child = getChildAt(i);
5037 if (child.getBottom() >= top) {
5038 break;
5039 } else {
5040 count++;
5041 int position = firstPosition + i;
5042 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005043 // The view will be rebound to new data, clear any
5044 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005045 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005046 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005047 }
5048 }
5049 }
5050 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005051 int bottom = getHeight() - incrementalDeltaY;
5052 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5053 bottom -= listPadding.bottom;
5054 }
Adam Powell45803472010-01-25 15:10:44 -08005055 for (int i = childCount - 1; i >= 0; i--) {
5056 final View child = getChildAt(i);
5057 if (child.getTop() <= bottom) {
5058 break;
5059 } else {
5060 start = i;
5061 count++;
5062 int position = firstPosition + i;
5063 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005064 // The view will be rebound to new data, clear any
5065 // system-managed transient state.
Alan Viverette632af842014-10-28 13:45:11 -07005066 child.clearAccessibilityFocus();
Dianne Hackborn079e2352010-10-18 17:02:43 -07005067 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005068 }
5069 }
5070 }
5071 }
5072
5073 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5074
5075 mBlockLayoutRequests = true;
5076
5077 if (count > 0) {
5078 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005079 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005080 }
Adam Powell539ee872012-02-03 19:00:49 -08005081
Romain Guy9d849a22012-03-14 16:41:42 -07005082 // invalidate before moving the children to avoid unnecessary invalidate
5083 // calls to bubble up from the children all the way to the top
5084 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005085 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005086 }
5087
Adam Powell45803472010-01-25 15:10:44 -08005088 offsetChildrenTopAndBottom(incrementalDeltaY);
5089
5090 if (down) {
5091 mFirstPosition += count;
5092 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005093
Adam Powell45803472010-01-25 15:10:44 -08005094 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5095 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5096 fillGap(down);
5097 }
5098
Adam Powell029cfbd2010-03-08 19:03:54 -08005099 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005100 final int childIndex = mSelectedPosition - mFirstPosition;
5101 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005102 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08005103 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005104 } else if (mSelectorPosition != INVALID_POSITION) {
5105 final int childIndex = mSelectorPosition - mFirstPosition;
5106 if (childIndex >= 0 && childIndex < getChildCount()) {
5107 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5108 }
5109 } else {
5110 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005111 }
5112
Adam Powell45803472010-01-25 15:10:44 -08005113 mBlockLayoutRequests = false;
5114
5115 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005116
Adam Powell45803472010-01-25 15:10:44 -08005117 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005118 }
5119
5120 /**
5121 * Returns the number of header views in the list. Header views are special views
5122 * at the top of the list that should not be recycled during a layout.
5123 *
5124 * @return The number of header views, 0 in the default implementation.
5125 */
5126 int getHeaderViewsCount() {
5127 return 0;
5128 }
5129
5130 /**
5131 * Returns the number of footer views in the list. Footer views are special views
5132 * at the bottom of the list that should not be recycled during a layout.
5133 *
5134 * @return The number of footer views, 0 in the default implementation.
5135 */
5136 int getFooterViewsCount() {
5137 return 0;
5138 }
5139
5140 /**
5141 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5142 * remain on screen are shifted and the other ones are discarded. The role of this
5143 * method is to fill the gap thus created by performing a partial layout in the
5144 * empty space.
5145 *
5146 * @param down true if the scroll is going down, false if it is going up
5147 */
5148 abstract void fillGap(boolean down);
5149
5150 void hideSelector() {
5151 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005152 if (mLayoutMode != LAYOUT_SPECIFIC) {
5153 mResurrectToPosition = mSelectedPosition;
5154 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005155 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5156 mResurrectToPosition = mNextSelectedPosition;
5157 }
5158 setSelectedPositionInt(INVALID_POSITION);
5159 setNextSelectedPositionInt(INVALID_POSITION);
5160 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005161 }
5162 }
5163
5164 /**
5165 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5166 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5167 * of items available in the adapter
5168 */
5169 int reconcileSelectedPosition() {
5170 int position = mSelectedPosition;
5171 if (position < 0) {
5172 position = mResurrectToPosition;
5173 }
5174 position = Math.max(0, position);
5175 position = Math.min(position, mItemCount - 1);
5176 return position;
5177 }
5178
5179 /**
5180 * Find the row closest to y. This row will be used as the motion row when scrolling
5181 *
5182 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005183 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005184 */
5185 abstract int findMotionRow(int y);
5186
5187 /**
Adam Powell637d3372010-08-25 14:37:03 -07005188 * Find the row closest to y. This row will be used as the motion row when scrolling.
5189 *
5190 * @param y Where the user touched
5191 * @return The position of the first (or only) item in the row closest to y
5192 */
5193 int findClosestMotionRow(int y) {
5194 final int childCount = getChildCount();
5195 if (childCount == 0) {
5196 return INVALID_POSITION;
5197 }
5198
5199 final int motionRow = findMotionRow(y);
5200 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5201 }
5202
5203 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005204 * Causes all the views to be rebuilt and redrawn.
5205 */
5206 public void invalidateViews() {
5207 mDataChanged = true;
5208 rememberSyncState();
5209 requestLayout();
5210 invalidate();
5211 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005212
Jeff Brown4e6319b2010-12-13 10:36:51 -08005213 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005214 * If there is a selection returns false.
5215 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005216 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005217 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005218 if (mSelectedPosition < 0 && resurrectSelection()) {
5219 updateSelectorState();
5220 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005221 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005222 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005223 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005224
5225 /**
5226 * Makes the item at the supplied position selected.
5227 *
5228 * @param position the position of the new selection
5229 */
5230 abstract void setSelectionInt(int position);
5231
5232 /**
5233 * Attempt to bring the selection back if the user is switching from touch
5234 * to trackball mode
5235 * @return Whether selection was set to something.
5236 */
5237 boolean resurrectSelection() {
5238 final int childCount = getChildCount();
5239
5240 if (childCount <= 0) {
5241 return false;
5242 }
5243
5244 int selectedTop = 0;
5245 int selectedPos;
5246 int childrenTop = mListPadding.top;
5247 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5248 final int firstPosition = mFirstPosition;
5249 final int toPosition = mResurrectToPosition;
5250 boolean down = true;
5251
5252 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5253 selectedPos = toPosition;
5254
5255 final View selected = getChildAt(selectedPos - mFirstPosition);
5256 selectedTop = selected.getTop();
5257 int selectedBottom = selected.getBottom();
5258
5259 // We are scrolled, don't get in the fade
5260 if (selectedTop < childrenTop) {
5261 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5262 } else if (selectedBottom > childrenBottom) {
5263 selectedTop = childrenBottom - selected.getMeasuredHeight()
5264 - getVerticalFadingEdgeLength();
5265 }
5266 } else {
5267 if (toPosition < firstPosition) {
5268 // Default to selecting whatever is first
5269 selectedPos = firstPosition;
5270 for (int i = 0; i < childCount; i++) {
5271 final View v = getChildAt(i);
5272 final int top = v.getTop();
5273
5274 if (i == 0) {
5275 // Remember the position of the first item
5276 selectedTop = top;
5277 // See if we are scrolled at all
5278 if (firstPosition > 0 || top < childrenTop) {
5279 // If we are scrolled, don't select anything that is
5280 // in the fade region
5281 childrenTop += getVerticalFadingEdgeLength();
5282 }
5283 }
5284 if (top >= childrenTop) {
5285 // Found a view whose top is fully visisble
5286 selectedPos = firstPosition + i;
5287 selectedTop = top;
5288 break;
5289 }
5290 }
5291 } else {
5292 final int itemCount = mItemCount;
5293 down = false;
5294 selectedPos = firstPosition + childCount - 1;
5295
5296 for (int i = childCount - 1; i >= 0; i--) {
5297 final View v = getChildAt(i);
5298 final int top = v.getTop();
5299 final int bottom = v.getBottom();
5300
5301 if (i == childCount - 1) {
5302 selectedTop = top;
5303 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5304 childrenBottom -= getVerticalFadingEdgeLength();
5305 }
5306 }
5307
5308 if (bottom <= childrenBottom) {
5309 selectedPos = firstPosition + i;
5310 selectedTop = top;
5311 break;
5312 }
5313 }
5314 }
5315 }
5316
5317 mResurrectToPosition = INVALID_POSITION;
5318 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005319 if (mPositionScroller != null) {
5320 mPositionScroller.stop();
5321 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005322 mTouchMode = TOUCH_MODE_REST;
5323 clearScrollingCache();
5324 mSpecificTop = selectedTop;
5325 selectedPos = lookForSelectablePosition(selectedPos, down);
5326 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5327 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005328 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005329 setSelectionInt(selectedPos);
5330 invokeOnItemScrollListener();
5331 } else {
5332 selectedPos = INVALID_POSITION;
5333 }
5334 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5335
5336 return selectedPos >= 0;
5337 }
5338
Adam Powell14c08042011-10-06 19:46:18 -07005339 void confirmCheckedPositionsById() {
5340 // Clear out the positional check states, we'll rebuild it below from IDs.
5341 mCheckStates.clear();
5342
5343 boolean checkedCountChanged = false;
5344 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5345 final long id = mCheckedIdStates.keyAt(checkedIndex);
5346 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5347
5348 final long lastPosId = mAdapter.getItemId(lastPos);
5349 if (id != lastPosId) {
5350 // Look around to see if the ID is nearby. If not, uncheck it.
5351 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5352 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5353 boolean found = false;
5354 for (int searchPos = start; searchPos < end; searchPos++) {
5355 final long searchId = mAdapter.getItemId(searchPos);
5356 if (id == searchId) {
5357 found = true;
5358 mCheckStates.put(searchPos, true);
5359 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5360 break;
5361 }
5362 }
5363
5364 if (!found) {
5365 mCheckedIdStates.delete(id);
5366 checkedIndex--;
5367 mCheckedItemCount--;
5368 checkedCountChanged = true;
5369 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5370 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5371 lastPos, id, false);
5372 }
5373 }
5374 } else {
5375 mCheckStates.put(lastPos, true);
5376 }
5377 }
5378
5379 if (checkedCountChanged && mChoiceActionMode != null) {
5380 mChoiceActionMode.invalidate();
5381 }
5382 }
5383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005384 @Override
5385 protected void handleDataChanged() {
5386 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005387 int lastHandledItemCount = mLastHandledItemCount;
5388 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005389
5390 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5391 confirmCheckedPositionsById();
5392 }
5393
Adam Powell539ee872012-02-03 19:00:49 -08005394 // TODO: In the future we can recycle these views based on stable ID instead.
5395 mRecycler.clearTransientStateViews();
5396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005397 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005398 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005399 int selectablePos;
5400
5401 // Find the row we are supposed to sync to
5402 if (mNeedSync) {
5403 // Update this first, since setNextSelectedPositionInt inspects it
5404 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005405 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005406
Adam Powell07852792010-11-10 16:57:05 -08005407 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005408 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5409 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005410 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5411 if (mForceTranscriptScroll) {
5412 mForceTranscriptScroll = false;
5413 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5414 return;
5415 }
Adam Powell07852792010-11-10 16:57:05 -08005416 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005417 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005418 final View lastChild = getChildAt(childCount - 1);
5419 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005420 if (mFirstPosition + childCount >= lastHandledItemCount &&
5421 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005422 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5423 return;
5424 }
5425 // Something new came in and we didn't scroll; give the user a clue that
5426 // there's something new.
5427 awakenScrollBars();
5428 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005429
5430 switch (mSyncMode) {
5431 case SYNC_SELECTED_POSITION:
5432 if (isInTouchMode()) {
5433 // We saved our state when not in touch mode. (We know this because
5434 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5435 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5436 // adjusting if the available range changed) and return.
5437 mLayoutMode = LAYOUT_SYNC;
5438 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5439
5440 return;
5441 } else {
5442 // See if we can find a position in the new data with the same
5443 // id as the old selection. This will change mSyncPosition.
5444 newPos = findSyncPosition();
5445 if (newPos >= 0) {
5446 // Found it. Now verify that new selection is still selectable
5447 selectablePos = lookForSelectablePosition(newPos, true);
5448 if (selectablePos == newPos) {
5449 // Same row id is selected
5450 mSyncPosition = newPos;
5451
5452 if (mSyncHeight == getHeight()) {
5453 // If we are at the same height as when we saved state, try
5454 // to restore the scroll position too.
5455 mLayoutMode = LAYOUT_SYNC;
5456 } else {
5457 // We are not the same height as when the selection was saved, so
5458 // don't try to restore the exact position
5459 mLayoutMode = LAYOUT_SET_SELECTION;
5460 }
5461
5462 // Restore selection
5463 setNextSelectedPositionInt(newPos);
5464 return;
5465 }
5466 }
5467 }
5468 break;
5469 case SYNC_FIRST_POSITION:
5470 // Leave mSyncPosition as it is -- just pin to available range
5471 mLayoutMode = LAYOUT_SYNC;
5472 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5473
5474 return;
5475 }
5476 }
5477
5478 if (!isInTouchMode()) {
5479 // We couldn't find matching data -- try to use the same position
5480 newPos = getSelectedItemPosition();
5481
5482 // Pin position to the available range
5483 if (newPos >= count) {
5484 newPos = count - 1;
5485 }
5486 if (newPos < 0) {
5487 newPos = 0;
5488 }
5489
5490 // Make sure we select something selectable -- first look down
5491 selectablePos = lookForSelectablePosition(newPos, true);
5492
5493 if (selectablePos >= 0) {
5494 setNextSelectedPositionInt(selectablePos);
5495 return;
5496 } else {
5497 // Looking down didn't work -- try looking up
5498 selectablePos = lookForSelectablePosition(newPos, false);
5499 if (selectablePos >= 0) {
5500 setNextSelectedPositionInt(selectablePos);
5501 return;
5502 }
5503 }
5504 } else {
5505
5506 // We already know where we want to resurrect the selection
5507 if (mResurrectToPosition >= 0) {
5508 return;
5509 }
5510 }
5511
5512 }
5513
5514 // Nothing is selected. Give up and reset everything.
5515 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5516 mSelectedPosition = INVALID_POSITION;
5517 mSelectedRowId = INVALID_ROW_ID;
5518 mNextSelectedPosition = INVALID_POSITION;
5519 mNextSelectedRowId = INVALID_ROW_ID;
5520 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005521 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005522 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005523 checkSelectionChanged();
5524 }
5525
Romain Guy43c9cdf2010-01-27 13:53:55 -08005526 @Override
5527 protected void onDisplayHint(int hint) {
5528 super.onDisplayHint(hint);
5529 switch (hint) {
5530 case INVISIBLE:
5531 if (mPopup != null && mPopup.isShowing()) {
5532 dismissPopup();
5533 }
5534 break;
5535 case VISIBLE:
5536 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5537 showPopup();
5538 }
5539 break;
5540 }
Romain Guy24562482010-02-01 14:56:19 -08005541 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005542 }
5543
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005544 /**
5545 * Removes the filter window
5546 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005547 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005548 if (mPopup != null) {
5549 mPopup.dismiss();
5550 }
5551 }
5552
5553 /**
5554 * Shows the filter window
5555 */
5556 private void showPopup() {
5557 // Make sure we have a window before showing the popup
5558 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005559 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005560 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005561 // Make sure we get focus if we are showing the popup
5562 checkFocus();
5563 }
5564 }
5565
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005566 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005567 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5568 final int[] xy = new int[2];
5569 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005570 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005571 // TODO: And the gravity should be defined in the theme as well
5572 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005573 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005574 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5575 xy[0], bottomGap);
5576 } else {
5577 mPopup.update(xy[0], bottomGap, -1, -1);
5578 }
5579 }
5580
5581 /**
5582 * What is the distance between the source and destination rectangles given the direction of
5583 * focus navigation between them? The direction basically helps figure out more quickly what is
5584 * self evident by the relationship between the rects...
5585 *
5586 * @param source the source rectangle
5587 * @param dest the destination rectangle
5588 * @param direction the direction
5589 * @return the distance between the rectangles
5590 */
5591 static int getDistance(Rect source, Rect dest, int direction) {
5592 int sX, sY; // source x, y
5593 int dX, dY; // dest x, y
5594 switch (direction) {
5595 case View.FOCUS_RIGHT:
5596 sX = source.right;
5597 sY = source.top + source.height() / 2;
5598 dX = dest.left;
5599 dY = dest.top + dest.height() / 2;
5600 break;
5601 case View.FOCUS_DOWN:
5602 sX = source.left + source.width() / 2;
5603 sY = source.bottom;
5604 dX = dest.left + dest.width() / 2;
5605 dY = dest.top;
5606 break;
5607 case View.FOCUS_LEFT:
5608 sX = source.left;
5609 sY = source.top + source.height() / 2;
5610 dX = dest.right;
5611 dY = dest.top + dest.height() / 2;
5612 break;
5613 case View.FOCUS_UP:
5614 sX = source.left + source.width() / 2;
5615 sY = source.top;
5616 dX = dest.left + dest.width() / 2;
5617 dY = dest.bottom;
5618 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005619 case View.FOCUS_FORWARD:
5620 case View.FOCUS_BACKWARD:
5621 sX = source.right + source.width() / 2;
5622 sY = source.top + source.height() / 2;
5623 dX = dest.left + dest.width() / 2;
5624 dY = dest.top + dest.height() / 2;
5625 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005626 default:
5627 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005628 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5629 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005630 }
5631 int deltaX = dX - sX;
5632 int deltaY = dY - sY;
5633 return deltaY * deltaY + deltaX * deltaX;
5634 }
5635
5636 @Override
5637 protected boolean isInFilterMode() {
5638 return mFiltered;
5639 }
5640
5641 /**
5642 * Sends a key to the text filter window
5643 *
5644 * @param keyCode The keycode for the event
5645 * @param event The actual key event
5646 *
5647 * @return True if the text filter handled the event, false otherwise.
5648 */
5649 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005650 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005651 return false;
5652 }
5653
5654 boolean handled = false;
5655 boolean okToSend = true;
5656 switch (keyCode) {
5657 case KeyEvent.KEYCODE_DPAD_UP:
5658 case KeyEvent.KEYCODE_DPAD_DOWN:
5659 case KeyEvent.KEYCODE_DPAD_LEFT:
5660 case KeyEvent.KEYCODE_DPAD_RIGHT:
5661 case KeyEvent.KEYCODE_DPAD_CENTER:
5662 case KeyEvent.KEYCODE_ENTER:
5663 okToSend = false;
5664 break;
5665 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005666 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005667 if (event.getAction() == KeyEvent.ACTION_DOWN
5668 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005669 KeyEvent.DispatcherState state = getKeyDispatcherState();
5670 if (state != null) {
5671 state.startTracking(event, this);
5672 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005673 handled = true;
5674 } else if (event.getAction() == KeyEvent.ACTION_UP
5675 && event.isTracking() && !event.isCanceled()) {
5676 handled = true;
5677 mTextFilter.setText("");
5678 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005679 }
5680 okToSend = false;
5681 break;
5682 case KeyEvent.KEYCODE_SPACE:
5683 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005684 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005685 break;
5686 }
5687
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005688 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005689 createTextFilter(true);
5690
5691 KeyEvent forwardEvent = event;
5692 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005693 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005694 }
5695
5696 int action = event.getAction();
5697 switch (action) {
5698 case KeyEvent.ACTION_DOWN:
5699 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5700 break;
5701
5702 case KeyEvent.ACTION_UP:
5703 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5704 break;
5705
5706 case KeyEvent.ACTION_MULTIPLE:
5707 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5708 break;
5709 }
5710 }
5711 return handled;
5712 }
5713
5714 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005715 * Return an InputConnection for editing of the filter text.
5716 */
5717 @Override
5718 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005719 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005720 if (mPublicInputConnection == null) {
5721 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005722 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005723 }
Romain Guyf6991302013-06-05 17:19:01 -07005724 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005725 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5726 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005727 }
5728 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005729 }
Romain Guy0a637162009-05-29 14:43:54 -07005730
Romain Guyf6991302013-06-05 17:19:01 -07005731 private class InputConnectionWrapper implements InputConnection {
5732 private final EditorInfo mOutAttrs;
5733 private InputConnection mTarget;
5734
5735 public InputConnectionWrapper(EditorInfo outAttrs) {
5736 mOutAttrs = outAttrs;
5737 }
5738
5739 private InputConnection getTarget() {
5740 if (mTarget == null) {
5741 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5742 }
5743 return mTarget;
5744 }
5745
5746 @Override
5747 public boolean reportFullscreenMode(boolean enabled) {
5748 // Use our own input connection, since it is
5749 // the "real" one the IME is talking with.
5750 return mDefInputConnection.reportFullscreenMode(enabled);
5751 }
5752
5753 @Override
5754 public boolean performEditorAction(int editorAction) {
5755 // The editor is off in its own window; we need to be
5756 // the one that does this.
5757 if (editorAction == EditorInfo.IME_ACTION_DONE) {
Yohei Yukawa777ef952015-11-25 20:32:24 -08005758 InputMethodManager imm =
5759 getContext().getSystemService(InputMethodManager.class);
Romain Guyf6991302013-06-05 17:19:01 -07005760 if (imm != null) {
5761 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5762 }
5763 return true;
5764 }
5765 return false;
5766 }
5767
5768 @Override
5769 public boolean sendKeyEvent(KeyEvent event) {
5770 // Use our own input connection, since the filter
5771 // text view may not be shown in a window so has
5772 // no ViewAncestor to dispatch events with.
5773 return mDefInputConnection.sendKeyEvent(event);
5774 }
5775
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005776 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005777 public CharSequence getTextBeforeCursor(int n, int flags) {
5778 if (mTarget == null) return "";
5779 return mTarget.getTextBeforeCursor(n, flags);
5780 }
5781
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005782 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005783 public CharSequence getTextAfterCursor(int n, int flags) {
5784 if (mTarget == null) return "";
5785 return mTarget.getTextAfterCursor(n, flags);
5786 }
5787
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005788 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005789 public CharSequence getSelectedText(int flags) {
5790 if (mTarget == null) return "";
5791 return mTarget.getSelectedText(flags);
5792 }
5793
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005794 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005795 public int getCursorCapsMode(int reqModes) {
5796 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5797 return mTarget.getCursorCapsMode(reqModes);
5798 }
5799
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005800 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005801 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5802 return getTarget().getExtractedText(request, flags);
5803 }
5804
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005805 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005806 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5807 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5808 }
5809
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005810 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005811 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5812 return getTarget().setComposingText(text, newCursorPosition);
5813 }
5814
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005815 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005816 public boolean setComposingRegion(int start, int end) {
5817 return getTarget().setComposingRegion(start, end);
5818 }
5819
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005820 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005821 public boolean finishComposingText() {
5822 return mTarget == null || mTarget.finishComposingText();
5823 }
5824
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005825 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005826 public boolean commitText(CharSequence text, int newCursorPosition) {
5827 return getTarget().commitText(text, newCursorPosition);
5828 }
5829
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005830 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005831 public boolean commitCompletion(CompletionInfo text) {
5832 return getTarget().commitCompletion(text);
5833 }
5834
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005835 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005836 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5837 return getTarget().commitCorrection(correctionInfo);
5838 }
5839
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005840 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005841 public boolean setSelection(int start, int end) {
5842 return getTarget().setSelection(start, end);
5843 }
5844
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005845 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005846 public boolean performContextMenuAction(int id) {
5847 return getTarget().performContextMenuAction(id);
5848 }
5849
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005850 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005851 public boolean beginBatchEdit() {
5852 return getTarget().beginBatchEdit();
5853 }
5854
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005855 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005856 public boolean endBatchEdit() {
5857 return getTarget().endBatchEdit();
5858 }
5859
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005860 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005861 public boolean clearMetaKeyStates(int states) {
5862 return getTarget().clearMetaKeyStates(states);
5863 }
5864
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005865 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005866 public boolean performPrivateCommand(String action, Bundle data) {
5867 return getTarget().performPrivateCommand(action, data);
5868 }
Yohei Yukawa0023d0e2014-07-11 04:13:03 +09005869
5870 @Override
Yohei Yukawad8636ea2014-09-02 22:03:30 -07005871 public boolean requestCursorUpdates(int cursorUpdateMode) {
5872 return getTarget().requestCursorUpdates(cursorUpdateMode);
5873 }
Romain Guyf6991302013-06-05 17:19:01 -07005874 }
5875
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005876 /**
5877 * For filtering we proxy an input connection to an internal text editor,
5878 * and this allows the proxying to happen.
5879 */
5880 @Override
5881 public boolean checkInputConnectionProxy(View view) {
5882 return view == mTextFilter;
5883 }
Romain Guy0a637162009-05-29 14:43:54 -07005884
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005885 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005886 * Creates the window for the text filter and populates it with an EditText field;
5887 *
5888 * @param animateEntrance true if the window should appear with an animation
5889 */
5890 private void createTextFilter(boolean animateEntrance) {
5891 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07005892 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005893 p.setFocusable(false);
5894 p.setTouchable(false);
5895 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07005896 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005897 p.setWidth(LayoutParams.WRAP_CONTENT);
5898 p.setHeight(LayoutParams.WRAP_CONTENT);
5899 p.setBackgroundDrawable(null);
5900 mPopup = p;
5901 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005902 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005903 }
5904 if (animateEntrance) {
5905 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5906 } else {
5907 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5908 }
5909 }
5910
Romain Guyf6991302013-06-05 17:19:01 -07005911 private EditText getTextFilterInput() {
5912 if (mTextFilter == null) {
5913 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
5914 mTextFilter = (EditText) layoutInflater.inflate(
5915 com.android.internal.R.layout.typing_filter, null);
5916 // For some reason setting this as the "real" input type changes
5917 // the text view in some way that it doesn't work, and I don't
5918 // want to figure out why this is.
5919 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5920 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5921 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5922 mTextFilter.addTextChangedListener(this);
5923 }
5924 return mTextFilter;
5925 }
5926
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005927 /**
5928 * Clear the text filter.
5929 */
5930 public void clearTextFilter() {
5931 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07005932 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005933 mFiltered = false;
5934 if (mPopup != null && mPopup.isShowing()) {
5935 dismissPopup();
5936 }
5937 }
5938 }
5939
5940 /**
5941 * Returns if the ListView currently has a text filter.
5942 */
5943 public boolean hasTextFilter() {
5944 return mFiltered;
5945 }
5946
Alan Viverette8fa327a2013-05-31 14:53:13 -07005947 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005948 public void onGlobalLayout() {
5949 if (isShown()) {
5950 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08005951 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005952 showPopup();
5953 }
5954 } else {
5955 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08005956 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005957 dismissPopup();
5958 }
5959 }
5960
5961 }
5962
5963 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005964 * For our text watcher that is associated with the text filter. Does
5965 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005966 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07005967 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005968 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5969 }
5970
5971 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005972 * For our text watcher that is associated with the text filter. Performs
5973 * the actual filtering as the text changes, and takes care of hiding and
5974 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005975 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07005976 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005977 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07005978 if (isTextFilterEnabled()) {
5979 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005980 int length = s.length();
5981 boolean showing = mPopup.isShowing();
5982 if (!showing && length > 0) {
5983 // Show the filter popup if necessary
5984 showPopup();
5985 mFiltered = true;
5986 } else if (showing && length == 0) {
5987 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005988 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005989 mFiltered = false;
5990 }
5991 if (mAdapter instanceof Filterable) {
5992 Filter f = ((Filterable) mAdapter).getFilter();
5993 // Filter should not be null when we reach this part
5994 if (f != null) {
5995 f.filter(s, this);
5996 } else {
5997 throw new IllegalStateException("You cannot call onTextChanged with a non "
5998 + "filterable adapter");
5999 }
6000 }
6001 }
6002 }
6003
6004 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006005 * For our text watcher that is associated with the text filter. Does
6006 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006007 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006008 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006009 public void afterTextChanged(Editable s) {
6010 }
6011
Alan Viverette8fa327a2013-05-31 14:53:13 -07006012 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006013 public void onFilterComplete(int count) {
6014 if (mSelectedPosition < 0 && count > 0) {
6015 mResurrectToPosition = INVALID_POSITION;
6016 resurrectSelection();
6017 }
6018 }
6019
6020 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006021 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6022 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6023 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6024 }
6025
6026 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006027 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6028 return new LayoutParams(p);
6029 }
6030
6031 @Override
6032 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6033 return new AbsListView.LayoutParams(getContext(), attrs);
6034 }
6035
6036 @Override
6037 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6038 return p instanceof AbsListView.LayoutParams;
6039 }
6040
6041 /**
6042 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6043 * to the bottom to show new items.
6044 *
6045 * @param mode the transcript mode to set
6046 *
6047 * @see #TRANSCRIPT_MODE_DISABLED
6048 * @see #TRANSCRIPT_MODE_NORMAL
6049 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6050 */
6051 public void setTranscriptMode(int mode) {
6052 mTranscriptMode = mode;
6053 }
6054
6055 /**
6056 * Returns the current transcript mode.
6057 *
6058 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6059 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6060 */
6061 public int getTranscriptMode() {
6062 return mTranscriptMode;
6063 }
6064
6065 @Override
6066 public int getSolidColor() {
6067 return mCacheColorHint;
6068 }
6069
6070 /**
6071 * 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 -07006072 * on top of a solid, single-color, opaque background.
6073 *
6074 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6075 * single color. This hint will not affect any existing background drawable set on this view (
6076 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006077 *
6078 * @param color The background color
6079 */
Tor Norbye80756e32015-03-02 09:39:27 -08006080 public void setCacheColorHint(@ColorInt int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006081 if (color != mCacheColorHint) {
6082 mCacheColorHint = color;
6083 int count = getChildCount();
6084 for (int i = 0; i < count; i++) {
6085 getChildAt(i).setDrawingCacheBackgroundColor(color);
6086 }
6087 mRecycler.setCacheColorHint(color);
6088 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006089 }
6090
6091 /**
6092 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6093 * on top of a solid, single-color, opaque background
6094 *
6095 * @return The cache color hint
6096 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006097 @ViewDebug.ExportedProperty(category = "drawing")
Tor Norbye80756e32015-03-02 09:39:27 -08006098 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006099 public int getCacheColorHint() {
6100 return mCacheColorHint;
6101 }
6102
6103 /**
6104 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6105 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6106 * internal view recycler.
6107 *
6108 * @param views A list into which to put the reclaimed views
6109 */
6110 public void reclaimViews(List<View> views) {
6111 int childCount = getChildCount();
6112 RecyclerListener listener = mRecycler.mRecyclerListener;
6113
6114 // Reclaim views on screen
6115 for (int i = 0; i < childCount; i++) {
6116 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006117 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006118 // Don't reclaim header or footer views, or views that should be ignored
6119 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6120 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006121 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006122 if (listener != null) {
6123 // Pretend they went through the scrap heap
6124 listener.onMovedToScrapHeap(child);
6125 }
6126 }
6127 }
6128 mRecycler.reclaimScrapViews(views);
6129 removeAllViewsInLayout();
6130 }
6131
Adam Powell637d3372010-08-25 14:37:03 -07006132 private void finishGlows() {
6133 if (mEdgeGlowTop != null) {
6134 mEdgeGlowTop.finish();
6135 mEdgeGlowBottom.finish();
6136 }
6137 }
6138
Romain Guy13922e02009-05-12 17:56:14 -07006139 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006140 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6141 * through the specified intent.
6142 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6143 */
6144 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006145 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6146 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006147 if (mRemoteAdapter != null) {
6148 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6149 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6150 mRemoteAdapter.getRemoteViewsServiceIntent());
6151 if (fcNew.equals(fcOld)) {
6152 return;
6153 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006154 }
Adam Cohen2148d432011-07-28 14:59:54 -07006155 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006156 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07006157 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07006158 if (mRemoteAdapter.isDataReady()) {
6159 setAdapter(mRemoteAdapter);
6160 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006161 }
6162
6163 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006164 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006165 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006166 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006167 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006168 * @hide
6169 */
6170 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6171 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6172 // service handling the specified intent.
6173 if (mRemoteAdapter != null) {
6174 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6175 }
6176 }
6177
6178 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006179 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6180 * connected yet.
6181 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006182 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006183 public void deferNotifyDataSetChanged() {
6184 mDeferNotifyDataSetChanged = true;
6185 }
6186
6187 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006188 * Called back when the adapter connects to the RemoteViewsService.
6189 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006190 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006191 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006192 if (mRemoteAdapter != mAdapter) {
6193 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006194 if (mDeferNotifyDataSetChanged) {
6195 mRemoteAdapter.notifyDataSetChanged();
6196 mDeferNotifyDataSetChanged = false;
6197 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006198 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006199 } else if (mRemoteAdapter != null) {
6200 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006201 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006202 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006203 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006204 }
6205
6206 /**
6207 * Called back when the adapter disconnects from the RemoteViewsService.
6208 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006209 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006210 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006211 // If the remote adapter disconnects, we keep it around
6212 // since the currently displayed items are still cached.
6213 // Further, we want the service to eventually reconnect
6214 // when necessary, as triggered by this view requesting
6215 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006216 }
6217
6218 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006219 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6220 * being displayed by the AbsListView.
6221 */
6222 void setVisibleRangeHint(int start, int end) {
6223 if (mRemoteAdapter != null) {
6224 mRemoteAdapter.setVisibleRangeHint(start, end);
6225 }
6226 }
6227
6228 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006229 * Sets the recycler listener to be notified whenever a View is set aside in
6230 * the recycler for later reuse. This listener can be used to free resources
6231 * associated to the View.
6232 *
6233 * @param listener The recycler listener to be notified of views set aside
6234 * in the recycler.
6235 *
6236 * @see android.widget.AbsListView.RecycleBin
6237 * @see android.widget.AbsListView.RecyclerListener
6238 */
6239 public void setRecyclerListener(RecyclerListener listener) {
6240 mRecycler.mRecyclerListener = listener;
6241 }
6242
Adam Powellb1f498a2011-01-18 20:43:23 -08006243 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6244 @Override
6245 public void onChanged() {
6246 super.onChanged();
Alan Viverette8636ace2013-10-31 15:41:31 -07006247 if (mFastScroll != null) {
6248 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006249 }
6250 }
6251
6252 @Override
6253 public void onInvalidated() {
6254 super.onInvalidated();
Alan Viverette8636ace2013-10-31 15:41:31 -07006255 if (mFastScroll != null) {
6256 mFastScroll.onSectionsChanged();
Adam Powellb1f498a2011-01-18 20:43:23 -08006257 }
6258 }
6259 }
6260
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006261 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006262 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6263 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6264 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6265 * selects and deselects list items.
6266 */
6267 public interface MultiChoiceModeListener extends ActionMode.Callback {
6268 /**
6269 * Called when an item is checked or unchecked during selection mode.
6270 *
6271 * @param mode The {@link ActionMode} providing the selection mode
6272 * @param position Adapter position of the item that was checked or unchecked
6273 * @param id Adapter ID of the item that was checked or unchecked
6274 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6275 * if the item is now unchecked.
6276 */
6277 public void onItemCheckedStateChanged(ActionMode mode,
6278 int position, long id, boolean checked);
6279 }
6280
6281 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6282 private MultiChoiceModeListener mWrapped;
6283
6284 public void setWrapped(MultiChoiceModeListener wrapped) {
6285 mWrapped = wrapped;
6286 }
6287
Adam Powella7981702012-08-24 12:43:41 -07006288 public boolean hasWrappedCallback() {
6289 return mWrapped != null;
6290 }
6291
Alan Viverette8fa327a2013-05-31 14:53:13 -07006292 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006293 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6294 if (mWrapped.onCreateActionMode(mode, menu)) {
6295 // Initialize checked graphic state?
6296 setLongClickable(false);
6297 return true;
6298 }
6299 return false;
6300 }
6301
Alan Viverette8fa327a2013-05-31 14:53:13 -07006302 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006303 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6304 return mWrapped.onPrepareActionMode(mode, menu);
6305 }
6306
Alan Viverette8fa327a2013-05-31 14:53:13 -07006307 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006308 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6309 return mWrapped.onActionItemClicked(mode, item);
6310 }
6311
Alan Viverette8fa327a2013-05-31 14:53:13 -07006312 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006313 public void onDestroyActionMode(ActionMode mode) {
6314 mWrapped.onDestroyActionMode(mode);
6315 mChoiceActionMode = null;
6316
6317 // Ending selection mode means deselecting everything.
6318 clearChoices();
6319
6320 mDataChanged = true;
6321 rememberSyncState();
6322 requestLayout();
6323
6324 setLongClickable(true);
6325 }
6326
Alan Viverette8fa327a2013-05-31 14:53:13 -07006327 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006328 public void onItemCheckedStateChanged(ActionMode mode,
6329 int position, long id, boolean checked) {
6330 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6331
6332 // If there are no items selected we no longer need the selection mode.
6333 if (getCheckedItemCount() == 0) {
6334 mode.finish();
6335 }
6336 }
6337 }
6338
6339 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006340 * AbsListView extends LayoutParams to provide a place to hold the view type.
6341 */
6342 public static class LayoutParams extends ViewGroup.LayoutParams {
6343 /**
6344 * View type for this view, as returned by
6345 * {@link android.widget.Adapter#getItemViewType(int) }
6346 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006347 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006348 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6349 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6350 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006351 int viewType;
6352
The Android Open Source Project4df24232009-03-05 14:34:35 -08006353 /**
6354 * When this boolean is set, the view has been added to the AbsListView
6355 * at least once. It is used to know whether headers/footers have already
6356 * been added to the list view and whether they should be treated as
6357 * recycled views or not.
6358 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006359 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006360 boolean recycledHeaderFooter;
6361
Romain Guy0bf88592010-03-02 13:38:44 -08006362 /**
6363 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6364 * to obtain children views to measure itself. When doing so, the children
6365 * are not attached to the window, but put in the recycler which assumes
6366 * they've been attached before. Setting this flag will force the reused
6367 * view to be attached to the window rather than just attached to the
6368 * parent.
6369 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006370 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006371 boolean forceAdd;
6372
Dianne Hackborn079e2352010-10-18 17:02:43 -07006373 /**
6374 * The position the view was removed from when pulled out of the
6375 * scrap heap.
6376 * @hide
6377 */
6378 int scrappedFromPosition;
6379
Adam Powell539ee872012-02-03 19:00:49 -08006380 /**
6381 * The ID the view represents
6382 */
6383 long itemId = -1;
6384
Alan Viverette92539d52015-09-14 10:49:25 -04006385 /** Whether the adapter considers the item enabled. */
6386 boolean isEnabled;
6387
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006388 public LayoutParams(Context c, AttributeSet attrs) {
6389 super(c, attrs);
6390 }
6391
6392 public LayoutParams(int w, int h) {
6393 super(w, h);
6394 }
6395
6396 public LayoutParams(int w, int h, int viewType) {
6397 super(w, h);
6398 this.viewType = viewType;
6399 }
6400
6401 public LayoutParams(ViewGroup.LayoutParams source) {
6402 super(source);
6403 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07006404
6405 /** @hide */
6406 @Override
6407 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6408 super.encodeProperties(encoder);
6409
6410 encoder.addProperty("list:viewType", viewType);
6411 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6412 encoder.addProperty("list:forceAdd", forceAdd);
Alan Viverette92539d52015-09-14 10:49:25 -04006413 encoder.addProperty("list:isEnabled", isEnabled);
Siva Velusamy94a6d152015-05-05 15:07:00 -07006414 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006415 }
6416
6417 /**
6418 * A RecyclerListener is used to receive a notification whenever a View is placed
6419 * inside the RecycleBin's scrap heap. This listener is used to free resources
6420 * associated to Views placed in the RecycleBin.
6421 *
6422 * @see android.widget.AbsListView.RecycleBin
6423 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6424 */
6425 public static interface RecyclerListener {
6426 /**
6427 * Indicates that the specified View was moved into the recycler's scrap heap.
6428 * The view is not displayed on screen any more and any expensive resource
6429 * associated with the view should be discarded.
6430 *
6431 * @param view
6432 */
6433 void onMovedToScrapHeap(View view);
6434 }
6435
6436 /**
6437 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6438 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6439 * start of a layout. By construction, they are displaying current information. At the end of
6440 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6441 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6442 *
6443 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6444 * @see android.widget.AbsListView.RecyclerListener
6445 */
6446 class RecycleBin {
6447 private RecyclerListener mRecyclerListener;
6448
6449 /**
6450 * The position of the first view stored in mActiveViews.
6451 */
6452 private int mFirstActivePosition;
6453
6454 /**
6455 * Views that were on screen at the start of layout. This array is populated at the start of
6456 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6457 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6458 * view store in mFirstActivePosition.
6459 */
6460 private View[] mActiveViews = new View[0];
6461
6462 /**
6463 * Unsorted views that can be used by the adapter as a convert view.
6464 */
6465 private ArrayList<View>[] mScrapViews;
6466
6467 private int mViewTypeCount;
6468
6469 private ArrayList<View> mCurrentScrap;
6470
Adam Powell539ee872012-02-03 19:00:49 -08006471 private ArrayList<View> mSkippedScrap;
6472
6473 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006474 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006475
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006476 public void setViewTypeCount(int viewTypeCount) {
6477 if (viewTypeCount < 1) {
6478 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6479 }
6480 //noinspection unchecked
6481 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6482 for (int i = 0; i < viewTypeCount; i++) {
6483 scrapViews[i] = new ArrayList<View>();
6484 }
6485 mViewTypeCount = viewTypeCount;
6486 mCurrentScrap = scrapViews[0];
6487 mScrapViews = scrapViews;
6488 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006489
Adam Powellf3c2eda2010-03-16 17:31:01 -07006490 public void markChildrenDirty() {
6491 if (mViewTypeCount == 1) {
6492 final ArrayList<View> scrap = mCurrentScrap;
6493 final int scrapCount = scrap.size();
6494 for (int i = 0; i < scrapCount; i++) {
6495 scrap.get(i).forceLayout();
6496 }
6497 } else {
6498 final int typeCount = mViewTypeCount;
6499 for (int i = 0; i < typeCount; i++) {
6500 final ArrayList<View> scrap = mScrapViews[i];
6501 final int scrapCount = scrap.size();
6502 for (int j = 0; j < scrapCount; j++) {
6503 scrap.get(j).forceLayout();
6504 }
6505 }
6506 }
Adam Powell539ee872012-02-03 19:00:49 -08006507 if (mTransientStateViews != null) {
6508 final int count = mTransientStateViews.size();
6509 for (int i = 0; i < count; i++) {
6510 mTransientStateViews.valueAt(i).forceLayout();
6511 }
6512 }
Chet Haase72871322013-02-26 16:12:13 -07006513 if (mTransientStateViewsById != null) {
6514 final int count = mTransientStateViewsById.size();
6515 for (int i = 0; i < count; i++) {
6516 mTransientStateViewsById.valueAt(i).forceLayout();
6517 }
6518 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006519 }
Romain Guy0a637162009-05-29 14:43:54 -07006520
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006521 public boolean shouldRecycleViewType(int viewType) {
6522 return viewType >= 0;
6523 }
6524
6525 /**
6526 * Clears the scrap heap.
6527 */
6528 void clear() {
6529 if (mViewTypeCount == 1) {
6530 final ArrayList<View> scrap = mCurrentScrap;
Alan Viverette3e141622014-02-18 17:05:13 -08006531 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006532 } else {
6533 final int typeCount = mViewTypeCount;
6534 for (int i = 0; i < typeCount; i++) {
6535 final ArrayList<View> scrap = mScrapViews[i];
Alan Viverette3e141622014-02-18 17:05:13 -08006536 clearScrap(scrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006537 }
6538 }
Alan Viverette59511502013-12-09 13:49:25 -08006539
6540 clearTransientStateViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006541 }
6542
6543 /**
6544 * Fill ActiveViews with all of the children of the AbsListView.
6545 *
6546 * @param childCount The minimum number of views mActiveViews should hold
6547 * @param firstActivePosition The position of the first view that will be stored in
6548 * mActiveViews
6549 */
6550 void fillActiveViews(int childCount, int firstActivePosition) {
6551 if (mActiveViews.length < childCount) {
6552 mActiveViews = new View[childCount];
6553 }
6554 mFirstActivePosition = firstActivePosition;
6555
Romain Guyf6991302013-06-05 17:19:01 -07006556 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006557 final View[] activeViews = mActiveViews;
6558 for (int i = 0; i < childCount; i++) {
6559 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006560 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006561 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006562 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006563 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6564 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006565 activeViews[i] = child;
Alan Viveretteb942b6f2014-12-08 10:37:39 -08006566 // Remember the position so that setupChild() doesn't reset state.
6567 lp.scrappedFromPosition = firstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006568 }
6569 }
6570 }
6571
6572 /**
6573 * Get the view corresponding to the specified position. The view will be removed from
6574 * mActiveViews if it is found.
6575 *
6576 * @param position The position to look up in mActiveViews
6577 * @return The view if it is found, null otherwise
6578 */
6579 View getActiveView(int position) {
6580 int index = position - mFirstActivePosition;
6581 final View[] activeViews = mActiveViews;
6582 if (index >=0 && index < activeViews.length) {
6583 final View match = activeViews[index];
6584 activeViews[index] = null;
6585 return match;
6586 }
6587 return null;
6588 }
6589
Adam Powell539ee872012-02-03 19:00:49 -08006590 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006591 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6592 long id = mAdapter.getItemId(position);
6593 View result = mTransientStateViewsById.get(id);
6594 mTransientStateViewsById.remove(id);
6595 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006596 }
Chet Haase72871322013-02-26 16:12:13 -07006597 if (mTransientStateViews != null) {
6598 final int index = mTransientStateViews.indexOfKey(position);
6599 if (index >= 0) {
6600 View result = mTransientStateViews.valueAt(index);
6601 mTransientStateViews.removeAt(index);
6602 return result;
6603 }
Adam Powell539ee872012-02-03 19:00:49 -08006604 }
Chet Haase72871322013-02-26 16:12:13 -07006605 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006606 }
6607
6608 /**
Alan Viverette59511502013-12-09 13:49:25 -08006609 * Dumps and fully detaches any currently saved views with transient
6610 * state.
Adam Powell539ee872012-02-03 19:00:49 -08006611 */
6612 void clearTransientStateViews() {
Alan Viverette59511502013-12-09 13:49:25 -08006613 final SparseArray<View> viewsByPos = mTransientStateViews;
6614 if (viewsByPos != null) {
6615 final int N = viewsByPos.size();
6616 for (int i = 0; i < N; i++) {
6617 removeDetachedView(viewsByPos.valueAt(i), false);
6618 }
6619 viewsByPos.clear();
Adam Powell539ee872012-02-03 19:00:49 -08006620 }
Alan Viverette59511502013-12-09 13:49:25 -08006621
6622 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6623 if (viewsById != null) {
6624 final int N = viewsById.size();
6625 for (int i = 0; i < N; i++) {
6626 removeDetachedView(viewsById.valueAt(i), false);
6627 }
6628 viewsById.clear();
Chet Haase72871322013-02-26 16:12:13 -07006629 }
Adam Powell539ee872012-02-03 19:00:49 -08006630 }
6631
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006632 /**
6633 * @return A view from the ScrapViews collection. These are unordered.
6634 */
6635 View getScrapView(int position) {
Yigit Boyarf85e6732015-06-15 19:02:50 -07006636 final int whichScrap = mAdapter.getItemViewType(position);
6637 if (whichScrap < 0) {
6638 return null;
6639 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006640 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006641 return retrieveFromScrap(mCurrentScrap, position);
Yigit Boyarf85e6732015-06-15 19:02:50 -07006642 } else if (whichScrap < mScrapViews.length) {
6643 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006644 }
6645 return null;
6646 }
6647
6648 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006649 * Puts a view into the list of scrap views.
6650 * <p>
6651 * If the list data hasn't changed or the adapter has stable IDs, views
6652 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006653 *
6654 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006655 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006656 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006657 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006658 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006659 if (lp == null) {
Alan Viverette16381332015-07-07 11:04:32 -07006660 // Can't recycle, but we don't know anything about the view.
6661 // Ignore it completely.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006662 return;
6663 }
6664
Adam Powell539ee872012-02-03 19:00:49 -08006665 lp.scrappedFromPosition = position;
6666
Alan Viverette1e51cc72013-09-27 14:32:20 -07006667 // Remove but don't scrap header or footer views, or views that
6668 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006669 final int viewType = lp.viewType;
6670 if (!shouldRecycleViewType(viewType)) {
Alan Viverette16381332015-07-07 11:04:32 -07006671 // Can't recycle. If it's not a header or footer, which have
6672 // special handling and should be ignored, then skip the scrap
6673 // heap and we'll fully detach the view later.
6674 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6675 getSkippedScrap().add(scrap);
6676 }
Alan Viveretted44696c2013-07-18 10:37:15 -07006677 return;
6678 }
6679
6680 scrap.dispatchStartTemporaryDetach();
6681
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006682 // The the accessibility state of the view may change while temporary
6683 // detached and we do not allow detached views to fire accessibility
6684 // events. So we are announcing that the subtree changed giving a chance
6685 // to clients holding on to a view in this subtree to refresh it.
6686 notifyViewAccessibilityStateChangedIfNeeded(
6687 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6688
Alan Viveretted44696c2013-07-18 10:37:15 -07006689 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006690 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006691 if (scrapHasTransientState) {
6692 if (mAdapter != null && mAdapterHasStableIds) {
6693 // If the adapter has stable IDs, we can reuse the view for
6694 // the same data.
6695 if (mTransientStateViewsById == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006696 mTransientStateViewsById = new LongSparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006697 }
6698 mTransientStateViewsById.put(lp.itemId, scrap);
6699 } else if (!mDataChanged) {
6700 // If the data hasn't changed, we can reuse the views at
6701 // their old positions.
6702 if (mTransientStateViews == null) {
Alan Viverette8bbae342015-06-25 14:49:29 -07006703 mTransientStateViews = new SparseArray<>();
Alan Viveretted44696c2013-07-18 10:37:15 -07006704 }
6705 mTransientStateViews.put(position, scrap);
6706 } else {
6707 // Otherwise, we'll have to remove the view and start over.
Alan Viverette8bbae342015-06-25 14:49:29 -07006708 getSkippedScrap().add(scrap);
Adam Powell539ee872012-02-03 19:00:49 -08006709 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006710 } else {
Alan Viveretted44696c2013-07-18 10:37:15 -07006711 if (mViewTypeCount == 1) {
6712 mCurrentScrap.add(scrap);
6713 } else {
6714 mScrapViews[viewType].add(scrap);
6715 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006716
Alan Viveretted44696c2013-07-18 10:37:15 -07006717 if (mRecyclerListener != null) {
6718 mRecyclerListener.onMovedToScrapHeap(scrap);
6719 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006720 }
6721 }
6722
Alan Viverette8bbae342015-06-25 14:49:29 -07006723 private ArrayList<View> getSkippedScrap() {
6724 if (mSkippedScrap == null) {
6725 mSkippedScrap = new ArrayList<>();
6726 }
6727 return mSkippedScrap;
6728 }
6729
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006730 /**
Adam Powell539ee872012-02-03 19:00:49 -08006731 * Finish the removal of any views that skipped the scrap heap.
6732 */
6733 void removeSkippedScrap() {
6734 if (mSkippedScrap == null) {
6735 return;
6736 }
6737 final int count = mSkippedScrap.size();
6738 for (int i = 0; i < count; i++) {
6739 removeDetachedView(mSkippedScrap.get(i), false);
6740 }
6741 mSkippedScrap.clear();
6742 }
6743
6744 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006745 * Move all views remaining in mActiveViews to mScrapViews.
6746 */
6747 void scrapActiveViews() {
6748 final View[] activeViews = mActiveViews;
6749 final boolean hasListener = mRecyclerListener != null;
6750 final boolean multipleScraps = mViewTypeCount > 1;
6751
6752 ArrayList<View> scrapViews = mCurrentScrap;
6753 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006754 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006755 final View victim = activeViews[i];
6756 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006757 final AbsListView.LayoutParams lp
6758 = (AbsListView.LayoutParams) victim.getLayoutParams();
Alan Viverette59511502013-12-09 13:49:25 -08006759 final int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006760
6761 activeViews[i] = null;
6762
Alan Viverette59511502013-12-09 13:49:25 -08006763 if (victim.hasTransientState()) {
6764 // Store views with transient state for later use.
6765 victim.dispatchStartTemporaryDetach();
6766
6767 if (mAdapter != null && mAdapterHasStableIds) {
6768 if (mTransientStateViewsById == null) {
6769 mTransientStateViewsById = new LongSparseArray<View>();
6770 }
6771 long id = mAdapter.getItemId(mFirstActivePosition + i);
6772 mTransientStateViewsById.put(id, victim);
6773 } else if (!mDataChanged) {
6774 if (mTransientStateViews == null) {
6775 mTransientStateViews = new SparseArray<View>();
6776 }
6777 mTransientStateViews.put(mFirstActivePosition + i, victim);
6778 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6779 // The data has changed, we can't keep this view.
Romain Guy9b1bb812010-02-26 14:14:13 -08006780 removeDetachedView(victim, false);
6781 }
Alan Viverette59511502013-12-09 13:49:25 -08006782 } else if (!shouldRecycleViewType(whichScrap)) {
6783 // Discard non-recyclable views except headers/footers.
6784 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6785 removeDetachedView(victim, false);
Adam Powell539ee872012-02-03 19:00:49 -08006786 }
Alan Viverette59511502013-12-09 13:49:25 -08006787 } else {
6788 // Store everything else on the appropriate scrap heap.
6789 if (multipleScraps) {
6790 scrapViews = mScrapViews[whichScrap];
6791 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006792
Alan Viverette59511502013-12-09 13:49:25 -08006793 victim.dispatchStartTemporaryDetach();
6794 lp.scrappedFromPosition = mFirstActivePosition + i;
6795 scrapViews.add(victim);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006796
Alan Viverette59511502013-12-09 13:49:25 -08006797 if (hasListener) {
6798 mRecyclerListener.onMovedToScrapHeap(victim);
6799 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006800 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006801 }
6802 }
6803
6804 pruneScrapViews();
6805 }
6806
6807 /**
Alan Viverette59511502013-12-09 13:49:25 -08006808 * Makes sure that the size of mScrapViews does not exceed the size of
6809 * mActiveViews, which can happen if an adapter does not recycle its
6810 * views. Removes cached transient state views that no longer have
6811 * transient state.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006812 */
6813 private void pruneScrapViews() {
6814 final int maxViews = mActiveViews.length;
6815 final int viewTypeCount = mViewTypeCount;
6816 final ArrayList<View>[] scrapViews = mScrapViews;
6817 for (int i = 0; i < viewTypeCount; ++i) {
6818 final ArrayList<View> scrapPile = scrapViews[i];
6819 int size = scrapPile.size();
6820 final int extras = size - maxViews;
6821 size--;
6822 for (int j = 0; j < extras; j++) {
6823 removeDetachedView(scrapPile.remove(size--), false);
6824 }
6825 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006826
Alan Viverette59511502013-12-09 13:49:25 -08006827 final SparseArray<View> transViewsByPos = mTransientStateViews;
6828 if (transViewsByPos != null) {
6829 for (int i = 0; i < transViewsByPos.size(); i++) {
6830 final View v = transViewsByPos.valueAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07006831 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08006832 removeDetachedView(v, false);
6833 transViewsByPos.removeAt(i);
Adam Powellbf1b81f2012-05-07 18:14:10 -07006834 i--;
6835 }
6836 }
6837 }
Alan Viverette59511502013-12-09 13:49:25 -08006838
6839 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
6840 if (transViewsById != null) {
6841 for (int i = 0; i < transViewsById.size(); i++) {
6842 final View v = transViewsById.valueAt(i);
Chet Haase72871322013-02-26 16:12:13 -07006843 if (!v.hasTransientState()) {
Alan Viverette59511502013-12-09 13:49:25 -08006844 removeDetachedView(v, false);
6845 transViewsById.removeAt(i);
Chet Haase72871322013-02-26 16:12:13 -07006846 i--;
6847 }
6848 }
6849 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006850 }
6851
6852 /**
6853 * Puts all views in the scrap heap into the supplied list.
6854 */
6855 void reclaimScrapViews(List<View> views) {
6856 if (mViewTypeCount == 1) {
6857 views.addAll(mCurrentScrap);
6858 } else {
6859 final int viewTypeCount = mViewTypeCount;
6860 final ArrayList<View>[] scrapViews = mScrapViews;
6861 for (int i = 0; i < viewTypeCount; ++i) {
6862 final ArrayList<View> scrapPile = scrapViews[i];
6863 views.addAll(scrapPile);
6864 }
6865 }
6866 }
Romain Guy52e2ef82010-01-14 12:11:48 -08006867
6868 /**
6869 * Updates the cache color hint of all known views.
6870 *
6871 * @param color The new cache color hint.
6872 */
6873 void setCacheColorHint(int color) {
6874 if (mViewTypeCount == 1) {
6875 final ArrayList<View> scrap = mCurrentScrap;
6876 final int scrapCount = scrap.size();
6877 for (int i = 0; i < scrapCount; i++) {
6878 scrap.get(i).setDrawingCacheBackgroundColor(color);
6879 }
6880 } else {
6881 final int typeCount = mViewTypeCount;
6882 for (int i = 0; i < typeCount; i++) {
6883 final ArrayList<View> scrap = mScrapViews[i];
6884 final int scrapCount = scrap.size();
6885 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07006886 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08006887 }
6888 }
6889 }
6890 // Just in case this is called during a layout pass
6891 final View[] activeViews = mActiveViews;
6892 final int count = activeViews.length;
6893 for (int i = 0; i < count; ++i) {
6894 final View victim = activeViews[i];
6895 if (victim != null) {
6896 victim.setDrawingCacheBackgroundColor(color);
6897 }
6898 }
6899 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006900
Alan Viverette3e141622014-02-18 17:05:13 -08006901 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6902 final int size = scrapViews.size();
6903 if (size > 0) {
6904 // See if we still have a view for this position or ID.
6905 for (int i = 0; i < size; i++) {
6906 final View view = scrapViews.get(i);
6907 final AbsListView.LayoutParams params =
6908 (AbsListView.LayoutParams) view.getLayoutParams();
6909
6910 if (mAdapterHasStableIds) {
6911 final long id = mAdapter.getItemId(position);
6912 if (id == params.itemId) {
6913 return scrapViews.remove(i);
6914 }
6915 } else if (params.scrappedFromPosition == position) {
6916 final View scrap = scrapViews.remove(i);
6917 clearAccessibilityFromScrap(scrap);
6918 return scrap;
6919 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006920 }
Alan Viverette3e141622014-02-18 17:05:13 -08006921 final View scrap = scrapViews.remove(size - 1);
6922 clearAccessibilityFromScrap(scrap);
6923 return scrap;
6924 } else {
6925 return null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07006926 }
Alan Viverette3e141622014-02-18 17:05:13 -08006927 }
6928
6929 private void clearScrap(final ArrayList<View> scrap) {
6930 final int scrapCount = scrap.size();
6931 for (int j = 0; j < scrapCount; j++) {
6932 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6933 }
6934 }
6935
6936 private void clearAccessibilityFromScrap(View view) {
Alan Viverette632af842014-10-28 13:45:11 -07006937 view.clearAccessibilityFocus();
Alan Viverette3e141622014-02-18 17:05:13 -08006938 view.setAccessibilityDelegate(null);
6939 }
6940
6941 private void removeDetachedView(View child, boolean animate) {
6942 child.setAccessibilityDelegate(null);
6943 AbsListView.this.removeDetachedView(child, animate);
Dianne Hackborn079e2352010-10-18 17:02:43 -07006944 }
6945 }
Alan Viverette441b4372014-02-12 13:30:20 -08006946
6947 /**
Alan Viverette441b4372014-02-12 13:30:20 -08006948 * Returns the height of the view for the specified position.
6949 *
6950 * @param position the item position
6951 * @return view height in pixels
6952 */
6953 int getHeightForPosition(int position) {
6954 final int firstVisiblePosition = getFirstVisiblePosition();
6955 final int childCount = getChildCount();
6956 final int index = position - firstVisiblePosition;
Alan Viveretted22db212014-02-13 17:47:38 -08006957 if (index >= 0 && index < childCount) {
6958 // Position is on-screen, use existing view.
Alan Viverette441b4372014-02-12 13:30:20 -08006959 final View view = getChildAt(index);
6960 return view.getHeight();
6961 } else {
Alan Viveretted22db212014-02-13 17:47:38 -08006962 // Position is off-screen, obtain & recycle view.
Alan Viverette441b4372014-02-12 13:30:20 -08006963 final View view = obtainView(position, mIsScrap);
6964 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
6965 final int height = view.getMeasuredHeight();
6966 mRecycler.addScrapView(view, position);
6967 return height;
6968 }
6969 }
6970
6971 /**
Alan Viverette441b4372014-02-12 13:30:20 -08006972 * Sets the selected item and positions the selection y pixels from the top edge
6973 * of the ListView. (If in touch mode, the item will not be selected but it will
6974 * still be positioned appropriately.)
6975 *
6976 * @param position Index (starting at 0) of the data item to be selected.
6977 * @param y The distance from the top edge of the ListView (plus padding) that the
6978 * item will be positioned.
6979 */
6980 public void setSelectionFromTop(int position, int y) {
6981 if (mAdapter == null) {
6982 return;
6983 }
6984
6985 if (!isInTouchMode()) {
6986 position = lookForSelectablePosition(position, true);
6987 if (position >= 0) {
6988 setNextSelectedPositionInt(position);
6989 }
6990 } else {
6991 mResurrectToPosition = position;
6992 }
6993
6994 if (position >= 0) {
6995 mLayoutMode = LAYOUT_SPECIFIC;
6996 mSpecificTop = mListPadding.top + y;
6997
6998 if (mNeedSync) {
6999 mSyncPosition = position;
7000 mSyncRowId = mAdapter.getItemId(position);
7001 }
7002
7003 if (mPositionScroller != null) {
7004 mPositionScroller.stop();
7005 }
7006 requestLayout();
7007 }
7008 }
7009
Siva Velusamy94a6d152015-05-05 15:07:00 -07007010 /** @hide */
7011 @Override
7012 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7013 super.encodeProperties(encoder);
7014
7015 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7016 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7017 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7018 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7019 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7020 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7021
7022 View selectedView = getSelectedView();
7023 if (selectedView != null) {
7024 encoder.addPropertyKey("selectedView");
7025 selectedView.encode(encoder);
7026 }
7027 }
7028
Alan Viveretted22db212014-02-13 17:47:38 -08007029 /**
7030 * Abstract positon scroller used to handle smooth scrolling.
7031 */
7032 static abstract class AbsPositionScroller {
7033 public abstract void start(int position);
7034 public abstract void start(int position, int boundPosition);
7035 public abstract void startWithOffset(int position, int offset);
7036 public abstract void startWithOffset(int position, int offset, int duration);
7037 public abstract void stop();
7038 }
7039
7040 /**
7041 * Default position scroller that simulates a fling.
7042 */
7043 class PositionScroller extends AbsPositionScroller implements Runnable {
7044 private static final int SCROLL_DURATION = 200;
7045
7046 private static final int MOVE_DOWN_POS = 1;
7047 private static final int MOVE_UP_POS = 2;
7048 private static final int MOVE_DOWN_BOUND = 3;
7049 private static final int MOVE_UP_BOUND = 4;
7050 private static final int MOVE_OFFSET = 5;
7051
7052 private int mMode;
7053 private int mTargetPos;
7054 private int mBoundPos;
7055 private int mLastSeenPos;
7056 private int mScrollDuration;
7057 private final int mExtraScroll;
7058
7059 private int mOffsetFromTop;
7060
7061 PositionScroller() {
7062 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7063 }
7064
7065 @Override
7066 public void start(final int position) {
7067 stop();
7068
7069 if (mDataChanged) {
7070 // Wait until we're back in a stable state to try this.
7071 mPositionScrollAfterLayout = new Runnable() {
7072 @Override public void run() {
7073 start(position);
7074 }
7075 };
7076 return;
7077 }
7078
7079 final int childCount = getChildCount();
7080 if (childCount == 0) {
7081 // Can't scroll without children.
7082 return;
7083 }
7084
7085 final int firstPos = mFirstPosition;
7086 final int lastPos = firstPos + childCount - 1;
7087
7088 int viewTravelCount;
7089 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7090 if (clampedPosition < firstPos) {
7091 viewTravelCount = firstPos - clampedPosition + 1;
7092 mMode = MOVE_UP_POS;
7093 } else if (clampedPosition > lastPos) {
7094 viewTravelCount = clampedPosition - lastPos + 1;
7095 mMode = MOVE_DOWN_POS;
7096 } else {
7097 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7098 return;
7099 }
7100
7101 if (viewTravelCount > 0) {
7102 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7103 } else {
7104 mScrollDuration = SCROLL_DURATION;
7105 }
7106 mTargetPos = clampedPosition;
7107 mBoundPos = INVALID_POSITION;
7108 mLastSeenPos = INVALID_POSITION;
7109
7110 postOnAnimation(this);
7111 }
7112
7113 @Override
7114 public void start(final int position, final int boundPosition) {
7115 stop();
7116
7117 if (boundPosition == INVALID_POSITION) {
7118 start(position);
7119 return;
7120 }
7121
7122 if (mDataChanged) {
7123 // Wait until we're back in a stable state to try this.
7124 mPositionScrollAfterLayout = new Runnable() {
7125 @Override public void run() {
7126 start(position, boundPosition);
7127 }
7128 };
7129 return;
7130 }
7131
7132 final int childCount = getChildCount();
7133 if (childCount == 0) {
7134 // Can't scroll without children.
7135 return;
7136 }
7137
7138 final int firstPos = mFirstPosition;
7139 final int lastPos = firstPos + childCount - 1;
7140
7141 int viewTravelCount;
7142 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7143 if (clampedPosition < firstPos) {
7144 final int boundPosFromLast = lastPos - boundPosition;
7145 if (boundPosFromLast < 1) {
7146 // Moving would shift our bound position off the screen. Abort.
7147 return;
7148 }
7149
7150 final int posTravel = firstPos - clampedPosition + 1;
7151 final int boundTravel = boundPosFromLast - 1;
7152 if (boundTravel < posTravel) {
7153 viewTravelCount = boundTravel;
7154 mMode = MOVE_UP_BOUND;
7155 } else {
7156 viewTravelCount = posTravel;
7157 mMode = MOVE_UP_POS;
7158 }
7159 } else if (clampedPosition > lastPos) {
7160 final int boundPosFromFirst = boundPosition - firstPos;
7161 if (boundPosFromFirst < 1) {
7162 // Moving would shift our bound position off the screen. Abort.
7163 return;
7164 }
7165
7166 final int posTravel = clampedPosition - lastPos + 1;
7167 final int boundTravel = boundPosFromFirst - 1;
7168 if (boundTravel < posTravel) {
7169 viewTravelCount = boundTravel;
7170 mMode = MOVE_DOWN_BOUND;
7171 } else {
7172 viewTravelCount = posTravel;
7173 mMode = MOVE_DOWN_POS;
7174 }
7175 } else {
7176 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7177 return;
7178 }
7179
7180 if (viewTravelCount > 0) {
7181 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7182 } else {
7183 mScrollDuration = SCROLL_DURATION;
7184 }
7185 mTargetPos = clampedPosition;
7186 mBoundPos = boundPosition;
7187 mLastSeenPos = INVALID_POSITION;
7188
7189 postOnAnimation(this);
7190 }
7191
7192 @Override
7193 public void startWithOffset(int position, int offset) {
7194 startWithOffset(position, offset, SCROLL_DURATION);
7195 }
7196
7197 @Override
7198 public void startWithOffset(final int position, int offset, final int duration) {
7199 stop();
7200
7201 if (mDataChanged) {
7202 // Wait until we're back in a stable state to try this.
7203 final int postOffset = offset;
7204 mPositionScrollAfterLayout = new Runnable() {
7205 @Override public void run() {
7206 startWithOffset(position, postOffset, duration);
7207 }
7208 };
7209 return;
7210 }
7211
7212 final int childCount = getChildCount();
7213 if (childCount == 0) {
7214 // Can't scroll without children.
7215 return;
7216 }
7217
7218 offset += getPaddingTop();
7219
7220 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7221 mOffsetFromTop = offset;
7222 mBoundPos = INVALID_POSITION;
7223 mLastSeenPos = INVALID_POSITION;
7224 mMode = MOVE_OFFSET;
7225
7226 final int firstPos = mFirstPosition;
7227 final int lastPos = firstPos + childCount - 1;
7228
7229 int viewTravelCount;
7230 if (mTargetPos < firstPos) {
7231 viewTravelCount = firstPos - mTargetPos;
7232 } else if (mTargetPos > lastPos) {
7233 viewTravelCount = mTargetPos - lastPos;
7234 } else {
7235 // On-screen, just scroll.
7236 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
7237 smoothScrollBy(targetTop - offset, duration, true);
7238 return;
7239 }
7240
7241 // Estimate how many screens we should travel
7242 final float screenTravelCount = (float) viewTravelCount / childCount;
7243 mScrollDuration = screenTravelCount < 1 ?
7244 duration : (int) (duration / screenTravelCount);
7245 mLastSeenPos = INVALID_POSITION;
7246
7247 postOnAnimation(this);
7248 }
7249
7250 /**
7251 * Scroll such that targetPos is in the visible padded region without scrolling
7252 * boundPos out of view. Assumes targetPos is onscreen.
7253 */
7254 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7255 final int firstPos = mFirstPosition;
7256 final int childCount = getChildCount();
7257 final int lastPos = firstPos + childCount - 1;
7258 final int paddedTop = mListPadding.top;
7259 final int paddedBottom = getHeight() - mListPadding.bottom;
7260
7261 if (targetPos < firstPos || targetPos > lastPos) {
7262 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7263 " not visible [" + firstPos + ", " + lastPos + "]");
7264 }
7265 if (boundPos < firstPos || boundPos > lastPos) {
7266 // boundPos doesn't matter, it's already offscreen.
7267 boundPos = INVALID_POSITION;
7268 }
7269
7270 final View targetChild = getChildAt(targetPos - firstPos);
7271 final int targetTop = targetChild.getTop();
7272 final int targetBottom = targetChild.getBottom();
7273 int scrollBy = 0;
7274
7275 if (targetBottom > paddedBottom) {
7276 scrollBy = targetBottom - paddedBottom;
7277 }
7278 if (targetTop < paddedTop) {
7279 scrollBy = targetTop - paddedTop;
7280 }
7281
7282 if (scrollBy == 0) {
7283 return;
7284 }
7285
7286 if (boundPos >= 0) {
7287 final View boundChild = getChildAt(boundPos - firstPos);
7288 final int boundTop = boundChild.getTop();
7289 final int boundBottom = boundChild.getBottom();
7290 final int absScroll = Math.abs(scrollBy);
7291
7292 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7293 // Don't scroll the bound view off the bottom of the screen.
7294 scrollBy = Math.max(0, boundBottom - paddedBottom);
7295 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7296 // Don't scroll the bound view off the top of the screen.
7297 scrollBy = Math.min(0, boundTop - paddedTop);
7298 }
7299 }
7300
7301 smoothScrollBy(scrollBy, duration);
7302 }
7303
7304 @Override
7305 public void stop() {
7306 removeCallbacks(this);
7307 }
7308
7309 @Override
7310 public void run() {
7311 final int listHeight = getHeight();
7312 final int firstPos = mFirstPosition;
7313
7314 switch (mMode) {
7315 case MOVE_DOWN_POS: {
7316 final int lastViewIndex = getChildCount() - 1;
7317 final int lastPos = firstPos + lastViewIndex;
7318
7319 if (lastViewIndex < 0) {
7320 return;
7321 }
7322
7323 if (lastPos == mLastSeenPos) {
7324 // No new views, let things keep going.
7325 postOnAnimation(this);
7326 return;
7327 }
7328
7329 final View lastView = getChildAt(lastViewIndex);
7330 final int lastViewHeight = lastView.getHeight();
7331 final int lastViewTop = lastView.getTop();
7332 final int lastViewPixelsShowing = listHeight - lastViewTop;
7333 final int extraScroll = lastPos < mItemCount - 1 ?
7334 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7335
7336 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
7337 smoothScrollBy(scrollBy, mScrollDuration, true);
7338
7339 mLastSeenPos = lastPos;
7340 if (lastPos < mTargetPos) {
7341 postOnAnimation(this);
7342 }
7343 break;
7344 }
7345
7346 case MOVE_DOWN_BOUND: {
7347 final int nextViewIndex = 1;
7348 final int childCount = getChildCount();
7349
7350 if (firstPos == mBoundPos || childCount <= nextViewIndex
7351 || firstPos + childCount >= mItemCount) {
7352 return;
7353 }
7354 final int nextPos = firstPos + nextViewIndex;
7355
7356 if (nextPos == mLastSeenPos) {
7357 // No new views, let things keep going.
7358 postOnAnimation(this);
7359 return;
7360 }
7361
7362 final View nextView = getChildAt(nextViewIndex);
7363 final int nextViewHeight = nextView.getHeight();
7364 final int nextViewTop = nextView.getTop();
7365 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7366 if (nextPos < mBoundPos) {
7367 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
7368 mScrollDuration, true);
7369
7370 mLastSeenPos = nextPos;
7371
7372 postOnAnimation(this);
7373 } else {
7374 if (nextViewTop > extraScroll) {
7375 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
7376 }
7377 }
7378 break;
7379 }
7380
7381 case MOVE_UP_POS: {
7382 if (firstPos == mLastSeenPos) {
7383 // No new views, let things keep going.
7384 postOnAnimation(this);
7385 return;
7386 }
7387
7388 final View firstView = getChildAt(0);
7389 if (firstView == null) {
7390 return;
7391 }
7392 final int firstViewTop = firstView.getTop();
7393 final int extraScroll = firstPos > 0 ?
7394 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7395
7396 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
7397
7398 mLastSeenPos = firstPos;
7399
7400 if (firstPos > mTargetPos) {
7401 postOnAnimation(this);
7402 }
7403 break;
7404 }
7405
7406 case MOVE_UP_BOUND: {
7407 final int lastViewIndex = getChildCount() - 2;
7408 if (lastViewIndex < 0) {
7409 return;
7410 }
7411 final int lastPos = firstPos + lastViewIndex;
7412
7413 if (lastPos == mLastSeenPos) {
7414 // No new views, let things keep going.
7415 postOnAnimation(this);
7416 return;
7417 }
7418
7419 final View lastView = getChildAt(lastViewIndex);
7420 final int lastViewHeight = lastView.getHeight();
7421 final int lastViewTop = lastView.getTop();
7422 final int lastViewPixelsShowing = listHeight - lastViewTop;
7423 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7424 mLastSeenPos = lastPos;
7425 if (lastPos > mBoundPos) {
7426 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
7427 postOnAnimation(this);
7428 } else {
7429 final int bottom = listHeight - extraScroll;
7430 final int lastViewBottom = lastViewTop + lastViewHeight;
7431 if (bottom > lastViewBottom) {
7432 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
7433 }
7434 }
7435 break;
7436 }
7437
7438 case MOVE_OFFSET: {
7439 if (mLastSeenPos == firstPos) {
7440 // No new views, let things keep going.
7441 postOnAnimation(this);
7442 return;
7443 }
7444
7445 mLastSeenPos = firstPos;
7446
7447 final int childCount = getChildCount();
7448 final int position = mTargetPos;
7449 final int lastPos = firstPos + childCount - 1;
7450
7451 int viewTravelCount = 0;
7452 if (position < firstPos) {
7453 viewTravelCount = firstPos - position + 1;
7454 } else if (position > lastPos) {
7455 viewTravelCount = position - lastPos;
7456 }
7457
7458 // Estimate how many screens we should travel
7459 final float screenTravelCount = (float) viewTravelCount / childCount;
7460
7461 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7462 if (position < firstPos) {
7463 final int distance = (int) (-getHeight() * modifier);
7464 final int duration = (int) (mScrollDuration * modifier);
7465 smoothScrollBy(distance, duration, true);
7466 postOnAnimation(this);
7467 } else if (position > lastPos) {
7468 final int distance = (int) (getHeight() * modifier);
7469 final int duration = (int) (mScrollDuration * modifier);
7470 smoothScrollBy(distance, duration, true);
7471 postOnAnimation(this);
7472 } else {
7473 // On-screen, just scroll.
7474 final int targetTop = getChildAt(position - firstPos).getTop();
7475 final int distance = targetTop - mOffsetFromTop;
7476 final int duration = (int) (mScrollDuration *
7477 ((float) Math.abs(distance) / getHeight()));
7478 smoothScrollBy(distance, duration, true);
7479 }
7480 break;
7481 }
7482
7483 default:
7484 break;
7485 }
7486 }
7487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007488}