blob: 19e3905bff19ddf0db2caba88f2392c281a6a407 [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 /**
226 * Controls if/how the user may choose/check items in the list
227 */
228 int mChoiceMode = CHOICE_MODE_NONE;
229
230 /**
231 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
232 */
233 ActionMode mChoiceActionMode;
234
235 /**
236 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
237 * a few extra actions around what application code does.
238 */
239 MultiChoiceModeWrapper mMultiChoiceModeCallback;
240
241 /**
242 * Running count of how many items are currently checked
243 */
244 int mCheckedItemCount;
245
246 /**
247 * Running state of which positions are currently checked
248 */
249 SparseBooleanArray mCheckStates;
250
251 /**
Adam Powell14c08042011-10-06 19:46:18 -0700252 * Running state of which IDs are currently checked.
253 * If there is a value for a given key, the checked state for that ID is true
254 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700255 */
Adam Powell14c08042011-10-06 19:46:18 -0700256 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700257
258 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 * Controls how the next layout will happen
260 */
261 int mLayoutMode = LAYOUT_NORMAL;
262
263 /**
264 * Should be used by subclasses to listen to changes in the dataset
265 */
266 AdapterDataSetObserver mDataSetObserver;
267
268 /**
269 * The adapter containing the data to be displayed by this view
270 */
271 ListAdapter mAdapter;
272
273 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700274 * The remote adapter containing the data to be displayed by this view to be set
275 */
276 private RemoteViewsAdapter mRemoteAdapter;
277
278 /**
Adam Powell539ee872012-02-03 19:00:49 -0800279 * If mAdapter != null, whenever this is true the adapter has stable IDs.
280 */
281 boolean mAdapterHasStableIds;
282
283 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700284 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
285 */
286 private boolean mDeferNotifyDataSetChanged = false;
287
288 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 * Indicates whether the list selector should be drawn on top of the children or behind
290 */
291 boolean mDrawSelectorOnTop = false;
292
293 /**
294 * The drawable used to draw the selector
295 */
296 Drawable mSelector;
297
298 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700299 * The current position of the selector in the list.
300 */
301 int mSelectorPosition = INVALID_POSITION;
302
303 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 * Defines the selector's location and dimension at drawing time
305 */
306 Rect mSelectorRect = new Rect();
307
308 /**
309 * The data set used to store unused views that should be reused during the next layout
310 * to avoid creating new ones
311 */
312 final RecycleBin mRecycler = new RecycleBin();
313
314 /**
315 * The selection's left padding
316 */
317 int mSelectionLeftPadding = 0;
318
319 /**
320 * The selection's top padding
321 */
322 int mSelectionTopPadding = 0;
323
324 /**
325 * The selection's right padding
326 */
327 int mSelectionRightPadding = 0;
328
329 /**
330 * The selection's bottom padding
331 */
332 int mSelectionBottomPadding = 0;
333
334 /**
335 * This view's padding
336 */
337 Rect mListPadding = new Rect();
338
339 /**
340 * Subclasses must retain their measure spec from onMeasure() into this member
341 */
342 int mWidthMeasureSpec = 0;
343
344 /**
345 * The top scroll indicator
346 */
347 View mScrollUp;
348
349 /**
350 * The down scroll indicator
351 */
352 View mScrollDown;
353
354 /**
355 * When the view is scrolling, this flag is set to true to indicate subclasses that
356 * the drawing cache was enabled on the children
357 */
358 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800359 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360
361 /**
362 * The position of the view that received the down motion event
363 */
364 int mMotionPosition;
365
366 /**
367 * The offset to the top of the mMotionPosition view when the down motion event was received
368 */
369 int mMotionViewOriginalTop;
370
371 /**
372 * The desired offset to the top of the mMotionPosition view after a scroll
373 */
374 int mMotionViewNewTop;
375
376 /**
377 * The X value associated with the the down motion event
378 */
379 int mMotionX;
380
381 /**
382 * The Y value associated with the the down motion event
383 */
384 int mMotionY;
385
386 /**
387 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
388 * TOUCH_MODE_DONE_WAITING
389 */
390 int mTouchMode = TOUCH_MODE_REST;
391
392 /**
393 * Y value from on the previous motion event (if any)
394 */
395 int mLastY;
396
397 /**
398 * How far the finger moved before we started scrolling
399 */
400 int mMotionCorrection;
401
402 /**
403 * Determines speed during touch scrolling
404 */
405 private VelocityTracker mVelocityTracker;
406
407 /**
408 * Handles one frame of a fling
409 */
410 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800411
Adam Powell45803472010-01-25 15:10:44 -0800412 /**
413 * Handles scrolling between positions within the list.
414 */
Adam Powell1fa179ef2012-04-12 15:01:40 -0700415 PositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416
417 /**
418 * The offset in pixels form the top of the AdapterView to the top
419 * of the currently selected view. Used to save and restore state.
420 */
421 int mSelectedTop = 0;
422
423 /**
424 * Indicates whether the list is stacked from the bottom edge or
425 * the top edge.
426 */
427 boolean mStackFromBottom;
428
429 /**
430 * When set to true, the list automatically discards the children's
431 * bitmap cache after scrolling.
432 */
433 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700434
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435 /**
436 * Whether or not to enable the fast scroll feature on this list
437 */
438 boolean mFastScrollEnabled;
439
440 /**
441 * Optional callback to notify client when scroll position has changed
442 */
443 private OnScrollListener mOnScrollListener;
444
445 /**
446 * Keeps track of our accessory window
447 */
448 PopupWindow mPopup;
449
450 /**
451 * Used with type filter window
452 */
453 EditText mTextFilter;
454
455 /**
456 * Indicates whether to use pixels-based or position-based scrollbar
457 * properties.
458 */
459 private boolean mSmoothScrollbarEnabled = true;
460
461 /**
462 * Indicates that this view supports filtering
463 */
464 private boolean mTextFilterEnabled;
465
466 /**
467 * Indicates that this view is currently displaying a filtered view of the data
468 */
469 private boolean mFiltered;
470
471 /**
472 * Rectangle used for hit testing children
473 */
474 private Rect mTouchFrame;
475
476 /**
477 * The position to resurrect the selected position to.
478 */
479 int mResurrectToPosition = INVALID_POSITION;
480
481 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800482
Adam Powell0b8bb422010-02-08 14:30:45 -0800483 /**
Adam Powell637d3372010-08-25 14:37:03 -0700484 * Maximum distance to record overscroll
485 */
486 int mOverscrollMax;
487
488 /**
489 * Content height divided by this is the overscroll limit.
490 */
491 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
492
493 /**
Adam Powell14c08042011-10-06 19:46:18 -0700494 * How many positions in either direction we will search to try to
495 * find a checked item with a stable ID that moved position across
496 * a data set change. If the item isn't found it will be unselected.
497 */
498 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
499
500 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 * Used to request a layout when we changed touch mode
502 */
503 private static final int TOUCH_MODE_UNKNOWN = -1;
504 private static final int TOUCH_MODE_ON = 0;
505 private static final int TOUCH_MODE_OFF = 1;
506
507 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
508
509 private static final boolean PROFILE_SCROLLING = false;
510 private boolean mScrollProfilingStarted = false;
511
512 private static final boolean PROFILE_FLINGING = false;
513 private boolean mFlingProfilingStarted = false;
514
515 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800516 * The StrictMode "critical time span" objects to catch animation
517 * stutters. Non-null when a time-sensitive animation is
518 * in-flight. Must call finish() on them when done animating.
519 * These are no-ops on user builds.
520 */
521 private StrictMode.Span mScrollStrictSpan = null;
522 private StrictMode.Span mFlingStrictSpan = null;
523
524 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 * The last CheckForLongPress runnable we posted, if any
526 */
527 private CheckForLongPress mPendingCheckForLongPress;
528
529 /**
530 * The last CheckForTap runnable we posted, if any
531 */
532 private Runnable mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700533
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 /**
535 * The last CheckForKeyLongPress runnable we posted, if any
536 */
537 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
538
539 /**
540 * Acts upon click
541 */
542 private AbsListView.PerformClick mPerformClick;
543
544 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800545 * Delayed action for touch mode.
546 */
547 private Runnable mTouchModeReset;
548
549 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 * This view is in transcript mode -- it shows the bottom of the list when the data
551 * changes
552 */
553 private int mTranscriptMode;
554
555 /**
556 * Indicates that this list is always drawn on top of a solid, single-color, opaque
557 * background
558 */
559 private int mCacheColorHint;
560
561 /**
562 * The select child's view (from the adapter's getView) is enabled.
563 */
564 private boolean mIsChildViewEnabled;
565
566 /**
567 * The last scroll state reported to clients through {@link OnScrollListener}.
568 */
569 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
570
571 /**
572 * Helper object that renders and controls the fast scroll thumb.
573 */
574 private FastScroller mFastScroller;
575
Romain Guyd6a463a2009-05-21 23:10:10 -0700576 private boolean mGlobalLayoutListenerAddedFilter;
577
578 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 private float mDensityScale;
580
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700581 private InputConnection mDefInputConnection;
582 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700583
584 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700585 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700586 private int mMinimumVelocity;
587 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700588 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800589
Romain Guy21875052010-01-06 18:48:08 -0800590 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800591
Romain Guy24562482010-02-01 14:56:19 -0800592 // True when the popup should be hidden because of a call to
593 // dispatchDisplayHint()
594 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800595
Adam Powell4cd47702010-02-25 11:21:14 -0800596 /**
597 * ID of the active pointer. This is used to retain consistency during
598 * drags/flings if multiple pointers are used.
599 */
600 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800601
Adam Powell4cd47702010-02-25 11:21:14 -0800602 /**
603 * Sentinel value for no current active pointer.
604 * Used by {@link #mActivePointerId}.
605 */
606 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700607
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 /**
Adam Powell637d3372010-08-25 14:37:03 -0700609 * Maximum distance to overscroll by during edge effects
610 */
611 int mOverscrollDistance;
612
613 /**
614 * Maximum distance to overfling during edge effects
615 */
616 int mOverflingDistance;
617
618 // These two EdgeGlows are always set and used together.
619 // Checking one for null is as good as checking both.
620
621 /**
622 * Tracks the state of the top edge glow.
623 */
Adam Powell89935e42011-08-31 14:26:12 -0700624 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700625
626 /**
627 * Tracks the state of the bottom edge glow.
628 */
Adam Powell89935e42011-08-31 14:26:12 -0700629 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700630
631 /**
632 * An estimate of how many pixels are between the top of the list and
633 * the top of the first position in the adapter, based on the last time
634 * we saw it. Used to hint where to draw edge glows.
635 */
636 private int mFirstPositionDistanceGuess;
637
638 /**
639 * An estimate of how many pixels are between the bottom of the list and
640 * the bottom of the last position in the adapter, based on the last time
641 * we saw it. Used to hint where to draw edge glows.
642 */
643 private int mLastPositionDistanceGuess;
644
645 /**
646 * Used for determining when to cancel out of overscroll.
647 */
648 private int mDirection = 0;
649
650 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800651 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
652 * the bottom correctly on resizes.
653 */
654 private boolean mForceTranscriptScroll;
655
Adam Powell07d6f7b2011-03-02 14:27:30 -0800656 private int mGlowPaddingLeft;
657 private int mGlowPaddingRight;
658
alanvc1d7e772012-05-08 14:47:24 -0700659 /**
660 * Used for interacting with list items from an accessibility service.
661 */
662 private ListItemAccessibilityDelegate mAccessibilityDelegate;
663
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700664 private int mLastAccessibilityScrollEventFromIndex;
665 private int mLastAccessibilityScrollEventToIndex;
666
Adam Powellda13dba2010-12-05 13:47:23 -0800667 /**
Adam Powellb3750132011-08-08 23:29:12 -0700668 * Track if we are currently attached to a window.
669 */
Adam Powella2b986e2011-09-14 14:21:33 -0700670 boolean mIsAttached;
Adam Powellb3750132011-08-08 23:29:12 -0700671
672 /**
Adam Powellee78b172011-08-16 16:39:20 -0700673 * Track the item count from the last time we handled a data change.
674 */
675 private int mLastHandledItemCount;
676
677 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700678 * Used for smooth scrolling at a consistent rate
679 */
680 static final Interpolator sLinearInterpolator = new LinearInterpolator();
681
682 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700683 * The saved state that we will be restoring from when we next sync.
684 * Kept here so that if we happen to be asked to save our state before
685 * the sync happens, we can return this existing data rather than losing
686 * it.
687 */
688 private SavedState mPendingSync;
689
690 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691 * Interface definition for a callback to be invoked when the list or grid
692 * has been scrolled.
693 */
694 public interface OnScrollListener {
695
696 /**
697 * The view is not scrolling. Note navigating the list using the trackball counts as
698 * being in the idle state since these transitions are not animated.
699 */
700 public static int SCROLL_STATE_IDLE = 0;
701
702 /**
703 * The user is scrolling using touch, and their finger is still on the screen
704 */
705 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
706
707 /**
708 * The user had previously been scrolling using touch and had performed a fling. The
709 * animation is now coasting to a stop
710 */
711 public static int SCROLL_STATE_FLING = 2;
712
713 /**
714 * Callback method to be invoked while the list view or grid view is being scrolled. If the
715 * view is being scrolled, this method will be called before the next frame of the scroll is
716 * rendered. In particular, it will be called before any calls to
717 * {@link Adapter#getView(int, View, ViewGroup)}.
718 *
719 * @param view The view whose scroll state is being reported
720 *
721 * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
722 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
723 */
724 public void onScrollStateChanged(AbsListView view, int scrollState);
725
726 /**
727 * Callback method to be invoked when the list or grid has been scrolled. This will be
728 * called after the scroll has completed
729 * @param view The view whose scroll state is being reported
730 * @param firstVisibleItem the index of the first visible cell (ignore if
731 * visibleItemCount == 0)
732 * @param visibleItemCount the number of visible cells
733 * @param totalItemCount the number of items in the list adaptor
734 */
735 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
736 int totalItemCount);
737 }
738
Dianne Hackborne2136772010-11-04 15:08:59 -0700739 /**
740 * The top-level view of a list item can implement this interface to allow
741 * itself to modify the bounds of the selection shown for that item.
742 */
743 public interface SelectionBoundsAdjuster {
744 /**
745 * Called to allow the list item to adjust the bounds shown for
746 * its selection.
747 *
748 * @param bounds On call, this contains the bounds the list has
749 * selected for the item (that is the bounds of the entire view). The
750 * values can be modified as desired.
751 */
752 public void adjustListItemSelectionBounds(Rect bounds);
753 }
754
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755 public AbsListView(Context context) {
756 super(context);
757 initAbsListView();
758
759 setVerticalScrollBarEnabled(true);
760 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
761 initializeScrollbars(a);
762 a.recycle();
763 }
764
765 public AbsListView(Context context, AttributeSet attrs) {
766 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
767 }
768
769 public AbsListView(Context context, AttributeSet attrs, int defStyle) {
770 super(context, attrs, defStyle);
771 initAbsListView();
772
773 TypedArray a = context.obtainStyledAttributes(attrs,
774 com.android.internal.R.styleable.AbsListView, defStyle, 0);
775
776 Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
777 if (d != null) {
778 setSelector(d);
779 }
780
781 mDrawSelectorOnTop = a.getBoolean(
782 com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
783
784 boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
785 setStackFromBottom(stackFromBottom);
786
787 boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
788 setScrollingCacheEnabled(scrollingCacheEnabled);
789
790 boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
791 setTextFilterEnabled(useTextFilter);
792
793 int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
794 TRANSCRIPT_MODE_DISABLED);
795 setTranscriptMode(transcriptMode);
796
797 int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
798 setCacheColorHint(color);
Romain Guy0a637162009-05-29 14:43:54 -0700799
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
801 setFastScrollEnabled(enableFastScroll);
802
803 boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
804 setSmoothScrollbarEnabled(smoothScrollbar);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800805
Adam Powellf343e1b2010-08-13 18:27:04 -0700806 setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
Adam Powell20232d02010-12-08 21:08:53 -0800807 setFastScrollAlwaysVisible(
808 a.getBoolean(R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700809
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 a.recycle();
811 }
812
Romain Guyd6a463a2009-05-21 23:10:10 -0700813 private void initAbsListView() {
814 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700815 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700816 setFocusableInTouchMode(true);
817 setWillNotDraw(false);
818 setAlwaysDrawnWithCacheEnabled(false);
819 setScrollingCacheEnabled(true);
820
Romain Guy4296fc42009-07-06 11:48:52 -0700821 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
822 mTouchSlop = configuration.getScaledTouchSlop();
823 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
824 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700825 mOverscrollDistance = configuration.getScaledOverscrollDistance();
826 mOverflingDistance = configuration.getScaledOverflingDistance();
827
Romain Guyd6a463a2009-05-21 23:10:10 -0700828 mDensityScale = getContext().getResources().getDisplayMetrics().density;
829 }
Romain Guy0a637162009-05-29 14:43:54 -0700830
Adam Powell637d3372010-08-25 14:37:03 -0700831 @Override
832 public void setOverScrollMode(int mode) {
833 if (mode != OVER_SCROLL_NEVER) {
834 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800835 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700836 mEdgeGlowTop = new EdgeEffect(context);
837 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700838 }
839 } else {
840 mEdgeGlowTop = null;
841 mEdgeGlowBottom = null;
842 }
843 super.setOverScrollMode(mode);
844 }
845
Romain Guyd6a463a2009-05-21 23:10:10 -0700846 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700847 * {@inheritDoc}
848 */
849 @Override
850 public void setAdapter(ListAdapter adapter) {
851 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800852 mAdapterHasStableIds = mAdapter.hasStableIds();
853 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700854 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700855 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700856 }
857 }
858
859 if (mCheckStates != null) {
860 mCheckStates.clear();
861 }
862
863 if (mCheckedIdStates != null) {
864 mCheckedIdStates.clear();
865 }
866 }
867
868 /**
869 * Returns the number of items currently selected. This will only be valid
870 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
871 *
872 * <p>To determine the specific items that are currently selected, use one of
873 * the <code>getChecked*</code> methods.
874 *
875 * @return The number of items currently selected
876 *
877 * @see #getCheckedItemPosition()
878 * @see #getCheckedItemPositions()
879 * @see #getCheckedItemIds()
880 */
881 public int getCheckedItemCount() {
882 return mCheckedItemCount;
883 }
884
885 /**
886 * Returns the checked state of the specified position. The result is only
887 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
888 * or {@link #CHOICE_MODE_MULTIPLE}.
889 *
890 * @param position The item whose checked state to return
891 * @return The item's checked state or <code>false</code> if choice mode
892 * is invalid
893 *
894 * @see #setChoiceMode(int)
895 */
896 public boolean isItemChecked(int position) {
897 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
898 return mCheckStates.get(position);
899 }
900
901 return false;
902 }
903
904 /**
905 * Returns the currently checked item. The result is only valid if the choice
906 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
907 *
908 * @return The position of the currently checked item or
909 * {@link #INVALID_POSITION} if nothing is selected
910 *
911 * @see #setChoiceMode(int)
912 */
913 public int getCheckedItemPosition() {
914 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
915 return mCheckStates.keyAt(0);
916 }
917
918 return INVALID_POSITION;
919 }
920
921 /**
922 * Returns the set of checked items in the list. The result is only valid if
923 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
924 *
925 * @return A SparseBooleanArray which will return true for each call to
926 * get(int position) where position is a position in the list,
927 * or <code>null</code> if the choice mode is set to
928 * {@link #CHOICE_MODE_NONE}.
929 */
930 public SparseBooleanArray getCheckedItemPositions() {
931 if (mChoiceMode != CHOICE_MODE_NONE) {
932 return mCheckStates;
933 }
934 return null;
935 }
936
937 /**
938 * Returns the set of checked items ids. The result is only valid if the
939 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
940 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
941 *
942 * @return A new array which contains the id of each checked item in the
943 * list.
944 */
945 public long[] getCheckedItemIds() {
946 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
947 return new long[0];
948 }
949
Adam Powell14c08042011-10-06 19:46:18 -0700950 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700951 final int count = idStates.size();
952 final long[] ids = new long[count];
953
954 for (int i = 0; i < count; i++) {
955 ids[i] = idStates.keyAt(i);
956 }
957
958 return ids;
959 }
960
961 /**
962 * Clear any choices previously set
963 */
964 public void clearChoices() {
965 if (mCheckStates != null) {
966 mCheckStates.clear();
967 }
968 if (mCheckedIdStates != null) {
969 mCheckedIdStates.clear();
970 }
971 mCheckedItemCount = 0;
972 }
973
974 /**
975 * Sets the checked state of the specified position. The is only valid if
976 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
977 * {@link #CHOICE_MODE_MULTIPLE}.
978 *
979 * @param position The item whose checked state is to be checked
980 * @param value The new checked state for the item
981 */
982 public void setItemChecked(int position, boolean value) {
983 if (mChoiceMode == CHOICE_MODE_NONE) {
984 return;
985 }
986
987 // Start selection mode if needed. We don't need to if we're unchecking something.
988 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -0700989 if (mMultiChoiceModeCallback == null ||
990 !mMultiChoiceModeCallback.hasWrappedCallback()) {
991 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
992 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
993 "supplied. Call setMultiChoiceModeListener to set a callback.");
994 }
Adam Powellf343e1b2010-08-13 18:27:04 -0700995 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
996 }
997
998 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
999 boolean oldValue = mCheckStates.get(position);
1000 mCheckStates.put(position, value);
1001 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1002 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -07001003 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001004 } else {
1005 mCheckedIdStates.delete(mAdapter.getItemId(position));
1006 }
1007 }
1008 if (oldValue != value) {
1009 if (value) {
1010 mCheckedItemCount++;
1011 } else {
1012 mCheckedItemCount--;
1013 }
1014 }
1015 if (mChoiceActionMode != null) {
1016 final long id = mAdapter.getItemId(position);
1017 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1018 position, id, value);
1019 }
1020 } else {
1021 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1022 // Clear all values if we're checking something, or unchecking the currently
1023 // selected item
1024 if (value || isItemChecked(position)) {
1025 mCheckStates.clear();
1026 if (updateIds) {
1027 mCheckedIdStates.clear();
1028 }
1029 }
1030 // this may end up selecting the value we just cleared but this way
1031 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1032 if (value) {
1033 mCheckStates.put(position, true);
1034 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001035 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001036 }
1037 mCheckedItemCount = 1;
1038 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1039 mCheckedItemCount = 0;
1040 }
1041 }
1042
1043 // Do not generate a data change while we are in the layout phase
1044 if (!mInLayout && !mBlockLayoutRequests) {
1045 mDataChanged = true;
1046 rememberSyncState();
1047 requestLayout();
1048 }
1049 }
1050
1051 @Override
1052 public boolean performItemClick(View view, int position, long id) {
1053 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001054 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001055
1056 if (mChoiceMode != CHOICE_MODE_NONE) {
1057 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001058 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001059
1060 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1061 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001062 boolean checked = !mCheckStates.get(position, false);
1063 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001064 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001065 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001066 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001067 } else {
1068 mCheckedIdStates.delete(mAdapter.getItemId(position));
1069 }
1070 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001071 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001072 mCheckedItemCount++;
1073 } else {
1074 mCheckedItemCount--;
1075 }
1076 if (mChoiceActionMode != null) {
1077 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001078 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001079 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001080 }
Adam Powell29382d92012-02-23 11:03:22 -08001081 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001082 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001083 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001084 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001085 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001086 mCheckStates.put(position, true);
1087 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1088 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001089 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001090 }
1091 mCheckedItemCount = 1;
1092 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1093 mCheckedItemCount = 0;
1094 }
Adam Powell29382d92012-02-23 11:03:22 -08001095 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001096 }
1097
Adam Powell29382d92012-02-23 11:03:22 -08001098 if (checkedStateChanged) {
1099 updateOnScreenCheckedViews();
1100 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001101 }
1102
Adam Powellbf5f2b32010-10-24 16:45:44 -07001103 if (dispatchItemClick) {
1104 handled |= super.performItemClick(view, position, id);
1105 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001106
1107 return handled;
1108 }
1109
1110 /**
Adam Powell29382d92012-02-23 11:03:22 -08001111 * Perform a quick, in-place update of the checked or activated state
1112 * on all visible item views. This should only be called when a valid
1113 * choice mode is active.
1114 */
1115 private void updateOnScreenCheckedViews() {
1116 final int firstPos = mFirstPosition;
1117 final int count = getChildCount();
1118 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1119 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1120 for (int i = 0; i < count; i++) {
1121 final View child = getChildAt(i);
1122 final int position = firstPos + i;
1123
1124 if (child instanceof Checkable) {
1125 ((Checkable) child).setChecked(mCheckStates.get(position));
1126 } else if (useActivated) {
1127 child.setActivated(mCheckStates.get(position));
1128 }
1129 }
1130 }
1131
1132 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001133 * @see #setChoiceMode(int)
1134 *
1135 * @return The current choice mode
1136 */
1137 public int getChoiceMode() {
1138 return mChoiceMode;
1139 }
1140
1141 /**
1142 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1143 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1144 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1145 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1146 *
1147 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1148 * {@link #CHOICE_MODE_MULTIPLE}
1149 */
1150 public void setChoiceMode(int choiceMode) {
1151 mChoiceMode = choiceMode;
1152 if (mChoiceActionMode != null) {
1153 mChoiceActionMode.finish();
1154 mChoiceActionMode = null;
1155 }
1156 if (mChoiceMode != CHOICE_MODE_NONE) {
1157 if (mCheckStates == null) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001158 mCheckStates = new SparseBooleanArray(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001159 }
1160 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001161 mCheckedIdStates = new LongSparseArray<Integer>(0);
Adam Powellf343e1b2010-08-13 18:27:04 -07001162 }
1163 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1164 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1165 clearChoices();
1166 setLongClickable(true);
1167 }
1168 }
1169 }
1170
1171 /**
1172 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1173 * selection {@link ActionMode}. Only used when the choice mode is set to
1174 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1175 *
1176 * @param listener Listener that will manage the selection mode
1177 *
1178 * @see #setChoiceMode(int)
1179 */
1180 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1181 if (mMultiChoiceModeCallback == null) {
1182 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1183 }
1184 mMultiChoiceModeCallback.setWrapped(listener);
1185 }
1186
1187 /**
Adam Powell637d3372010-08-25 14:37:03 -07001188 * @return true if all list content currently fits within the view boundaries
1189 */
1190 private boolean contentFits() {
1191 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001192 if (childCount == 0) return true;
1193 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001194
Adam Powell4ce35412011-01-24 14:55:00 -08001195 return getChildAt(0).getTop() >= mListPadding.top &&
1196 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001197 }
1198
1199 /**
Romain Guy0a637162009-05-29 14:43:54 -07001200 * Enables fast scrolling by letting the user quickly scroll through lists by
1201 * dragging the fast scroll thumb. The adapter attached to the list may want
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001202 * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
Romain Guy0a637162009-05-29 14:43:54 -07001203 * jump between sections of the list.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 * @see SectionIndexer
1205 * @see #isFastScrollEnabled()
1206 * @param enabled whether or not to enable fast scrolling
1207 */
1208 public void setFastScrollEnabled(boolean enabled) {
1209 mFastScrollEnabled = enabled;
Alan Viverette447cdf22013-07-15 17:47:34 -07001210
1211 if (enabled && mFastScroller == null) {
1212 mFastScroller = new FastScroller(getContext(), this);
1213 }
1214
1215 if (mFastScroller != null) {
1216 mFastScroller.setEnabled(enabled);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001217 }
1218 }
Romain Guy0a637162009-05-29 14:43:54 -07001219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 /**
Adam Powell20232d02010-12-08 21:08:53 -08001221 * Set whether or not the fast scroller should always be shown in place of the
1222 * standard scrollbars. Fast scrollers shown in this way will not fade out and will
1223 * be a permanent fixture within the list. Best combined with an inset scroll bar style
1224 * that will ensure enough padding. This will enable fast scrolling if it is not
1225 * already enabled.
1226 *
1227 * @param alwaysShow true if the fast scroller should always be displayed.
1228 * @see #setScrollBarStyle(int)
1229 * @see #setFastScrollEnabled(boolean)
1230 */
1231 public void setFastScrollAlwaysVisible(boolean alwaysShow) {
1232 if (alwaysShow && !mFastScrollEnabled) {
1233 setFastScrollEnabled(true);
1234 }
1235
1236 if (mFastScroller != null) {
1237 mFastScroller.setAlwaysShow(alwaysShow);
1238 }
1239
1240 computeOpaqueFlags();
1241 recomputePadding();
1242 }
1243
1244 /**
1245 * Returns true if the fast scroller is set to always show on this view rather than
1246 * fade out when not in use.
1247 *
1248 * @return true if the fast scroller will always show.
1249 * @see #setFastScrollAlwaysVisible(boolean)
1250 */
1251 public boolean isFastScrollAlwaysVisible() {
1252 return mFastScrollEnabled && mFastScroller.isAlwaysShowEnabled();
1253 }
1254
1255 @Override
1256 public int getVerticalScrollbarWidth() {
Adam Powell16bb80a2010-12-09 23:30:50 -08001257 if (isFastScrollAlwaysVisible()) {
Adam Powell20232d02010-12-08 21:08:53 -08001258 return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth());
1259 }
1260 return super.getVerticalScrollbarWidth();
1261 }
1262
1263 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 * Returns the current state of the fast scroll feature.
1265 * @see #setFastScrollEnabled(boolean)
1266 * @return true if fast scroll is enabled, false otherwise
1267 */
1268 @ViewDebug.ExportedProperty
1269 public boolean isFastScrollEnabled() {
1270 return mFastScrollEnabled;
1271 }
Romain Guy0a637162009-05-29 14:43:54 -07001272
Adam Powell20232d02010-12-08 21:08:53 -08001273 @Override
1274 public void setVerticalScrollbarPosition(int position) {
1275 super.setVerticalScrollbarPosition(position);
1276 if (mFastScroller != null) {
1277 mFastScroller.setScrollbarPosition(position);
1278 }
1279 }
1280
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 /**
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001282 * If fast scroll is enabled, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001283 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284 */
1285 @Override
1286 protected boolean isVerticalScrollBarHidden() {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001287 return mFastScrollEnabled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 }
1289
1290 /**
1291 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1292 * is computed based on the number of visible pixels in the visible items. This
1293 * however assumes that all list items have the same height. If you use a list in
1294 * which items have different heights, the scrollbar will change appearance as the
1295 * user scrolls through the list. To avoid this issue, you need to disable this
1296 * property.
1297 *
1298 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1299 * is based solely on the number of items in the adapter and the position of the
1300 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001301 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 *
1303 * @param enabled Whether or not to enable smooth scrollbar.
1304 *
Romain Guy0a637162009-05-29 14:43:54 -07001305 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001306 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1307 */
1308 public void setSmoothScrollbarEnabled(boolean enabled) {
1309 mSmoothScrollbarEnabled = enabled;
1310 }
1311
1312 /**
1313 * Returns the current state of the fast scroll feature.
1314 *
1315 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1316 *
1317 * @see #setSmoothScrollbarEnabled(boolean)
1318 */
1319 @ViewDebug.ExportedProperty
1320 public boolean isSmoothScrollbarEnabled() {
1321 return mSmoothScrollbarEnabled;
1322 }
1323
1324 /**
1325 * Set the listener that will receive notifications every time the list scrolls.
1326 *
1327 * @param l the scroll listener
1328 */
1329 public void setOnScrollListener(OnScrollListener l) {
1330 mOnScrollListener = l;
1331 invokeOnItemScrollListener();
1332 }
1333
1334 /**
1335 * Notify our scroll listener (if there is one) of a change in scroll state
1336 */
1337 void invokeOnItemScrollListener() {
1338 if (mFastScroller != null) {
Alan Viverette0ebe81e2013-06-21 17:01:36 -07001339 mFastScroller.onScroll(mFirstPosition, getChildCount(), mItemCount);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001340 }
1341 if (mOnScrollListener != null) {
1342 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1343 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001344 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001345 }
1346
Svetoslav Ganova0156172011-06-26 17:55:44 -07001347 @Override
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001348 public void sendAccessibilityEvent(int eventType) {
1349 // Since this class calls onScrollChanged even if the mFirstPosition and the
1350 // child count have not changed we will avoid sending duplicate accessibility
1351 // events.
1352 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001353 final int firstVisiblePosition = getFirstVisiblePosition();
1354 final int lastVisiblePosition = getLastVisiblePosition();
1355 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1356 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001357 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001358 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001359 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1360 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001361 }
1362 }
1363 super.sendAccessibilityEvent(eventType);
1364 }
1365
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001366 @Override
1367 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1368 super.onInitializeAccessibilityEvent(event);
1369 event.setClassName(AbsListView.class.getName());
1370 }
1371
1372 @Override
1373 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1374 super.onInitializeAccessibilityNodeInfo(info);
1375 info.setClassName(AbsListView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001376 if (isEnabled()) {
1377 if (getFirstVisiblePosition() > 0) {
1378 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001379 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001380 }
1381 if (getLastVisiblePosition() < getCount() - 1) {
1382 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001383 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001384 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001385 }
1386 }
1387
1388 @Override
1389 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001390 if (super.performAccessibilityAction(action, arguments)) {
1391 return true;
1392 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001393 switch (action) {
1394 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001395 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001396 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1397 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1398 return true;
1399 }
1400 } return false;
1401 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001402 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001403 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1404 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1405 return true;
1406 }
1407 } return false;
1408 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001409 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001410 }
1411
Svetoslav5b578da2013-05-08 14:23:32 -07001412 /** @hide */
1413 @Override
1414 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1415 if (accessibilityId == getAccessibilityViewId()) {
1416 return this;
1417 }
1418 // If the data changed the children are invalid since the data model changed.
1419 // Hence, we pretend they do not exist. After a layout the children will sync
1420 // with the model at which point we notify that the accessibility state changed,
1421 // so a service will be able to re-fetch the views.
1422 if (mDataChanged) {
1423 return null;
1424 }
1425 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1426 }
1427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001428 /**
1429 * Indicates whether the children's drawing cache is used during a scroll.
1430 * By default, the drawing cache is enabled but this will consume more memory.
1431 *
1432 * @return true if the scrolling cache is enabled, false otherwise
1433 *
1434 * @see #setScrollingCacheEnabled(boolean)
1435 * @see View#setDrawingCacheEnabled(boolean)
1436 */
1437 @ViewDebug.ExportedProperty
1438 public boolean isScrollingCacheEnabled() {
1439 return mScrollingCacheEnabled;
1440 }
1441
1442 /**
1443 * Enables or disables the children's drawing cache during a scroll.
1444 * By default, the drawing cache is enabled but this will use more memory.
1445 *
1446 * When the scrolling cache is enabled, the caches are kept after the
1447 * first scrolling. You can manually clear the cache by calling
1448 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1449 *
1450 * @param enabled true to enable the scroll cache, false otherwise
1451 *
1452 * @see #isScrollingCacheEnabled()
1453 * @see View#setDrawingCacheEnabled(boolean)
1454 */
1455 public void setScrollingCacheEnabled(boolean enabled) {
1456 if (mScrollingCacheEnabled && !enabled) {
1457 clearScrollingCache();
1458 }
1459 mScrollingCacheEnabled = enabled;
1460 }
1461
1462 /**
1463 * Enables or disables the type filter window. If enabled, typing when
1464 * this view has focus will filter the children to match the users input.
1465 * Note that the {@link Adapter} used by this view must implement the
1466 * {@link Filterable} interface.
1467 *
1468 * @param textFilterEnabled true to enable type filtering, false otherwise
1469 *
1470 * @see Filterable
1471 */
1472 public void setTextFilterEnabled(boolean textFilterEnabled) {
1473 mTextFilterEnabled = textFilterEnabled;
1474 }
1475
1476 /**
1477 * Indicates whether type filtering is enabled for this view
1478 *
1479 * @return true if type filtering is enabled, false otherwise
1480 *
1481 * @see #setTextFilterEnabled(boolean)
1482 * @see Filterable
1483 */
1484 @ViewDebug.ExportedProperty
1485 public boolean isTextFilterEnabled() {
1486 return mTextFilterEnabled;
1487 }
1488
1489 @Override
1490 public void getFocusedRect(Rect r) {
1491 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001492 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001493 // the focused rectangle of the selected view offset into the
1494 // coordinate space of this view.
1495 view.getFocusedRect(r);
1496 offsetDescendantRectToMyCoords(view, r);
1497 } else {
1498 // otherwise, just the norm
1499 super.getFocusedRect(r);
1500 }
1501 }
1502
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 private void useDefaultSelector() {
1504 setSelector(getResources().getDrawable(
1505 com.android.internal.R.drawable.list_selector_background));
1506 }
1507
1508 /**
1509 * Indicates whether the content of this view is pinned to, or stacked from,
1510 * the bottom edge.
1511 *
1512 * @return true if the content is stacked from the bottom edge, false otherwise
1513 */
1514 @ViewDebug.ExportedProperty
1515 public boolean isStackFromBottom() {
1516 return mStackFromBottom;
1517 }
1518
1519 /**
1520 * When stack from bottom is set to true, the list fills its content starting from
1521 * the bottom of the view.
1522 *
1523 * @param stackFromBottom true to pin the view's content to the bottom edge,
1524 * false to pin the view's content to the top edge
1525 */
1526 public void setStackFromBottom(boolean stackFromBottom) {
1527 if (mStackFromBottom != stackFromBottom) {
1528 mStackFromBottom = stackFromBottom;
1529 requestLayoutIfNecessary();
1530 }
1531 }
1532
1533 void requestLayoutIfNecessary() {
1534 if (getChildCount() > 0) {
1535 resetList();
1536 requestLayout();
1537 invalidate();
1538 }
1539 }
1540
1541 static class SavedState extends BaseSavedState {
1542 long selectedId;
1543 long firstId;
1544 int viewTop;
1545 int position;
1546 int height;
1547 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001548 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001549 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001550 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001551 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001552
1553 /**
1554 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1555 */
1556 SavedState(Parcelable superState) {
1557 super(superState);
1558 }
1559
1560 /**
1561 * Constructor called from {@link #CREATOR}
1562 */
1563 private SavedState(Parcel in) {
1564 super(in);
1565 selectedId = in.readLong();
1566 firstId = in.readLong();
1567 viewTop = in.readInt();
1568 position = in.readInt();
1569 height = in.readInt();
1570 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001571 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001572 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001573 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001574 final int N = in.readInt();
1575 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001576 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001577 for (int i=0; i<N; i++) {
1578 final long key = in.readLong();
1579 final int value = in.readInt();
1580 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001581 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001583 }
1584
1585 @Override
1586 public void writeToParcel(Parcel out, int flags) {
1587 super.writeToParcel(out, flags);
1588 out.writeLong(selectedId);
1589 out.writeLong(firstId);
1590 out.writeInt(viewTop);
1591 out.writeInt(position);
1592 out.writeInt(height);
1593 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001594 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001595 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001596 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001597 final int N = checkIdState != null ? checkIdState.size() : 0;
1598 out.writeInt(N);
1599 for (int i=0; i<N; i++) {
1600 out.writeLong(checkIdState.keyAt(i));
1601 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001602 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001603 }
1604
1605 @Override
1606 public String toString() {
1607 return "AbsListView.SavedState{"
1608 + Integer.toHexString(System.identityHashCode(this))
1609 + " selectedId=" + selectedId
1610 + " firstId=" + firstId
1611 + " viewTop=" + viewTop
1612 + " position=" + position
1613 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001614 + " filter=" + filter
1615 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001616 }
1617
1618 public static final Parcelable.Creator<SavedState> CREATOR
1619 = new Parcelable.Creator<SavedState>() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07001620 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001621 public SavedState createFromParcel(Parcel in) {
1622 return new SavedState(in);
1623 }
1624
Alan Viverette8fa327a2013-05-31 14:53:13 -07001625 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001626 public SavedState[] newArray(int size) {
1627 return new SavedState[size];
1628 }
1629 };
1630 }
1631
1632 @Override
1633 public Parcelable onSaveInstanceState() {
1634 /*
1635 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001636 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001637 * that happen early enough to keep from getting complaints
1638 * about having leaked the window.
1639 */
1640 dismissPopup();
1641
1642 Parcelable superState = super.onSaveInstanceState();
1643
1644 SavedState ss = new SavedState(superState);
1645
Dianne Hackborne181bd92012-09-25 14:15:15 -07001646 if (mPendingSync != null) {
1647 // Just keep what we last restored.
1648 ss.selectedId = mPendingSync.selectedId;
1649 ss.firstId = mPendingSync.firstId;
1650 ss.viewTop = mPendingSync.viewTop;
1651 ss.position = mPendingSync.position;
1652 ss.height = mPendingSync.height;
1653 ss.filter = mPendingSync.filter;
1654 ss.inActionMode = mPendingSync.inActionMode;
1655 ss.checkedItemCount = mPendingSync.checkedItemCount;
1656 ss.checkState = mPendingSync.checkState;
1657 ss.checkIdState = mPendingSync.checkIdState;
1658 return ss;
1659 }
1660
Dianne Hackborn99441c42010-12-15 11:02:55 -08001661 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001662 long selectedId = getSelectedItemId();
1663 ss.selectedId = selectedId;
1664 ss.height = getHeight();
1665
1666 if (selectedId >= 0) {
1667 // Remember the selection
1668 ss.viewTop = mSelectedTop;
1669 ss.position = getSelectedItemPosition();
1670 ss.firstId = INVALID_POSITION;
1671 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001672 if (haveChildren && mFirstPosition > 0) {
1673 // Remember the position of the first child.
1674 // We only do this if we are not currently at the top of
1675 // the list, for two reasons:
1676 // (1) The list may be in the process of becoming empty, in
1677 // which case mItemCount may not be 0, but if we try to
1678 // ask for any information about position 0 we will crash.
1679 // (2) Being "at the top" seems like a special case, anyway,
1680 // and the user wouldn't expect to end up somewhere else when
1681 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001682 View v = getChildAt(0);
1683 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001684 int firstPos = mFirstPosition;
1685 if (firstPos >= mItemCount) {
1686 firstPos = mItemCount - 1;
1687 }
1688 ss.position = firstPos;
1689 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001690 } else {
1691 ss.viewTop = 0;
1692 ss.firstId = INVALID_POSITION;
1693 ss.position = 0;
1694 }
1695 }
1696
1697 ss.filter = null;
1698 if (mFiltered) {
1699 final EditText textFilter = mTextFilter;
1700 if (textFilter != null) {
1701 Editable filterText = textFilter.getText();
1702 if (filterText != null) {
1703 ss.filter = filterText.toString();
1704 }
1705 }
1706 }
1707
Adam Powella0eeeac2010-11-05 11:55:05 -07001708 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1709
Adam Powell9a5cc282011-08-28 16:18:16 -07001710 if (mCheckStates != null) {
1711 ss.checkState = mCheckStates.clone();
1712 }
1713 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001714 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001715 final int count = mCheckedIdStates.size();
1716 for (int i = 0; i < count; i++) {
1717 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1718 }
1719 ss.checkIdState = idState;
1720 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001721 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001722
Adam Cohen335c3b62012-07-24 17:18:16 -07001723 if (mRemoteAdapter != null) {
1724 mRemoteAdapter.saveRemoteViewsCache();
1725 }
1726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001727 return ss;
1728 }
1729
1730 @Override
1731 public void onRestoreInstanceState(Parcelable state) {
1732 SavedState ss = (SavedState) state;
1733
1734 super.onRestoreInstanceState(ss.getSuperState());
1735 mDataChanged = true;
1736
1737 mSyncHeight = ss.height;
1738
1739 if (ss.selectedId >= 0) {
1740 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001741 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001742 mSyncRowId = ss.selectedId;
1743 mSyncPosition = ss.position;
1744 mSpecificTop = ss.viewTop;
1745 mSyncMode = SYNC_SELECTED_POSITION;
1746 } else if (ss.firstId >= 0) {
1747 setSelectedPositionInt(INVALID_POSITION);
1748 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1749 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001750 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001751 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001752 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001753 mSyncRowId = ss.firstId;
1754 mSyncPosition = ss.position;
1755 mSpecificTop = ss.viewTop;
1756 mSyncMode = SYNC_FIRST_POSITION;
1757 }
1758
1759 setFilterText(ss.filter);
1760
Adam Powellf343e1b2010-08-13 18:27:04 -07001761 if (ss.checkState != null) {
1762 mCheckStates = ss.checkState;
1763 }
1764
1765 if (ss.checkIdState != null) {
1766 mCheckedIdStates = ss.checkIdState;
1767 }
1768
Adam Powell2614c6c2010-11-04 17:54:45 -07001769 mCheckedItemCount = ss.checkedItemCount;
1770
Adam Powella0eeeac2010-11-05 11:55:05 -07001771 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1772 mMultiChoiceModeCallback != null) {
1773 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1774 }
1775
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001776 requestLayout();
1777 }
1778
1779 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001780 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1781 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001782 }
1783
1784 /**
1785 * Sets the initial value for the text filter.
1786 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001787 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001788 * @see #setTextFilterEnabled
1789 */
1790 public void setFilterText(String filterText) {
1791 // TODO: Should we check for acceptFilter()?
1792 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1793 createTextFilter(false);
1794 // This is going to call our listener onTextChanged, but we might not
1795 // be ready to bring up a window yet
1796 mTextFilter.setText(filterText);
1797 mTextFilter.setSelection(filterText.length());
1798 if (mAdapter instanceof Filterable) {
1799 // if mPopup is non-null, then onTextChanged will do the filtering
1800 if (mPopup == null) {
1801 Filter f = ((Filterable) mAdapter).getFilter();
1802 f.filter(filterText);
1803 }
1804 // Set filtered to true so we will display the filter window when our main
1805 // window is ready
1806 mFiltered = true;
1807 mDataSetObserver.clearSavedState();
1808 }
1809 }
1810 }
1811
1812 /**
Romain Guy0a637162009-05-29 14:43:54 -07001813 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001814 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001815 */
1816 public CharSequence getTextFilter() {
1817 if (mTextFilterEnabled && mTextFilter != null) {
1818 return mTextFilter.getText();
1819 }
1820 return null;
1821 }
Romain Guy0a637162009-05-29 14:43:54 -07001822
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001823 @Override
1824 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1825 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1826 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powellb3750132011-08-08 23:29:12 -07001827 if (!mIsAttached && mAdapter != null) {
1828 // Data may have changed while we were detached and it's valid
1829 // to change focus while detached. Refresh so we don't die.
1830 mDataChanged = true;
1831 mOldItemCount = mItemCount;
1832 mItemCount = mAdapter.getCount();
1833 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834 resurrectSelection();
1835 }
1836 }
1837
1838 @Override
1839 public void requestLayout() {
1840 if (!mBlockLayoutRequests && !mInLayout) {
1841 super.requestLayout();
1842 }
1843 }
1844
1845 /**
1846 * The list is empty. Clear everything out.
1847 */
1848 void resetList() {
1849 removeAllViewsInLayout();
1850 mFirstPosition = 0;
1851 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001852 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001853 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001854 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001855 mOldSelectedPosition = INVALID_POSITION;
1856 mOldSelectedRowId = INVALID_ROW_ID;
1857 setSelectedPositionInt(INVALID_POSITION);
1858 setNextSelectedPositionInt(INVALID_POSITION);
1859 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07001860 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001861 mSelectorRect.setEmpty();
1862 invalidate();
1863 }
1864
1865 @Override
1866 protected int computeVerticalScrollExtent() {
1867 final int count = getChildCount();
1868 if (count > 0) {
1869 if (mSmoothScrollbarEnabled) {
1870 int extent = count * 100;
1871
1872 View view = getChildAt(0);
1873 final int top = view.getTop();
1874 int height = view.getHeight();
1875 if (height > 0) {
1876 extent += (top * 100) / height;
1877 }
1878
1879 view = getChildAt(count - 1);
1880 final int bottom = view.getBottom();
1881 height = view.getHeight();
1882 if (height > 0) {
1883 extent -= ((bottom - getHeight()) * 100) / height;
1884 }
1885
1886 return extent;
1887 } else {
1888 return 1;
1889 }
1890 }
1891 return 0;
1892 }
1893
1894 @Override
1895 protected int computeVerticalScrollOffset() {
1896 final int firstPosition = mFirstPosition;
1897 final int childCount = getChildCount();
1898 if (firstPosition >= 0 && childCount > 0) {
1899 if (mSmoothScrollbarEnabled) {
1900 final View view = getChildAt(0);
1901 final int top = view.getTop();
1902 int height = view.getHeight();
1903 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08001904 return Math.max(firstPosition * 100 - (top * 100) / height +
1905 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001906 }
1907 } else {
1908 int index;
1909 final int count = mItemCount;
1910 if (firstPosition == 0) {
1911 index = 0;
1912 } else if (firstPosition + childCount == count) {
1913 index = count;
1914 } else {
1915 index = firstPosition + childCount / 2;
1916 }
1917 return (int) (firstPosition + childCount * (index / (float) count));
1918 }
1919 }
1920 return 0;
1921 }
1922
1923 @Override
1924 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08001925 int result;
1926 if (mSmoothScrollbarEnabled) {
1927 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07001928 if (mScrollY != 0) {
1929 // Compensate for overscroll
1930 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
1931 }
Adam Powell0b8bb422010-02-08 14:30:45 -08001932 } else {
1933 result = mItemCount;
1934 }
1935 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001936 }
1937
1938 @Override
1939 protected float getTopFadingEdgeStrength() {
1940 final int count = getChildCount();
1941 final float fadeEdge = super.getTopFadingEdgeStrength();
1942 if (count == 0) {
1943 return fadeEdge;
1944 } else {
1945 if (mFirstPosition > 0) {
1946 return 1.0f;
1947 }
1948
1949 final int top = getChildAt(0).getTop();
Alan Viverette8fa327a2013-05-31 14:53:13 -07001950 final float fadeLength = getVerticalFadingEdgeLength();
1951 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001952 }
1953 }
1954
1955 @Override
1956 protected float getBottomFadingEdgeStrength() {
1957 final int count = getChildCount();
1958 final float fadeEdge = super.getBottomFadingEdgeStrength();
1959 if (count == 0) {
1960 return fadeEdge;
1961 } else {
1962 if (mFirstPosition + count - 1 < mItemCount - 1) {
1963 return 1.0f;
1964 }
1965
1966 final int bottom = getChildAt(count - 1).getBottom();
1967 final int height = getHeight();
Alan Viverette8fa327a2013-05-31 14:53:13 -07001968 final float fadeLength = getVerticalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001969 return bottom > height - mPaddingBottom ?
Alan Viverette8fa327a2013-05-31 14:53:13 -07001970 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001971 }
1972 }
1973
1974 @Override
1975 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1976 if (mSelector == null) {
1977 useDefaultSelector();
1978 }
1979 final Rect listPadding = mListPadding;
1980 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
1981 listPadding.top = mSelectionTopPadding + mPaddingTop;
1982 listPadding.right = mSelectionRightPadding + mPaddingRight;
1983 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08001984
1985 // Check if our previous measured size was at a point where we should scroll later.
1986 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
1987 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07001988 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08001989 final View lastChild = getChildAt(childCount - 1);
1990 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07001991 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08001992 lastBottom <= listBottom;
1993 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001994 }
1995
Romain Guyd6a463a2009-05-21 23:10:10 -07001996 /**
1997 * Subclasses should NOT override this method but
1998 * {@link #layoutChildren()} instead.
1999 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002000 @Override
2001 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2002 super.onLayout(changed, l, t, r, b);
2003 mInLayout = true;
Adam Powellf3c2eda2010-03-16 17:31:01 -07002004 if (changed) {
2005 int childCount = getChildCount();
2006 for (int i = 0; i < childCount; i++) {
2007 getChildAt(i).forceLayout();
2008 }
2009 mRecycler.markChildrenDirty();
2010 }
Alan Viverette0ebe81e2013-06-21 17:01:36 -07002011
Adam Powell2c6196a2010-12-10 14:31:54 -08002012 if (mFastScroller != null && mItemCount != mOldItemCount) {
2013 mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
2014 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07002015
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002016 layoutChildren();
2017 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002018
2019 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002020 }
2021
2022 /**
2023 * @hide
2024 */
2025 @Override
2026 protected boolean setFrame(int left, int top, int right, int bottom) {
2027 final boolean changed = super.setFrame(left, top, right, bottom);
2028
Romain Guyd6a463a2009-05-21 23:10:10 -07002029 if (changed) {
2030 // Reposition the popup when the frame has changed. This includes
2031 // translating the widget, not just changing its dimension. The
2032 // filter popup needs to follow the widget.
2033 final boolean visible = getWindowVisibility() == View.VISIBLE;
2034 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2035 positionPopup();
2036 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002037 }
2038
2039 return changed;
2040 }
2041
Romain Guyd6a463a2009-05-21 23:10:10 -07002042 /**
2043 * Subclasses must override this method to layout their children.
2044 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002045 protected void layoutChildren() {
2046 }
2047
2048 void updateScrollIndicators() {
2049 if (mScrollUp != null) {
2050 boolean canScrollUp;
2051 // 0th element is not visible
2052 canScrollUp = mFirstPosition > 0;
2053
2054 // ... Or top of 0th element is not visible
2055 if (!canScrollUp) {
2056 if (getChildCount() > 0) {
2057 View child = getChildAt(0);
2058 canScrollUp = child.getTop() < mListPadding.top;
2059 }
2060 }
2061
2062 mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
2063 }
2064
2065 if (mScrollDown != null) {
2066 boolean canScrollDown;
2067 int count = getChildCount();
2068
2069 // Last item is not visible
2070 canScrollDown = (mFirstPosition + count) < mItemCount;
2071
2072 // ... Or bottom of the last element is not visible
2073 if (!canScrollDown && count > 0) {
2074 View child = getChildAt(count - 1);
2075 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2076 }
2077
2078 mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
2079 }
2080 }
2081
2082 @Override
2083 @ViewDebug.ExportedProperty
2084 public View getSelectedView() {
2085 if (mItemCount > 0 && mSelectedPosition >= 0) {
2086 return getChildAt(mSelectedPosition - mFirstPosition);
2087 } else {
2088 return null;
2089 }
2090 }
2091
2092 /**
2093 * List padding is the maximum of the normal view's padding and the padding of the selector.
2094 *
2095 * @see android.view.View#getPaddingTop()
2096 * @see #getSelector()
2097 *
2098 * @return The top list padding.
2099 */
2100 public int getListPaddingTop() {
2101 return mListPadding.top;
2102 }
2103
2104 /**
2105 * List padding is the maximum of the normal view's padding and the padding of the selector.
2106 *
2107 * @see android.view.View#getPaddingBottom()
2108 * @see #getSelector()
2109 *
2110 * @return The bottom list padding.
2111 */
2112 public int getListPaddingBottom() {
2113 return mListPadding.bottom;
2114 }
2115
2116 /**
2117 * List padding is the maximum of the normal view's padding and the padding of the selector.
2118 *
2119 * @see android.view.View#getPaddingLeft()
2120 * @see #getSelector()
2121 *
2122 * @return The left list padding.
2123 */
2124 public int getListPaddingLeft() {
2125 return mListPadding.left;
2126 }
2127
2128 /**
2129 * List padding is the maximum of the normal view's padding and the padding of the selector.
2130 *
2131 * @see android.view.View#getPaddingRight()
2132 * @see #getSelector()
2133 *
2134 * @return The right list padding.
2135 */
2136 public int getListPaddingRight() {
2137 return mListPadding.right;
2138 }
2139
2140 /**
2141 * Get a view and have it show the data associated with the specified
2142 * position. This is called when we have already discovered that the view is
2143 * not available for reuse in the recycle bin. The only choices left are
2144 * converting an old view or making a new one.
2145 *
2146 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002147 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2148 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002149 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002150 * @return A view displaying the data associated with the specified position
2151 */
Romain Guy21875052010-01-06 18:48:08 -08002152 View obtainView(int position, boolean[] isScrap) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002153 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2154
Romain Guy21875052010-01-06 18:48:08 -08002155 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002156 View scrapView;
2157
Adam Powell539ee872012-02-03 19:00:49 -08002158 scrapView = mRecycler.getTransientStateView(position);
2159 if (scrapView != null) {
2160 return scrapView;
2161 }
2162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002163 scrapView = mRecycler.getScrapView(position);
2164
2165 View child;
2166 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002167 child = mAdapter.getView(position, scrapView, this);
2168
Svetoslav Ganov42138042012-03-20 11:51:39 -07002169 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2170 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2171 }
2172
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002173 if (child != scrapView) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002174 mRecycler.addScrapView(scrapView, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002175 if (mCacheColorHint != 0) {
2176 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2177 }
Romain Guy21875052010-01-06 18:48:08 -08002178 } else {
Romain Guya440b002010-02-24 15:57:54 -08002179 isScrap[0] = true;
2180 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002181 }
2182 } else {
2183 child = mAdapter.getView(position, null, this);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002184
2185 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2186 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2187 }
2188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002189 if (mCacheColorHint != 0) {
2190 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2191 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002192 }
2193
Adam Powellaebd28f2012-02-22 10:31:16 -08002194 if (mAdapterHasStableIds) {
2195 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2196 LayoutParams lp;
2197 if (vlp == null) {
2198 lp = (LayoutParams) generateDefaultLayoutParams();
2199 } else if (!checkLayoutParams(vlp)) {
2200 lp = (LayoutParams) generateLayoutParams(vlp);
2201 } else {
2202 lp = (LayoutParams) vlp;
2203 }
2204 lp.itemId = mAdapter.getItemId(position);
2205 child.setLayoutParams(lp);
2206 }
2207
alanvc1d7e772012-05-08 14:47:24 -07002208 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2209 if (mAccessibilityDelegate == null) {
2210 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2211 }
alanvb72fe7a2012-08-27 16:44:25 -07002212 if (child.getAccessibilityDelegate() == null) {
2213 child.setAccessibilityDelegate(mAccessibilityDelegate);
2214 }
alanvc1d7e772012-05-08 14:47:24 -07002215 }
2216
Romain Guy5fade8c2013-07-10 16:36:18 -07002217 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2218
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002219 return child;
2220 }
2221
alanvc1d7e772012-05-08 14:47:24 -07002222 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2223 @Override
Svetoslav Ganov50776862013-05-17 18:06:31 -07002224 public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
2225 // If the data changed the children are invalid since the data model changed.
2226 // Hence, we pretend they do not exist. After a layout the children will sync
2227 // with the model at which point we notify that the accessibility state changed,
2228 // so a service will be able to re-fetch the views.
2229 if (mDataChanged) {
2230 return null;
2231 }
2232 return super.createAccessibilityNodeInfo(host);
2233 }
2234
2235 @Override
alanvc1d7e772012-05-08 14:47:24 -07002236 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2237 super.onInitializeAccessibilityNodeInfo(host, info);
2238
2239 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002240 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002241
alanv9c3e0e62012-05-18 17:43:35 -07002242 if ((position == INVALID_POSITION) || (adapter == null)) {
alanvc1d7e772012-05-08 14:47:24 -07002243 return;
2244 }
2245
alanv9c3e0e62012-05-18 17:43:35 -07002246 if (!isEnabled() || !adapter.isEnabled(position)) {
alanv9c3e0e62012-05-18 17:43:35 -07002247 return;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002248 }
alanvc1d7e772012-05-08 14:47:24 -07002249
2250 if (position == getSelectedItemPosition()) {
2251 info.setSelected(true);
alanv9c3e0e62012-05-18 17:43:35 -07002252 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
2253 } else {
2254 info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
alanvc1d7e772012-05-08 14:47:24 -07002255 }
alanv9c3e0e62012-05-18 17:43:35 -07002256
2257 if (isClickable()) {
2258 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2259 info.setClickable(true);
2260 }
2261
2262 if (isLongClickable()) {
2263 info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
2264 info.setLongClickable(true);
2265 }
2266
alanvc1d7e772012-05-08 14:47:24 -07002267 }
2268
2269 @Override
2270 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002271 if (super.performAccessibilityAction(host, action, arguments)) {
2272 return true;
2273 }
2274
alanvc1d7e772012-05-08 14:47:24 -07002275 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002276 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002277
alanv9c3e0e62012-05-18 17:43:35 -07002278 if ((position == INVALID_POSITION) || (adapter == null)) {
2279 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002280 return false;
2281 }
2282
alanv9c3e0e62012-05-18 17:43:35 -07002283 if (!isEnabled() || !adapter.isEnabled(position)) {
2284 // Cannot perform actions on disabled items.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002285 return false;
2286 }
2287
alanvc1d7e772012-05-08 14:47:24 -07002288 final long id = getItemIdAtPosition(position);
2289
2290 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002291 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2292 if (getSelectedItemPosition() == position) {
2293 setSelection(INVALID_POSITION);
2294 return true;
2295 }
2296 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002297 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002298 if (getSelectedItemPosition() != position) {
2299 setSelection(position);
2300 return true;
2301 }
2302 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002303 case AccessibilityNodeInfo.ACTION_CLICK: {
2304 if (isClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002305 return performItemClick(host, position, id);
2306 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002307 } return false;
2308 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2309 if (isLongClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002310 return performLongPress(host, position, id);
2311 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002312 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002313 }
2314
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002315 return false;
alanvc1d7e772012-05-08 14:47:24 -07002316 }
2317 }
2318
Dianne Hackborn079e2352010-10-18 17:02:43 -07002319 void positionSelector(int position, View sel) {
2320 if (position != INVALID_POSITION) {
2321 mSelectorPosition = position;
2322 }
2323
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002324 final Rect selectorRect = mSelectorRect;
2325 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002326 if (sel instanceof SelectionBoundsAdjuster) {
2327 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2328 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002329 positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
2330 selectorRect.bottom);
2331
2332 final boolean isChildViewEnabled = mIsChildViewEnabled;
2333 if (sel.isEnabled() != isChildViewEnabled) {
2334 mIsChildViewEnabled = !isChildViewEnabled;
Jason Bayera79f4b72011-03-22 20:03:31 -07002335 if (getSelectedItemPosition() != INVALID_POSITION) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002336 refreshDrawableState();
2337 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002338 }
2339 }
2340
2341 private void positionSelector(int l, int t, int r, int b) {
2342 mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
2343 + mSelectionRightPadding, b + mSelectionBottomPadding);
2344 }
2345
2346 @Override
2347 protected void dispatchDraw(Canvas canvas) {
2348 int saveCount = 0;
2349 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2350 if (clipToPadding) {
2351 saveCount = canvas.save();
2352 final int scrollX = mScrollX;
2353 final int scrollY = mScrollY;
2354 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2355 scrollX + mRight - mLeft - mPaddingRight,
2356 scrollY + mBottom - mTop - mPaddingBottom);
2357 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2358 }
2359
2360 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2361 if (!drawSelectorOnTop) {
2362 drawSelector(canvas);
2363 }
2364
2365 super.dispatchDraw(canvas);
2366
2367 if (drawSelectorOnTop) {
2368 drawSelector(canvas);
2369 }
2370
2371 if (clipToPadding) {
2372 canvas.restoreToCount(saveCount);
2373 mGroupFlags |= CLIP_TO_PADDING_MASK;
2374 }
2375 }
2376
2377 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002378 protected boolean isPaddingOffsetRequired() {
2379 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2380 }
2381
2382 @Override
2383 protected int getLeftPaddingOffset() {
2384 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2385 }
2386
2387 @Override
2388 protected int getTopPaddingOffset() {
2389 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2390 }
2391
2392 @Override
2393 protected int getRightPaddingOffset() {
2394 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2395 }
2396
2397 @Override
2398 protected int getBottomPaddingOffset() {
2399 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2400 }
2401
2402 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002403 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2404 if (getChildCount() > 0) {
2405 mDataChanged = true;
2406 rememberSyncState();
2407 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002408
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002409 if (mFastScroller != null) {
2410 mFastScroller.onSizeChanged(w, h, oldw, oldh);
2411 }
2412 }
2413
2414 /**
2415 * @return True if the current touch mode requires that we draw the selector in the pressed
2416 * state.
2417 */
2418 boolean touchModeDrawsInPressedState() {
2419 // FIXME use isPressed for this
2420 switch (mTouchMode) {
2421 case TOUCH_MODE_TAP:
2422 case TOUCH_MODE_DONE_WAITING:
2423 return true;
2424 default:
2425 return false;
2426 }
2427 }
2428
2429 /**
2430 * Indicates whether this view is in a state where the selector should be drawn. This will
2431 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2432 * the pressed state for an item.
2433 *
2434 * @return True if the selector should be shown
2435 */
2436 boolean shouldShowSelector() {
Alan Viverette74ded292013-06-03 15:34:11 -07002437 return (!isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002438 }
2439
2440 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002441 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002442 final Drawable selector = mSelector;
2443 selector.setBounds(mSelectorRect);
2444 selector.draw(canvas);
2445 }
2446 }
2447
2448 /**
2449 * Controls whether the selection highlight drawable should be drawn on top of the item or
2450 * behind it.
2451 *
2452 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2453 * is false.
2454 *
2455 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2456 */
2457 public void setDrawSelectorOnTop(boolean onTop) {
2458 mDrawSelectorOnTop = onTop;
2459 }
2460
2461 /**
2462 * Set a Drawable that should be used to highlight the currently selected item.
2463 *
2464 * @param resID A Drawable resource to use as the selection highlight.
2465 *
2466 * @attr ref android.R.styleable#AbsListView_listSelector
2467 */
2468 public void setSelector(int resID) {
2469 setSelector(getResources().getDrawable(resID));
2470 }
2471
2472 public void setSelector(Drawable sel) {
2473 if (mSelector != null) {
2474 mSelector.setCallback(null);
2475 unscheduleDrawable(mSelector);
2476 }
2477 mSelector = sel;
2478 Rect padding = new Rect();
2479 sel.getPadding(padding);
2480 mSelectionLeftPadding = padding.left;
2481 mSelectionTopPadding = padding.top;
2482 mSelectionRightPadding = padding.right;
2483 mSelectionBottomPadding = padding.bottom;
2484 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002485 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002486 }
2487
2488 /**
2489 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2490 * selection in the list.
2491 *
2492 * @return the drawable used to display the selector
2493 */
2494 public Drawable getSelector() {
2495 return mSelector;
2496 }
2497
2498 /**
2499 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2500 * this is a long press.
2501 */
2502 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002503 if (!isEnabled() || !isClickable()) {
2504 return;
2505 }
2506
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002507 Drawable selector = mSelector;
2508 Rect selectorRect = mSelectorRect;
2509 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002510 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002511
2512 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2513
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002514 if (v != null) {
2515 if (v.hasFocusable()) return;
2516 v.setPressed(true);
2517 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002518 setPressed(true);
2519
2520 final boolean longClickable = isLongClickable();
2521 Drawable d = selector.getCurrent();
2522 if (d != null && d instanceof TransitionDrawable) {
2523 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002524 ((TransitionDrawable) d).startTransition(
2525 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002526 } else {
2527 ((TransitionDrawable) d).resetTransition();
2528 }
2529 }
2530 if (longClickable && !mDataChanged) {
2531 if (mPendingCheckForKeyLongPress == null) {
2532 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2533 }
2534 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2535 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2536 }
2537 }
2538 }
2539
2540 public void setScrollIndicators(View up, View down) {
2541 mScrollUp = up;
2542 mScrollDown = down;
2543 }
2544
Dianne Hackborn079e2352010-10-18 17:02:43 -07002545 void updateSelectorState() {
2546 if (mSelector != null) {
2547 if (shouldShowSelector()) {
2548 mSelector.setState(getDrawableState());
2549 } else {
2550 mSelector.setState(StateSet.NOTHING);
2551 }
2552 }
2553 }
2554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002555 @Override
2556 protected void drawableStateChanged() {
2557 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002558 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002559 }
2560
2561 @Override
2562 protected int[] onCreateDrawableState(int extraSpace) {
2563 // If the child view is enabled then do the default behavior.
2564 if (mIsChildViewEnabled) {
2565 // Common case
2566 return super.onCreateDrawableState(extraSpace);
2567 }
2568
2569 // The selector uses this View's drawable state. The selected child view
2570 // is disabled, so we need to remove the enabled state from the drawable
2571 // states.
2572 final int enabledState = ENABLED_STATE_SET[0];
2573
2574 // If we don't have any extra space, it will return one of the static state arrays,
2575 // and clearing the enabled state on those arrays is a bad thing! If we specify
2576 // we need extra space, it will create+copy into a new array that safely mutable.
2577 int[] state = super.onCreateDrawableState(extraSpace + 1);
2578 int enabledPos = -1;
2579 for (int i = state.length - 1; i >= 0; i--) {
2580 if (state[i] == enabledState) {
2581 enabledPos = i;
2582 break;
2583 }
2584 }
2585
2586 // Remove the enabled state
2587 if (enabledPos >= 0) {
2588 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2589 state.length - enabledPos - 1);
2590 }
Romain Guy0a637162009-05-29 14:43:54 -07002591
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002592 return state;
2593 }
2594
2595 @Override
2596 public boolean verifyDrawable(Drawable dr) {
2597 return mSelector == dr || super.verifyDrawable(dr);
2598 }
2599
2600 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002601 public void jumpDrawablesToCurrentState() {
2602 super.jumpDrawablesToCurrentState();
2603 if (mSelector != null) mSelector.jumpToCurrentState();
2604 }
2605
2606 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002607 protected void onAttachedToWindow() {
2608 super.onAttachedToWindow();
2609
2610 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002611 treeObserver.addOnTouchModeChangeListener(this);
2612 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2613 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002614 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002615
Romain Guy82afc7b2010-05-13 11:52:37 -07002616 if (mAdapter != null && mDataSetObserver == null) {
2617 mDataSetObserver = new AdapterDataSetObserver();
2618 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002619
2620 // Data may have changed while we were detached. Refresh.
2621 mDataChanged = true;
2622 mOldItemCount = mItemCount;
2623 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002624 }
Adam Powellb3750132011-08-08 23:29:12 -07002625 mIsAttached = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002626 }
2627
2628 @Override
2629 protected void onDetachedFromWindow() {
2630 super.onDetachedFromWindow();
2631
Romain Guy1f7f3c32009-07-22 11:25:42 -07002632 // Dismiss the popup in case onSaveInstanceState() was not invoked
2633 dismissPopup();
2634
Romain Guy21875052010-01-06 18:48:08 -08002635 // Detach any view left in the scrap heap
2636 mRecycler.clear();
2637
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002638 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002639 treeObserver.removeOnTouchModeChangeListener(this);
2640 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002641 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002642 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002643 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002644
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002645 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002646 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2647 mDataSetObserver = null;
2648 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002649
2650 if (mScrollStrictSpan != null) {
2651 mScrollStrictSpan.finish();
2652 mScrollStrictSpan = null;
2653 }
2654
2655 if (mFlingStrictSpan != null) {
2656 mFlingStrictSpan.finish();
2657 mFlingStrictSpan = null;
2658 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002659
2660 if (mFlingRunnable != null) {
2661 removeCallbacks(mFlingRunnable);
2662 }
2663
2664 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002665 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002666 }
2667
2668 if (mClearScrollingCache != null) {
2669 removeCallbacks(mClearScrollingCache);
2670 }
2671
2672 if (mPerformClick != null) {
2673 removeCallbacks(mPerformClick);
2674 }
2675
2676 if (mTouchModeReset != null) {
2677 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002678 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002679 }
Adam Powellb3750132011-08-08 23:29:12 -07002680 mIsAttached = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002681 }
2682
2683 @Override
2684 public void onWindowFocusChanged(boolean hasWindowFocus) {
2685 super.onWindowFocusChanged(hasWindowFocus);
2686
2687 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2688
2689 if (!hasWindowFocus) {
2690 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002691 if (mFlingRunnable != null) {
2692 removeCallbacks(mFlingRunnable);
2693 // let the fling runnable report it's new state which
2694 // should be idle
2695 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002696 if (mPositionScroller != null) {
2697 mPositionScroller.stop();
2698 }
Adam Powell45803472010-01-25 15:10:44 -08002699 if (mScrollY != 0) {
2700 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002701 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002702 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002703 invalidate();
2704 }
Mark Wagner670dd812010-01-13 16:17:47 -08002705 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002706 // Always hide the type filter
2707 dismissPopup();
2708
2709 if (touchMode == TOUCH_MODE_OFF) {
2710 // Remember the last selected element
2711 mResurrectToPosition = mSelectedPosition;
2712 }
2713 } else {
Adam Powell97566042010-03-09 15:34:09 -08002714 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002715 // Show the type filter only if a filter is in effect
2716 showPopup();
2717 }
2718
2719 // If we changed touch mode since the last time we had focus
2720 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2721 // If we come back in trackball mode, we bring the selection back
2722 if (touchMode == TOUCH_MODE_OFF) {
2723 // This will trigger a layout
2724 resurrectSelection();
2725
2726 // If we come back in touch mode, then we want to hide the selector
2727 } else {
2728 hideSelector();
2729 mLayoutMode = LAYOUT_NORMAL;
2730 layoutChildren();
2731 }
2732 }
2733 }
2734
2735 mLastTouchMode = touchMode;
2736 }
2737
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07002738 @Override
2739 public void onRtlPropertiesChanged(int layoutDirection) {
2740 super.onRtlPropertiesChanged(layoutDirection);
2741
2742 if (mFastScroller != null) {
2743 mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition());
2744 }
2745 }
2746
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002747 /**
2748 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
2749 * methods knows the view, position and ID of the item that received the
2750 * long press.
2751 *
2752 * @param view The view that received the long press.
2753 * @param position The position of the item that received the long press.
2754 * @param id The ID of the item that received the long press.
2755 * @return The extra information that should be returned by
2756 * {@link #getContextMenuInfo()}.
2757 */
2758 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
2759 return new AdapterContextMenuInfo(view, position, id);
2760 }
2761
2762 /**
2763 * A base class for Runnables that will check that their view is still attached to
2764 * the original window as when the Runnable was created.
2765 *
2766 */
2767 private class WindowRunnnable {
2768 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07002769
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002770 public void rememberWindowAttachCount() {
2771 mOriginalAttachCount = getWindowAttachCount();
2772 }
Romain Guy0a637162009-05-29 14:43:54 -07002773
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002774 public boolean sameWindow() {
Craig Mautner29219d92013-04-16 20:19:12 -07002775 return getWindowAttachCount() == mOriginalAttachCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002776 }
2777 }
Romain Guy0a637162009-05-29 14:43:54 -07002778
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002779 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002780 int mClickMotionPosition;
2781
Alan Viverette8fa327a2013-05-31 14:53:13 -07002782 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002783 public void run() {
2784 // The data has changed since we posted this action in the event queue,
2785 // bail out before bad things happen
2786 if (mDataChanged) return;
2787
Adam Powell005c0a42010-03-30 16:26:36 -07002788 final ListAdapter adapter = mAdapter;
2789 final int motionPosition = mClickMotionPosition;
2790 if (adapter != null && mItemCount > 0 &&
2791 motionPosition != INVALID_POSITION &&
2792 motionPosition < adapter.getCount() && sameWindow()) {
Romain Guy7890fe22011-01-18 20:24:18 -08002793 final View view = getChildAt(motionPosition - mFirstPosition);
2794 // If there is no view, something bad happened (the view scrolled off the
2795 // screen, etc.) and we should cancel the click
2796 if (view != null) {
2797 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2798 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002799 }
2800 }
2801 }
2802
2803 private class CheckForLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07002804 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002805 public void run() {
2806 final int motionPosition = mMotionPosition;
2807 final View child = getChildAt(motionPosition - mFirstPosition);
2808 if (child != null) {
2809 final int longPressPosition = mMotionPosition;
2810 final long longPressId = mAdapter.getItemId(mMotionPosition);
2811
2812 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07002813 if (sameWindow() && !mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002814 handled = performLongPress(child, longPressPosition, longPressId);
2815 }
2816 if (handled) {
2817 mTouchMode = TOUCH_MODE_REST;
2818 setPressed(false);
2819 child.setPressed(false);
2820 } else {
2821 mTouchMode = TOUCH_MODE_DONE_WAITING;
2822 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002823 }
2824 }
2825 }
Romain Guy0a637162009-05-29 14:43:54 -07002826
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002827 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07002828 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002829 public void run() {
2830 if (isPressed() && mSelectedPosition >= 0) {
2831 int index = mSelectedPosition - mFirstPosition;
2832 View v = getChildAt(index);
2833
2834 if (!mDataChanged) {
2835 boolean handled = false;
2836 if (sameWindow()) {
2837 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2838 }
2839 if (handled) {
2840 setPressed(false);
2841 v.setPressed(false);
2842 }
2843 } else {
2844 setPressed(false);
2845 if (v != null) v.setPressed(false);
2846 }
2847 }
2848 }
2849 }
2850
Adam Powell8350f7d2010-07-28 14:27:28 -07002851 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002852 final int longPressPosition, final long longPressId) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002853 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2854 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07002855 if (mChoiceActionMode == null &&
2856 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002857 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07002858 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07002859 }
Adam Powellf343e1b2010-08-13 18:27:04 -07002860 return true;
2861 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002862
Adam Powellf343e1b2010-08-13 18:27:04 -07002863 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002864 if (mOnItemLongClickListener != null) {
2865 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2866 longPressPosition, longPressId);
2867 }
2868 if (!handled) {
2869 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2870 handled = super.showContextMenuForChild(AbsListView.this);
2871 }
2872 if (handled) {
2873 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2874 }
2875 return handled;
2876 }
2877
2878 @Override
2879 protected ContextMenuInfo getContextMenuInfo() {
2880 return mContextMenuInfo;
2881 }
2882
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07002883 /** @hide */
2884 @Override
2885 public boolean showContextMenu(float x, float y, int metaState) {
2886 final int position = pointToPosition((int)x, (int)y);
2887 if (position != INVALID_POSITION) {
2888 final long id = mAdapter.getItemId(position);
2889 View child = getChildAt(position - mFirstPosition);
2890 if (child != null) {
2891 mContextMenuInfo = createContextMenuInfo(child, position, id);
2892 return super.showContextMenuForChild(AbsListView.this);
2893 }
2894 }
2895 return super.showContextMenu(x, y, metaState);
2896 }
2897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002898 @Override
2899 public boolean showContextMenuForChild(View originalView) {
2900 final int longPressPosition = getPositionForView(originalView);
2901 if (longPressPosition >= 0) {
2902 final long longPressId = mAdapter.getItemId(longPressPosition);
2903 boolean handled = false;
2904
2905 if (mOnItemLongClickListener != null) {
2906 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
2907 longPressPosition, longPressId);
2908 }
2909 if (!handled) {
2910 mContextMenuInfo = createContextMenuInfo(
2911 getChildAt(longPressPosition - mFirstPosition),
2912 longPressPosition, longPressId);
2913 handled = super.showContextMenuForChild(originalView);
2914 }
2915
2916 return handled;
2917 }
2918 return false;
2919 }
2920
2921 @Override
Romain Guydf016072009-08-17 12:51:30 -07002922 public boolean onKeyDown(int keyCode, KeyEvent event) {
2923 return false;
2924 }
2925
2926 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 public boolean onKeyUp(int keyCode, KeyEvent event) {
2928 switch (keyCode) {
2929 case KeyEvent.KEYCODE_DPAD_CENTER:
2930 case KeyEvent.KEYCODE_ENTER:
Romain Guydd753ae2009-08-17 10:36:23 -07002931 if (!isEnabled()) {
2932 return true;
2933 }
Romain Guydf016072009-08-17 12:51:30 -07002934 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07002935 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002936 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07002937
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002938 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07002939 if (view != null) {
2940 performItemClick(view, mSelectedPosition, mSelectedRowId);
2941 view.setPressed(false);
2942 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002943 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002944 return true;
2945 }
Romain Guydd753ae2009-08-17 10:36:23 -07002946 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002947 }
2948 return super.onKeyUp(keyCode, event);
2949 }
2950
2951 @Override
2952 protected void dispatchSetPressed(boolean pressed) {
2953 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
2954 // get the selector in the right state, but we don't want to press each child.
2955 }
2956
2957 /**
2958 * Maps a point to a position in the list.
2959 *
2960 * @param x X in local coordinate
2961 * @param y Y in local coordinate
2962 * @return The position of the item which contains the specified point, or
2963 * {@link #INVALID_POSITION} if the point does not intersect an item.
2964 */
2965 public int pointToPosition(int x, int y) {
2966 Rect frame = mTouchFrame;
2967 if (frame == null) {
2968 mTouchFrame = new Rect();
2969 frame = mTouchFrame;
2970 }
2971
2972 final int count = getChildCount();
2973 for (int i = count - 1; i >= 0; i--) {
2974 final View child = getChildAt(i);
2975 if (child.getVisibility() == View.VISIBLE) {
2976 child.getHitRect(frame);
2977 if (frame.contains(x, y)) {
2978 return mFirstPosition + i;
2979 }
2980 }
2981 }
2982 return INVALID_POSITION;
2983 }
2984
2985
2986 /**
2987 * Maps a point to a the rowId of the item which intersects that point.
2988 *
2989 * @param x X in local coordinate
2990 * @param y Y in local coordinate
2991 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
2992 * if the point does not intersect an item.
2993 */
2994 public long pointToRowId(int x, int y) {
2995 int position = pointToPosition(x, y);
2996 if (position >= 0) {
2997 return mAdapter.getItemId(position);
2998 }
2999 return INVALID_ROW_ID;
3000 }
3001
3002 final class CheckForTap implements Runnable {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003003 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003004 public void run() {
3005 if (mTouchMode == TOUCH_MODE_DOWN) {
3006 mTouchMode = TOUCH_MODE_TAP;
3007 final View child = getChildAt(mMotionPosition - mFirstPosition);
3008 if (child != null && !child.hasFocusable()) {
3009 mLayoutMode = LAYOUT_NORMAL;
3010
3011 if (!mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003012 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003013 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003014 layoutChildren();
3015 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003016 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003017
3018 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3019 final boolean longClickable = isLongClickable();
3020
3021 if (mSelector != null) {
3022 Drawable d = mSelector.getCurrent();
3023 if (d != null && d instanceof TransitionDrawable) {
3024 if (longClickable) {
3025 ((TransitionDrawable) d).startTransition(longPressTimeout);
3026 } else {
3027 ((TransitionDrawable) d).resetTransition();
3028 }
3029 }
3030 }
3031
3032 if (longClickable) {
3033 if (mPendingCheckForLongPress == null) {
3034 mPendingCheckForLongPress = new CheckForLongPress();
3035 }
3036 mPendingCheckForLongPress.rememberWindowAttachCount();
3037 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3038 } else {
3039 mTouchMode = TOUCH_MODE_DONE_WAITING;
3040 }
3041 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003042 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003043 }
3044 }
3045 }
3046 }
3047 }
3048
Jeff Brown78f6e632011-09-09 17:15:31 -07003049 private boolean startScrollIfNeeded(int y) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003050 // Check if we have moved far enough that it looks more like a
3051 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003052 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003053 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003054 final boolean overscroll = mScrollY != 0;
3055 if (overscroll || distance > mTouchSlop) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003056 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003057 if (overscroll) {
3058 mTouchMode = TOUCH_MODE_OVERSCROLL;
3059 mMotionCorrection = 0;
3060 } else {
3061 mTouchMode = TOUCH_MODE_SCROLL;
3062 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3063 }
Alan Viverette74ded292013-06-03 15:34:11 -07003064 removeCallbacks(mPendingCheckForLongPress);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003065 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003066 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003067 if (motionView != null) {
3068 motionView.setPressed(false);
3069 }
3070 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3071 // Time to start stealing events! Once we've stolen them, don't let anyone
3072 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003073 final ViewParent parent = getParent();
3074 if (parent != null) {
3075 parent.requestDisallowInterceptTouchEvent(true);
3076 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003077 scrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003078 return true;
3079 }
3080
3081 return false;
3082 }
3083
Jeff Brown78f6e632011-09-09 17:15:31 -07003084 private void scrollIfNeeded(int y) {
3085 final int rawDeltaY = y - mMotionY;
3086 final int deltaY = rawDeltaY - mMotionCorrection;
3087 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3088
3089 if (mTouchMode == TOUCH_MODE_SCROLL) {
3090 if (PROFILE_SCROLLING) {
3091 if (!mScrollProfilingStarted) {
3092 Debug.startMethodTracing("AbsListViewScroll");
3093 mScrollProfilingStarted = true;
3094 }
3095 }
3096
3097 if (mScrollStrictSpan == null) {
3098 // If it's non-null, we're already in a scroll.
3099 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3100 }
3101
3102 if (y != mLastY) {
3103 // We may be here after stopping a fling and continuing to scroll.
3104 // If so, we haven't disallowed intercepting touch events yet.
3105 // Make sure that we do so in case we're in a parent that can intercept.
3106 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3107 Math.abs(rawDeltaY) > mTouchSlop) {
3108 final ViewParent parent = getParent();
3109 if (parent != null) {
3110 parent.requestDisallowInterceptTouchEvent(true);
3111 }
3112 }
3113
3114 final int motionIndex;
3115 if (mMotionPosition >= 0) {
3116 motionIndex = mMotionPosition - mFirstPosition;
3117 } else {
3118 // If we don't have a motion position that we can reliably track,
3119 // pick something in the middle to make a best guess at things below.
3120 motionIndex = getChildCount() / 2;
3121 }
3122
3123 int motionViewPrevTop = 0;
3124 View motionView = this.getChildAt(motionIndex);
3125 if (motionView != null) {
3126 motionViewPrevTop = motionView.getTop();
3127 }
3128
3129 // No need to do all this work if we're not going to move anyway
3130 boolean atEdge = false;
3131 if (incrementalDeltaY != 0) {
3132 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3133 }
3134
3135 // Check to see if we have bumped into the scroll limit
3136 motionView = this.getChildAt(motionIndex);
3137 if (motionView != null) {
3138 // Check if the top of the motion view is where it is
3139 // supposed to be
3140 final int motionViewRealTop = motionView.getTop();
3141 if (atEdge) {
3142 // Apply overscroll
3143
3144 int overscroll = -incrementalDeltaY -
3145 (motionViewRealTop - motionViewPrevTop);
3146 overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3147 0, mOverscrollDistance, true);
3148 if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3149 // Don't allow overfling if we're at the edge.
3150 if (mVelocityTracker != null) {
3151 mVelocityTracker.clear();
3152 }
3153 }
3154
3155 final int overscrollMode = getOverScrollMode();
3156 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3157 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3158 !contentFits())) {
3159 mDirection = 0; // Reset when entering overscroll.
3160 mTouchMode = TOUCH_MODE_OVERSCROLL;
3161 if (rawDeltaY > 0) {
3162 mEdgeGlowTop.onPull((float) overscroll / getHeight());
3163 if (!mEdgeGlowBottom.isFinished()) {
3164 mEdgeGlowBottom.onRelease();
3165 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003166 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003167 } else if (rawDeltaY < 0) {
3168 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3169 if (!mEdgeGlowTop.isFinished()) {
3170 mEdgeGlowTop.onRelease();
3171 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003172 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003173 }
3174 }
3175 }
3176 mMotionY = y;
Jeff Brown78f6e632011-09-09 17:15:31 -07003177 }
3178 mLastY = y;
3179 }
3180 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3181 if (y != mLastY) {
3182 final int oldScroll = mScrollY;
3183 final int newScroll = oldScroll - incrementalDeltaY;
3184 int newDirection = y > mLastY ? 1 : -1;
3185
3186 if (mDirection == 0) {
3187 mDirection = newDirection;
3188 }
3189
3190 int overScrollDistance = -incrementalDeltaY;
3191 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3192 overScrollDistance = -oldScroll;
3193 incrementalDeltaY += overScrollDistance;
3194 } else {
3195 incrementalDeltaY = 0;
3196 }
3197
3198 if (overScrollDistance != 0) {
3199 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3200 0, mOverscrollDistance, true);
3201 final int overscrollMode = getOverScrollMode();
3202 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3203 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3204 !contentFits())) {
3205 if (rawDeltaY > 0) {
3206 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3207 if (!mEdgeGlowBottom.isFinished()) {
3208 mEdgeGlowBottom.onRelease();
3209 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003210 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003211 } else if (rawDeltaY < 0) {
3212 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3213 if (!mEdgeGlowTop.isFinished()) {
3214 mEdgeGlowTop.onRelease();
3215 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003216 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003217 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003218 }
3219 }
3220
3221 if (incrementalDeltaY != 0) {
3222 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003223 if (mScrollY != 0) {
3224 mScrollY = 0;
3225 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003226 }
3227
Romain Guy9d849a22012-03-14 16:41:42 -07003228 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3229
Jeff Brown78f6e632011-09-09 17:15:31 -07003230 mTouchMode = TOUCH_MODE_SCROLL;
3231
3232 // We did not scroll the full amount. Treat this essentially like the
3233 // start of a new touch scroll
3234 final int motionPosition = findClosestMotionRow(y);
3235
3236 mMotionCorrection = 0;
3237 View motionView = getChildAt(motionPosition - mFirstPosition);
3238 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3239 mMotionY = y;
3240 mMotionPosition = motionPosition;
3241 }
3242 mLastY = y;
3243 mDirection = newDirection;
3244 }
3245 }
3246 }
3247
Alan Viverette8fa327a2013-05-31 14:53:13 -07003248 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003249 public void onTouchModeChanged(boolean isInTouchMode) {
3250 if (isInTouchMode) {
3251 // Get rid of the selection when we enter touch mode
3252 hideSelector();
3253 // Layout, but only if we already have done so previously.
3254 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3255 // state.)
3256 if (getHeight() > 0 && getChildCount() > 0) {
3257 // We do not lose focus initiating a touch (since AbsListView is focusable in
3258 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003259 layoutChildren();
3260 }
Jeff Brown1e209462011-07-14 22:19:19 -07003261 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003262 } else {
3263 int touchMode = mTouchMode;
3264 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3265 if (mFlingRunnable != null) {
3266 mFlingRunnable.endFling();
3267 }
Adam Powell40322522011-01-12 21:58:20 -08003268 if (mPositionScroller != null) {
3269 mPositionScroller.stop();
3270 }
Adam Powell637d3372010-08-25 14:37:03 -07003271
3272 if (mScrollY != 0) {
3273 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003274 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003275 finishGlows();
3276 invalidate();
3277 }
3278 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003279 }
3280 }
3281
3282 @Override
3283 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003284 if (!isEnabled()) {
3285 // A disabled view that is clickable still consumes the touch
3286 // events, it just doesn't respond to them.
3287 return isClickable() || isLongClickable();
3288 }
3289
Adam Powell1fa179ef2012-04-12 15:01:40 -07003290 if (mPositionScroller != null) {
3291 mPositionScroller.stop();
3292 }
3293
Adam Powell28048d02012-06-06 22:46:42 -07003294 if (!mIsAttached) {
3295 // Something isn't right.
3296 // Since we rely on being attached to get data set change notifications,
3297 // don't risk doing anything where we might try to resync and find things
3298 // in a bogus state.
3299 return false;
3300 }
3301
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003302 if (mFastScroller != null) {
3303 boolean intercepted = mFastScroller.onTouchEvent(ev);
3304 if (intercepted) {
3305 return true;
Romain Guy0a637162009-05-29 14:43:54 -07003306 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003307 }
Romain Guy82f34952009-05-24 18:40:45 -07003308
Michael Jurka13451a42011-08-22 15:54:21 -07003309 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003310 mVelocityTracker.addMovement(ev);
3311
Alan Viverette8fa327a2013-05-31 14:53:13 -07003312 final int actionMasked = ev.getActionMasked();
3313 switch (actionMasked) {
3314 case MotionEvent.ACTION_DOWN: {
3315 onTouchDown(ev);
3316 break;
Adam Powell4cd47702010-02-25 11:21:14 -08003317 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003318
Alan Viverette8fa327a2013-05-31 14:53:13 -07003319 case MotionEvent.ACTION_MOVE: {
3320 onTouchMove(ev);
3321 break;
Adam Powell9bc30d32011-02-28 10:27:49 -08003322 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003323
3324 case MotionEvent.ACTION_UP: {
3325 onTouchUp(ev);
3326 break;
3327 }
3328
3329 case MotionEvent.ACTION_CANCEL: {
3330 onTouchCancel();
3331 break;
3332 }
3333
3334 case MotionEvent.ACTION_POINTER_UP: {
3335 onSecondaryPointerUp(ev);
3336 final int x = mMotionX;
3337 final int y = mMotionY;
3338 final int motionPosition = pointToPosition(x, y);
3339 if (motionPosition >= 0) {
3340 // Remember where the motion event started
3341 final View child = getChildAt(motionPosition - mFirstPosition);
3342 mMotionViewOriginalTop = child.getTop();
3343 mMotionPosition = motionPosition;
3344 }
3345 mLastY = y;
3346 break;
3347 }
3348
3349 case MotionEvent.ACTION_POINTER_DOWN: {
3350 // New pointers take over dragging duties
3351 final int index = ev.getActionIndex();
3352 final int id = ev.getPointerId(index);
3353 final int x = (int) ev.getX(index);
3354 final int y = (int) ev.getY(index);
3355 mMotionCorrection = 0;
3356 mActivePointerId = id;
3357 mMotionX = x;
3358 mMotionY = y;
3359 final int motionPosition = pointToPosition(x, y);
3360 if (motionPosition >= 0) {
3361 // Remember where the motion event started
3362 final View child = getChildAt(motionPosition - mFirstPosition);
3363 mMotionViewOriginalTop = child.getTop();
3364 mMotionPosition = motionPosition;
3365 }
3366 mLastY = y;
3367 break;
3368 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003369 }
3370
3371 return true;
3372 }
Romain Guy0a637162009-05-29 14:43:54 -07003373
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003374 private void onTouchDown(MotionEvent ev) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003375 mActivePointerId = ev.getPointerId(0);
3376
3377 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3378 // Stopped the fling. It is a scroll.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003379 mFlingRunnable.endFling();
3380 if (mPositionScroller != null) {
3381 mPositionScroller.stop();
3382 }
3383 mTouchMode = TOUCH_MODE_OVERSCROLL;
3384 mMotionX = (int) ev.getX();
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003385 mMotionY = (int) ev.getY();
3386 mLastY = mMotionY;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003387 mMotionCorrection = 0;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003388 mDirection = 0;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003389 } else {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003390 final int x = (int) ev.getX();
3391 final int y = (int) ev.getY();
3392 int motionPosition = pointToPosition(x, y);
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003393
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003394 if (!mDataChanged) {
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003395 if (mTouchMode == TOUCH_MODE_FLING) {
3396 // Stopped a fling. It is a scroll.
3397 createScrollingCache();
3398 mTouchMode = TOUCH_MODE_SCROLL;
3399 mMotionCorrection = 0;
3400 motionPosition = findMotionRow(y);
3401 mFlingRunnable.flywheelTouch();
3402 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3403 // User clicked on an actual view (and was not stopping a
3404 // fling). It might be a click or a scroll. Assume it is a
3405 // click until proven otherwise.
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003406 mTouchMode = TOUCH_MODE_DOWN;
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003407
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003408 // FIXME Debounce
3409 if (mPendingCheckForTap == null) {
3410 mPendingCheckForTap = new CheckForTap();
3411 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003412
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003413 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003414 }
3415 }
3416
3417 if (motionPosition >= 0) {
3418 // Remember where the motion event started
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003419 final View v = getChildAt(motionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003420 mMotionViewOriginalTop = v.getTop();
3421 }
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003422
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003423 mMotionX = x;
3424 mMotionY = y;
3425 mMotionPosition = motionPosition;
3426 mLastY = Integer.MIN_VALUE;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003427 }
3428
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003429 if (performButtonActionOnTouchDown(ev) && (mTouchMode == TOUCH_MODE_DOWN)) {
3430 removeCallbacks(mPendingCheckForTap);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003431 }
3432 }
3433
3434 private void onTouchMove(MotionEvent ev) {
3435 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3436 if (pointerIndex == -1) {
3437 pointerIndex = 0;
3438 mActivePointerId = ev.getPointerId(pointerIndex);
3439 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003440
3441 if (mDataChanged) {
3442 // Re-sync everything if data has been changed
3443 // since the scroll operation can query the adapter.
3444 layoutChildren();
3445 }
3446
Alan Viverette8fa327a2013-05-31 14:53:13 -07003447 final int y = (int) ev.getY(pointerIndex);
3448
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003449 switch (mTouchMode) {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003450 case TOUCH_MODE_DOWN:
3451 case TOUCH_MODE_TAP:
3452 case TOUCH_MODE_DONE_WAITING:
3453 // Check if we have moved far enough that it looks more like a
Alan Viverette74ded292013-06-03 15:34:11 -07003454 // scroll than a tap. If so, we'll enter scrolling mode.
3455 if (startScrollIfNeeded(y)) {
3456 break;
3457 }
3458 // Otherwise, check containment within list bounds. If we're
3459 // outside bounds, cancel any active presses.
Alan Viverette3d15a2b2013-06-25 17:27:37 -07003460 final float x = ev.getX(pointerIndex);
3461 if (!pointInView(x, y, mTouchSlop)) {
Alan Viverette74ded292013-06-03 15:34:11 -07003462 setPressed(false);
3463 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3464 if (motionView != null) {
3465 motionView.setPressed(false);
3466 }
3467 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3468 mPendingCheckForTap : mPendingCheckForLongPress);
3469 mTouchMode = TOUCH_MODE_DONE_WAITING;
3470 updateSelectorState();
3471 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07003472 break;
3473 case TOUCH_MODE_SCROLL:
3474 case TOUCH_MODE_OVERSCROLL:
3475 scrollIfNeeded(y);
3476 break;
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003477 }
3478 }
3479
3480 private void onTouchUp(MotionEvent ev) {
3481 switch (mTouchMode) {
3482 case TOUCH_MODE_DOWN:
3483 case TOUCH_MODE_TAP:
3484 case TOUCH_MODE_DONE_WAITING:
3485 final int motionPosition = mMotionPosition;
3486 final View child = getChildAt(motionPosition - mFirstPosition);
Alan Viverette74ded292013-06-03 15:34:11 -07003487 if (child != null) {
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003488 if (mTouchMode != TOUCH_MODE_DOWN) {
3489 child.setPressed(false);
3490 }
3491
Alan Viverette74ded292013-06-03 15:34:11 -07003492 final float x = ev.getX();
3493 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3494 if (inList && !child.hasFocusable()) {
3495 if (mPerformClick == null) {
3496 mPerformClick = new PerformClick();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003497 }
Alan Viverette74ded292013-06-03 15:34:11 -07003498
3499 final AbsListView.PerformClick performClick = mPerformClick;
3500 performClick.mClickMotionPosition = motionPosition;
3501 performClick.rememberWindowAttachCount();
3502
3503 mResurrectToPosition = motionPosition;
3504
3505 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3506 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3507 mPendingCheckForTap : mPendingCheckForLongPress);
3508 mLayoutMode = LAYOUT_NORMAL;
3509 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3510 mTouchMode = TOUCH_MODE_TAP;
3511 setSelectedPositionInt(mMotionPosition);
3512 layoutChildren();
3513 child.setPressed(true);
3514 positionSelector(mMotionPosition, child);
3515 setPressed(true);
3516 if (mSelector != null) {
3517 Drawable d = mSelector.getCurrent();
3518 if (d != null && d instanceof TransitionDrawable) {
3519 ((TransitionDrawable) d).resetTransition();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003520 }
3521 }
Alan Viverette74ded292013-06-03 15:34:11 -07003522 if (mTouchModeReset != null) {
3523 removeCallbacks(mTouchModeReset);
3524 }
3525 mTouchModeReset = new Runnable() {
3526 @Override
3527 public void run() {
3528 mTouchModeReset = null;
3529 mTouchMode = TOUCH_MODE_REST;
3530 child.setPressed(false);
3531 setPressed(false);
3532 if (!mDataChanged) {
3533 performClick.run();
3534 }
3535 }
3536 };
3537 postDelayed(mTouchModeReset,
3538 ViewConfiguration.getPressedStateDuration());
3539 } else {
3540 mTouchMode = TOUCH_MODE_REST;
3541 updateSelectorState();
3542 }
3543 return;
3544 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3545 performClick.run();
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003546 }
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003547 }
3548 }
3549 mTouchMode = TOUCH_MODE_REST;
3550 updateSelectorState();
3551 break;
3552 case TOUCH_MODE_SCROLL:
3553 final int childCount = getChildCount();
3554 if (childCount > 0) {
3555 final int firstChildTop = getChildAt(0).getTop();
3556 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3557 final int contentTop = mListPadding.top;
3558 final int contentBottom = getHeight() - mListPadding.bottom;
3559 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
3560 mFirstPosition + childCount < mItemCount &&
3561 lastChildBottom <= getHeight() - contentBottom) {
3562 mTouchMode = TOUCH_MODE_REST;
3563 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3564 } else {
3565 final VelocityTracker velocityTracker = mVelocityTracker;
3566 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3567
3568 final int initialVelocity = (int)
3569 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
3570 // Fling if we have enough velocity and we aren't at a boundary.
3571 // Since we can potentially overfling more than we can overscroll, don't
3572 // allow the weird behavior where you can scroll to a boundary then
3573 // fling further.
3574 if (Math.abs(initialVelocity) > mMinimumVelocity &&
3575 !((mFirstPosition == 0 &&
3576 firstChildTop == contentTop - mOverscrollDistance) ||
3577 (mFirstPosition + childCount == mItemCount &&
3578 lastChildBottom == contentBottom + mOverscrollDistance))) {
3579 if (mFlingRunnable == null) {
3580 mFlingRunnable = new FlingRunnable();
3581 }
3582 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3583
3584 mFlingRunnable.start(-initialVelocity);
3585 } else {
3586 mTouchMode = TOUCH_MODE_REST;
3587 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3588 if (mFlingRunnable != null) {
3589 mFlingRunnable.endFling();
3590 }
3591 if (mPositionScroller != null) {
3592 mPositionScroller.stop();
3593 }
3594 }
3595 }
3596 } else {
3597 mTouchMode = TOUCH_MODE_REST;
3598 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3599 }
3600 break;
3601
3602 case TOUCH_MODE_OVERSCROLL:
3603 if (mFlingRunnable == null) {
3604 mFlingRunnable = new FlingRunnable();
3605 }
3606 final VelocityTracker velocityTracker = mVelocityTracker;
3607 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3608 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3609
3610 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3611 if (Math.abs(initialVelocity) > mMinimumVelocity) {
3612 mFlingRunnable.startOverfling(-initialVelocity);
3613 } else {
3614 mFlingRunnable.startSpringback();
3615 }
3616
3617 break;
3618 }
3619
3620 setPressed(false);
3621
3622 if (mEdgeGlowTop != null) {
3623 mEdgeGlowTop.onRelease();
3624 mEdgeGlowBottom.onRelease();
3625 }
3626
3627 // Need to redraw since we probably aren't drawing the selector anymore
3628 invalidate();
Alan Viverette74ded292013-06-03 15:34:11 -07003629 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003630 recycleVelocityTracker();
3631
3632 mActivePointerId = INVALID_POINTER;
3633
3634 if (PROFILE_SCROLLING) {
3635 if (mScrollProfilingStarted) {
3636 Debug.stopMethodTracing();
3637 mScrollProfilingStarted = false;
3638 }
3639 }
3640
3641 if (mScrollStrictSpan != null) {
3642 mScrollStrictSpan.finish();
3643 mScrollStrictSpan = null;
3644 }
3645 }
3646
3647 private void onTouchCancel() {
3648 switch (mTouchMode) {
3649 case TOUCH_MODE_OVERSCROLL:
3650 if (mFlingRunnable == null) {
3651 mFlingRunnable = new FlingRunnable();
3652 }
3653 mFlingRunnable.startSpringback();
3654 break;
3655
3656 case TOUCH_MODE_OVERFLING:
3657 // Do nothing - let it play out.
3658 break;
3659
3660 default:
3661 mTouchMode = TOUCH_MODE_REST;
3662 setPressed(false);
Alan Viverette74ded292013-06-03 15:34:11 -07003663 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003664 if (motionView != null) {
3665 motionView.setPressed(false);
3666 }
3667 clearScrollingCache();
Alan Viverette74ded292013-06-03 15:34:11 -07003668 removeCallbacks(mPendingCheckForLongPress);
Alan Viverettee7f30dc2013-05-31 14:14:51 -07003669 recycleVelocityTracker();
3670 }
3671
3672 if (mEdgeGlowTop != null) {
3673 mEdgeGlowTop.onRelease();
3674 mEdgeGlowBottom.onRelease();
3675 }
3676 mActivePointerId = INVALID_POINTER;
3677 }
3678
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003679 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003680 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3681 if (mScrollY != scrollY) {
3682 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3683 mScrollY = scrollY;
3684 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07003685
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003686 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07003687 }
Adam Powell637d3372010-08-25 14:37:03 -07003688 }
3689
3690 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08003691 public boolean onGenericMotionEvent(MotionEvent event) {
3692 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3693 switch (event.getAction()) {
3694 case MotionEvent.ACTION_SCROLL: {
3695 if (mTouchMode == TOUCH_MODE_REST) {
3696 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3697 if (vscroll != 0) {
3698 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08003699 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08003700 return true;
3701 }
3702 }
3703 }
3704 }
3705 }
3706 }
3707 return super.onGenericMotionEvent(event);
3708 }
3709
3710 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003711 public void draw(Canvas canvas) {
3712 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07003713 if (mEdgeGlowTop != null) {
3714 final int scrollY = mScrollY;
3715 if (!mEdgeGlowTop.isFinished()) {
3716 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003717 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3718 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3719 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003720
Romain Guy9d849a22012-03-14 16:41:42 -07003721 int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3722 canvas.translate(leftPadding, edgeY);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003723 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07003724 if (mEdgeGlowTop.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003725 mEdgeGlowTop.setPosition(leftPadding, edgeY);
Romain Guya8bfeaf2012-03-15 13:14:14 -07003726 invalidate(mEdgeGlowTop.getBounds(false));
Adam Powell637d3372010-08-25 14:37:03 -07003727 }
3728 canvas.restoreToCount(restoreCount);
3729 }
3730 if (!mEdgeGlowBottom.isFinished()) {
3731 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003732 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3733 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3734 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003735 final int height = getHeight();
3736
Romain Guy9d849a22012-03-14 16:41:42 -07003737 int edgeX = -width + leftPadding;
3738 int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3739 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08003740 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003741 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07003742 if (mEdgeGlowBottom.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003743 // Account for the rotation
Romain Guya8bfeaf2012-03-15 13:14:14 -07003744 mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
3745 invalidate(mEdgeGlowBottom.getBounds(true));
Adam Powell637d3372010-08-25 14:37:03 -07003746 }
3747 canvas.restoreToCount(restoreCount);
3748 }
3749 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003750 }
3751
Adam Powell07d6f7b2011-03-02 14:27:30 -08003752 /**
3753 * @hide
3754 */
3755 public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3756 mGlowPaddingLeft = leftPadding;
3757 mGlowPaddingRight = rightPadding;
3758 }
3759
Michael Jurka13451a42011-08-22 15:54:21 -07003760 private void initOrResetVelocityTracker() {
3761 if (mVelocityTracker == null) {
3762 mVelocityTracker = VelocityTracker.obtain();
3763 } else {
3764 mVelocityTracker.clear();
3765 }
3766 }
3767
3768 private void initVelocityTrackerIfNotExists() {
3769 if (mVelocityTracker == null) {
3770 mVelocityTracker = VelocityTracker.obtain();
3771 }
3772 }
3773
3774 private void recycleVelocityTracker() {
3775 if (mVelocityTracker != null) {
3776 mVelocityTracker.recycle();
3777 mVelocityTracker = null;
3778 }
3779 }
3780
3781 @Override
3782 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3783 if (disallowIntercept) {
3784 recycleVelocityTracker();
3785 }
3786 super.requestDisallowInterceptTouchEvent(disallowIntercept);
3787 }
3788
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003789 @Override
3790 public boolean onInterceptTouchEvent(MotionEvent ev) {
3791 int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003792 View v;
Romain Guy0a637162009-05-29 14:43:54 -07003793
Adam Powell1fa179ef2012-04-12 15:01:40 -07003794 if (mPositionScroller != null) {
3795 mPositionScroller.stop();
3796 }
3797
Adam Powell28048d02012-06-06 22:46:42 -07003798 if (!mIsAttached) {
3799 // Something isn't right.
3800 // Since we rely on being attached to get data set change notifications,
3801 // don't risk doing anything where we might try to resync and find things
3802 // in a bogus state.
3803 return false;
3804 }
3805
Alan Viverette0ebe81e2013-06-21 17:01:36 -07003806 if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
3807 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003808 }
Romain Guy0a637162009-05-29 14:43:54 -07003809
Adam Powell4cd47702010-02-25 11:21:14 -08003810 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003811 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08003812 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07003813 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3814 mMotionCorrection = 0;
3815 return true;
3816 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003817
Adam Powell4cd47702010-02-25 11:21:14 -08003818 final int x = (int) ev.getX();
3819 final int y = (int) ev.getY();
3820 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003821
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003822 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08003823 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003824 // User clicked on an actual view (and was not stopping a fling).
3825 // Remember where the motion event started
3826 v = getChildAt(motionPosition - mFirstPosition);
3827 mMotionViewOriginalTop = v.getTop();
3828 mMotionX = x;
3829 mMotionY = y;
3830 mMotionPosition = motionPosition;
3831 mTouchMode = TOUCH_MODE_DOWN;
3832 clearScrollingCache();
3833 }
3834 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07003835 initOrResetVelocityTracker();
3836 mVelocityTracker.addMovement(ev);
Adam Powell79ac3392010-01-28 21:22:20 -08003837 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08003838 return true;
3839 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003840 break;
3841 }
3842
3843 case MotionEvent.ACTION_MOVE: {
3844 switch (mTouchMode) {
3845 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07003846 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3847 if (pointerIndex == -1) {
3848 pointerIndex = 0;
3849 mActivePointerId = ev.getPointerId(pointerIndex);
3850 }
Adam Powell4cd47702010-02-25 11:21:14 -08003851 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07003852 initVelocityTrackerIfNotExists();
3853 mVelocityTracker.addMovement(ev);
Jeff Brown78f6e632011-09-09 17:15:31 -07003854 if (startScrollIfNeeded(y)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003855 return true;
3856 }
3857 break;
3858 }
3859 break;
3860 }
3861
Michael Jurka13451a42011-08-22 15:54:21 -07003862 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003863 case MotionEvent.ACTION_UP: {
3864 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08003865 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07003866 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003867 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3868 break;
3869 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003870
Adam Powell4cd47702010-02-25 11:21:14 -08003871 case MotionEvent.ACTION_POINTER_UP: {
3872 onSecondaryPointerUp(ev);
3873 break;
3874 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003875 }
3876
3877 return false;
3878 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003879
Adam Powell4cd47702010-02-25 11:21:14 -08003880 private void onSecondaryPointerUp(MotionEvent ev) {
3881 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3882 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3883 final int pointerId = ev.getPointerId(pointerIndex);
3884 if (pointerId == mActivePointerId) {
3885 // This was our active pointer going up. Choose a new
3886 // active pointer and adjust accordingly.
3887 // TODO: Make this decision more intelligent.
3888 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3889 mMotionX = (int) ev.getX(newPointerIndex);
3890 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07003891 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08003892 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08003893 }
3894 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003895
3896 /**
3897 * {@inheritDoc}
3898 */
3899 @Override
3900 public void addTouchables(ArrayList<View> views) {
3901 final int count = getChildCount();
3902 final int firstPosition = mFirstPosition;
3903 final ListAdapter adapter = mAdapter;
3904
3905 if (adapter == null) {
3906 return;
3907 }
3908
3909 for (int i = 0; i < count; i++) {
3910 final View child = getChildAt(i);
3911 if (adapter.isEnabled(firstPosition + i)) {
3912 views.add(child);
3913 }
3914 child.addTouchables(views);
3915 }
3916 }
3917
3918 /**
3919 * Fires an "on scroll state changed" event to the registered
3920 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
3921 * is fired only if the specified state is different from the previously known state.
3922 *
3923 * @param newState The new scroll state.
3924 */
3925 void reportScrollStateChange(int newState) {
3926 if (newState != mLastScrollState) {
3927 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003928 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08003929 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003930 }
3931 }
3932 }
3933
3934 /**
3935 * Responsible for fling behavior. Use {@link #start(int)} to
3936 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
3937 * A FlingRunnable will keep re-posting itself until the fling is done.
3938 *
3939 */
3940 private class FlingRunnable implements Runnable {
3941 /**
3942 * Tracks the decay of a fling scroll
3943 */
Adam Powell637d3372010-08-25 14:37:03 -07003944 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003945
3946 /**
3947 * Y value reported by mScroller on the previous fling
3948 */
3949 private int mLastFlingY;
3950
Gilles Debunned348bb42010-11-15 12:19:35 -08003951 private final Runnable mCheckFlywheel = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07003952 @Override
Gilles Debunned348bb42010-11-15 12:19:35 -08003953 public void run() {
3954 final int activeId = mActivePointerId;
3955 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07003956 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08003957 if (vt == null || activeId == INVALID_POINTER) {
3958 return;
3959 }
3960
3961 vt.computeCurrentVelocity(1000, mMaximumVelocity);
3962 final float yvel = -vt.getYVelocity(activeId);
3963
Jeff Brownb0c71eb2011-09-16 21:40:49 -07003964 if (Math.abs(yvel) >= mMinimumVelocity
3965 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08003966 // Keep the fling alive a little longer
3967 postDelayed(this, FLYWHEEL_TIMEOUT);
3968 } else {
3969 endFling();
3970 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08003971 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08003972 }
3973 }
3974 };
3975
3976 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
3977
Adam Powell79ac3392010-01-28 21:22:20 -08003978 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07003979 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003980 }
3981
Adam Powell79ac3392010-01-28 21:22:20 -08003982 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003983 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
3984 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07003985 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003986 mScroller.fling(0, initialY, 0, initialVelocity,
3987 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
3988 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07003989 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003990
3991 if (PROFILE_FLINGING) {
3992 if (!mFlingProfilingStarted) {
3993 Debug.startMethodTracing("AbsListViewFling");
3994 mFlingProfilingStarted = true;
3995 }
3996 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003997
3998 if (mFlingStrictSpan == null) {
3999 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4000 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004001 }
Adam Powell45803472010-01-25 15:10:44 -08004002
Adam Powell637d3372010-08-25 14:37:03 -07004003 void startSpringback() {
4004 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4005 mTouchMode = TOUCH_MODE_OVERFLING;
4006 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004007 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004008 } else {
4009 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004010 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004011 }
4012 }
4013
4014 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004015 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004016 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4017 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004018 mTouchMode = TOUCH_MODE_OVERFLING;
4019 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004020 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004021 }
4022
4023 void edgeReached(int delta) {
4024 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4025 final int overscrollMode = getOverScrollMode();
4026 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4027 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4028 mTouchMode = TOUCH_MODE_OVERFLING;
4029 final int vel = (int) mScroller.getCurrVelocity();
4030 if (delta > 0) {
4031 mEdgeGlowTop.onAbsorb(vel);
4032 } else {
4033 mEdgeGlowBottom.onAbsorb(vel);
4034 }
Adam Powell40322522011-01-12 21:58:20 -08004035 } else {
4036 mTouchMode = TOUCH_MODE_REST;
4037 if (mPositionScroller != null) {
4038 mPositionScroller.stop();
4039 }
Adam Powell637d3372010-08-25 14:37:03 -07004040 }
4041 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004042 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004043 }
4044
Adam Powell0b8acd82012-04-25 20:29:23 -07004045 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004046 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4047 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004048 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004049 mScroller.startScroll(0, initialY, 0, distance, duration);
4050 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004051 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004052 }
4053
Gilles Debunned348bb42010-11-15 12:19:35 -08004054 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004055 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004056
Adam Powell79ac3392010-01-28 21:22:20 -08004057 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004058 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004059
4060 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4061 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004062 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004063
4064 if (mFlingStrictSpan != null) {
4065 mFlingStrictSpan.finish();
4066 mFlingStrictSpan = null;
4067 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004068 }
4069
4070 void flywheelTouch() {
4071 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004072 }
4073
Alan Viverette8fa327a2013-05-31 14:53:13 -07004074 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004075 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004076 switch (mTouchMode) {
4077 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004078 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004079 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004080
Gilles Debunned348bb42010-11-15 12:19:35 -08004081 case TOUCH_MODE_SCROLL:
4082 if (mScroller.isFinished()) {
4083 return;
4084 }
4085 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004086 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004087 if (mDataChanged) {
4088 layoutChildren();
4089 }
4090
Adam Powell79ac3392010-01-28 21:22:20 -08004091 if (mItemCount == 0 || getChildCount() == 0) {
4092 endFling();
4093 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004094 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004095
Adam Powell637d3372010-08-25 14:37:03 -07004096 final OverScroller scroller = mScroller;
4097 boolean more = scroller.computeScrollOffset();
4098 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004099
Adam Powell637d3372010-08-25 14:37:03 -07004100 // Flip sign to convert finger direction to list items direction
4101 // (e.g. finger moving down means list is moving towards the top)
4102 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004103
Adam Powell637d3372010-08-25 14:37:03 -07004104 // Pretend that each frame of a fling scroll is a touch scroll
4105 if (delta > 0) {
4106 // List is moving towards the top. Use first view as mMotionPosition
4107 mMotionPosition = mFirstPosition;
4108 final View firstView = getChildAt(0);
4109 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004110
Adam Powell637d3372010-08-25 14:37:03 -07004111 // Don't fling more than 1 screen
4112 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4113 } else {
4114 // List is moving towards the bottom. Use last view as mMotionPosition
4115 int offsetToLast = getChildCount() - 1;
4116 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004117
Adam Powell637d3372010-08-25 14:37:03 -07004118 final View lastView = getChildAt(offsetToLast);
4119 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004120
Adam Powell637d3372010-08-25 14:37:03 -07004121 // Don't fling more than 1 screen
4122 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4123 }
Adam Powell79ac3392010-01-28 21:22:20 -08004124
Adam Powell637d3372010-08-25 14:37:03 -07004125 // Check to see if we have bumped into the scroll limit
4126 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4127 int oldTop = 0;
4128 if (motionView != null) {
4129 oldTop = motionView.getTop();
4130 }
Adam Powell9d32d242010-03-29 16:02:07 -07004131
Adam Powell637d3372010-08-25 14:37:03 -07004132 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004133 final boolean atEdge = trackMotionScroll(delta, delta);
4134 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004135 if (atEnd) {
4136 if (motionView != null) {
4137 // Tweak the scroll for how far we overshot
4138 int overshoot = -(delta - (motionView.getTop() - oldTop));
4139 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4140 0, mOverflingDistance, false);
4141 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004142 if (more) {
4143 edgeReached(delta);
4144 }
Adam Powell637d3372010-08-25 14:37:03 -07004145 break;
4146 }
4147
4148 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004149 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004150 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004151 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004152 } else {
4153 endFling();
4154
4155 if (PROFILE_FLINGING) {
4156 if (mFlingProfilingStarted) {
4157 Debug.stopMethodTracing();
4158 mFlingProfilingStarted = false;
4159 }
4160
4161 if (mFlingStrictSpan != null) {
4162 mFlingStrictSpan.finish();
4163 mFlingStrictSpan = null;
4164 }
Adam Powell79ac3392010-01-28 21:22:20 -08004165 }
4166 }
Adam Powell637d3372010-08-25 14:37:03 -07004167 break;
4168 }
4169
4170 case TOUCH_MODE_OVERFLING: {
4171 final OverScroller scroller = mScroller;
4172 if (scroller.computeScrollOffset()) {
4173 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004174 final int currY = scroller.getCurrY();
4175 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004176 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4177 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004178 final boolean crossDown = scrollY <= 0 && currY > 0;
4179 final boolean crossUp = scrollY >= 0 && currY < 0;
4180 if (crossDown || crossUp) {
4181 int velocity = (int) scroller.getCurrVelocity();
4182 if (crossUp) velocity = -velocity;
4183
4184 // Don't flywheel from this; we're just continuing things.
4185 scroller.abortAnimation();
4186 start(velocity);
4187 } else {
4188 startSpringback();
4189 }
Adam Powell637d3372010-08-25 14:37:03 -07004190 } else {
4191 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004192 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004193 }
4194 } else {
4195 endFling();
4196 }
4197 break;
4198 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004199 }
4200 }
4201 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004202
Adam Powell45803472010-01-25 15:10:44 -08004203 class PositionScroller implements Runnable {
Adam Powelle69370e2012-05-07 15:17:20 -07004204 private static final int SCROLL_DURATION = 200;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004205
Adam Powell45803472010-01-25 15:10:44 -08004206 private static final int MOVE_DOWN_POS = 1;
4207 private static final int MOVE_UP_POS = 2;
4208 private static final int MOVE_DOWN_BOUND = 3;
4209 private static final int MOVE_UP_BOUND = 4;
Adam Powelle44afae2010-07-01 10:10:35 -07004210 private static final int MOVE_OFFSET = 5;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004211
Adam Powell45803472010-01-25 15:10:44 -08004212 private int mMode;
4213 private int mTargetPos;
4214 private int mBoundPos;
4215 private int mLastSeenPos;
4216 private int mScrollDuration;
Gilles Debunne52964242010-02-24 11:05:19 -08004217 private final int mExtraScroll;
Adam Powelle44afae2010-07-01 10:10:35 -07004218
4219 private int mOffsetFromTop;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004220
Adam Powell45803472010-01-25 15:10:44 -08004221 PositionScroller() {
4222 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
4223 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004224
Adam Powelle69370e2012-05-07 15:17:20 -07004225 void start(final int position) {
Adam Powell40322522011-01-12 21:58:20 -08004226 stop();
4227
Adam Powellaadf4fb2012-05-08 15:42:13 -07004228 if (mDataChanged) {
4229 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004230 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004231 @Override public void run() {
4232 start(position);
4233 }
Adam Powell161abf32012-05-23 17:22:49 -07004234 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004235 return;
4236 }
4237
Adam Powelle69370e2012-05-07 15:17:20 -07004238 final int childCount = getChildCount();
4239 if (childCount == 0) {
4240 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004241 return;
4242 }
4243
Adam Powell45803472010-01-25 15:10:44 -08004244 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004245 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004246
Romain Guy4bede9e2010-10-11 19:36:59 -07004247 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004248 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4249 if (clampedPosition < firstPos) {
4250 viewTravelCount = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004251 mMode = MOVE_UP_POS;
Chet Haase0061e162012-06-08 15:01:56 -07004252 } else if (clampedPosition > lastPos) {
4253 viewTravelCount = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004254 mMode = MOVE_DOWN_POS;
4255 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004256 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004257 return;
4258 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004259
Adam Powell45803472010-01-25 15:10:44 -08004260 if (viewTravelCount > 0) {
4261 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4262 } else {
4263 mScrollDuration = SCROLL_DURATION;
4264 }
Chet Haase0061e162012-06-08 15:01:56 -07004265 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004266 mBoundPos = INVALID_POSITION;
4267 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004268
Adam Powell1fa179ef2012-04-12 15:01:40 -07004269 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004270 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004271
Adam Powelle69370e2012-05-07 15:17:20 -07004272 void start(final int position, final int boundPosition) {
Adam Powell40322522011-01-12 21:58:20 -08004273 stop();
4274
Adam Powell45803472010-01-25 15:10:44 -08004275 if (boundPosition == INVALID_POSITION) {
4276 start(position);
4277 return;
4278 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004279
Adam Powellaadf4fb2012-05-08 15:42:13 -07004280 if (mDataChanged) {
4281 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004282 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004283 @Override public void run() {
4284 start(position, boundPosition);
4285 }
Adam Powell161abf32012-05-23 17:22:49 -07004286 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004287 return;
4288 }
4289
Adam Powelle69370e2012-05-07 15:17:20 -07004290 final int childCount = getChildCount();
4291 if (childCount == 0) {
4292 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004293 return;
4294 }
4295
Adam Powell45803472010-01-25 15:10:44 -08004296 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004297 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004298
Romain Guy4bede9e2010-10-11 19:36:59 -07004299 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004300 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4301 if (clampedPosition < firstPos) {
Adam Powell45803472010-01-25 15:10:44 -08004302 final int boundPosFromLast = lastPos - boundPosition;
4303 if (boundPosFromLast < 1) {
4304 // Moving would shift our bound position off the screen. Abort.
4305 return;
4306 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004307
Chet Haase0061e162012-06-08 15:01:56 -07004308 final int posTravel = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004309 final int boundTravel = boundPosFromLast - 1;
4310 if (boundTravel < posTravel) {
4311 viewTravelCount = boundTravel;
4312 mMode = MOVE_UP_BOUND;
4313 } else {
4314 viewTravelCount = posTravel;
4315 mMode = MOVE_UP_POS;
4316 }
Chet Haase0061e162012-06-08 15:01:56 -07004317 } else if (clampedPosition > lastPos) {
Adam Powell45803472010-01-25 15:10:44 -08004318 final int boundPosFromFirst = boundPosition - firstPos;
4319 if (boundPosFromFirst < 1) {
4320 // Moving would shift our bound position off the screen. Abort.
4321 return;
4322 }
4323
Chet Haase0061e162012-06-08 15:01:56 -07004324 final int posTravel = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004325 final int boundTravel = boundPosFromFirst - 1;
4326 if (boundTravel < posTravel) {
4327 viewTravelCount = boundTravel;
4328 mMode = MOVE_DOWN_BOUND;
4329 } else {
4330 viewTravelCount = posTravel;
4331 mMode = MOVE_DOWN_POS;
4332 }
4333 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004334 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004335 return;
4336 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004337
Adam Powell45803472010-01-25 15:10:44 -08004338 if (viewTravelCount > 0) {
4339 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4340 } else {
4341 mScrollDuration = SCROLL_DURATION;
4342 }
Chet Haase0061e162012-06-08 15:01:56 -07004343 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004344 mBoundPos = boundPosition;
4345 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004346
Adam Powell1fa179ef2012-04-12 15:01:40 -07004347 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004348 }
Adam Powelle44afae2010-07-01 10:10:35 -07004349
4350 void startWithOffset(int position, int offset) {
Erik322171b2010-10-13 15:46:00 -07004351 startWithOffset(position, offset, SCROLL_DURATION);
4352 }
4353
Adam Powellaadf4fb2012-05-08 15:42:13 -07004354 void startWithOffset(final int position, int offset, final int duration) {
Adam Powell40322522011-01-12 21:58:20 -08004355 stop();
4356
Adam Powellaadf4fb2012-05-08 15:42:13 -07004357 if (mDataChanged) {
4358 // Wait until we're back in a stable state to try this.
4359 final int postOffset = offset;
Adam Powell161abf32012-05-23 17:22:49 -07004360 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004361 @Override public void run() {
4362 startWithOffset(position, postOffset, duration);
4363 }
Adam Powell161abf32012-05-23 17:22:49 -07004364 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004365 return;
4366 }
4367
4368 final int childCount = getChildCount();
4369 if (childCount == 0) {
4370 // Can't scroll without children.
4371 return;
4372 }
4373
Adam Powell1fa179ef2012-04-12 15:01:40 -07004374 offset += getPaddingTop();
4375
Chet Haase0061e162012-06-08 15:01:56 -07004376 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
Adam Powelle44afae2010-07-01 10:10:35 -07004377 mOffsetFromTop = offset;
4378 mBoundPos = INVALID_POSITION;
4379 mLastSeenPos = INVALID_POSITION;
4380 mMode = MOVE_OFFSET;
4381
4382 final int firstPos = mFirstPosition;
Adam Powell37113312010-07-08 18:21:48 -07004383 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004384
Romain Guy4bede9e2010-10-11 19:36:59 -07004385 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004386 if (mTargetPos < firstPos) {
4387 viewTravelCount = firstPos - mTargetPos;
4388 } else if (mTargetPos > lastPos) {
4389 viewTravelCount = mTargetPos - lastPos;
Adam Powelle44afae2010-07-01 10:10:35 -07004390 } else {
4391 // On-screen, just scroll.
Chet Haase0061e162012-06-08 15:01:56 -07004392 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Adam Powell0b8acd82012-04-25 20:29:23 -07004393 smoothScrollBy(targetTop - offset, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004394 return;
4395 }
4396
Adam Powell37113312010-07-08 18:21:48 -07004397 // Estimate how many screens we should travel
Daniel Lehmann4ef1da32010-08-27 16:40:59 -07004398 final float screenTravelCount = (float) viewTravelCount / childCount;
Adam Powell0b8acd82012-04-25 20:29:23 -07004399 mScrollDuration = screenTravelCount < 1 ?
4400 duration : (int) (duration / screenTravelCount);
Adam Powell37113312010-07-08 18:21:48 -07004401 mLastSeenPos = INVALID_POSITION;
Adam Powell234a5712010-09-14 10:34:56 -07004402
Adam Powell1fa179ef2012-04-12 15:01:40 -07004403 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004404 }
4405
Adam Powelle69370e2012-05-07 15:17:20 -07004406 /**
4407 * Scroll such that targetPos is in the visible padded region without scrolling
4408 * boundPos out of view. Assumes targetPos is onscreen.
4409 */
4410 void scrollToVisible(int targetPos, int boundPos, int duration) {
4411 final int firstPos = mFirstPosition;
4412 final int childCount = getChildCount();
4413 final int lastPos = firstPos + childCount - 1;
4414 final int paddedTop = mListPadding.top;
4415 final int paddedBottom = getHeight() - mListPadding.bottom;
4416
4417 if (targetPos < firstPos || targetPos > lastPos) {
4418 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
4419 " not visible [" + firstPos + ", " + lastPos + "]");
4420 }
4421 if (boundPos < firstPos || boundPos > lastPos) {
4422 // boundPos doesn't matter, it's already offscreen.
4423 boundPos = INVALID_POSITION;
4424 }
4425
4426 final View targetChild = getChildAt(targetPos - firstPos);
4427 final int targetTop = targetChild.getTop();
4428 final int targetBottom = targetChild.getBottom();
4429 int scrollBy = 0;
4430
4431 if (targetBottom > paddedBottom) {
4432 scrollBy = targetBottom - paddedBottom;
4433 }
4434 if (targetTop < paddedTop) {
4435 scrollBy = targetTop - paddedTop;
4436 }
4437
4438 if (scrollBy == 0) {
4439 return;
4440 }
4441
4442 if (boundPos >= 0) {
4443 final View boundChild = getChildAt(boundPos - firstPos);
4444 final int boundTop = boundChild.getTop();
4445 final int boundBottom = boundChild.getBottom();
4446 final int absScroll = Math.abs(scrollBy);
4447
4448 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
4449 // Don't scroll the bound view off the bottom of the screen.
4450 scrollBy = Math.max(0, boundBottom - paddedBottom);
4451 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
4452 // Don't scroll the bound view off the top of the screen.
4453 scrollBy = Math.min(0, boundTop - paddedTop);
4454 }
4455 }
4456
4457 smoothScrollBy(scrollBy, duration);
4458 }
4459
Adam Powell45803472010-01-25 15:10:44 -08004460 void stop() {
4461 removeCallbacks(this);
4462 }
Adam Powelle44afae2010-07-01 10:10:35 -07004463
Alan Viverette8fa327a2013-05-31 14:53:13 -07004464 @Override
Adam Powell45803472010-01-25 15:10:44 -08004465 public void run() {
4466 final int listHeight = getHeight();
4467 final int firstPos = mFirstPosition;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004468
Adam Powell45803472010-01-25 15:10:44 -08004469 switch (mMode) {
4470 case MOVE_DOWN_POS: {
4471 final int lastViewIndex = getChildCount() - 1;
4472 final int lastPos = firstPos + lastViewIndex;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004473
Adam Powell0b8bb422010-02-08 14:30:45 -08004474 if (lastViewIndex < 0) {
4475 return;
4476 }
Adam Powell45803472010-01-25 15:10:44 -08004477
4478 if (lastPos == mLastSeenPos) {
4479 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004480 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004481 return;
4482 }
4483
4484 final View lastView = getChildAt(lastViewIndex);
4485 final int lastViewHeight = lastView.getHeight();
4486 final int lastViewTop = lastView.getTop();
4487 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004488 final int extraScroll = lastPos < mItemCount - 1 ?
4489 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
Adam Powell45803472010-01-25 15:10:44 -08004490
Adam Powell1fa179ef2012-04-12 15:01:40 -07004491 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Adam Powell0b8acd82012-04-25 20:29:23 -07004492 smoothScrollBy(scrollBy, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004493
4494 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004495 if (lastPos < mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004496 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004497 }
4498 break;
4499 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004500
Adam Powell45803472010-01-25 15:10:44 -08004501 case MOVE_DOWN_BOUND: {
4502 final int nextViewIndex = 1;
Adam Powell029cfbd2010-03-08 19:03:54 -08004503 final int childCount = getChildCount();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004504
Adam Powell029cfbd2010-03-08 19:03:54 -08004505 if (firstPos == mBoundPos || childCount <= nextViewIndex
4506 || firstPos + childCount >= mItemCount) {
Adam Powell45803472010-01-25 15:10:44 -08004507 return;
4508 }
4509 final int nextPos = firstPos + nextViewIndex;
4510
4511 if (nextPos == mLastSeenPos) {
4512 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004513 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004514 return;
4515 }
4516
4517 final View nextView = getChildAt(nextViewIndex);
4518 final int nextViewHeight = nextView.getHeight();
4519 final int nextViewTop = nextView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004520 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
Adam Powell7e5e3742010-05-24 15:13:41 -07004521 if (nextPos < mBoundPos) {
Adam Powell45803472010-01-25 15:10:44 -08004522 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Adam Powell0b8acd82012-04-25 20:29:23 -07004523 mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004524
4525 mLastSeenPos = nextPos;
4526
Adam Powell1fa179ef2012-04-12 15:01:40 -07004527 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004528 } else {
Mindy Pereira4e30d892010-11-24 15:32:39 -08004529 if (nextViewTop > extraScroll) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004530 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004531 }
4532 }
4533 break;
4534 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004535
Adam Powell45803472010-01-25 15:10:44 -08004536 case MOVE_UP_POS: {
4537 if (firstPos == mLastSeenPos) {
4538 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004539 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004540 return;
4541 }
4542
4543 final View firstView = getChildAt(0);
Adam Powell0b8bb422010-02-08 14:30:45 -08004544 if (firstView == null) {
4545 return;
4546 }
Adam Powell45803472010-01-25 15:10:44 -08004547 final int firstViewTop = firstView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004548 final int extraScroll = firstPos > 0 ?
4549 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
Adam Powell45803472010-01-25 15:10:44 -08004550
Adam Powell0b8acd82012-04-25 20:29:23 -07004551 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004552
4553 mLastSeenPos = firstPos;
4554
Adam Powell7e5e3742010-05-24 15:13:41 -07004555 if (firstPos > mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004556 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004557 }
4558 break;
4559 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004560
Adam Powell45803472010-01-25 15:10:44 -08004561 case MOVE_UP_BOUND: {
4562 final int lastViewIndex = getChildCount() - 2;
4563 if (lastViewIndex < 0) {
4564 return;
4565 }
4566 final int lastPos = firstPos + lastViewIndex;
4567
4568 if (lastPos == mLastSeenPos) {
4569 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004570 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004571 return;
4572 }
4573
4574 final View lastView = getChildAt(lastViewIndex);
4575 final int lastViewHeight = lastView.getHeight();
4576 final int lastViewTop = lastView.getTop();
4577 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004578 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
Adam Powell45803472010-01-25 15:10:44 -08004579 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004580 if (lastPos > mBoundPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004581 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004582 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004583 } else {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004584 final int bottom = listHeight - extraScroll;
Adam Powell45803472010-01-25 15:10:44 -08004585 final int lastViewBottom = lastViewTop + lastViewHeight;
4586 if (bottom > lastViewBottom) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004587 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004588 }
4589 }
4590 break;
4591 }
4592
Adam Powelle44afae2010-07-01 10:10:35 -07004593 case MOVE_OFFSET: {
Adam Powell234a5712010-09-14 10:34:56 -07004594 if (mLastSeenPos == firstPos) {
4595 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004596 postOnAnimation(this);
Adam Powell234a5712010-09-14 10:34:56 -07004597 return;
4598 }
Adam Powelle44afae2010-07-01 10:10:35 -07004599
Adam Powell37113312010-07-08 18:21:48 -07004600 mLastSeenPos = firstPos;
Adam Powell234a5712010-09-14 10:34:56 -07004601
4602 final int childCount = getChildCount();
Adam Powelle44afae2010-07-01 10:10:35 -07004603 final int position = mTargetPos;
Adam Powell37113312010-07-08 18:21:48 -07004604 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004605
Adam Powell40322522011-01-12 21:58:20 -08004606 int viewTravelCount = 0;
Adam Powelle44afae2010-07-01 10:10:35 -07004607 if (position < firstPos) {
Adam Powell40322522011-01-12 21:58:20 -08004608 viewTravelCount = firstPos - position + 1;
4609 } else if (position > lastPos) {
4610 viewTravelCount = position - lastPos;
4611 }
4612
4613 // Estimate how many screens we should travel
4614 final float screenTravelCount = (float) viewTravelCount / childCount;
4615
4616 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4617 if (position < firstPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004618 final int distance = (int) (-getHeight() * modifier);
4619 final int duration = (int) (mScrollDuration * modifier);
4620 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004621 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004622 } else if (position > lastPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004623 final int distance = (int) (getHeight() * modifier);
4624 final int duration = (int) (mScrollDuration * modifier);
4625 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004626 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004627 } else {
4628 // On-screen, just scroll.
4629 final int targetTop = getChildAt(position - firstPos).getTop();
Adam Powell234a5712010-09-14 10:34:56 -07004630 final int distance = targetTop - mOffsetFromTop;
Adam Powell0b8acd82012-04-25 20:29:23 -07004631 final int duration = (int) (mScrollDuration *
4632 ((float) Math.abs(distance) / getHeight()));
4633 smoothScrollBy(distance, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004634 }
4635 break;
4636 }
4637
Adam Powell45803472010-01-25 15:10:44 -08004638 default:
4639 break;
4640 }
4641 }
4642 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004643
Adam Powell45803472010-01-25 15:10:44 -08004644 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004645 * The amount of friction applied to flings. The default value
4646 * is {@link ViewConfiguration#getScrollFriction}.
Romain Guy4bede9e2010-10-11 19:36:59 -07004647 */
4648 public void setFriction(float friction) {
4649 if (mFlingRunnable == null) {
4650 mFlingRunnable = new FlingRunnable();
4651 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004652 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004653 }
Romain Guy21317d12010-10-12 13:32:31 -07004654
4655 /**
4656 * Sets a scale factor for the fling velocity. The initial scale
4657 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004658 *
Romain Guy21317d12010-10-12 13:32:31 -07004659 * @param scale The scale factor to multiply the velocity by.
4660 */
4661 public void setVelocityScale(float scale) {
4662 mVelocityScale = scale;
4663 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004664
Romain Guy4bede9e2010-10-11 19:36:59 -07004665 /**
Adam Powell45803472010-01-25 15:10:44 -08004666 * Smoothly scroll to the specified adapter position. The view will
4667 * scroll such that the indicated position is displayed.
4668 * @param position Scroll to this adapter position.
4669 */
4670 public void smoothScrollToPosition(int position) {
4671 if (mPositionScroller == null) {
4672 mPositionScroller = new PositionScroller();
4673 }
4674 mPositionScroller.start(position);
4675 }
Erik322171b2010-10-13 15:46:00 -07004676
4677 /**
4678 * Smoothly scroll to the specified adapter position. The view will scroll
4679 * such that the indicated position is displayed <code>offset</code> pixels from
4680 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4681 * the first or last item beyond the boundaries of the list) it will get as close
4682 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4683 *
4684 * @param position Position to scroll to
4685 * @param offset Desired distance in pixels of <code>position</code> from the top
4686 * of the view when scrolling is finished
4687 * @param duration Number of milliseconds to use for the scroll
4688 */
4689 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4690 if (mPositionScroller == null) {
4691 mPositionScroller = new PositionScroller();
4692 }
4693 mPositionScroller.startWithOffset(position, offset, duration);
4694 }
4695
Adam Powell45803472010-01-25 15:10:44 -08004696 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004697 * Smoothly scroll to the specified adapter position. The view will scroll
4698 * such that the indicated position is displayed <code>offset</code> pixels from
4699 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4700 * the first or last item beyond the boundaries of the list) it will get as close
4701 * as possible.
4702 *
4703 * @param position Position to scroll to
4704 * @param offset Desired distance in pixels of <code>position</code> from the top
4705 * of the view when scrolling is finished
4706 */
4707 public void smoothScrollToPositionFromTop(int position, int offset) {
4708 if (mPositionScroller == null) {
4709 mPositionScroller = new PositionScroller();
4710 }
4711 mPositionScroller.startWithOffset(position, offset);
4712 }
4713
4714 /**
Adam Powell45803472010-01-25 15:10:44 -08004715 * Smoothly scroll to the specified adapter position. The view will
4716 * scroll such that the indicated position is displayed, but it will
4717 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004718 * view.
Adam Powell45803472010-01-25 15:10:44 -08004719 * @param position Scroll to this adapter position.
4720 * @param boundPosition Do not scroll if it would move this adapter
4721 * position out of view.
4722 */
4723 public void smoothScrollToPosition(int position, int boundPosition) {
4724 if (mPositionScroller == null) {
4725 mPositionScroller = new PositionScroller();
4726 }
4727 mPositionScroller.start(position, boundPosition);
4728 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004729
Adam Powell45803472010-01-25 15:10:44 -08004730 /**
4731 * Smoothly scroll by distance pixels over duration milliseconds.
4732 * @param distance Distance to scroll in pixels.
4733 * @param duration Duration of the scroll animation in milliseconds.
4734 */
4735 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004736 smoothScrollBy(distance, duration, false);
4737 }
4738
4739 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004740 if (mFlingRunnable == null) {
4741 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004742 }
Adam Powell40322522011-01-12 21:58:20 -08004743
Marc Blank299acb52010-10-21 11:03:53 -07004744 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004745 final int firstPos = mFirstPosition;
4746 final int childCount = getChildCount();
4747 final int lastPos = firstPos + childCount;
4748 final int topLimit = getPaddingTop();
4749 final int bottomLimit = getHeight() - getPaddingBottom();
4750
Adam Powell79303752011-01-13 22:06:49 -08004751 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004752 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004753 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004754 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4755 mFlingRunnable.endFling();
4756 if (mPositionScroller != null) {
4757 mPositionScroller.stop();
4758 }
4759 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004760 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004761 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004762 }
Adam Powell45803472010-01-25 15:10:44 -08004763 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004764
Winson Chung499cb9f2010-07-16 11:18:17 -07004765 /**
4766 * Allows RemoteViews to scroll relatively to a position.
4767 */
4768 void smoothScrollByOffset(int position) {
4769 int index = -1;
4770 if (position < 0) {
4771 index = getFirstVisiblePosition();
4772 } else if (position > 0) {
4773 index = getLastVisiblePosition();
4774 }
4775
4776 if (index > -1) {
4777 View child = getChildAt(index - getFirstVisiblePosition());
4778 if (child != null) {
4779 Rect visibleRect = new Rect();
4780 if (child.getGlobalVisibleRect(visibleRect)) {
4781 // the child is partially visible
4782 int childRectArea = child.getWidth() * child.getHeight();
4783 int visibleRectArea = visibleRect.width() * visibleRect.height();
4784 float visibleArea = (visibleRectArea / (float) childRectArea);
4785 final float visibleThreshold = 0.75f;
4786 if ((position < 0) && (visibleArea < visibleThreshold)) {
4787 // the top index is not perceivably visible so offset
4788 // to account for showing that top index as well
4789 ++index;
4790 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4791 // the bottom index is not perceivably visible so offset
4792 // to account for showing that bottom index as well
4793 --index;
4794 }
4795 }
4796 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4797 }
4798 }
4799 }
4800
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004801 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004802 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004803 setChildrenDrawnWithCacheEnabled(true);
4804 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004805 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004806 }
4807 }
4808
4809 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004810 if (!isHardwareAccelerated()) {
4811 if (mClearScrollingCache == null) {
4812 mClearScrollingCache = new Runnable() {
Alan Viverette8fa327a2013-05-31 14:53:13 -07004813 @Override
Romain Guy9d849a22012-03-14 16:41:42 -07004814 public void run() {
4815 if (mCachingStarted) {
4816 mCachingStarted = mCachingActive = false;
4817 setChildrenDrawnWithCacheEnabled(false);
4818 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4819 setChildrenDrawingCacheEnabled(false);
4820 }
4821 if (!isAlwaysDrawnWithCacheEnabled()) {
4822 invalidate();
4823 }
Romain Guy6dfed242009-05-11 18:25:05 -07004824 }
4825 }
Romain Guy9d849a22012-03-14 16:41:42 -07004826 };
4827 }
4828 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004829 }
4830 }
4831
4832 /**
4833 * Track a motion scroll
4834 *
4835 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4836 * began. Positive numbers mean the user's finger is moving down the screen.
4837 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08004838 * @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 -08004839 */
Adam Powell45803472010-01-25 15:10:44 -08004840 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004841 final int childCount = getChildCount();
4842 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08004843 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004844 }
4845
4846 final int firstTop = getChildAt(0).getTop();
4847 final int lastBottom = getChildAt(childCount - 1).getBottom();
4848
4849 final Rect listPadding = mListPadding;
4850
Adam Powellbdccc2d2010-12-14 17:34:27 -08004851 // "effective padding" In this case is the amount of padding that affects
4852 // how much space should not be filled by items. If we don't clip to padding
4853 // there is no effective padding.
4854 int effectivePaddingTop = 0;
4855 int effectivePaddingBottom = 0;
4856 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4857 effectivePaddingTop = listPadding.top;
4858 effectivePaddingBottom = listPadding.bottom;
4859 }
4860
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004861 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08004862 final int spaceAbove = effectivePaddingTop - firstTop;
4863 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004864 final int spaceBelow = lastBottom - end;
4865
4866 final int height = getHeight() - mPaddingBottom - mPaddingTop;
4867 if (deltaY < 0) {
4868 deltaY = Math.max(-(height - 1), deltaY);
4869 } else {
4870 deltaY = Math.min(height - 1, deltaY);
4871 }
4872
4873 if (incrementalDeltaY < 0) {
4874 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4875 } else {
4876 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4877 }
4878
Adam Powell45803472010-01-25 15:10:44 -08004879 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004880
Adam Powell637d3372010-08-25 14:37:03 -07004881 // Update our guesses for where the first and last views are
4882 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004883 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07004884 } else {
4885 mFirstPositionDistanceGuess += incrementalDeltaY;
4886 }
4887 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004888 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07004889 } else {
4890 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004891 }
Adam Powell45803472010-01-25 15:10:44 -08004892
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004893 final boolean cannotScrollDown = (firstPosition == 0 &&
4894 firstTop >= listPadding.top && incrementalDeltaY >= 0);
4895 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4896 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07004897
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004898 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07004899 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08004900 }
4901
4902 final boolean down = incrementalDeltaY < 0;
4903
Adam Powell029cfbd2010-03-08 19:03:54 -08004904 final boolean inTouchMode = isInTouchMode();
4905 if (inTouchMode) {
4906 hideSelector();
4907 }
Adam Powell45803472010-01-25 15:10:44 -08004908
4909 final int headerViewsCount = getHeaderViewsCount();
4910 final int footerViewsStart = mItemCount - getFooterViewsCount();
4911
4912 int start = 0;
4913 int count = 0;
4914
4915 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004916 int top = -incrementalDeltaY;
4917 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4918 top += listPadding.top;
4919 }
Adam Powell45803472010-01-25 15:10:44 -08004920 for (int i = 0; i < childCount; i++) {
4921 final View child = getChildAt(i);
4922 if (child.getBottom() >= top) {
4923 break;
4924 } else {
4925 count++;
4926 int position = firstPosition + i;
4927 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004928 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004929 }
4930 }
4931 }
4932 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004933 int bottom = getHeight() - incrementalDeltaY;
4934 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4935 bottom -= listPadding.bottom;
4936 }
Adam Powell45803472010-01-25 15:10:44 -08004937 for (int i = childCount - 1; i >= 0; i--) {
4938 final View child = getChildAt(i);
4939 if (child.getTop() <= bottom) {
4940 break;
4941 } else {
4942 start = i;
4943 count++;
4944 int position = firstPosition + i;
4945 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004946 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004947 }
4948 }
4949 }
4950 }
4951
4952 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
4953
4954 mBlockLayoutRequests = true;
4955
4956 if (count > 0) {
4957 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08004958 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08004959 }
Adam Powell539ee872012-02-03 19:00:49 -08004960
Romain Guy9d849a22012-03-14 16:41:42 -07004961 // invalidate before moving the children to avoid unnecessary invalidate
4962 // calls to bubble up from the children all the way to the top
4963 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004964 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07004965 }
4966
Adam Powell45803472010-01-25 15:10:44 -08004967 offsetChildrenTopAndBottom(incrementalDeltaY);
4968
4969 if (down) {
4970 mFirstPosition += count;
4971 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004972
Adam Powell45803472010-01-25 15:10:44 -08004973 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
4974 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
4975 fillGap(down);
4976 }
4977
Adam Powell029cfbd2010-03-08 19:03:54 -08004978 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08004979 final int childIndex = mSelectedPosition - mFirstPosition;
4980 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004981 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08004982 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07004983 } else if (mSelectorPosition != INVALID_POSITION) {
4984 final int childIndex = mSelectorPosition - mFirstPosition;
4985 if (childIndex >= 0 && childIndex < getChildCount()) {
4986 positionSelector(INVALID_POSITION, getChildAt(childIndex));
4987 }
4988 } else {
4989 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08004990 }
4991
Adam Powell45803472010-01-25 15:10:44 -08004992 mBlockLayoutRequests = false;
4993
4994 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004995
Adam Powell45803472010-01-25 15:10:44 -08004996 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004997 }
4998
4999 /**
5000 * Returns the number of header views in the list. Header views are special views
5001 * at the top of the list that should not be recycled during a layout.
5002 *
5003 * @return The number of header views, 0 in the default implementation.
5004 */
5005 int getHeaderViewsCount() {
5006 return 0;
5007 }
5008
5009 /**
5010 * Returns the number of footer views in the list. Footer views are special views
5011 * at the bottom of the list that should not be recycled during a layout.
5012 *
5013 * @return The number of footer views, 0 in the default implementation.
5014 */
5015 int getFooterViewsCount() {
5016 return 0;
5017 }
5018
5019 /**
5020 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5021 * remain on screen are shifted and the other ones are discarded. The role of this
5022 * method is to fill the gap thus created by performing a partial layout in the
5023 * empty space.
5024 *
5025 * @param down true if the scroll is going down, false if it is going up
5026 */
5027 abstract void fillGap(boolean down);
5028
5029 void hideSelector() {
5030 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005031 if (mLayoutMode != LAYOUT_SPECIFIC) {
5032 mResurrectToPosition = mSelectedPosition;
5033 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005034 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5035 mResurrectToPosition = mNextSelectedPosition;
5036 }
5037 setSelectedPositionInt(INVALID_POSITION);
5038 setNextSelectedPositionInt(INVALID_POSITION);
5039 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005040 }
5041 }
5042
5043 /**
5044 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5045 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5046 * of items available in the adapter
5047 */
5048 int reconcileSelectedPosition() {
5049 int position = mSelectedPosition;
5050 if (position < 0) {
5051 position = mResurrectToPosition;
5052 }
5053 position = Math.max(0, position);
5054 position = Math.min(position, mItemCount - 1);
5055 return position;
5056 }
5057
5058 /**
5059 * Find the row closest to y. This row will be used as the motion row when scrolling
5060 *
5061 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005062 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005063 */
5064 abstract int findMotionRow(int y);
5065
5066 /**
Adam Powell637d3372010-08-25 14:37:03 -07005067 * Find the row closest to y. This row will be used as the motion row when scrolling.
5068 *
5069 * @param y Where the user touched
5070 * @return The position of the first (or only) item in the row closest to y
5071 */
5072 int findClosestMotionRow(int y) {
5073 final int childCount = getChildCount();
5074 if (childCount == 0) {
5075 return INVALID_POSITION;
5076 }
5077
5078 final int motionRow = findMotionRow(y);
5079 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5080 }
5081
5082 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005083 * Causes all the views to be rebuilt and redrawn.
5084 */
5085 public void invalidateViews() {
5086 mDataChanged = true;
5087 rememberSyncState();
5088 requestLayout();
5089 invalidate();
5090 }
Alan Viverette8fa327a2013-05-31 14:53:13 -07005091
Jeff Brown4e6319b2010-12-13 10:36:51 -08005092 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005093 * If there is a selection returns false.
5094 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005095 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005096 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005097 if (mSelectedPosition < 0 && resurrectSelection()) {
5098 updateSelectorState();
5099 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005100 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005101 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005102 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005103
5104 /**
5105 * Makes the item at the supplied position selected.
5106 *
5107 * @param position the position of the new selection
5108 */
5109 abstract void setSelectionInt(int position);
5110
5111 /**
5112 * Attempt to bring the selection back if the user is switching from touch
5113 * to trackball mode
5114 * @return Whether selection was set to something.
5115 */
5116 boolean resurrectSelection() {
5117 final int childCount = getChildCount();
5118
5119 if (childCount <= 0) {
5120 return false;
5121 }
5122
5123 int selectedTop = 0;
5124 int selectedPos;
5125 int childrenTop = mListPadding.top;
5126 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5127 final int firstPosition = mFirstPosition;
5128 final int toPosition = mResurrectToPosition;
5129 boolean down = true;
5130
5131 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5132 selectedPos = toPosition;
5133
5134 final View selected = getChildAt(selectedPos - mFirstPosition);
5135 selectedTop = selected.getTop();
5136 int selectedBottom = selected.getBottom();
5137
5138 // We are scrolled, don't get in the fade
5139 if (selectedTop < childrenTop) {
5140 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5141 } else if (selectedBottom > childrenBottom) {
5142 selectedTop = childrenBottom - selected.getMeasuredHeight()
5143 - getVerticalFadingEdgeLength();
5144 }
5145 } else {
5146 if (toPosition < firstPosition) {
5147 // Default to selecting whatever is first
5148 selectedPos = firstPosition;
5149 for (int i = 0; i < childCount; i++) {
5150 final View v = getChildAt(i);
5151 final int top = v.getTop();
5152
5153 if (i == 0) {
5154 // Remember the position of the first item
5155 selectedTop = top;
5156 // See if we are scrolled at all
5157 if (firstPosition > 0 || top < childrenTop) {
5158 // If we are scrolled, don't select anything that is
5159 // in the fade region
5160 childrenTop += getVerticalFadingEdgeLength();
5161 }
5162 }
5163 if (top >= childrenTop) {
5164 // Found a view whose top is fully visisble
5165 selectedPos = firstPosition + i;
5166 selectedTop = top;
5167 break;
5168 }
5169 }
5170 } else {
5171 final int itemCount = mItemCount;
5172 down = false;
5173 selectedPos = firstPosition + childCount - 1;
5174
5175 for (int i = childCount - 1; i >= 0; i--) {
5176 final View v = getChildAt(i);
5177 final int top = v.getTop();
5178 final int bottom = v.getBottom();
5179
5180 if (i == childCount - 1) {
5181 selectedTop = top;
5182 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5183 childrenBottom -= getVerticalFadingEdgeLength();
5184 }
5185 }
5186
5187 if (bottom <= childrenBottom) {
5188 selectedPos = firstPosition + i;
5189 selectedTop = top;
5190 break;
5191 }
5192 }
5193 }
5194 }
5195
5196 mResurrectToPosition = INVALID_POSITION;
5197 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005198 if (mPositionScroller != null) {
5199 mPositionScroller.stop();
5200 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005201 mTouchMode = TOUCH_MODE_REST;
5202 clearScrollingCache();
5203 mSpecificTop = selectedTop;
5204 selectedPos = lookForSelectablePosition(selectedPos, down);
5205 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5206 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005207 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005208 setSelectionInt(selectedPos);
5209 invokeOnItemScrollListener();
5210 } else {
5211 selectedPos = INVALID_POSITION;
5212 }
5213 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5214
5215 return selectedPos >= 0;
5216 }
5217
Adam Powell14c08042011-10-06 19:46:18 -07005218 void confirmCheckedPositionsById() {
5219 // Clear out the positional check states, we'll rebuild it below from IDs.
5220 mCheckStates.clear();
5221
5222 boolean checkedCountChanged = false;
5223 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5224 final long id = mCheckedIdStates.keyAt(checkedIndex);
5225 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5226
5227 final long lastPosId = mAdapter.getItemId(lastPos);
5228 if (id != lastPosId) {
5229 // Look around to see if the ID is nearby. If not, uncheck it.
5230 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5231 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5232 boolean found = false;
5233 for (int searchPos = start; searchPos < end; searchPos++) {
5234 final long searchId = mAdapter.getItemId(searchPos);
5235 if (id == searchId) {
5236 found = true;
5237 mCheckStates.put(searchPos, true);
5238 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5239 break;
5240 }
5241 }
5242
5243 if (!found) {
5244 mCheckedIdStates.delete(id);
5245 checkedIndex--;
5246 mCheckedItemCount--;
5247 checkedCountChanged = true;
5248 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5249 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5250 lastPos, id, false);
5251 }
5252 }
5253 } else {
5254 mCheckStates.put(lastPos, true);
5255 }
5256 }
5257
5258 if (checkedCountChanged && mChoiceActionMode != null) {
5259 mChoiceActionMode.invalidate();
5260 }
5261 }
5262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005263 @Override
5264 protected void handleDataChanged() {
5265 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005266 int lastHandledItemCount = mLastHandledItemCount;
5267 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005268
5269 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5270 confirmCheckedPositionsById();
5271 }
5272
Adam Powell539ee872012-02-03 19:00:49 -08005273 // TODO: In the future we can recycle these views based on stable ID instead.
5274 mRecycler.clearTransientStateViews();
5275
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005276 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005277 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005278 int selectablePos;
5279
5280 // Find the row we are supposed to sync to
5281 if (mNeedSync) {
5282 // Update this first, since setNextSelectedPositionInt inspects it
5283 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005284 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005285
Adam Powell07852792010-11-10 16:57:05 -08005286 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005287 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5288 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005289 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5290 if (mForceTranscriptScroll) {
5291 mForceTranscriptScroll = false;
5292 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5293 return;
5294 }
Adam Powell07852792010-11-10 16:57:05 -08005295 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005296 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005297 final View lastChild = getChildAt(childCount - 1);
5298 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005299 if (mFirstPosition + childCount >= lastHandledItemCount &&
5300 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005301 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5302 return;
5303 }
5304 // Something new came in and we didn't scroll; give the user a clue that
5305 // there's something new.
5306 awakenScrollBars();
5307 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005308
5309 switch (mSyncMode) {
5310 case SYNC_SELECTED_POSITION:
5311 if (isInTouchMode()) {
5312 // We saved our state when not in touch mode. (We know this because
5313 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5314 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5315 // adjusting if the available range changed) and return.
5316 mLayoutMode = LAYOUT_SYNC;
5317 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5318
5319 return;
5320 } else {
5321 // See if we can find a position in the new data with the same
5322 // id as the old selection. This will change mSyncPosition.
5323 newPos = findSyncPosition();
5324 if (newPos >= 0) {
5325 // Found it. Now verify that new selection is still selectable
5326 selectablePos = lookForSelectablePosition(newPos, true);
5327 if (selectablePos == newPos) {
5328 // Same row id is selected
5329 mSyncPosition = newPos;
5330
5331 if (mSyncHeight == getHeight()) {
5332 // If we are at the same height as when we saved state, try
5333 // to restore the scroll position too.
5334 mLayoutMode = LAYOUT_SYNC;
5335 } else {
5336 // We are not the same height as when the selection was saved, so
5337 // don't try to restore the exact position
5338 mLayoutMode = LAYOUT_SET_SELECTION;
5339 }
5340
5341 // Restore selection
5342 setNextSelectedPositionInt(newPos);
5343 return;
5344 }
5345 }
5346 }
5347 break;
5348 case SYNC_FIRST_POSITION:
5349 // Leave mSyncPosition as it is -- just pin to available range
5350 mLayoutMode = LAYOUT_SYNC;
5351 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5352
5353 return;
5354 }
5355 }
5356
5357 if (!isInTouchMode()) {
5358 // We couldn't find matching data -- try to use the same position
5359 newPos = getSelectedItemPosition();
5360
5361 // Pin position to the available range
5362 if (newPos >= count) {
5363 newPos = count - 1;
5364 }
5365 if (newPos < 0) {
5366 newPos = 0;
5367 }
5368
5369 // Make sure we select something selectable -- first look down
5370 selectablePos = lookForSelectablePosition(newPos, true);
5371
5372 if (selectablePos >= 0) {
5373 setNextSelectedPositionInt(selectablePos);
5374 return;
5375 } else {
5376 // Looking down didn't work -- try looking up
5377 selectablePos = lookForSelectablePosition(newPos, false);
5378 if (selectablePos >= 0) {
5379 setNextSelectedPositionInt(selectablePos);
5380 return;
5381 }
5382 }
5383 } else {
5384
5385 // We already know where we want to resurrect the selection
5386 if (mResurrectToPosition >= 0) {
5387 return;
5388 }
5389 }
5390
5391 }
5392
5393 // Nothing is selected. Give up and reset everything.
5394 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5395 mSelectedPosition = INVALID_POSITION;
5396 mSelectedRowId = INVALID_ROW_ID;
5397 mNextSelectedPosition = INVALID_POSITION;
5398 mNextSelectedRowId = INVALID_ROW_ID;
5399 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005400 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005401 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005402 checkSelectionChanged();
5403 }
5404
Romain Guy43c9cdf2010-01-27 13:53:55 -08005405 @Override
5406 protected void onDisplayHint(int hint) {
5407 super.onDisplayHint(hint);
5408 switch (hint) {
5409 case INVISIBLE:
5410 if (mPopup != null && mPopup.isShowing()) {
5411 dismissPopup();
5412 }
5413 break;
5414 case VISIBLE:
5415 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5416 showPopup();
5417 }
5418 break;
5419 }
Romain Guy24562482010-02-01 14:56:19 -08005420 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005421 }
5422
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005423 /**
5424 * Removes the filter window
5425 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005426 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005427 if (mPopup != null) {
5428 mPopup.dismiss();
5429 }
5430 }
5431
5432 /**
5433 * Shows the filter window
5434 */
5435 private void showPopup() {
5436 // Make sure we have a window before showing the popup
5437 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005438 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005439 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005440 // Make sure we get focus if we are showing the popup
5441 checkFocus();
5442 }
5443 }
5444
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005445 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005446 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5447 final int[] xy = new int[2];
5448 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005449 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005450 // TODO: And the gravity should be defined in the theme as well
5451 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005452 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005453 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5454 xy[0], bottomGap);
5455 } else {
5456 mPopup.update(xy[0], bottomGap, -1, -1);
5457 }
5458 }
5459
5460 /**
5461 * What is the distance between the source and destination rectangles given the direction of
5462 * focus navigation between them? The direction basically helps figure out more quickly what is
5463 * self evident by the relationship between the rects...
5464 *
5465 * @param source the source rectangle
5466 * @param dest the destination rectangle
5467 * @param direction the direction
5468 * @return the distance between the rectangles
5469 */
5470 static int getDistance(Rect source, Rect dest, int direction) {
5471 int sX, sY; // source x, y
5472 int dX, dY; // dest x, y
5473 switch (direction) {
5474 case View.FOCUS_RIGHT:
5475 sX = source.right;
5476 sY = source.top + source.height() / 2;
5477 dX = dest.left;
5478 dY = dest.top + dest.height() / 2;
5479 break;
5480 case View.FOCUS_DOWN:
5481 sX = source.left + source.width() / 2;
5482 sY = source.bottom;
5483 dX = dest.left + dest.width() / 2;
5484 dY = dest.top;
5485 break;
5486 case View.FOCUS_LEFT:
5487 sX = source.left;
5488 sY = source.top + source.height() / 2;
5489 dX = dest.right;
5490 dY = dest.top + dest.height() / 2;
5491 break;
5492 case View.FOCUS_UP:
5493 sX = source.left + source.width() / 2;
5494 sY = source.top;
5495 dX = dest.left + dest.width() / 2;
5496 dY = dest.bottom;
5497 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005498 case View.FOCUS_FORWARD:
5499 case View.FOCUS_BACKWARD:
5500 sX = source.right + source.width() / 2;
5501 sY = source.top + source.height() / 2;
5502 dX = dest.left + dest.width() / 2;
5503 dY = dest.top + dest.height() / 2;
5504 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005505 default:
5506 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005507 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5508 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005509 }
5510 int deltaX = dX - sX;
5511 int deltaY = dY - sY;
5512 return deltaY * deltaY + deltaX * deltaX;
5513 }
5514
5515 @Override
5516 protected boolean isInFilterMode() {
5517 return mFiltered;
5518 }
5519
5520 /**
5521 * Sends a key to the text filter window
5522 *
5523 * @param keyCode The keycode for the event
5524 * @param event The actual key event
5525 *
5526 * @return True if the text filter handled the event, false otherwise.
5527 */
5528 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005529 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005530 return false;
5531 }
5532
5533 boolean handled = false;
5534 boolean okToSend = true;
5535 switch (keyCode) {
5536 case KeyEvent.KEYCODE_DPAD_UP:
5537 case KeyEvent.KEYCODE_DPAD_DOWN:
5538 case KeyEvent.KEYCODE_DPAD_LEFT:
5539 case KeyEvent.KEYCODE_DPAD_RIGHT:
5540 case KeyEvent.KEYCODE_DPAD_CENTER:
5541 case KeyEvent.KEYCODE_ENTER:
5542 okToSend = false;
5543 break;
5544 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005545 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005546 if (event.getAction() == KeyEvent.ACTION_DOWN
5547 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005548 KeyEvent.DispatcherState state = getKeyDispatcherState();
5549 if (state != null) {
5550 state.startTracking(event, this);
5551 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005552 handled = true;
5553 } else if (event.getAction() == KeyEvent.ACTION_UP
5554 && event.isTracking() && !event.isCanceled()) {
5555 handled = true;
5556 mTextFilter.setText("");
5557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005558 }
5559 okToSend = false;
5560 break;
5561 case KeyEvent.KEYCODE_SPACE:
5562 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005563 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005564 break;
5565 }
5566
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005567 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005568 createTextFilter(true);
5569
5570 KeyEvent forwardEvent = event;
5571 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005572 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005573 }
5574
5575 int action = event.getAction();
5576 switch (action) {
5577 case KeyEvent.ACTION_DOWN:
5578 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5579 break;
5580
5581 case KeyEvent.ACTION_UP:
5582 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5583 break;
5584
5585 case KeyEvent.ACTION_MULTIPLE:
5586 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5587 break;
5588 }
5589 }
5590 return handled;
5591 }
5592
5593 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005594 * Return an InputConnection for editing of the filter text.
5595 */
5596 @Override
5597 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005598 if (isTextFilterEnabled()) {
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005599 if (mPublicInputConnection == null) {
5600 mDefInputConnection = new BaseInputConnection(this, false);
Romain Guyf6991302013-06-05 17:19:01 -07005601 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005602 }
Romain Guyf6991302013-06-05 17:19:01 -07005603 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005604 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5605 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005606 }
5607 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005608 }
Romain Guy0a637162009-05-29 14:43:54 -07005609
Romain Guyf6991302013-06-05 17:19:01 -07005610 private class InputConnectionWrapper implements InputConnection {
5611 private final EditorInfo mOutAttrs;
5612 private InputConnection mTarget;
5613
5614 public InputConnectionWrapper(EditorInfo outAttrs) {
5615 mOutAttrs = outAttrs;
5616 }
5617
5618 private InputConnection getTarget() {
5619 if (mTarget == null) {
5620 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5621 }
5622 return mTarget;
5623 }
5624
5625 @Override
5626 public boolean reportFullscreenMode(boolean enabled) {
5627 // Use our own input connection, since it is
5628 // the "real" one the IME is talking with.
5629 return mDefInputConnection.reportFullscreenMode(enabled);
5630 }
5631
5632 @Override
5633 public boolean performEditorAction(int editorAction) {
5634 // The editor is off in its own window; we need to be
5635 // the one that does this.
5636 if (editorAction == EditorInfo.IME_ACTION_DONE) {
5637 InputMethodManager imm = (InputMethodManager)
5638 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
5639 if (imm != null) {
5640 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5641 }
5642 return true;
5643 }
5644 return false;
5645 }
5646
5647 @Override
5648 public boolean sendKeyEvent(KeyEvent event) {
5649 // Use our own input connection, since the filter
5650 // text view may not be shown in a window so has
5651 // no ViewAncestor to dispatch events with.
5652 return mDefInputConnection.sendKeyEvent(event);
5653 }
5654
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005655 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005656 public CharSequence getTextBeforeCursor(int n, int flags) {
5657 if (mTarget == null) return "";
5658 return mTarget.getTextBeforeCursor(n, flags);
5659 }
5660
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005661 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005662 public CharSequence getTextAfterCursor(int n, int flags) {
5663 if (mTarget == null) return "";
5664 return mTarget.getTextAfterCursor(n, flags);
5665 }
5666
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005667 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005668 public CharSequence getSelectedText(int flags) {
5669 if (mTarget == null) return "";
5670 return mTarget.getSelectedText(flags);
5671 }
5672
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005673 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005674 public int getCursorCapsMode(int reqModes) {
5675 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5676 return mTarget.getCursorCapsMode(reqModes);
5677 }
5678
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005679 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005680 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5681 return getTarget().getExtractedText(request, flags);
5682 }
5683
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005684 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005685 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5686 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5687 }
5688
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005689 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005690 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5691 return getTarget().setComposingText(text, newCursorPosition);
5692 }
5693
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005694 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005695 public boolean setComposingRegion(int start, int end) {
5696 return getTarget().setComposingRegion(start, end);
5697 }
5698
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005699 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005700 public boolean finishComposingText() {
5701 return mTarget == null || mTarget.finishComposingText();
5702 }
5703
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005704 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005705 public boolean commitText(CharSequence text, int newCursorPosition) {
5706 return getTarget().commitText(text, newCursorPosition);
5707 }
5708
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005709 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005710 public boolean commitCompletion(CompletionInfo text) {
5711 return getTarget().commitCompletion(text);
5712 }
5713
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005714 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005715 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5716 return getTarget().commitCorrection(correctionInfo);
5717 }
5718
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005719 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005720 public boolean setSelection(int start, int end) {
5721 return getTarget().setSelection(start, end);
5722 }
5723
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005724 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005725 public boolean performContextMenuAction(int id) {
5726 return getTarget().performContextMenuAction(id);
5727 }
5728
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005729 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005730 public boolean beginBatchEdit() {
5731 return getTarget().beginBatchEdit();
5732 }
5733
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005734 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005735 public boolean endBatchEdit() {
5736 return getTarget().endBatchEdit();
5737 }
5738
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005739 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005740 public boolean clearMetaKeyStates(int states) {
5741 return getTarget().clearMetaKeyStates(states);
5742 }
5743
Alan Viverette0ebe81e2013-06-21 17:01:36 -07005744 @Override
Romain Guyf6991302013-06-05 17:19:01 -07005745 public boolean performPrivateCommand(String action, Bundle data) {
5746 return getTarget().performPrivateCommand(action, data);
5747 }
5748 }
5749
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005750 /**
5751 * For filtering we proxy an input connection to an internal text editor,
5752 * and this allows the proxying to happen.
5753 */
5754 @Override
5755 public boolean checkInputConnectionProxy(View view) {
5756 return view == mTextFilter;
5757 }
Romain Guy0a637162009-05-29 14:43:54 -07005758
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005759 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005760 * Creates the window for the text filter and populates it with an EditText field;
5761 *
5762 * @param animateEntrance true if the window should appear with an animation
5763 */
5764 private void createTextFilter(boolean animateEntrance) {
5765 if (mPopup == null) {
Romain Guyf6991302013-06-05 17:19:01 -07005766 PopupWindow p = new PopupWindow(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005767 p.setFocusable(false);
5768 p.setTouchable(false);
5769 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
Romain Guyf6991302013-06-05 17:19:01 -07005770 p.setContentView(getTextFilterInput());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005771 p.setWidth(LayoutParams.WRAP_CONTENT);
5772 p.setHeight(LayoutParams.WRAP_CONTENT);
5773 p.setBackgroundDrawable(null);
5774 mPopup = p;
5775 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005776 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005777 }
5778 if (animateEntrance) {
5779 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5780 } else {
5781 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5782 }
5783 }
5784
Romain Guyf6991302013-06-05 17:19:01 -07005785 private EditText getTextFilterInput() {
5786 if (mTextFilter == null) {
5787 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
5788 mTextFilter = (EditText) layoutInflater.inflate(
5789 com.android.internal.R.layout.typing_filter, null);
5790 // For some reason setting this as the "real" input type changes
5791 // the text view in some way that it doesn't work, and I don't
5792 // want to figure out why this is.
5793 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5794 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
5795 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
5796 mTextFilter.addTextChangedListener(this);
5797 }
5798 return mTextFilter;
5799 }
5800
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005801 /**
5802 * Clear the text filter.
5803 */
5804 public void clearTextFilter() {
5805 if (mFiltered) {
Romain Guyf6991302013-06-05 17:19:01 -07005806 getTextFilterInput().setText("");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005807 mFiltered = false;
5808 if (mPopup != null && mPopup.isShowing()) {
5809 dismissPopup();
5810 }
5811 }
5812 }
5813
5814 /**
5815 * Returns if the ListView currently has a text filter.
5816 */
5817 public boolean hasTextFilter() {
5818 return mFiltered;
5819 }
5820
Alan Viverette8fa327a2013-05-31 14:53:13 -07005821 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005822 public void onGlobalLayout() {
5823 if (isShown()) {
5824 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08005825 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005826 showPopup();
5827 }
5828 } else {
5829 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08005830 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005831 dismissPopup();
5832 }
5833 }
5834
5835 }
5836
5837 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005838 * For our text watcher that is associated with the text filter. Does
5839 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005840 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07005841 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005842 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5843 }
5844
5845 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005846 * For our text watcher that is associated with the text filter. Performs
5847 * the actual filtering as the text changes, and takes care of hiding and
5848 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005849 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07005850 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005851 public void onTextChanged(CharSequence s, int start, int before, int count) {
Romain Guyf6991302013-06-05 17:19:01 -07005852 if (isTextFilterEnabled()) {
5853 createTextFilter(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005854 int length = s.length();
5855 boolean showing = mPopup.isShowing();
5856 if (!showing && length > 0) {
5857 // Show the filter popup if necessary
5858 showPopup();
5859 mFiltered = true;
5860 } else if (showing && length == 0) {
5861 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005862 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005863 mFiltered = false;
5864 }
5865 if (mAdapter instanceof Filterable) {
5866 Filter f = ((Filterable) mAdapter).getFilter();
5867 // Filter should not be null when we reach this part
5868 if (f != null) {
5869 f.filter(s, this);
5870 } else {
5871 throw new IllegalStateException("You cannot call onTextChanged with a non "
5872 + "filterable adapter");
5873 }
5874 }
5875 }
5876 }
5877
5878 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005879 * For our text watcher that is associated with the text filter. Does
5880 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005881 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07005882 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005883 public void afterTextChanged(Editable s) {
5884 }
5885
Alan Viverette8fa327a2013-05-31 14:53:13 -07005886 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005887 public void onFilterComplete(int count) {
5888 if (mSelectedPosition < 0 && count > 0) {
5889 mResurrectToPosition = INVALID_POSITION;
5890 resurrectSelection();
5891 }
5892 }
5893
5894 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08005895 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
5896 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
5897 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
5898 }
5899
5900 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005901 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
5902 return new LayoutParams(p);
5903 }
5904
5905 @Override
5906 public LayoutParams generateLayoutParams(AttributeSet attrs) {
5907 return new AbsListView.LayoutParams(getContext(), attrs);
5908 }
5909
5910 @Override
5911 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
5912 return p instanceof AbsListView.LayoutParams;
5913 }
5914
5915 /**
5916 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
5917 * to the bottom to show new items.
5918 *
5919 * @param mode the transcript mode to set
5920 *
5921 * @see #TRANSCRIPT_MODE_DISABLED
5922 * @see #TRANSCRIPT_MODE_NORMAL
5923 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
5924 */
5925 public void setTranscriptMode(int mode) {
5926 mTranscriptMode = mode;
5927 }
5928
5929 /**
5930 * Returns the current transcript mode.
5931 *
5932 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
5933 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
5934 */
5935 public int getTranscriptMode() {
5936 return mTranscriptMode;
5937 }
5938
5939 @Override
5940 public int getSolidColor() {
5941 return mCacheColorHint;
5942 }
5943
5944 /**
5945 * 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 -07005946 * on top of a solid, single-color, opaque background.
5947 *
5948 * Zero means that what's behind this object is translucent (non solid) or is not made of a
5949 * single color. This hint will not affect any existing background drawable set on this view (
5950 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005951 *
5952 * @param color The background color
5953 */
5954 public void setCacheColorHint(int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08005955 if (color != mCacheColorHint) {
5956 mCacheColorHint = color;
5957 int count = getChildCount();
5958 for (int i = 0; i < count; i++) {
5959 getChildAt(i).setDrawingCacheBackgroundColor(color);
5960 }
5961 mRecycler.setCacheColorHint(color);
5962 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005963 }
5964
5965 /**
5966 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
5967 * on top of a solid, single-color, opaque background
5968 *
5969 * @return The cache color hint
5970 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07005971 @ViewDebug.ExportedProperty(category = "drawing")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005972 public int getCacheColorHint() {
5973 return mCacheColorHint;
5974 }
5975
5976 /**
5977 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
5978 * List. This includes views displayed on the screen as well as views stored in AbsListView's
5979 * internal view recycler.
5980 *
5981 * @param views A list into which to put the reclaimed views
5982 */
5983 public void reclaimViews(List<View> views) {
5984 int childCount = getChildCount();
5985 RecyclerListener listener = mRecycler.mRecyclerListener;
5986
5987 // Reclaim views on screen
5988 for (int i = 0; i < childCount; i++) {
5989 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07005990 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005991 // Don't reclaim header or footer views, or views that should be ignored
5992 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
5993 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07005994 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005995 if (listener != null) {
5996 // Pretend they went through the scrap heap
5997 listener.onMovedToScrapHeap(child);
5998 }
5999 }
6000 }
6001 mRecycler.reclaimScrapViews(views);
6002 removeAllViewsInLayout();
6003 }
6004
Adam Powell637d3372010-08-25 14:37:03 -07006005 private void finishGlows() {
6006 if (mEdgeGlowTop != null) {
6007 mEdgeGlowTop.finish();
6008 mEdgeGlowBottom.finish();
6009 }
6010 }
6011
Romain Guy13922e02009-05-12 17:56:14 -07006012 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006013 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6014 * through the specified intent.
6015 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6016 */
6017 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006018 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6019 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07006020 if (mRemoteAdapter != null) {
6021 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6022 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6023 mRemoteAdapter.getRemoteViewsServiceIntent());
6024 if (fcNew.equals(fcOld)) {
6025 return;
6026 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006027 }
Adam Cohen2148d432011-07-28 14:59:54 -07006028 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07006029 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07006030 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07006031 if (mRemoteAdapter.isDataReady()) {
6032 setAdapter(mRemoteAdapter);
6033 }
Winson Chung499cb9f2010-07-16 11:18:17 -07006034 }
6035
6036 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006037 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006038 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006039 * @param handler The OnClickHandler to use when inflating RemoteViews.
Alan Viverette0ebe81e2013-06-21 17:01:36 -07006040 *
Adam Cohena6a4cbc2012-09-26 17:36:40 -07006041 * @hide
6042 */
6043 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6044 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6045 // service handling the specified intent.
6046 if (mRemoteAdapter != null) {
6047 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6048 }
6049 }
6050
6051 /**
Adam Cohen2148d432011-07-28 14:59:54 -07006052 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6053 * connected yet.
6054 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006055 @Override
Adam Cohen2148d432011-07-28 14:59:54 -07006056 public void deferNotifyDataSetChanged() {
6057 mDeferNotifyDataSetChanged = true;
6058 }
6059
6060 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07006061 * Called back when the adapter connects to the RemoteViewsService.
6062 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006063 @Override
Winson Chung16c8d8a2011-01-20 16:19:33 -08006064 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07006065 if (mRemoteAdapter != mAdapter) {
6066 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07006067 if (mDeferNotifyDataSetChanged) {
6068 mRemoteAdapter.notifyDataSetChanged();
6069 mDeferNotifyDataSetChanged = false;
6070 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006071 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08006072 } else if (mRemoteAdapter != null) {
6073 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08006074 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07006075 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08006076 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07006077 }
6078
6079 /**
6080 * Called back when the adapter disconnects from the RemoteViewsService.
6081 */
Alan Viverette8fa327a2013-05-31 14:53:13 -07006082 @Override
Winson Chung499cb9f2010-07-16 11:18:17 -07006083 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08006084 // If the remote adapter disconnects, we keep it around
6085 // since the currently displayed items are still cached.
6086 // Further, we want the service to eventually reconnect
6087 // when necessary, as triggered by this view requesting
6088 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07006089 }
6090
6091 /**
Adam Cohenb9673922012-01-05 13:58:47 -08006092 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6093 * being displayed by the AbsListView.
6094 */
6095 void setVisibleRangeHint(int start, int end) {
6096 if (mRemoteAdapter != null) {
6097 mRemoteAdapter.setVisibleRangeHint(start, end);
6098 }
6099 }
6100
6101 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006102 * Sets the recycler listener to be notified whenever a View is set aside in
6103 * the recycler for later reuse. This listener can be used to free resources
6104 * associated to the View.
6105 *
6106 * @param listener The recycler listener to be notified of views set aside
6107 * in the recycler.
6108 *
6109 * @see android.widget.AbsListView.RecycleBin
6110 * @see android.widget.AbsListView.RecyclerListener
6111 */
6112 public void setRecyclerListener(RecyclerListener listener) {
6113 mRecycler.mRecyclerListener = listener;
6114 }
6115
Adam Powellb1f498a2011-01-18 20:43:23 -08006116 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6117 @Override
6118 public void onChanged() {
6119 super.onChanged();
6120 if (mFastScroller != null) {
6121 mFastScroller.onSectionsChanged();
6122 }
6123 }
6124
6125 @Override
6126 public void onInvalidated() {
6127 super.onInvalidated();
6128 if (mFastScroller != null) {
6129 mFastScroller.onSectionsChanged();
6130 }
6131 }
6132 }
6133
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006134 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006135 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6136 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6137 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6138 * selects and deselects list items.
6139 */
6140 public interface MultiChoiceModeListener extends ActionMode.Callback {
6141 /**
6142 * Called when an item is checked or unchecked during selection mode.
6143 *
6144 * @param mode The {@link ActionMode} providing the selection mode
6145 * @param position Adapter position of the item that was checked or unchecked
6146 * @param id Adapter ID of the item that was checked or unchecked
6147 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6148 * if the item is now unchecked.
6149 */
6150 public void onItemCheckedStateChanged(ActionMode mode,
6151 int position, long id, boolean checked);
6152 }
6153
6154 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6155 private MultiChoiceModeListener mWrapped;
6156
6157 public void setWrapped(MultiChoiceModeListener wrapped) {
6158 mWrapped = wrapped;
6159 }
6160
Adam Powella7981702012-08-24 12:43:41 -07006161 public boolean hasWrappedCallback() {
6162 return mWrapped != null;
6163 }
6164
Alan Viverette8fa327a2013-05-31 14:53:13 -07006165 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006166 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6167 if (mWrapped.onCreateActionMode(mode, menu)) {
6168 // Initialize checked graphic state?
6169 setLongClickable(false);
6170 return true;
6171 }
6172 return false;
6173 }
6174
Alan Viverette8fa327a2013-05-31 14:53:13 -07006175 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006176 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6177 return mWrapped.onPrepareActionMode(mode, menu);
6178 }
6179
Alan Viverette8fa327a2013-05-31 14:53:13 -07006180 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006181 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6182 return mWrapped.onActionItemClicked(mode, item);
6183 }
6184
Alan Viverette8fa327a2013-05-31 14:53:13 -07006185 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006186 public void onDestroyActionMode(ActionMode mode) {
6187 mWrapped.onDestroyActionMode(mode);
6188 mChoiceActionMode = null;
6189
6190 // Ending selection mode means deselecting everything.
6191 clearChoices();
6192
6193 mDataChanged = true;
6194 rememberSyncState();
6195 requestLayout();
6196
6197 setLongClickable(true);
6198 }
6199
Alan Viverette8fa327a2013-05-31 14:53:13 -07006200 @Override
Adam Powellf343e1b2010-08-13 18:27:04 -07006201 public void onItemCheckedStateChanged(ActionMode mode,
6202 int position, long id, boolean checked) {
6203 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6204
6205 // If there are no items selected we no longer need the selection mode.
6206 if (getCheckedItemCount() == 0) {
6207 mode.finish();
6208 }
6209 }
6210 }
6211
6212 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006213 * AbsListView extends LayoutParams to provide a place to hold the view type.
6214 */
6215 public static class LayoutParams extends ViewGroup.LayoutParams {
6216 /**
6217 * View type for this view, as returned by
6218 * {@link android.widget.Adapter#getItemViewType(int) }
6219 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006220 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006221 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6222 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6223 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006224 int viewType;
6225
The Android Open Source Project4df24232009-03-05 14:34:35 -08006226 /**
6227 * When this boolean is set, the view has been added to the AbsListView
6228 * at least once. It is used to know whether headers/footers have already
6229 * been added to the list view and whether they should be treated as
6230 * recycled views or not.
6231 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006232 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006233 boolean recycledHeaderFooter;
6234
Romain Guy0bf88592010-03-02 13:38:44 -08006235 /**
6236 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6237 * to obtain children views to measure itself. When doing so, the children
6238 * are not attached to the window, but put in the recycler which assumes
6239 * they've been attached before. Setting this flag will force the reused
6240 * view to be attached to the window rather than just attached to the
6241 * parent.
6242 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006243 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006244 boolean forceAdd;
6245
Dianne Hackborn079e2352010-10-18 17:02:43 -07006246 /**
6247 * The position the view was removed from when pulled out of the
6248 * scrap heap.
6249 * @hide
6250 */
6251 int scrappedFromPosition;
6252
Adam Powell539ee872012-02-03 19:00:49 -08006253 /**
6254 * The ID the view represents
6255 */
6256 long itemId = -1;
6257
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006258 public LayoutParams(Context c, AttributeSet attrs) {
6259 super(c, attrs);
6260 }
6261
6262 public LayoutParams(int w, int h) {
6263 super(w, h);
6264 }
6265
6266 public LayoutParams(int w, int h, int viewType) {
6267 super(w, h);
6268 this.viewType = viewType;
6269 }
6270
6271 public LayoutParams(ViewGroup.LayoutParams source) {
6272 super(source);
6273 }
6274 }
6275
6276 /**
6277 * A RecyclerListener is used to receive a notification whenever a View is placed
6278 * inside the RecycleBin's scrap heap. This listener is used to free resources
6279 * associated to Views placed in the RecycleBin.
6280 *
6281 * @see android.widget.AbsListView.RecycleBin
6282 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6283 */
6284 public static interface RecyclerListener {
6285 /**
6286 * Indicates that the specified View was moved into the recycler's scrap heap.
6287 * The view is not displayed on screen any more and any expensive resource
6288 * associated with the view should be discarded.
6289 *
6290 * @param view
6291 */
6292 void onMovedToScrapHeap(View view);
6293 }
6294
6295 /**
6296 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6297 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6298 * start of a layout. By construction, they are displaying current information. At the end of
6299 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6300 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6301 *
6302 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6303 * @see android.widget.AbsListView.RecyclerListener
6304 */
6305 class RecycleBin {
6306 private RecyclerListener mRecyclerListener;
6307
6308 /**
6309 * The position of the first view stored in mActiveViews.
6310 */
6311 private int mFirstActivePosition;
6312
6313 /**
6314 * Views that were on screen at the start of layout. This array is populated at the start of
6315 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6316 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6317 * view store in mFirstActivePosition.
6318 */
6319 private View[] mActiveViews = new View[0];
6320
6321 /**
6322 * Unsorted views that can be used by the adapter as a convert view.
6323 */
6324 private ArrayList<View>[] mScrapViews;
6325
6326 private int mViewTypeCount;
6327
6328 private ArrayList<View> mCurrentScrap;
6329
Adam Powell539ee872012-02-03 19:00:49 -08006330 private ArrayList<View> mSkippedScrap;
6331
6332 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006333 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006335 public void setViewTypeCount(int viewTypeCount) {
6336 if (viewTypeCount < 1) {
6337 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6338 }
6339 //noinspection unchecked
6340 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6341 for (int i = 0; i < viewTypeCount; i++) {
6342 scrapViews[i] = new ArrayList<View>();
6343 }
6344 mViewTypeCount = viewTypeCount;
6345 mCurrentScrap = scrapViews[0];
6346 mScrapViews = scrapViews;
6347 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006348
Adam Powellf3c2eda2010-03-16 17:31:01 -07006349 public void markChildrenDirty() {
6350 if (mViewTypeCount == 1) {
6351 final ArrayList<View> scrap = mCurrentScrap;
6352 final int scrapCount = scrap.size();
6353 for (int i = 0; i < scrapCount; i++) {
6354 scrap.get(i).forceLayout();
6355 }
6356 } else {
6357 final int typeCount = mViewTypeCount;
6358 for (int i = 0; i < typeCount; i++) {
6359 final ArrayList<View> scrap = mScrapViews[i];
6360 final int scrapCount = scrap.size();
6361 for (int j = 0; j < scrapCount; j++) {
6362 scrap.get(j).forceLayout();
6363 }
6364 }
6365 }
Adam Powell539ee872012-02-03 19:00:49 -08006366 if (mTransientStateViews != null) {
6367 final int count = mTransientStateViews.size();
6368 for (int i = 0; i < count; i++) {
6369 mTransientStateViews.valueAt(i).forceLayout();
6370 }
6371 }
Chet Haase72871322013-02-26 16:12:13 -07006372 if (mTransientStateViewsById != null) {
6373 final int count = mTransientStateViewsById.size();
6374 for (int i = 0; i < count; i++) {
6375 mTransientStateViewsById.valueAt(i).forceLayout();
6376 }
6377 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006378 }
Romain Guy0a637162009-05-29 14:43:54 -07006379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006380 public boolean shouldRecycleViewType(int viewType) {
6381 return viewType >= 0;
6382 }
6383
6384 /**
6385 * Clears the scrap heap.
6386 */
6387 void clear() {
6388 if (mViewTypeCount == 1) {
6389 final ArrayList<View> scrap = mCurrentScrap;
6390 final int scrapCount = scrap.size();
6391 for (int i = 0; i < scrapCount; i++) {
6392 removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
6393 }
6394 } else {
6395 final int typeCount = mViewTypeCount;
6396 for (int i = 0; i < typeCount; i++) {
6397 final ArrayList<View> scrap = mScrapViews[i];
6398 final int scrapCount = scrap.size();
6399 for (int j = 0; j < scrapCount; j++) {
6400 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6401 }
6402 }
6403 }
Adam Powell539ee872012-02-03 19:00:49 -08006404 if (mTransientStateViews != null) {
6405 mTransientStateViews.clear();
6406 }
Chet Haase72871322013-02-26 16:12:13 -07006407 if (mTransientStateViewsById != null) {
6408 mTransientStateViewsById.clear();
6409 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006410 }
6411
6412 /**
6413 * Fill ActiveViews with all of the children of the AbsListView.
6414 *
6415 * @param childCount The minimum number of views mActiveViews should hold
6416 * @param firstActivePosition The position of the first view that will be stored in
6417 * mActiveViews
6418 */
6419 void fillActiveViews(int childCount, int firstActivePosition) {
6420 if (mActiveViews.length < childCount) {
6421 mActiveViews = new View[childCount];
6422 }
6423 mFirstActivePosition = firstActivePosition;
6424
Romain Guyf6991302013-06-05 17:19:01 -07006425 //noinspection MismatchedReadAndWriteOfArray
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006426 final View[] activeViews = mActiveViews;
6427 for (int i = 0; i < childCount; i++) {
6428 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006429 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006430 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006431 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006432 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6433 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006434 activeViews[i] = child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006435 }
6436 }
6437 }
6438
6439 /**
6440 * Get the view corresponding to the specified position. The view will be removed from
6441 * mActiveViews if it is found.
6442 *
6443 * @param position The position to look up in mActiveViews
6444 * @return The view if it is found, null otherwise
6445 */
6446 View getActiveView(int position) {
6447 int index = position - mFirstActivePosition;
6448 final View[] activeViews = mActiveViews;
6449 if (index >=0 && index < activeViews.length) {
6450 final View match = activeViews[index];
6451 activeViews[index] = null;
6452 return match;
6453 }
6454 return null;
6455 }
6456
Adam Powell539ee872012-02-03 19:00:49 -08006457 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006458 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6459 long id = mAdapter.getItemId(position);
6460 View result = mTransientStateViewsById.get(id);
6461 mTransientStateViewsById.remove(id);
6462 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006463 }
Chet Haase72871322013-02-26 16:12:13 -07006464 if (mTransientStateViews != null) {
6465 final int index = mTransientStateViews.indexOfKey(position);
6466 if (index >= 0) {
6467 View result = mTransientStateViews.valueAt(index);
6468 mTransientStateViews.removeAt(index);
6469 return result;
6470 }
Adam Powell539ee872012-02-03 19:00:49 -08006471 }
Chet Haase72871322013-02-26 16:12:13 -07006472 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006473 }
6474
6475 /**
6476 * Dump any currently saved views with transient state.
6477 */
6478 void clearTransientStateViews() {
6479 if (mTransientStateViews != null) {
6480 mTransientStateViews.clear();
6481 }
Chet Haase72871322013-02-26 16:12:13 -07006482 if (mTransientStateViewsById != null) {
6483 mTransientStateViewsById.clear();
6484 }
Adam Powell539ee872012-02-03 19:00:49 -08006485 }
6486
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006487 /**
6488 * @return A view from the ScrapViews collection. These are unordered.
6489 */
6490 View getScrapView(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006491 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006492 return retrieveFromScrap(mCurrentScrap, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006493 } else {
6494 int whichScrap = mAdapter.getItemViewType(position);
6495 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006496 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006497 }
6498 }
6499 return null;
6500 }
6501
6502 /**
Alan Viveretted44696c2013-07-18 10:37:15 -07006503 * Puts a view into the list of scrap views.
6504 * <p>
6505 * If the list data hasn't changed or the adapter has stable IDs, views
6506 * with transient state will be preserved for later retrieval.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006507 *
6508 * @param scrap The view to add
Alan Viveretted44696c2013-07-18 10:37:15 -07006509 * @param position The view's position within its parent
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006510 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006511 void addScrapView(View scrap, int position) {
Alan Viveretted44696c2013-07-18 10:37:15 -07006512 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006513 if (lp == null) {
6514 return;
6515 }
6516
Adam Powell539ee872012-02-03 19:00:49 -08006517 lp.scrappedFromPosition = position;
6518
Alan Viveretted44696c2013-07-18 10:37:15 -07006519 // Don't scrap header or footer views, or views that should
6520 // otherwise not be recycled.
6521 final int viewType = lp.viewType;
6522 if (!shouldRecycleViewType(viewType)) {
6523 return;
6524 }
6525
6526 scrap.dispatchStartTemporaryDetach();
6527
6528 // Don't scrap views that have transient state.
Adam Powell539ee872012-02-03 19:00:49 -08006529 final boolean scrapHasTransientState = scrap.hasTransientState();
Alan Viveretted44696c2013-07-18 10:37:15 -07006530 if (scrapHasTransientState) {
6531 if (mAdapter != null && mAdapterHasStableIds) {
6532 // If the adapter has stable IDs, we can reuse the view for
6533 // the same data.
6534 if (mTransientStateViewsById == null) {
6535 mTransientStateViewsById = new LongSparseArray<View>();
6536 }
6537 mTransientStateViewsById.put(lp.itemId, scrap);
6538 } else if (!mDataChanged) {
6539 // If the data hasn't changed, we can reuse the views at
6540 // their old positions.
6541 if (mTransientStateViews == null) {
6542 mTransientStateViews = new SparseArray<View>();
6543 }
6544 mTransientStateViews.put(position, scrap);
6545 } else {
6546 // Otherwise, we'll have to remove the view and start over.
Adam Powell539ee872012-02-03 19:00:49 -08006547 if (mSkippedScrap == null) {
6548 mSkippedScrap = new ArrayList<View>();
6549 }
6550 mSkippedScrap.add(scrap);
6551 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006552 } else {
Alan Viveretted44696c2013-07-18 10:37:15 -07006553 if (mViewTypeCount == 1) {
6554 mCurrentScrap.add(scrap);
6555 } else {
6556 mScrapViews[viewType].add(scrap);
6557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006558
Alan Viveretted44696c2013-07-18 10:37:15 -07006559 scrap.setAccessibilityDelegate(null);
6560
6561 if (mRecyclerListener != null) {
6562 mRecyclerListener.onMovedToScrapHeap(scrap);
6563 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006564 }
6565 }
6566
6567 /**
Adam Powell539ee872012-02-03 19:00:49 -08006568 * Finish the removal of any views that skipped the scrap heap.
6569 */
6570 void removeSkippedScrap() {
6571 if (mSkippedScrap == null) {
6572 return;
6573 }
6574 final int count = mSkippedScrap.size();
6575 for (int i = 0; i < count; i++) {
6576 removeDetachedView(mSkippedScrap.get(i), false);
6577 }
6578 mSkippedScrap.clear();
6579 }
6580
6581 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006582 * Move all views remaining in mActiveViews to mScrapViews.
6583 */
6584 void scrapActiveViews() {
6585 final View[] activeViews = mActiveViews;
6586 final boolean hasListener = mRecyclerListener != null;
6587 final boolean multipleScraps = mViewTypeCount > 1;
6588
6589 ArrayList<View> scrapViews = mCurrentScrap;
6590 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006591 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006592 final View victim = activeViews[i];
6593 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006594 final AbsListView.LayoutParams lp
6595 = (AbsListView.LayoutParams) victim.getLayoutParams();
6596 int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006597
6598 activeViews[i] = null;
6599
Adam Powell539ee872012-02-03 19:00:49 -08006600 final boolean scrapHasTransientState = victim.hasTransientState();
6601 if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006602 // Do not move views that should be ignored
Romain Guyb7e0f792013-06-07 15:36:49 -07006603 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
Adam Powell539ee872012-02-03 19:00:49 -08006604 scrapHasTransientState) {
Romain Guy9b1bb812010-02-26 14:14:13 -08006605 removeDetachedView(victim, false);
6606 }
Adam Powell539ee872012-02-03 19:00:49 -08006607 if (scrapHasTransientState) {
Chet Haase72871322013-02-26 16:12:13 -07006608 if (mAdapter != null && mAdapterHasStableIds) {
6609 if (mTransientStateViewsById == null) {
6610 mTransientStateViewsById = new LongSparseArray<View>();
6611 }
6612 long id = mAdapter.getItemId(mFirstActivePosition + i);
6613 mTransientStateViewsById.put(id, victim);
6614 } else {
6615 if (mTransientStateViews == null) {
6616 mTransientStateViews = new SparseArray<View>();
6617 }
6618 mTransientStateViews.put(mFirstActivePosition + i, victim);
Adam Powell539ee872012-02-03 19:00:49 -08006619 }
Adam Powell539ee872012-02-03 19:00:49 -08006620 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006621 continue;
6622 }
6623
6624 if (multipleScraps) {
6625 scrapViews = mScrapViews[whichScrap];
6626 }
Romain Guya440b002010-02-24 15:57:54 -08006627 victim.dispatchStartTemporaryDetach();
Dianne Hackborn079e2352010-10-18 17:02:43 -07006628 lp.scrappedFromPosition = mFirstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006629 scrapViews.add(victim);
6630
alanvc1d7e772012-05-08 14:47:24 -07006631 victim.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006632 if (hasListener) {
6633 mRecyclerListener.onMovedToScrapHeap(victim);
6634 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006635 }
6636 }
6637
6638 pruneScrapViews();
6639 }
6640
6641 /**
6642 * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
6643 * (This can happen if an adapter does not recycle its views).
6644 */
6645 private void pruneScrapViews() {
6646 final int maxViews = mActiveViews.length;
6647 final int viewTypeCount = mViewTypeCount;
6648 final ArrayList<View>[] scrapViews = mScrapViews;
6649 for (int i = 0; i < viewTypeCount; ++i) {
6650 final ArrayList<View> scrapPile = scrapViews[i];
6651 int size = scrapPile.size();
6652 final int extras = size - maxViews;
6653 size--;
6654 for (int j = 0; j < extras; j++) {
6655 removeDetachedView(scrapPile.remove(size--), false);
6656 }
6657 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006658
6659 if (mTransientStateViews != null) {
6660 for (int i = 0; i < mTransientStateViews.size(); i++) {
6661 final View v = mTransientStateViews.valueAt(i);
6662 if (!v.hasTransientState()) {
6663 mTransientStateViews.removeAt(i);
6664 i--;
6665 }
6666 }
6667 }
Chet Haase72871322013-02-26 16:12:13 -07006668 if (mTransientStateViewsById != null) {
6669 for (int i = 0; i < mTransientStateViewsById.size(); i++) {
6670 final View v = mTransientStateViewsById.valueAt(i);
6671 if (!v.hasTransientState()) {
6672 mTransientStateViewsById.removeAt(i);
6673 i--;
6674 }
6675 }
6676 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006677 }
6678
6679 /**
6680 * Puts all views in the scrap heap into the supplied list.
6681 */
6682 void reclaimScrapViews(List<View> views) {
6683 if (mViewTypeCount == 1) {
6684 views.addAll(mCurrentScrap);
6685 } else {
6686 final int viewTypeCount = mViewTypeCount;
6687 final ArrayList<View>[] scrapViews = mScrapViews;
6688 for (int i = 0; i < viewTypeCount; ++i) {
6689 final ArrayList<View> scrapPile = scrapViews[i];
6690 views.addAll(scrapPile);
6691 }
6692 }
6693 }
Romain Guy52e2ef82010-01-14 12:11:48 -08006694
6695 /**
6696 * Updates the cache color hint of all known views.
6697 *
6698 * @param color The new cache color hint.
6699 */
6700 void setCacheColorHint(int color) {
6701 if (mViewTypeCount == 1) {
6702 final ArrayList<View> scrap = mCurrentScrap;
6703 final int scrapCount = scrap.size();
6704 for (int i = 0; i < scrapCount; i++) {
6705 scrap.get(i).setDrawingCacheBackgroundColor(color);
6706 }
6707 } else {
6708 final int typeCount = mViewTypeCount;
6709 for (int i = 0; i < typeCount; i++) {
6710 final ArrayList<View> scrap = mScrapViews[i];
6711 final int scrapCount = scrap.size();
6712 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07006713 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08006714 }
6715 }
6716 }
6717 // Just in case this is called during a layout pass
6718 final View[] activeViews = mActiveViews;
6719 final int count = activeViews.length;
6720 for (int i = 0; i < count; ++i) {
6721 final View victim = activeViews[i];
6722 if (victim != null) {
6723 victim.setDrawingCacheBackgroundColor(color);
6724 }
6725 }
6726 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006727 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006728
6729 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6730 int size = scrapViews.size();
6731 if (size > 0) {
6732 // See if we still have a view for this position.
6733 for (int i=0; i<size; i++) {
6734 View view = scrapViews.get(i);
6735 if (((AbsListView.LayoutParams)view.getLayoutParams())
6736 .scrappedFromPosition == position) {
6737 scrapViews.remove(i);
6738 return view;
6739 }
6740 }
6741 return scrapViews.remove(size - 1);
6742 } else {
6743 return null;
6744 }
6745 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006746}