blob: 1899a1df40d9349f4aed5592a6fddc3b27c2652d [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
19import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070020import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070026import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.os.Parcel;
29import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080030import android.os.StrictMode;
Romain Guy5fade8c2013-07-10 16:36:18 -070031import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.text.Editable;
Romain Guyf6991302013-06-05 17:19:01 -070033import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.text.TextUtils;
35import android.text.TextWatcher;
36import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080037import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070038import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080039import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070040import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070041import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070042import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070043import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.Gravity;
45import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080046import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.view.KeyEvent;
48import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070049import android.view.Menu;
50import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.view.MotionEvent;
52import android.view.VelocityTracker;
53import android.view.View;
54import android.view.ViewConfiguration;
55import android.view.ViewDebug;
56import android.view.ViewGroup;
Michael Jurka13451a42011-08-22 15:54:21 -070057import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070059import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070060import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070061import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070062import android.view.animation.Interpolator;
63import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070064import android.view.inputmethod.BaseInputConnection;
Romain Guyf6991302013-06-05 17:19:01 -070065import android.view.inputmethod.CompletionInfo;
66import android.view.inputmethod.CorrectionInfo;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070067import android.view.inputmethod.EditorInfo;
Romain Guyf6991302013-06-05 17:19:01 -070068import android.view.inputmethod.ExtractedText;
69import android.view.inputmethod.ExtractedTextRequest;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070070import android.view.inputmethod.InputConnection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070072import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073
Adam Cohen335c3b62012-07-24 17:18:16 -070074import com.android.internal.R;
75
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076import java.util.ArrayList;
77import java.util.List;
78
79/**
Romain Guyd6a463a2009-05-21 23:10:10 -070080 * Base class that can be used to implement virtualized lists of items. A list does
81 * not have a spatial definition here. For instance, subclases of this class can
82 * 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 -080083 *
84 * @attr ref android.R.styleable#AbsListView_listSelector
85 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
86 * @attr ref android.R.styleable#AbsListView_stackFromBottom
87 * @attr ref android.R.styleable#AbsListView_scrollingCache
88 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
89 * @attr ref android.R.styleable#AbsListView_transcriptMode
90 * @attr ref android.R.styleable#AbsListView_cacheColorHint
91 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
92 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -070093 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 */
95public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
96 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -070097 ViewTreeObserver.OnTouchModeChangeListener,
98 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099
Romain Guy9d849a22012-03-14 16:41:42 -0700100 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -0800101 private static final String TAG = "AbsListView";
102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 /**
104 * Disables the transcript mode.
105 *
106 * @see #setTranscriptMode(int)
107 */
108 public static final int TRANSCRIPT_MODE_DISABLED = 0;
109 /**
110 * The list will automatically scroll to the bottom when a data set change
111 * notification is received and only if the last item is already visible
112 * on screen.
113 *
114 * @see #setTranscriptMode(int)
115 */
116 public static final int TRANSCRIPT_MODE_NORMAL = 1;
117 /**
118 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700119 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 *
121 * @see #setTranscriptMode(int)
122 */
123 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
124
125 /**
126 * Indicates that we are not in the middle of a touch gesture
127 */
128 static final int TOUCH_MODE_REST = -1;
129
130 /**
131 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
132 * scroll gesture.
133 */
134 static final int TOUCH_MODE_DOWN = 0;
135
136 /**
137 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
138 * is a longpress
139 */
140 static final int TOUCH_MODE_TAP = 1;
141
142 /**
143 * Indicates we have waited for everything we can wait for, but the user's finger is still down
144 */
145 static final int TOUCH_MODE_DONE_WAITING = 2;
146
147 /**
148 * Indicates the touch gesture is a scroll
149 */
150 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800151
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 /**
153 * Indicates the view is in the process of being flung
154 */
155 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 /**
Adam Powell637d3372010-08-25 14:37:03 -0700158 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
159 */
160 static final int TOUCH_MODE_OVERSCROLL = 5;
161
162 /**
163 * Indicates the view is being flung outside of normal content bounds
164 * and will spring back.
165 */
166 static final int TOUCH_MODE_OVERFLING = 6;
167
168 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 * Regular layout - usually an unsolicited layout from the view system
170 */
171 static final int LAYOUT_NORMAL = 0;
172
173 /**
174 * Show the first item
175 */
176 static final int LAYOUT_FORCE_TOP = 1;
177
178 /**
179 * Force the selected item to be on somewhere on the screen
180 */
181 static final int LAYOUT_SET_SELECTION = 2;
182
183 /**
184 * Show the last item
185 */
186 static final int LAYOUT_FORCE_BOTTOM = 3;
187
188 /**
189 * Make a mSelectedItem appear in a specific location and build the rest of
190 * the views from there. The top is specified by mSpecificTop.
191 */
192 static final int LAYOUT_SPECIFIC = 4;
193
194 /**
195 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
196 * at mSpecificTop
197 */
198 static final int LAYOUT_SYNC = 5;
199
200 /**
201 * Layout as a result of using the navigation keys
202 */
203 static final int LAYOUT_MOVE_SELECTION = 6;
204
205 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700206 * Normal list that does not indicate choices
207 */
208 public static final int CHOICE_MODE_NONE = 0;
209
210 /**
211 * The list allows up to one choice
212 */
213 public static final int CHOICE_MODE_SINGLE = 1;
214
215 /**
216 * The list allows multiple choices
217 */
218 public static final int CHOICE_MODE_MULTIPLE = 2;
219
220 /**
221 * The list allows multiple choices in a modal selection mode
222 */
223 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
224
225 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700226 * The thread that created this view.
227 */
228 private final Thread mOwnerThread;
229
230 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700231 * Controls if/how the user may choose/check items in the list
232 */
233 int mChoiceMode = CHOICE_MODE_NONE;
234
235 /**
236 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
237 */
238 ActionMode mChoiceActionMode;
239
240 /**
241 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
242 * a few extra actions around what application code does.
243 */
244 MultiChoiceModeWrapper mMultiChoiceModeCallback;
245
246 /**
247 * Running count of how many items are currently checked
248 */
249 int mCheckedItemCount;
250
251 /**
252 * Running state of which positions are currently checked
253 */
254 SparseBooleanArray mCheckStates;
255
256 /**
Adam Powell14c08042011-10-06 19:46:18 -0700257 * Running state of which IDs are currently checked.
258 * If there is a value for a given key, the checked state for that ID is true
259 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700260 */
Adam Powell14c08042011-10-06 19:46:18 -0700261 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700262
263 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 * Controls how the next layout will happen
265 */
266 int mLayoutMode = LAYOUT_NORMAL;
267
268 /**
269 * Should be used by subclasses to listen to changes in the dataset
270 */
271 AdapterDataSetObserver mDataSetObserver;
272
273 /**
274 * The adapter containing the data to be displayed by this view
275 */
276 ListAdapter mAdapter;
277
278 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700279 * The remote adapter containing the data to be displayed by this view to be set
280 */
281 private RemoteViewsAdapter mRemoteAdapter;
282
283 /**
Adam Powell539ee872012-02-03 19:00:49 -0800284 * If mAdapter != null, whenever this is true the adapter has stable IDs.
285 */
286 boolean mAdapterHasStableIds;
287
288 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700289 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
290 */
291 private boolean mDeferNotifyDataSetChanged = false;
292
293 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 * Indicates whether the list selector should be drawn on top of the children or behind
295 */
296 boolean mDrawSelectorOnTop = false;
297
298 /**
299 * The drawable used to draw the selector
300 */
301 Drawable mSelector;
302
303 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700304 * The current position of the selector in the list.
305 */
306 int mSelectorPosition = INVALID_POSITION;
307
308 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 * Defines the selector's location and dimension at drawing time
310 */
311 Rect mSelectorRect = new Rect();
312
313 /**
314 * The data set used to store unused views that should be reused during the next layout
315 * to avoid creating new ones
316 */
317 final RecycleBin mRecycler = new RecycleBin();
318
319 /**
320 * The selection's left padding
321 */
322 int mSelectionLeftPadding = 0;
323
324 /**
325 * The selection's top padding
326 */
327 int mSelectionTopPadding = 0;
328
329 /**
330 * The selection's right padding
331 */
332 int mSelectionRightPadding = 0;
333
334 /**
335 * The selection's bottom padding
336 */
337 int mSelectionBottomPadding = 0;
338
339 /**
340 * This view's padding
341 */
342 Rect mListPadding = new Rect();
343
344 /**
345 * Subclasses must retain their measure spec from onMeasure() into this member
346 */
347 int mWidthMeasureSpec = 0;
348
349 /**
350 * The top scroll indicator
351 */
352 View mScrollUp;
353
354 /**
355 * The down scroll indicator
356 */
357 View mScrollDown;
358
359 /**
360 * When the view is scrolling, this flag is set to true to indicate subclasses that
361 * the drawing cache was enabled on the children
362 */
363 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800364 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365
366 /**
367 * The position of the view that received the down motion event
368 */
369 int mMotionPosition;
370
371 /**
372 * The offset to the top of the mMotionPosition view when the down motion event was received
373 */
374 int mMotionViewOriginalTop;
375
376 /**
377 * The desired offset to the top of the mMotionPosition view after a scroll
378 */
379 int mMotionViewNewTop;
380
381 /**
382 * The X value associated with the the down motion event
383 */
384 int mMotionX;
385
386 /**
387 * The Y value associated with the the down motion event
388 */
389 int mMotionY;
390
391 /**
392 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
393 * TOUCH_MODE_DONE_WAITING
394 */
395 int mTouchMode = TOUCH_MODE_REST;
396
397 /**
398 * Y value from on the previous motion event (if any)
399 */
400 int mLastY;
401
402 /**
403 * How far the finger moved before we started scrolling
404 */
405 int mMotionCorrection;
406
407 /**
408 * Determines speed during touch scrolling
409 */
410 private VelocityTracker mVelocityTracker;
411
412 /**
413 * Handles one frame of a fling
414 */
415 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800416
Adam Powell45803472010-01-25 15:10:44 -0800417 /**
418 * Handles scrolling between positions within the list.
419 */
Adam Powell1fa179ef2012-04-12 15:01:40 -0700420 PositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421
422 /**
423 * The offset in pixels form the top of the AdapterView to the top
424 * of the currently selected view. Used to save and restore state.
425 */
426 int mSelectedTop = 0;
427
428 /**
429 * Indicates whether the list is stacked from the bottom edge or
430 * the top edge.
431 */
432 boolean mStackFromBottom;
433
434 /**
435 * When set to true, the list automatically discards the children's
436 * bitmap cache after scrolling.
437 */
438 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700439
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 /**
441 * Whether or not to enable the fast scroll feature on this list
442 */
443 boolean mFastScrollEnabled;
444
445 /**
Alan Viverette39bed692013-08-07 15:47:04 -0700446 * Whether or not to always show the fast scroll feature on this list
447 */
448 boolean mFastScrollAlwaysVisible;
449
450 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 * Optional callback to notify client when scroll position has changed
452 */
453 private OnScrollListener mOnScrollListener;
454
455 /**
456 * Keeps track of our accessory window
457 */
458 PopupWindow mPopup;
459
460 /**
461 * Used with type filter window
462 */
463 EditText mTextFilter;
464
465 /**
466 * Indicates whether to use pixels-based or position-based scrollbar
467 * properties.
468 */
469 private boolean mSmoothScrollbarEnabled = true;
470
471 /**
472 * Indicates that this view supports filtering
473 */
474 private boolean mTextFilterEnabled;
475
476 /**
477 * Indicates that this view is currently displaying a filtered view of the data
478 */
479 private boolean mFiltered;
480
481 /**
482 * Rectangle used for hit testing children
483 */
484 private Rect mTouchFrame;
485
486 /**
487 * The position to resurrect the selected position to.
488 */
489 int mResurrectToPosition = INVALID_POSITION;
490
491 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800492
Adam Powell0b8bb422010-02-08 14:30:45 -0800493 /**
Adam Powell637d3372010-08-25 14:37:03 -0700494 * Maximum distance to record overscroll
495 */
496 int mOverscrollMax;
497
498 /**
499 * Content height divided by this is the overscroll limit.
500 */
501 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
502
503 /**
Adam Powell14c08042011-10-06 19:46:18 -0700504 * How many positions in either direction we will search to try to
505 * find a checked item with a stable ID that moved position across
506 * a data set change. If the item isn't found it will be unselected.
507 */
508 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
509
510 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 * Used to request a layout when we changed touch mode
512 */
513 private static final int TOUCH_MODE_UNKNOWN = -1;
514 private static final int TOUCH_MODE_ON = 0;
515 private static final int TOUCH_MODE_OFF = 1;
516
517 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
518
519 private static final boolean PROFILE_SCROLLING = false;
520 private boolean mScrollProfilingStarted = false;
521
522 private static final boolean PROFILE_FLINGING = false;
523 private boolean mFlingProfilingStarted = false;
524
525 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800526 * The StrictMode "critical time span" objects to catch animation
527 * stutters. Non-null when a time-sensitive animation is
528 * in-flight. Must call finish() on them when done animating.
529 * These are no-ops on user builds.
530 */
531 private StrictMode.Span mScrollStrictSpan = null;
532 private StrictMode.Span mFlingStrictSpan = null;
533
534 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 * The last CheckForLongPress runnable we posted, if any
536 */
537 private CheckForLongPress mPendingCheckForLongPress;
538
539 /**
540 * The last CheckForTap runnable we posted, if any
541 */
542 private Runnable mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700543
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 /**
545 * The last CheckForKeyLongPress runnable we posted, if any
546 */
547 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
548
549 /**
550 * Acts upon click
551 */
552 private AbsListView.PerformClick mPerformClick;
553
554 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800555 * Delayed action for touch mode.
556 */
557 private Runnable mTouchModeReset;
558
559 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 * This view is in transcript mode -- it shows the bottom of the list when the data
561 * changes
562 */
563 private int mTranscriptMode;
564
565 /**
566 * Indicates that this list is always drawn on top of a solid, single-color, opaque
567 * background
568 */
569 private int mCacheColorHint;
570
571 /**
572 * The select child's view (from the adapter's getView) is enabled.
573 */
574 private boolean mIsChildViewEnabled;
575
576 /**
577 * The last scroll state reported to clients through {@link OnScrollListener}.
578 */
579 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
580
581 /**
582 * Helper object that renders and controls the fast scroll thumb.
583 */
584 private FastScroller mFastScroller;
585
Romain Guyd6a463a2009-05-21 23:10:10 -0700586 private boolean mGlobalLayoutListenerAddedFilter;
587
588 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 private float mDensityScale;
590
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700591 private InputConnection mDefInputConnection;
592 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700593
594 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700595 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700596 private int mMinimumVelocity;
597 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700598 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800599
Romain Guy21875052010-01-06 18:48:08 -0800600 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800601
Romain Guy24562482010-02-01 14:56:19 -0800602 // True when the popup should be hidden because of a call to
603 // dispatchDisplayHint()
604 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800605
Adam Powell4cd47702010-02-25 11:21:14 -0800606 /**
607 * ID of the active pointer. This is used to retain consistency during
608 * drags/flings if multiple pointers are used.
609 */
610 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800611
Adam Powell4cd47702010-02-25 11:21:14 -0800612 /**
613 * Sentinel value for no current active pointer.
614 * Used by {@link #mActivePointerId}.
615 */
616 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 /**
Adam Powell637d3372010-08-25 14:37:03 -0700619 * Maximum distance to overscroll by during edge effects
620 */
621 int mOverscrollDistance;
622
623 /**
624 * Maximum distance to overfling during edge effects
625 */
626 int mOverflingDistance;
627
628 // These two EdgeGlows are always set and used together.
629 // Checking one for null is as good as checking both.
630
631 /**
632 * Tracks the state of the top edge glow.
633 */
Adam Powell89935e42011-08-31 14:26:12 -0700634 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700635
636 /**
637 * Tracks the state of the bottom edge glow.
638 */
Adam Powell89935e42011-08-31 14:26:12 -0700639 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700640
641 /**
642 * An estimate of how many pixels are between the top of the list and
643 * the top of the first position in the adapter, based on the last time
644 * we saw it. Used to hint where to draw edge glows.
645 */
646 private int mFirstPositionDistanceGuess;
647
648 /**
649 * An estimate of how many pixels are between the bottom of the list and
650 * the bottom of the last position in the adapter, based on the last time
651 * we saw it. Used to hint where to draw edge glows.
652 */
653 private int mLastPositionDistanceGuess;
654
655 /**
656 * Used for determining when to cancel out of overscroll.
657 */
658 private int mDirection = 0;
659
660 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800661 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
662 * the bottom correctly on resizes.
663 */
664 private boolean mForceTranscriptScroll;
665
Adam Powell07d6f7b2011-03-02 14:27:30 -0800666 private int mGlowPaddingLeft;
667 private int mGlowPaddingRight;
668
alanvc1d7e772012-05-08 14:47:24 -0700669 /**
670 * Used for interacting with list items from an accessibility service.
671 */
672 private ListItemAccessibilityDelegate mAccessibilityDelegate;
673
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700674 private int mLastAccessibilityScrollEventFromIndex;
675 private int mLastAccessibilityScrollEventToIndex;
676
Adam Powellda13dba2010-12-05 13:47:23 -0800677 /**
Adam Powellee78b172011-08-16 16:39:20 -0700678 * Track the item count from the last time we handled a data change.
679 */
680 private int mLastHandledItemCount;
681
682 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700683 * Used for smooth scrolling at a consistent rate
684 */
685 static final Interpolator sLinearInterpolator = new LinearInterpolator();
686
687 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700688 * The saved state that we will be restoring from when we next sync.
689 * Kept here so that if we happen to be asked to save our state before
690 * the sync happens, we can return this existing data rather than losing
691 * it.
692 */
693 private SavedState mPendingSync;
694
695 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 * Interface definition for a callback to be invoked when the list or grid
697 * has been scrolled.
698 */
699 public interface OnScrollListener {
700
701 /**
702 * The view is not scrolling. Note navigating the list using the trackball counts as
703 * being in the idle state since these transitions are not animated.
704 */
705 public static int SCROLL_STATE_IDLE = 0;
706
707 /**
708 * The user is scrolling using touch, and their finger is still on the screen
709 */
710 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
711
712 /**
713 * The user had previously been scrolling using touch and had performed a fling. The
714 * animation is now coasting to a stop
715 */
716 public static int SCROLL_STATE_FLING = 2;
717
718 /**
719 * Callback method to be invoked while the list view or grid view is being scrolled. If the
720 * view is being scrolled, this method will be called before the next frame of the scroll is
721 * rendered. In particular, it will be called before any calls to
722 * {@link Adapter#getView(int, View, ViewGroup)}.
723 *
724 * @param view The view whose scroll state is being reported
725 *
quddusc90721102013-12-20 14:55:09 -0800726 * @param scrollState The current scroll state. One of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
728 */
729 public void onScrollStateChanged(AbsListView view, int scrollState);
730
731 /**
732 * Callback method to be invoked when the list or grid has been scrolled. This will be
733 * called after the scroll has completed
734 * @param view The view whose scroll state is being reported
735 * @param firstVisibleItem the index of the first visible cell (ignore if
736 * visibleItemCount == 0)
737 * @param visibleItemCount the number of visible cells
738 * @param totalItemCount the number of items in the list adaptor
739 */
740 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
741 int totalItemCount);
742 }
743
Dianne Hackborne2136772010-11-04 15:08:59 -0700744 /**
745 * The top-level view of a list item can implement this interface to allow
746 * itself to modify the bounds of the selection shown for that item.
747 */
748 public interface SelectionBoundsAdjuster {
749 /**
750 * Called to allow the list item to adjust the bounds shown for
751 * its selection.
752 *
753 * @param bounds On call, this contains the bounds the list has
754 * selected for the item (that is the bounds of the entire view). The
755 * values can be modified as desired.
756 */
757 public void adjustListItemSelectionBounds(Rect bounds);
758 }
759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 public AbsListView(Context context) {
761 super(context);
762 initAbsListView();
763
Alan Viverette39bed692013-08-07 15:47:04 -0700764 mOwnerThread = Thread.currentThread();
765
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 setVerticalScrollBarEnabled(true);
767 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
768 initializeScrollbars(a);
769 a.recycle();
770 }
771
772 public AbsListView(Context context, AttributeSet attrs) {
773 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
774 }
775
776 public AbsListView(Context context, AttributeSet attrs, int defStyle) {
777 super(context, attrs, defStyle);
778 initAbsListView();
779
Alan Viverette39bed692013-08-07 15:47:04 -0700780 mOwnerThread = Thread.currentThread();
781
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 TypedArray a = context.obtainStyledAttributes(attrs,
783 com.android.internal.R.styleable.AbsListView, defStyle, 0);
784
785 Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
786 if (d != null) {
787 setSelector(d);
788 }
789
790 mDrawSelectorOnTop = a.getBoolean(
791 com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
792
793 boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
794 setStackFromBottom(stackFromBottom);
795
796 boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
797 setScrollingCacheEnabled(scrollingCacheEnabled);
798
799 boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
800 setTextFilterEnabled(useTextFilter);
801
802 int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
803 TRANSCRIPT_MODE_DISABLED);
804 setTranscriptMode(transcriptMode);
805
806 int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
807 setCacheColorHint(color);
Romain Guy0a637162009-05-29 14:43:54 -0700808
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809 boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
810 setFastScrollEnabled(enableFastScroll);
811
812 boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
813 setSmoothScrollbarEnabled(smoothScrollbar);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800814
Adam Powellf343e1b2010-08-13 18:27:04 -0700815 setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
Adam Powell20232d02010-12-08 21:08:53 -0800816 setFastScrollAlwaysVisible(
817 a.getBoolean(R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700818
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 a.recycle();
820 }
821
Romain Guyd6a463a2009-05-21 23:10:10 -0700822 private void initAbsListView() {
823 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700824 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700825 setFocusableInTouchMode(true);
826 setWillNotDraw(false);
827 setAlwaysDrawnWithCacheEnabled(false);
828 setScrollingCacheEnabled(true);
829
Romain Guy4296fc42009-07-06 11:48:52 -0700830 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
831 mTouchSlop = configuration.getScaledTouchSlop();
832 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
833 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700834 mOverscrollDistance = configuration.getScaledOverscrollDistance();
835 mOverflingDistance = configuration.getScaledOverflingDistance();
836
Romain Guyd6a463a2009-05-21 23:10:10 -0700837 mDensityScale = getContext().getResources().getDisplayMetrics().density;
838 }
Romain Guy0a637162009-05-29 14:43:54 -0700839
Adam Powell637d3372010-08-25 14:37:03 -0700840 @Override
841 public void setOverScrollMode(int mode) {
842 if (mode != OVER_SCROLL_NEVER) {
843 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800844 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700845 mEdgeGlowTop = new EdgeEffect(context);
846 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700847 }
848 } else {
849 mEdgeGlowTop = null;
850 mEdgeGlowBottom = null;
851 }
852 super.setOverScrollMode(mode);
853 }
854
Romain Guyd6a463a2009-05-21 23:10:10 -0700855 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700856 * {@inheritDoc}
857 */
858 @Override
859 public void setAdapter(ListAdapter adapter) {
860 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800861 mAdapterHasStableIds = mAdapter.hasStableIds();
862 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700863 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700864 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700865 }
866 }
867
868 if (mCheckStates != null) {
869 mCheckStates.clear();
870 }
871
872 if (mCheckedIdStates != null) {
873 mCheckedIdStates.clear();
874 }
875 }
876
877 /**
878 * Returns the number of items currently selected. This will only be valid
879 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
880 *
881 * <p>To determine the specific items that are currently selected, use one of
882 * the <code>getChecked*</code> methods.
883 *
884 * @return The number of items currently selected
885 *
886 * @see #getCheckedItemPosition()
887 * @see #getCheckedItemPositions()
888 * @see #getCheckedItemIds()
889 */
890 public int getCheckedItemCount() {
891 return mCheckedItemCount;
892 }
893
894 /**
895 * Returns the checked state of the specified position. The result is only
896 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
897 * or {@link #CHOICE_MODE_MULTIPLE}.
898 *
899 * @param position The item whose checked state to return
900 * @return The item's checked state or <code>false</code> if choice mode
901 * is invalid
902 *
903 * @see #setChoiceMode(int)
904 */
905 public boolean isItemChecked(int position) {
906 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
907 return mCheckStates.get(position);
908 }
909
910 return false;
911 }
912
913 /**
914 * Returns the currently checked item. The result is only valid if the choice
915 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
916 *
917 * @return The position of the currently checked item or
918 * {@link #INVALID_POSITION} if nothing is selected
919 *
920 * @see #setChoiceMode(int)
921 */
922 public int getCheckedItemPosition() {
923 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
924 return mCheckStates.keyAt(0);
925 }
926
927 return INVALID_POSITION;
928 }
929
930 /**
931 * Returns the set of checked items in the list. The result is only valid if
932 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
933 *
934 * @return A SparseBooleanArray which will return true for each call to
Cyril Mottier82ff2412013-07-23 13:58:33 +0200935 * get(int position) where position is a checked position in the
936 * list and false otherwise, or <code>null</code> if the choice
937 * mode is set to {@link #CHOICE_MODE_NONE}.
Adam Powellf343e1b2010-08-13 18:27:04 -0700938 */
939 public SparseBooleanArray getCheckedItemPositions() {
940 if (mChoiceMode != CHOICE_MODE_NONE) {
941 return mCheckStates;
942 }
943 return null;
944 }
945
946 /**
947 * Returns the set of checked items ids. The result is only valid if the
948 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
949 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
950 *
951 * @return A new array which contains the id of each checked item in the
952 * list.
953 */
954 public long[] getCheckedItemIds() {
955 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
956 return new long[0];
957 }
958
Adam Powell14c08042011-10-06 19:46:18 -0700959 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700960 final int count = idStates.size();
961 final long[] ids = new long[count];
962
963 for (int i = 0; i < count; i++) {
964 ids[i] = idStates.keyAt(i);
965 }
966
967 return ids;
968 }
969
970 /**
971 * Clear any choices previously set
972 */
973 public void clearChoices() {
974 if (mCheckStates != null) {
975 mCheckStates.clear();
976 }
977 if (mCheckedIdStates != null) {
978 mCheckedIdStates.clear();
979 }
980 mCheckedItemCount = 0;
981 }
982
983 /**
984 * Sets the checked state of the specified position. The is only valid if
985 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
986 * {@link #CHOICE_MODE_MULTIPLE}.
987 *
988 * @param position The item whose checked state is to be checked
989 * @param value The new checked state for the item
990 */
991 public void setItemChecked(int position, boolean value) {
992 if (mChoiceMode == CHOICE_MODE_NONE) {
993 return;
994 }
995
996 // Start selection mode if needed. We don't need to if we're unchecking something.
997 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -0700998 if (mMultiChoiceModeCallback == null ||
999 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1000 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1001 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1002 "supplied. Call setMultiChoiceModeListener to set a callback.");
1003 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001004 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1005 }
1006
1007 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1008 boolean oldValue = mCheckStates.get(position);
1009 mCheckStates.put(position, value);
1010 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1011 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001012 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001013 } else {
1014 mCheckedIdStates.delete(mAdapter.getItemId(position));
1015 }
1016 }
1017 if (oldValue != value) {
1018 if (value) {
1019 mCheckedItemCount++;
1020 } else {
1021 mCheckedItemCount--;
1022 }
1023 }
1024 if (mChoiceActionMode != null) {
1025 final long id = mAdapter.getItemId(position);
1026 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1027 position, id, value);
1028 }
1029 } else {
1030 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1031 // Clear all values if we're checking something, or unchecking the currently
1032 // selected item
1033 if (value || isItemChecked(position)) {
1034 mCheckStates.clear();
1035 if (updateIds) {
1036 mCheckedIdStates.clear();
1037 }
1038 }
1039 // this may end up selecting the value we just cleared but this way
1040 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1041 if (value) {
1042 mCheckStates.put(position, true);
1043 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001044 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001045 }
1046 mCheckedItemCount = 1;
1047 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1048 mCheckedItemCount = 0;
1049 }
1050 }
1051
1052 // Do not generate a data change while we are in the layout phase
1053 if (!mInLayout && !mBlockLayoutRequests) {
1054 mDataChanged = true;
1055 rememberSyncState();
1056 requestLayout();
1057 }
1058 }
1059
1060 @Override
1061 public boolean performItemClick(View view, int position, long id) {
1062 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001063 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001064
1065 if (mChoiceMode != CHOICE_MODE_NONE) {
1066 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001067 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001068
1069 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1070 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001071 boolean checked = !mCheckStates.get(position, false);
1072 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001073 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001074 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001075 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001076 } else {
1077 mCheckedIdStates.delete(mAdapter.getItemId(position));
1078 }
1079 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001080 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001081 mCheckedItemCount++;
1082 } else {
1083 mCheckedItemCount--;
1084 }
1085 if (mChoiceActionMode != null) {
1086 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001087 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001088 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001089 }
Adam Powell29382d92012-02-23 11:03:22 -08001090 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001091 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001092 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001093 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001094 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001095 mCheckStates.put(position, true);
1096 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1097 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001098 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001099 }
1100 mCheckedItemCount = 1;
1101 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1102 mCheckedItemCount = 0;
1103 }
Adam Powell29382d92012-02-23 11:03:22 -08001104 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001105 }
1106
Adam Powell29382d92012-02-23 11:03:22 -08001107 if (checkedStateChanged) {
1108 updateOnScreenCheckedViews();
1109 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001110 }
1111
Adam Powellbf5f2b32010-10-24 16:45:44 -07001112 if (dispatchItemClick) {
1113 handled |= super.performItemClick(view, position, id);
1114 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001115
1116 return handled;
1117 }
1118
1119 /**
Adam Powell29382d92012-02-23 11:03:22 -08001120 * Perform a quick, in-place update of the checked or activated state
1121 * on all visible item views. This should only be called when a valid
1122 * choice mode is active.
1123 */
1124 private void updateOnScreenCheckedViews() {
1125 final int firstPos = mFirstPosition;
1126 final int count = getChildCount();
1127 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1128 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1129 for (int i = 0; i < count; i++) {
1130 final View child = getChildAt(i);
1131 final int position = firstPos + i;
1132
1133 if (child instanceof Checkable) {
1134 ((Checkable) child).setChecked(mCheckStates.get(position));
1135 } else if (useActivated) {
1136 child.setActivated(mCheckStates.get(position));
1137 }
1138 }
1139 }
1140
1141 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001142 * @see #setChoiceMode(int)
1143 *
1144 * @return The current choice mode
1145 */
1146 public int getChoiceMode() {
1147 return mChoiceMode;
1148 }
1149
1150 /**
1151 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1152 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1153 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1154 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1155 *
1156 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1157 * {@link #CHOICE_MODE_MULTIPLE}
1158 */
1159 public void setChoiceMode(int choiceMode) {
1160 mChoiceMode = choiceMode;
1161 if (mChoiceActionMode != null) {
1162 mChoiceActionMode.finish();
1163 mChoiceActionMode = null;
1164 }
1165 if (mChoiceMode != CHOICE_MODE_NONE) {
1166 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001167 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001168 }
1169 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001170 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001171 }
1172 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1173 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1174 clearChoices();
1175 setLongClickable(true);
1176 }
1177 }
1178 }
1179
1180 /**
1181 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1182 * selection {@link ActionMode}. Only used when the choice mode is set to
1183 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1184 *
1185 * @param listener Listener that will manage the selection mode
1186 *
1187 * @see #setChoiceMode(int)
1188 */
1189 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1190 if (mMultiChoiceModeCallback == null) {
1191 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1192 }
1193 mMultiChoiceModeCallback.setWrapped(listener);
1194 }
1195
1196 /**
Adam Powell637d3372010-08-25 14:37:03 -07001197 * @return true if all list content currently fits within the view boundaries
1198 */
1199 private boolean contentFits() {
1200 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001201 if (childCount == 0) return true;
1202 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001203
Adam Powell4ce35412011-01-24 14:55:00 -08001204 return getChildAt(0).getTop() >= mListPadding.top &&
1205 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001206 }
1207
1208 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001209 * Specifies whether fast scrolling is enabled or disabled.
1210 * <p>
1211 * When fast scrolling is enabled, the user can quickly scroll through lists
1212 * by dragging the fast scroll thumb.
1213 * <p>
1214 * If the adapter backing this list implements {@link SectionIndexer}, the
1215 * fast scroller will display section header previews as the user scrolls.
1216 * Additionally, the user will be able to quickly jump between sections by
1217 * tapping along the length of the scroll bar.
1218 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219 * @see SectionIndexer
1220 * @see #isFastScrollEnabled()
Alan Viverette86f5e892013-08-15 18:16:06 -07001221 * @param enabled true to enable fast scrolling, false otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 */
Alan Viverette39bed692013-08-07 15:47:04 -07001223 public void setFastScrollEnabled(final boolean enabled) {
1224 if (mFastScrollEnabled != enabled) {
1225 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001226
Alan Viverette39bed692013-08-07 15:47:04 -07001227 if (isOwnerThread()) {
1228 setFastScrollerEnabledUiThread(enabled);
1229 } else {
1230 post(new Runnable() {
1231 @Override
1232 public void run() {
1233 setFastScrollerEnabledUiThread(enabled);
1234 }
1235 });
1236 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001237 }
Alan Viverette39bed692013-08-07 15:47:04 -07001238 }
Alan Viverette447cdf22013-07-15 17:47:34 -07001239
Alan Viverette39bed692013-08-07 15:47:04 -07001240 private void setFastScrollerEnabledUiThread(boolean enabled) {
Alan Viverette447cdf22013-07-15 17:47:34 -07001241 if (mFastScroller != null) {
1242 mFastScroller.setEnabled(enabled);
Alan Viverette39bed692013-08-07 15:47:04 -07001243 } else if (enabled) {
1244 mFastScroller = new FastScroller(this);
Alan Viverettefb664152013-08-07 17:57:51 -07001245 mFastScroller.setEnabled(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 }
Alan Viverette26bb2532013-08-09 10:40:50 -07001247
Alan Viveretteb9f27222013-09-06 19:39:47 -07001248 resolvePadding();
Alan Viverette26bb2532013-08-09 10:40:50 -07001249
1250 if (mFastScroller != null) {
1251 mFastScroller.updateLayout();
1252 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 }
Romain Guy0a637162009-05-29 14:43:54 -07001254
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001256 * Set whether or not the fast scroller should always be shown in place of
1257 * the standard scroll bars. This will enable fast scrolling if it is not
Adam Powell20232d02010-12-08 21:08:53 -08001258 * already enabled.
Alan Viverette86f5e892013-08-15 18:16:06 -07001259 * <p>
1260 * Fast scrollers shown in this way will not fade out and will be a
1261 * permanent fixture within the list. This is best combined with an inset
1262 * scroll bar style to ensure the scroll bar does not overlap content.
Adam Powell20232d02010-12-08 21:08:53 -08001263 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001264 * @param alwaysShow true if the fast scroller should always be displayed,
1265 * false otherwise
Adam Powell20232d02010-12-08 21:08:53 -08001266 * @see #setScrollBarStyle(int)
1267 * @see #setFastScrollEnabled(boolean)
1268 */
Alan Viverette39bed692013-08-07 15:47:04 -07001269 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1270 if (mFastScrollAlwaysVisible != alwaysShow) {
1271 if (alwaysShow && !mFastScrollEnabled) {
1272 setFastScrollEnabled(true);
1273 }
Adam Powell20232d02010-12-08 21:08:53 -08001274
Alan Viverette39bed692013-08-07 15:47:04 -07001275 mFastScrollAlwaysVisible = alwaysShow;
1276
1277 if (isOwnerThread()) {
1278 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1279 } else {
1280 post(new Runnable() {
1281 @Override
1282 public void run() {
1283 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1284 }
1285 });
1286 }
1287 }
1288 }
1289
1290 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
Adam Powell20232d02010-12-08 21:08:53 -08001291 if (mFastScroller != null) {
1292 mFastScroller.setAlwaysShow(alwaysShow);
1293 }
Alan Viverette39bed692013-08-07 15:47:04 -07001294 }
Adam Powell20232d02010-12-08 21:08:53 -08001295
Alan Viverette39bed692013-08-07 15:47:04 -07001296 /**
1297 * @return whether the current thread is the one that created the view
1298 */
1299 private boolean isOwnerThread() {
1300 return mOwnerThread == Thread.currentThread();
Adam Powell20232d02010-12-08 21:08:53 -08001301 }
1302
1303 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001304 * Returns true if the fast scroller is set to always show on this view.
Adam Powell20232d02010-12-08 21:08:53 -08001305 *
Alan Viverette86f5e892013-08-15 18:16:06 -07001306 * @return true if the fast scroller will always show
Adam Powell20232d02010-12-08 21:08:53 -08001307 * @see #setFastScrollAlwaysVisible(boolean)
1308 */
1309 public boolean isFastScrollAlwaysVisible() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001310 if (mFastScroller == null) {
1311 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1312 } else {
1313 return mFastScroller.isEnabled() && mFastScroller.isAlwaysShowEnabled();
1314 }
Adam Powell20232d02010-12-08 21:08:53 -08001315 }
1316
1317 @Override
1318 public int getVerticalScrollbarWidth() {
Alan Viverette26bb2532013-08-09 10:40:50 -07001319 if (mFastScroller != null && mFastScroller.isEnabled()) {
Adam Powell20232d02010-12-08 21:08:53 -08001320 return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth());
1321 }
1322 return super.getVerticalScrollbarWidth();
1323 }
1324
1325 /**
Alan Viverette86f5e892013-08-15 18:16:06 -07001326 * Returns true if the fast scroller is enabled.
1327 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001328 * @see #setFastScrollEnabled(boolean)
1329 * @return true if fast scroll is enabled, false otherwise
1330 */
1331 @ViewDebug.ExportedProperty
1332 public boolean isFastScrollEnabled() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001333 if (mFastScroller == null) {
1334 return mFastScrollEnabled;
1335 } else {
1336 return mFastScroller.isEnabled();
1337 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001338 }
Romain Guy0a637162009-05-29 14:43:54 -07001339
Adam Powell20232d02010-12-08 21:08:53 -08001340 @Override
1341 public void setVerticalScrollbarPosition(int position) {
1342 super.setVerticalScrollbarPosition(position);
1343 if (mFastScroller != null) {
1344 mFastScroller.setScrollbarPosition(position);
1345 }
1346 }
1347
Alan Viverette26bb2532013-08-09 10:40:50 -07001348 @Override
1349 public void setScrollBarStyle(int style) {
1350 super.setScrollBarStyle(style);
1351 if (mFastScroller != null) {
1352 mFastScroller.setScrollBarStyle(style);
1353 }
1354 }
1355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001357 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001358 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001359 */
1360 @Override
1361 protected boolean isVerticalScrollBarHidden() {
Alan Viveretteb9f27222013-09-06 19:39:47 -07001362 return isFastScrollEnabled();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001363 }
1364
1365 /**
1366 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1367 * is computed based on the number of visible pixels in the visible items. This
1368 * however assumes that all list items have the same height. If you use a list in
1369 * which items have different heights, the scrollbar will change appearance as the
1370 * user scrolls through the list. To avoid this issue, you need to disable this
1371 * property.
1372 *
1373 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1374 * is based solely on the number of items in the adapter and the position of the
1375 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001376 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001377 *
1378 * @param enabled Whether or not to enable smooth scrollbar.
1379 *
Romain Guy0a637162009-05-29 14:43:54 -07001380 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1382 */
1383 public void setSmoothScrollbarEnabled(boolean enabled) {
1384 mSmoothScrollbarEnabled = enabled;
1385 }
1386
1387 /**
1388 * Returns the current state of the fast scroll feature.
1389 *
1390 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1391 *
1392 * @see #setSmoothScrollbarEnabled(boolean)
1393 */
1394 @ViewDebug.ExportedProperty
1395 public boolean isSmoothScrollbarEnabled() {
1396 return mSmoothScrollbarEnabled;
1397 }
1398
1399 /**
1400 * Set the listener that will receive notifications every time the list scrolls.
1401 *
1402 * @param l the scroll listener
1403 */
1404 public void setOnScrollListener(OnScrollListener l) {
1405 mOnScrollListener = l;
1406 invokeOnItemScrollListener();
1407 }
1408
1409 /**
1410 * Notify our scroll listener (if there is one) of a change in scroll state
1411 */
1412 void invokeOnItemScrollListener() {
1413 if (mFastScroller != null) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001414 mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 }
1416 if (mOnScrollListener != null) {
1417 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1418 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001419 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001420 }
1421
Svetoslav Ganova0156172011-06-26 17:55:44 -07001422 @Override
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001423 public void sendAccessibilityEvent(int eventType) {
1424 // Since this class calls onScrollChanged even if the mFirstPosition and the
1425 // child count have not changed we will avoid sending duplicate accessibility
1426 // events.
1427 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001428 final int firstVisiblePosition = getFirstVisiblePosition();
1429 final int lastVisiblePosition = getLastVisiblePosition();
1430 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1431 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001432 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001433 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001434 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1435 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001436 }
1437 }
1438 super.sendAccessibilityEvent(eventType);
1439 }
1440
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001441 @Override
1442 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1443 super.onInitializeAccessibilityEvent(event);
1444 event.setClassName(AbsListView.class.getName());
1445 }
1446
1447 @Override
1448 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1449 super.onInitializeAccessibilityNodeInfo(info);
1450 info.setClassName(AbsListView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001451 if (isEnabled()) {
1452 if (getFirstVisiblePosition() > 0) {
1453 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001454 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001455 }
1456 if (getLastVisiblePosition() < getCount() - 1) {
1457 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001458 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001459 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001460 }
1461 }
1462
1463 @Override
1464 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001465 if (super.performAccessibilityAction(action, arguments)) {
1466 return true;
1467 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001468 switch (action) {
1469 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001470 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001471 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1472 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1473 return true;
1474 }
1475 } return false;
1476 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001477 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001478 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1479 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1480 return true;
1481 }
1482 } return false;
1483 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001484 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001485 }
1486
Svetoslav5b578da2013-05-08 14:23:32 -07001487 /** @hide */
1488 @Override
1489 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1490 if (accessibilityId == getAccessibilityViewId()) {
1491 return this;
1492 }
1493 // If the data changed the children are invalid since the data model changed.
1494 // Hence, we pretend they do not exist. After a layout the children will sync
1495 // with the model at which point we notify that the accessibility state changed,
1496 // so a service will be able to re-fetch the views.
1497 if (mDataChanged) {
1498 return null;
1499 }
1500 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1501 }
1502
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 /**
1504 * Indicates whether the children's drawing cache is used during a scroll.
1505 * By default, the drawing cache is enabled but this will consume more memory.
1506 *
1507 * @return true if the scrolling cache is enabled, false otherwise
1508 *
1509 * @see #setScrollingCacheEnabled(boolean)
1510 * @see View#setDrawingCacheEnabled(boolean)
1511 */
1512 @ViewDebug.ExportedProperty
1513 public boolean isScrollingCacheEnabled() {
1514 return mScrollingCacheEnabled;
1515 }
1516
1517 /**
1518 * Enables or disables the children's drawing cache during a scroll.
1519 * By default, the drawing cache is enabled but this will use more memory.
1520 *
1521 * When the scrolling cache is enabled, the caches are kept after the
1522 * first scrolling. You can manually clear the cache by calling
1523 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1524 *
1525 * @param enabled true to enable the scroll cache, false otherwise
1526 *
1527 * @see #isScrollingCacheEnabled()
1528 * @see View#setDrawingCacheEnabled(boolean)
1529 */
1530 public void setScrollingCacheEnabled(boolean enabled) {
1531 if (mScrollingCacheEnabled && !enabled) {
1532 clearScrollingCache();
1533 }
1534 mScrollingCacheEnabled = enabled;
1535 }
1536
1537 /**
1538 * Enables or disables the type filter window. If enabled, typing when
1539 * this view has focus will filter the children to match the users input.
1540 * Note that the {@link Adapter} used by this view must implement the
1541 * {@link Filterable} interface.
1542 *
1543 * @param textFilterEnabled true to enable type filtering, false otherwise
1544 *
1545 * @see Filterable
1546 */
1547 public void setTextFilterEnabled(boolean textFilterEnabled) {
1548 mTextFilterEnabled = textFilterEnabled;
1549 }
1550
1551 /**
1552 * Indicates whether type filtering is enabled for this view
1553 *
1554 * @return true if type filtering is enabled, false otherwise
1555 *
1556 * @see #setTextFilterEnabled(boolean)
1557 * @see Filterable
1558 */
1559 @ViewDebug.ExportedProperty
1560 public boolean isTextFilterEnabled() {
1561 return mTextFilterEnabled;
1562 }
1563
1564 @Override
1565 public void getFocusedRect(Rect r) {
1566 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001567 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 // the focused rectangle of the selected view offset into the
1569 // coordinate space of this view.
1570 view.getFocusedRect(r);
1571 offsetDescendantRectToMyCoords(view, r);
1572 } else {
1573 // otherwise, just the norm
1574 super.getFocusedRect(r);
1575 }
1576 }
1577
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001578 private void useDefaultSelector() {
1579 setSelector(getResources().getDrawable(
1580 com.android.internal.R.drawable.list_selector_background));
1581 }
1582
1583 /**
1584 * Indicates whether the content of this view is pinned to, or stacked from,
1585 * the bottom edge.
1586 *
1587 * @return true if the content is stacked from the bottom edge, false otherwise
1588 */
1589 @ViewDebug.ExportedProperty
1590 public boolean isStackFromBottom() {
1591 return mStackFromBottom;
1592 }
1593
1594 /**
1595 * When stack from bottom is set to true, the list fills its content starting from
1596 * the bottom of the view.
1597 *
1598 * @param stackFromBottom true to pin the view's content to the bottom edge,
1599 * false to pin the view's content to the top edge
1600 */
1601 public void setStackFromBottom(boolean stackFromBottom) {
1602 if (mStackFromBottom != stackFromBottom) {
1603 mStackFromBottom = stackFromBottom;
1604 requestLayoutIfNecessary();
1605 }
1606 }
1607
1608 void requestLayoutIfNecessary() {
1609 if (getChildCount() > 0) {
1610 resetList();
1611 requestLayout();
1612 invalidate();
1613 }
1614 }
1615
1616 static class SavedState extends BaseSavedState {
1617 long selectedId;
1618 long firstId;
1619 int viewTop;
1620 int position;
1621 int height;
1622 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001623 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001624 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001625 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001626 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001627
1628 /**
1629 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1630 */
1631 SavedState(Parcelable superState) {
1632 super(superState);
1633 }
1634
1635 /**
1636 * Constructor called from {@link #CREATOR}
1637 */
1638 private SavedState(Parcel in) {
1639 super(in);
1640 selectedId = in.readLong();
1641 firstId = in.readLong();
1642 viewTop = in.readInt();
1643 position = in.readInt();
1644 height = in.readInt();
1645 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001646 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001647 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001648 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001649 final int N = in.readInt();
1650 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001651 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001652 for (int i=0; i<N; i++) {
1653 final long key = in.readLong();
1654 final int value = in.readInt();
1655 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001656 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001657 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001658 }
1659
1660 @Override
1661 public void writeToParcel(Parcel out, int flags) {
1662 super.writeToParcel(out, flags);
1663 out.writeLong(selectedId);
1664 out.writeLong(firstId);
1665 out.writeInt(viewTop);
1666 out.writeInt(position);
1667 out.writeInt(height);
1668 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001669 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001670 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001671 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001672 final int N = checkIdState != null ? checkIdState.size() : 0;
1673 out.writeInt(N);
1674 for (int i=0; i<N; i++) {
1675 out.writeLong(checkIdState.keyAt(i));
1676 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001677 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001678 }
1679
1680 @Override
1681 public String toString() {
1682 return "AbsListView.SavedState{"
1683 + Integer.toHexString(System.identityHashCode(this))
1684 + " selectedId=" + selectedId
1685 + " firstId=" + firstId
1686 + " viewTop=" + viewTop
1687 + " position=" + position
1688 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001689 + " filter=" + filter
1690 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001691 }
1692
1693 public static final Parcelable.Creator<SavedState> CREATOR
1694 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001695 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001696 public SavedState createFromParcel(Parcel in) {
1697 return new SavedState(in);
1698 }
1699
Alan Viverette8fa327a2013-05-31 14:53:13 -07001700 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001701 public SavedState[] newArray(int size) {
1702 return new SavedState[size];
1703 }
1704 };
1705 }
1706
1707 @Override
1708 public Parcelable onSaveInstanceState() {
1709 /*
1710 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001711 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001712 * that happen early enough to keep from getting complaints
1713 * about having leaked the window.
1714 */
1715 dismissPopup();
1716
1717 Parcelable superState = super.onSaveInstanceState();
1718
1719 SavedState ss = new SavedState(superState);
1720
Dianne Hackborne181bd92012-09-25 14:15:15 -07001721 if (mPendingSync != null) {
1722 // Just keep what we last restored.
1723 ss.selectedId = mPendingSync.selectedId;
1724 ss.firstId = mPendingSync.firstId;
1725 ss.viewTop = mPendingSync.viewTop;
1726 ss.position = mPendingSync.position;
1727 ss.height = mPendingSync.height;
1728 ss.filter = mPendingSync.filter;
1729 ss.inActionMode = mPendingSync.inActionMode;
1730 ss.checkedItemCount = mPendingSync.checkedItemCount;
1731 ss.checkState = mPendingSync.checkState;
1732 ss.checkIdState = mPendingSync.checkIdState;
1733 return ss;
1734 }
1735
Dianne Hackborn99441c42010-12-15 11:02:55 -08001736 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001737 long selectedId = getSelectedItemId();
1738 ss.selectedId = selectedId;
1739 ss.height = getHeight();
1740
1741 if (selectedId >= 0) {
1742 // Remember the selection
1743 ss.viewTop = mSelectedTop;
1744 ss.position = getSelectedItemPosition();
1745 ss.firstId = INVALID_POSITION;
1746 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001747 if (haveChildren && mFirstPosition > 0) {
1748 // Remember the position of the first child.
1749 // We only do this if we are not currently at the top of
1750 // the list, for two reasons:
1751 // (1) The list may be in the process of becoming empty, in
1752 // which case mItemCount may not be 0, but if we try to
1753 // ask for any information about position 0 we will crash.
1754 // (2) Being "at the top" seems like a special case, anyway,
1755 // and the user wouldn't expect to end up somewhere else when
1756 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001757 View v = getChildAt(0);
1758 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001759 int firstPos = mFirstPosition;
1760 if (firstPos >= mItemCount) {
1761 firstPos = mItemCount - 1;
1762 }
1763 ss.position = firstPos;
1764 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001765 } else {
1766 ss.viewTop = 0;
1767 ss.firstId = INVALID_POSITION;
1768 ss.position = 0;
1769 }
1770 }
1771
1772 ss.filter = null;
1773 if (mFiltered) {
1774 final EditText textFilter = mTextFilter;
1775 if (textFilter != null) {
1776 Editable filterText = textFilter.getText();
1777 if (filterText != null) {
1778 ss.filter = filterText.toString();
1779 }
1780 }
1781 }
1782
Adam Powella0eeeac2010-11-05 11:55:05 -07001783 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1784
Adam Powell9a5cc282011-08-28 16:18:16 -07001785 if (mCheckStates != null) {
1786 ss.checkState = mCheckStates.clone();
1787 }
1788 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001789 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001790 final int count = mCheckedIdStates.size();
1791 for (int i = 0; i < count; i++) {
1792 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1793 }
1794 ss.checkIdState = idState;
1795 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001796 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001797
Adam Cohen335c3b62012-07-24 17:18:16 -07001798 if (mRemoteAdapter != null) {
1799 mRemoteAdapter.saveRemoteViewsCache();
1800 }
1801
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001802 return ss;
1803 }
1804
1805 @Override
1806 public void onRestoreInstanceState(Parcelable state) {
1807 SavedState ss = (SavedState) state;
1808
1809 super.onRestoreInstanceState(ss.getSuperState());
1810 mDataChanged = true;
1811
1812 mSyncHeight = ss.height;
1813
1814 if (ss.selectedId >= 0) {
1815 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001816 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001817 mSyncRowId = ss.selectedId;
1818 mSyncPosition = ss.position;
1819 mSpecificTop = ss.viewTop;
1820 mSyncMode = SYNC_SELECTED_POSITION;
1821 } else if (ss.firstId >= 0) {
1822 setSelectedPositionInt(INVALID_POSITION);
1823 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1824 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001825 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001826 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001827 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001828 mSyncRowId = ss.firstId;
1829 mSyncPosition = ss.position;
1830 mSpecificTop = ss.viewTop;
1831 mSyncMode = SYNC_FIRST_POSITION;
1832 }
1833
1834 setFilterText(ss.filter);
1835
Adam Powellf343e1b2010-08-13 18:27:04 -07001836 if (ss.checkState != null) {
1837 mCheckStates = ss.checkState;
1838 }
1839
1840 if (ss.checkIdState != null) {
1841 mCheckedIdStates = ss.checkIdState;
1842 }
1843
Adam Powell2614c6c2010-11-04 17:54:45 -07001844 mCheckedItemCount = ss.checkedItemCount;
1845
Adam Powella0eeeac2010-11-05 11:55:05 -07001846 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1847 mMultiChoiceModeCallback != null) {
1848 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1849 }
1850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001851 requestLayout();
1852 }
1853
1854 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001855 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1856 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001857 }
1858
1859 /**
1860 * Sets the initial value for the text filter.
1861 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001862 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001863 * @see #setTextFilterEnabled
1864 */
1865 public void setFilterText(String filterText) {
1866 // TODO: Should we check for acceptFilter()?
1867 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1868 createTextFilter(false);
1869 // This is going to call our listener onTextChanged, but we might not
1870 // be ready to bring up a window yet
1871 mTextFilter.setText(filterText);
1872 mTextFilter.setSelection(filterText.length());
1873 if (mAdapter instanceof Filterable) {
1874 // if mPopup is non-null, then onTextChanged will do the filtering
1875 if (mPopup == null) {
1876 Filter f = ((Filterable) mAdapter).getFilter();
1877 f.filter(filterText);
1878 }
1879 // Set filtered to true so we will display the filter window when our main
1880 // window is ready
1881 mFiltered = true;
1882 mDataSetObserver.clearSavedState();
1883 }
1884 }
1885 }
1886
1887 /**
Romain Guy0a637162009-05-29 14:43:54 -07001888 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001889 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001890 */
1891 public CharSequence getTextFilter() {
1892 if (mTextFilterEnabled && mTextFilter != null) {
1893 return mTextFilter.getText();
1894 }
1895 return null;
1896 }
Romain Guy0a637162009-05-29 14:43:54 -07001897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001898 @Override
1899 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1900 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1901 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powell31986b52013-09-24 14:53:30 -07001902 if (!isAttachedToWindow() && mAdapter != null) {
Adam Powellb3750132011-08-08 23:29:12 -07001903 // Data may have changed while we were detached and it's valid
1904 // to change focus while detached. Refresh so we don't die.
1905 mDataChanged = true;
1906 mOldItemCount = mItemCount;
1907 mItemCount = mAdapter.getCount();
1908 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001909 resurrectSelection();
1910 }
1911 }
1912
1913 @Override
1914 public void requestLayout() {
1915 if (!mBlockLayoutRequests && !mInLayout) {
1916 super.requestLayout();
1917 }
1918 }
1919
1920 /**
1921 * The list is empty. Clear everything out.
1922 */
1923 void resetList() {
1924 removeAllViewsInLayout();
1925 mFirstPosition = 0;
1926 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001927 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001928 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001929 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001930 mOldSelectedPosition = INVALID_POSITION;
1931 mOldSelectedRowId = INVALID_ROW_ID;
1932 setSelectedPositionInt(INVALID_POSITION);
1933 setNextSelectedPositionInt(INVALID_POSITION);
1934 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07001935 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001936 mSelectorRect.setEmpty();
1937 invalidate();
1938 }
1939
1940 @Override
1941 protected int computeVerticalScrollExtent() {
1942 final int count = getChildCount();
1943 if (count > 0) {
1944 if (mSmoothScrollbarEnabled) {
1945 int extent = count * 100;
1946
1947 View view = getChildAt(0);
1948 final int top = view.getTop();
1949 int height = view.getHeight();
1950 if (height > 0) {
1951 extent += (top * 100) / height;
1952 }
1953
1954 view = getChildAt(count - 1);
1955 final int bottom = view.getBottom();
1956 height = view.getHeight();
1957 if (height > 0) {
1958 extent -= ((bottom - getHeight()) * 100) / height;
1959 }
1960
1961 return extent;
1962 } else {
1963 return 1;
1964 }
1965 }
1966 return 0;
1967 }
1968
1969 @Override
1970 protected int computeVerticalScrollOffset() {
1971 final int firstPosition = mFirstPosition;
1972 final int childCount = getChildCount();
1973 if (firstPosition >= 0 && childCount > 0) {
1974 if (mSmoothScrollbarEnabled) {
1975 final View view = getChildAt(0);
1976 final int top = view.getTop();
1977 int height = view.getHeight();
1978 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08001979 return Math.max(firstPosition * 100 - (top * 100) / height +
1980 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001981 }
1982 } else {
1983 int index;
1984 final int count = mItemCount;
1985 if (firstPosition == 0) {
1986 index = 0;
1987 } else if (firstPosition + childCount == count) {
1988 index = count;
1989 } else {
1990 index = firstPosition + childCount / 2;
1991 }
1992 return (int) (firstPosition + childCount * (index / (float) count));
1993 }
1994 }
1995 return 0;
1996 }
1997
1998 @Override
1999 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002000 int result;
2001 if (mSmoothScrollbarEnabled) {
2002 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002003 if (mScrollY != 0) {
2004 // Compensate for overscroll
2005 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2006 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002007 } else {
2008 result = mItemCount;
2009 }
2010 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002011 }
2012
2013 @Override
2014 protected float getTopFadingEdgeStrength() {
2015 final int count = getChildCount();
2016 final float fadeEdge = super.getTopFadingEdgeStrength();
2017 if (count == 0) {
2018 return fadeEdge;
2019 } else {
2020 if (mFirstPosition > 0) {
2021 return 1.0f;
2022 }
2023
2024 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002025 final float fadeLength = getVerticalFadingEdgeLength();
2026 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002027 }
2028 }
2029
2030 @Override
2031 protected float getBottomFadingEdgeStrength() {
2032 final int count = getChildCount();
2033 final float fadeEdge = super.getBottomFadingEdgeStrength();
2034 if (count == 0) {
2035 return fadeEdge;
2036 } else {
2037 if (mFirstPosition + count - 1 < mItemCount - 1) {
2038 return 1.0f;
2039 }
2040
2041 final int bottom = getChildAt(count - 1).getBottom();
2042 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07002043 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002044 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07002045 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002046 }
2047 }
2048
2049 @Override
2050 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2051 if (mSelector == null) {
2052 useDefaultSelector();
2053 }
2054 final Rect listPadding = mListPadding;
2055 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2056 listPadding.top = mSelectionTopPadding + mPaddingTop;
2057 listPadding.right = mSelectionRightPadding + mPaddingRight;
2058 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002059
2060 // Check if our previous measured size was at a point where we should scroll later.
2061 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2062 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002063 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002064 final View lastChild = getChildAt(childCount - 1);
2065 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002066 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002067 lastBottom <= listBottom;
2068 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002069 }
2070
Romain Guyd6a463a2009-05-21 23:10:10 -07002071 /**
2072 * Subclasses should NOT override this method but
2073 * {@link #layoutChildren()} instead.
2074 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002075 @Override
2076 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2077 super.onLayout(changed, l, t, r, b);
2078 mInLayout = true;
Adam Powellf3c2eda2010-03-16 17:31:01 -07002079 if (changed) {
2080 int childCount = getChildCount();
2081 for (int i = 0; i < childCount; i++) {
2082 getChildAt(i).forceLayout();
2083 }
2084 mRecycler.markChildrenDirty();
2085 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002086
Adam Powell5db566f2013-10-13 17:19:10 -07002087 if (mFastScroller != null && (mItemCount != mOldItemCount || mDataChanged)) {
2088 mFastScroller.onItemCountChanged(mItemCount);
Adam Powell2c6196a2010-12-10 14:31:54 -08002089 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07002090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002091 layoutChildren();
2092 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002093
2094 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002095 }
2096
2097 /**
2098 * @hide
2099 */
2100 @Override
2101 protected boolean setFrame(int left, int top, int right, int bottom) {
2102 final boolean changed = super.setFrame(left, top, right, bottom);
2103
Romain Guyd6a463a2009-05-21 23:10:10 -07002104 if (changed) {
2105 // Reposition the popup when the frame has changed. This includes
2106 // translating the widget, not just changing its dimension. The
2107 // filter popup needs to follow the widget.
2108 final boolean visible = getWindowVisibility() == View.VISIBLE;
2109 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2110 positionPopup();
2111 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002112 }
2113
2114 return changed;
2115 }
2116
Romain Guyd6a463a2009-05-21 23:10:10 -07002117 /**
2118 * Subclasses must override this method to layout their children.
2119 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002120 protected void layoutChildren() {
2121 }
2122
2123 void updateScrollIndicators() {
2124 if (mScrollUp != null) {
2125 boolean canScrollUp;
2126 // 0th element is not visible
2127 canScrollUp = mFirstPosition > 0;
2128
2129 // ... Or top of 0th element is not visible
2130 if (!canScrollUp) {
2131 if (getChildCount() > 0) {
2132 View child = getChildAt(0);
2133 canScrollUp = child.getTop() < mListPadding.top;
2134 }
2135 }
2136
2137 mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
2138 }
2139
2140 if (mScrollDown != null) {
2141 boolean canScrollDown;
2142 int count = getChildCount();
2143
2144 // Last item is not visible
2145 canScrollDown = (mFirstPosition + count) < mItemCount;
2146
2147 // ... Or bottom of the last element is not visible
2148 if (!canScrollDown && count > 0) {
2149 View child = getChildAt(count - 1);
2150 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2151 }
2152
2153 mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
2154 }
2155 }
2156
2157 @Override
2158 @ViewDebug.ExportedProperty
2159 public View getSelectedView() {
2160 if (mItemCount > 0 && mSelectedPosition >= 0) {
2161 return getChildAt(mSelectedPosition - mFirstPosition);
2162 } else {
2163 return null;
2164 }
2165 }
2166
2167 /**
2168 * List padding is the maximum of the normal view's padding and the padding of the selector.
2169 *
2170 * @see android.view.View#getPaddingTop()
2171 * @see #getSelector()
2172 *
2173 * @return The top list padding.
2174 */
2175 public int getListPaddingTop() {
2176 return mListPadding.top;
2177 }
2178
2179 /**
2180 * List padding is the maximum of the normal view's padding and the padding of the selector.
2181 *
2182 * @see android.view.View#getPaddingBottom()
2183 * @see #getSelector()
2184 *
2185 * @return The bottom list padding.
2186 */
2187 public int getListPaddingBottom() {
2188 return mListPadding.bottom;
2189 }
2190
2191 /**
2192 * List padding is the maximum of the normal view's padding and the padding of the selector.
2193 *
2194 * @see android.view.View#getPaddingLeft()
2195 * @see #getSelector()
2196 *
2197 * @return The left list padding.
2198 */
2199 public int getListPaddingLeft() {
2200 return mListPadding.left;
2201 }
2202
2203 /**
2204 * List padding is the maximum of the normal view's padding and the padding of the selector.
2205 *
2206 * @see android.view.View#getPaddingRight()
2207 * @see #getSelector()
2208 *
2209 * @return The right list padding.
2210 */
2211 public int getListPaddingRight() {
2212 return mListPadding.right;
2213 }
2214
2215 /**
2216 * Get a view and have it show the data associated with the specified
2217 * position. This is called when we have already discovered that the view is
2218 * not available for reuse in the recycle bin. The only choices left are
2219 * converting an old view or making a new one.
2220 *
2221 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002222 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2223 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002224 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002225 * @return A view displaying the data associated with the specified position
2226 */
Romain Guy21875052010-01-06 18:48:08 -08002227 View obtainView(int position, boolean[] isScrap) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002228 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2229
Romain Guy21875052010-01-06 18:48:08 -08002230 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002231 View scrapView;
2232
Adam Powell539ee872012-02-03 19:00:49 -08002233 scrapView = mRecycler.getTransientStateView(position);
Alan Viverettef9736d32013-09-30 12:44:54 -07002234 if (scrapView == null) {
2235 scrapView = mRecycler.getScrapView(position);
Adam Powell539ee872012-02-03 19:00:49 -08002236 }
2237
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002238 View child;
2239 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002240 child = mAdapter.getView(position, scrapView, this);
2241
Svetoslav Ganov42138042012-03-20 11:51:39 -07002242 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2243 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2244 }
2245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002246 if (child != scrapView) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002247 mRecycler.addScrapView(scrapView, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002248 if (mCacheColorHint != 0) {
2249 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2250 }
Romain Guy21875052010-01-06 18:48:08 -08002251 } else {
Romain Guya440b002010-02-24 15:57:54 -08002252 isScrap[0] = true;
Alan Viverette1e51cc72013-09-27 14:32:20 -07002253
2254 // Clear any system-managed transient state so that we can
2255 // recycle this view and bind it to different data.
2256 if (child.isAccessibilityFocused()) {
2257 child.clearAccessibilityFocus();
2258 }
2259
Romain Guya440b002010-02-24 15:57:54 -08002260 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002261 }
2262 } else {
2263 child = mAdapter.getView(position, null, this);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002264
2265 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2266 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2267 }
2268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002269 if (mCacheColorHint != 0) {
2270 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2271 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002272 }
2273
Adam Powellaebd28f2012-02-22 10:31:16 -08002274 if (mAdapterHasStableIds) {
2275 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2276 LayoutParams lp;
2277 if (vlp == null) {
2278 lp = (LayoutParams) generateDefaultLayoutParams();
2279 } else if (!checkLayoutParams(vlp)) {
2280 lp = (LayoutParams) generateLayoutParams(vlp);
2281 } else {
2282 lp = (LayoutParams) vlp;
2283 }
2284 lp.itemId = mAdapter.getItemId(position);
2285 child.setLayoutParams(lp);
2286 }
2287
alanvc1d7e772012-05-08 14:47:24 -07002288 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2289 if (mAccessibilityDelegate == null) {
2290 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2291 }
alanvb72fe7a2012-08-27 16:44:25 -07002292 if (child.getAccessibilityDelegate() == null) {
2293 child.setAccessibilityDelegate(mAccessibilityDelegate);
2294 }
alanvc1d7e772012-05-08 14:47:24 -07002295 }
2296
Romain Guy5fade8c2013-07-10 16:36:18 -07002297 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2298
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002299 return child;
2300 }
2301
alanvc1d7e772012-05-08 14:47:24 -07002302 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2303 @Override
Svetoslav Ganov50776862013-05-17 18:06:31 -07002304 public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
2305 // If the data changed the children are invalid since the data model changed.
2306 // Hence, we pretend they do not exist. After a layout the children will sync
2307 // with the model at which point we notify that the accessibility state changed,
2308 // so a service will be able to re-fetch the views.
2309 if (mDataChanged) {
2310 return null;
2311 }
2312 return super.createAccessibilityNodeInfo(host);
2313 }
2314
2315 @Override
alanvc1d7e772012-05-08 14:47:24 -07002316 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2317 super.onInitializeAccessibilityNodeInfo(host, info);
2318
2319 final int position = getPositionForView(host);
Alan Viverette5b2081d2013-08-28 10:43:07 -07002320 onInitializeAccessibilityNodeInfoForItem(host, position, info);
alanvc1d7e772012-05-08 14:47:24 -07002321 }
2322
2323 @Override
2324 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002325 if (super.performAccessibilityAction(host, action, arguments)) {
2326 return true;
2327 }
2328
alanvc1d7e772012-05-08 14:47:24 -07002329 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002330 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002331
alanv9c3e0e62012-05-18 17:43:35 -07002332 if ((position == INVALID_POSITION) || (adapter == null)) {
2333 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002334 return false;
2335 }
2336
alanv9c3e0e62012-05-18 17:43:35 -07002337 if (!isEnabled() || !adapter.isEnabled(position)) {
2338 // Cannot perform actions on disabled items.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002339 return false;
2340 }
2341
alanvc1d7e772012-05-08 14:47:24 -07002342 final long id = getItemIdAtPosition(position);
2343
2344 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002345 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2346 if (getSelectedItemPosition() == position) {
2347 setSelection(INVALID_POSITION);
2348 return true;
2349 }
2350 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002351 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002352 if (getSelectedItemPosition() != position) {
2353 setSelection(position);
2354 return true;
2355 }
2356 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002357 case AccessibilityNodeInfo.ACTION_CLICK: {
2358 if (isClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002359 return performItemClick(host, position, id);
2360 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002361 } return false;
2362 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2363 if (isLongClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002364 return performLongPress(host, position, id);
2365 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002366 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002367 }
2368
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002369 return false;
alanvc1d7e772012-05-08 14:47:24 -07002370 }
2371 }
2372
Alan Viverette5b2081d2013-08-28 10:43:07 -07002373 /**
2374 * Initializes an {@link AccessibilityNodeInfo} with information about a
2375 * particular item in the list.
2376 *
2377 * @param view View representing the list item.
2378 * @param position Position of the list item within the adapter.
2379 * @param info Node info to populate.
2380 */
2381 public void onInitializeAccessibilityNodeInfoForItem(
2382 View view, int position, AccessibilityNodeInfo info) {
2383 final ListAdapter adapter = getAdapter();
2384 if (position == INVALID_POSITION || adapter == null) {
2385 // The item doesn't exist, so there's not much we can do here.
2386 return;
2387 }
2388
2389 if (!isEnabled() || !adapter.isEnabled(position)) {
2390 info.setEnabled(false);
2391 return;
2392 }
2393
2394 if (position == getSelectedItemPosition()) {
2395 info.setSelected(true);
2396 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
2397 } else {
2398 info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
2399 }
2400
2401 if (isClickable()) {
2402 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2403 info.setClickable(true);
2404 }
2405
2406 if (isLongClickable()) {
2407 info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
2408 info.setLongClickable(true);
2409 }
2410 }
2411
Dianne Hackborn079e2352010-10-18 17:02:43 -07002412 void positionSelector(int position, View sel) {
2413 if (position != INVALID_POSITION) {
2414 mSelectorPosition = position;
2415 }
2416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002417 final Rect selectorRect = mSelectorRect;
2418 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002419 if (sel instanceof SelectionBoundsAdjuster) {
2420 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2421 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002422 positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
2423 selectorRect.bottom);
2424
2425 final boolean isChildViewEnabled = mIsChildViewEnabled;
2426 if (sel.isEnabled() != isChildViewEnabled) {
2427 mIsChildViewEnabled = !isChildViewEnabled;
Jason Bayera79f4b72011-03-22 20:03:31 -07002428 if (getSelectedItemPosition() != INVALID_POSITION) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002429 refreshDrawableState();
2430 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002431 }
2432 }
2433
2434 private void positionSelector(int l, int t, int r, int b) {
2435 mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
2436 + mSelectionRightPadding, b + mSelectionBottomPadding);
2437 }
2438
2439 @Override
2440 protected void dispatchDraw(Canvas canvas) {
2441 int saveCount = 0;
2442 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2443 if (clipToPadding) {
2444 saveCount = canvas.save();
2445 final int scrollX = mScrollX;
2446 final int scrollY = mScrollY;
2447 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2448 scrollX + mRight - mLeft - mPaddingRight,
2449 scrollY + mBottom - mTop - mPaddingBottom);
2450 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2451 }
2452
2453 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2454 if (!drawSelectorOnTop) {
2455 drawSelector(canvas);
2456 }
2457
2458 super.dispatchDraw(canvas);
2459
2460 if (drawSelectorOnTop) {
2461 drawSelector(canvas);
2462 }
2463
2464 if (clipToPadding) {
2465 canvas.restoreToCount(saveCount);
2466 mGroupFlags |= CLIP_TO_PADDING_MASK;
2467 }
2468 }
2469
2470 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002471 protected boolean isPaddingOffsetRequired() {
2472 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2473 }
2474
2475 @Override
2476 protected int getLeftPaddingOffset() {
2477 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2478 }
2479
2480 @Override
2481 protected int getTopPaddingOffset() {
2482 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2483 }
2484
2485 @Override
2486 protected int getRightPaddingOffset() {
2487 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2488 }
2489
2490 @Override
2491 protected int getBottomPaddingOffset() {
2492 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2493 }
2494
2495 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002496 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2497 if (getChildCount() > 0) {
2498 mDataChanged = true;
2499 rememberSyncState();
2500 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002501
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002502 if (mFastScroller != null) {
2503 mFastScroller.onSizeChanged(w, h, oldw, oldh);
2504 }
2505 }
2506
2507 /**
2508 * @return True if the current touch mode requires that we draw the selector in the pressed
2509 * state.
2510 */
2511 boolean touchModeDrawsInPressedState() {
2512 // FIXME use isPressed for this
2513 switch (mTouchMode) {
2514 case TOUCH_MODE_TAP:
2515 case TOUCH_MODE_DONE_WAITING:
2516 return true;
2517 default:
2518 return false;
2519 }
2520 }
2521
2522 /**
2523 * Indicates whether this view is in a state where the selector should be drawn. This will
2524 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2525 * the pressed state for an item.
2526 *
2527 * @return True if the selector should be shown
2528 */
2529 boolean shouldShowSelector() {
Alan Viverette74ded292013-06-03 15:34:11 -07002530 return (!isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002531 }
2532
2533 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002534 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002535 final Drawable selector = mSelector;
2536 selector.setBounds(mSelectorRect);
2537 selector.draw(canvas);
2538 }
2539 }
2540
2541 /**
2542 * Controls whether the selection highlight drawable should be drawn on top of the item or
2543 * behind it.
2544 *
2545 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2546 * is false.
2547 *
2548 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2549 */
2550 public void setDrawSelectorOnTop(boolean onTop) {
2551 mDrawSelectorOnTop = onTop;
2552 }
2553
2554 /**
2555 * Set a Drawable that should be used to highlight the currently selected item.
2556 *
2557 * @param resID A Drawable resource to use as the selection highlight.
2558 *
2559 * @attr ref android.R.styleable#AbsListView_listSelector
2560 */
2561 public void setSelector(int resID) {
2562 setSelector(getResources().getDrawable(resID));
2563 }
2564
2565 public void setSelector(Drawable sel) {
2566 if (mSelector != null) {
2567 mSelector.setCallback(null);
2568 unscheduleDrawable(mSelector);
2569 }
2570 mSelector = sel;
2571 Rect padding = new Rect();
2572 sel.getPadding(padding);
2573 mSelectionLeftPadding = padding.left;
2574 mSelectionTopPadding = padding.top;
2575 mSelectionRightPadding = padding.right;
2576 mSelectionBottomPadding = padding.bottom;
2577 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002578 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002579 }
2580
2581 /**
2582 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2583 * selection in the list.
2584 *
2585 * @return the drawable used to display the selector
2586 */
2587 public Drawable getSelector() {
2588 return mSelector;
2589 }
2590
2591 /**
2592 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2593 * this is a long press.
2594 */
2595 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002596 if (!isEnabled() || !isClickable()) {
2597 return;
2598 }
2599
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002600 Drawable selector = mSelector;
2601 Rect selectorRect = mSelectorRect;
2602 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002603 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002604
2605 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2606
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002607 if (v != null) {
2608 if (v.hasFocusable()) return;
2609 v.setPressed(true);
2610 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002611 setPressed(true);
2612
2613 final boolean longClickable = isLongClickable();
2614 Drawable d = selector.getCurrent();
2615 if (d != null && d instanceof TransitionDrawable) {
2616 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002617 ((TransitionDrawable) d).startTransition(
2618 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002619 } else {
2620 ((TransitionDrawable) d).resetTransition();
2621 }
2622 }
2623 if (longClickable && !mDataChanged) {
2624 if (mPendingCheckForKeyLongPress == null) {
2625 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2626 }
2627 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2628 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2629 }
2630 }
2631 }
2632
2633 public void setScrollIndicators(View up, View down) {
2634 mScrollUp = up;
2635 mScrollDown = down;
2636 }
2637
Dianne Hackborn079e2352010-10-18 17:02:43 -07002638 void updateSelectorState() {
2639 if (mSelector != null) {
2640 if (shouldShowSelector()) {
2641 mSelector.setState(getDrawableState());
2642 } else {
2643 mSelector.setState(StateSet.NOTHING);
2644 }
2645 }
2646 }
2647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002648 @Override
2649 protected void drawableStateChanged() {
2650 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002651 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002652 }
2653
2654 @Override
2655 protected int[] onCreateDrawableState(int extraSpace) {
2656 // If the child view is enabled then do the default behavior.
2657 if (mIsChildViewEnabled) {
2658 // Common case
2659 return super.onCreateDrawableState(extraSpace);
2660 }
2661
2662 // The selector uses this View's drawable state. The selected child view
2663 // is disabled, so we need to remove the enabled state from the drawable
2664 // states.
2665 final int enabledState = ENABLED_STATE_SET[0];
2666
2667 // If we don't have any extra space, it will return one of the static state arrays,
2668 // and clearing the enabled state on those arrays is a bad thing! If we specify
2669 // we need extra space, it will create+copy into a new array that safely mutable.
2670 int[] state = super.onCreateDrawableState(extraSpace + 1);
2671 int enabledPos = -1;
2672 for (int i = state.length - 1; i >= 0; i--) {
2673 if (state[i] == enabledState) {
2674 enabledPos = i;
2675 break;
2676 }
2677 }
2678
2679 // Remove the enabled state
2680 if (enabledPos >= 0) {
2681 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2682 state.length - enabledPos - 1);
2683 }
Romain Guy0a637162009-05-29 14:43:54 -07002684
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002685 return state;
2686 }
2687
2688 @Override
2689 public boolean verifyDrawable(Drawable dr) {
2690 return mSelector == dr || super.verifyDrawable(dr);
2691 }
2692
2693 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002694 public void jumpDrawablesToCurrentState() {
2695 super.jumpDrawablesToCurrentState();
2696 if (mSelector != null) mSelector.jumpToCurrentState();
2697 }
2698
2699 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002700 protected void onAttachedToWindow() {
2701 super.onAttachedToWindow();
2702
2703 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002704 treeObserver.addOnTouchModeChangeListener(this);
2705 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2706 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002707 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002708
Romain Guy82afc7b2010-05-13 11:52:37 -07002709 if (mAdapter != null && mDataSetObserver == null) {
2710 mDataSetObserver = new AdapterDataSetObserver();
2711 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002712
2713 // Data may have changed while we were detached. Refresh.
2714 mDataChanged = true;
2715 mOldItemCount = mItemCount;
2716 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002717 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002718 }
2719
2720 @Override
2721 protected void onDetachedFromWindow() {
2722 super.onDetachedFromWindow();
2723
Romain Guy1f7f3c32009-07-22 11:25:42 -07002724 // Dismiss the popup in case onSaveInstanceState() was not invoked
2725 dismissPopup();
2726
Romain Guy21875052010-01-06 18:48:08 -08002727 // Detach any view left in the scrap heap
2728 mRecycler.clear();
2729
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002730 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002731 treeObserver.removeOnTouchModeChangeListener(this);
2732 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002733 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002734 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002735 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002736
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002737 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002738 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2739 mDataSetObserver = null;
2740 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002741
2742 if (mScrollStrictSpan != null) {
2743 mScrollStrictSpan.finish();
2744 mScrollStrictSpan = null;
2745 }
2746
2747 if (mFlingStrictSpan != null) {
2748 mFlingStrictSpan.finish();
2749 mFlingStrictSpan = null;
2750 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002751
2752 if (mFlingRunnable != null) {
2753 removeCallbacks(mFlingRunnable);
2754 }
2755
2756 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002757 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002758 }
2759
2760 if (mClearScrollingCache != null) {
2761 removeCallbacks(mClearScrollingCache);
2762 }
2763
2764 if (mPerformClick != null) {
2765 removeCallbacks(mPerformClick);
2766 }
2767
2768 if (mTouchModeReset != null) {
2769 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002770 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002771 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002772 }
2773
2774 @Override
2775 public void onWindowFocusChanged(boolean hasWindowFocus) {
2776 super.onWindowFocusChanged(hasWindowFocus);
2777
2778 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2779
2780 if (!hasWindowFocus) {
2781 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002782 if (mFlingRunnable != null) {
2783 removeCallbacks(mFlingRunnable);
2784 // let the fling runnable report it's new state which
2785 // should be idle
2786 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002787 if (mPositionScroller != null) {
2788 mPositionScroller.stop();
2789 }
Adam Powell45803472010-01-25 15:10:44 -08002790 if (mScrollY != 0) {
2791 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002792 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002793 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002794 invalidate();
2795 }
Mark Wagner670dd812010-01-13 16:17:47 -08002796 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002797 // Always hide the type filter
2798 dismissPopup();
2799
2800 if (touchMode == TOUCH_MODE_OFF) {
2801 // Remember the last selected element
2802 mResurrectToPosition = mSelectedPosition;
2803 }
2804 } else {
Adam Powell97566042010-03-09 15:34:09 -08002805 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002806 // Show the type filter only if a filter is in effect
2807 showPopup();
2808 }
2809
2810 // If we changed touch mode since the last time we had focus
2811 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2812 // If we come back in trackball mode, we bring the selection back
2813 if (touchMode == TOUCH_MODE_OFF) {
2814 // This will trigger a layout
2815 resurrectSelection();
2816
2817 // If we come back in touch mode, then we want to hide the selector
2818 } else {
2819 hideSelector();
2820 mLayoutMode = LAYOUT_NORMAL;
2821 layoutChildren();
2822 }
2823 }
2824 }
2825
2826 mLastTouchMode = touchMode;
2827 }
2828
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07002829 @Override
2830 public void onRtlPropertiesChanged(int layoutDirection) {
2831 super.onRtlPropertiesChanged(layoutDirection);
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07002832 if (mFastScroller != null) {
2833 mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition());
2834 }
2835 }
2836
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002837 /**
2838 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
2839 * methods knows the view, position and ID of the item that received the
2840 * long press.
2841 *
2842 * @param view The view that received the long press.
2843 * @param position The position of the item that received the long press.
2844 * @param id The ID of the item that received the long press.
2845 * @return The extra information that should be returned by
2846 * {@link #getContextMenuInfo()}.
2847 */
2848 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
2849 return new AdapterContextMenuInfo(view, position, id);
2850 }
2851
Adam Powell14874662013-07-18 19:42:41 -07002852 @Override
2853 public void onCancelPendingInputEvents() {
2854 super.onCancelPendingInputEvents();
2855 if (mPerformClick != null) {
2856 removeCallbacks(mPerformClick);
2857 }
2858 if (mPendingCheckForTap != null) {
2859 removeCallbacks(mPendingCheckForTap);
2860 }
2861 if (mPendingCheckForLongPress != null) {
2862 removeCallbacks(mPendingCheckForLongPress);
2863 }
2864 if (mPendingCheckForKeyLongPress != null) {
2865 removeCallbacks(mPendingCheckForKeyLongPress);
2866 }
2867 }
2868
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002869 /**
2870 * A base class for Runnables that will check that their view is still attached to
2871 * the original window as when the Runnable was created.
2872 *
2873 */
2874 private class WindowRunnnable {
2875 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07002876
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002877 public void rememberWindowAttachCount() {
2878 mOriginalAttachCount = getWindowAttachCount();
2879 }
Romain Guy0a637162009-05-29 14:43:54 -07002880
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002881 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07002882 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002883 }
2884 }
Romain Guy0a637162009-05-29 14:43:54 -07002885
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002886 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002887 int mClickMotionPosition;
2888
Alan Viverette8fa327a2013-05-31 14:53:13 -07002889 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002890 public void run() {
2891 // The data has changed since we posted this action in the event queue,
2892 // bail out before bad things happen
2893 if (mDataChanged) return;
2894
Adam Powell005c0a42010-03-30 16:26:36 -07002895 final ListAdapter adapter = mAdapter;
2896 final int motionPosition = mClickMotionPosition;
2897 if (adapter != null && mItemCount > 0 &&
2898 motionPosition != INVALID_POSITION &&
2899 motionPosition < adapter.getCount() && sameWindow()) {
Romain Guy7890fe22011-01-18 20:24:18 -08002900 final View view = getChildAt(motionPosition - mFirstPosition);
2901 // If there is no view, something bad happened (the view scrolled off the
2902 // screen, etc.) and we should cancel the click
2903 if (view != null) {
2904 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2905 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002906 }
2907 }
2908 }
2909
2910 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07002911 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002912 public void run() {
2913 final int motionPosition = mMotionPosition;
2914 final View child = getChildAt(motionPosition - mFirstPosition);
2915 if (child != null) {
2916 final int longPressPosition = mMotionPosition;
2917 final long longPressId = mAdapter.getItemId(mMotionPosition);
2918
2919 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07002920 if (sameWindow() && !mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002921 handled = performLongPress(child, longPressPosition, longPressId);
2922 }
2923 if (handled) {
2924 mTouchMode = TOUCH_MODE_REST;
2925 setPressed(false);
2926 child.setPressed(false);
2927 } else {
2928 mTouchMode = TOUCH_MODE_DONE_WAITING;
2929 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002930 }
2931 }
2932 }
Romain Guy0a637162009-05-29 14:43:54 -07002933
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002934 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07002935 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002936 public void run() {
2937 if (isPressed() && mSelectedPosition >= 0) {
2938 int index = mSelectedPosition - mFirstPosition;
2939 View v = getChildAt(index);
2940
2941 if (!mDataChanged) {
2942 boolean handled = false;
2943 if (sameWindow()) {
2944 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2945 }
2946 if (handled) {
2947 setPressed(false);
2948 v.setPressed(false);
2949 }
2950 } else {
2951 setPressed(false);
2952 if (v != null) v.setPressed(false);
2953 }
2954 }
2955 }
2956 }
2957
Adam Powell8350f7d2010-07-28 14:27:28 -07002958 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002959 final int longPressPosition, final long longPressId) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002960 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2961 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07002962 if (mChoiceActionMode == null &&
2963 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002964 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07002965 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07002966 }
Adam Powellf343e1b2010-08-13 18:27:04 -07002967 return true;
2968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002969
Adam Powellf343e1b2010-08-13 18:27:04 -07002970 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002971 if (mOnItemLongClickListener != null) {
2972 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2973 longPressPosition, longPressId);
2974 }
2975 if (!handled) {
2976 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2977 handled = super.showContextMenuForChild(AbsListView.this);
2978 }
2979 if (handled) {
2980 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2981 }
2982 return handled;
2983 }
2984
2985 @Override
2986 protected ContextMenuInfo getContextMenuInfo() {
2987 return mContextMenuInfo;
2988 }
2989
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07002990 /** @hide */
2991 @Override
2992 public boolean showContextMenu(float x, float y, int metaState) {
2993 final int position = pointToPosition((int)x, (int)y);
2994 if (position != INVALID_POSITION) {
2995 final long id = mAdapter.getItemId(position);
2996 View child = getChildAt(position - mFirstPosition);
2997 if (child != null) {
2998 mContextMenuInfo = createContextMenuInfo(child, position, id);
2999 return super.showContextMenuForChild(AbsListView.this);
3000 }
3001 }
3002 return super.showContextMenu(x, y, metaState);
3003 }
3004
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003005 @Override
3006 public boolean showContextMenuForChild(View originalView) {
3007 final int longPressPosition = getPositionForView(originalView);
3008 if (longPressPosition >= 0) {
3009 final long longPressId = mAdapter.getItemId(longPressPosition);
3010 boolean handled = false;
3011
3012 if (mOnItemLongClickListener != null) {
3013 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
3014 longPressPosition, longPressId);
3015 }
3016 if (!handled) {
3017 mContextMenuInfo = createContextMenuInfo(
3018 getChildAt(longPressPosition - mFirstPosition),
3019 longPressPosition, longPressId);
3020 handled = super.showContextMenuForChild(originalView);
3021 }
3022
3023 return handled;
3024 }
3025 return false;
3026 }
3027
3028 @Override
Romain Guydf016072009-08-17 12:51:30 -07003029 public boolean onKeyDown(int keyCode, KeyEvent event) {
3030 return false;
3031 }
3032
3033 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003034 public boolean onKeyUp(int keyCode, KeyEvent event) {
Michael Wright24d36f52013-07-19 15:55:14 -07003035 if (KeyEvent.isConfirmKey(keyCode)) {
Romain Guydd753ae2009-08-17 10:36:23 -07003036 if (!isEnabled()) {
3037 return true;
3038 }
Romain Guydf016072009-08-17 12:51:30 -07003039 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07003040 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003041 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07003042
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003043 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07003044 if (view != null) {
3045 performItemClick(view, mSelectedPosition, mSelectedRowId);
3046 view.setPressed(false);
3047 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003048 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003049 return true;
3050 }
3051 }
3052 return super.onKeyUp(keyCode, event);
3053 }
3054
3055 @Override
3056 protected void dispatchSetPressed(boolean pressed) {
3057 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3058 // get the selector in the right state, but we don't want to press each child.
3059 }
3060
3061 /**
3062 * Maps a point to a position in the list.
3063 *
3064 * @param x X in local coordinate
3065 * @param y Y in local coordinate
3066 * @return The position of the item which contains the specified point, or
3067 * {@link #INVALID_POSITION} if the point does not intersect an item.
3068 */
3069 public int pointToPosition(int x, int y) {
3070 Rect frame = mTouchFrame;
3071 if (frame == null) {
3072 mTouchFrame = new Rect();
3073 frame = mTouchFrame;
3074 }
3075
3076 final int count = getChildCount();
3077 for (int i = count - 1; i >= 0; i--) {
3078 final View child = getChildAt(i);
3079 if (child.getVisibility() == View.VISIBLE) {
3080 child.getHitRect(frame);
3081 if (frame.contains(x, y)) {
3082 return mFirstPosition + i;
3083 }
3084 }
3085 }
3086 return INVALID_POSITION;
3087 }
3088
3089
3090 /**
3091 * Maps a point to a the rowId of the item which intersects that point.
3092 *
3093 * @param x X in local coordinate
3094 * @param y Y in local coordinate
3095 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3096 * if the point does not intersect an item.
3097 */
3098 public long pointToRowId(int x, int y) {
3099 int position = pointToPosition(x, y);
3100 if (position >= 0) {
3101 return mAdapter.getItemId(position);
3102 }
3103 return INVALID_ROW_ID;
3104 }
3105
3106 final class CheckForTap implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003107 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003108 public void run() {
3109 if (mTouchMode == TOUCH_MODE_DOWN) {
3110 mTouchMode = TOUCH_MODE_TAP;
3111 final View child = getChildAt(mMotionPosition - mFirstPosition);
3112 if (child != null && !child.hasFocusable()) {
3113 mLayoutMode = LAYOUT_NORMAL;
3114
3115 if (!mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003116 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003117 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003118 layoutChildren();
3119 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003120 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003121
3122 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3123 final boolean longClickable = isLongClickable();
3124
3125 if (mSelector != null) {
3126 Drawable d = mSelector.getCurrent();
3127 if (d != null && d instanceof TransitionDrawable) {
3128 if (longClickable) {
3129 ((TransitionDrawable) d).startTransition(longPressTimeout);
3130 } else {
3131 ((TransitionDrawable) d).resetTransition();
3132 }
3133 }
3134 }
3135
3136 if (longClickable) {
3137 if (mPendingCheckForLongPress == null) {
3138 mPendingCheckForLongPress = new CheckForLongPress();
3139 }
3140 mPendingCheckForLongPress.rememberWindowAttachCount();
3141 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3142 } else {
3143 mTouchMode = TOUCH_MODE_DONE_WAITING;
3144 }
3145 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003146 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003147 }
3148 }
3149 }
3150 }
3151 }
3152
Jeff Brown78f6e632011-09-09 17:15:31 -07003153 private boolean startScrollIfNeeded(int y) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003154 // Check if we have moved far enough that it looks more like a
3155 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003156 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003157 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003158 final boolean overscroll = mScrollY != 0;
3159 if (overscroll || distance > mTouchSlop) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003160 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003161 if (overscroll) {
3162 mTouchMode = TOUCH_MODE_OVERSCROLL;
3163 mMotionCorrection = 0;
3164 } else {
3165 mTouchMode = TOUCH_MODE_SCROLL;
3166 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3167 }
Alan Viverette74ded292013-06-03 15:34:11 -07003168 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003169 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003170 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003171 if (motionView != null) {
3172 motionView.setPressed(false);
3173 }
3174 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3175 // Time to start stealing events! Once we've stolen them, don't let anyone
3176 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003177 final ViewParent parent = getParent();
3178 if (parent != null) {
3179 parent.requestDisallowInterceptTouchEvent(true);
3180 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003181 scrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003182 return true;
3183 }
3184
3185 return false;
3186 }
3187
Jeff Brown78f6e632011-09-09 17:15:31 -07003188 private void scrollIfNeeded(int y) {
3189 final int rawDeltaY = y - mMotionY;
3190 final int deltaY = rawDeltaY - mMotionCorrection;
3191 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3192
3193 if (mTouchMode == TOUCH_MODE_SCROLL) {
3194 if (PROFILE_SCROLLING) {
3195 if (!mScrollProfilingStarted) {
3196 Debug.startMethodTracing("AbsListViewScroll");
3197 mScrollProfilingStarted = true;
3198 }
3199 }
3200
3201 if (mScrollStrictSpan == null) {
3202 // If it's non-null, we're already in a scroll.
3203 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3204 }
3205
3206 if (y != mLastY) {
3207 // We may be here after stopping a fling and continuing to scroll.
3208 // If so, we haven't disallowed intercepting touch events yet.
3209 // Make sure that we do so in case we're in a parent that can intercept.
3210 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3211 Math.abs(rawDeltaY) > mTouchSlop) {
3212 final ViewParent parent = getParent();
3213 if (parent != null) {
3214 parent.requestDisallowInterceptTouchEvent(true);
3215 }
3216 }
3217
3218 final int motionIndex;
3219 if (mMotionPosition >= 0) {
3220 motionIndex = mMotionPosition - mFirstPosition;
3221 } else {
3222 // If we don't have a motion position that we can reliably track,
3223 // pick something in the middle to make a best guess at things below.
3224 motionIndex = getChildCount() / 2;
3225 }
3226
3227 int motionViewPrevTop = 0;
3228 View motionView = this.getChildAt(motionIndex);
3229 if (motionView != null) {
3230 motionViewPrevTop = motionView.getTop();
3231 }
3232
3233 // No need to do all this work if we're not going to move anyway
3234 boolean atEdge = false;
3235 if (incrementalDeltaY != 0) {
3236 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3237 }
3238
3239 // Check to see if we have bumped into the scroll limit
3240 motionView = this.getChildAt(motionIndex);
3241 if (motionView != null) {
3242 // Check if the top of the motion view is where it is
3243 // supposed to be
3244 final int motionViewRealTop = motionView.getTop();
3245 if (atEdge) {
3246 // Apply overscroll
3247
3248 int overscroll = -incrementalDeltaY -
3249 (motionViewRealTop - motionViewPrevTop);
3250 overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3251 0, mOverscrollDistance, true);
3252 if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3253 // Don't allow overfling if we're at the edge.
3254 if (mVelocityTracker != null) {
3255 mVelocityTracker.clear();
3256 }
3257 }
3258
3259 final int overscrollMode = getOverScrollMode();
3260 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3261 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3262 !contentFits())) {
3263 mDirection = 0; // Reset when entering overscroll.
3264 mTouchMode = TOUCH_MODE_OVERSCROLL;
3265 if (rawDeltaY > 0) {
3266 mEdgeGlowTop.onPull((float) overscroll / getHeight());
3267 if (!mEdgeGlowBottom.isFinished()) {
3268 mEdgeGlowBottom.onRelease();
3269 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003270 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003271 } else if (rawDeltaY < 0) {
3272 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3273 if (!mEdgeGlowTop.isFinished()) {
3274 mEdgeGlowTop.onRelease();
3275 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003276 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003277 }
3278 }
3279 }
3280 mMotionY = y;
Jeff Brown78f6e632011-09-09 17:15:31 -07003281 }
3282 mLastY = y;
3283 }
3284 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3285 if (y != mLastY) {
3286 final int oldScroll = mScrollY;
3287 final int newScroll = oldScroll - incrementalDeltaY;
3288 int newDirection = y > mLastY ? 1 : -1;
3289
3290 if (mDirection == 0) {
3291 mDirection = newDirection;
3292 }
3293
3294 int overScrollDistance = -incrementalDeltaY;
3295 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3296 overScrollDistance = -oldScroll;
3297 incrementalDeltaY += overScrollDistance;
3298 } else {
3299 incrementalDeltaY = 0;
3300 }
3301
3302 if (overScrollDistance != 0) {
3303 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3304 0, mOverscrollDistance, true);
3305 final int overscrollMode = getOverScrollMode();
3306 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3307 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3308 !contentFits())) {
3309 if (rawDeltaY > 0) {
3310 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3311 if (!mEdgeGlowBottom.isFinished()) {
3312 mEdgeGlowBottom.onRelease();
3313 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003314 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003315 } else if (rawDeltaY < 0) {
3316 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3317 if (!mEdgeGlowTop.isFinished()) {
3318 mEdgeGlowTop.onRelease();
3319 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003320 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003321 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003322 }
3323 }
3324
3325 if (incrementalDeltaY != 0) {
3326 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003327 if (mScrollY != 0) {
3328 mScrollY = 0;
3329 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003330 }
3331
Romain Guy9d849a22012-03-14 16:41:42 -07003332 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3333
Jeff Brown78f6e632011-09-09 17:15:31 -07003334 mTouchMode = TOUCH_MODE_SCROLL;
3335
3336 // We did not scroll the full amount. Treat this essentially like the
3337 // start of a new touch scroll
3338 final int motionPosition = findClosestMotionRow(y);
3339
3340 mMotionCorrection = 0;
3341 View motionView = getChildAt(motionPosition - mFirstPosition);
3342 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3343 mMotionY = y;
3344 mMotionPosition = motionPosition;
3345 }
3346 mLastY = y;
3347 mDirection = newDirection;
3348 }
3349 }
3350 }
3351
Alan Viverette8fa327a2013-05-31 14:53:13 -07003352 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353 public void onTouchModeChanged(boolean isInTouchMode) {
3354 if (isInTouchMode) {
3355 // Get rid of the selection when we enter touch mode
3356 hideSelector();
3357 // Layout, but only if we already have done so previously.
3358 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3359 // state.)
3360 if (getHeight() > 0 && getChildCount() > 0) {
3361 // We do not lose focus initiating a touch (since AbsListView is focusable in
3362 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003363 layoutChildren();
3364 }
Jeff Brown1e209462011-07-14 22:19:19 -07003365 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003366 } else {
3367 int touchMode = mTouchMode;
3368 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3369 if (mFlingRunnable != null) {
3370 mFlingRunnable.endFling();
3371 }
Adam Powell40322522011-01-12 21:58:20 -08003372 if (mPositionScroller != null) {
3373 mPositionScroller.stop();
3374 }
Adam Powell637d3372010-08-25 14:37:03 -07003375
3376 if (mScrollY != 0) {
3377 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003378 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003379 finishGlows();
3380 invalidate();
3381 }
3382 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003383 }
3384 }
3385
3386 @Override
3387 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003388 if (!isEnabled()) {
3389 // A disabled view that is clickable still consumes the touch
3390 // events, it just doesn't respond to them.
3391 return isClickable() || isLongClickable();
3392 }
3393
Adam Powell1fa179ef2012-04-12 15:01:40 -07003394 if (mPositionScroller != null) {
3395 mPositionScroller.stop();
3396 }
3397
Adam Powell31986b52013-09-24 14:53:30 -07003398 if (!isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003399 // Something isn't right.
3400 // Since we rely on being attached to get data set change notifications,
3401 // don't risk doing anything where we might try to resync and find things
3402 // in a bogus state.
3403 return false;
3404 }
3405
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003406 if (mFastScroller != null) {
3407 boolean intercepted = mFastScroller.onTouchEvent(ev);
3408 if (intercepted) {
3409 return true;
Romain Guy0a637162009-05-29 14:43:54 -07003410 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003411 }
Romain Guy82f34952009-05-24 18:40:45 -07003412
Michael Jurka13451a42011-08-22 15:54:21 -07003413 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003414 mVelocityTracker.addMovement(ev);
3415
Alan Viverette8fa327a2013-05-31 14:53:13 -07003416 final int actionMasked = ev.getActionMasked();
3417 switch (actionMasked) {
3418 case MotionEvent.ACTION_DOWN: {
3419 onTouchDown(ev);
3420 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003421 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003422
Alan Viverette8fa327a2013-05-31 14:53:13 -07003423 case MotionEvent.ACTION_MOVE: {
3424 onTouchMove(ev);
3425 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003426 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003427
3428 case MotionEvent.ACTION_UP: {
3429 onTouchUp(ev);
3430 break;
3431 }
3432
3433 case MotionEvent.ACTION_CANCEL: {
3434 onTouchCancel();
3435 break;
3436 }
3437
3438 case MotionEvent.ACTION_POINTER_UP: {
3439 onSecondaryPointerUp(ev);
3440 final int x = mMotionX;
3441 final int y = mMotionY;
3442 final int motionPosition = pointToPosition(x, y);
3443 if (motionPosition >= 0) {
3444 // Remember where the motion event started
3445 final View child = getChildAt(motionPosition - mFirstPosition);
3446 mMotionViewOriginalTop = child.getTop();
3447 mMotionPosition = motionPosition;
3448 }
3449 mLastY = y;
3450 break;
3451 }
3452
3453 case MotionEvent.ACTION_POINTER_DOWN: {
3454 // New pointers take over dragging duties
3455 final int index = ev.getActionIndex();
3456 final int id = ev.getPointerId(index);
3457 final int x = (int) ev.getX(index);
3458 final int y = (int) ev.getY(index);
3459 mMotionCorrection = 0;
3460 mActivePointerId = id;
3461 mMotionX = x;
3462 mMotionY = y;
3463 final int motionPosition = pointToPosition(x, y);
3464 if (motionPosition >= 0) {
3465 // Remember where the motion event started
3466 final View child = getChildAt(motionPosition - mFirstPosition);
3467 mMotionViewOriginalTop = child.getTop();
3468 mMotionPosition = motionPosition;
3469 }
3470 mLastY = y;
3471 break;
3472 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003473 }
3474
3475 return true;
3476 }
Romain Guy0a637162009-05-29 14:43:54 -07003477
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003478 private void onTouchDown(MotionEvent ev) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003479 mActivePointerId = ev.getPointerId(0);
3480
3481 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3482 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003483 mFlingRunnable.endFling();
3484 if (mPositionScroller != null) {
3485 mPositionScroller.stop();
3486 }
3487 mTouchMode = TOUCH_MODE_OVERSCROLL;
3488 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003489 mMotionY = (int) ev.getY();
3490 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003491 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003492 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003493 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003494 final int x = (int) ev.getX();
3495 final int y = (int) ev.getY();
3496 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003497
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003498 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003499 if (mTouchMode == TOUCH_MODE_FLING) {
3500 // Stopped a fling. It is a scroll.
3501 createScrollingCache();
3502 mTouchMode = TOUCH_MODE_SCROLL;
3503 mMotionCorrection = 0;
3504 motionPosition = findMotionRow(y);
3505 mFlingRunnable.flywheelTouch();
3506 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3507 // User clicked on an actual view (and was not stopping a
3508 // fling). It might be a click or a scroll. Assume it is a
3509 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003510 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003511
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003512 // FIXME Debounce
3513 if (mPendingCheckForTap == null) {
3514 mPendingCheckForTap = new CheckForTap();
3515 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003516
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003517 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003518 }
3519 }
3520
3521 if (motionPosition >= 0) {
3522 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003523 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003524 mMotionViewOriginalTop = v.getTop();
3525 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003526
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003527 mMotionX = x;
3528 mMotionY = y;
3529 mMotionPosition = motionPosition;
3530 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003531 }
3532
Alan Viveretteb339cc52013-08-12 13:29:15 -07003533 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
3534 && performButtonActionOnTouchDown(ev)) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003535 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003536 }
3537 }
3538
3539 private void onTouchMove(MotionEvent ev) {
3540 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3541 if (pointerIndex == -1) {
3542 pointerIndex = 0;
3543 mActivePointerId = ev.getPointerId(pointerIndex);
3544 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003545
3546 if (mDataChanged) {
3547 // Re-sync everything if data has been changed
3548 // since the scroll operation can query the adapter.
3549 layoutChildren();
3550 }
3551
Alan Viverette8fa327a2013-05-31 14:53:13 -07003552 final int y = (int) ev.getY(pointerIndex);
3553
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003554 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003555 case TOUCH_MODE_DOWN:
3556 case TOUCH_MODE_TAP:
3557 case TOUCH_MODE_DONE_WAITING:
3558 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003559 // scroll than a tap. If so, we'll enter scrolling mode.
3560 if (startScrollIfNeeded(y)) {
3561 break;
3562 }
3563 // Otherwise, check containment within list bounds. If we're
3564 // outside bounds, cancel any active presses.
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003565 final float x = ev.getX(pointerIndex);
3566 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003567 setPressed(false);
3568 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3569 if (motionView != null) {
3570 motionView.setPressed(false);
3571 }
3572 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3573 mPendingCheckForTap : mPendingCheckForLongPress);
3574 mTouchMode = TOUCH_MODE_DONE_WAITING;
3575 updateSelectorState();
3576 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003577 break;
3578 case TOUCH_MODE_SCROLL:
3579 case TOUCH_MODE_OVERSCROLL:
3580 scrollIfNeeded(y);
3581 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003582 }
3583 }
3584
3585 private void onTouchUp(MotionEvent ev) {
3586 switch (mTouchMode) {
3587 case TOUCH_MODE_DOWN:
3588 case TOUCH_MODE_TAP:
3589 case TOUCH_MODE_DONE_WAITING:
3590 final int motionPosition = mMotionPosition;
3591 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003592 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003593 if (mTouchMode != TOUCH_MODE_DOWN) {
3594 child.setPressed(false);
3595 }
3596
Alan Viverette74ded292013-06-03 15:34:11 -07003597 final float x = ev.getX();
3598 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3599 if (inList && !child.hasFocusable()) {
3600 if (mPerformClick == null) {
3601 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003602 }
Alan Viverette74ded292013-06-03 15:34:11 -07003603
3604 final AbsListView.PerformClick performClick = mPerformClick;
3605 performClick.mClickMotionPosition = motionPosition;
3606 performClick.rememberWindowAttachCount();
3607
3608 mResurrectToPosition = motionPosition;
3609
3610 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3611 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3612 mPendingCheckForTap : mPendingCheckForLongPress);
3613 mLayoutMode = LAYOUT_NORMAL;
3614 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3615 mTouchMode = TOUCH_MODE_TAP;
3616 setSelectedPositionInt(mMotionPosition);
3617 layoutChildren();
3618 child.setPressed(true);
3619 positionSelector(mMotionPosition, child);
3620 setPressed(true);
3621 if (mSelector != null) {
3622 Drawable d = mSelector.getCurrent();
3623 if (d != null && d instanceof TransitionDrawable) {
3624 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003625 }
3626 }
Alan Viverette74ded292013-06-03 15:34:11 -07003627 if (mTouchModeReset != null) {
3628 removeCallbacks(mTouchModeReset);
3629 }
3630 mTouchModeReset = new Runnable() {
3631 @Override
3632 public void run() {
3633 mTouchModeReset = null;
3634 mTouchMode = TOUCH_MODE_REST;
3635 child.setPressed(false);
3636 setPressed(false);
Adam Powell31986b52013-09-24 14:53:30 -07003637 if (!mDataChanged && isAttachedToWindow()) {
Alan Viverette74ded292013-06-03 15:34:11 -07003638 performClick.run();
3639 }
3640 }
3641 };
3642 postDelayed(mTouchModeReset,
3643 ViewConfiguration.getPressedStateDuration());
3644 } else {
3645 mTouchMode = TOUCH_MODE_REST;
3646 updateSelectorState();
3647 }
3648 return;
3649 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3650 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003651 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003652 }
3653 }
3654 mTouchMode = TOUCH_MODE_REST;
3655 updateSelectorState();
3656 break;
3657 case TOUCH_MODE_SCROLL:
3658 final int childCount = getChildCount();
3659 if (childCount > 0) {
3660 final int firstChildTop = getChildAt(0).getTop();
3661 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3662 final int contentTop = mListPadding.top;
3663 final int contentBottom = getHeight() - mListPadding.bottom;
3664 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
3665 mFirstPosition + childCount < mItemCount &&
3666 lastChildBottom <= getHeight() - contentBottom) {
3667 mTouchMode = TOUCH_MODE_REST;
3668 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3669 } else {
3670 final VelocityTracker velocityTracker = mVelocityTracker;
3671 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3672
3673 final int initialVelocity = (int)
3674 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
3675 // Fling if we have enough velocity and we aren't at a boundary.
3676 // Since we can potentially overfling more than we can overscroll, don't
3677 // allow the weird behavior where you can scroll to a boundary then
3678 // fling further.
3679 if (Math.abs(initialVelocity) > mMinimumVelocity &&
3680 !((mFirstPosition == 0 &&
3681 firstChildTop == contentTop - mOverscrollDistance) ||
3682 (mFirstPosition + childCount == mItemCount &&
3683 lastChildBottom == contentBottom + mOverscrollDistance))) {
3684 if (mFlingRunnable == null) {
3685 mFlingRunnable = new FlingRunnable();
3686 }
3687 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3688
3689 mFlingRunnable.start(-initialVelocity);
3690 } else {
3691 mTouchMode = TOUCH_MODE_REST;
3692 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3693 if (mFlingRunnable != null) {
3694 mFlingRunnable.endFling();
3695 }
3696 if (mPositionScroller != null) {
3697 mPositionScroller.stop();
3698 }
3699 }
3700 }
3701 } else {
3702 mTouchMode = TOUCH_MODE_REST;
3703 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3704 }
3705 break;
3706
3707 case TOUCH_MODE_OVERSCROLL:
3708 if (mFlingRunnable == null) {
3709 mFlingRunnable = new FlingRunnable();
3710 }
3711 final VelocityTracker velocityTracker = mVelocityTracker;
3712 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3713 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3714
3715 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3716 if (Math.abs(initialVelocity) > mMinimumVelocity) {
3717 mFlingRunnable.startOverfling(-initialVelocity);
3718 } else {
3719 mFlingRunnable.startSpringback();
3720 }
3721
3722 break;
3723 }
3724
3725 setPressed(false);
3726
3727 if (mEdgeGlowTop != null) {
3728 mEdgeGlowTop.onRelease();
3729 mEdgeGlowBottom.onRelease();
3730 }
3731
3732 // Need to redraw since we probably aren't drawing the selector anymore
3733 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07003734 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003735 recycleVelocityTracker();
3736
3737 mActivePointerId = INVALID_POINTER;
3738
3739 if (PROFILE_SCROLLING) {
3740 if (mScrollProfilingStarted) {
3741 Debug.stopMethodTracing();
3742 mScrollProfilingStarted = false;
3743 }
3744 }
3745
3746 if (mScrollStrictSpan != null) {
3747 mScrollStrictSpan.finish();
3748 mScrollStrictSpan = null;
3749 }
3750 }
3751
3752 private void onTouchCancel() {
3753 switch (mTouchMode) {
3754 case TOUCH_MODE_OVERSCROLL:
3755 if (mFlingRunnable == null) {
3756 mFlingRunnable = new FlingRunnable();
3757 }
3758 mFlingRunnable.startSpringback();
3759 break;
3760
3761 case TOUCH_MODE_OVERFLING:
3762 // Do nothing - let it play out.
3763 break;
3764
3765 default:
3766 mTouchMode = TOUCH_MODE_REST;
3767 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003768 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003769 if (motionView != null) {
3770 motionView.setPressed(false);
3771 }
3772 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07003773 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003774 recycleVelocityTracker();
3775 }
3776
3777 if (mEdgeGlowTop != null) {
3778 mEdgeGlowTop.onRelease();
3779 mEdgeGlowBottom.onRelease();
3780 }
3781 mActivePointerId = INVALID_POINTER;
3782 }
3783
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003784 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003785 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3786 if (mScrollY != scrollY) {
3787 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3788 mScrollY = scrollY;
3789 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07003790
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003791 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07003792 }
Adam Powell637d3372010-08-25 14:37:03 -07003793 }
3794
3795 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08003796 public boolean onGenericMotionEvent(MotionEvent event) {
3797 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3798 switch (event.getAction()) {
3799 case MotionEvent.ACTION_SCROLL: {
3800 if (mTouchMode == TOUCH_MODE_REST) {
3801 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3802 if (vscroll != 0) {
3803 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08003804 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08003805 return true;
3806 }
3807 }
3808 }
3809 }
3810 }
3811 }
3812 return super.onGenericMotionEvent(event);
3813 }
3814
3815 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003816 public void draw(Canvas canvas) {
3817 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07003818 if (mEdgeGlowTop != null) {
3819 final int scrollY = mScrollY;
3820 if (!mEdgeGlowTop.isFinished()) {
3821 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003822 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3823 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3824 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003825
Romain Guy9d849a22012-03-14 16:41:42 -07003826 int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3827 canvas.translate(leftPadding, edgeY);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003828 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07003829 if (mEdgeGlowTop.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003830 mEdgeGlowTop.setPosition(leftPadding, edgeY);
Romain Guya8bfeaf2012-03-15 13:14:14 -07003831 invalidate(mEdgeGlowTop.getBounds(false));
Adam Powell637d3372010-08-25 14:37:03 -07003832 }
3833 canvas.restoreToCount(restoreCount);
3834 }
3835 if (!mEdgeGlowBottom.isFinished()) {
3836 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003837 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3838 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3839 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003840 final int height = getHeight();
3841
Romain Guy9d849a22012-03-14 16:41:42 -07003842 int edgeX = -width + leftPadding;
3843 int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3844 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08003845 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003846 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07003847 if (mEdgeGlowBottom.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003848 // Account for the rotation
Romain Guya8bfeaf2012-03-15 13:14:14 -07003849 mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
3850 invalidate(mEdgeGlowBottom.getBounds(true));
Adam Powell637d3372010-08-25 14:37:03 -07003851 }
3852 canvas.restoreToCount(restoreCount);
3853 }
3854 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003855 }
3856
Adam Powell07d6f7b2011-03-02 14:27:30 -08003857 /**
3858 * @hide
3859 */
3860 public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3861 mGlowPaddingLeft = leftPadding;
3862 mGlowPaddingRight = rightPadding;
3863 }
3864
Michael Jurka13451a42011-08-22 15:54:21 -07003865 private void initOrResetVelocityTracker() {
3866 if (mVelocityTracker == null) {
3867 mVelocityTracker = VelocityTracker.obtain();
3868 } else {
3869 mVelocityTracker.clear();
3870 }
3871 }
3872
3873 private void initVelocityTrackerIfNotExists() {
3874 if (mVelocityTracker == null) {
3875 mVelocityTracker = VelocityTracker.obtain();
3876 }
3877 }
3878
3879 private void recycleVelocityTracker() {
3880 if (mVelocityTracker != null) {
3881 mVelocityTracker.recycle();
3882 mVelocityTracker = null;
3883 }
3884 }
3885
3886 @Override
3887 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3888 if (disallowIntercept) {
3889 recycleVelocityTracker();
3890 }
3891 super.requestDisallowInterceptTouchEvent(disallowIntercept);
3892 }
3893
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003894 @Override
Alan Viverettea709b372013-07-25 14:43:24 -07003895 public boolean onInterceptHoverEvent(MotionEvent event) {
3896 if (mFastScroller != null && mFastScroller.onInterceptHoverEvent(event)) {
3897 return true;
3898 }
3899
3900 return super.onInterceptHoverEvent(event);
3901 }
3902
3903 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003904 public boolean onInterceptTouchEvent(MotionEvent ev) {
3905 int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003906 View v;
Romain Guy0a637162009-05-29 14:43:54 -07003907
Adam Powell1fa179ef2012-04-12 15:01:40 -07003908 if (mPositionScroller != null) {
3909 mPositionScroller.stop();
3910 }
3911
Adam Powell31986b52013-09-24 14:53:30 -07003912 if (!isAttachedToWindow()) {
Adam Powell28048d02012-06-06 22:46:42 -07003913 // Something isn't right.
3914 // Since we rely on being attached to get data set change notifications,
3915 // don't risk doing anything where we might try to resync and find things
3916 // in a bogus state.
3917 return false;
3918 }
3919
Alan Viverette0ebe81e2013-06-21 17:01:36 -07003920 if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
3921 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003922 }
Romain Guy0a637162009-05-29 14:43:54 -07003923
Adam Powell4cd47702010-02-25 11:21:14 -08003924 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003925 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08003926 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07003927 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3928 mMotionCorrection = 0;
3929 return true;
3930 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003931
Adam Powell4cd47702010-02-25 11:21:14 -08003932 final int x = (int) ev.getX();
3933 final int y = (int) ev.getY();
3934 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003935
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003936 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08003937 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003938 // User clicked on an actual view (and was not stopping a fling).
3939 // Remember where the motion event started
3940 v = getChildAt(motionPosition - mFirstPosition);
3941 mMotionViewOriginalTop = v.getTop();
3942 mMotionX = x;
3943 mMotionY = y;
3944 mMotionPosition = motionPosition;
3945 mTouchMode = TOUCH_MODE_DOWN;
3946 clearScrollingCache();
3947 }
3948 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07003949 initOrResetVelocityTracker();
3950 mVelocityTracker.addMovement(ev);
Adam Powell79ac3392010-01-28 21:22:20 -08003951 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08003952 return true;
3953 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003954 break;
3955 }
3956
3957 case MotionEvent.ACTION_MOVE: {
3958 switch (mTouchMode) {
3959 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07003960 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3961 if (pointerIndex == -1) {
3962 pointerIndex = 0;
3963 mActivePointerId = ev.getPointerId(pointerIndex);
3964 }
Adam Powell4cd47702010-02-25 11:21:14 -08003965 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07003966 initVelocityTrackerIfNotExists();
3967 mVelocityTracker.addMovement(ev);
Jeff Brown78f6e632011-09-09 17:15:31 -07003968 if (startScrollIfNeeded(y)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003969 return true;
3970 }
3971 break;
3972 }
3973 break;
3974 }
3975
Michael Jurka13451a42011-08-22 15:54:21 -07003976 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003977 case MotionEvent.ACTION_UP: {
3978 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08003979 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07003980 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003981 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3982 break;
3983 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003984
Adam Powell4cd47702010-02-25 11:21:14 -08003985 case MotionEvent.ACTION_POINTER_UP: {
3986 onSecondaryPointerUp(ev);
3987 break;
3988 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003989 }
3990
3991 return false;
3992 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003993
Adam Powell4cd47702010-02-25 11:21:14 -08003994 private void onSecondaryPointerUp(MotionEvent ev) {
3995 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3996 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3997 final int pointerId = ev.getPointerId(pointerIndex);
3998 if (pointerId == mActivePointerId) {
3999 // This was our active pointer going up. Choose a new
4000 // active pointer and adjust accordingly.
4001 // TODO: Make this decision more intelligent.
4002 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4003 mMotionX = (int) ev.getX(newPointerIndex);
4004 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07004005 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08004006 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08004007 }
4008 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004009
4010 /**
4011 * {@inheritDoc}
4012 */
4013 @Override
4014 public void addTouchables(ArrayList<View> views) {
4015 final int count = getChildCount();
4016 final int firstPosition = mFirstPosition;
4017 final ListAdapter adapter = mAdapter;
4018
4019 if (adapter == null) {
4020 return;
4021 }
4022
4023 for (int i = 0; i < count; i++) {
4024 final View child = getChildAt(i);
4025 if (adapter.isEnabled(firstPosition + i)) {
4026 views.add(child);
4027 }
4028 child.addTouchables(views);
4029 }
4030 }
4031
4032 /**
4033 * Fires an "on scroll state changed" event to the registered
4034 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4035 * is fired only if the specified state is different from the previously known state.
4036 *
4037 * @param newState The new scroll state.
4038 */
4039 void reportScrollStateChange(int newState) {
4040 if (newState != mLastScrollState) {
4041 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004042 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08004043 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004044 }
4045 }
4046 }
4047
4048 /**
4049 * Responsible for fling behavior. Use {@link #start(int)} to
4050 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4051 * A FlingRunnable will keep re-posting itself until the fling is done.
4052 *
4053 */
4054 private class FlingRunnable implements Runnable {
4055 /**
4056 * Tracks the decay of a fling scroll
4057 */
Adam Powell637d3372010-08-25 14:37:03 -07004058 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004059
4060 /**
4061 * Y value reported by mScroller on the previous fling
4062 */
4063 private int mLastFlingY;
4064
Gilles Debunned348bb42010-11-15 12:19:35 -08004065 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004066 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08004067 public void run() {
4068 final int activeId = mActivePointerId;
4069 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004070 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004071 if (vt == null || activeId == INVALID_POINTER) {
4072 return;
4073 }
4074
4075 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4076 final float yvel = -vt.getYVelocity(activeId);
4077
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004078 if (Math.abs(yvel) >= mMinimumVelocity
4079 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004080 // Keep the fling alive a little longer
4081 postDelayed(this, FLYWHEEL_TIMEOUT);
4082 } else {
4083 endFling();
4084 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004085 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004086 }
4087 }
4088 };
4089
4090 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4091
Adam Powell79ac3392010-01-28 21:22:20 -08004092 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004093 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004094 }
4095
Adam Powell79ac3392010-01-28 21:22:20 -08004096 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004097 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4098 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004099 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004100 mScroller.fling(0, initialY, 0, initialVelocity,
4101 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4102 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004103 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004104
4105 if (PROFILE_FLINGING) {
4106 if (!mFlingProfilingStarted) {
4107 Debug.startMethodTracing("AbsListViewFling");
4108 mFlingProfilingStarted = true;
4109 }
4110 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004111
4112 if (mFlingStrictSpan == null) {
4113 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4114 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004115 }
Adam Powell45803472010-01-25 15:10:44 -08004116
Adam Powell637d3372010-08-25 14:37:03 -07004117 void startSpringback() {
4118 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4119 mTouchMode = TOUCH_MODE_OVERFLING;
4120 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004121 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004122 } else {
4123 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004124 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004125 }
4126 }
4127
4128 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004129 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004130 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4131 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004132 mTouchMode = TOUCH_MODE_OVERFLING;
4133 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004134 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004135 }
4136
4137 void edgeReached(int delta) {
4138 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4139 final int overscrollMode = getOverScrollMode();
4140 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4141 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4142 mTouchMode = TOUCH_MODE_OVERFLING;
4143 final int vel = (int) mScroller.getCurrVelocity();
4144 if (delta > 0) {
4145 mEdgeGlowTop.onAbsorb(vel);
4146 } else {
4147 mEdgeGlowBottom.onAbsorb(vel);
4148 }
Adam Powell40322522011-01-12 21:58:20 -08004149 } else {
4150 mTouchMode = TOUCH_MODE_REST;
4151 if (mPositionScroller != null) {
4152 mPositionScroller.stop();
4153 }
Adam Powell637d3372010-08-25 14:37:03 -07004154 }
4155 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004156 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004157 }
4158
Adam Powell0b8acd82012-04-25 20:29:23 -07004159 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004160 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4161 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004162 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004163 mScroller.startScroll(0, initialY, 0, distance, duration);
4164 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004165 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004166 }
4167
Gilles Debunned348bb42010-11-15 12:19:35 -08004168 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004169 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004170
Adam Powell79ac3392010-01-28 21:22:20 -08004171 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004172 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004173
4174 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4175 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004176 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004177
4178 if (mFlingStrictSpan != null) {
4179 mFlingStrictSpan.finish();
4180 mFlingStrictSpan = null;
4181 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004182 }
4183
4184 void flywheelTouch() {
4185 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004186 }
4187
Alan Viverette8fa327a2013-05-31 14:53:13 -07004188 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004189 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004190 switch (mTouchMode) {
4191 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004192 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004193 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004194
Gilles Debunned348bb42010-11-15 12:19:35 -08004195 case TOUCH_MODE_SCROLL:
4196 if (mScroller.isFinished()) {
4197 return;
4198 }
4199 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004200 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004201 if (mDataChanged) {
4202 layoutChildren();
4203 }
4204
Adam Powell79ac3392010-01-28 21:22:20 -08004205 if (mItemCount == 0 || getChildCount() == 0) {
4206 endFling();
4207 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004208 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004209
Adam Powell637d3372010-08-25 14:37:03 -07004210 final OverScroller scroller = mScroller;
4211 boolean more = scroller.computeScrollOffset();
4212 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004213
Adam Powell637d3372010-08-25 14:37:03 -07004214 // Flip sign to convert finger direction to list items direction
4215 // (e.g. finger moving down means list is moving towards the top)
4216 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004217
Adam Powell637d3372010-08-25 14:37:03 -07004218 // Pretend that each frame of a fling scroll is a touch scroll
4219 if (delta > 0) {
4220 // List is moving towards the top. Use first view as mMotionPosition
4221 mMotionPosition = mFirstPosition;
4222 final View firstView = getChildAt(0);
4223 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004224
Adam Powell637d3372010-08-25 14:37:03 -07004225 // Don't fling more than 1 screen
4226 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4227 } else {
4228 // List is moving towards the bottom. Use last view as mMotionPosition
4229 int offsetToLast = getChildCount() - 1;
4230 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004231
Adam Powell637d3372010-08-25 14:37:03 -07004232 final View lastView = getChildAt(offsetToLast);
4233 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004234
Adam Powell637d3372010-08-25 14:37:03 -07004235 // Don't fling more than 1 screen
4236 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4237 }
Adam Powell79ac3392010-01-28 21:22:20 -08004238
Adam Powell637d3372010-08-25 14:37:03 -07004239 // Check to see if we have bumped into the scroll limit
4240 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4241 int oldTop = 0;
4242 if (motionView != null) {
4243 oldTop = motionView.getTop();
4244 }
Adam Powell9d32d242010-03-29 16:02:07 -07004245
Adam Powell637d3372010-08-25 14:37:03 -07004246 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004247 final boolean atEdge = trackMotionScroll(delta, delta);
4248 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004249 if (atEnd) {
4250 if (motionView != null) {
4251 // Tweak the scroll for how far we overshot
4252 int overshoot = -(delta - (motionView.getTop() - oldTop));
4253 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4254 0, mOverflingDistance, false);
4255 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004256 if (more) {
4257 edgeReached(delta);
4258 }
Adam Powell637d3372010-08-25 14:37:03 -07004259 break;
4260 }
4261
4262 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004263 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004264 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004265 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004266 } else {
4267 endFling();
4268
4269 if (PROFILE_FLINGING) {
4270 if (mFlingProfilingStarted) {
4271 Debug.stopMethodTracing();
4272 mFlingProfilingStarted = false;
4273 }
4274
4275 if (mFlingStrictSpan != null) {
4276 mFlingStrictSpan.finish();
4277 mFlingStrictSpan = null;
4278 }
Adam Powell79ac3392010-01-28 21:22:20 -08004279 }
4280 }
Adam Powell637d3372010-08-25 14:37:03 -07004281 break;
4282 }
4283
4284 case TOUCH_MODE_OVERFLING: {
4285 final OverScroller scroller = mScroller;
4286 if (scroller.computeScrollOffset()) {
4287 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004288 final int currY = scroller.getCurrY();
4289 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004290 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4291 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004292 final boolean crossDown = scrollY <= 0 && currY > 0;
4293 final boolean crossUp = scrollY >= 0 && currY < 0;
4294 if (crossDown || crossUp) {
4295 int velocity = (int) scroller.getCurrVelocity();
4296 if (crossUp) velocity = -velocity;
4297
4298 // Don't flywheel from this; we're just continuing things.
4299 scroller.abortAnimation();
4300 start(velocity);
4301 } else {
4302 startSpringback();
4303 }
Adam Powell637d3372010-08-25 14:37:03 -07004304 } else {
4305 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004306 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004307 }
4308 } else {
4309 endFling();
4310 }
4311 break;
4312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004313 }
4314 }
4315 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004316
Adam Powell45803472010-01-25 15:10:44 -08004317 class PositionScroller implements Runnable {
Adam Powelle69370e2012-05-07 15:17:20 -07004318 private static final int SCROLL_DURATION = 200;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004319
Adam Powell45803472010-01-25 15:10:44 -08004320 private static final int MOVE_DOWN_POS = 1;
4321 private static final int MOVE_UP_POS = 2;
4322 private static final int MOVE_DOWN_BOUND = 3;
4323 private static final int MOVE_UP_BOUND = 4;
Adam Powelle44afae2010-07-01 10:10:35 -07004324 private static final int MOVE_OFFSET = 5;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004325
Adam Powell45803472010-01-25 15:10:44 -08004326 private int mMode;
4327 private int mTargetPos;
4328 private int mBoundPos;
4329 private int mLastSeenPos;
4330 private int mScrollDuration;
Gilles Debunne52964242010-02-24 11:05:19 -08004331 private final int mExtraScroll;
Adam Powelle44afae2010-07-01 10:10:35 -07004332
4333 private int mOffsetFromTop;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004334
Adam Powell45803472010-01-25 15:10:44 -08004335 PositionScroller() {
4336 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
4337 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004338
Adam Powelle69370e2012-05-07 15:17:20 -07004339 void start(final int position) {
Adam Powell40322522011-01-12 21:58:20 -08004340 stop();
4341
Adam Powellaadf4fb2012-05-08 15:42:13 -07004342 if (mDataChanged) {
4343 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004344 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004345 @Override public void run() {
4346 start(position);
4347 }
Adam Powell161abf32012-05-23 17:22:49 -07004348 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004349 return;
4350 }
4351
Adam Powelle69370e2012-05-07 15:17:20 -07004352 final int childCount = getChildCount();
4353 if (childCount == 0) {
4354 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004355 return;
4356 }
4357
Adam Powell45803472010-01-25 15:10:44 -08004358 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004359 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004360
Romain Guy4bede9e2010-10-11 19:36:59 -07004361 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004362 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4363 if (clampedPosition < firstPos) {
4364 viewTravelCount = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004365 mMode = MOVE_UP_POS;
Chet Haase0061e162012-06-08 15:01:56 -07004366 } else if (clampedPosition > lastPos) {
4367 viewTravelCount = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004368 mMode = MOVE_DOWN_POS;
4369 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004370 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004371 return;
4372 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004373
Adam Powell45803472010-01-25 15:10:44 -08004374 if (viewTravelCount > 0) {
4375 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4376 } else {
4377 mScrollDuration = SCROLL_DURATION;
4378 }
Chet Haase0061e162012-06-08 15:01:56 -07004379 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004380 mBoundPos = INVALID_POSITION;
4381 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004382
Adam Powell1fa179ef2012-04-12 15:01:40 -07004383 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004384 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004385
Adam Powelle69370e2012-05-07 15:17:20 -07004386 void start(final int position, final int boundPosition) {
Adam Powell40322522011-01-12 21:58:20 -08004387 stop();
4388
Adam Powell45803472010-01-25 15:10:44 -08004389 if (boundPosition == INVALID_POSITION) {
4390 start(position);
4391 return;
4392 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004393
Adam Powellaadf4fb2012-05-08 15:42:13 -07004394 if (mDataChanged) {
4395 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004396 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004397 @Override public void run() {
4398 start(position, boundPosition);
4399 }
Adam Powell161abf32012-05-23 17:22:49 -07004400 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004401 return;
4402 }
4403
Adam Powelle69370e2012-05-07 15:17:20 -07004404 final int childCount = getChildCount();
4405 if (childCount == 0) {
4406 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004407 return;
4408 }
4409
Adam Powell45803472010-01-25 15:10:44 -08004410 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004411 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004412
Romain Guy4bede9e2010-10-11 19:36:59 -07004413 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004414 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4415 if (clampedPosition < firstPos) {
Adam Powell45803472010-01-25 15:10:44 -08004416 final int boundPosFromLast = lastPos - boundPosition;
4417 if (boundPosFromLast < 1) {
4418 // Moving would shift our bound position off the screen. Abort.
4419 return;
4420 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004421
Chet Haase0061e162012-06-08 15:01:56 -07004422 final int posTravel = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004423 final int boundTravel = boundPosFromLast - 1;
4424 if (boundTravel < posTravel) {
4425 viewTravelCount = boundTravel;
4426 mMode = MOVE_UP_BOUND;
4427 } else {
4428 viewTravelCount = posTravel;
4429 mMode = MOVE_UP_POS;
4430 }
Chet Haase0061e162012-06-08 15:01:56 -07004431 } else if (clampedPosition > lastPos) {
Adam Powell45803472010-01-25 15:10:44 -08004432 final int boundPosFromFirst = boundPosition - firstPos;
4433 if (boundPosFromFirst < 1) {
4434 // Moving would shift our bound position off the screen. Abort.
4435 return;
4436 }
4437
Chet Haase0061e162012-06-08 15:01:56 -07004438 final int posTravel = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004439 final int boundTravel = boundPosFromFirst - 1;
4440 if (boundTravel < posTravel) {
4441 viewTravelCount = boundTravel;
4442 mMode = MOVE_DOWN_BOUND;
4443 } else {
4444 viewTravelCount = posTravel;
4445 mMode = MOVE_DOWN_POS;
4446 }
4447 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004448 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004449 return;
4450 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004451
Adam Powell45803472010-01-25 15:10:44 -08004452 if (viewTravelCount > 0) {
4453 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4454 } else {
4455 mScrollDuration = SCROLL_DURATION;
4456 }
Chet Haase0061e162012-06-08 15:01:56 -07004457 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004458 mBoundPos = boundPosition;
4459 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004460
Adam Powell1fa179ef2012-04-12 15:01:40 -07004461 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004462 }
Adam Powelle44afae2010-07-01 10:10:35 -07004463
4464 void startWithOffset(int position, int offset) {
Erik322171b2010-10-13 15:46:00 -07004465 startWithOffset(position, offset, SCROLL_DURATION);
4466 }
4467
Adam Powellaadf4fb2012-05-08 15:42:13 -07004468 void startWithOffset(final int position, int offset, final int duration) {
Adam Powell40322522011-01-12 21:58:20 -08004469 stop();
4470
Adam Powellaadf4fb2012-05-08 15:42:13 -07004471 if (mDataChanged) {
4472 // Wait until we're back in a stable state to try this.
4473 final int postOffset = offset;
Adam Powell161abf32012-05-23 17:22:49 -07004474 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004475 @Override public void run() {
4476 startWithOffset(position, postOffset, duration);
4477 }
Adam Powell161abf32012-05-23 17:22:49 -07004478 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004479 return;
4480 }
4481
4482 final int childCount = getChildCount();
4483 if (childCount == 0) {
4484 // Can't scroll without children.
4485 return;
4486 }
4487
Adam Powell1fa179ef2012-04-12 15:01:40 -07004488 offset += getPaddingTop();
4489
Chet Haase0061e162012-06-08 15:01:56 -07004490 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
Adam Powelle44afae2010-07-01 10:10:35 -07004491 mOffsetFromTop = offset;
4492 mBoundPos = INVALID_POSITION;
4493 mLastSeenPos = INVALID_POSITION;
4494 mMode = MOVE_OFFSET;
4495
4496 final int firstPos = mFirstPosition;
Adam Powell37113312010-07-08 18:21:48 -07004497 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004498
Romain Guy4bede9e2010-10-11 19:36:59 -07004499 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004500 if (mTargetPos < firstPos) {
4501 viewTravelCount = firstPos - mTargetPos;
4502 } else if (mTargetPos > lastPos) {
4503 viewTravelCount = mTargetPos - lastPos;
Adam Powelle44afae2010-07-01 10:10:35 -07004504 } else {
4505 // On-screen, just scroll.
Chet Haase0061e162012-06-08 15:01:56 -07004506 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Adam Powell0b8acd82012-04-25 20:29:23 -07004507 smoothScrollBy(targetTop - offset, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004508 return;
4509 }
4510
Adam Powell37113312010-07-08 18:21:48 -07004511 // Estimate how many screens we should travel
Daniel Lehmann4ef1da32010-08-27 16:40:59 -07004512 final float screenTravelCount = (float) viewTravelCount / childCount;
Adam Powell0b8acd82012-04-25 20:29:23 -07004513 mScrollDuration = screenTravelCount < 1 ?
4514 duration : (int) (duration / screenTravelCount);
Adam Powell37113312010-07-08 18:21:48 -07004515 mLastSeenPos = INVALID_POSITION;
Adam Powell234a5712010-09-14 10:34:56 -07004516
Adam Powell1fa179ef2012-04-12 15:01:40 -07004517 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004518 }
4519
Adam Powelle69370e2012-05-07 15:17:20 -07004520 /**
4521 * Scroll such that targetPos is in the visible padded region without scrolling
4522 * boundPos out of view. Assumes targetPos is onscreen.
4523 */
4524 void scrollToVisible(int targetPos, int boundPos, int duration) {
4525 final int firstPos = mFirstPosition;
4526 final int childCount = getChildCount();
4527 final int lastPos = firstPos + childCount - 1;
4528 final int paddedTop = mListPadding.top;
4529 final int paddedBottom = getHeight() - mListPadding.bottom;
4530
4531 if (targetPos < firstPos || targetPos > lastPos) {
4532 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
4533 " not visible [" + firstPos + ", " + lastPos + "]");
4534 }
4535 if (boundPos < firstPos || boundPos > lastPos) {
4536 // boundPos doesn't matter, it's already offscreen.
4537 boundPos = INVALID_POSITION;
4538 }
4539
4540 final View targetChild = getChildAt(targetPos - firstPos);
4541 final int targetTop = targetChild.getTop();
4542 final int targetBottom = targetChild.getBottom();
4543 int scrollBy = 0;
4544
4545 if (targetBottom > paddedBottom) {
4546 scrollBy = targetBottom - paddedBottom;
4547 }
4548 if (targetTop < paddedTop) {
4549 scrollBy = targetTop - paddedTop;
4550 }
4551
4552 if (scrollBy == 0) {
4553 return;
4554 }
4555
4556 if (boundPos >= 0) {
4557 final View boundChild = getChildAt(boundPos - firstPos);
4558 final int boundTop = boundChild.getTop();
4559 final int boundBottom = boundChild.getBottom();
4560 final int absScroll = Math.abs(scrollBy);
4561
4562 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
4563 // Don't scroll the bound view off the bottom of the screen.
4564 scrollBy = Math.max(0, boundBottom - paddedBottom);
4565 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
4566 // Don't scroll the bound view off the top of the screen.
4567 scrollBy = Math.min(0, boundTop - paddedTop);
4568 }
4569 }
4570
4571 smoothScrollBy(scrollBy, duration);
4572 }
4573
Adam Powell45803472010-01-25 15:10:44 -08004574 void stop() {
4575 removeCallbacks(this);
4576 }
Adam Powelle44afae2010-07-01 10:10:35 -07004577
Alan Viverette8fa327a2013-05-31 14:53:13 -07004578 @Override
Adam Powell45803472010-01-25 15:10:44 -08004579 public void run() {
4580 final int listHeight = getHeight();
4581 final int firstPos = mFirstPosition;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004582
Adam Powell45803472010-01-25 15:10:44 -08004583 switch (mMode) {
4584 case MOVE_DOWN_POS: {
4585 final int lastViewIndex = getChildCount() - 1;
4586 final int lastPos = firstPos + lastViewIndex;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004587
Adam Powell0b8bb422010-02-08 14:30:45 -08004588 if (lastViewIndex < 0) {
4589 return;
4590 }
Adam Powell45803472010-01-25 15:10:44 -08004591
4592 if (lastPos == mLastSeenPos) {
4593 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004594 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004595 return;
4596 }
4597
4598 final View lastView = getChildAt(lastViewIndex);
4599 final int lastViewHeight = lastView.getHeight();
4600 final int lastViewTop = lastView.getTop();
4601 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004602 final int extraScroll = lastPos < mItemCount - 1 ?
4603 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
Adam Powell45803472010-01-25 15:10:44 -08004604
Adam Powell1fa179ef2012-04-12 15:01:40 -07004605 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Adam Powell0b8acd82012-04-25 20:29:23 -07004606 smoothScrollBy(scrollBy, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004607
4608 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004609 if (lastPos < mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004610 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004611 }
4612 break;
4613 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004614
Adam Powell45803472010-01-25 15:10:44 -08004615 case MOVE_DOWN_BOUND: {
4616 final int nextViewIndex = 1;
Adam Powell029cfbd2010-03-08 19:03:54 -08004617 final int childCount = getChildCount();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004618
Adam Powell029cfbd2010-03-08 19:03:54 -08004619 if (firstPos == mBoundPos || childCount <= nextViewIndex
4620 || firstPos + childCount >= mItemCount) {
Adam Powell45803472010-01-25 15:10:44 -08004621 return;
4622 }
4623 final int nextPos = firstPos + nextViewIndex;
4624
4625 if (nextPos == mLastSeenPos) {
4626 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004627 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004628 return;
4629 }
4630
4631 final View nextView = getChildAt(nextViewIndex);
4632 final int nextViewHeight = nextView.getHeight();
4633 final int nextViewTop = nextView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004634 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
Adam Powell7e5e3742010-05-24 15:13:41 -07004635 if (nextPos < mBoundPos) {
Adam Powell45803472010-01-25 15:10:44 -08004636 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Adam Powell0b8acd82012-04-25 20:29:23 -07004637 mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004638
4639 mLastSeenPos = nextPos;
4640
Adam Powell1fa179ef2012-04-12 15:01:40 -07004641 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004642 } else {
Mindy Pereira4e30d892010-11-24 15:32:39 -08004643 if (nextViewTop > extraScroll) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004644 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004645 }
4646 }
4647 break;
4648 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004649
Adam Powell45803472010-01-25 15:10:44 -08004650 case MOVE_UP_POS: {
4651 if (firstPos == mLastSeenPos) {
4652 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004653 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004654 return;
4655 }
4656
4657 final View firstView = getChildAt(0);
Adam Powell0b8bb422010-02-08 14:30:45 -08004658 if (firstView == null) {
4659 return;
4660 }
Adam Powell45803472010-01-25 15:10:44 -08004661 final int firstViewTop = firstView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004662 final int extraScroll = firstPos > 0 ?
4663 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
Adam Powell45803472010-01-25 15:10:44 -08004664
Adam Powell0b8acd82012-04-25 20:29:23 -07004665 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004666
4667 mLastSeenPos = firstPos;
4668
Adam Powell7e5e3742010-05-24 15:13:41 -07004669 if (firstPos > mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004670 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004671 }
4672 break;
4673 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004674
Adam Powell45803472010-01-25 15:10:44 -08004675 case MOVE_UP_BOUND: {
4676 final int lastViewIndex = getChildCount() - 2;
4677 if (lastViewIndex < 0) {
4678 return;
4679 }
4680 final int lastPos = firstPos + lastViewIndex;
4681
4682 if (lastPos == mLastSeenPos) {
4683 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004684 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004685 return;
4686 }
4687
4688 final View lastView = getChildAt(lastViewIndex);
4689 final int lastViewHeight = lastView.getHeight();
4690 final int lastViewTop = lastView.getTop();
4691 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004692 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
Adam Powell45803472010-01-25 15:10:44 -08004693 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004694 if (lastPos > mBoundPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004695 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004696 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004697 } else {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004698 final int bottom = listHeight - extraScroll;
Adam Powell45803472010-01-25 15:10:44 -08004699 final int lastViewBottom = lastViewTop + lastViewHeight;
4700 if (bottom > lastViewBottom) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004701 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004702 }
4703 }
4704 break;
4705 }
4706
Adam Powelle44afae2010-07-01 10:10:35 -07004707 case MOVE_OFFSET: {
Adam Powell234a5712010-09-14 10:34:56 -07004708 if (mLastSeenPos == firstPos) {
4709 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004710 postOnAnimation(this);
Adam Powell234a5712010-09-14 10:34:56 -07004711 return;
4712 }
Adam Powelle44afae2010-07-01 10:10:35 -07004713
Adam Powell37113312010-07-08 18:21:48 -07004714 mLastSeenPos = firstPos;
Adam Powell234a5712010-09-14 10:34:56 -07004715
4716 final int childCount = getChildCount();
Adam Powelle44afae2010-07-01 10:10:35 -07004717 final int position = mTargetPos;
Adam Powell37113312010-07-08 18:21:48 -07004718 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004719
Adam Powell40322522011-01-12 21:58:20 -08004720 int viewTravelCount = 0;
Adam Powelle44afae2010-07-01 10:10:35 -07004721 if (position < firstPos) {
Adam Powell40322522011-01-12 21:58:20 -08004722 viewTravelCount = firstPos - position + 1;
4723 } else if (position > lastPos) {
4724 viewTravelCount = position - lastPos;
4725 }
4726
4727 // Estimate how many screens we should travel
4728 final float screenTravelCount = (float) viewTravelCount / childCount;
4729
4730 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4731 if (position < firstPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004732 final int distance = (int) (-getHeight() * modifier);
4733 final int duration = (int) (mScrollDuration * modifier);
4734 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004735 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004736 } else if (position > lastPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004737 final int distance = (int) (getHeight() * modifier);
4738 final int duration = (int) (mScrollDuration * modifier);
4739 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004740 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004741 } else {
4742 // On-screen, just scroll.
4743 final int targetTop = getChildAt(position - firstPos).getTop();
Adam Powell234a5712010-09-14 10:34:56 -07004744 final int distance = targetTop - mOffsetFromTop;
Adam Powell0b8acd82012-04-25 20:29:23 -07004745 final int duration = (int) (mScrollDuration *
4746 ((float) Math.abs(distance) / getHeight()));
4747 smoothScrollBy(distance, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004748 }
4749 break;
4750 }
4751
Adam Powell45803472010-01-25 15:10:44 -08004752 default:
4753 break;
4754 }
4755 }
4756 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004757
Adam Powell45803472010-01-25 15:10:44 -08004758 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004759 * The amount of friction applied to flings. The default value
4760 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004761 */
4762 public void setFriction(float friction) {
4763 if (mFlingRunnable == null) {
4764 mFlingRunnable = new FlingRunnable();
4765 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004766 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004767 }
Romain Guy21317d12010-10-12 13:32:31 -07004768
4769 /**
4770 * Sets a scale factor for the fling velocity. The initial scale
4771 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004772 *
Romain Guy21317d12010-10-12 13:32:31 -07004773 * @param scale The scale factor to multiply the velocity by.
4774 */
4775 public void setVelocityScale(float scale) {
4776 mVelocityScale = scale;
4777 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004778
Romain Guy4bede9e2010-10-11 19:36:59 -07004779 /**
Adam Powell45803472010-01-25 15:10:44 -08004780 * Smoothly scroll to the specified adapter position. The view will
4781 * scroll such that the indicated position is displayed.
4782 * @param position Scroll to this adapter position.
4783 */
4784 public void smoothScrollToPosition(int position) {
4785 if (mPositionScroller == null) {
4786 mPositionScroller = new PositionScroller();
4787 }
4788 mPositionScroller.start(position);
4789 }
Erik322171b2010-10-13 15:46:00 -07004790
4791 /**
4792 * Smoothly scroll to the specified adapter position. The view will scroll
4793 * such that the indicated position is displayed <code>offset</code> pixels from
4794 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4795 * the first or last item beyond the boundaries of the list) it will get as close
4796 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4797 *
4798 * @param position Position to scroll to
4799 * @param offset Desired distance in pixels of <code>position</code> from the top
4800 * of the view when scrolling is finished
4801 * @param duration Number of milliseconds to use for the scroll
4802 */
4803 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4804 if (mPositionScroller == null) {
4805 mPositionScroller = new PositionScroller();
4806 }
4807 mPositionScroller.startWithOffset(position, offset, duration);
4808 }
4809
Adam Powell45803472010-01-25 15:10:44 -08004810 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004811 * Smoothly scroll to the specified adapter position. The view will scroll
4812 * such that the indicated position is displayed <code>offset</code> pixels from
4813 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4814 * the first or last item beyond the boundaries of the list) it will get as close
4815 * as possible.
4816 *
4817 * @param position Position to scroll to
4818 * @param offset Desired distance in pixels of <code>position</code> from the top
4819 * of the view when scrolling is finished
4820 */
4821 public void smoothScrollToPositionFromTop(int position, int offset) {
4822 if (mPositionScroller == null) {
4823 mPositionScroller = new PositionScroller();
4824 }
4825 mPositionScroller.startWithOffset(position, offset);
4826 }
4827
4828 /**
Adam Powell45803472010-01-25 15:10:44 -08004829 * Smoothly scroll to the specified adapter position. The view will
4830 * scroll such that the indicated position is displayed, but it will
4831 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004832 * view.
Adam Powell45803472010-01-25 15:10:44 -08004833 * @param position Scroll to this adapter position.
4834 * @param boundPosition Do not scroll if it would move this adapter
4835 * position out of view.
4836 */
4837 public void smoothScrollToPosition(int position, int boundPosition) {
4838 if (mPositionScroller == null) {
4839 mPositionScroller = new PositionScroller();
4840 }
4841 mPositionScroller.start(position, boundPosition);
4842 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004843
Adam Powell45803472010-01-25 15:10:44 -08004844 /**
4845 * Smoothly scroll by distance pixels over duration milliseconds.
4846 * @param distance Distance to scroll in pixels.
4847 * @param duration Duration of the scroll animation in milliseconds.
4848 */
4849 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004850 smoothScrollBy(distance, duration, false);
4851 }
4852
4853 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004854 if (mFlingRunnable == null) {
4855 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004856 }
Adam Powell40322522011-01-12 21:58:20 -08004857
Marc Blank299acb52010-10-21 11:03:53 -07004858 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004859 final int firstPos = mFirstPosition;
4860 final int childCount = getChildCount();
4861 final int lastPos = firstPos + childCount;
4862 final int topLimit = getPaddingTop();
4863 final int bottomLimit = getHeight() - getPaddingBottom();
4864
Adam Powell79303752011-01-13 22:06:49 -08004865 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004866 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004867 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004868 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4869 mFlingRunnable.endFling();
4870 if (mPositionScroller != null) {
4871 mPositionScroller.stop();
4872 }
4873 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004874 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004875 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004876 }
Adam Powell45803472010-01-25 15:10:44 -08004877 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004878
Winson Chung499cb9f2010-07-16 11:18:17 -07004879 /**
4880 * Allows RemoteViews to scroll relatively to a position.
4881 */
4882 void smoothScrollByOffset(int position) {
4883 int index = -1;
4884 if (position < 0) {
4885 index = getFirstVisiblePosition();
4886 } else if (position > 0) {
4887 index = getLastVisiblePosition();
4888 }
4889
4890 if (index > -1) {
4891 View child = getChildAt(index - getFirstVisiblePosition());
4892 if (child != null) {
4893 Rect visibleRect = new Rect();
4894 if (child.getGlobalVisibleRect(visibleRect)) {
4895 // the child is partially visible
4896 int childRectArea = child.getWidth() * child.getHeight();
4897 int visibleRectArea = visibleRect.width() * visibleRect.height();
4898 float visibleArea = (visibleRectArea / (float) childRectArea);
4899 final float visibleThreshold = 0.75f;
4900 if ((position < 0) && (visibleArea < visibleThreshold)) {
4901 // the top index is not perceivably visible so offset
4902 // to account for showing that top index as well
4903 ++index;
4904 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4905 // the bottom index is not perceivably visible so offset
4906 // to account for showing that bottom index as well
4907 --index;
4908 }
4909 }
4910 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4911 }
4912 }
4913 }
4914
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004915 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004916 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004917 setChildrenDrawnWithCacheEnabled(true);
4918 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004919 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004920 }
4921 }
4922
4923 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004924 if (!isHardwareAccelerated()) {
4925 if (mClearScrollingCache == null) {
4926 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004927 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07004928 public void run() {
4929 if (mCachingStarted) {
4930 mCachingStarted = mCachingActive = false;
4931 setChildrenDrawnWithCacheEnabled(false);
4932 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4933 setChildrenDrawingCacheEnabled(false);
4934 }
4935 if (!isAlwaysDrawnWithCacheEnabled()) {
4936 invalidate();
4937 }
Romain Guy6dfed242009-05-11 18:25:05 -07004938 }
4939 }
Romain Guy9d849a22012-03-14 16:41:42 -07004940 };
4941 }
4942 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004943 }
4944 }
4945
4946 /**
Alan Viverette2f3317a2013-08-06 18:19:48 -07004947 * Scrolls the list items within the view by a specified number of pixels.
4948 *
4949 * @param y the amount of pixels to scroll by vertically
Alan Viveretteba299062013-09-03 16:01:51 -07004950 * @see #canScrollList(int)
Alan Viverette2f3317a2013-08-06 18:19:48 -07004951 */
Alan Viveretteba299062013-09-03 16:01:51 -07004952 public void scrollListBy(int y) {
4953 trackMotionScroll(-y, -y);
4954 }
4955
4956 /**
4957 * Check if the items in the list can be scrolled in a certain direction.
4958 *
4959 * @param direction Negative to check scrolling up, positive to check
4960 * scrolling down.
4961 * @return true if the list can be scrolled in the specified direction,
4962 * false otherwise.
4963 * @see #scrollListBy(int)
4964 */
4965 public boolean canScrollList(int direction) {
4966 final int childCount = getChildCount();
4967 if (childCount == 0) {
4968 return false;
4969 }
4970
4971 final int firstPosition = mFirstPosition;
4972 final Rect listPadding = mListPadding;
4973 if (direction > 0) {
4974 final int lastBottom = getChildAt(childCount - 1).getBottom();
4975 final int lastPosition = firstPosition + childCount;
4976 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
4977 } else {
4978 final int firstTop = getChildAt(0).getTop();
4979 return firstPosition > 0 || firstTop < listPadding.top;
4980 }
Alan Viverette2f3317a2013-08-06 18:19:48 -07004981 }
4982
4983 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004984 * Track a motion scroll
4985 *
4986 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4987 * began. Positive numbers mean the user's finger is moving down the screen.
4988 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08004989 * @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 -08004990 */
Adam Powell45803472010-01-25 15:10:44 -08004991 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004992 final int childCount = getChildCount();
4993 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08004994 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004995 }
4996
4997 final int firstTop = getChildAt(0).getTop();
4998 final int lastBottom = getChildAt(childCount - 1).getBottom();
4999
5000 final Rect listPadding = mListPadding;
5001
Adam Powellbdccc2d2010-12-14 17:34:27 -08005002 // "effective padding" In this case is the amount of padding that affects
5003 // how much space should not be filled by items. If we don't clip to padding
5004 // there is no effective padding.
5005 int effectivePaddingTop = 0;
5006 int effectivePaddingBottom = 0;
5007 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5008 effectivePaddingTop = listPadding.top;
5009 effectivePaddingBottom = listPadding.bottom;
5010 }
5011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005012 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08005013 final int spaceAbove = effectivePaddingTop - firstTop;
5014 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005015 final int spaceBelow = lastBottom - end;
5016
5017 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5018 if (deltaY < 0) {
5019 deltaY = Math.max(-(height - 1), deltaY);
5020 } else {
5021 deltaY = Math.min(height - 1, deltaY);
5022 }
5023
5024 if (incrementalDeltaY < 0) {
5025 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5026 } else {
5027 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5028 }
5029
Adam Powell45803472010-01-25 15:10:44 -08005030 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005031
Adam Powell637d3372010-08-25 14:37:03 -07005032 // Update our guesses for where the first and last views are
5033 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005034 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07005035 } else {
5036 mFirstPositionDistanceGuess += incrementalDeltaY;
5037 }
5038 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08005039 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07005040 } else {
5041 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005042 }
Adam Powell45803472010-01-25 15:10:44 -08005043
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005044 final boolean cannotScrollDown = (firstPosition == 0 &&
5045 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5046 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5047 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07005048
Gilles Debunne0a1b8182011-02-28 16:01:09 -08005049 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07005050 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08005051 }
5052
5053 final boolean down = incrementalDeltaY < 0;
5054
Adam Powell029cfbd2010-03-08 19:03:54 -08005055 final boolean inTouchMode = isInTouchMode();
5056 if (inTouchMode) {
5057 hideSelector();
5058 }
Adam Powell45803472010-01-25 15:10:44 -08005059
5060 final int headerViewsCount = getHeaderViewsCount();
5061 final int footerViewsStart = mItemCount - getFooterViewsCount();
5062
5063 int start = 0;
5064 int count = 0;
5065
5066 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005067 int top = -incrementalDeltaY;
5068 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5069 top += listPadding.top;
5070 }
Adam Powell45803472010-01-25 15:10:44 -08005071 for (int i = 0; i < childCount; i++) {
5072 final View child = getChildAt(i);
5073 if (child.getBottom() >= top) {
5074 break;
5075 } else {
5076 count++;
5077 int position = firstPosition + i;
5078 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005079 // The view will be rebound to new data, clear any
5080 // system-managed transient state.
5081 if (child.isAccessibilityFocused()) {
5082 child.clearAccessibilityFocus();
5083 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005084 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005085 }
5086 }
5087 }
5088 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08005089 int bottom = getHeight() - incrementalDeltaY;
5090 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5091 bottom -= listPadding.bottom;
5092 }
Adam Powell45803472010-01-25 15:10:44 -08005093 for (int i = childCount - 1; i >= 0; i--) {
5094 final View child = getChildAt(i);
5095 if (child.getTop() <= bottom) {
5096 break;
5097 } else {
5098 start = i;
5099 count++;
5100 int position = firstPosition + i;
5101 if (position >= headerViewsCount && position < footerViewsStart) {
Alan Viverette1e51cc72013-09-27 14:32:20 -07005102 // The view will be rebound to new data, clear any
5103 // system-managed transient state.
5104 if (child.isAccessibilityFocused()) {
5105 child.clearAccessibilityFocus();
5106 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005107 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08005108 }
5109 }
5110 }
5111 }
5112
5113 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5114
5115 mBlockLayoutRequests = true;
5116
5117 if (count > 0) {
5118 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005119 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005120 }
Adam Powell539ee872012-02-03 19:00:49 -08005121
Romain Guy9d849a22012-03-14 16:41:42 -07005122 // invalidate before moving the children to avoid unnecessary invalidate
5123 // calls to bubble up from the children all the way to the top
5124 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005125 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005126 }
5127
Adam Powell45803472010-01-25 15:10:44 -08005128 offsetChildrenTopAndBottom(incrementalDeltaY);
5129
5130 if (down) {
5131 mFirstPosition += count;
5132 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005133
Adam Powell45803472010-01-25 15:10:44 -08005134 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5135 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5136 fillGap(down);
5137 }
5138
Adam Powell029cfbd2010-03-08 19:03:54 -08005139 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005140 final int childIndex = mSelectedPosition - mFirstPosition;
5141 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005142 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08005143 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005144 } else if (mSelectorPosition != INVALID_POSITION) {
5145 final int childIndex = mSelectorPosition - mFirstPosition;
5146 if (childIndex >= 0 && childIndex < getChildCount()) {
5147 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5148 }
5149 } else {
5150 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005151 }
5152
Adam Powell45803472010-01-25 15:10:44 -08005153 mBlockLayoutRequests = false;
5154
5155 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005156
Adam Powell45803472010-01-25 15:10:44 -08005157 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005158 }
5159
5160 /**
5161 * Returns the number of header views in the list. Header views are special views
5162 * at the top of the list that should not be recycled during a layout.
5163 *
5164 * @return The number of header views, 0 in the default implementation.
5165 */
5166 int getHeaderViewsCount() {
5167 return 0;
5168 }
5169
5170 /**
5171 * Returns the number of footer views in the list. Footer views are special views
5172 * at the bottom of the list that should not be recycled during a layout.
5173 *
5174 * @return The number of footer views, 0 in the default implementation.
5175 */
5176 int getFooterViewsCount() {
5177 return 0;
5178 }
5179
5180 /**
5181 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5182 * remain on screen are shifted and the other ones are discarded. The role of this
5183 * method is to fill the gap thus created by performing a partial layout in the
5184 * empty space.
5185 *
5186 * @param down true if the scroll is going down, false if it is going up
5187 */
5188 abstract void fillGap(boolean down);
5189
5190 void hideSelector() {
5191 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005192 if (mLayoutMode != LAYOUT_SPECIFIC) {
5193 mResurrectToPosition = mSelectedPosition;
5194 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005195 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5196 mResurrectToPosition = mNextSelectedPosition;
5197 }
5198 setSelectedPositionInt(INVALID_POSITION);
5199 setNextSelectedPositionInt(INVALID_POSITION);
5200 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005201 }
5202 }
5203
5204 /**
5205 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5206 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5207 * of items available in the adapter
5208 */
5209 int reconcileSelectedPosition() {
5210 int position = mSelectedPosition;
5211 if (position < 0) {
5212 position = mResurrectToPosition;
5213 }
5214 position = Math.max(0, position);
5215 position = Math.min(position, mItemCount - 1);
5216 return position;
5217 }
5218
5219 /**
5220 * Find the row closest to y. This row will be used as the motion row when scrolling
5221 *
5222 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005223 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005224 */
5225 abstract int findMotionRow(int y);
5226
5227 /**
Adam Powell637d3372010-08-25 14:37:03 -07005228 * Find the row closest to y. This row will be used as the motion row when scrolling.
5229 *
5230 * @param y Where the user touched
5231 * @return The position of the first (or only) item in the row closest to y
5232 */
5233 int findClosestMotionRow(int y) {
5234 final int childCount = getChildCount();
5235 if (childCount == 0) {
5236 return INVALID_POSITION;
5237 }
5238
5239 final int motionRow = findMotionRow(y);
5240 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5241 }
5242
5243 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005244 * Causes all the views to be rebuilt and redrawn.
5245 */
5246 public void invalidateViews() {
5247 mDataChanged = true;
5248 rememberSyncState();
5249 requestLayout();
5250 invalidate();
5251 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005252
Jeff Brown4e6319b2010-12-13 10:36:51 -08005253 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005254 * If there is a selection returns false.
5255 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005256 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005257 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005258 if (mSelectedPosition < 0 && resurrectSelection()) {
5259 updateSelectorState();
5260 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005261 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005262 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005263 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005264
5265 /**
5266 * Makes the item at the supplied position selected.
5267 *
5268 * @param position the position of the new selection
5269 */
5270 abstract void setSelectionInt(int position);
5271
5272 /**
5273 * Attempt to bring the selection back if the user is switching from touch
5274 * to trackball mode
5275 * @return Whether selection was set to something.
5276 */
5277 boolean resurrectSelection() {
5278 final int childCount = getChildCount();
5279
5280 if (childCount <= 0) {
5281 return false;
5282 }
5283
5284 int selectedTop = 0;
5285 int selectedPos;
5286 int childrenTop = mListPadding.top;
5287 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5288 final int firstPosition = mFirstPosition;
5289 final int toPosition = mResurrectToPosition;
5290 boolean down = true;
5291
5292 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5293 selectedPos = toPosition;
5294
5295 final View selected = getChildAt(selectedPos - mFirstPosition);
5296 selectedTop = selected.getTop();
5297 int selectedBottom = selected.getBottom();
5298
5299 // We are scrolled, don't get in the fade
5300 if (selectedTop < childrenTop) {
5301 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5302 } else if (selectedBottom > childrenBottom) {
5303 selectedTop = childrenBottom - selected.getMeasuredHeight()
5304 - getVerticalFadingEdgeLength();
5305 }
5306 } else {
5307 if (toPosition < firstPosition) {
5308 // Default to selecting whatever is first
5309 selectedPos = firstPosition;
5310 for (int i = 0; i < childCount; i++) {
5311 final View v = getChildAt(i);
5312 final int top = v.getTop();
5313
5314 if (i == 0) {
5315 // Remember the position of the first item
5316 selectedTop = top;
5317 // See if we are scrolled at all
5318 if (firstPosition > 0 || top < childrenTop) {
5319 // If we are scrolled, don't select anything that is
5320 // in the fade region
5321 childrenTop += getVerticalFadingEdgeLength();
5322 }
5323 }
5324 if (top >= childrenTop) {
5325 // Found a view whose top is fully visisble
5326 selectedPos = firstPosition + i;
5327 selectedTop = top;
5328 break;
5329 }
5330 }
5331 } else {
5332 final int itemCount = mItemCount;
5333 down = false;
5334 selectedPos = firstPosition + childCount - 1;
5335
5336 for (int i = childCount - 1; i >= 0; i--) {
5337 final View v = getChildAt(i);
5338 final int top = v.getTop();
5339 final int bottom = v.getBottom();
5340
5341 if (i == childCount - 1) {
5342 selectedTop = top;
5343 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5344 childrenBottom -= getVerticalFadingEdgeLength();
5345 }
5346 }
5347
5348 if (bottom <= childrenBottom) {
5349 selectedPos = firstPosition + i;
5350 selectedTop = top;
5351 break;
5352 }
5353 }
5354 }
5355 }
5356
5357 mResurrectToPosition = INVALID_POSITION;
5358 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005359 if (mPositionScroller != null) {
5360 mPositionScroller.stop();
5361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005362 mTouchMode = TOUCH_MODE_REST;
5363 clearScrollingCache();
5364 mSpecificTop = selectedTop;
5365 selectedPos = lookForSelectablePosition(selectedPos, down);
5366 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5367 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005368 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005369 setSelectionInt(selectedPos);
5370 invokeOnItemScrollListener();
5371 } else {
5372 selectedPos = INVALID_POSITION;
5373 }
5374 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5375
5376 return selectedPos >= 0;
5377 }
5378
Adam Powell14c08042011-10-06 19:46:18 -07005379 void confirmCheckedPositionsById() {
5380 // Clear out the positional check states, we'll rebuild it below from IDs.
5381 mCheckStates.clear();
5382
5383 boolean checkedCountChanged = false;
5384 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5385 final long id = mCheckedIdStates.keyAt(checkedIndex);
5386 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5387
5388 final long lastPosId = mAdapter.getItemId(lastPos);
5389 if (id != lastPosId) {
5390 // Look around to see if the ID is nearby. If not, uncheck it.
5391 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5392 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5393 boolean found = false;
5394 for (int searchPos = start; searchPos < end; searchPos++) {
5395 final long searchId = mAdapter.getItemId(searchPos);
5396 if (id == searchId) {
5397 found = true;
5398 mCheckStates.put(searchPos, true);
5399 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5400 break;
5401 }
5402 }
5403
5404 if (!found) {
5405 mCheckedIdStates.delete(id);
5406 checkedIndex--;
5407 mCheckedItemCount--;
5408 checkedCountChanged = true;
5409 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5410 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5411 lastPos, id, false);
5412 }
5413 }
5414 } else {
5415 mCheckStates.put(lastPos, true);
5416 }
5417 }
5418
5419 if (checkedCountChanged && mChoiceActionMode != null) {
5420 mChoiceActionMode.invalidate();
5421 }
5422 }
5423
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005424 @Override
5425 protected void handleDataChanged() {
5426 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005427 int lastHandledItemCount = mLastHandledItemCount;
5428 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005429
5430 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5431 confirmCheckedPositionsById();
5432 }
5433
Adam Powell539ee872012-02-03 19:00:49 -08005434 // TODO: In the future we can recycle these views based on stable ID instead.
5435 mRecycler.clearTransientStateViews();
5436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005437 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005438 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005439 int selectablePos;
5440
5441 // Find the row we are supposed to sync to
5442 if (mNeedSync) {
5443 // Update this first, since setNextSelectedPositionInt inspects it
5444 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005445 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005446
Adam Powell07852792010-11-10 16:57:05 -08005447 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005448 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5449 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005450 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5451 if (mForceTranscriptScroll) {
5452 mForceTranscriptScroll = false;
5453 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5454 return;
5455 }
Adam Powell07852792010-11-10 16:57:05 -08005456 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005457 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005458 final View lastChild = getChildAt(childCount - 1);
5459 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005460 if (mFirstPosition + childCount >= lastHandledItemCount &&
5461 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005462 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5463 return;
5464 }
5465 // Something new came in and we didn't scroll; give the user a clue that
5466 // there's something new.
5467 awakenScrollBars();
5468 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005469
5470 switch (mSyncMode) {
5471 case SYNC_SELECTED_POSITION:
5472 if (isInTouchMode()) {
5473 // We saved our state when not in touch mode. (We know this because
5474 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5475 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5476 // adjusting if the available range changed) and return.
5477 mLayoutMode = LAYOUT_SYNC;
5478 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5479
5480 return;
5481 } else {
5482 // See if we can find a position in the new data with the same
5483 // id as the old selection. This will change mSyncPosition.
5484 newPos = findSyncPosition();
5485 if (newPos >= 0) {
5486 // Found it. Now verify that new selection is still selectable
5487 selectablePos = lookForSelectablePosition(newPos, true);
5488 if (selectablePos == newPos) {
5489 // Same row id is selected
5490 mSyncPosition = newPos;
5491
5492 if (mSyncHeight == getHeight()) {
5493 // If we are at the same height as when we saved state, try
5494 // to restore the scroll position too.
5495 mLayoutMode = LAYOUT_SYNC;
5496 } else {
5497 // We are not the same height as when the selection was saved, so
5498 // don't try to restore the exact position
5499 mLayoutMode = LAYOUT_SET_SELECTION;
5500 }
5501
5502 // Restore selection
5503 setNextSelectedPositionInt(newPos);
5504 return;
5505 }
5506 }
5507 }
5508 break;
5509 case SYNC_FIRST_POSITION:
5510 // Leave mSyncPosition as it is -- just pin to available range
5511 mLayoutMode = LAYOUT_SYNC;
5512 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5513
5514 return;
5515 }
5516 }
5517
5518 if (!isInTouchMode()) {
5519 // We couldn't find matching data -- try to use the same position
5520 newPos = getSelectedItemPosition();
5521
5522 // Pin position to the available range
5523 if (newPos >= count) {
5524 newPos = count - 1;
5525 }
5526 if (newPos < 0) {
5527 newPos = 0;
5528 }
5529
5530 // Make sure we select something selectable -- first look down
5531 selectablePos = lookForSelectablePosition(newPos, true);
5532
5533 if (selectablePos >= 0) {
5534 setNextSelectedPositionInt(selectablePos);
5535 return;
5536 } else {
5537 // Looking down didn't work -- try looking up
5538 selectablePos = lookForSelectablePosition(newPos, false);
5539 if (selectablePos >= 0) {
5540 setNextSelectedPositionInt(selectablePos);
5541 return;
5542 }
5543 }
5544 } else {
5545
5546 // We already know where we want to resurrect the selection
5547 if (mResurrectToPosition >= 0) {
5548 return;
5549 }
5550 }
5551
5552 }
5553
5554 // Nothing is selected. Give up and reset everything.
5555 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5556 mSelectedPosition = INVALID_POSITION;
5557 mSelectedRowId = INVALID_ROW_ID;
5558 mNextSelectedPosition = INVALID_POSITION;
5559 mNextSelectedRowId = INVALID_ROW_ID;
5560 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005561 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005562 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005563 checkSelectionChanged();
5564 }
5565
Romain Guy43c9cdf2010-01-27 13:53:55 -08005566 @Override
5567 protected void onDisplayHint(int hint) {
5568 super.onDisplayHint(hint);
5569 switch (hint) {
5570 case INVISIBLE:
5571 if (mPopup != null && mPopup.isShowing()) {
5572 dismissPopup();
5573 }
5574 break;
5575 case VISIBLE:
5576 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5577 showPopup();
5578 }
5579 break;
5580 }
Romain Guy24562482010-02-01 14:56:19 -08005581 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005582 }
5583
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005584 /**
5585 * Removes the filter window
5586 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005587 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005588 if (mPopup != null) {
5589 mPopup.dismiss();
5590 }
5591 }
5592
5593 /**
5594 * Shows the filter window
5595 */
5596 private void showPopup() {
5597 // Make sure we have a window before showing the popup
5598 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005599 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005600 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005601 // Make sure we get focus if we are showing the popup
5602 checkFocus();
5603 }
5604 }
5605
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005606 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005607 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5608 final int[] xy = new int[2];
5609 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005610 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005611 // TODO: And the gravity should be defined in the theme as well
5612 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005613 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005614 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5615 xy[0], bottomGap);
5616 } else {
5617 mPopup.update(xy[0], bottomGap, -1, -1);
5618 }
5619 }
5620
5621 /**
5622 * What is the distance between the source and destination rectangles given the direction of
5623 * focus navigation between them? The direction basically helps figure out more quickly what is
5624 * self evident by the relationship between the rects...
5625 *
5626 * @param source the source rectangle
5627 * @param dest the destination rectangle
5628 * @param direction the direction
5629 * @return the distance between the rectangles
5630 */
5631 static int getDistance(Rect source, Rect dest, int direction) {
5632 int sX, sY; // source x, y
5633 int dX, dY; // dest x, y
5634 switch (direction) {
5635 case View.FOCUS_RIGHT:
5636 sX = source.right;
5637 sY = source.top + source.height() / 2;
5638 dX = dest.left;
5639 dY = dest.top + dest.height() / 2;
5640 break;
5641 case View.FOCUS_DOWN:
5642 sX = source.left + source.width() / 2;
5643 sY = source.bottom;
5644 dX = dest.left + dest.width() / 2;
5645 dY = dest.top;
5646 break;
5647 case View.FOCUS_LEFT:
5648 sX = source.left;
5649 sY = source.top + source.height() / 2;
5650 dX = dest.right;
5651 dY = dest.top + dest.height() / 2;
5652 break;
5653 case View.FOCUS_UP:
5654 sX = source.left + source.width() / 2;
5655 sY = source.top;
5656 dX = dest.left + dest.width() / 2;
5657 dY = dest.bottom;
5658 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005659 case View.FOCUS_FORWARD:
5660 case View.FOCUS_BACKWARD:
5661 sX = source.right + source.width() / 2;
5662 sY = source.top + source.height() / 2;
5663 dX = dest.left + dest.width() / 2;
5664 dY = dest.top + dest.height() / 2;
5665 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005666 default:
5667 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005668 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5669 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005670 }
5671 int deltaX = dX - sX;
5672 int deltaY = dY - sY;
5673 return deltaY * deltaY + deltaX * deltaX;
5674 }
5675
5676 @Override
5677 protected boolean isInFilterMode() {
5678 return mFiltered;
5679 }
5680
5681 /**
5682 * Sends a key to the text filter window
5683 *
5684 * @param keyCode The keycode for the event
5685 * @param event The actual key event
5686 *
5687 * @return True if the text filter handled the event, false otherwise.
5688 */
5689 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005690 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005691 return false;
5692 }
5693
5694 boolean handled = false;
5695 boolean okToSend = true;
5696 switch (keyCode) {
5697 case KeyEvent.KEYCODE_DPAD_UP:
5698 case KeyEvent.KEYCODE_DPAD_DOWN:
5699 case KeyEvent.KEYCODE_DPAD_LEFT:
5700 case KeyEvent.KEYCODE_DPAD_RIGHT:
5701 case KeyEvent.KEYCODE_DPAD_CENTER:
5702 case KeyEvent.KEYCODE_ENTER:
5703 okToSend = false;
5704 break;
5705 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005706 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005707 if (event.getAction() == KeyEvent.ACTION_DOWN
5708 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005709 KeyEvent.DispatcherState state = getKeyDispatcherState();
5710 if (state != null) {
5711 state.startTracking(event, this);
5712 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005713 handled = true;
5714 } else if (event.getAction() == KeyEvent.ACTION_UP
5715 && event.isTracking() && !event.isCanceled()) {
5716 handled = true;
5717 mTextFilter.setText("");
5718 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005719 }
5720 okToSend = false;
5721 break;
5722 case KeyEvent.KEYCODE_SPACE:
5723 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005724 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005725 break;
5726 }
5727
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005728 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005729 createTextFilter(true);
5730
5731 KeyEvent forwardEvent = event;
5732 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005733 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005734 }
5735
5736 int action = event.getAction();
5737 switch (action) {
5738 case KeyEvent.ACTION_DOWN:
5739 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5740 break;
5741
5742 case KeyEvent.ACTION_UP:
5743 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5744 break;
5745
5746 case KeyEvent.ACTION_MULTIPLE:
5747 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5748 break;
5749 }
5750 }
5751 return handled;
5752 }
5753
5754 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005755 * Return an InputConnection for editing of the filter text.
5756 */
5757 @Override
5758 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005759 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005760 if (mPublicInputConnection == null) {
5761 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005762 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005763 }
Romain Guyf6991302013-06-05 17:19:01 -07005764 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005765 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5766 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005767 }
5768 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005769 }
Romain Guy0a637162009-05-29 14:43:54 -07005770
Romain Guyf6991302013-06-05 17:19:01 -07005771 private class InputConnectionWrapper implements InputConnection {
5772 private final EditorInfo mOutAttrs;
5773 private InputConnection mTarget;
5774
5775 public InputConnectionWrapper(EditorInfo outAttrs) {
5776 mOutAttrs = outAttrs;
5777 }
5778
5779 private InputConnection getTarget() {
5780 if (mTarget == null) {
5781 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5782 }
5783 return mTarget;
5784 }
5785
5786 @Override
5787 public boolean reportFullscreenMode(boolean enabled) {
5788 // Use our own input connection, since it is
5789 // the "real" one the IME is talking with.
5790 return mDefInputConnection.reportFullscreenMode(enabled);
5791 }
5792
5793 @Override
5794 public boolean performEditorAction(int editorAction) {
5795 // The editor is off in its own window; we need to be
5796 // the one that does this.
5797 if (editorAction == EditorInfo.IME_ACTION_DONE) {
5798 InputMethodManager imm = (InputMethodManager)
5799 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
5800 if (imm != null) {
5801 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5802 }
5803 return true;
5804 }
5805 return false;
5806 }
5807
5808 @Override
5809 public boolean sendKeyEvent(KeyEvent event) {
5810 // Use our own input connection, since the filter
5811 // text view may not be shown in a window so has
5812 // no ViewAncestor to dispatch events with.
5813 return mDefInputConnection.sendKeyEvent(event);
5814 }
5815
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005816 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005817 public CharSequence getTextBeforeCursor(int n, int flags) {
5818 if (mTarget == null) return "";
5819 return mTarget.getTextBeforeCursor(n, flags);
5820 }
5821
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005822 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005823 public CharSequence getTextAfterCursor(int n, int flags) {
5824 if (mTarget == null) return "";
5825 return mTarget.getTextAfterCursor(n, flags);
5826 }
5827
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005828 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005829 public CharSequence getSelectedText(int flags) {
5830 if (mTarget == null) return "";
5831 return mTarget.getSelectedText(flags);
5832 }
5833
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005834 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005835 public int getCursorCapsMode(int reqModes) {
5836 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5837 return mTarget.getCursorCapsMode(reqModes);
5838 }
5839
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005840 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005841 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5842 return getTarget().getExtractedText(request, flags);
5843 }
5844
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005845 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005846 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5847 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5848 }
5849
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005850 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005851 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5852 return getTarget().setComposingText(text, newCursorPosition);
5853 }
5854
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005855 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005856 public boolean setComposingRegion(int start, int end) {
5857 return getTarget().setComposingRegion(start, end);
5858 }
5859
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005860 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005861 public boolean finishComposingText() {
5862 return mTarget == null || mTarget.finishComposingText();
5863 }
5864
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005865 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005866 public boolean commitText(CharSequence text, int newCursorPosition) {
5867 return getTarget().commitText(text, newCursorPosition);
5868 }
5869
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005870 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005871 public boolean commitCompletion(CompletionInfo text) {
5872 return getTarget().commitCompletion(text);
5873 }
5874
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005875 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005876 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5877 return getTarget().commitCorrection(correctionInfo);
5878 }
5879
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005880 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005881 public boolean setSelection(int start, int end) {
5882 return getTarget().setSelection(start, end);
5883 }
5884
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005885 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005886 public boolean performContextMenuAction(int id) {
5887 return getTarget().performContextMenuAction(id);
5888 }
5889
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005890 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005891 public boolean beginBatchEdit() {
5892 return getTarget().beginBatchEdit();
5893 }
5894
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005895 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005896 public boolean endBatchEdit() {
5897 return getTarget().endBatchEdit();
5898 }
5899
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005900 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005901 public boolean clearMetaKeyStates(int states) {
5902 return getTarget().clearMetaKeyStates(states);
5903 }
5904
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005905 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005906 public boolean performPrivateCommand(String action, Bundle data) {
5907 return getTarget().performPrivateCommand(action, data);
5908 }
5909 }
5910
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005911 /**
5912 * For filtering we proxy an input connection to an internal text editor,
5913 * and this allows the proxying to happen.
5914 */
5915 @Override
5916 public boolean checkInputConnectionProxy(View view) {
5917 return view == mTextFilter;
5918 }
Romain Guy0a637162009-05-29 14:43:54 -07005919
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005920 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005921 * Creates the window for the text filter and populates it with an EditText field;
5922 *
5923 * @param animateEntrance true if the window should appear with an animation
5924 */
5925 private void createTextFilter(boolean animateEntrance) {
5926 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07005927 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005928 p.setFocusable(false);
5929 p.setTouchable(false);
5930 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07005931 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005932 p.setWidth(LayoutParams.WRAP_CONTENT);
5933 p.setHeight(LayoutParams.WRAP_CONTENT);
5934 p.setBackgroundDrawable(null);
5935 mPopup = p;
5936 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005937 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005938 }
5939 if (animateEntrance) {
5940 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5941 } else {
5942 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5943 }
5944 }
5945
Romain Guyf6991302013-06-05 17:19:01 -07005946 private EditText getTextFilterInput() {
5947 if (mTextFilter == null) {
5948 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
5949 mTextFilter = (EditText) layoutInflater.inflate(
5950 com.android.internal.R.layout.typing_filter, null);
5951 // For some reason setting this as the "real" input type changes
5952 // the text view in some way that it doesn't work, and I don't
5953 // want to figure out why this is.
5954 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5955 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5956 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5957 mTextFilter.addTextChangedListener(this);
5958 }
5959 return mTextFilter;
5960 }
5961
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005962 /**
5963 * Clear the text filter.
5964 */
5965 public void clearTextFilter() {
5966 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07005967 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005968 mFiltered = false;
5969 if (mPopup != null && mPopup.isShowing()) {
5970 dismissPopup();
5971 }
5972 }
5973 }
5974
5975 /**
5976 * Returns if the ListView currently has a text filter.
5977 */
5978 public boolean hasTextFilter() {
5979 return mFiltered;
5980 }
5981
Alan Viverette8fa327a2013-05-31 14:53:13 -07005982 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005983 public void onGlobalLayout() {
5984 if (isShown()) {
5985 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08005986 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005987 showPopup();
5988 }
5989 } else {
5990 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08005991 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005992 dismissPopup();
5993 }
5994 }
5995
5996 }
5997
5998 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005999 * For our text watcher that is associated with the text filter. Does
6000 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006001 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006002 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006003 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6004 }
6005
6006 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006007 * For our text watcher that is associated with the text filter. Performs
6008 * the actual filtering as the text changes, and takes care of hiding and
6009 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006010 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006011 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006012 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07006013 if (isTextFilterEnabled()) {
6014 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006015 int length = s.length();
6016 boolean showing = mPopup.isShowing();
6017 if (!showing && length > 0) {
6018 // Show the filter popup if necessary
6019 showPopup();
6020 mFiltered = true;
6021 } else if (showing && length == 0) {
6022 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006023 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006024 mFiltered = false;
6025 }
6026 if (mAdapter instanceof Filterable) {
6027 Filter f = ((Filterable) mAdapter).getFilter();
6028 // Filter should not be null when we reach this part
6029 if (f != null) {
6030 f.filter(s, this);
6031 } else {
6032 throw new IllegalStateException("You cannot call onTextChanged with a non "
6033 + "filterable adapter");
6034 }
6035 }
6036 }
6037 }
6038
6039 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07006040 * For our text watcher that is associated with the text filter. Does
6041 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006042 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006043 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006044 public void afterTextChanged(Editable s) {
6045 }
6046
Alan Viverette8fa327a2013-05-31 14:53:13 -07006047 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006048 public void onFilterComplete(int count) {
6049 if (mSelectedPosition < 0 && count > 0) {
6050 mResurrectToPosition = INVALID_POSITION;
6051 resurrectSelection();
6052 }
6053 }
6054
6055 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08006056 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6057 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6058 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6059 }
6060
6061 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006062 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6063 return new LayoutParams(p);
6064 }
6065
6066 @Override
6067 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6068 return new AbsListView.LayoutParams(getContext(), attrs);
6069 }
6070
6071 @Override
6072 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6073 return p instanceof AbsListView.LayoutParams;
6074 }
6075
6076 /**
6077 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6078 * to the bottom to show new items.
6079 *
6080 * @param mode the transcript mode to set
6081 *
6082 * @see #TRANSCRIPT_MODE_DISABLED
6083 * @see #TRANSCRIPT_MODE_NORMAL
6084 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6085 */
6086 public void setTranscriptMode(int mode) {
6087 mTranscriptMode = mode;
6088 }
6089
6090 /**
6091 * Returns the current transcript mode.
6092 *
6093 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6094 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6095 */
6096 public int getTranscriptMode() {
6097 return mTranscriptMode;
6098 }
6099
6100 @Override
6101 public int getSolidColor() {
6102 return mCacheColorHint;
6103 }
6104
6105 /**
6106 * 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 -07006107 * on top of a solid, single-color, opaque background.
6108 *
6109 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6110 * single color. This hint will not affect any existing background drawable set on this view (
6111 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006112 *
6113 * @param color The background color
6114 */
6115 public void setCacheColorHint(int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08006116 if (color != mCacheColorHint) {
6117 mCacheColorHint = color;
6118 int count = getChildCount();
6119 for (int i = 0; i < count; i++) {
6120 getChildAt(i).setDrawingCacheBackgroundColor(color);
6121 }
6122 mRecycler.setCacheColorHint(color);
6123 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006124 }
6125
6126 /**
6127 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6128 * on top of a solid, single-color, opaque background
6129 *
6130 * @return The cache color hint
6131 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07006132 @ViewDebug.ExportedProperty(category = "drawing")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006133 public int getCacheColorHint() {
6134 return mCacheColorHint;
6135 }
6136
6137 /**
6138 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6139 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6140 * internal view recycler.
6141 *
6142 * @param views A list into which to put the reclaimed views
6143 */
6144 public void reclaimViews(List<View> views) {
6145 int childCount = getChildCount();
6146 RecyclerListener listener = mRecycler.mRecyclerListener;
6147
6148 // Reclaim views on screen
6149 for (int i = 0; i < childCount; i++) {
6150 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07006151 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006152 // Don't reclaim header or footer views, or views that should be ignored
6153 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6154 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07006155 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006156 if (listener != null) {
6157 // Pretend they went through the scrap heap
6158 listener.onMovedToScrapHeap(child);
6159 }
6160 }
6161 }
6162 mRecycler.reclaimScrapViews(views);
6163 removeAllViewsInLayout();
6164 }
6165
Adam Powell637d3372010-08-25 14:37:03 -07006166 private void finishGlows() {
6167 if (mEdgeGlowTop != null) {
6168 mEdgeGlowTop.finish();
6169 mEdgeGlowBottom.finish();
6170 }
6171 }
6172
Romain Guy13922e02009-05-12 17:56:14 -07006173 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006174 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6175 * through the specified intent.
6176 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6177 */
6178 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006179 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6180 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006181 if (mRemoteAdapter != null) {
6182 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6183 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6184 mRemoteAdapter.getRemoteViewsServiceIntent());
6185 if (fcNew.equals(fcOld)) {
6186 return;
6187 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006188 }
Adam Cohen2148d432011-07-28 14:59:54 -07006189 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006190 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07006191 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07006192 if (mRemoteAdapter.isDataReady()) {
6193 setAdapter(mRemoteAdapter);
6194 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006195 }
6196
6197 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006198 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006199 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006200 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006201 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006202 * @hide
6203 */
6204 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6205 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6206 // service handling the specified intent.
6207 if (mRemoteAdapter != null) {
6208 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6209 }
6210 }
6211
6212 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006213 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6214 * connected yet.
6215 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006216 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006217 public void deferNotifyDataSetChanged() {
6218 mDeferNotifyDataSetChanged = true;
6219 }
6220
6221 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006222 * Called back when the adapter connects to the RemoteViewsService.
6223 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006224 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006225 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006226 if (mRemoteAdapter != mAdapter) {
6227 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006228 if (mDeferNotifyDataSetChanged) {
6229 mRemoteAdapter.notifyDataSetChanged();
6230 mDeferNotifyDataSetChanged = false;
6231 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006232 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006233 } else if (mRemoteAdapter != null) {
6234 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006235 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006236 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006237 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006238 }
6239
6240 /**
6241 * Called back when the adapter disconnects from the RemoteViewsService.
6242 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006243 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006244 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006245 // If the remote adapter disconnects, we keep it around
6246 // since the currently displayed items are still cached.
6247 // Further, we want the service to eventually reconnect
6248 // when necessary, as triggered by this view requesting
6249 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006250 }
6251
6252 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006253 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6254 * being displayed by the AbsListView.
6255 */
6256 void setVisibleRangeHint(int start, int end) {
6257 if (mRemoteAdapter != null) {
6258 mRemoteAdapter.setVisibleRangeHint(start, end);
6259 }
6260 }
6261
6262 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006263 * Sets the recycler listener to be notified whenever a View is set aside in
6264 * the recycler for later reuse. This listener can be used to free resources
6265 * associated to the View.
6266 *
6267 * @param listener The recycler listener to be notified of views set aside
6268 * in the recycler.
6269 *
6270 * @see android.widget.AbsListView.RecycleBin
6271 * @see android.widget.AbsListView.RecyclerListener
6272 */
6273 public void setRecyclerListener(RecyclerListener listener) {
6274 mRecycler.mRecyclerListener = listener;
6275 }
6276
Adam Powellb1f498a2011-01-18 20:43:23 -08006277 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6278 @Override
6279 public void onChanged() {
6280 super.onChanged();
6281 if (mFastScroller != null) {
6282 mFastScroller.onSectionsChanged();
6283 }
6284 }
6285
6286 @Override
6287 public void onInvalidated() {
6288 super.onInvalidated();
6289 if (mFastScroller != null) {
6290 mFastScroller.onSectionsChanged();
6291 }
6292 }
6293 }
6294
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006295 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006296 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6297 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6298 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6299 * selects and deselects list items.
6300 */
6301 public interface MultiChoiceModeListener extends ActionMode.Callback {
6302 /**
6303 * Called when an item is checked or unchecked during selection mode.
6304 *
6305 * @param mode The {@link ActionMode} providing the selection mode
6306 * @param position Adapter position of the item that was checked or unchecked
6307 * @param id Adapter ID of the item that was checked or unchecked
6308 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6309 * if the item is now unchecked.
6310 */
6311 public void onItemCheckedStateChanged(ActionMode mode,
6312 int position, long id, boolean checked);
6313 }
6314
6315 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6316 private MultiChoiceModeListener mWrapped;
6317
6318 public void setWrapped(MultiChoiceModeListener wrapped) {
6319 mWrapped = wrapped;
6320 }
6321
Adam Powella7981702012-08-24 12:43:41 -07006322 public boolean hasWrappedCallback() {
6323 return mWrapped != null;
6324 }
6325
Alan Viverette8fa327a2013-05-31 14:53:13 -07006326 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006327 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6328 if (mWrapped.onCreateActionMode(mode, menu)) {
6329 // Initialize checked graphic state?
6330 setLongClickable(false);
6331 return true;
6332 }
6333 return false;
6334 }
6335
Alan Viverette8fa327a2013-05-31 14:53:13 -07006336 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006337 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6338 return mWrapped.onPrepareActionMode(mode, menu);
6339 }
6340
Alan Viverette8fa327a2013-05-31 14:53:13 -07006341 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006342 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6343 return mWrapped.onActionItemClicked(mode, item);
6344 }
6345
Alan Viverette8fa327a2013-05-31 14:53:13 -07006346 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006347 public void onDestroyActionMode(ActionMode mode) {
6348 mWrapped.onDestroyActionMode(mode);
6349 mChoiceActionMode = null;
6350
6351 // Ending selection mode means deselecting everything.
6352 clearChoices();
6353
6354 mDataChanged = true;
6355 rememberSyncState();
6356 requestLayout();
6357
6358 setLongClickable(true);
6359 }
6360
Alan Viverette8fa327a2013-05-31 14:53:13 -07006361 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006362 public void onItemCheckedStateChanged(ActionMode mode,
6363 int position, long id, boolean checked) {
6364 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6365
6366 // If there are no items selected we no longer need the selection mode.
6367 if (getCheckedItemCount() == 0) {
6368 mode.finish();
6369 }
6370 }
6371 }
6372
6373 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006374 * AbsListView extends LayoutParams to provide a place to hold the view type.
6375 */
6376 public static class LayoutParams extends ViewGroup.LayoutParams {
6377 /**
6378 * View type for this view, as returned by
6379 * {@link android.widget.Adapter#getItemViewType(int) }
6380 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006381 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006382 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6383 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6384 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006385 int viewType;
6386
The Android Open Source Project4df24232009-03-05 14:34:35 -08006387 /**
6388 * When this boolean is set, the view has been added to the AbsListView
6389 * at least once. It is used to know whether headers/footers have already
6390 * been added to the list view and whether they should be treated as
6391 * recycled views or not.
6392 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006393 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006394 boolean recycledHeaderFooter;
6395
Romain Guy0bf88592010-03-02 13:38:44 -08006396 /**
6397 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6398 * to obtain children views to measure itself. When doing so, the children
6399 * are not attached to the window, but put in the recycler which assumes
6400 * they've been attached before. Setting this flag will force the reused
6401 * view to be attached to the window rather than just attached to the
6402 * parent.
6403 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006404 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006405 boolean forceAdd;
6406
Dianne Hackborn079e2352010-10-18 17:02:43 -07006407 /**
6408 * The position the view was removed from when pulled out of the
6409 * scrap heap.
6410 * @hide
6411 */
6412 int scrappedFromPosition;
6413
Adam Powell539ee872012-02-03 19:00:49 -08006414 /**
6415 * The ID the view represents
6416 */
6417 long itemId = -1;
6418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006419 public LayoutParams(Context c, AttributeSet attrs) {
6420 super(c, attrs);
6421 }
6422
6423 public LayoutParams(int w, int h) {
6424 super(w, h);
6425 }
6426
6427 public LayoutParams(int w, int h, int viewType) {
6428 super(w, h);
6429 this.viewType = viewType;
6430 }
6431
6432 public LayoutParams(ViewGroup.LayoutParams source) {
6433 super(source);
6434 }
6435 }
6436
6437 /**
6438 * A RecyclerListener is used to receive a notification whenever a View is placed
6439 * inside the RecycleBin's scrap heap. This listener is used to free resources
6440 * associated to Views placed in the RecycleBin.
6441 *
6442 * @see android.widget.AbsListView.RecycleBin
6443 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6444 */
6445 public static interface RecyclerListener {
6446 /**
6447 * Indicates that the specified View was moved into the recycler's scrap heap.
6448 * The view is not displayed on screen any more and any expensive resource
6449 * associated with the view should be discarded.
6450 *
6451 * @param view
6452 */
6453 void onMovedToScrapHeap(View view);
6454 }
6455
6456 /**
6457 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6458 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6459 * start of a layout. By construction, they are displaying current information. At the end of
6460 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6461 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6462 *
6463 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6464 * @see android.widget.AbsListView.RecyclerListener
6465 */
6466 class RecycleBin {
6467 private RecyclerListener mRecyclerListener;
6468
6469 /**
6470 * The position of the first view stored in mActiveViews.
6471 */
6472 private int mFirstActivePosition;
6473
6474 /**
6475 * Views that were on screen at the start of layout. This array is populated at the start of
6476 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6477 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6478 * view store in mFirstActivePosition.
6479 */
6480 private View[] mActiveViews = new View[0];
6481
6482 /**
6483 * Unsorted views that can be used by the adapter as a convert view.
6484 */
6485 private ArrayList<View>[] mScrapViews;
6486
6487 private int mViewTypeCount;
6488
6489 private ArrayList<View> mCurrentScrap;
6490
Adam Powell539ee872012-02-03 19:00:49 -08006491 private ArrayList<View> mSkippedScrap;
6492
6493 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006494 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006495
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006496 public void setViewTypeCount(int viewTypeCount) {
6497 if (viewTypeCount < 1) {
6498 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6499 }
6500 //noinspection unchecked
6501 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6502 for (int i = 0; i < viewTypeCount; i++) {
6503 scrapViews[i] = new ArrayList<View>();
6504 }
6505 mViewTypeCount = viewTypeCount;
6506 mCurrentScrap = scrapViews[0];
6507 mScrapViews = scrapViews;
6508 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006509
Adam Powellf3c2eda2010-03-16 17:31:01 -07006510 public void markChildrenDirty() {
6511 if (mViewTypeCount == 1) {
6512 final ArrayList<View> scrap = mCurrentScrap;
6513 final int scrapCount = scrap.size();
6514 for (int i = 0; i < scrapCount; i++) {
6515 scrap.get(i).forceLayout();
6516 }
6517 } else {
6518 final int typeCount = mViewTypeCount;
6519 for (int i = 0; i < typeCount; i++) {
6520 final ArrayList<View> scrap = mScrapViews[i];
6521 final int scrapCount = scrap.size();
6522 for (int j = 0; j < scrapCount; j++) {
6523 scrap.get(j).forceLayout();
6524 }
6525 }
6526 }
Adam Powell539ee872012-02-03 19:00:49 -08006527 if (mTransientStateViews != null) {
6528 final int count = mTransientStateViews.size();
6529 for (int i = 0; i < count; i++) {
6530 mTransientStateViews.valueAt(i).forceLayout();
6531 }
6532 }
Chet Haase72871322013-02-26 16:12:13 -07006533 if (mTransientStateViewsById != null) {
6534 final int count = mTransientStateViewsById.size();
6535 for (int i = 0; i < count; i++) {
6536 mTransientStateViewsById.valueAt(i).forceLayout();
6537 }
6538 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006539 }
Romain Guy0a637162009-05-29 14:43:54 -07006540
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006541 public boolean shouldRecycleViewType(int viewType) {
6542 return viewType >= 0;
6543 }
6544
6545 /**
6546 * Clears the scrap heap.
6547 */
6548 void clear() {
6549 if (mViewTypeCount == 1) {
6550 final ArrayList<View> scrap = mCurrentScrap;
6551 final int scrapCount = scrap.size();
6552 for (int i = 0; i < scrapCount; i++) {
6553 removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
6554 }
6555 } else {
6556 final int typeCount = mViewTypeCount;
6557 for (int i = 0; i < typeCount; i++) {
6558 final ArrayList<View> scrap = mScrapViews[i];
6559 final int scrapCount = scrap.size();
6560 for (int j = 0; j < scrapCount; j++) {
6561 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6562 }
6563 }
6564 }
Adam Powell539ee872012-02-03 19:00:49 -08006565 if (mTransientStateViews != null) {
6566 mTransientStateViews.clear();
6567 }
Chet Haase72871322013-02-26 16:12:13 -07006568 if (mTransientStateViewsById != null) {
6569 mTransientStateViewsById.clear();
6570 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006571 }
6572
6573 /**
6574 * Fill ActiveViews with all of the children of the AbsListView.
6575 *
6576 * @param childCount The minimum number of views mActiveViews should hold
6577 * @param firstActivePosition The position of the first view that will be stored in
6578 * mActiveViews
6579 */
6580 void fillActiveViews(int childCount, int firstActivePosition) {
6581 if (mActiveViews.length < childCount) {
6582 mActiveViews = new View[childCount];
6583 }
6584 mFirstActivePosition = firstActivePosition;
6585
Romain Guyf6991302013-06-05 17:19:01 -07006586 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006587 final View[] activeViews = mActiveViews;
6588 for (int i = 0; i < childCount; i++) {
6589 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006590 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006591 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006592 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006593 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6594 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006595 activeViews[i] = child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006596 }
6597 }
6598 }
6599
6600 /**
6601 * Get the view corresponding to the specified position. The view will be removed from
6602 * mActiveViews if it is found.
6603 *
6604 * @param position The position to look up in mActiveViews
6605 * @return The view if it is found, null otherwise
6606 */
6607 View getActiveView(int position) {
6608 int index = position - mFirstActivePosition;
6609 final View[] activeViews = mActiveViews;
6610 if (index >=0 && index < activeViews.length) {
6611 final View match = activeViews[index];
6612 activeViews[index] = null;
6613 return match;
6614 }
6615 return null;
6616 }
6617
Adam Powell539ee872012-02-03 19:00:49 -08006618 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006619 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6620 long id = mAdapter.getItemId(position);
6621 View result = mTransientStateViewsById.get(id);
6622 mTransientStateViewsById.remove(id);
6623 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006624 }
Chet Haase72871322013-02-26 16:12:13 -07006625 if (mTransientStateViews != null) {
6626 final int index = mTransientStateViews.indexOfKey(position);
6627 if (index >= 0) {
6628 View result = mTransientStateViews.valueAt(index);
6629 mTransientStateViews.removeAt(index);
6630 return result;
6631 }
Adam Powell539ee872012-02-03 19:00:49 -08006632 }
Chet Haase72871322013-02-26 16:12:13 -07006633 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006634 }
6635
6636 /**
6637 * Dump any currently saved views with transient state.
6638 */
6639 void clearTransientStateViews() {
6640 if (mTransientStateViews != null) {
6641 mTransientStateViews.clear();
6642 }
Chet Haase72871322013-02-26 16:12:13 -07006643 if (mTransientStateViewsById != null) {
6644 mTransientStateViewsById.clear();
6645 }
Adam Powell539ee872012-02-03 19:00:49 -08006646 }
6647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006648 /**
6649 * @return A view from the ScrapViews collection. These are unordered.
6650 */
6651 View getScrapView(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006652 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006653 return retrieveFromScrap(mCurrentScrap, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006654 } else {
6655 int whichScrap = mAdapter.getItemViewType(position);
6656 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006657 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006658 }
6659 }
6660 return null;
6661 }
6662
6663 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006664 * Puts a view into the list of scrap views.
6665 * <p>
6666 * If the list data hasn't changed or the adapter has stable IDs, views
6667 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006668 *
6669 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006670 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006671 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006672 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006673 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006674 if (lp == null) {
6675 return;
6676 }
6677
Adam Powell539ee872012-02-03 19:00:49 -08006678 lp.scrappedFromPosition = position;
6679
Alan Viverette1e51cc72013-09-27 14:32:20 -07006680 // Remove but don't scrap header or footer views, or views that
6681 // should otherwise not be recycled.
Alan Viveretted44696c2013-07-18 10:37:15 -07006682 final int viewType = lp.viewType;
6683 if (!shouldRecycleViewType(viewType)) {
6684 return;
6685 }
6686
6687 scrap.dispatchStartTemporaryDetach();
6688
Svetoslavd4bdd6b2013-10-31 17:25:01 -07006689 // The the accessibility state of the view may change while temporary
6690 // detached and we do not allow detached views to fire accessibility
6691 // events. So we are announcing that the subtree changed giving a chance
6692 // to clients holding on to a view in this subtree to refresh it.
6693 notifyViewAccessibilityStateChangedIfNeeded(
6694 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6695
Alan Viveretted44696c2013-07-18 10:37:15 -07006696 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006697 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006698 if (scrapHasTransientState) {
6699 if (mAdapter != null && mAdapterHasStableIds) {
6700 // If the adapter has stable IDs, we can reuse the view for
6701 // the same data.
6702 if (mTransientStateViewsById == null) {
6703 mTransientStateViewsById = new LongSparseArray<View>();
6704 }
6705 mTransientStateViewsById.put(lp.itemId, scrap);
6706 } else if (!mDataChanged) {
6707 // If the data hasn't changed, we can reuse the views at
6708 // their old positions.
6709 if (mTransientStateViews == null) {
6710 mTransientStateViews = new SparseArray<View>();
6711 }
6712 mTransientStateViews.put(position, scrap);
6713 } else {
6714 // Otherwise, we'll have to remove the view and start over.
Adam Powell539ee872012-02-03 19:00:49 -08006715 if (mSkippedScrap == null) {
6716 mSkippedScrap = new ArrayList<View>();
6717 }
6718 mSkippedScrap.add(scrap);
6719 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006720 } else {
Alan Viveretted44696c2013-07-18 10:37:15 -07006721 if (mViewTypeCount == 1) {
6722 mCurrentScrap.add(scrap);
6723 } else {
6724 mScrapViews[viewType].add(scrap);
6725 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006726
Alan Viverette1e51cc72013-09-27 14:32:20 -07006727 // Clear any system-managed transient state.
6728 if (scrap.isAccessibilityFocused()) {
6729 scrap.clearAccessibilityFocus();
6730 }
6731
Alan Viveretted44696c2013-07-18 10:37:15 -07006732 scrap.setAccessibilityDelegate(null);
6733
6734 if (mRecyclerListener != null) {
6735 mRecyclerListener.onMovedToScrapHeap(scrap);
6736 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006737 }
6738 }
6739
6740 /**
Adam Powell539ee872012-02-03 19:00:49 -08006741 * Finish the removal of any views that skipped the scrap heap.
6742 */
6743 void removeSkippedScrap() {
6744 if (mSkippedScrap == null) {
6745 return;
6746 }
6747 final int count = mSkippedScrap.size();
6748 for (int i = 0; i < count; i++) {
6749 removeDetachedView(mSkippedScrap.get(i), false);
6750 }
6751 mSkippedScrap.clear();
6752 }
6753
6754 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006755 * Move all views remaining in mActiveViews to mScrapViews.
6756 */
6757 void scrapActiveViews() {
6758 final View[] activeViews = mActiveViews;
6759 final boolean hasListener = mRecyclerListener != null;
6760 final boolean multipleScraps = mViewTypeCount > 1;
6761
6762 ArrayList<View> scrapViews = mCurrentScrap;
6763 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006764 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006765 final View victim = activeViews[i];
6766 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006767 final AbsListView.LayoutParams lp
6768 = (AbsListView.LayoutParams) victim.getLayoutParams();
6769 int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006770
6771 activeViews[i] = null;
6772
Adam Powell539ee872012-02-03 19:00:49 -08006773 final boolean scrapHasTransientState = victim.hasTransientState();
6774 if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006775 // Do not move views that should be ignored
Romain Guyb7e0f792013-06-07 15:36:49 -07006776 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
Adam Powell539ee872012-02-03 19:00:49 -08006777 scrapHasTransientState) {
Romain Guy9b1bb812010-02-26 14:14:13 -08006778 removeDetachedView(victim, false);
6779 }
Adam Powell539ee872012-02-03 19:00:49 -08006780 if (scrapHasTransientState) {
Chet Haase72871322013-02-26 16:12:13 -07006781 if (mAdapter != null && mAdapterHasStableIds) {
6782 if (mTransientStateViewsById == null) {
6783 mTransientStateViewsById = new LongSparseArray<View>();
6784 }
6785 long id = mAdapter.getItemId(mFirstActivePosition + i);
6786 mTransientStateViewsById.put(id, victim);
6787 } else {
6788 if (mTransientStateViews == null) {
6789 mTransientStateViews = new SparseArray<View>();
6790 }
6791 mTransientStateViews.put(mFirstActivePosition + i, victim);
Adam Powell539ee872012-02-03 19:00:49 -08006792 }
Adam Powell539ee872012-02-03 19:00:49 -08006793 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006794 continue;
6795 }
6796
6797 if (multipleScraps) {
6798 scrapViews = mScrapViews[whichScrap];
6799 }
Romain Guya440b002010-02-24 15:57:54 -08006800 victim.dispatchStartTemporaryDetach();
Dianne Hackborn079e2352010-10-18 17:02:43 -07006801 lp.scrappedFromPosition = mFirstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006802 scrapViews.add(victim);
6803
alanvc1d7e772012-05-08 14:47:24 -07006804 victim.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006805 if (hasListener) {
6806 mRecyclerListener.onMovedToScrapHeap(victim);
6807 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006808 }
6809 }
6810
6811 pruneScrapViews();
6812 }
6813
6814 /**
6815 * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
6816 * (This can happen if an adapter does not recycle its views).
6817 */
6818 private void pruneScrapViews() {
6819 final int maxViews = mActiveViews.length;
6820 final int viewTypeCount = mViewTypeCount;
6821 final ArrayList<View>[] scrapViews = mScrapViews;
6822 for (int i = 0; i < viewTypeCount; ++i) {
6823 final ArrayList<View> scrapPile = scrapViews[i];
6824 int size = scrapPile.size();
6825 final int extras = size - maxViews;
6826 size--;
6827 for (int j = 0; j < extras; j++) {
6828 removeDetachedView(scrapPile.remove(size--), false);
6829 }
6830 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006831
6832 if (mTransientStateViews != null) {
6833 for (int i = 0; i < mTransientStateViews.size(); i++) {
6834 final View v = mTransientStateViews.valueAt(i);
6835 if (!v.hasTransientState()) {
6836 mTransientStateViews.removeAt(i);
6837 i--;
6838 }
6839 }
6840 }
Chet Haase72871322013-02-26 16:12:13 -07006841 if (mTransientStateViewsById != null) {
6842 for (int i = 0; i < mTransientStateViewsById.size(); i++) {
6843 final View v = mTransientStateViewsById.valueAt(i);
6844 if (!v.hasTransientState()) {
6845 mTransientStateViewsById.removeAt(i);
6846 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006900 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006901
6902 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6903 int size = scrapViews.size();
6904 if (size > 0) {
6905 // See if we still have a view for this position.
6906 for (int i=0; i<size; i++) {
6907 View view = scrapViews.get(i);
6908 if (((AbsListView.LayoutParams)view.getLayoutParams())
6909 .scrappedFromPosition == position) {
6910 scrapViews.remove(i);
6911 return view;
6912 }
6913 }
6914 return scrapViews.remove(size - 1);
6915 } else {
6916 return null;
6917 }
6918 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006919}