blob: 219891c4627b247e3ea96d0a7bdcf09ab4ab126b [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;
28import android.os.Handler;
29import android.os.Parcel;
30import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080031import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.text.Editable;
33import android.text.TextUtils;
34import android.text.TextWatcher;
35import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080036import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070037import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080038import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070039import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070040import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070041import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070042import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.Gravity;
44import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080045import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.view.KeyEvent;
47import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070048import android.view.Menu;
49import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.view.MotionEvent;
51import android.view.VelocityTracker;
52import android.view.View;
53import android.view.ViewConfiguration;
54import android.view.ViewDebug;
55import android.view.ViewGroup;
Michael Jurka13451a42011-08-22 15:54:21 -070056import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070058import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070059import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070060import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070061import android.view.animation.Interpolator;
62import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070063import android.view.inputmethod.BaseInputConnection;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070064import android.view.inputmethod.EditorInfo;
65import android.view.inputmethod.InputConnection;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070066import android.view.inputmethod.InputConnectionWrapper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070068import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
Adam Cohen335c3b62012-07-24 17:18:16 -070070import com.android.internal.R;
71
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072import java.util.ArrayList;
73import java.util.List;
74
75/**
Romain Guyd6a463a2009-05-21 23:10:10 -070076 * Base class that can be used to implement virtualized lists of items. A list does
77 * not have a spatial definition here. For instance, subclases of this class can
78 * 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 -080079 *
80 * @attr ref android.R.styleable#AbsListView_listSelector
81 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
82 * @attr ref android.R.styleable#AbsListView_stackFromBottom
83 * @attr ref android.R.styleable#AbsListView_scrollingCache
84 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
85 * @attr ref android.R.styleable#AbsListView_transcriptMode
86 * @attr ref android.R.styleable#AbsListView_cacheColorHint
87 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
88 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -070089 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 */
91public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
92 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -070093 ViewTreeObserver.OnTouchModeChangeListener,
94 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095
Romain Guy9d849a22012-03-14 16:41:42 -070096 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -080097 private static final String TAG = "AbsListView";
98
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 /**
100 * Disables the transcript mode.
101 *
102 * @see #setTranscriptMode(int)
103 */
104 public static final int TRANSCRIPT_MODE_DISABLED = 0;
105 /**
106 * The list will automatically scroll to the bottom when a data set change
107 * notification is received and only if the last item is already visible
108 * on screen.
109 *
110 * @see #setTranscriptMode(int)
111 */
112 public static final int TRANSCRIPT_MODE_NORMAL = 1;
113 /**
114 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700115 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 *
117 * @see #setTranscriptMode(int)
118 */
119 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
120
121 /**
122 * Indicates that we are not in the middle of a touch gesture
123 */
124 static final int TOUCH_MODE_REST = -1;
125
126 /**
127 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
128 * scroll gesture.
129 */
130 static final int TOUCH_MODE_DOWN = 0;
131
132 /**
133 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
134 * is a longpress
135 */
136 static final int TOUCH_MODE_TAP = 1;
137
138 /**
139 * Indicates we have waited for everything we can wait for, but the user's finger is still down
140 */
141 static final int TOUCH_MODE_DONE_WAITING = 2;
142
143 /**
144 * Indicates the touch gesture is a scroll
145 */
146 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800147
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 /**
149 * Indicates the view is in the process of being flung
150 */
151 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700152
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 /**
Adam Powell637d3372010-08-25 14:37:03 -0700154 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
155 */
156 static final int TOUCH_MODE_OVERSCROLL = 5;
157
158 /**
159 * Indicates the view is being flung outside of normal content bounds
160 * and will spring back.
161 */
162 static final int TOUCH_MODE_OVERFLING = 6;
163
164 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 * Regular layout - usually an unsolicited layout from the view system
166 */
167 static final int LAYOUT_NORMAL = 0;
168
169 /**
170 * Show the first item
171 */
172 static final int LAYOUT_FORCE_TOP = 1;
173
174 /**
175 * Force the selected item to be on somewhere on the screen
176 */
177 static final int LAYOUT_SET_SELECTION = 2;
178
179 /**
180 * Show the last item
181 */
182 static final int LAYOUT_FORCE_BOTTOM = 3;
183
184 /**
185 * Make a mSelectedItem appear in a specific location and build the rest of
186 * the views from there. The top is specified by mSpecificTop.
187 */
188 static final int LAYOUT_SPECIFIC = 4;
189
190 /**
191 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
192 * at mSpecificTop
193 */
194 static final int LAYOUT_SYNC = 5;
195
196 /**
197 * Layout as a result of using the navigation keys
198 */
199 static final int LAYOUT_MOVE_SELECTION = 6;
200
201 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700202 * Normal list that does not indicate choices
203 */
204 public static final int CHOICE_MODE_NONE = 0;
205
206 /**
207 * The list allows up to one choice
208 */
209 public static final int CHOICE_MODE_SINGLE = 1;
210
211 /**
212 * The list allows multiple choices
213 */
214 public static final int CHOICE_MODE_MULTIPLE = 2;
215
216 /**
217 * The list allows multiple choices in a modal selection mode
218 */
219 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
220
221 /**
222 * Controls if/how the user may choose/check items in the list
223 */
224 int mChoiceMode = CHOICE_MODE_NONE;
225
226 /**
227 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
228 */
229 ActionMode mChoiceActionMode;
230
231 /**
232 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
233 * a few extra actions around what application code does.
234 */
235 MultiChoiceModeWrapper mMultiChoiceModeCallback;
236
237 /**
238 * Running count of how many items are currently checked
239 */
240 int mCheckedItemCount;
241
242 /**
243 * Running state of which positions are currently checked
244 */
245 SparseBooleanArray mCheckStates;
246
247 /**
Adam Powell14c08042011-10-06 19:46:18 -0700248 * Running state of which IDs are currently checked.
249 * If there is a value for a given key, the checked state for that ID is true
250 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700251 */
Adam Powell14c08042011-10-06 19:46:18 -0700252 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700253
254 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 * Controls how the next layout will happen
256 */
257 int mLayoutMode = LAYOUT_NORMAL;
258
259 /**
260 * Should be used by subclasses to listen to changes in the dataset
261 */
262 AdapterDataSetObserver mDataSetObserver;
263
264 /**
265 * The adapter containing the data to be displayed by this view
266 */
267 ListAdapter mAdapter;
268
269 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700270 * The remote adapter containing the data to be displayed by this view to be set
271 */
272 private RemoteViewsAdapter mRemoteAdapter;
273
274 /**
Adam Powell539ee872012-02-03 19:00:49 -0800275 * If mAdapter != null, whenever this is true the adapter has stable IDs.
276 */
277 boolean mAdapterHasStableIds;
278
279 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700280 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
281 */
282 private boolean mDeferNotifyDataSetChanged = false;
283
284 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 * Indicates whether the list selector should be drawn on top of the children or behind
286 */
287 boolean mDrawSelectorOnTop = false;
288
289 /**
290 * The drawable used to draw the selector
291 */
292 Drawable mSelector;
293
294 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700295 * The current position of the selector in the list.
296 */
297 int mSelectorPosition = INVALID_POSITION;
298
299 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 * Defines the selector's location and dimension at drawing time
301 */
302 Rect mSelectorRect = new Rect();
303
304 /**
305 * The data set used to store unused views that should be reused during the next layout
306 * to avoid creating new ones
307 */
308 final RecycleBin mRecycler = new RecycleBin();
309
310 /**
311 * The selection's left padding
312 */
313 int mSelectionLeftPadding = 0;
314
315 /**
316 * The selection's top padding
317 */
318 int mSelectionTopPadding = 0;
319
320 /**
321 * The selection's right padding
322 */
323 int mSelectionRightPadding = 0;
324
325 /**
326 * The selection's bottom padding
327 */
328 int mSelectionBottomPadding = 0;
329
330 /**
331 * This view's padding
332 */
333 Rect mListPadding = new Rect();
334
335 /**
336 * Subclasses must retain their measure spec from onMeasure() into this member
337 */
338 int mWidthMeasureSpec = 0;
339
340 /**
341 * The top scroll indicator
342 */
343 View mScrollUp;
344
345 /**
346 * The down scroll indicator
347 */
348 View mScrollDown;
349
350 /**
351 * When the view is scrolling, this flag is set to true to indicate subclasses that
352 * the drawing cache was enabled on the children
353 */
354 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800355 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356
357 /**
358 * The position of the view that received the down motion event
359 */
360 int mMotionPosition;
361
362 /**
363 * The offset to the top of the mMotionPosition view when the down motion event was received
364 */
365 int mMotionViewOriginalTop;
366
367 /**
368 * The desired offset to the top of the mMotionPosition view after a scroll
369 */
370 int mMotionViewNewTop;
371
372 /**
373 * The X value associated with the the down motion event
374 */
375 int mMotionX;
376
377 /**
378 * The Y value associated with the the down motion event
379 */
380 int mMotionY;
381
382 /**
383 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
384 * TOUCH_MODE_DONE_WAITING
385 */
386 int mTouchMode = TOUCH_MODE_REST;
387
388 /**
389 * Y value from on the previous motion event (if any)
390 */
391 int mLastY;
392
393 /**
394 * How far the finger moved before we started scrolling
395 */
396 int mMotionCorrection;
397
398 /**
399 * Determines speed during touch scrolling
400 */
401 private VelocityTracker mVelocityTracker;
402
403 /**
404 * Handles one frame of a fling
405 */
406 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800407
Adam Powell45803472010-01-25 15:10:44 -0800408 /**
409 * Handles scrolling between positions within the list.
410 */
Adam Powell1fa179ef2012-04-12 15:01:40 -0700411 PositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412
413 /**
414 * The offset in pixels form the top of the AdapterView to the top
415 * of the currently selected view. Used to save and restore state.
416 */
417 int mSelectedTop = 0;
418
419 /**
420 * Indicates whether the list is stacked from the bottom edge or
421 * the top edge.
422 */
423 boolean mStackFromBottom;
424
425 /**
426 * When set to true, the list automatically discards the children's
427 * bitmap cache after scrolling.
428 */
429 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700430
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 /**
432 * Whether or not to enable the fast scroll feature on this list
433 */
434 boolean mFastScrollEnabled;
435
436 /**
437 * Optional callback to notify client when scroll position has changed
438 */
439 private OnScrollListener mOnScrollListener;
440
441 /**
442 * Keeps track of our accessory window
443 */
444 PopupWindow mPopup;
445
446 /**
447 * Used with type filter window
448 */
449 EditText mTextFilter;
450
451 /**
452 * Indicates whether to use pixels-based or position-based scrollbar
453 * properties.
454 */
455 private boolean mSmoothScrollbarEnabled = true;
456
457 /**
458 * Indicates that this view supports filtering
459 */
460 private boolean mTextFilterEnabled;
461
462 /**
463 * Indicates that this view is currently displaying a filtered view of the data
464 */
465 private boolean mFiltered;
466
467 /**
468 * Rectangle used for hit testing children
469 */
470 private Rect mTouchFrame;
471
472 /**
473 * The position to resurrect the selected position to.
474 */
475 int mResurrectToPosition = INVALID_POSITION;
476
477 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800478
Adam Powell0b8bb422010-02-08 14:30:45 -0800479 /**
Adam Powell637d3372010-08-25 14:37:03 -0700480 * Maximum distance to record overscroll
481 */
482 int mOverscrollMax;
483
484 /**
485 * Content height divided by this is the overscroll limit.
486 */
487 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
488
489 /**
Adam Powell14c08042011-10-06 19:46:18 -0700490 * How many positions in either direction we will search to try to
491 * find a checked item with a stable ID that moved position across
492 * a data set change. If the item isn't found it will be unselected.
493 */
494 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
495
496 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 * Used to request a layout when we changed touch mode
498 */
499 private static final int TOUCH_MODE_UNKNOWN = -1;
500 private static final int TOUCH_MODE_ON = 0;
501 private static final int TOUCH_MODE_OFF = 1;
502
503 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
504
505 private static final boolean PROFILE_SCROLLING = false;
506 private boolean mScrollProfilingStarted = false;
507
508 private static final boolean PROFILE_FLINGING = false;
509 private boolean mFlingProfilingStarted = false;
510
511 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800512 * The StrictMode "critical time span" objects to catch animation
513 * stutters. Non-null when a time-sensitive animation is
514 * in-flight. Must call finish() on them when done animating.
515 * These are no-ops on user builds.
516 */
517 private StrictMode.Span mScrollStrictSpan = null;
518 private StrictMode.Span mFlingStrictSpan = null;
519
520 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 * The last CheckForLongPress runnable we posted, if any
522 */
523 private CheckForLongPress mPendingCheckForLongPress;
524
525 /**
526 * The last CheckForTap runnable we posted, if any
527 */
528 private Runnable mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700529
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 /**
531 * The last CheckForKeyLongPress runnable we posted, if any
532 */
533 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
534
535 /**
536 * Acts upon click
537 */
538 private AbsListView.PerformClick mPerformClick;
539
540 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800541 * Delayed action for touch mode.
542 */
543 private Runnable mTouchModeReset;
544
545 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 * This view is in transcript mode -- it shows the bottom of the list when the data
547 * changes
548 */
549 private int mTranscriptMode;
550
551 /**
552 * Indicates that this list is always drawn on top of a solid, single-color, opaque
553 * background
554 */
555 private int mCacheColorHint;
556
557 /**
558 * The select child's view (from the adapter's getView) is enabled.
559 */
560 private boolean mIsChildViewEnabled;
561
562 /**
563 * The last scroll state reported to clients through {@link OnScrollListener}.
564 */
565 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
566
567 /**
568 * Helper object that renders and controls the fast scroll thumb.
569 */
570 private FastScroller mFastScroller;
571
Romain Guyd6a463a2009-05-21 23:10:10 -0700572 private boolean mGlobalLayoutListenerAddedFilter;
573
574 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 private float mDensityScale;
576
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700577 private InputConnection mDefInputConnection;
578 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700579
580 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700581 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700582 private int mMinimumVelocity;
583 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700584 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800585
Romain Guy21875052010-01-06 18:48:08 -0800586 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800587
Romain Guy24562482010-02-01 14:56:19 -0800588 // True when the popup should be hidden because of a call to
589 // dispatchDisplayHint()
590 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800591
Adam Powell4cd47702010-02-25 11:21:14 -0800592 /**
593 * ID of the active pointer. This is used to retain consistency during
594 * drags/flings if multiple pointers are used.
595 */
596 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800597
Adam Powell4cd47702010-02-25 11:21:14 -0800598 /**
599 * Sentinel value for no current active pointer.
600 * Used by {@link #mActivePointerId}.
601 */
602 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700603
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 /**
Adam Powell637d3372010-08-25 14:37:03 -0700605 * Maximum distance to overscroll by during edge effects
606 */
607 int mOverscrollDistance;
608
609 /**
610 * Maximum distance to overfling during edge effects
611 */
612 int mOverflingDistance;
613
614 // These two EdgeGlows are always set and used together.
615 // Checking one for null is as good as checking both.
616
617 /**
618 * Tracks the state of the top edge glow.
619 */
Adam Powell89935e42011-08-31 14:26:12 -0700620 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700621
622 /**
623 * Tracks the state of the bottom edge glow.
624 */
Adam Powell89935e42011-08-31 14:26:12 -0700625 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700626
627 /**
628 * An estimate of how many pixels are between the top of the list and
629 * the top of the first position in the adapter, based on the last time
630 * we saw it. Used to hint where to draw edge glows.
631 */
632 private int mFirstPositionDistanceGuess;
633
634 /**
635 * An estimate of how many pixels are between the bottom of the list and
636 * the bottom of the last position in the adapter, based on the last time
637 * we saw it. Used to hint where to draw edge glows.
638 */
639 private int mLastPositionDistanceGuess;
640
641 /**
642 * Used for determining when to cancel out of overscroll.
643 */
644 private int mDirection = 0;
645
646 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800647 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
648 * the bottom correctly on resizes.
649 */
650 private boolean mForceTranscriptScroll;
651
Adam Powell07d6f7b2011-03-02 14:27:30 -0800652 private int mGlowPaddingLeft;
653 private int mGlowPaddingRight;
654
alanvc1d7e772012-05-08 14:47:24 -0700655 /**
656 * Used for interacting with list items from an accessibility service.
657 */
658 private ListItemAccessibilityDelegate mAccessibilityDelegate;
659
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700660 private int mLastAccessibilityScrollEventFromIndex;
661 private int mLastAccessibilityScrollEventToIndex;
662
Adam Powellda13dba2010-12-05 13:47:23 -0800663 /**
Adam Powellb3750132011-08-08 23:29:12 -0700664 * Track if we are currently attached to a window.
665 */
Adam Powella2b986e2011-09-14 14:21:33 -0700666 boolean mIsAttached;
Adam Powellb3750132011-08-08 23:29:12 -0700667
668 /**
Adam Powellee78b172011-08-16 16:39:20 -0700669 * Track the item count from the last time we handled a data change.
670 */
671 private int mLastHandledItemCount;
672
673 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700674 * Used for smooth scrolling at a consistent rate
675 */
676 static final Interpolator sLinearInterpolator = new LinearInterpolator();
677
678 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700679 * The saved state that we will be restoring from when we next sync.
680 * Kept here so that if we happen to be asked to save our state before
681 * the sync happens, we can return this existing data rather than losing
682 * it.
683 */
684 private SavedState mPendingSync;
685
686 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 * Interface definition for a callback to be invoked when the list or grid
688 * has been scrolled.
689 */
690 public interface OnScrollListener {
691
692 /**
693 * The view is not scrolling. Note navigating the list using the trackball counts as
694 * being in the idle state since these transitions are not animated.
695 */
696 public static int SCROLL_STATE_IDLE = 0;
697
698 /**
699 * The user is scrolling using touch, and their finger is still on the screen
700 */
701 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
702
703 /**
704 * The user had previously been scrolling using touch and had performed a fling. The
705 * animation is now coasting to a stop
706 */
707 public static int SCROLL_STATE_FLING = 2;
708
709 /**
710 * Callback method to be invoked while the list view or grid view is being scrolled. If the
711 * view is being scrolled, this method will be called before the next frame of the scroll is
712 * rendered. In particular, it will be called before any calls to
713 * {@link Adapter#getView(int, View, ViewGroup)}.
714 *
715 * @param view The view whose scroll state is being reported
716 *
717 * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
718 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
719 */
720 public void onScrollStateChanged(AbsListView view, int scrollState);
721
722 /**
723 * Callback method to be invoked when the list or grid has been scrolled. This will be
724 * called after the scroll has completed
725 * @param view The view whose scroll state is being reported
726 * @param firstVisibleItem the index of the first visible cell (ignore if
727 * visibleItemCount == 0)
728 * @param visibleItemCount the number of visible cells
729 * @param totalItemCount the number of items in the list adaptor
730 */
731 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
732 int totalItemCount);
733 }
734
Dianne Hackborne2136772010-11-04 15:08:59 -0700735 /**
736 * The top-level view of a list item can implement this interface to allow
737 * itself to modify the bounds of the selection shown for that item.
738 */
739 public interface SelectionBoundsAdjuster {
740 /**
741 * Called to allow the list item to adjust the bounds shown for
742 * its selection.
743 *
744 * @param bounds On call, this contains the bounds the list has
745 * selected for the item (that is the bounds of the entire view). The
746 * values can be modified as desired.
747 */
748 public void adjustListItemSelectionBounds(Rect bounds);
749 }
750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 public AbsListView(Context context) {
752 super(context);
753 initAbsListView();
754
755 setVerticalScrollBarEnabled(true);
756 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
757 initializeScrollbars(a);
758 a.recycle();
759 }
760
761 public AbsListView(Context context, AttributeSet attrs) {
762 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
763 }
764
765 public AbsListView(Context context, AttributeSet attrs, int defStyle) {
766 super(context, attrs, defStyle);
767 initAbsListView();
768
769 TypedArray a = context.obtainStyledAttributes(attrs,
770 com.android.internal.R.styleable.AbsListView, defStyle, 0);
771
772 Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
773 if (d != null) {
774 setSelector(d);
775 }
776
777 mDrawSelectorOnTop = a.getBoolean(
778 com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
779
780 boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
781 setStackFromBottom(stackFromBottom);
782
783 boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
784 setScrollingCacheEnabled(scrollingCacheEnabled);
785
786 boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
787 setTextFilterEnabled(useTextFilter);
788
789 int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
790 TRANSCRIPT_MODE_DISABLED);
791 setTranscriptMode(transcriptMode);
792
793 int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
794 setCacheColorHint(color);
Romain Guy0a637162009-05-29 14:43:54 -0700795
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
797 setFastScrollEnabled(enableFastScroll);
798
799 boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
800 setSmoothScrollbarEnabled(smoothScrollbar);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800801
Adam Powellf343e1b2010-08-13 18:27:04 -0700802 setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
Adam Powell20232d02010-12-08 21:08:53 -0800803 setFastScrollAlwaysVisible(
804 a.getBoolean(R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700805
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 a.recycle();
807 }
808
Romain Guyd6a463a2009-05-21 23:10:10 -0700809 private void initAbsListView() {
810 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700811 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700812 setFocusableInTouchMode(true);
813 setWillNotDraw(false);
814 setAlwaysDrawnWithCacheEnabled(false);
815 setScrollingCacheEnabled(true);
816
Romain Guy4296fc42009-07-06 11:48:52 -0700817 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
818 mTouchSlop = configuration.getScaledTouchSlop();
819 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
820 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700821 mOverscrollDistance = configuration.getScaledOverscrollDistance();
822 mOverflingDistance = configuration.getScaledOverflingDistance();
823
Romain Guyd6a463a2009-05-21 23:10:10 -0700824 mDensityScale = getContext().getResources().getDisplayMetrics().density;
825 }
Romain Guy0a637162009-05-29 14:43:54 -0700826
Adam Powell637d3372010-08-25 14:37:03 -0700827 @Override
828 public void setOverScrollMode(int mode) {
829 if (mode != OVER_SCROLL_NEVER) {
830 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800831 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700832 mEdgeGlowTop = new EdgeEffect(context);
833 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700834 }
835 } else {
836 mEdgeGlowTop = null;
837 mEdgeGlowBottom = null;
838 }
839 super.setOverScrollMode(mode);
840 }
841
Romain Guyd6a463a2009-05-21 23:10:10 -0700842 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700843 * {@inheritDoc}
844 */
845 @Override
846 public void setAdapter(ListAdapter adapter) {
847 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800848 mAdapterHasStableIds = mAdapter.hasStableIds();
849 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700850 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700851 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700852 }
853 }
854
855 if (mCheckStates != null) {
856 mCheckStates.clear();
857 }
858
859 if (mCheckedIdStates != null) {
860 mCheckedIdStates.clear();
861 }
862 }
863
864 /**
865 * Returns the number of items currently selected. This will only be valid
866 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
867 *
868 * <p>To determine the specific items that are currently selected, use one of
869 * the <code>getChecked*</code> methods.
870 *
871 * @return The number of items currently selected
872 *
873 * @see #getCheckedItemPosition()
874 * @see #getCheckedItemPositions()
875 * @see #getCheckedItemIds()
876 */
877 public int getCheckedItemCount() {
878 return mCheckedItemCount;
879 }
880
881 /**
882 * Returns the checked state of the specified position. The result is only
883 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
884 * or {@link #CHOICE_MODE_MULTIPLE}.
885 *
886 * @param position The item whose checked state to return
887 * @return The item's checked state or <code>false</code> if choice mode
888 * is invalid
889 *
890 * @see #setChoiceMode(int)
891 */
892 public boolean isItemChecked(int position) {
893 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
894 return mCheckStates.get(position);
895 }
896
897 return false;
898 }
899
900 /**
901 * Returns the currently checked item. The result is only valid if the choice
902 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
903 *
904 * @return The position of the currently checked item or
905 * {@link #INVALID_POSITION} if nothing is selected
906 *
907 * @see #setChoiceMode(int)
908 */
909 public int getCheckedItemPosition() {
910 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
911 return mCheckStates.keyAt(0);
912 }
913
914 return INVALID_POSITION;
915 }
916
917 /**
918 * Returns the set of checked items in the list. The result is only valid if
919 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
920 *
921 * @return A SparseBooleanArray which will return true for each call to
922 * get(int position) where position is a position in the list,
923 * or <code>null</code> if the choice mode is set to
924 * {@link #CHOICE_MODE_NONE}.
925 */
926 public SparseBooleanArray getCheckedItemPositions() {
927 if (mChoiceMode != CHOICE_MODE_NONE) {
928 return mCheckStates;
929 }
930 return null;
931 }
932
933 /**
934 * Returns the set of checked items ids. The result is only valid if the
935 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
936 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
937 *
938 * @return A new array which contains the id of each checked item in the
939 * list.
940 */
941 public long[] getCheckedItemIds() {
942 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
943 return new long[0];
944 }
945
Adam Powell14c08042011-10-06 19:46:18 -0700946 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700947 final int count = idStates.size();
948 final long[] ids = new long[count];
949
950 for (int i = 0; i < count; i++) {
951 ids[i] = idStates.keyAt(i);
952 }
953
954 return ids;
955 }
956
957 /**
958 * Clear any choices previously set
959 */
960 public void clearChoices() {
961 if (mCheckStates != null) {
962 mCheckStates.clear();
963 }
964 if (mCheckedIdStates != null) {
965 mCheckedIdStates.clear();
966 }
967 mCheckedItemCount = 0;
968 }
969
970 /**
971 * Sets the checked state of the specified position. The is only valid if
972 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
973 * {@link #CHOICE_MODE_MULTIPLE}.
974 *
975 * @param position The item whose checked state is to be checked
976 * @param value The new checked state for the item
977 */
978 public void setItemChecked(int position, boolean value) {
979 if (mChoiceMode == CHOICE_MODE_NONE) {
980 return;
981 }
982
983 // Start selection mode if needed. We don't need to if we're unchecking something.
984 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -0700985 if (mMultiChoiceModeCallback == null ||
986 !mMultiChoiceModeCallback.hasWrappedCallback()) {
987 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
988 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
989 "supplied. Call setMultiChoiceModeListener to set a callback.");
990 }
Adam Powellf343e1b2010-08-13 18:27:04 -0700991 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
992 }
993
994 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
995 boolean oldValue = mCheckStates.get(position);
996 mCheckStates.put(position, value);
997 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
998 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -0700999 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001000 } else {
1001 mCheckedIdStates.delete(mAdapter.getItemId(position));
1002 }
1003 }
1004 if (oldValue != value) {
1005 if (value) {
1006 mCheckedItemCount++;
1007 } else {
1008 mCheckedItemCount--;
1009 }
1010 }
1011 if (mChoiceActionMode != null) {
1012 final long id = mAdapter.getItemId(position);
1013 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1014 position, id, value);
1015 }
1016 } else {
1017 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1018 // Clear all values if we're checking something, or unchecking the currently
1019 // selected item
1020 if (value || isItemChecked(position)) {
1021 mCheckStates.clear();
1022 if (updateIds) {
1023 mCheckedIdStates.clear();
1024 }
1025 }
1026 // this may end up selecting the value we just cleared but this way
1027 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1028 if (value) {
1029 mCheckStates.put(position, true);
1030 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001031 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001032 }
1033 mCheckedItemCount = 1;
1034 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1035 mCheckedItemCount = 0;
1036 }
1037 }
1038
1039 // Do not generate a data change while we are in the layout phase
1040 if (!mInLayout && !mBlockLayoutRequests) {
1041 mDataChanged = true;
1042 rememberSyncState();
1043 requestLayout();
1044 }
1045 }
1046
1047 @Override
1048 public boolean performItemClick(View view, int position, long id) {
1049 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001050 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001051
1052 if (mChoiceMode != CHOICE_MODE_NONE) {
1053 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001054 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001055
1056 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1057 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001058 boolean checked = !mCheckStates.get(position, false);
1059 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001060 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001061 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001062 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001063 } else {
1064 mCheckedIdStates.delete(mAdapter.getItemId(position));
1065 }
1066 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001067 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001068 mCheckedItemCount++;
1069 } else {
1070 mCheckedItemCount--;
1071 }
1072 if (mChoiceActionMode != null) {
1073 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001074 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001075 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001076 }
Adam Powell29382d92012-02-23 11:03:22 -08001077 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001078 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001079 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001080 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001081 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001082 mCheckStates.put(position, true);
1083 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1084 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001085 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001086 }
1087 mCheckedItemCount = 1;
1088 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1089 mCheckedItemCount = 0;
1090 }
Adam Powell29382d92012-02-23 11:03:22 -08001091 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001092 }
1093
Adam Powell29382d92012-02-23 11:03:22 -08001094 if (checkedStateChanged) {
1095 updateOnScreenCheckedViews();
1096 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001097 }
1098
Adam Powellbf5f2b32010-10-24 16:45:44 -07001099 if (dispatchItemClick) {
1100 handled |= super.performItemClick(view, position, id);
1101 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001102
1103 return handled;
1104 }
1105
1106 /**
Adam Powell29382d92012-02-23 11:03:22 -08001107 * Perform a quick, in-place update of the checked or activated state
1108 * on all visible item views. This should only be called when a valid
1109 * choice mode is active.
1110 */
1111 private void updateOnScreenCheckedViews() {
1112 final int firstPos = mFirstPosition;
1113 final int count = getChildCount();
1114 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1115 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1116 for (int i = 0; i < count; i++) {
1117 final View child = getChildAt(i);
1118 final int position = firstPos + i;
1119
1120 if (child instanceof Checkable) {
1121 ((Checkable) child).setChecked(mCheckStates.get(position));
1122 } else if (useActivated) {
1123 child.setActivated(mCheckStates.get(position));
1124 }
1125 }
1126 }
1127
1128 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001129 * @see #setChoiceMode(int)
1130 *
1131 * @return The current choice mode
1132 */
1133 public int getChoiceMode() {
1134 return mChoiceMode;
1135 }
1136
1137 /**
1138 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1139 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1140 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1141 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1142 *
1143 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1144 * {@link #CHOICE_MODE_MULTIPLE}
1145 */
1146 public void setChoiceMode(int choiceMode) {
1147 mChoiceMode = choiceMode;
1148 if (mChoiceActionMode != null) {
1149 mChoiceActionMode.finish();
1150 mChoiceActionMode = null;
1151 }
1152 if (mChoiceMode != CHOICE_MODE_NONE) {
1153 if (mCheckStates == null) {
1154 mCheckStates = new SparseBooleanArray();
1155 }
1156 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Adam Powell14c08042011-10-06 19:46:18 -07001157 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -07001158 }
1159 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1160 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1161 clearChoices();
1162 setLongClickable(true);
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1169 * selection {@link ActionMode}. Only used when the choice mode is set to
1170 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1171 *
1172 * @param listener Listener that will manage the selection mode
1173 *
1174 * @see #setChoiceMode(int)
1175 */
1176 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1177 if (mMultiChoiceModeCallback == null) {
1178 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1179 }
1180 mMultiChoiceModeCallback.setWrapped(listener);
1181 }
1182
1183 /**
Adam Powell637d3372010-08-25 14:37:03 -07001184 * @return true if all list content currently fits within the view boundaries
1185 */
1186 private boolean contentFits() {
1187 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001188 if (childCount == 0) return true;
1189 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001190
Adam Powell4ce35412011-01-24 14:55:00 -08001191 return getChildAt(0).getTop() >= mListPadding.top &&
1192 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001193 }
1194
1195 /**
Romain Guy0a637162009-05-29 14:43:54 -07001196 * Enables fast scrolling by letting the user quickly scroll through lists by
1197 * dragging the fast scroll thumb. The adapter attached to the list may want
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198 * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
Romain Guy0a637162009-05-29 14:43:54 -07001199 * jump between sections of the list.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001200 * @see SectionIndexer
1201 * @see #isFastScrollEnabled()
1202 * @param enabled whether or not to enable fast scrolling
1203 */
1204 public void setFastScrollEnabled(boolean enabled) {
1205 mFastScrollEnabled = enabled;
1206 if (enabled) {
1207 if (mFastScroller == null) {
1208 mFastScroller = new FastScroller(getContext(), this);
1209 }
1210 } else {
1211 if (mFastScroller != null) {
1212 mFastScroller.stop();
1213 mFastScroller = null;
1214 }
1215 }
1216 }
Romain Guy0a637162009-05-29 14:43:54 -07001217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 /**
Adam Powell20232d02010-12-08 21:08:53 -08001219 * Set whether or not the fast scroller should always be shown in place of the
1220 * standard scrollbars. Fast scrollers shown in this way will not fade out and will
1221 * be a permanent fixture within the list. Best combined with an inset scroll bar style
1222 * that will ensure enough padding. This will enable fast scrolling if it is not
1223 * already enabled.
1224 *
1225 * @param alwaysShow true if the fast scroller should always be displayed.
1226 * @see #setScrollBarStyle(int)
1227 * @see #setFastScrollEnabled(boolean)
1228 */
1229 public void setFastScrollAlwaysVisible(boolean alwaysShow) {
1230 if (alwaysShow && !mFastScrollEnabled) {
1231 setFastScrollEnabled(true);
1232 }
1233
1234 if (mFastScroller != null) {
1235 mFastScroller.setAlwaysShow(alwaysShow);
1236 }
1237
1238 computeOpaqueFlags();
1239 recomputePadding();
1240 }
1241
1242 /**
1243 * Returns true if the fast scroller is set to always show on this view rather than
1244 * fade out when not in use.
1245 *
1246 * @return true if the fast scroller will always show.
1247 * @see #setFastScrollAlwaysVisible(boolean)
1248 */
1249 public boolean isFastScrollAlwaysVisible() {
1250 return mFastScrollEnabled && mFastScroller.isAlwaysShowEnabled();
1251 }
1252
1253 @Override
1254 public int getVerticalScrollbarWidth() {
Adam Powell16bb80a2010-12-09 23:30:50 -08001255 if (isFastScrollAlwaysVisible()) {
Adam Powell20232d02010-12-08 21:08:53 -08001256 return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth());
1257 }
1258 return super.getVerticalScrollbarWidth();
1259 }
1260
1261 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 * Returns the current state of the fast scroll feature.
1263 * @see #setFastScrollEnabled(boolean)
1264 * @return true if fast scroll is enabled, false otherwise
1265 */
1266 @ViewDebug.ExportedProperty
1267 public boolean isFastScrollEnabled() {
1268 return mFastScrollEnabled;
1269 }
Romain Guy0a637162009-05-29 14:43:54 -07001270
Adam Powell20232d02010-12-08 21:08:53 -08001271 @Override
1272 public void setVerticalScrollbarPosition(int position) {
1273 super.setVerticalScrollbarPosition(position);
1274 if (mFastScroller != null) {
1275 mFastScroller.setScrollbarPosition(position);
1276 }
1277 }
1278
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279 /**
1280 * If fast scroll is visible, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001281 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 */
1283 @Override
1284 protected boolean isVerticalScrollBarHidden() {
1285 return mFastScroller != null && mFastScroller.isVisible();
1286 }
1287
1288 /**
1289 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1290 * is computed based on the number of visible pixels in the visible items. This
1291 * however assumes that all list items have the same height. If you use a list in
1292 * which items have different heights, the scrollbar will change appearance as the
1293 * user scrolls through the list. To avoid this issue, you need to disable this
1294 * property.
1295 *
1296 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1297 * is based solely on the number of items in the adapter and the position of the
1298 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001299 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 *
1301 * @param enabled Whether or not to enable smooth scrollbar.
1302 *
Romain Guy0a637162009-05-29 14:43:54 -07001303 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1305 */
1306 public void setSmoothScrollbarEnabled(boolean enabled) {
1307 mSmoothScrollbarEnabled = enabled;
1308 }
1309
1310 /**
1311 * Returns the current state of the fast scroll feature.
1312 *
1313 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1314 *
1315 * @see #setSmoothScrollbarEnabled(boolean)
1316 */
1317 @ViewDebug.ExportedProperty
1318 public boolean isSmoothScrollbarEnabled() {
1319 return mSmoothScrollbarEnabled;
1320 }
1321
1322 /**
1323 * Set the listener that will receive notifications every time the list scrolls.
1324 *
1325 * @param l the scroll listener
1326 */
1327 public void setOnScrollListener(OnScrollListener l) {
1328 mOnScrollListener = l;
1329 invokeOnItemScrollListener();
1330 }
1331
1332 /**
1333 * Notify our scroll listener (if there is one) of a change in scroll state
1334 */
1335 void invokeOnItemScrollListener() {
1336 if (mFastScroller != null) {
1337 mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1338 }
1339 if (mOnScrollListener != null) {
1340 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1341 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001342 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343 }
1344
Svetoslav Ganova0156172011-06-26 17:55:44 -07001345 @Override
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001346 public void sendAccessibilityEvent(int eventType) {
1347 // Since this class calls onScrollChanged even if the mFirstPosition and the
1348 // child count have not changed we will avoid sending duplicate accessibility
1349 // events.
1350 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001351 final int firstVisiblePosition = getFirstVisiblePosition();
1352 final int lastVisiblePosition = getLastVisiblePosition();
1353 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1354 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001355 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001356 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001357 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1358 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001359 }
1360 }
1361 super.sendAccessibilityEvent(eventType);
1362 }
1363
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001364 @Override
1365 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1366 super.onInitializeAccessibilityEvent(event);
1367 event.setClassName(AbsListView.class.getName());
1368 }
1369
1370 @Override
1371 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1372 super.onInitializeAccessibilityNodeInfo(info);
1373 info.setClassName(AbsListView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001374 if (isEnabled()) {
1375 if (getFirstVisiblePosition() > 0) {
1376 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001377 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001378 }
1379 if (getLastVisiblePosition() < getCount() - 1) {
1380 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001381 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001382 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001383 }
1384 }
1385
1386 @Override
1387 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001388 if (super.performAccessibilityAction(action, arguments)) {
1389 return true;
1390 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001391 switch (action) {
1392 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001393 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001394 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1395 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1396 return true;
1397 }
1398 } return false;
1399 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001400 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001401 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1402 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1403 return true;
1404 }
1405 } return false;
1406 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001407 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001408 }
1409
Svetoslav5b578da2013-05-08 14:23:32 -07001410 /** @hide */
1411 @Override
1412 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1413 if (accessibilityId == getAccessibilityViewId()) {
1414 return this;
1415 }
1416 // If the data changed the children are invalid since the data model changed.
1417 // Hence, we pretend they do not exist. After a layout the children will sync
1418 // with the model at which point we notify that the accessibility state changed,
1419 // so a service will be able to re-fetch the views.
1420 if (mDataChanged) {
1421 return null;
1422 }
1423 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1424 }
1425
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426 /**
1427 * Indicates whether the children's drawing cache is used during a scroll.
1428 * By default, the drawing cache is enabled but this will consume more memory.
1429 *
1430 * @return true if the scrolling cache is enabled, false otherwise
1431 *
1432 * @see #setScrollingCacheEnabled(boolean)
1433 * @see View#setDrawingCacheEnabled(boolean)
1434 */
1435 @ViewDebug.ExportedProperty
1436 public boolean isScrollingCacheEnabled() {
1437 return mScrollingCacheEnabled;
1438 }
1439
1440 /**
1441 * Enables or disables the children's drawing cache during a scroll.
1442 * By default, the drawing cache is enabled but this will use more memory.
1443 *
1444 * When the scrolling cache is enabled, the caches are kept after the
1445 * first scrolling. You can manually clear the cache by calling
1446 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1447 *
1448 * @param enabled true to enable the scroll cache, false otherwise
1449 *
1450 * @see #isScrollingCacheEnabled()
1451 * @see View#setDrawingCacheEnabled(boolean)
1452 */
1453 public void setScrollingCacheEnabled(boolean enabled) {
1454 if (mScrollingCacheEnabled && !enabled) {
1455 clearScrollingCache();
1456 }
1457 mScrollingCacheEnabled = enabled;
1458 }
1459
1460 /**
1461 * Enables or disables the type filter window. If enabled, typing when
1462 * this view has focus will filter the children to match the users input.
1463 * Note that the {@link Adapter} used by this view must implement the
1464 * {@link Filterable} interface.
1465 *
1466 * @param textFilterEnabled true to enable type filtering, false otherwise
1467 *
1468 * @see Filterable
1469 */
1470 public void setTextFilterEnabled(boolean textFilterEnabled) {
1471 mTextFilterEnabled = textFilterEnabled;
1472 }
1473
1474 /**
1475 * Indicates whether type filtering is enabled for this view
1476 *
1477 * @return true if type filtering is enabled, false otherwise
1478 *
1479 * @see #setTextFilterEnabled(boolean)
1480 * @see Filterable
1481 */
1482 @ViewDebug.ExportedProperty
1483 public boolean isTextFilterEnabled() {
1484 return mTextFilterEnabled;
1485 }
1486
1487 @Override
1488 public void getFocusedRect(Rect r) {
1489 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001490 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 // the focused rectangle of the selected view offset into the
1492 // coordinate space of this view.
1493 view.getFocusedRect(r);
1494 offsetDescendantRectToMyCoords(view, r);
1495 } else {
1496 // otherwise, just the norm
1497 super.getFocusedRect(r);
1498 }
1499 }
1500
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 private void useDefaultSelector() {
1502 setSelector(getResources().getDrawable(
1503 com.android.internal.R.drawable.list_selector_background));
1504 }
1505
1506 /**
1507 * Indicates whether the content of this view is pinned to, or stacked from,
1508 * the bottom edge.
1509 *
1510 * @return true if the content is stacked from the bottom edge, false otherwise
1511 */
1512 @ViewDebug.ExportedProperty
1513 public boolean isStackFromBottom() {
1514 return mStackFromBottom;
1515 }
1516
1517 /**
1518 * When stack from bottom is set to true, the list fills its content starting from
1519 * the bottom of the view.
1520 *
1521 * @param stackFromBottom true to pin the view's content to the bottom edge,
1522 * false to pin the view's content to the top edge
1523 */
1524 public void setStackFromBottom(boolean stackFromBottom) {
1525 if (mStackFromBottom != stackFromBottom) {
1526 mStackFromBottom = stackFromBottom;
1527 requestLayoutIfNecessary();
1528 }
1529 }
1530
1531 void requestLayoutIfNecessary() {
1532 if (getChildCount() > 0) {
1533 resetList();
1534 requestLayout();
1535 invalidate();
1536 }
1537 }
1538
1539 static class SavedState extends BaseSavedState {
1540 long selectedId;
1541 long firstId;
1542 int viewTop;
1543 int position;
1544 int height;
1545 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001546 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001547 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001548 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001549 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550
1551 /**
1552 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1553 */
1554 SavedState(Parcelable superState) {
1555 super(superState);
1556 }
1557
1558 /**
1559 * Constructor called from {@link #CREATOR}
1560 */
1561 private SavedState(Parcel in) {
1562 super(in);
1563 selectedId = in.readLong();
1564 firstId = in.readLong();
1565 viewTop = in.readInt();
1566 position = in.readInt();
1567 height = in.readInt();
1568 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001569 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001570 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001571 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001572 final int N = in.readInt();
1573 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001574 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001575 for (int i=0; i<N; i++) {
1576 final long key = in.readLong();
1577 final int value = in.readInt();
1578 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001579 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001580 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001581 }
1582
1583 @Override
1584 public void writeToParcel(Parcel out, int flags) {
1585 super.writeToParcel(out, flags);
1586 out.writeLong(selectedId);
1587 out.writeLong(firstId);
1588 out.writeInt(viewTop);
1589 out.writeInt(position);
1590 out.writeInt(height);
1591 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001592 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001593 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001594 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001595 final int N = checkIdState != null ? checkIdState.size() : 0;
1596 out.writeInt(N);
1597 for (int i=0; i<N; i++) {
1598 out.writeLong(checkIdState.keyAt(i));
1599 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001600 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001601 }
1602
1603 @Override
1604 public String toString() {
1605 return "AbsListView.SavedState{"
1606 + Integer.toHexString(System.identityHashCode(this))
1607 + " selectedId=" + selectedId
1608 + " firstId=" + firstId
1609 + " viewTop=" + viewTop
1610 + " position=" + position
1611 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001612 + " filter=" + filter
1613 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001614 }
1615
1616 public static final Parcelable.Creator<SavedState> CREATOR
1617 = new Parcelable.Creator<SavedState>() {
1618 public SavedState createFromParcel(Parcel in) {
1619 return new SavedState(in);
1620 }
1621
1622 public SavedState[] newArray(int size) {
1623 return new SavedState[size];
1624 }
1625 };
1626 }
1627
1628 @Override
1629 public Parcelable onSaveInstanceState() {
1630 /*
1631 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001632 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001633 * that happen early enough to keep from getting complaints
1634 * about having leaked the window.
1635 */
1636 dismissPopup();
1637
1638 Parcelable superState = super.onSaveInstanceState();
1639
1640 SavedState ss = new SavedState(superState);
1641
Dianne Hackborne181bd92012-09-25 14:15:15 -07001642 if (mPendingSync != null) {
1643 // Just keep what we last restored.
1644 ss.selectedId = mPendingSync.selectedId;
1645 ss.firstId = mPendingSync.firstId;
1646 ss.viewTop = mPendingSync.viewTop;
1647 ss.position = mPendingSync.position;
1648 ss.height = mPendingSync.height;
1649 ss.filter = mPendingSync.filter;
1650 ss.inActionMode = mPendingSync.inActionMode;
1651 ss.checkedItemCount = mPendingSync.checkedItemCount;
1652 ss.checkState = mPendingSync.checkState;
1653 ss.checkIdState = mPendingSync.checkIdState;
1654 return ss;
1655 }
1656
Dianne Hackborn99441c42010-12-15 11:02:55 -08001657 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001658 long selectedId = getSelectedItemId();
1659 ss.selectedId = selectedId;
1660 ss.height = getHeight();
1661
1662 if (selectedId >= 0) {
1663 // Remember the selection
1664 ss.viewTop = mSelectedTop;
1665 ss.position = getSelectedItemPosition();
1666 ss.firstId = INVALID_POSITION;
1667 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001668 if (haveChildren && mFirstPosition > 0) {
1669 // Remember the position of the first child.
1670 // We only do this if we are not currently at the top of
1671 // the list, for two reasons:
1672 // (1) The list may be in the process of becoming empty, in
1673 // which case mItemCount may not be 0, but if we try to
1674 // ask for any information about position 0 we will crash.
1675 // (2) Being "at the top" seems like a special case, anyway,
1676 // and the user wouldn't expect to end up somewhere else when
1677 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001678 View v = getChildAt(0);
1679 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001680 int firstPos = mFirstPosition;
1681 if (firstPos >= mItemCount) {
1682 firstPos = mItemCount - 1;
1683 }
1684 ss.position = firstPos;
1685 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001686 } else {
1687 ss.viewTop = 0;
1688 ss.firstId = INVALID_POSITION;
1689 ss.position = 0;
1690 }
1691 }
1692
1693 ss.filter = null;
1694 if (mFiltered) {
1695 final EditText textFilter = mTextFilter;
1696 if (textFilter != null) {
1697 Editable filterText = textFilter.getText();
1698 if (filterText != null) {
1699 ss.filter = filterText.toString();
1700 }
1701 }
1702 }
1703
Adam Powella0eeeac2010-11-05 11:55:05 -07001704 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1705
Adam Powell9a5cc282011-08-28 16:18:16 -07001706 if (mCheckStates != null) {
1707 ss.checkState = mCheckStates.clone();
1708 }
1709 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001710 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001711 final int count = mCheckedIdStates.size();
1712 for (int i = 0; i < count; i++) {
1713 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1714 }
1715 ss.checkIdState = idState;
1716 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001717 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001718
Adam Cohen335c3b62012-07-24 17:18:16 -07001719 if (mRemoteAdapter != null) {
1720 mRemoteAdapter.saveRemoteViewsCache();
1721 }
1722
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001723 return ss;
1724 }
1725
1726 @Override
1727 public void onRestoreInstanceState(Parcelable state) {
1728 SavedState ss = (SavedState) state;
1729
1730 super.onRestoreInstanceState(ss.getSuperState());
1731 mDataChanged = true;
1732
1733 mSyncHeight = ss.height;
1734
1735 if (ss.selectedId >= 0) {
1736 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001737 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001738 mSyncRowId = ss.selectedId;
1739 mSyncPosition = ss.position;
1740 mSpecificTop = ss.viewTop;
1741 mSyncMode = SYNC_SELECTED_POSITION;
1742 } else if (ss.firstId >= 0) {
1743 setSelectedPositionInt(INVALID_POSITION);
1744 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1745 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001746 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001747 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001748 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001749 mSyncRowId = ss.firstId;
1750 mSyncPosition = ss.position;
1751 mSpecificTop = ss.viewTop;
1752 mSyncMode = SYNC_FIRST_POSITION;
1753 }
1754
1755 setFilterText(ss.filter);
1756
Adam Powellf343e1b2010-08-13 18:27:04 -07001757 if (ss.checkState != null) {
1758 mCheckStates = ss.checkState;
1759 }
1760
1761 if (ss.checkIdState != null) {
1762 mCheckedIdStates = ss.checkIdState;
1763 }
1764
Adam Powell2614c6c2010-11-04 17:54:45 -07001765 mCheckedItemCount = ss.checkedItemCount;
1766
Adam Powella0eeeac2010-11-05 11:55:05 -07001767 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1768 mMultiChoiceModeCallback != null) {
1769 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1770 }
1771
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001772 requestLayout();
1773 }
1774
1775 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001776 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1777 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001778 }
1779
1780 /**
1781 * Sets the initial value for the text filter.
1782 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001783 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001784 * @see #setTextFilterEnabled
1785 */
1786 public void setFilterText(String filterText) {
1787 // TODO: Should we check for acceptFilter()?
1788 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1789 createTextFilter(false);
1790 // This is going to call our listener onTextChanged, but we might not
1791 // be ready to bring up a window yet
1792 mTextFilter.setText(filterText);
1793 mTextFilter.setSelection(filterText.length());
1794 if (mAdapter instanceof Filterable) {
1795 // if mPopup is non-null, then onTextChanged will do the filtering
1796 if (mPopup == null) {
1797 Filter f = ((Filterable) mAdapter).getFilter();
1798 f.filter(filterText);
1799 }
1800 // Set filtered to true so we will display the filter window when our main
1801 // window is ready
1802 mFiltered = true;
1803 mDataSetObserver.clearSavedState();
1804 }
1805 }
1806 }
1807
1808 /**
Romain Guy0a637162009-05-29 14:43:54 -07001809 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001810 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001811 */
1812 public CharSequence getTextFilter() {
1813 if (mTextFilterEnabled && mTextFilter != null) {
1814 return mTextFilter.getText();
1815 }
1816 return null;
1817 }
Romain Guy0a637162009-05-29 14:43:54 -07001818
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001819 @Override
1820 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1821 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1822 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powellb3750132011-08-08 23:29:12 -07001823 if (!mIsAttached && mAdapter != null) {
1824 // Data may have changed while we were detached and it's valid
1825 // to change focus while detached. Refresh so we don't die.
1826 mDataChanged = true;
1827 mOldItemCount = mItemCount;
1828 mItemCount = mAdapter.getCount();
1829 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001830 resurrectSelection();
1831 }
1832 }
1833
1834 @Override
1835 public void requestLayout() {
1836 if (!mBlockLayoutRequests && !mInLayout) {
1837 super.requestLayout();
1838 }
1839 }
1840
1841 /**
1842 * The list is empty. Clear everything out.
1843 */
1844 void resetList() {
1845 removeAllViewsInLayout();
1846 mFirstPosition = 0;
1847 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001848 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001849 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001850 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001851 mOldSelectedPosition = INVALID_POSITION;
1852 mOldSelectedRowId = INVALID_ROW_ID;
1853 setSelectedPositionInt(INVALID_POSITION);
1854 setNextSelectedPositionInt(INVALID_POSITION);
1855 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07001856 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001857 mSelectorRect.setEmpty();
1858 invalidate();
1859 }
1860
1861 @Override
1862 protected int computeVerticalScrollExtent() {
1863 final int count = getChildCount();
1864 if (count > 0) {
1865 if (mSmoothScrollbarEnabled) {
1866 int extent = count * 100;
1867
1868 View view = getChildAt(0);
1869 final int top = view.getTop();
1870 int height = view.getHeight();
1871 if (height > 0) {
1872 extent += (top * 100) / height;
1873 }
1874
1875 view = getChildAt(count - 1);
1876 final int bottom = view.getBottom();
1877 height = view.getHeight();
1878 if (height > 0) {
1879 extent -= ((bottom - getHeight()) * 100) / height;
1880 }
1881
1882 return extent;
1883 } else {
1884 return 1;
1885 }
1886 }
1887 return 0;
1888 }
1889
1890 @Override
1891 protected int computeVerticalScrollOffset() {
1892 final int firstPosition = mFirstPosition;
1893 final int childCount = getChildCount();
1894 if (firstPosition >= 0 && childCount > 0) {
1895 if (mSmoothScrollbarEnabled) {
1896 final View view = getChildAt(0);
1897 final int top = view.getTop();
1898 int height = view.getHeight();
1899 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08001900 return Math.max(firstPosition * 100 - (top * 100) / height +
1901 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001902 }
1903 } else {
1904 int index;
1905 final int count = mItemCount;
1906 if (firstPosition == 0) {
1907 index = 0;
1908 } else if (firstPosition + childCount == count) {
1909 index = count;
1910 } else {
1911 index = firstPosition + childCount / 2;
1912 }
1913 return (int) (firstPosition + childCount * (index / (float) count));
1914 }
1915 }
1916 return 0;
1917 }
1918
1919 @Override
1920 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08001921 int result;
1922 if (mSmoothScrollbarEnabled) {
1923 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07001924 if (mScrollY != 0) {
1925 // Compensate for overscroll
1926 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
1927 }
Adam Powell0b8bb422010-02-08 14:30:45 -08001928 } else {
1929 result = mItemCount;
1930 }
1931 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001932 }
1933
1934 @Override
1935 protected float getTopFadingEdgeStrength() {
1936 final int count = getChildCount();
1937 final float fadeEdge = super.getTopFadingEdgeStrength();
1938 if (count == 0) {
1939 return fadeEdge;
1940 } else {
1941 if (mFirstPosition > 0) {
1942 return 1.0f;
1943 }
1944
1945 final int top = getChildAt(0).getTop();
1946 final float fadeLength = (float) getVerticalFadingEdgeLength();
1947 return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
1948 }
1949 }
1950
1951 @Override
1952 protected float getBottomFadingEdgeStrength() {
1953 final int count = getChildCount();
1954 final float fadeEdge = super.getBottomFadingEdgeStrength();
1955 if (count == 0) {
1956 return fadeEdge;
1957 } else {
1958 if (mFirstPosition + count - 1 < mItemCount - 1) {
1959 return 1.0f;
1960 }
1961
1962 final int bottom = getChildAt(count - 1).getBottom();
1963 final int height = getHeight();
1964 final float fadeLength = (float) getVerticalFadingEdgeLength();
1965 return bottom > height - mPaddingBottom ?
1966 (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
1967 }
1968 }
1969
1970 @Override
1971 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1972 if (mSelector == null) {
1973 useDefaultSelector();
1974 }
1975 final Rect listPadding = mListPadding;
1976 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
1977 listPadding.top = mSelectionTopPadding + mPaddingTop;
1978 listPadding.right = mSelectionRightPadding + mPaddingRight;
1979 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08001980
1981 // Check if our previous measured size was at a point where we should scroll later.
1982 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
1983 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07001984 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08001985 final View lastChild = getChildAt(childCount - 1);
1986 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07001987 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08001988 lastBottom <= listBottom;
1989 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001990 }
1991
Romain Guyd6a463a2009-05-21 23:10:10 -07001992 /**
1993 * Subclasses should NOT override this method but
1994 * {@link #layoutChildren()} instead.
1995 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001996 @Override
1997 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1998 super.onLayout(changed, l, t, r, b);
1999 mInLayout = true;
Adam Powellf3c2eda2010-03-16 17:31:01 -07002000 if (changed) {
2001 int childCount = getChildCount();
2002 for (int i = 0; i < childCount; i++) {
2003 getChildAt(i).forceLayout();
2004 }
2005 mRecycler.markChildrenDirty();
2006 }
Adam Powell2c6196a2010-12-10 14:31:54 -08002007
2008 if (mFastScroller != null && mItemCount != mOldItemCount) {
2009 mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
2010 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07002011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002012 layoutChildren();
2013 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002014
2015 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002016 }
2017
2018 /**
2019 * @hide
2020 */
2021 @Override
2022 protected boolean setFrame(int left, int top, int right, int bottom) {
2023 final boolean changed = super.setFrame(left, top, right, bottom);
2024
Romain Guyd6a463a2009-05-21 23:10:10 -07002025 if (changed) {
2026 // Reposition the popup when the frame has changed. This includes
2027 // translating the widget, not just changing its dimension. The
2028 // filter popup needs to follow the widget.
2029 final boolean visible = getWindowVisibility() == View.VISIBLE;
2030 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2031 positionPopup();
2032 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002033 }
2034
2035 return changed;
2036 }
2037
Romain Guyd6a463a2009-05-21 23:10:10 -07002038 /**
2039 * Subclasses must override this method to layout their children.
2040 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002041 protected void layoutChildren() {
2042 }
2043
2044 void updateScrollIndicators() {
2045 if (mScrollUp != null) {
2046 boolean canScrollUp;
2047 // 0th element is not visible
2048 canScrollUp = mFirstPosition > 0;
2049
2050 // ... Or top of 0th element is not visible
2051 if (!canScrollUp) {
2052 if (getChildCount() > 0) {
2053 View child = getChildAt(0);
2054 canScrollUp = child.getTop() < mListPadding.top;
2055 }
2056 }
2057
2058 mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
2059 }
2060
2061 if (mScrollDown != null) {
2062 boolean canScrollDown;
2063 int count = getChildCount();
2064
2065 // Last item is not visible
2066 canScrollDown = (mFirstPosition + count) < mItemCount;
2067
2068 // ... Or bottom of the last element is not visible
2069 if (!canScrollDown && count > 0) {
2070 View child = getChildAt(count - 1);
2071 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2072 }
2073
2074 mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
2075 }
2076 }
2077
2078 @Override
2079 @ViewDebug.ExportedProperty
2080 public View getSelectedView() {
2081 if (mItemCount > 0 && mSelectedPosition >= 0) {
2082 return getChildAt(mSelectedPosition - mFirstPosition);
2083 } else {
2084 return null;
2085 }
2086 }
2087
2088 /**
2089 * List padding is the maximum of the normal view's padding and the padding of the selector.
2090 *
2091 * @see android.view.View#getPaddingTop()
2092 * @see #getSelector()
2093 *
2094 * @return The top list padding.
2095 */
2096 public int getListPaddingTop() {
2097 return mListPadding.top;
2098 }
2099
2100 /**
2101 * List padding is the maximum of the normal view's padding and the padding of the selector.
2102 *
2103 * @see android.view.View#getPaddingBottom()
2104 * @see #getSelector()
2105 *
2106 * @return The bottom list padding.
2107 */
2108 public int getListPaddingBottom() {
2109 return mListPadding.bottom;
2110 }
2111
2112 /**
2113 * List padding is the maximum of the normal view's padding and the padding of the selector.
2114 *
2115 * @see android.view.View#getPaddingLeft()
2116 * @see #getSelector()
2117 *
2118 * @return The left list padding.
2119 */
2120 public int getListPaddingLeft() {
2121 return mListPadding.left;
2122 }
2123
2124 /**
2125 * List padding is the maximum of the normal view's padding and the padding of the selector.
2126 *
2127 * @see android.view.View#getPaddingRight()
2128 * @see #getSelector()
2129 *
2130 * @return The right list padding.
2131 */
2132 public int getListPaddingRight() {
2133 return mListPadding.right;
2134 }
2135
2136 /**
2137 * Get a view and have it show the data associated with the specified
2138 * position. This is called when we have already discovered that the view is
2139 * not available for reuse in the recycle bin. The only choices left are
2140 * converting an old view or making a new one.
2141 *
2142 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002143 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2144 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002145 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002146 * @return A view displaying the data associated with the specified position
2147 */
Romain Guy21875052010-01-06 18:48:08 -08002148 View obtainView(int position, boolean[] isScrap) {
2149 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002150 View scrapView;
2151
Adam Powell539ee872012-02-03 19:00:49 -08002152 scrapView = mRecycler.getTransientStateView(position);
2153 if (scrapView != null) {
2154 return scrapView;
2155 }
2156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002157 scrapView = mRecycler.getScrapView(position);
2158
2159 View child;
2160 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002161 child = mAdapter.getView(position, scrapView, this);
2162
Svetoslav Ganov42138042012-03-20 11:51:39 -07002163 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2164 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2165 }
2166
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002167 if (child != scrapView) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002168 mRecycler.addScrapView(scrapView, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002169 if (mCacheColorHint != 0) {
2170 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2171 }
Romain Guy21875052010-01-06 18:48:08 -08002172 } else {
Romain Guya440b002010-02-24 15:57:54 -08002173 isScrap[0] = true;
2174 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002175 }
2176 } else {
2177 child = mAdapter.getView(position, null, this);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002178
2179 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2180 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2181 }
2182
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002183 if (mCacheColorHint != 0) {
2184 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2185 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002186 }
2187
Adam Powellaebd28f2012-02-22 10:31:16 -08002188 if (mAdapterHasStableIds) {
2189 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2190 LayoutParams lp;
2191 if (vlp == null) {
2192 lp = (LayoutParams) generateDefaultLayoutParams();
2193 } else if (!checkLayoutParams(vlp)) {
2194 lp = (LayoutParams) generateLayoutParams(vlp);
2195 } else {
2196 lp = (LayoutParams) vlp;
2197 }
2198 lp.itemId = mAdapter.getItemId(position);
2199 child.setLayoutParams(lp);
2200 }
2201
alanvc1d7e772012-05-08 14:47:24 -07002202 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2203 if (mAccessibilityDelegate == null) {
2204 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2205 }
alanvb72fe7a2012-08-27 16:44:25 -07002206 if (child.getAccessibilityDelegate() == null) {
2207 child.setAccessibilityDelegate(mAccessibilityDelegate);
2208 }
alanvc1d7e772012-05-08 14:47:24 -07002209 }
2210
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002211 return child;
2212 }
2213
alanvc1d7e772012-05-08 14:47:24 -07002214 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2215 @Override
Svetoslav Ganov50776862013-05-17 18:06:31 -07002216 public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
2217 // If the data changed the children are invalid since the data model changed.
2218 // Hence, we pretend they do not exist. After a layout the children will sync
2219 // with the model at which point we notify that the accessibility state changed,
2220 // so a service will be able to re-fetch the views.
2221 if (mDataChanged) {
2222 return null;
2223 }
2224 return super.createAccessibilityNodeInfo(host);
2225 }
2226
2227 @Override
alanvc1d7e772012-05-08 14:47:24 -07002228 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2229 super.onInitializeAccessibilityNodeInfo(host, info);
2230
2231 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002232 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002233
alanv9c3e0e62012-05-18 17:43:35 -07002234 if ((position == INVALID_POSITION) || (adapter == null)) {
alanvc1d7e772012-05-08 14:47:24 -07002235 return;
2236 }
2237
alanv9c3e0e62012-05-18 17:43:35 -07002238 if (!isEnabled() || !adapter.isEnabled(position)) {
alanv9c3e0e62012-05-18 17:43:35 -07002239 return;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002240 }
alanvc1d7e772012-05-08 14:47:24 -07002241
2242 if (position == getSelectedItemPosition()) {
2243 info.setSelected(true);
alanv9c3e0e62012-05-18 17:43:35 -07002244 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
2245 } else {
2246 info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
alanvc1d7e772012-05-08 14:47:24 -07002247 }
alanv9c3e0e62012-05-18 17:43:35 -07002248
2249 if (isClickable()) {
2250 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2251 info.setClickable(true);
2252 }
2253
2254 if (isLongClickable()) {
2255 info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
2256 info.setLongClickable(true);
2257 }
2258
alanvc1d7e772012-05-08 14:47:24 -07002259 }
2260
2261 @Override
2262 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002263 if (super.performAccessibilityAction(host, action, arguments)) {
2264 return true;
2265 }
2266
alanvc1d7e772012-05-08 14:47:24 -07002267 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002268 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002269
alanv9c3e0e62012-05-18 17:43:35 -07002270 if ((position == INVALID_POSITION) || (adapter == null)) {
2271 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002272 return false;
2273 }
2274
alanv9c3e0e62012-05-18 17:43:35 -07002275 if (!isEnabled() || !adapter.isEnabled(position)) {
2276 // Cannot perform actions on disabled items.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002277 return false;
2278 }
2279
alanvc1d7e772012-05-08 14:47:24 -07002280 final long id = getItemIdAtPosition(position);
2281
2282 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002283 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2284 if (getSelectedItemPosition() == position) {
2285 setSelection(INVALID_POSITION);
2286 return true;
2287 }
2288 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002289 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002290 if (getSelectedItemPosition() != position) {
2291 setSelection(position);
2292 return true;
2293 }
2294 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002295 case AccessibilityNodeInfo.ACTION_CLICK: {
2296 if (isClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002297 return performItemClick(host, position, id);
2298 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002299 } return false;
2300 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2301 if (isLongClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002302 return performLongPress(host, position, id);
2303 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002304 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002305 }
2306
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002307 return false;
alanvc1d7e772012-05-08 14:47:24 -07002308 }
2309 }
2310
Dianne Hackborn079e2352010-10-18 17:02:43 -07002311 void positionSelector(int position, View sel) {
2312 if (position != INVALID_POSITION) {
2313 mSelectorPosition = position;
2314 }
2315
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002316 final Rect selectorRect = mSelectorRect;
2317 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002318 if (sel instanceof SelectionBoundsAdjuster) {
2319 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2320 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002321 positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
2322 selectorRect.bottom);
2323
2324 final boolean isChildViewEnabled = mIsChildViewEnabled;
2325 if (sel.isEnabled() != isChildViewEnabled) {
2326 mIsChildViewEnabled = !isChildViewEnabled;
Jason Bayera79f4b72011-03-22 20:03:31 -07002327 if (getSelectedItemPosition() != INVALID_POSITION) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002328 refreshDrawableState();
2329 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002330 }
2331 }
2332
2333 private void positionSelector(int l, int t, int r, int b) {
2334 mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
2335 + mSelectionRightPadding, b + mSelectionBottomPadding);
2336 }
2337
2338 @Override
2339 protected void dispatchDraw(Canvas canvas) {
2340 int saveCount = 0;
2341 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2342 if (clipToPadding) {
2343 saveCount = canvas.save();
2344 final int scrollX = mScrollX;
2345 final int scrollY = mScrollY;
2346 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2347 scrollX + mRight - mLeft - mPaddingRight,
2348 scrollY + mBottom - mTop - mPaddingBottom);
2349 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2350 }
2351
2352 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2353 if (!drawSelectorOnTop) {
2354 drawSelector(canvas);
2355 }
2356
2357 super.dispatchDraw(canvas);
2358
2359 if (drawSelectorOnTop) {
2360 drawSelector(canvas);
2361 }
2362
2363 if (clipToPadding) {
2364 canvas.restoreToCount(saveCount);
2365 mGroupFlags |= CLIP_TO_PADDING_MASK;
2366 }
2367 }
2368
2369 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002370 protected boolean isPaddingOffsetRequired() {
2371 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2372 }
2373
2374 @Override
2375 protected int getLeftPaddingOffset() {
2376 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2377 }
2378
2379 @Override
2380 protected int getTopPaddingOffset() {
2381 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2382 }
2383
2384 @Override
2385 protected int getRightPaddingOffset() {
2386 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2387 }
2388
2389 @Override
2390 protected int getBottomPaddingOffset() {
2391 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2392 }
2393
2394 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002395 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2396 if (getChildCount() > 0) {
2397 mDataChanged = true;
2398 rememberSyncState();
2399 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002400
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002401 if (mFastScroller != null) {
2402 mFastScroller.onSizeChanged(w, h, oldw, oldh);
2403 }
2404 }
2405
2406 /**
2407 * @return True if the current touch mode requires that we draw the selector in the pressed
2408 * state.
2409 */
2410 boolean touchModeDrawsInPressedState() {
2411 // FIXME use isPressed for this
2412 switch (mTouchMode) {
2413 case TOUCH_MODE_TAP:
2414 case TOUCH_MODE_DONE_WAITING:
2415 return true;
2416 default:
2417 return false;
2418 }
2419 }
2420
2421 /**
2422 * Indicates whether this view is in a state where the selector should be drawn. This will
2423 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2424 * the pressed state for an item.
2425 *
2426 * @return True if the selector should be shown
2427 */
2428 boolean shouldShowSelector() {
2429 return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
2430 }
2431
2432 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002433 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002434 final Drawable selector = mSelector;
2435 selector.setBounds(mSelectorRect);
2436 selector.draw(canvas);
2437 }
2438 }
2439
2440 /**
2441 * Controls whether the selection highlight drawable should be drawn on top of the item or
2442 * behind it.
2443 *
2444 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2445 * is false.
2446 *
2447 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2448 */
2449 public void setDrawSelectorOnTop(boolean onTop) {
2450 mDrawSelectorOnTop = onTop;
2451 }
2452
2453 /**
2454 * Set a Drawable that should be used to highlight the currently selected item.
2455 *
2456 * @param resID A Drawable resource to use as the selection highlight.
2457 *
2458 * @attr ref android.R.styleable#AbsListView_listSelector
2459 */
2460 public void setSelector(int resID) {
2461 setSelector(getResources().getDrawable(resID));
2462 }
2463
2464 public void setSelector(Drawable sel) {
2465 if (mSelector != null) {
2466 mSelector.setCallback(null);
2467 unscheduleDrawable(mSelector);
2468 }
2469 mSelector = sel;
2470 Rect padding = new Rect();
2471 sel.getPadding(padding);
2472 mSelectionLeftPadding = padding.left;
2473 mSelectionTopPadding = padding.top;
2474 mSelectionRightPadding = padding.right;
2475 mSelectionBottomPadding = padding.bottom;
2476 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002477 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002478 }
2479
2480 /**
2481 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2482 * selection in the list.
2483 *
2484 * @return the drawable used to display the selector
2485 */
2486 public Drawable getSelector() {
2487 return mSelector;
2488 }
2489
2490 /**
2491 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2492 * this is a long press.
2493 */
2494 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002495 if (!isEnabled() || !isClickable()) {
2496 return;
2497 }
2498
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002499 Drawable selector = mSelector;
2500 Rect selectorRect = mSelectorRect;
2501 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002502 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002503
2504 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2505
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002506 if (v != null) {
2507 if (v.hasFocusable()) return;
2508 v.setPressed(true);
2509 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002510 setPressed(true);
2511
2512 final boolean longClickable = isLongClickable();
2513 Drawable d = selector.getCurrent();
2514 if (d != null && d instanceof TransitionDrawable) {
2515 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002516 ((TransitionDrawable) d).startTransition(
2517 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002518 } else {
2519 ((TransitionDrawable) d).resetTransition();
2520 }
2521 }
2522 if (longClickable && !mDataChanged) {
2523 if (mPendingCheckForKeyLongPress == null) {
2524 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2525 }
2526 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2527 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2528 }
2529 }
2530 }
2531
2532 public void setScrollIndicators(View up, View down) {
2533 mScrollUp = up;
2534 mScrollDown = down;
2535 }
2536
Dianne Hackborn079e2352010-10-18 17:02:43 -07002537 void updateSelectorState() {
2538 if (mSelector != null) {
2539 if (shouldShowSelector()) {
2540 mSelector.setState(getDrawableState());
2541 } else {
2542 mSelector.setState(StateSet.NOTHING);
2543 }
2544 }
2545 }
2546
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002547 @Override
2548 protected void drawableStateChanged() {
2549 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002550 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002551 }
2552
2553 @Override
2554 protected int[] onCreateDrawableState(int extraSpace) {
2555 // If the child view is enabled then do the default behavior.
2556 if (mIsChildViewEnabled) {
2557 // Common case
2558 return super.onCreateDrawableState(extraSpace);
2559 }
2560
2561 // The selector uses this View's drawable state. The selected child view
2562 // is disabled, so we need to remove the enabled state from the drawable
2563 // states.
2564 final int enabledState = ENABLED_STATE_SET[0];
2565
2566 // If we don't have any extra space, it will return one of the static state arrays,
2567 // and clearing the enabled state on those arrays is a bad thing! If we specify
2568 // we need extra space, it will create+copy into a new array that safely mutable.
2569 int[] state = super.onCreateDrawableState(extraSpace + 1);
2570 int enabledPos = -1;
2571 for (int i = state.length - 1; i >= 0; i--) {
2572 if (state[i] == enabledState) {
2573 enabledPos = i;
2574 break;
2575 }
2576 }
2577
2578 // Remove the enabled state
2579 if (enabledPos >= 0) {
2580 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2581 state.length - enabledPos - 1);
2582 }
Romain Guy0a637162009-05-29 14:43:54 -07002583
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002584 return state;
2585 }
2586
2587 @Override
2588 public boolean verifyDrawable(Drawable dr) {
2589 return mSelector == dr || super.verifyDrawable(dr);
2590 }
2591
2592 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002593 public void jumpDrawablesToCurrentState() {
2594 super.jumpDrawablesToCurrentState();
2595 if (mSelector != null) mSelector.jumpToCurrentState();
2596 }
2597
2598 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002599 protected void onAttachedToWindow() {
2600 super.onAttachedToWindow();
2601
2602 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002603 treeObserver.addOnTouchModeChangeListener(this);
2604 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2605 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002606 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002607
Romain Guy82afc7b2010-05-13 11:52:37 -07002608 if (mAdapter != null && mDataSetObserver == null) {
2609 mDataSetObserver = new AdapterDataSetObserver();
2610 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002611
2612 // Data may have changed while we were detached. Refresh.
2613 mDataChanged = true;
2614 mOldItemCount = mItemCount;
2615 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002616 }
Adam Powellb3750132011-08-08 23:29:12 -07002617 mIsAttached = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002618 }
2619
2620 @Override
2621 protected void onDetachedFromWindow() {
2622 super.onDetachedFromWindow();
2623
Romain Guy1f7f3c32009-07-22 11:25:42 -07002624 // Dismiss the popup in case onSaveInstanceState() was not invoked
2625 dismissPopup();
2626
Romain Guy21875052010-01-06 18:48:08 -08002627 // Detach any view left in the scrap heap
2628 mRecycler.clear();
2629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002630 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002631 treeObserver.removeOnTouchModeChangeListener(this);
2632 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002633 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002634 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002635 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002636
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002637 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002638 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2639 mDataSetObserver = null;
2640 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002641
2642 if (mScrollStrictSpan != null) {
2643 mScrollStrictSpan.finish();
2644 mScrollStrictSpan = null;
2645 }
2646
2647 if (mFlingStrictSpan != null) {
2648 mFlingStrictSpan.finish();
2649 mFlingStrictSpan = null;
2650 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002651
2652 if (mFlingRunnable != null) {
2653 removeCallbacks(mFlingRunnable);
2654 }
2655
2656 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002657 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002658 }
2659
2660 if (mClearScrollingCache != null) {
2661 removeCallbacks(mClearScrollingCache);
2662 }
2663
2664 if (mPerformClick != null) {
2665 removeCallbacks(mPerformClick);
2666 }
2667
2668 if (mTouchModeReset != null) {
2669 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002670 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002671 }
Adam Powellb3750132011-08-08 23:29:12 -07002672 mIsAttached = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002673 }
2674
2675 @Override
2676 public void onWindowFocusChanged(boolean hasWindowFocus) {
2677 super.onWindowFocusChanged(hasWindowFocus);
2678
2679 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2680
2681 if (!hasWindowFocus) {
2682 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002683 if (mFlingRunnable != null) {
2684 removeCallbacks(mFlingRunnable);
2685 // let the fling runnable report it's new state which
2686 // should be idle
2687 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002688 if (mPositionScroller != null) {
2689 mPositionScroller.stop();
2690 }
Adam Powell45803472010-01-25 15:10:44 -08002691 if (mScrollY != 0) {
2692 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002693 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002694 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002695 invalidate();
2696 }
Mark Wagner670dd812010-01-13 16:17:47 -08002697 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002698 // Always hide the type filter
2699 dismissPopup();
2700
2701 if (touchMode == TOUCH_MODE_OFF) {
2702 // Remember the last selected element
2703 mResurrectToPosition = mSelectedPosition;
2704 }
2705 } else {
Adam Powell97566042010-03-09 15:34:09 -08002706 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002707 // Show the type filter only if a filter is in effect
2708 showPopup();
2709 }
2710
2711 // If we changed touch mode since the last time we had focus
2712 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2713 // If we come back in trackball mode, we bring the selection back
2714 if (touchMode == TOUCH_MODE_OFF) {
2715 // This will trigger a layout
2716 resurrectSelection();
2717
2718 // If we come back in touch mode, then we want to hide the selector
2719 } else {
2720 hideSelector();
2721 mLayoutMode = LAYOUT_NORMAL;
2722 layoutChildren();
2723 }
2724 }
2725 }
2726
2727 mLastTouchMode = touchMode;
2728 }
2729
Fabrice Di Meglio3a1f1e52013-04-16 15:40:18 -07002730 @Override
2731 public void onRtlPropertiesChanged(int layoutDirection) {
2732 super.onRtlPropertiesChanged(layoutDirection);
2733
2734 if (mFastScroller != null) {
2735 mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition());
2736 }
2737 }
2738
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002739 /**
2740 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
2741 * methods knows the view, position and ID of the item that received the
2742 * long press.
2743 *
2744 * @param view The view that received the long press.
2745 * @param position The position of the item that received the long press.
2746 * @param id The ID of the item that received the long press.
2747 * @return The extra information that should be returned by
2748 * {@link #getContextMenuInfo()}.
2749 */
2750 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
2751 return new AdapterContextMenuInfo(view, position, id);
2752 }
2753
2754 /**
2755 * A base class for Runnables that will check that their view is still attached to
2756 * the original window as when the Runnable was created.
2757 *
2758 */
2759 private class WindowRunnnable {
2760 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07002761
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002762 public void rememberWindowAttachCount() {
2763 mOriginalAttachCount = getWindowAttachCount();
2764 }
Romain Guy0a637162009-05-29 14:43:54 -07002765
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002766 public boolean sameWindow() {
2767 return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
2768 }
2769 }
Romain Guy0a637162009-05-29 14:43:54 -07002770
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002771 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002772 int mClickMotionPosition;
2773
2774 public void run() {
2775 // The data has changed since we posted this action in the event queue,
2776 // bail out before bad things happen
2777 if (mDataChanged) return;
2778
Adam Powell005c0a42010-03-30 16:26:36 -07002779 final ListAdapter adapter = mAdapter;
2780 final int motionPosition = mClickMotionPosition;
2781 if (adapter != null && mItemCount > 0 &&
2782 motionPosition != INVALID_POSITION &&
2783 motionPosition < adapter.getCount() && sameWindow()) {
Romain Guy7890fe22011-01-18 20:24:18 -08002784 final View view = getChildAt(motionPosition - mFirstPosition);
2785 // If there is no view, something bad happened (the view scrolled off the
2786 // screen, etc.) and we should cancel the click
2787 if (view != null) {
2788 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2789 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002790 }
2791 }
2792 }
2793
2794 private class CheckForLongPress extends WindowRunnnable implements Runnable {
2795 public void run() {
2796 final int motionPosition = mMotionPosition;
2797 final View child = getChildAt(motionPosition - mFirstPosition);
2798 if (child != null) {
2799 final int longPressPosition = mMotionPosition;
2800 final long longPressId = mAdapter.getItemId(mMotionPosition);
2801
2802 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07002803 if (sameWindow() && !mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002804 handled = performLongPress(child, longPressPosition, longPressId);
2805 }
2806 if (handled) {
2807 mTouchMode = TOUCH_MODE_REST;
2808 setPressed(false);
2809 child.setPressed(false);
2810 } else {
2811 mTouchMode = TOUCH_MODE_DONE_WAITING;
2812 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002813 }
2814 }
2815 }
Romain Guy0a637162009-05-29 14:43:54 -07002816
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002817 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
2818 public void run() {
2819 if (isPressed() && mSelectedPosition >= 0) {
2820 int index = mSelectedPosition - mFirstPosition;
2821 View v = getChildAt(index);
2822
2823 if (!mDataChanged) {
2824 boolean handled = false;
2825 if (sameWindow()) {
2826 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2827 }
2828 if (handled) {
2829 setPressed(false);
2830 v.setPressed(false);
2831 }
2832 } else {
2833 setPressed(false);
2834 if (v != null) v.setPressed(false);
2835 }
2836 }
2837 }
2838 }
2839
Adam Powell8350f7d2010-07-28 14:27:28 -07002840 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002841 final int longPressPosition, final long longPressId) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002842 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2843 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07002844 if (mChoiceActionMode == null &&
2845 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002846 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07002847 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07002848 }
Adam Powellf343e1b2010-08-13 18:27:04 -07002849 return true;
2850 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002851
Adam Powellf343e1b2010-08-13 18:27:04 -07002852 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002853 if (mOnItemLongClickListener != null) {
2854 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2855 longPressPosition, longPressId);
2856 }
2857 if (!handled) {
2858 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2859 handled = super.showContextMenuForChild(AbsListView.this);
2860 }
2861 if (handled) {
2862 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2863 }
2864 return handled;
2865 }
2866
2867 @Override
2868 protected ContextMenuInfo getContextMenuInfo() {
2869 return mContextMenuInfo;
2870 }
2871
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07002872 /** @hide */
2873 @Override
2874 public boolean showContextMenu(float x, float y, int metaState) {
2875 final int position = pointToPosition((int)x, (int)y);
2876 if (position != INVALID_POSITION) {
2877 final long id = mAdapter.getItemId(position);
2878 View child = getChildAt(position - mFirstPosition);
2879 if (child != null) {
2880 mContextMenuInfo = createContextMenuInfo(child, position, id);
2881 return super.showContextMenuForChild(AbsListView.this);
2882 }
2883 }
2884 return super.showContextMenu(x, y, metaState);
2885 }
2886
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002887 @Override
2888 public boolean showContextMenuForChild(View originalView) {
2889 final int longPressPosition = getPositionForView(originalView);
2890 if (longPressPosition >= 0) {
2891 final long longPressId = mAdapter.getItemId(longPressPosition);
2892 boolean handled = false;
2893
2894 if (mOnItemLongClickListener != null) {
2895 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
2896 longPressPosition, longPressId);
2897 }
2898 if (!handled) {
2899 mContextMenuInfo = createContextMenuInfo(
2900 getChildAt(longPressPosition - mFirstPosition),
2901 longPressPosition, longPressId);
2902 handled = super.showContextMenuForChild(originalView);
2903 }
2904
2905 return handled;
2906 }
2907 return false;
2908 }
2909
2910 @Override
Romain Guydf016072009-08-17 12:51:30 -07002911 public boolean onKeyDown(int keyCode, KeyEvent event) {
2912 return false;
2913 }
2914
2915 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002916 public boolean onKeyUp(int keyCode, KeyEvent event) {
2917 switch (keyCode) {
2918 case KeyEvent.KEYCODE_DPAD_CENTER:
2919 case KeyEvent.KEYCODE_ENTER:
Romain Guydd753ae2009-08-17 10:36:23 -07002920 if (!isEnabled()) {
2921 return true;
2922 }
Romain Guydf016072009-08-17 12:51:30 -07002923 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07002924 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002925 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07002926
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07002928 if (view != null) {
2929 performItemClick(view, mSelectedPosition, mSelectedRowId);
2930 view.setPressed(false);
2931 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002932 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002933 return true;
2934 }
Romain Guydd753ae2009-08-17 10:36:23 -07002935 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002936 }
2937 return super.onKeyUp(keyCode, event);
2938 }
2939
2940 @Override
2941 protected void dispatchSetPressed(boolean pressed) {
2942 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
2943 // get the selector in the right state, but we don't want to press each child.
2944 }
2945
2946 /**
2947 * Maps a point to a position in the list.
2948 *
2949 * @param x X in local coordinate
2950 * @param y Y in local coordinate
2951 * @return The position of the item which contains the specified point, or
2952 * {@link #INVALID_POSITION} if the point does not intersect an item.
2953 */
2954 public int pointToPosition(int x, int y) {
2955 Rect frame = mTouchFrame;
2956 if (frame == null) {
2957 mTouchFrame = new Rect();
2958 frame = mTouchFrame;
2959 }
2960
2961 final int count = getChildCount();
2962 for (int i = count - 1; i >= 0; i--) {
2963 final View child = getChildAt(i);
2964 if (child.getVisibility() == View.VISIBLE) {
2965 child.getHitRect(frame);
2966 if (frame.contains(x, y)) {
2967 return mFirstPosition + i;
2968 }
2969 }
2970 }
2971 return INVALID_POSITION;
2972 }
2973
2974
2975 /**
2976 * Maps a point to a the rowId of the item which intersects that point.
2977 *
2978 * @param x X in local coordinate
2979 * @param y Y in local coordinate
2980 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
2981 * if the point does not intersect an item.
2982 */
2983 public long pointToRowId(int x, int y) {
2984 int position = pointToPosition(x, y);
2985 if (position >= 0) {
2986 return mAdapter.getItemId(position);
2987 }
2988 return INVALID_ROW_ID;
2989 }
2990
2991 final class CheckForTap implements Runnable {
2992 public void run() {
2993 if (mTouchMode == TOUCH_MODE_DOWN) {
2994 mTouchMode = TOUCH_MODE_TAP;
2995 final View child = getChildAt(mMotionPosition - mFirstPosition);
2996 if (child != null && !child.hasFocusable()) {
2997 mLayoutMode = LAYOUT_NORMAL;
2998
2999 if (!mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003000 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003001 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003002 layoutChildren();
3003 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003004 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003005
3006 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3007 final boolean longClickable = isLongClickable();
3008
3009 if (mSelector != null) {
3010 Drawable d = mSelector.getCurrent();
3011 if (d != null && d instanceof TransitionDrawable) {
3012 if (longClickable) {
3013 ((TransitionDrawable) d).startTransition(longPressTimeout);
3014 } else {
3015 ((TransitionDrawable) d).resetTransition();
3016 }
3017 }
3018 }
3019
3020 if (longClickable) {
3021 if (mPendingCheckForLongPress == null) {
3022 mPendingCheckForLongPress = new CheckForLongPress();
3023 }
3024 mPendingCheckForLongPress.rememberWindowAttachCount();
3025 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3026 } else {
3027 mTouchMode = TOUCH_MODE_DONE_WAITING;
3028 }
3029 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003030 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003031 }
3032 }
3033 }
3034 }
3035 }
3036
Jeff Brown78f6e632011-09-09 17:15:31 -07003037 private boolean startScrollIfNeeded(int y) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003038 // Check if we have moved far enough that it looks more like a
3039 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003040 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003041 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003042 final boolean overscroll = mScrollY != 0;
3043 if (overscroll || distance > mTouchSlop) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003044 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003045 if (overscroll) {
3046 mTouchMode = TOUCH_MODE_OVERSCROLL;
3047 mMotionCorrection = 0;
3048 } else {
3049 mTouchMode = TOUCH_MODE_SCROLL;
3050 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3051 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003052 final Handler handler = getHandler();
3053 // Handler should not be null unless the AbsListView is not attached to a
3054 // window, which would make it very hard to scroll it... but the monkeys
3055 // say it's possible.
3056 if (handler != null) {
3057 handler.removeCallbacks(mPendingCheckForLongPress);
3058 }
3059 setPressed(false);
3060 View motionView = getChildAt(mMotionPosition - mFirstPosition);
3061 if (motionView != null) {
3062 motionView.setPressed(false);
3063 }
3064 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3065 // Time to start stealing events! Once we've stolen them, don't let anyone
3066 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003067 final ViewParent parent = getParent();
3068 if (parent != null) {
3069 parent.requestDisallowInterceptTouchEvent(true);
3070 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003071 scrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003072 return true;
3073 }
3074
3075 return false;
3076 }
3077
Jeff Brown78f6e632011-09-09 17:15:31 -07003078 private void scrollIfNeeded(int y) {
3079 final int rawDeltaY = y - mMotionY;
3080 final int deltaY = rawDeltaY - mMotionCorrection;
3081 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3082
3083 if (mTouchMode == TOUCH_MODE_SCROLL) {
3084 if (PROFILE_SCROLLING) {
3085 if (!mScrollProfilingStarted) {
3086 Debug.startMethodTracing("AbsListViewScroll");
3087 mScrollProfilingStarted = true;
3088 }
3089 }
3090
3091 if (mScrollStrictSpan == null) {
3092 // If it's non-null, we're already in a scroll.
3093 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3094 }
3095
3096 if (y != mLastY) {
3097 // We may be here after stopping a fling and continuing to scroll.
3098 // If so, we haven't disallowed intercepting touch events yet.
3099 // Make sure that we do so in case we're in a parent that can intercept.
3100 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3101 Math.abs(rawDeltaY) > mTouchSlop) {
3102 final ViewParent parent = getParent();
3103 if (parent != null) {
3104 parent.requestDisallowInterceptTouchEvent(true);
3105 }
3106 }
3107
3108 final int motionIndex;
3109 if (mMotionPosition >= 0) {
3110 motionIndex = mMotionPosition - mFirstPosition;
3111 } else {
3112 // If we don't have a motion position that we can reliably track,
3113 // pick something in the middle to make a best guess at things below.
3114 motionIndex = getChildCount() / 2;
3115 }
3116
3117 int motionViewPrevTop = 0;
3118 View motionView = this.getChildAt(motionIndex);
3119 if (motionView != null) {
3120 motionViewPrevTop = motionView.getTop();
3121 }
3122
3123 // No need to do all this work if we're not going to move anyway
3124 boolean atEdge = false;
3125 if (incrementalDeltaY != 0) {
3126 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3127 }
3128
3129 // Check to see if we have bumped into the scroll limit
3130 motionView = this.getChildAt(motionIndex);
3131 if (motionView != null) {
3132 // Check if the top of the motion view is where it is
3133 // supposed to be
3134 final int motionViewRealTop = motionView.getTop();
3135 if (atEdge) {
3136 // Apply overscroll
3137
3138 int overscroll = -incrementalDeltaY -
3139 (motionViewRealTop - motionViewPrevTop);
3140 overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3141 0, mOverscrollDistance, true);
3142 if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3143 // Don't allow overfling if we're at the edge.
3144 if (mVelocityTracker != null) {
3145 mVelocityTracker.clear();
3146 }
3147 }
3148
3149 final int overscrollMode = getOverScrollMode();
3150 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3151 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3152 !contentFits())) {
3153 mDirection = 0; // Reset when entering overscroll.
3154 mTouchMode = TOUCH_MODE_OVERSCROLL;
3155 if (rawDeltaY > 0) {
3156 mEdgeGlowTop.onPull((float) overscroll / getHeight());
3157 if (!mEdgeGlowBottom.isFinished()) {
3158 mEdgeGlowBottom.onRelease();
3159 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003160 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003161 } else if (rawDeltaY < 0) {
3162 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3163 if (!mEdgeGlowTop.isFinished()) {
3164 mEdgeGlowTop.onRelease();
3165 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003166 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003167 }
3168 }
3169 }
3170 mMotionY = y;
Jeff Brown78f6e632011-09-09 17:15:31 -07003171 }
3172 mLastY = y;
3173 }
3174 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3175 if (y != mLastY) {
3176 final int oldScroll = mScrollY;
3177 final int newScroll = oldScroll - incrementalDeltaY;
3178 int newDirection = y > mLastY ? 1 : -1;
3179
3180 if (mDirection == 0) {
3181 mDirection = newDirection;
3182 }
3183
3184 int overScrollDistance = -incrementalDeltaY;
3185 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3186 overScrollDistance = -oldScroll;
3187 incrementalDeltaY += overScrollDistance;
3188 } else {
3189 incrementalDeltaY = 0;
3190 }
3191
3192 if (overScrollDistance != 0) {
3193 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3194 0, mOverscrollDistance, true);
3195 final int overscrollMode = getOverScrollMode();
3196 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3197 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3198 !contentFits())) {
3199 if (rawDeltaY > 0) {
3200 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3201 if (!mEdgeGlowBottom.isFinished()) {
3202 mEdgeGlowBottom.onRelease();
3203 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003204 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003205 } else if (rawDeltaY < 0) {
3206 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3207 if (!mEdgeGlowTop.isFinished()) {
3208 mEdgeGlowTop.onRelease();
3209 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003210 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003211 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003212 }
3213 }
3214
3215 if (incrementalDeltaY != 0) {
3216 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003217 if (mScrollY != 0) {
3218 mScrollY = 0;
3219 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003220 }
3221
Romain Guy9d849a22012-03-14 16:41:42 -07003222 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3223
Jeff Brown78f6e632011-09-09 17:15:31 -07003224 mTouchMode = TOUCH_MODE_SCROLL;
3225
3226 // We did not scroll the full amount. Treat this essentially like the
3227 // start of a new touch scroll
3228 final int motionPosition = findClosestMotionRow(y);
3229
3230 mMotionCorrection = 0;
3231 View motionView = getChildAt(motionPosition - mFirstPosition);
3232 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3233 mMotionY = y;
3234 mMotionPosition = motionPosition;
3235 }
3236 mLastY = y;
3237 mDirection = newDirection;
3238 }
3239 }
3240 }
3241
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003242 public void onTouchModeChanged(boolean isInTouchMode) {
3243 if (isInTouchMode) {
3244 // Get rid of the selection when we enter touch mode
3245 hideSelector();
3246 // Layout, but only if we already have done so previously.
3247 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3248 // state.)
3249 if (getHeight() > 0 && getChildCount() > 0) {
3250 // We do not lose focus initiating a touch (since AbsListView is focusable in
3251 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003252 layoutChildren();
3253 }
Jeff Brown1e209462011-07-14 22:19:19 -07003254 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003255 } else {
3256 int touchMode = mTouchMode;
3257 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3258 if (mFlingRunnable != null) {
3259 mFlingRunnable.endFling();
3260 }
Adam Powell40322522011-01-12 21:58:20 -08003261 if (mPositionScroller != null) {
3262 mPositionScroller.stop();
3263 }
Adam Powell637d3372010-08-25 14:37:03 -07003264
3265 if (mScrollY != 0) {
3266 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003267 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003268 finishGlows();
3269 invalidate();
3270 }
3271 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003272 }
3273 }
3274
3275 @Override
3276 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003277 if (!isEnabled()) {
3278 // A disabled view that is clickable still consumes the touch
3279 // events, it just doesn't respond to them.
3280 return isClickable() || isLongClickable();
3281 }
3282
Adam Powell1fa179ef2012-04-12 15:01:40 -07003283 if (mPositionScroller != null) {
3284 mPositionScroller.stop();
3285 }
3286
Adam Powell28048d02012-06-06 22:46:42 -07003287 if (!mIsAttached) {
3288 // Something isn't right.
3289 // Since we rely on being attached to get data set change notifications,
3290 // don't risk doing anything where we might try to resync and find things
3291 // in a bogus state.
3292 return false;
3293 }
3294
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003295 if (mFastScroller != null) {
3296 boolean intercepted = mFastScroller.onTouchEvent(ev);
3297 if (intercepted) {
3298 return true;
Romain Guy0a637162009-05-29 14:43:54 -07003299 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003300 }
Romain Guy82f34952009-05-24 18:40:45 -07003301
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003302 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003303
3304 View v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003305
Michael Jurka13451a42011-08-22 15:54:21 -07003306 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003307 mVelocityTracker.addMovement(ev);
3308
Adam Powell4cd47702010-02-25 11:21:14 -08003309 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003310 case MotionEvent.ACTION_DOWN: {
Adam Powell637d3372010-08-25 14:37:03 -07003311 switch (mTouchMode) {
3312 case TOUCH_MODE_OVERFLING: {
3313 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003314 if (mPositionScroller != null) {
3315 mPositionScroller.stop();
3316 }
Adam Powell637d3372010-08-25 14:37:03 -07003317 mTouchMode = TOUCH_MODE_OVERSCROLL;
Adam Powell044a46b2011-07-25 19:07:06 -07003318 mMotionX = (int) ev.getX();
Adam Powell637d3372010-08-25 14:37:03 -07003319 mMotionY = mLastY = (int) ev.getY();
3320 mMotionCorrection = 0;
3321 mActivePointerId = ev.getPointerId(0);
Adam Powell044a46b2011-07-25 19:07:06 -07003322 mDirection = 0;
Adam Powell637d3372010-08-25 14:37:03 -07003323 break;
3324 }
Adam Powell9d32d242010-03-29 16:02:07 -07003325
Adam Powell637d3372010-08-25 14:37:03 -07003326 default: {
3327 mActivePointerId = ev.getPointerId(0);
3328 final int x = (int) ev.getX();
3329 final int y = (int) ev.getY();
3330 int motionPosition = pointToPosition(x, y);
3331 if (!mDataChanged) {
3332 if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
3333 && (getAdapter().isEnabled(motionPosition))) {
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003334 // User clicked on an actual view (and was not stopping a fling).
3335 // It might be a click or a scroll. Assume it is a click until
3336 // proven otherwise
Adam Powell637d3372010-08-25 14:37:03 -07003337 mTouchMode = TOUCH_MODE_DOWN;
3338 // FIXME Debounce
3339 if (mPendingCheckForTap == null) {
3340 mPendingCheckForTap = new CheckForTap();
3341 }
3342 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
3343 } else {
Adam Powell637d3372010-08-25 14:37:03 -07003344 if (mTouchMode == TOUCH_MODE_FLING) {
3345 // Stopped a fling. It is a scroll.
3346 createScrollingCache();
3347 mTouchMode = TOUCH_MODE_SCROLL;
3348 mMotionCorrection = 0;
3349 motionPosition = findMotionRow(y);
3350 mFlingRunnable.flywheelTouch();
3351 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003352 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003354
Adam Powell637d3372010-08-25 14:37:03 -07003355 if (motionPosition >= 0) {
3356 // Remember where the motion event started
3357 v = getChildAt(motionPosition - mFirstPosition);
3358 mMotionViewOriginalTop = v.getTop();
3359 }
3360 mMotionX = x;
3361 mMotionY = y;
3362 mMotionPosition = motionPosition;
3363 mLastY = Integer.MIN_VALUE;
3364 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003365 }
Adam Powell637d3372010-08-25 14:37:03 -07003366 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003367
3368 if (performButtonActionOnTouchDown(ev)) {
3369 if (mTouchMode == TOUCH_MODE_DOWN) {
3370 removeCallbacks(mPendingCheckForTap);
3371 }
3372 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003373 break;
3374 }
3375
3376 case MotionEvent.ACTION_MOVE: {
Justin Koh2585e9b2011-06-30 17:11:26 -07003377 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3378 if (pointerIndex == -1) {
3379 pointerIndex = 0;
3380 mActivePointerId = ev.getPointerId(pointerIndex);
3381 }
Adam Powell4cd47702010-02-25 11:21:14 -08003382 final int y = (int) ev.getY(pointerIndex);
Adam Powell6f663c12012-04-30 16:59:02 -07003383
3384 if (mDataChanged) {
3385 // Re-sync everything if data has been changed
3386 // since the scroll operation can query the adapter.
3387 layoutChildren();
3388 }
3389
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003390 switch (mTouchMode) {
3391 case TOUCH_MODE_DOWN:
3392 case TOUCH_MODE_TAP:
3393 case TOUCH_MODE_DONE_WAITING:
3394 // Check if we have moved far enough that it looks more like a
3395 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003396 startScrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003397 break;
3398 case TOUCH_MODE_SCROLL:
Adam Powell637d3372010-08-25 14:37:03 -07003399 case TOUCH_MODE_OVERSCROLL:
Jeff Brown78f6e632011-09-09 17:15:31 -07003400 scrollIfNeeded(y);
Adam Powell637d3372010-08-25 14:37:03 -07003401 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003402 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003403 break;
3404 }
3405
3406 case MotionEvent.ACTION_UP: {
3407 switch (mTouchMode) {
3408 case TOUCH_MODE_DOWN:
3409 case TOUCH_MODE_TAP:
3410 case TOUCH_MODE_DONE_WAITING:
3411 final int motionPosition = mMotionPosition;
3412 final View child = getChildAt(motionPosition - mFirstPosition);
Adam Powell498e43d2011-03-01 15:39:53 -08003413
3414 final float x = ev.getX();
3415 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3416
3417 if (child != null && !child.hasFocusable() && inList) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003418 if (mTouchMode != TOUCH_MODE_DOWN) {
3419 child.setPressed(false);
3420 }
3421
3422 if (mPerformClick == null) {
3423 mPerformClick = new PerformClick();
3424 }
3425
3426 final AbsListView.PerformClick performClick = mPerformClick;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003427 performClick.mClickMotionPosition = motionPosition;
3428 performClick.rememberWindowAttachCount();
3429
3430 mResurrectToPosition = motionPosition;
3431
3432 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3433 final Handler handler = getHandler();
3434 if (handler != null) {
3435 handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3436 mPendingCheckForTap : mPendingCheckForLongPress);
3437 }
3438 mLayoutMode = LAYOUT_NORMAL;
Adam Powell005c0a42010-03-30 16:26:36 -07003439 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3440 mTouchMode = TOUCH_MODE_TAP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003441 setSelectedPositionInt(mMotionPosition);
3442 layoutChildren();
3443 child.setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003444 positionSelector(mMotionPosition, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003445 setPressed(true);
3446 if (mSelector != null) {
3447 Drawable d = mSelector.getCurrent();
3448 if (d != null && d instanceof TransitionDrawable) {
Romain Guy6198ae82009-08-31 17:45:55 -07003449 ((TransitionDrawable) d).resetTransition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003450 }
3451 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003452 if (mTouchModeReset != null) {
3453 removeCallbacks(mTouchModeReset);
3454 }
3455 mTouchModeReset = new Runnable() {
3456 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003457 public void run() {
Adam Powell847be742012-12-11 15:41:17 -08003458 mTouchModeReset = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003459 mTouchMode = TOUCH_MODE_REST;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003460 child.setPressed(false);
3461 setPressed(false);
3462 if (!mDataChanged) {
Romain Guyd0b83652011-01-09 15:26:13 -08003463 performClick.run();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003464 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003465 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003466 };
3467 postDelayed(mTouchModeReset,
3468 ViewConfiguration.getPressedStateDuration());
Adam Powell005c0a42010-03-30 16:26:36 -07003469 } else {
3470 mTouchMode = TOUCH_MODE_REST;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003471 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003472 }
3473 return true;
Adam Powell005c0a42010-03-30 16:26:36 -07003474 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
Romain Guyd0b83652011-01-09 15:26:13 -08003475 performClick.run();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003476 }
3477 }
3478 mTouchMode = TOUCH_MODE_REST;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003479 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003480 break;
3481 case TOUCH_MODE_SCROLL:
Romain Guy6198ae82009-08-31 17:45:55 -07003482 final int childCount = getChildCount();
3483 if (childCount > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003484 final int firstChildTop = getChildAt(0).getTop();
3485 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3486 final int contentTop = mListPadding.top;
3487 final int contentBottom = getHeight() - mListPadding.bottom;
3488 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
Romain Guy6198ae82009-08-31 17:45:55 -07003489 mFirstPosition + childCount < mItemCount &&
Adam Powell637d3372010-08-25 14:37:03 -07003490 lastChildBottom <= getHeight() - contentBottom) {
Romain Guy6198ae82009-08-31 17:45:55 -07003491 mTouchMode = TOUCH_MODE_REST;
3492 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3493 } else {
3494 final VelocityTracker velocityTracker = mVelocityTracker;
3495 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powell637d3372010-08-25 14:37:03 -07003496
Romain Guy21317d12010-10-12 13:32:31 -07003497 final int initialVelocity = (int)
3498 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
Adam Powell637d3372010-08-25 14:37:03 -07003499 // Fling if we have enough velocity and we aren't at a boundary.
3500 // Since we can potentially overfling more than we can overscroll, don't
3501 // allow the weird behavior where you can scroll to a boundary then
3502 // fling further.
3503 if (Math.abs(initialVelocity) > mMinimumVelocity &&
3504 !((mFirstPosition == 0 &&
3505 firstChildTop == contentTop - mOverscrollDistance) ||
3506 (mFirstPosition + childCount == mItemCount &&
3507 lastChildBottom == contentBottom + mOverscrollDistance))) {
Romain Guy6198ae82009-08-31 17:45:55 -07003508 if (mFlingRunnable == null) {
3509 mFlingRunnable = new FlingRunnable();
3510 }
3511 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003512
Romain Guy6198ae82009-08-31 17:45:55 -07003513 mFlingRunnable.start(-initialVelocity);
Romain Guyf3c7d422009-12-04 15:38:22 -08003514 } else {
3515 mTouchMode = TOUCH_MODE_REST;
3516 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Gilles Debunned348bb42010-11-15 12:19:35 -08003517 if (mFlingRunnable != null) {
3518 mFlingRunnable.endFling();
3519 }
Adam Powell40322522011-01-12 21:58:20 -08003520 if (mPositionScroller != null) {
3521 mPositionScroller.stop();
3522 }
Romain Guy6198ae82009-08-31 17:45:55 -07003523 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003524 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003525 } else {
3526 mTouchMode = TOUCH_MODE_REST;
3527 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3528 }
Adam Powell79ac3392010-01-28 21:22:20 -08003529 break;
Adam Powell637d3372010-08-25 14:37:03 -07003530
3531 case TOUCH_MODE_OVERSCROLL:
3532 if (mFlingRunnable == null) {
3533 mFlingRunnable = new FlingRunnable();
3534 }
3535 final VelocityTracker velocityTracker = mVelocityTracker;
3536 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3537 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3538
3539 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3540 if (Math.abs(initialVelocity) > mMinimumVelocity) {
3541 mFlingRunnable.startOverfling(-initialVelocity);
3542 } else {
3543 mFlingRunnable.startSpringback();
3544 }
3545
3546 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003547 }
3548
3549 setPressed(false);
Romain Guy0a637162009-05-29 14:43:54 -07003550
Adam Powell637d3372010-08-25 14:37:03 -07003551 if (mEdgeGlowTop != null) {
3552 mEdgeGlowTop.onRelease();
3553 mEdgeGlowBottom.onRelease();
3554 }
3555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003556 // Need to redraw since we probably aren't drawing the selector anymore
3557 invalidate();
Romain Guy0a637162009-05-29 14:43:54 -07003558
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003559 final Handler handler = getHandler();
3560 if (handler != null) {
3561 handler.removeCallbacks(mPendingCheckForLongPress);
3562 }
3563
Michael Jurka13451a42011-08-22 15:54:21 -07003564 recycleVelocityTracker();
Mindy Pereira4e30d892010-11-24 15:32:39 -08003565
Adam Powell4cd47702010-02-25 11:21:14 -08003566 mActivePointerId = INVALID_POINTER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003567
3568 if (PROFILE_SCROLLING) {
3569 if (mScrollProfilingStarted) {
3570 Debug.stopMethodTracing();
3571 mScrollProfilingStarted = false;
3572 }
3573 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003574
3575 if (mScrollStrictSpan != null) {
3576 mScrollStrictSpan.finish();
3577 mScrollStrictSpan = null;
3578 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003579 break;
3580 }
3581
3582 case MotionEvent.ACTION_CANCEL: {
Adam Powell637d3372010-08-25 14:37:03 -07003583 switch (mTouchMode) {
3584 case TOUCH_MODE_OVERSCROLL:
3585 if (mFlingRunnable == null) {
3586 mFlingRunnable = new FlingRunnable();
3587 }
3588 mFlingRunnable.startSpringback();
3589 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003590
Adam Powell637d3372010-08-25 14:37:03 -07003591 case TOUCH_MODE_OVERFLING:
3592 // Do nothing - let it play out.
3593 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003594
Adam Powell637d3372010-08-25 14:37:03 -07003595 default:
3596 mTouchMode = TOUCH_MODE_REST;
3597 setPressed(false);
3598 View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
3599 if (motionView != null) {
3600 motionView.setPressed(false);
3601 }
3602 clearScrollingCache();
3603
3604 final Handler handler = getHandler();
3605 if (handler != null) {
3606 handler.removeCallbacks(mPendingCheckForLongPress);
3607 }
3608
Michael Jurka13451a42011-08-22 15:54:21 -07003609 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003610 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003611
Adam Powell637d3372010-08-25 14:37:03 -07003612 if (mEdgeGlowTop != null) {
3613 mEdgeGlowTop.onRelease();
3614 mEdgeGlowBottom.onRelease();
3615 }
Adam Powell4cd47702010-02-25 11:21:14 -08003616 mActivePointerId = INVALID_POINTER;
3617 break;
3618 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003619
Adam Powell4cd47702010-02-25 11:21:14 -08003620 case MotionEvent.ACTION_POINTER_UP: {
3621 onSecondaryPointerUp(ev);
3622 final int x = mMotionX;
3623 final int y = mMotionY;
3624 final int motionPosition = pointToPosition(x, y);
3625 if (motionPosition >= 0) {
3626 // Remember where the motion event started
3627 v = getChildAt(motionPosition - mFirstPosition);
3628 mMotionViewOriginalTop = v.getTop();
3629 mMotionPosition = motionPosition;
3630 }
3631 mLastY = y;
3632 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003633 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003634
3635 case MotionEvent.ACTION_POINTER_DOWN: {
3636 // New pointers take over dragging duties
3637 final int index = ev.getActionIndex();
3638 final int id = ev.getPointerId(index);
3639 final int x = (int) ev.getX(index);
3640 final int y = (int) ev.getY(index);
3641 mMotionCorrection = 0;
3642 mActivePointerId = id;
3643 mMotionX = x;
3644 mMotionY = y;
3645 final int motionPosition = pointToPosition(x, y);
3646 if (motionPosition >= 0) {
3647 // Remember where the motion event started
3648 v = getChildAt(motionPosition - mFirstPosition);
3649 mMotionViewOriginalTop = v.getTop();
3650 mMotionPosition = motionPosition;
3651 }
3652 mLastY = y;
3653 break;
3654 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003655 }
3656
3657 return true;
3658 }
Romain Guy0a637162009-05-29 14:43:54 -07003659
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003660 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003661 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3662 if (mScrollY != scrollY) {
3663 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3664 mScrollY = scrollY;
3665 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07003666
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003667 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07003668 }
Adam Powell637d3372010-08-25 14:37:03 -07003669 }
3670
3671 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08003672 public boolean onGenericMotionEvent(MotionEvent event) {
3673 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3674 switch (event.getAction()) {
3675 case MotionEvent.ACTION_SCROLL: {
3676 if (mTouchMode == TOUCH_MODE_REST) {
3677 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3678 if (vscroll != 0) {
3679 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08003680 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08003681 return true;
3682 }
3683 }
3684 }
3685 }
3686 }
3687 }
3688 return super.onGenericMotionEvent(event);
3689 }
3690
3691 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003692 public void draw(Canvas canvas) {
3693 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07003694 if (mEdgeGlowTop != null) {
3695 final int scrollY = mScrollY;
3696 if (!mEdgeGlowTop.isFinished()) {
3697 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003698 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3699 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3700 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003701
Romain Guy9d849a22012-03-14 16:41:42 -07003702 int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3703 canvas.translate(leftPadding, edgeY);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003704 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07003705 if (mEdgeGlowTop.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003706 mEdgeGlowTop.setPosition(leftPadding, edgeY);
Romain Guya8bfeaf2012-03-15 13:14:14 -07003707 invalidate(mEdgeGlowTop.getBounds(false));
Adam Powell637d3372010-08-25 14:37:03 -07003708 }
3709 canvas.restoreToCount(restoreCount);
3710 }
3711 if (!mEdgeGlowBottom.isFinished()) {
3712 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003713 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3714 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3715 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003716 final int height = getHeight();
3717
Romain Guy9d849a22012-03-14 16:41:42 -07003718 int edgeX = -width + leftPadding;
3719 int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3720 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08003721 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003722 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07003723 if (mEdgeGlowBottom.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003724 // Account for the rotation
Romain Guya8bfeaf2012-03-15 13:14:14 -07003725 mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
3726 invalidate(mEdgeGlowBottom.getBounds(true));
Adam Powell637d3372010-08-25 14:37:03 -07003727 }
3728 canvas.restoreToCount(restoreCount);
3729 }
3730 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003731 if (mFastScroller != null) {
Adam Powell637d3372010-08-25 14:37:03 -07003732 final int scrollY = mScrollY;
3733 if (scrollY != 0) {
3734 // Pin to the top/bottom during overscroll
3735 int restoreCount = canvas.save();
3736 canvas.translate(0, (float) scrollY);
3737 mFastScroller.draw(canvas);
3738 canvas.restoreToCount(restoreCount);
3739 } else {
3740 mFastScroller.draw(canvas);
3741 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003742 }
3743 }
3744
Adam Powell07d6f7b2011-03-02 14:27:30 -08003745 /**
3746 * @hide
3747 */
3748 public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3749 mGlowPaddingLeft = leftPadding;
3750 mGlowPaddingRight = rightPadding;
3751 }
3752
Michael Jurka13451a42011-08-22 15:54:21 -07003753 private void initOrResetVelocityTracker() {
3754 if (mVelocityTracker == null) {
3755 mVelocityTracker = VelocityTracker.obtain();
3756 } else {
3757 mVelocityTracker.clear();
3758 }
3759 }
3760
3761 private void initVelocityTrackerIfNotExists() {
3762 if (mVelocityTracker == null) {
3763 mVelocityTracker = VelocityTracker.obtain();
3764 }
3765 }
3766
3767 private void recycleVelocityTracker() {
3768 if (mVelocityTracker != null) {
3769 mVelocityTracker.recycle();
3770 mVelocityTracker = null;
3771 }
3772 }
3773
3774 @Override
3775 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3776 if (disallowIntercept) {
3777 recycleVelocityTracker();
3778 }
3779 super.requestDisallowInterceptTouchEvent(disallowIntercept);
3780 }
3781
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003782 @Override
3783 public boolean onInterceptTouchEvent(MotionEvent ev) {
3784 int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003785 View v;
Romain Guy0a637162009-05-29 14:43:54 -07003786
Adam Powell1fa179ef2012-04-12 15:01:40 -07003787 if (mPositionScroller != null) {
3788 mPositionScroller.stop();
3789 }
3790
Adam Powell28048d02012-06-06 22:46:42 -07003791 if (!mIsAttached) {
3792 // Something isn't right.
3793 // Since we rely on being attached to get data set change notifications,
3794 // don't risk doing anything where we might try to resync and find things
3795 // in a bogus state.
3796 return false;
3797 }
3798
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003799 if (mFastScroller != null) {
3800 boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
3801 if (intercepted) {
3802 return true;
3803 }
3804 }
Romain Guy0a637162009-05-29 14:43:54 -07003805
Adam Powell4cd47702010-02-25 11:21:14 -08003806 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003807 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08003808 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07003809 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3810 mMotionCorrection = 0;
3811 return true;
3812 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003813
Adam Powell4cd47702010-02-25 11:21:14 -08003814 final int x = (int) ev.getX();
3815 final int y = (int) ev.getY();
3816 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003817
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003818 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08003819 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003820 // User clicked on an actual view (and was not stopping a fling).
3821 // Remember where the motion event started
3822 v = getChildAt(motionPosition - mFirstPosition);
3823 mMotionViewOriginalTop = v.getTop();
3824 mMotionX = x;
3825 mMotionY = y;
3826 mMotionPosition = motionPosition;
3827 mTouchMode = TOUCH_MODE_DOWN;
3828 clearScrollingCache();
3829 }
3830 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07003831 initOrResetVelocityTracker();
3832 mVelocityTracker.addMovement(ev);
Adam Powell79ac3392010-01-28 21:22:20 -08003833 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08003834 return true;
3835 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003836 break;
3837 }
3838
3839 case MotionEvent.ACTION_MOVE: {
3840 switch (mTouchMode) {
3841 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07003842 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3843 if (pointerIndex == -1) {
3844 pointerIndex = 0;
3845 mActivePointerId = ev.getPointerId(pointerIndex);
3846 }
Adam Powell4cd47702010-02-25 11:21:14 -08003847 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07003848 initVelocityTrackerIfNotExists();
3849 mVelocityTracker.addMovement(ev);
Jeff Brown78f6e632011-09-09 17:15:31 -07003850 if (startScrollIfNeeded(y)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003851 return true;
3852 }
3853 break;
3854 }
3855 break;
3856 }
3857
Michael Jurka13451a42011-08-22 15:54:21 -07003858 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003859 case MotionEvent.ACTION_UP: {
3860 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08003861 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07003862 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003863 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3864 break;
3865 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003866
Adam Powell4cd47702010-02-25 11:21:14 -08003867 case MotionEvent.ACTION_POINTER_UP: {
3868 onSecondaryPointerUp(ev);
3869 break;
3870 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003871 }
3872
3873 return false;
3874 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003875
Adam Powell4cd47702010-02-25 11:21:14 -08003876 private void onSecondaryPointerUp(MotionEvent ev) {
3877 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3878 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3879 final int pointerId = ev.getPointerId(pointerIndex);
3880 if (pointerId == mActivePointerId) {
3881 // This was our active pointer going up. Choose a new
3882 // active pointer and adjust accordingly.
3883 // TODO: Make this decision more intelligent.
3884 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3885 mMotionX = (int) ev.getX(newPointerIndex);
3886 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07003887 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08003888 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08003889 }
3890 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003891
3892 /**
3893 * {@inheritDoc}
3894 */
3895 @Override
3896 public void addTouchables(ArrayList<View> views) {
3897 final int count = getChildCount();
3898 final int firstPosition = mFirstPosition;
3899 final ListAdapter adapter = mAdapter;
3900
3901 if (adapter == null) {
3902 return;
3903 }
3904
3905 for (int i = 0; i < count; i++) {
3906 final View child = getChildAt(i);
3907 if (adapter.isEnabled(firstPosition + i)) {
3908 views.add(child);
3909 }
3910 child.addTouchables(views);
3911 }
3912 }
3913
3914 /**
3915 * Fires an "on scroll state changed" event to the registered
3916 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
3917 * is fired only if the specified state is different from the previously known state.
3918 *
3919 * @param newState The new scroll state.
3920 */
3921 void reportScrollStateChange(int newState) {
3922 if (newState != mLastScrollState) {
3923 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003924 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08003925 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003926 }
3927 }
3928 }
3929
3930 /**
3931 * Responsible for fling behavior. Use {@link #start(int)} to
3932 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
3933 * A FlingRunnable will keep re-posting itself until the fling is done.
3934 *
3935 */
3936 private class FlingRunnable implements Runnable {
3937 /**
3938 * Tracks the decay of a fling scroll
3939 */
Adam Powell637d3372010-08-25 14:37:03 -07003940 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003941
3942 /**
3943 * Y value reported by mScroller on the previous fling
3944 */
3945 private int mLastFlingY;
3946
Gilles Debunned348bb42010-11-15 12:19:35 -08003947 private final Runnable mCheckFlywheel = new Runnable() {
3948 public void run() {
3949 final int activeId = mActivePointerId;
3950 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07003951 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08003952 if (vt == null || activeId == INVALID_POINTER) {
3953 return;
3954 }
3955
3956 vt.computeCurrentVelocity(1000, mMaximumVelocity);
3957 final float yvel = -vt.getYVelocity(activeId);
3958
Jeff Brownb0c71eb2011-09-16 21:40:49 -07003959 if (Math.abs(yvel) >= mMinimumVelocity
3960 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08003961 // Keep the fling alive a little longer
3962 postDelayed(this, FLYWHEEL_TIMEOUT);
3963 } else {
3964 endFling();
3965 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08003966 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08003967 }
3968 }
3969 };
3970
3971 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
3972
Adam Powell79ac3392010-01-28 21:22:20 -08003973 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07003974 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003975 }
3976
Adam Powell79ac3392010-01-28 21:22:20 -08003977 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003978 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
3979 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07003980 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003981 mScroller.fling(0, initialY, 0, initialVelocity,
3982 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
3983 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07003984 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003985
3986 if (PROFILE_FLINGING) {
3987 if (!mFlingProfilingStarted) {
3988 Debug.startMethodTracing("AbsListViewFling");
3989 mFlingProfilingStarted = true;
3990 }
3991 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003992
3993 if (mFlingStrictSpan == null) {
3994 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
3995 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003996 }
Adam Powell45803472010-01-25 15:10:44 -08003997
Adam Powell637d3372010-08-25 14:37:03 -07003998 void startSpringback() {
3999 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4000 mTouchMode = TOUCH_MODE_OVERFLING;
4001 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004002 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004003 } else {
4004 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004005 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004006 }
4007 }
4008
4009 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004010 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004011 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4012 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004013 mTouchMode = TOUCH_MODE_OVERFLING;
4014 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004015 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004016 }
4017
4018 void edgeReached(int delta) {
4019 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4020 final int overscrollMode = getOverScrollMode();
4021 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4022 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4023 mTouchMode = TOUCH_MODE_OVERFLING;
4024 final int vel = (int) mScroller.getCurrVelocity();
4025 if (delta > 0) {
4026 mEdgeGlowTop.onAbsorb(vel);
4027 } else {
4028 mEdgeGlowBottom.onAbsorb(vel);
4029 }
Adam Powell40322522011-01-12 21:58:20 -08004030 } else {
4031 mTouchMode = TOUCH_MODE_REST;
4032 if (mPositionScroller != null) {
4033 mPositionScroller.stop();
4034 }
Adam Powell637d3372010-08-25 14:37:03 -07004035 }
4036 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004037 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004038 }
4039
Adam Powell0b8acd82012-04-25 20:29:23 -07004040 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004041 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4042 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004043 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004044 mScroller.startScroll(0, initialY, 0, distance, duration);
4045 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004046 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004047 }
4048
Gilles Debunned348bb42010-11-15 12:19:35 -08004049 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004050 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004051
Adam Powell79ac3392010-01-28 21:22:20 -08004052 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004053 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004054
4055 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4056 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004057 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004058
4059 if (mFlingStrictSpan != null) {
4060 mFlingStrictSpan.finish();
4061 mFlingStrictSpan = null;
4062 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004063 }
4064
4065 void flywheelTouch() {
4066 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004067 }
4068
4069 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004070 switch (mTouchMode) {
4071 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004072 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004073 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004074
Gilles Debunned348bb42010-11-15 12:19:35 -08004075 case TOUCH_MODE_SCROLL:
4076 if (mScroller.isFinished()) {
4077 return;
4078 }
4079 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004080 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004081 if (mDataChanged) {
4082 layoutChildren();
4083 }
4084
Adam Powell79ac3392010-01-28 21:22:20 -08004085 if (mItemCount == 0 || getChildCount() == 0) {
4086 endFling();
4087 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004088 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004089
Adam Powell637d3372010-08-25 14:37:03 -07004090 final OverScroller scroller = mScroller;
4091 boolean more = scroller.computeScrollOffset();
4092 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004093
Adam Powell637d3372010-08-25 14:37:03 -07004094 // Flip sign to convert finger direction to list items direction
4095 // (e.g. finger moving down means list is moving towards the top)
4096 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004097
Adam Powell637d3372010-08-25 14:37:03 -07004098 // Pretend that each frame of a fling scroll is a touch scroll
4099 if (delta > 0) {
4100 // List is moving towards the top. Use first view as mMotionPosition
4101 mMotionPosition = mFirstPosition;
4102 final View firstView = getChildAt(0);
4103 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004104
Adam Powell637d3372010-08-25 14:37:03 -07004105 // Don't fling more than 1 screen
4106 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4107 } else {
4108 // List is moving towards the bottom. Use last view as mMotionPosition
4109 int offsetToLast = getChildCount() - 1;
4110 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004111
Adam Powell637d3372010-08-25 14:37:03 -07004112 final View lastView = getChildAt(offsetToLast);
4113 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004114
Adam Powell637d3372010-08-25 14:37:03 -07004115 // Don't fling more than 1 screen
4116 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4117 }
Adam Powell79ac3392010-01-28 21:22:20 -08004118
Adam Powell637d3372010-08-25 14:37:03 -07004119 // Check to see if we have bumped into the scroll limit
4120 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4121 int oldTop = 0;
4122 if (motionView != null) {
4123 oldTop = motionView.getTop();
4124 }
Adam Powell9d32d242010-03-29 16:02:07 -07004125
Adam Powell637d3372010-08-25 14:37:03 -07004126 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004127 final boolean atEdge = trackMotionScroll(delta, delta);
4128 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004129 if (atEnd) {
4130 if (motionView != null) {
4131 // Tweak the scroll for how far we overshot
4132 int overshoot = -(delta - (motionView.getTop() - oldTop));
4133 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4134 0, mOverflingDistance, false);
4135 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004136 if (more) {
4137 edgeReached(delta);
4138 }
Adam Powell637d3372010-08-25 14:37:03 -07004139 break;
4140 }
4141
4142 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004143 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004144 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004145 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004146 } else {
4147 endFling();
4148
4149 if (PROFILE_FLINGING) {
4150 if (mFlingProfilingStarted) {
4151 Debug.stopMethodTracing();
4152 mFlingProfilingStarted = false;
4153 }
4154
4155 if (mFlingStrictSpan != null) {
4156 mFlingStrictSpan.finish();
4157 mFlingStrictSpan = null;
4158 }
Adam Powell79ac3392010-01-28 21:22:20 -08004159 }
4160 }
Adam Powell637d3372010-08-25 14:37:03 -07004161 break;
4162 }
4163
4164 case TOUCH_MODE_OVERFLING: {
4165 final OverScroller scroller = mScroller;
4166 if (scroller.computeScrollOffset()) {
4167 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004168 final int currY = scroller.getCurrY();
4169 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004170 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4171 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004172 final boolean crossDown = scrollY <= 0 && currY > 0;
4173 final boolean crossUp = scrollY >= 0 && currY < 0;
4174 if (crossDown || crossUp) {
4175 int velocity = (int) scroller.getCurrVelocity();
4176 if (crossUp) velocity = -velocity;
4177
4178 // Don't flywheel from this; we're just continuing things.
4179 scroller.abortAnimation();
4180 start(velocity);
4181 } else {
4182 startSpringback();
4183 }
Adam Powell637d3372010-08-25 14:37:03 -07004184 } else {
4185 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004186 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004187 }
4188 } else {
4189 endFling();
4190 }
4191 break;
4192 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004193 }
4194 }
4195 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004196
Adam Powell45803472010-01-25 15:10:44 -08004197 class PositionScroller implements Runnable {
Adam Powelle69370e2012-05-07 15:17:20 -07004198 private static final int SCROLL_DURATION = 200;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004199
Adam Powell45803472010-01-25 15:10:44 -08004200 private static final int MOVE_DOWN_POS = 1;
4201 private static final int MOVE_UP_POS = 2;
4202 private static final int MOVE_DOWN_BOUND = 3;
4203 private static final int MOVE_UP_BOUND = 4;
Adam Powelle44afae2010-07-01 10:10:35 -07004204 private static final int MOVE_OFFSET = 5;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004205
Adam Powell45803472010-01-25 15:10:44 -08004206 private int mMode;
4207 private int mTargetPos;
4208 private int mBoundPos;
4209 private int mLastSeenPos;
4210 private int mScrollDuration;
Gilles Debunne52964242010-02-24 11:05:19 -08004211 private final int mExtraScroll;
Adam Powelle44afae2010-07-01 10:10:35 -07004212
4213 private int mOffsetFromTop;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004214
Adam Powell45803472010-01-25 15:10:44 -08004215 PositionScroller() {
4216 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
4217 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004218
Adam Powelle69370e2012-05-07 15:17:20 -07004219 void start(final int position) {
Adam Powell40322522011-01-12 21:58:20 -08004220 stop();
4221
Adam Powellaadf4fb2012-05-08 15:42:13 -07004222 if (mDataChanged) {
4223 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004224 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004225 @Override public void run() {
4226 start(position);
4227 }
Adam Powell161abf32012-05-23 17:22:49 -07004228 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004229 return;
4230 }
4231
Adam Powelle69370e2012-05-07 15:17:20 -07004232 final int childCount = getChildCount();
4233 if (childCount == 0) {
4234 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004235 return;
4236 }
4237
Adam Powell45803472010-01-25 15:10:44 -08004238 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004239 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004240
Romain Guy4bede9e2010-10-11 19:36:59 -07004241 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004242 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4243 if (clampedPosition < firstPos) {
4244 viewTravelCount = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004245 mMode = MOVE_UP_POS;
Chet Haase0061e162012-06-08 15:01:56 -07004246 } else if (clampedPosition > lastPos) {
4247 viewTravelCount = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004248 mMode = MOVE_DOWN_POS;
4249 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004250 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004251 return;
4252 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004253
Adam Powell45803472010-01-25 15:10:44 -08004254 if (viewTravelCount > 0) {
4255 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4256 } else {
4257 mScrollDuration = SCROLL_DURATION;
4258 }
Chet Haase0061e162012-06-08 15:01:56 -07004259 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004260 mBoundPos = INVALID_POSITION;
4261 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004262
Adam Powell1fa179ef2012-04-12 15:01:40 -07004263 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004264 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004265
Adam Powelle69370e2012-05-07 15:17:20 -07004266 void start(final int position, final int boundPosition) {
Adam Powell40322522011-01-12 21:58:20 -08004267 stop();
4268
Adam Powell45803472010-01-25 15:10:44 -08004269 if (boundPosition == INVALID_POSITION) {
4270 start(position);
4271 return;
4272 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004273
Adam Powellaadf4fb2012-05-08 15:42:13 -07004274 if (mDataChanged) {
4275 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004276 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004277 @Override public void run() {
4278 start(position, boundPosition);
4279 }
Adam Powell161abf32012-05-23 17:22:49 -07004280 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004281 return;
4282 }
4283
Adam Powelle69370e2012-05-07 15:17:20 -07004284 final int childCount = getChildCount();
4285 if (childCount == 0) {
4286 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004287 return;
4288 }
4289
Adam Powell45803472010-01-25 15:10:44 -08004290 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004291 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004292
Romain Guy4bede9e2010-10-11 19:36:59 -07004293 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004294 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4295 if (clampedPosition < firstPos) {
Adam Powell45803472010-01-25 15:10:44 -08004296 final int boundPosFromLast = lastPos - boundPosition;
4297 if (boundPosFromLast < 1) {
4298 // Moving would shift our bound position off the screen. Abort.
4299 return;
4300 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004301
Chet Haase0061e162012-06-08 15:01:56 -07004302 final int posTravel = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004303 final int boundTravel = boundPosFromLast - 1;
4304 if (boundTravel < posTravel) {
4305 viewTravelCount = boundTravel;
4306 mMode = MOVE_UP_BOUND;
4307 } else {
4308 viewTravelCount = posTravel;
4309 mMode = MOVE_UP_POS;
4310 }
Chet Haase0061e162012-06-08 15:01:56 -07004311 } else if (clampedPosition > lastPos) {
Adam Powell45803472010-01-25 15:10:44 -08004312 final int boundPosFromFirst = boundPosition - firstPos;
4313 if (boundPosFromFirst < 1) {
4314 // Moving would shift our bound position off the screen. Abort.
4315 return;
4316 }
4317
Chet Haase0061e162012-06-08 15:01:56 -07004318 final int posTravel = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004319 final int boundTravel = boundPosFromFirst - 1;
4320 if (boundTravel < posTravel) {
4321 viewTravelCount = boundTravel;
4322 mMode = MOVE_DOWN_BOUND;
4323 } else {
4324 viewTravelCount = posTravel;
4325 mMode = MOVE_DOWN_POS;
4326 }
4327 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004328 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004329 return;
4330 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004331
Adam Powell45803472010-01-25 15:10:44 -08004332 if (viewTravelCount > 0) {
4333 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4334 } else {
4335 mScrollDuration = SCROLL_DURATION;
4336 }
Chet Haase0061e162012-06-08 15:01:56 -07004337 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004338 mBoundPos = boundPosition;
4339 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004340
Adam Powell1fa179ef2012-04-12 15:01:40 -07004341 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004342 }
Adam Powelle44afae2010-07-01 10:10:35 -07004343
4344 void startWithOffset(int position, int offset) {
Erik322171b2010-10-13 15:46:00 -07004345 startWithOffset(position, offset, SCROLL_DURATION);
4346 }
4347
Adam Powellaadf4fb2012-05-08 15:42:13 -07004348 void startWithOffset(final int position, int offset, final int duration) {
Adam Powell40322522011-01-12 21:58:20 -08004349 stop();
4350
Adam Powellaadf4fb2012-05-08 15:42:13 -07004351 if (mDataChanged) {
4352 // Wait until we're back in a stable state to try this.
4353 final int postOffset = offset;
Adam Powell161abf32012-05-23 17:22:49 -07004354 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004355 @Override public void run() {
4356 startWithOffset(position, postOffset, duration);
4357 }
Adam Powell161abf32012-05-23 17:22:49 -07004358 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004359 return;
4360 }
4361
4362 final int childCount = getChildCount();
4363 if (childCount == 0) {
4364 // Can't scroll without children.
4365 return;
4366 }
4367
Adam Powell1fa179ef2012-04-12 15:01:40 -07004368 offset += getPaddingTop();
4369
Chet Haase0061e162012-06-08 15:01:56 -07004370 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
Adam Powelle44afae2010-07-01 10:10:35 -07004371 mOffsetFromTop = offset;
4372 mBoundPos = INVALID_POSITION;
4373 mLastSeenPos = INVALID_POSITION;
4374 mMode = MOVE_OFFSET;
4375
4376 final int firstPos = mFirstPosition;
Adam Powell37113312010-07-08 18:21:48 -07004377 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004378
Romain Guy4bede9e2010-10-11 19:36:59 -07004379 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004380 if (mTargetPos < firstPos) {
4381 viewTravelCount = firstPos - mTargetPos;
4382 } else if (mTargetPos > lastPos) {
4383 viewTravelCount = mTargetPos - lastPos;
Adam Powelle44afae2010-07-01 10:10:35 -07004384 } else {
4385 // On-screen, just scroll.
Chet Haase0061e162012-06-08 15:01:56 -07004386 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Adam Powell0b8acd82012-04-25 20:29:23 -07004387 smoothScrollBy(targetTop - offset, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004388 return;
4389 }
4390
Adam Powell37113312010-07-08 18:21:48 -07004391 // Estimate how many screens we should travel
Daniel Lehmann4ef1da32010-08-27 16:40:59 -07004392 final float screenTravelCount = (float) viewTravelCount / childCount;
Adam Powell0b8acd82012-04-25 20:29:23 -07004393 mScrollDuration = screenTravelCount < 1 ?
4394 duration : (int) (duration / screenTravelCount);
Adam Powell37113312010-07-08 18:21:48 -07004395 mLastSeenPos = INVALID_POSITION;
Adam Powell234a5712010-09-14 10:34:56 -07004396
Adam Powell1fa179ef2012-04-12 15:01:40 -07004397 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004398 }
4399
Adam Powelle69370e2012-05-07 15:17:20 -07004400 /**
4401 * Scroll such that targetPos is in the visible padded region without scrolling
4402 * boundPos out of view. Assumes targetPos is onscreen.
4403 */
4404 void scrollToVisible(int targetPos, int boundPos, int duration) {
4405 final int firstPos = mFirstPosition;
4406 final int childCount = getChildCount();
4407 final int lastPos = firstPos + childCount - 1;
4408 final int paddedTop = mListPadding.top;
4409 final int paddedBottom = getHeight() - mListPadding.bottom;
4410
4411 if (targetPos < firstPos || targetPos > lastPos) {
4412 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
4413 " not visible [" + firstPos + ", " + lastPos + "]");
4414 }
4415 if (boundPos < firstPos || boundPos > lastPos) {
4416 // boundPos doesn't matter, it's already offscreen.
4417 boundPos = INVALID_POSITION;
4418 }
4419
4420 final View targetChild = getChildAt(targetPos - firstPos);
4421 final int targetTop = targetChild.getTop();
4422 final int targetBottom = targetChild.getBottom();
4423 int scrollBy = 0;
4424
4425 if (targetBottom > paddedBottom) {
4426 scrollBy = targetBottom - paddedBottom;
4427 }
4428 if (targetTop < paddedTop) {
4429 scrollBy = targetTop - paddedTop;
4430 }
4431
4432 if (scrollBy == 0) {
4433 return;
4434 }
4435
4436 if (boundPos >= 0) {
4437 final View boundChild = getChildAt(boundPos - firstPos);
4438 final int boundTop = boundChild.getTop();
4439 final int boundBottom = boundChild.getBottom();
4440 final int absScroll = Math.abs(scrollBy);
4441
4442 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
4443 // Don't scroll the bound view off the bottom of the screen.
4444 scrollBy = Math.max(0, boundBottom - paddedBottom);
4445 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
4446 // Don't scroll the bound view off the top of the screen.
4447 scrollBy = Math.min(0, boundTop - paddedTop);
4448 }
4449 }
4450
4451 smoothScrollBy(scrollBy, duration);
4452 }
4453
Adam Powell45803472010-01-25 15:10:44 -08004454 void stop() {
4455 removeCallbacks(this);
4456 }
Adam Powelle44afae2010-07-01 10:10:35 -07004457
Adam Powell45803472010-01-25 15:10:44 -08004458 public void run() {
4459 final int listHeight = getHeight();
4460 final int firstPos = mFirstPosition;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004461
Adam Powell45803472010-01-25 15:10:44 -08004462 switch (mMode) {
4463 case MOVE_DOWN_POS: {
4464 final int lastViewIndex = getChildCount() - 1;
4465 final int lastPos = firstPos + lastViewIndex;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004466
Adam Powell0b8bb422010-02-08 14:30:45 -08004467 if (lastViewIndex < 0) {
4468 return;
4469 }
Adam Powell45803472010-01-25 15:10:44 -08004470
4471 if (lastPos == mLastSeenPos) {
4472 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004473 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004474 return;
4475 }
4476
4477 final View lastView = getChildAt(lastViewIndex);
4478 final int lastViewHeight = lastView.getHeight();
4479 final int lastViewTop = lastView.getTop();
4480 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004481 final int extraScroll = lastPos < mItemCount - 1 ?
4482 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
Adam Powell45803472010-01-25 15:10:44 -08004483
Adam Powell1fa179ef2012-04-12 15:01:40 -07004484 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Adam Powell0b8acd82012-04-25 20:29:23 -07004485 smoothScrollBy(scrollBy, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004486
4487 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004488 if (lastPos < mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004489 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004490 }
4491 break;
4492 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004493
Adam Powell45803472010-01-25 15:10:44 -08004494 case MOVE_DOWN_BOUND: {
4495 final int nextViewIndex = 1;
Adam Powell029cfbd2010-03-08 19:03:54 -08004496 final int childCount = getChildCount();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004497
Adam Powell029cfbd2010-03-08 19:03:54 -08004498 if (firstPos == mBoundPos || childCount <= nextViewIndex
4499 || firstPos + childCount >= mItemCount) {
Adam Powell45803472010-01-25 15:10:44 -08004500 return;
4501 }
4502 final int nextPos = firstPos + nextViewIndex;
4503
4504 if (nextPos == mLastSeenPos) {
4505 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004506 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004507 return;
4508 }
4509
4510 final View nextView = getChildAt(nextViewIndex);
4511 final int nextViewHeight = nextView.getHeight();
4512 final int nextViewTop = nextView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004513 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
Adam Powell7e5e3742010-05-24 15:13:41 -07004514 if (nextPos < mBoundPos) {
Adam Powell45803472010-01-25 15:10:44 -08004515 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Adam Powell0b8acd82012-04-25 20:29:23 -07004516 mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004517
4518 mLastSeenPos = nextPos;
4519
Adam Powell1fa179ef2012-04-12 15:01:40 -07004520 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004521 } else {
Mindy Pereira4e30d892010-11-24 15:32:39 -08004522 if (nextViewTop > extraScroll) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004523 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004524 }
4525 }
4526 break;
4527 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004528
Adam Powell45803472010-01-25 15:10:44 -08004529 case MOVE_UP_POS: {
4530 if (firstPos == mLastSeenPos) {
4531 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004532 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004533 return;
4534 }
4535
4536 final View firstView = getChildAt(0);
Adam Powell0b8bb422010-02-08 14:30:45 -08004537 if (firstView == null) {
4538 return;
4539 }
Adam Powell45803472010-01-25 15:10:44 -08004540 final int firstViewTop = firstView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004541 final int extraScroll = firstPos > 0 ?
4542 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
Adam Powell45803472010-01-25 15:10:44 -08004543
Adam Powell0b8acd82012-04-25 20:29:23 -07004544 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004545
4546 mLastSeenPos = firstPos;
4547
Adam Powell7e5e3742010-05-24 15:13:41 -07004548 if (firstPos > mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004549 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004550 }
4551 break;
4552 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004553
Adam Powell45803472010-01-25 15:10:44 -08004554 case MOVE_UP_BOUND: {
4555 final int lastViewIndex = getChildCount() - 2;
4556 if (lastViewIndex < 0) {
4557 return;
4558 }
4559 final int lastPos = firstPos + lastViewIndex;
4560
4561 if (lastPos == mLastSeenPos) {
4562 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004563 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004564 return;
4565 }
4566
4567 final View lastView = getChildAt(lastViewIndex);
4568 final int lastViewHeight = lastView.getHeight();
4569 final int lastViewTop = lastView.getTop();
4570 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004571 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
Adam Powell45803472010-01-25 15:10:44 -08004572 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004573 if (lastPos > mBoundPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004574 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004575 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004576 } else {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004577 final int bottom = listHeight - extraScroll;
Adam Powell45803472010-01-25 15:10:44 -08004578 final int lastViewBottom = lastViewTop + lastViewHeight;
4579 if (bottom > lastViewBottom) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004580 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004581 }
4582 }
4583 break;
4584 }
4585
Adam Powelle44afae2010-07-01 10:10:35 -07004586 case MOVE_OFFSET: {
Adam Powell234a5712010-09-14 10:34:56 -07004587 if (mLastSeenPos == firstPos) {
4588 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004589 postOnAnimation(this);
Adam Powell234a5712010-09-14 10:34:56 -07004590 return;
4591 }
Adam Powelle44afae2010-07-01 10:10:35 -07004592
Adam Powell37113312010-07-08 18:21:48 -07004593 mLastSeenPos = firstPos;
Adam Powell234a5712010-09-14 10:34:56 -07004594
4595 final int childCount = getChildCount();
Adam Powelle44afae2010-07-01 10:10:35 -07004596 final int position = mTargetPos;
Adam Powell37113312010-07-08 18:21:48 -07004597 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004598
Adam Powell40322522011-01-12 21:58:20 -08004599 int viewTravelCount = 0;
Adam Powelle44afae2010-07-01 10:10:35 -07004600 if (position < firstPos) {
Adam Powell40322522011-01-12 21:58:20 -08004601 viewTravelCount = firstPos - position + 1;
4602 } else if (position > lastPos) {
4603 viewTravelCount = position - lastPos;
4604 }
4605
4606 // Estimate how many screens we should travel
4607 final float screenTravelCount = (float) viewTravelCount / childCount;
4608
4609 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4610 if (position < firstPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004611 final int distance = (int) (-getHeight() * modifier);
4612 final int duration = (int) (mScrollDuration * modifier);
4613 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004614 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004615 } else if (position > lastPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004616 final int distance = (int) (getHeight() * modifier);
4617 final int duration = (int) (mScrollDuration * modifier);
4618 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004619 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004620 } else {
4621 // On-screen, just scroll.
4622 final int targetTop = getChildAt(position - firstPos).getTop();
Adam Powell234a5712010-09-14 10:34:56 -07004623 final int distance = targetTop - mOffsetFromTop;
Adam Powell0b8acd82012-04-25 20:29:23 -07004624 final int duration = (int) (mScrollDuration *
4625 ((float) Math.abs(distance) / getHeight()));
4626 smoothScrollBy(distance, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004627 }
4628 break;
4629 }
4630
Adam Powell45803472010-01-25 15:10:44 -08004631 default:
4632 break;
4633 }
4634 }
4635 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004636
Adam Powell45803472010-01-25 15:10:44 -08004637 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004638 * The amount of friction applied to flings. The default value
4639 * is {@link ViewConfiguration#getScrollFriction}.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004640 *
Romain Guy4bede9e2010-10-11 19:36:59 -07004641 * @return A scalar dimensionless value representing the coefficient of
4642 * friction.
4643 */
4644 public void setFriction(float friction) {
4645 if (mFlingRunnable == null) {
4646 mFlingRunnable = new FlingRunnable();
4647 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004648 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004649 }
Romain Guy21317d12010-10-12 13:32:31 -07004650
4651 /**
4652 * Sets a scale factor for the fling velocity. The initial scale
4653 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004654 *
Romain Guy21317d12010-10-12 13:32:31 -07004655 * @param scale The scale factor to multiply the velocity by.
4656 */
4657 public void setVelocityScale(float scale) {
4658 mVelocityScale = scale;
4659 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004660
Romain Guy4bede9e2010-10-11 19:36:59 -07004661 /**
Adam Powell45803472010-01-25 15:10:44 -08004662 * Smoothly scroll to the specified adapter position. The view will
4663 * scroll such that the indicated position is displayed.
4664 * @param position Scroll to this adapter position.
4665 */
4666 public void smoothScrollToPosition(int position) {
4667 if (mPositionScroller == null) {
4668 mPositionScroller = new PositionScroller();
4669 }
4670 mPositionScroller.start(position);
4671 }
Erik322171b2010-10-13 15:46:00 -07004672
4673 /**
4674 * Smoothly scroll to the specified adapter position. The view will scroll
4675 * such that the indicated position is displayed <code>offset</code> pixels from
4676 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4677 * the first or last item beyond the boundaries of the list) it will get as close
4678 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4679 *
4680 * @param position Position to scroll to
4681 * @param offset Desired distance in pixels of <code>position</code> from the top
4682 * of the view when scrolling is finished
4683 * @param duration Number of milliseconds to use for the scroll
4684 */
4685 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4686 if (mPositionScroller == null) {
4687 mPositionScroller = new PositionScroller();
4688 }
4689 mPositionScroller.startWithOffset(position, offset, duration);
4690 }
4691
Adam Powell45803472010-01-25 15:10:44 -08004692 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004693 * Smoothly scroll to the specified adapter position. The view will scroll
4694 * such that the indicated position is displayed <code>offset</code> pixels from
4695 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4696 * the first or last item beyond the boundaries of the list) it will get as close
4697 * as possible.
4698 *
4699 * @param position Position to scroll to
4700 * @param offset Desired distance in pixels of <code>position</code> from the top
4701 * of the view when scrolling is finished
4702 */
4703 public void smoothScrollToPositionFromTop(int position, int offset) {
4704 if (mPositionScroller == null) {
4705 mPositionScroller = new PositionScroller();
4706 }
4707 mPositionScroller.startWithOffset(position, offset);
4708 }
4709
4710 /**
Adam Powell45803472010-01-25 15:10:44 -08004711 * Smoothly scroll to the specified adapter position. The view will
4712 * scroll such that the indicated position is displayed, but it will
4713 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004714 * view.
Adam Powell45803472010-01-25 15:10:44 -08004715 * @param position Scroll to this adapter position.
4716 * @param boundPosition Do not scroll if it would move this adapter
4717 * position out of view.
4718 */
4719 public void smoothScrollToPosition(int position, int boundPosition) {
4720 if (mPositionScroller == null) {
4721 mPositionScroller = new PositionScroller();
4722 }
4723 mPositionScroller.start(position, boundPosition);
4724 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004725
Adam Powell45803472010-01-25 15:10:44 -08004726 /**
4727 * Smoothly scroll by distance pixels over duration milliseconds.
4728 * @param distance Distance to scroll in pixels.
4729 * @param duration Duration of the scroll animation in milliseconds.
4730 */
4731 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004732 smoothScrollBy(distance, duration, false);
4733 }
4734
4735 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004736 if (mFlingRunnable == null) {
4737 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004738 }
Adam Powell40322522011-01-12 21:58:20 -08004739
Marc Blank299acb52010-10-21 11:03:53 -07004740 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004741 final int firstPos = mFirstPosition;
4742 final int childCount = getChildCount();
4743 final int lastPos = firstPos + childCount;
4744 final int topLimit = getPaddingTop();
4745 final int bottomLimit = getHeight() - getPaddingBottom();
4746
Adam Powell79303752011-01-13 22:06:49 -08004747 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004748 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004749 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004750 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4751 mFlingRunnable.endFling();
4752 if (mPositionScroller != null) {
4753 mPositionScroller.stop();
4754 }
4755 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004756 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004757 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004758 }
Adam Powell45803472010-01-25 15:10:44 -08004759 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004760
Winson Chung499cb9f2010-07-16 11:18:17 -07004761 /**
4762 * Allows RemoteViews to scroll relatively to a position.
4763 */
4764 void smoothScrollByOffset(int position) {
4765 int index = -1;
4766 if (position < 0) {
4767 index = getFirstVisiblePosition();
4768 } else if (position > 0) {
4769 index = getLastVisiblePosition();
4770 }
4771
4772 if (index > -1) {
4773 View child = getChildAt(index - getFirstVisiblePosition());
4774 if (child != null) {
4775 Rect visibleRect = new Rect();
4776 if (child.getGlobalVisibleRect(visibleRect)) {
4777 // the child is partially visible
4778 int childRectArea = child.getWidth() * child.getHeight();
4779 int visibleRectArea = visibleRect.width() * visibleRect.height();
4780 float visibleArea = (visibleRectArea / (float) childRectArea);
4781 final float visibleThreshold = 0.75f;
4782 if ((position < 0) && (visibleArea < visibleThreshold)) {
4783 // the top index is not perceivably visible so offset
4784 // to account for showing that top index as well
4785 ++index;
4786 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4787 // the bottom index is not perceivably visible so offset
4788 // to account for showing that bottom index as well
4789 --index;
4790 }
4791 }
4792 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4793 }
4794 }
4795 }
4796
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004797 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004798 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004799 setChildrenDrawnWithCacheEnabled(true);
4800 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004801 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004802 }
4803 }
4804
4805 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004806 if (!isHardwareAccelerated()) {
4807 if (mClearScrollingCache == null) {
4808 mClearScrollingCache = new Runnable() {
4809 public void run() {
4810 if (mCachingStarted) {
4811 mCachingStarted = mCachingActive = false;
4812 setChildrenDrawnWithCacheEnabled(false);
4813 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4814 setChildrenDrawingCacheEnabled(false);
4815 }
4816 if (!isAlwaysDrawnWithCacheEnabled()) {
4817 invalidate();
4818 }
Romain Guy6dfed242009-05-11 18:25:05 -07004819 }
4820 }
Romain Guy9d849a22012-03-14 16:41:42 -07004821 };
4822 }
4823 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004824 }
4825 }
4826
4827 /**
4828 * Track a motion scroll
4829 *
4830 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4831 * began. Positive numbers mean the user's finger is moving down the screen.
4832 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08004833 * @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 -08004834 */
Adam Powell45803472010-01-25 15:10:44 -08004835 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004836 final int childCount = getChildCount();
4837 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08004838 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004839 }
4840
4841 final int firstTop = getChildAt(0).getTop();
4842 final int lastBottom = getChildAt(childCount - 1).getBottom();
4843
4844 final Rect listPadding = mListPadding;
4845
Adam Powellbdccc2d2010-12-14 17:34:27 -08004846 // "effective padding" In this case is the amount of padding that affects
4847 // how much space should not be filled by items. If we don't clip to padding
4848 // there is no effective padding.
4849 int effectivePaddingTop = 0;
4850 int effectivePaddingBottom = 0;
4851 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4852 effectivePaddingTop = listPadding.top;
4853 effectivePaddingBottom = listPadding.bottom;
4854 }
4855
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004856 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08004857 final int spaceAbove = effectivePaddingTop - firstTop;
4858 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004859 final int spaceBelow = lastBottom - end;
4860
4861 final int height = getHeight() - mPaddingBottom - mPaddingTop;
4862 if (deltaY < 0) {
4863 deltaY = Math.max(-(height - 1), deltaY);
4864 } else {
4865 deltaY = Math.min(height - 1, deltaY);
4866 }
4867
4868 if (incrementalDeltaY < 0) {
4869 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4870 } else {
4871 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4872 }
4873
Adam Powell45803472010-01-25 15:10:44 -08004874 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004875
Adam Powell637d3372010-08-25 14:37:03 -07004876 // Update our guesses for where the first and last views are
4877 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004878 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07004879 } else {
4880 mFirstPositionDistanceGuess += incrementalDeltaY;
4881 }
4882 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004883 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07004884 } else {
4885 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004886 }
Adam Powell45803472010-01-25 15:10:44 -08004887
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004888 final boolean cannotScrollDown = (firstPosition == 0 &&
4889 firstTop >= listPadding.top && incrementalDeltaY >= 0);
4890 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4891 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07004892
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004893 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07004894 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08004895 }
4896
4897 final boolean down = incrementalDeltaY < 0;
4898
Adam Powell029cfbd2010-03-08 19:03:54 -08004899 final boolean inTouchMode = isInTouchMode();
4900 if (inTouchMode) {
4901 hideSelector();
4902 }
Adam Powell45803472010-01-25 15:10:44 -08004903
4904 final int headerViewsCount = getHeaderViewsCount();
4905 final int footerViewsStart = mItemCount - getFooterViewsCount();
4906
4907 int start = 0;
4908 int count = 0;
4909
4910 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004911 int top = -incrementalDeltaY;
4912 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4913 top += listPadding.top;
4914 }
Adam Powell45803472010-01-25 15:10:44 -08004915 for (int i = 0; i < childCount; i++) {
4916 final View child = getChildAt(i);
4917 if (child.getBottom() >= top) {
4918 break;
4919 } else {
4920 count++;
4921 int position = firstPosition + i;
4922 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004923 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004924 }
4925 }
4926 }
4927 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004928 int bottom = getHeight() - incrementalDeltaY;
4929 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4930 bottom -= listPadding.bottom;
4931 }
Adam Powell45803472010-01-25 15:10:44 -08004932 for (int i = childCount - 1; i >= 0; i--) {
4933 final View child = getChildAt(i);
4934 if (child.getTop() <= bottom) {
4935 break;
4936 } else {
4937 start = i;
4938 count++;
4939 int position = firstPosition + i;
4940 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004941 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004942 }
4943 }
4944 }
4945 }
4946
4947 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
4948
4949 mBlockLayoutRequests = true;
4950
4951 if (count > 0) {
4952 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08004953 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08004954 }
Adam Powell539ee872012-02-03 19:00:49 -08004955
Romain Guy9d849a22012-03-14 16:41:42 -07004956 // invalidate before moving the children to avoid unnecessary invalidate
4957 // calls to bubble up from the children all the way to the top
4958 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004959 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07004960 }
4961
Adam Powell45803472010-01-25 15:10:44 -08004962 offsetChildrenTopAndBottom(incrementalDeltaY);
4963
4964 if (down) {
4965 mFirstPosition += count;
4966 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004967
Adam Powell45803472010-01-25 15:10:44 -08004968 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
4969 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
4970 fillGap(down);
4971 }
4972
Adam Powell029cfbd2010-03-08 19:03:54 -08004973 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08004974 final int childIndex = mSelectedPosition - mFirstPosition;
4975 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004976 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08004977 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07004978 } else if (mSelectorPosition != INVALID_POSITION) {
4979 final int childIndex = mSelectorPosition - mFirstPosition;
4980 if (childIndex >= 0 && childIndex < getChildCount()) {
4981 positionSelector(INVALID_POSITION, getChildAt(childIndex));
4982 }
4983 } else {
4984 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08004985 }
4986
Adam Powell45803472010-01-25 15:10:44 -08004987 mBlockLayoutRequests = false;
4988
4989 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004990
Adam Powell45803472010-01-25 15:10:44 -08004991 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004992 }
4993
4994 /**
4995 * Returns the number of header views in the list. Header views are special views
4996 * at the top of the list that should not be recycled during a layout.
4997 *
4998 * @return The number of header views, 0 in the default implementation.
4999 */
5000 int getHeaderViewsCount() {
5001 return 0;
5002 }
5003
5004 /**
5005 * Returns the number of footer views in the list. Footer views are special views
5006 * at the bottom of the list that should not be recycled during a layout.
5007 *
5008 * @return The number of footer views, 0 in the default implementation.
5009 */
5010 int getFooterViewsCount() {
5011 return 0;
5012 }
5013
5014 /**
5015 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5016 * remain on screen are shifted and the other ones are discarded. The role of this
5017 * method is to fill the gap thus created by performing a partial layout in the
5018 * empty space.
5019 *
5020 * @param down true if the scroll is going down, false if it is going up
5021 */
5022 abstract void fillGap(boolean down);
5023
5024 void hideSelector() {
5025 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005026 if (mLayoutMode != LAYOUT_SPECIFIC) {
5027 mResurrectToPosition = mSelectedPosition;
5028 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005029 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5030 mResurrectToPosition = mNextSelectedPosition;
5031 }
5032 setSelectedPositionInt(INVALID_POSITION);
5033 setNextSelectedPositionInt(INVALID_POSITION);
5034 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005035 }
5036 }
5037
5038 /**
5039 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5040 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5041 * of items available in the adapter
5042 */
5043 int reconcileSelectedPosition() {
5044 int position = mSelectedPosition;
5045 if (position < 0) {
5046 position = mResurrectToPosition;
5047 }
5048 position = Math.max(0, position);
5049 position = Math.min(position, mItemCount - 1);
5050 return position;
5051 }
5052
5053 /**
5054 * Find the row closest to y. This row will be used as the motion row when scrolling
5055 *
5056 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005057 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005058 */
5059 abstract int findMotionRow(int y);
5060
5061 /**
Adam Powell637d3372010-08-25 14:37:03 -07005062 * Find the row closest to y. This row will be used as the motion row when scrolling.
5063 *
5064 * @param y Where the user touched
5065 * @return The position of the first (or only) item in the row closest to y
5066 */
5067 int findClosestMotionRow(int y) {
5068 final int childCount = getChildCount();
5069 if (childCount == 0) {
5070 return INVALID_POSITION;
5071 }
5072
5073 final int motionRow = findMotionRow(y);
5074 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5075 }
5076
5077 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005078 * Causes all the views to be rebuilt and redrawn.
5079 */
5080 public void invalidateViews() {
5081 mDataChanged = true;
5082 rememberSyncState();
5083 requestLayout();
5084 invalidate();
5085 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005086
5087 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005088 * If there is a selection returns false.
5089 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005090 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005091 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005092 if (mSelectedPosition < 0 && resurrectSelection()) {
5093 updateSelectorState();
5094 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005095 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005096 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005097 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005098
5099 /**
5100 * Makes the item at the supplied position selected.
5101 *
5102 * @param position the position of the new selection
5103 */
5104 abstract void setSelectionInt(int position);
5105
5106 /**
5107 * Attempt to bring the selection back if the user is switching from touch
5108 * to trackball mode
5109 * @return Whether selection was set to something.
5110 */
5111 boolean resurrectSelection() {
5112 final int childCount = getChildCount();
5113
5114 if (childCount <= 0) {
5115 return false;
5116 }
5117
5118 int selectedTop = 0;
5119 int selectedPos;
5120 int childrenTop = mListPadding.top;
5121 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5122 final int firstPosition = mFirstPosition;
5123 final int toPosition = mResurrectToPosition;
5124 boolean down = true;
5125
5126 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5127 selectedPos = toPosition;
5128
5129 final View selected = getChildAt(selectedPos - mFirstPosition);
5130 selectedTop = selected.getTop();
5131 int selectedBottom = selected.getBottom();
5132
5133 // We are scrolled, don't get in the fade
5134 if (selectedTop < childrenTop) {
5135 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5136 } else if (selectedBottom > childrenBottom) {
5137 selectedTop = childrenBottom - selected.getMeasuredHeight()
5138 - getVerticalFadingEdgeLength();
5139 }
5140 } else {
5141 if (toPosition < firstPosition) {
5142 // Default to selecting whatever is first
5143 selectedPos = firstPosition;
5144 for (int i = 0; i < childCount; i++) {
5145 final View v = getChildAt(i);
5146 final int top = v.getTop();
5147
5148 if (i == 0) {
5149 // Remember the position of the first item
5150 selectedTop = top;
5151 // See if we are scrolled at all
5152 if (firstPosition > 0 || top < childrenTop) {
5153 // If we are scrolled, don't select anything that is
5154 // in the fade region
5155 childrenTop += getVerticalFadingEdgeLength();
5156 }
5157 }
5158 if (top >= childrenTop) {
5159 // Found a view whose top is fully visisble
5160 selectedPos = firstPosition + i;
5161 selectedTop = top;
5162 break;
5163 }
5164 }
5165 } else {
5166 final int itemCount = mItemCount;
5167 down = false;
5168 selectedPos = firstPosition + childCount - 1;
5169
5170 for (int i = childCount - 1; i >= 0; i--) {
5171 final View v = getChildAt(i);
5172 final int top = v.getTop();
5173 final int bottom = v.getBottom();
5174
5175 if (i == childCount - 1) {
5176 selectedTop = top;
5177 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5178 childrenBottom -= getVerticalFadingEdgeLength();
5179 }
5180 }
5181
5182 if (bottom <= childrenBottom) {
5183 selectedPos = firstPosition + i;
5184 selectedTop = top;
5185 break;
5186 }
5187 }
5188 }
5189 }
5190
5191 mResurrectToPosition = INVALID_POSITION;
5192 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005193 if (mPositionScroller != null) {
5194 mPositionScroller.stop();
5195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005196 mTouchMode = TOUCH_MODE_REST;
5197 clearScrollingCache();
5198 mSpecificTop = selectedTop;
5199 selectedPos = lookForSelectablePosition(selectedPos, down);
5200 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5201 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005202 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005203 setSelectionInt(selectedPos);
5204 invokeOnItemScrollListener();
5205 } else {
5206 selectedPos = INVALID_POSITION;
5207 }
5208 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5209
5210 return selectedPos >= 0;
5211 }
5212
Adam Powell14c08042011-10-06 19:46:18 -07005213 void confirmCheckedPositionsById() {
5214 // Clear out the positional check states, we'll rebuild it below from IDs.
5215 mCheckStates.clear();
5216
5217 boolean checkedCountChanged = false;
5218 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5219 final long id = mCheckedIdStates.keyAt(checkedIndex);
5220 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5221
5222 final long lastPosId = mAdapter.getItemId(lastPos);
5223 if (id != lastPosId) {
5224 // Look around to see if the ID is nearby. If not, uncheck it.
5225 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5226 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5227 boolean found = false;
5228 for (int searchPos = start; searchPos < end; searchPos++) {
5229 final long searchId = mAdapter.getItemId(searchPos);
5230 if (id == searchId) {
5231 found = true;
5232 mCheckStates.put(searchPos, true);
5233 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5234 break;
5235 }
5236 }
5237
5238 if (!found) {
5239 mCheckedIdStates.delete(id);
5240 checkedIndex--;
5241 mCheckedItemCount--;
5242 checkedCountChanged = true;
5243 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5244 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5245 lastPos, id, false);
5246 }
5247 }
5248 } else {
5249 mCheckStates.put(lastPos, true);
5250 }
5251 }
5252
5253 if (checkedCountChanged && mChoiceActionMode != null) {
5254 mChoiceActionMode.invalidate();
5255 }
5256 }
5257
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005258 @Override
5259 protected void handleDataChanged() {
5260 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005261 int lastHandledItemCount = mLastHandledItemCount;
5262 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005263
5264 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5265 confirmCheckedPositionsById();
5266 }
5267
Adam Powell539ee872012-02-03 19:00:49 -08005268 // TODO: In the future we can recycle these views based on stable ID instead.
5269 mRecycler.clearTransientStateViews();
5270
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005271 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005272 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005273 int selectablePos;
5274
5275 // Find the row we are supposed to sync to
5276 if (mNeedSync) {
5277 // Update this first, since setNextSelectedPositionInt inspects it
5278 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005279 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005280
Adam Powell07852792010-11-10 16:57:05 -08005281 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005282 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5283 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005284 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5285 if (mForceTranscriptScroll) {
5286 mForceTranscriptScroll = false;
5287 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5288 return;
5289 }
Adam Powell07852792010-11-10 16:57:05 -08005290 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005291 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005292 final View lastChild = getChildAt(childCount - 1);
5293 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005294 if (mFirstPosition + childCount >= lastHandledItemCount &&
5295 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005296 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5297 return;
5298 }
5299 // Something new came in and we didn't scroll; give the user a clue that
5300 // there's something new.
5301 awakenScrollBars();
5302 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005303
5304 switch (mSyncMode) {
5305 case SYNC_SELECTED_POSITION:
5306 if (isInTouchMode()) {
5307 // We saved our state when not in touch mode. (We know this because
5308 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5309 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5310 // adjusting if the available range changed) and return.
5311 mLayoutMode = LAYOUT_SYNC;
5312 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5313
5314 return;
5315 } else {
5316 // See if we can find a position in the new data with the same
5317 // id as the old selection. This will change mSyncPosition.
5318 newPos = findSyncPosition();
5319 if (newPos >= 0) {
5320 // Found it. Now verify that new selection is still selectable
5321 selectablePos = lookForSelectablePosition(newPos, true);
5322 if (selectablePos == newPos) {
5323 // Same row id is selected
5324 mSyncPosition = newPos;
5325
5326 if (mSyncHeight == getHeight()) {
5327 // If we are at the same height as when we saved state, try
5328 // to restore the scroll position too.
5329 mLayoutMode = LAYOUT_SYNC;
5330 } else {
5331 // We are not the same height as when the selection was saved, so
5332 // don't try to restore the exact position
5333 mLayoutMode = LAYOUT_SET_SELECTION;
5334 }
5335
5336 // Restore selection
5337 setNextSelectedPositionInt(newPos);
5338 return;
5339 }
5340 }
5341 }
5342 break;
5343 case SYNC_FIRST_POSITION:
5344 // Leave mSyncPosition as it is -- just pin to available range
5345 mLayoutMode = LAYOUT_SYNC;
5346 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5347
5348 return;
5349 }
5350 }
5351
5352 if (!isInTouchMode()) {
5353 // We couldn't find matching data -- try to use the same position
5354 newPos = getSelectedItemPosition();
5355
5356 // Pin position to the available range
5357 if (newPos >= count) {
5358 newPos = count - 1;
5359 }
5360 if (newPos < 0) {
5361 newPos = 0;
5362 }
5363
5364 // Make sure we select something selectable -- first look down
5365 selectablePos = lookForSelectablePosition(newPos, true);
5366
5367 if (selectablePos >= 0) {
5368 setNextSelectedPositionInt(selectablePos);
5369 return;
5370 } else {
5371 // Looking down didn't work -- try looking up
5372 selectablePos = lookForSelectablePosition(newPos, false);
5373 if (selectablePos >= 0) {
5374 setNextSelectedPositionInt(selectablePos);
5375 return;
5376 }
5377 }
5378 } else {
5379
5380 // We already know where we want to resurrect the selection
5381 if (mResurrectToPosition >= 0) {
5382 return;
5383 }
5384 }
5385
5386 }
5387
5388 // Nothing is selected. Give up and reset everything.
5389 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5390 mSelectedPosition = INVALID_POSITION;
5391 mSelectedRowId = INVALID_ROW_ID;
5392 mNextSelectedPosition = INVALID_POSITION;
5393 mNextSelectedRowId = INVALID_ROW_ID;
5394 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005395 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005396 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005397 checkSelectionChanged();
5398 }
5399
Romain Guy43c9cdf2010-01-27 13:53:55 -08005400 @Override
5401 protected void onDisplayHint(int hint) {
5402 super.onDisplayHint(hint);
5403 switch (hint) {
5404 case INVISIBLE:
5405 if (mPopup != null && mPopup.isShowing()) {
5406 dismissPopup();
5407 }
5408 break;
5409 case VISIBLE:
5410 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5411 showPopup();
5412 }
5413 break;
5414 }
Romain Guy24562482010-02-01 14:56:19 -08005415 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005416 }
5417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005418 /**
5419 * Removes the filter window
5420 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005421 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005422 if (mPopup != null) {
5423 mPopup.dismiss();
5424 }
5425 }
5426
5427 /**
5428 * Shows the filter window
5429 */
5430 private void showPopup() {
5431 // Make sure we have a window before showing the popup
5432 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005433 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005434 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005435 // Make sure we get focus if we are showing the popup
5436 checkFocus();
5437 }
5438 }
5439
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005440 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005441 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5442 final int[] xy = new int[2];
5443 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005444 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005445 // TODO: And the gravity should be defined in the theme as well
5446 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005447 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005448 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5449 xy[0], bottomGap);
5450 } else {
5451 mPopup.update(xy[0], bottomGap, -1, -1);
5452 }
5453 }
5454
5455 /**
5456 * What is the distance between the source and destination rectangles given the direction of
5457 * focus navigation between them? The direction basically helps figure out more quickly what is
5458 * self evident by the relationship between the rects...
5459 *
5460 * @param source the source rectangle
5461 * @param dest the destination rectangle
5462 * @param direction the direction
5463 * @return the distance between the rectangles
5464 */
5465 static int getDistance(Rect source, Rect dest, int direction) {
5466 int sX, sY; // source x, y
5467 int dX, dY; // dest x, y
5468 switch (direction) {
5469 case View.FOCUS_RIGHT:
5470 sX = source.right;
5471 sY = source.top + source.height() / 2;
5472 dX = dest.left;
5473 dY = dest.top + dest.height() / 2;
5474 break;
5475 case View.FOCUS_DOWN:
5476 sX = source.left + source.width() / 2;
5477 sY = source.bottom;
5478 dX = dest.left + dest.width() / 2;
5479 dY = dest.top;
5480 break;
5481 case View.FOCUS_LEFT:
5482 sX = source.left;
5483 sY = source.top + source.height() / 2;
5484 dX = dest.right;
5485 dY = dest.top + dest.height() / 2;
5486 break;
5487 case View.FOCUS_UP:
5488 sX = source.left + source.width() / 2;
5489 sY = source.top;
5490 dX = dest.left + dest.width() / 2;
5491 dY = dest.bottom;
5492 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005493 case View.FOCUS_FORWARD:
5494 case View.FOCUS_BACKWARD:
5495 sX = source.right + source.width() / 2;
5496 sY = source.top + source.height() / 2;
5497 dX = dest.left + dest.width() / 2;
5498 dY = dest.top + dest.height() / 2;
5499 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005500 default:
5501 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005502 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5503 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005504 }
5505 int deltaX = dX - sX;
5506 int deltaY = dY - sY;
5507 return deltaY * deltaY + deltaX * deltaX;
5508 }
5509
5510 @Override
5511 protected boolean isInFilterMode() {
5512 return mFiltered;
5513 }
5514
5515 /**
5516 * Sends a key to the text filter window
5517 *
5518 * @param keyCode The keycode for the event
5519 * @param event The actual key event
5520 *
5521 * @return True if the text filter handled the event, false otherwise.
5522 */
5523 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005524 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005525 return false;
5526 }
5527
5528 boolean handled = false;
5529 boolean okToSend = true;
5530 switch (keyCode) {
5531 case KeyEvent.KEYCODE_DPAD_UP:
5532 case KeyEvent.KEYCODE_DPAD_DOWN:
5533 case KeyEvent.KEYCODE_DPAD_LEFT:
5534 case KeyEvent.KEYCODE_DPAD_RIGHT:
5535 case KeyEvent.KEYCODE_DPAD_CENTER:
5536 case KeyEvent.KEYCODE_ENTER:
5537 okToSend = false;
5538 break;
5539 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005540 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005541 if (event.getAction() == KeyEvent.ACTION_DOWN
5542 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005543 KeyEvent.DispatcherState state = getKeyDispatcherState();
5544 if (state != null) {
5545 state.startTracking(event, this);
5546 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005547 handled = true;
5548 } else if (event.getAction() == KeyEvent.ACTION_UP
5549 && event.isTracking() && !event.isCanceled()) {
5550 handled = true;
5551 mTextFilter.setText("");
5552 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005553 }
5554 okToSend = false;
5555 break;
5556 case KeyEvent.KEYCODE_SPACE:
5557 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005558 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005559 break;
5560 }
5561
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005562 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005563 createTextFilter(true);
5564
5565 KeyEvent forwardEvent = event;
5566 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005567 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005568 }
5569
5570 int action = event.getAction();
5571 switch (action) {
5572 case KeyEvent.ACTION_DOWN:
5573 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5574 break;
5575
5576 case KeyEvent.ACTION_UP:
5577 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5578 break;
5579
5580 case KeyEvent.ACTION_MULTIPLE:
5581 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5582 break;
5583 }
5584 }
5585 return handled;
5586 }
5587
5588 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005589 * Return an InputConnection for editing of the filter text.
5590 */
5591 @Override
5592 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005593 if (isTextFilterEnabled()) {
5594 // XXX we need to have the text filter created, so we can get an
5595 // InputConnection to proxy to. Unfortunately this means we pretty
5596 // much need to make it as soon as a list view gets focus.
5597 createTextFilter(false);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005598 if (mPublicInputConnection == null) {
5599 mDefInputConnection = new BaseInputConnection(this, false);
5600 mPublicInputConnection = new InputConnectionWrapper(
5601 mTextFilter.onCreateInputConnection(outAttrs), true) {
5602 @Override
5603 public boolean reportFullscreenMode(boolean enabled) {
5604 // Use our own input connection, since it is
5605 // the "real" one the IME is talking with.
5606 return mDefInputConnection.reportFullscreenMode(enabled);
5607 }
5608
5609 @Override
5610 public boolean performEditorAction(int editorAction) {
5611 // The editor is off in its own window; we need to be
5612 // the one that does this.
5613 if (editorAction == EditorInfo.IME_ACTION_DONE) {
5614 InputMethodManager imm = (InputMethodManager)
5615 getContext().getSystemService(
5616 Context.INPUT_METHOD_SERVICE);
5617 if (imm != null) {
5618 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5619 }
5620 return true;
5621 }
5622 return false;
5623 }
5624
5625 @Override
5626 public boolean sendKeyEvent(KeyEvent event) {
5627 // Use our own input connection, since the filter
5628 // text view may not be shown in a window so has
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07005629 // no ViewAncestor to dispatch events with.
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005630 return mDefInputConnection.sendKeyEvent(event);
5631 }
5632 };
5633 }
5634 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
5635 | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5636 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5637 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005638 }
5639 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005640 }
Romain Guy0a637162009-05-29 14:43:54 -07005641
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005642 /**
5643 * For filtering we proxy an input connection to an internal text editor,
5644 * and this allows the proxying to happen.
5645 */
5646 @Override
5647 public boolean checkInputConnectionProxy(View view) {
5648 return view == mTextFilter;
5649 }
Romain Guy0a637162009-05-29 14:43:54 -07005650
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005651 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005652 * Creates the window for the text filter and populates it with an EditText field;
5653 *
5654 * @param animateEntrance true if the window should appear with an animation
5655 */
5656 private void createTextFilter(boolean animateEntrance) {
5657 if (mPopup == null) {
5658 Context c = getContext();
5659 PopupWindow p = new PopupWindow(c);
The Android Open Source Project4df24232009-03-05 14:34:35 -08005660 LayoutInflater layoutInflater = (LayoutInflater)
5661 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005662 mTextFilter = (EditText) layoutInflater.inflate(
5663 com.android.internal.R.layout.typing_filter, null);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005664 // For some reason setting this as the "real" input type changes
5665 // the text view in some way that it doesn't work, and I don't
5666 // want to figure out why this is.
5667 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5668 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
The Android Open Source Project10592532009-03-18 17:39:46 -07005669 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005670 mTextFilter.addTextChangedListener(this);
5671 p.setFocusable(false);
5672 p.setTouchable(false);
5673 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
5674 p.setContentView(mTextFilter);
5675 p.setWidth(LayoutParams.WRAP_CONTENT);
5676 p.setHeight(LayoutParams.WRAP_CONTENT);
5677 p.setBackgroundDrawable(null);
5678 mPopup = p;
5679 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005680 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005681 }
5682 if (animateEntrance) {
5683 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5684 } else {
5685 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5686 }
5687 }
5688
5689 /**
5690 * Clear the text filter.
5691 */
5692 public void clearTextFilter() {
5693 if (mFiltered) {
5694 mTextFilter.setText("");
5695 mFiltered = false;
5696 if (mPopup != null && mPopup.isShowing()) {
5697 dismissPopup();
5698 }
5699 }
5700 }
5701
5702 /**
5703 * Returns if the ListView currently has a text filter.
5704 */
5705 public boolean hasTextFilter() {
5706 return mFiltered;
5707 }
5708
5709 public void onGlobalLayout() {
5710 if (isShown()) {
5711 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08005712 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005713 showPopup();
5714 }
5715 } else {
5716 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08005717 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005718 dismissPopup();
5719 }
5720 }
5721
5722 }
5723
5724 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005725 * For our text watcher that is associated with the text filter. Does
5726 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005727 */
5728 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5729 }
5730
5731 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005732 * For our text watcher that is associated with the text filter. Performs
5733 * the actual filtering as the text changes, and takes care of hiding and
5734 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005735 */
5736 public void onTextChanged(CharSequence s, int start, int before, int count) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005737 if (mPopup != null && isTextFilterEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005738 int length = s.length();
5739 boolean showing = mPopup.isShowing();
5740 if (!showing && length > 0) {
5741 // Show the filter popup if necessary
5742 showPopup();
5743 mFiltered = true;
5744 } else if (showing && length == 0) {
5745 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005746 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005747 mFiltered = false;
5748 }
5749 if (mAdapter instanceof Filterable) {
5750 Filter f = ((Filterable) mAdapter).getFilter();
5751 // Filter should not be null when we reach this part
5752 if (f != null) {
5753 f.filter(s, this);
5754 } else {
5755 throw new IllegalStateException("You cannot call onTextChanged with a non "
5756 + "filterable adapter");
5757 }
5758 }
5759 }
5760 }
5761
5762 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005763 * For our text watcher that is associated with the text filter. Does
5764 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005765 */
5766 public void afterTextChanged(Editable s) {
5767 }
5768
5769 public void onFilterComplete(int count) {
5770 if (mSelectedPosition < 0 && count > 0) {
5771 mResurrectToPosition = INVALID_POSITION;
5772 resurrectSelection();
5773 }
5774 }
5775
5776 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08005777 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
5778 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
5779 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
5780 }
5781
5782 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005783 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
5784 return new LayoutParams(p);
5785 }
5786
5787 @Override
5788 public LayoutParams generateLayoutParams(AttributeSet attrs) {
5789 return new AbsListView.LayoutParams(getContext(), attrs);
5790 }
5791
5792 @Override
5793 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
5794 return p instanceof AbsListView.LayoutParams;
5795 }
5796
5797 /**
5798 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
5799 * to the bottom to show new items.
5800 *
5801 * @param mode the transcript mode to set
5802 *
5803 * @see #TRANSCRIPT_MODE_DISABLED
5804 * @see #TRANSCRIPT_MODE_NORMAL
5805 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
5806 */
5807 public void setTranscriptMode(int mode) {
5808 mTranscriptMode = mode;
5809 }
5810
5811 /**
5812 * Returns the current transcript mode.
5813 *
5814 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
5815 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
5816 */
5817 public int getTranscriptMode() {
5818 return mTranscriptMode;
5819 }
5820
5821 @Override
5822 public int getSolidColor() {
5823 return mCacheColorHint;
5824 }
5825
5826 /**
5827 * 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 -07005828 * on top of a solid, single-color, opaque background.
5829 *
5830 * Zero means that what's behind this object is translucent (non solid) or is not made of a
5831 * single color. This hint will not affect any existing background drawable set on this view (
5832 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005833 *
5834 * @param color The background color
5835 */
5836 public void setCacheColorHint(int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08005837 if (color != mCacheColorHint) {
5838 mCacheColorHint = color;
5839 int count = getChildCount();
5840 for (int i = 0; i < count; i++) {
5841 getChildAt(i).setDrawingCacheBackgroundColor(color);
5842 }
5843 mRecycler.setCacheColorHint(color);
5844 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005845 }
5846
5847 /**
5848 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
5849 * on top of a solid, single-color, opaque background
5850 *
5851 * @return The cache color hint
5852 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07005853 @ViewDebug.ExportedProperty(category = "drawing")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005854 public int getCacheColorHint() {
5855 return mCacheColorHint;
5856 }
5857
5858 /**
5859 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
5860 * List. This includes views displayed on the screen as well as views stored in AbsListView's
5861 * internal view recycler.
5862 *
5863 * @param views A list into which to put the reclaimed views
5864 */
5865 public void reclaimViews(List<View> views) {
5866 int childCount = getChildCount();
5867 RecyclerListener listener = mRecycler.mRecyclerListener;
5868
5869 // Reclaim views on screen
5870 for (int i = 0; i < childCount; i++) {
5871 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07005872 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005873 // Don't reclaim header or footer views, or views that should be ignored
5874 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
5875 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07005876 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005877 if (listener != null) {
5878 // Pretend they went through the scrap heap
5879 listener.onMovedToScrapHeap(child);
5880 }
5881 }
5882 }
5883 mRecycler.reclaimScrapViews(views);
5884 removeAllViewsInLayout();
5885 }
5886
Adam Powell637d3372010-08-25 14:37:03 -07005887 private void finishGlows() {
5888 if (mEdgeGlowTop != null) {
5889 mEdgeGlowTop.finish();
5890 mEdgeGlowBottom.finish();
5891 }
5892 }
5893
Romain Guy13922e02009-05-12 17:56:14 -07005894 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07005895 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
5896 * through the specified intent.
5897 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
5898 */
5899 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005900 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
5901 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07005902 if (mRemoteAdapter != null) {
5903 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
5904 Intent.FilterComparison fcOld = new Intent.FilterComparison(
5905 mRemoteAdapter.getRemoteViewsServiceIntent());
5906 if (fcNew.equals(fcOld)) {
5907 return;
5908 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005909 }
Adam Cohen2148d432011-07-28 14:59:54 -07005910 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005911 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07005912 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07005913 if (mRemoteAdapter.isDataReady()) {
5914 setAdapter(mRemoteAdapter);
5915 }
Winson Chung499cb9f2010-07-16 11:18:17 -07005916 }
5917
5918 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07005919 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
5920 *
5921 * @param handler The OnClickHandler to use when inflating RemoteViews.
5922 *
5923 * @hide
5924 */
5925 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
5926 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
5927 // service handling the specified intent.
5928 if (mRemoteAdapter != null) {
5929 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
5930 }
5931 }
5932
5933 /**
Adam Cohen2148d432011-07-28 14:59:54 -07005934 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
5935 * connected yet.
5936 */
5937 public void deferNotifyDataSetChanged() {
5938 mDeferNotifyDataSetChanged = true;
5939 }
5940
5941 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07005942 * Called back when the adapter connects to the RemoteViewsService.
5943 */
Winson Chung16c8d8a2011-01-20 16:19:33 -08005944 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07005945 if (mRemoteAdapter != mAdapter) {
5946 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07005947 if (mDeferNotifyDataSetChanged) {
5948 mRemoteAdapter.notifyDataSetChanged();
5949 mDeferNotifyDataSetChanged = false;
5950 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08005951 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08005952 } else if (mRemoteAdapter != null) {
5953 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08005954 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07005955 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08005956 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07005957 }
5958
5959 /**
5960 * Called back when the adapter disconnects from the RemoteViewsService.
5961 */
5962 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08005963 // If the remote adapter disconnects, we keep it around
5964 // since the currently displayed items are still cached.
5965 // Further, we want the service to eventually reconnect
5966 // when necessary, as triggered by this view requesting
5967 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07005968 }
5969
5970 /**
Adam Cohenb9673922012-01-05 13:58:47 -08005971 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
5972 * being displayed by the AbsListView.
5973 */
5974 void setVisibleRangeHint(int start, int end) {
5975 if (mRemoteAdapter != null) {
5976 mRemoteAdapter.setVisibleRangeHint(start, end);
5977 }
5978 }
5979
5980 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005981 * Sets the recycler listener to be notified whenever a View is set aside in
5982 * the recycler for later reuse. This listener can be used to free resources
5983 * associated to the View.
5984 *
5985 * @param listener The recycler listener to be notified of views set aside
5986 * in the recycler.
5987 *
5988 * @see android.widget.AbsListView.RecycleBin
5989 * @see android.widget.AbsListView.RecyclerListener
5990 */
5991 public void setRecyclerListener(RecyclerListener listener) {
5992 mRecycler.mRecyclerListener = listener;
5993 }
5994
Adam Powellb1f498a2011-01-18 20:43:23 -08005995 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
5996 @Override
5997 public void onChanged() {
5998 super.onChanged();
5999 if (mFastScroller != null) {
6000 mFastScroller.onSectionsChanged();
6001 }
6002 }
6003
6004 @Override
6005 public void onInvalidated() {
6006 super.onInvalidated();
6007 if (mFastScroller != null) {
6008 mFastScroller.onSectionsChanged();
6009 }
6010 }
6011 }
6012
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006013 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006014 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6015 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6016 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6017 * selects and deselects list items.
6018 */
6019 public interface MultiChoiceModeListener extends ActionMode.Callback {
6020 /**
6021 * Called when an item is checked or unchecked during selection mode.
6022 *
6023 * @param mode The {@link ActionMode} providing the selection mode
6024 * @param position Adapter position of the item that was checked or unchecked
6025 * @param id Adapter ID of the item that was checked or unchecked
6026 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6027 * if the item is now unchecked.
6028 */
6029 public void onItemCheckedStateChanged(ActionMode mode,
6030 int position, long id, boolean checked);
6031 }
6032
6033 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6034 private MultiChoiceModeListener mWrapped;
6035
6036 public void setWrapped(MultiChoiceModeListener wrapped) {
6037 mWrapped = wrapped;
6038 }
6039
Adam Powella7981702012-08-24 12:43:41 -07006040 public boolean hasWrappedCallback() {
6041 return mWrapped != null;
6042 }
6043
Adam Powellf343e1b2010-08-13 18:27:04 -07006044 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6045 if (mWrapped.onCreateActionMode(mode, menu)) {
6046 // Initialize checked graphic state?
6047 setLongClickable(false);
6048 return true;
6049 }
6050 return false;
6051 }
6052
6053 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6054 return mWrapped.onPrepareActionMode(mode, menu);
6055 }
6056
6057 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6058 return mWrapped.onActionItemClicked(mode, item);
6059 }
6060
6061 public void onDestroyActionMode(ActionMode mode) {
6062 mWrapped.onDestroyActionMode(mode);
6063 mChoiceActionMode = null;
6064
6065 // Ending selection mode means deselecting everything.
6066 clearChoices();
6067
6068 mDataChanged = true;
6069 rememberSyncState();
6070 requestLayout();
6071
6072 setLongClickable(true);
6073 }
6074
6075 public void onItemCheckedStateChanged(ActionMode mode,
6076 int position, long id, boolean checked) {
6077 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6078
6079 // If there are no items selected we no longer need the selection mode.
6080 if (getCheckedItemCount() == 0) {
6081 mode.finish();
6082 }
6083 }
6084 }
6085
6086 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006087 * AbsListView extends LayoutParams to provide a place to hold the view type.
6088 */
6089 public static class LayoutParams extends ViewGroup.LayoutParams {
6090 /**
6091 * View type for this view, as returned by
6092 * {@link android.widget.Adapter#getItemViewType(int) }
6093 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006094 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006095 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6096 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6097 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006098 int viewType;
6099
The Android Open Source Project4df24232009-03-05 14:34:35 -08006100 /**
6101 * When this boolean is set, the view has been added to the AbsListView
6102 * at least once. It is used to know whether headers/footers have already
6103 * been added to the list view and whether they should be treated as
6104 * recycled views or not.
6105 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006106 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006107 boolean recycledHeaderFooter;
6108
Romain Guy0bf88592010-03-02 13:38:44 -08006109 /**
6110 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6111 * to obtain children views to measure itself. When doing so, the children
6112 * are not attached to the window, but put in the recycler which assumes
6113 * they've been attached before. Setting this flag will force the reused
6114 * view to be attached to the window rather than just attached to the
6115 * parent.
6116 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006117 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006118 boolean forceAdd;
6119
Dianne Hackborn079e2352010-10-18 17:02:43 -07006120 /**
6121 * The position the view was removed from when pulled out of the
6122 * scrap heap.
6123 * @hide
6124 */
6125 int scrappedFromPosition;
6126
Adam Powell539ee872012-02-03 19:00:49 -08006127 /**
6128 * The ID the view represents
6129 */
6130 long itemId = -1;
6131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006132 public LayoutParams(Context c, AttributeSet attrs) {
6133 super(c, attrs);
6134 }
6135
6136 public LayoutParams(int w, int h) {
6137 super(w, h);
6138 }
6139
6140 public LayoutParams(int w, int h, int viewType) {
6141 super(w, h);
6142 this.viewType = viewType;
6143 }
6144
6145 public LayoutParams(ViewGroup.LayoutParams source) {
6146 super(source);
6147 }
6148 }
6149
6150 /**
6151 * A RecyclerListener is used to receive a notification whenever a View is placed
6152 * inside the RecycleBin's scrap heap. This listener is used to free resources
6153 * associated to Views placed in the RecycleBin.
6154 *
6155 * @see android.widget.AbsListView.RecycleBin
6156 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6157 */
6158 public static interface RecyclerListener {
6159 /**
6160 * Indicates that the specified View was moved into the recycler's scrap heap.
6161 * The view is not displayed on screen any more and any expensive resource
6162 * associated with the view should be discarded.
6163 *
6164 * @param view
6165 */
6166 void onMovedToScrapHeap(View view);
6167 }
6168
6169 /**
6170 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6171 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6172 * start of a layout. By construction, they are displaying current information. At the end of
6173 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6174 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6175 *
6176 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6177 * @see android.widget.AbsListView.RecyclerListener
6178 */
6179 class RecycleBin {
6180 private RecyclerListener mRecyclerListener;
6181
6182 /**
6183 * The position of the first view stored in mActiveViews.
6184 */
6185 private int mFirstActivePosition;
6186
6187 /**
6188 * Views that were on screen at the start of layout. This array is populated at the start of
6189 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6190 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6191 * view store in mFirstActivePosition.
6192 */
6193 private View[] mActiveViews = new View[0];
6194
6195 /**
6196 * Unsorted views that can be used by the adapter as a convert view.
6197 */
6198 private ArrayList<View>[] mScrapViews;
6199
6200 private int mViewTypeCount;
6201
6202 private ArrayList<View> mCurrentScrap;
6203
Adam Powell539ee872012-02-03 19:00:49 -08006204 private ArrayList<View> mSkippedScrap;
6205
6206 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006207 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006208
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006209 public void setViewTypeCount(int viewTypeCount) {
6210 if (viewTypeCount < 1) {
6211 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6212 }
6213 //noinspection unchecked
6214 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6215 for (int i = 0; i < viewTypeCount; i++) {
6216 scrapViews[i] = new ArrayList<View>();
6217 }
6218 mViewTypeCount = viewTypeCount;
6219 mCurrentScrap = scrapViews[0];
6220 mScrapViews = scrapViews;
6221 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006222
Adam Powellf3c2eda2010-03-16 17:31:01 -07006223 public void markChildrenDirty() {
6224 if (mViewTypeCount == 1) {
6225 final ArrayList<View> scrap = mCurrentScrap;
6226 final int scrapCount = scrap.size();
6227 for (int i = 0; i < scrapCount; i++) {
6228 scrap.get(i).forceLayout();
6229 }
6230 } else {
6231 final int typeCount = mViewTypeCount;
6232 for (int i = 0; i < typeCount; i++) {
6233 final ArrayList<View> scrap = mScrapViews[i];
6234 final int scrapCount = scrap.size();
6235 for (int j = 0; j < scrapCount; j++) {
6236 scrap.get(j).forceLayout();
6237 }
6238 }
6239 }
Adam Powell539ee872012-02-03 19:00:49 -08006240 if (mTransientStateViews != null) {
6241 final int count = mTransientStateViews.size();
6242 for (int i = 0; i < count; i++) {
6243 mTransientStateViews.valueAt(i).forceLayout();
6244 }
6245 }
Chet Haase72871322013-02-26 16:12:13 -07006246 if (mTransientStateViewsById != null) {
6247 final int count = mTransientStateViewsById.size();
6248 for (int i = 0; i < count; i++) {
6249 mTransientStateViewsById.valueAt(i).forceLayout();
6250 }
6251 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006252 }
Romain Guy0a637162009-05-29 14:43:54 -07006253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006254 public boolean shouldRecycleViewType(int viewType) {
6255 return viewType >= 0;
6256 }
6257
6258 /**
6259 * Clears the scrap heap.
6260 */
6261 void clear() {
6262 if (mViewTypeCount == 1) {
6263 final ArrayList<View> scrap = mCurrentScrap;
6264 final int scrapCount = scrap.size();
6265 for (int i = 0; i < scrapCount; i++) {
6266 removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
6267 }
6268 } else {
6269 final int typeCount = mViewTypeCount;
6270 for (int i = 0; i < typeCount; i++) {
6271 final ArrayList<View> scrap = mScrapViews[i];
6272 final int scrapCount = scrap.size();
6273 for (int j = 0; j < scrapCount; j++) {
6274 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6275 }
6276 }
6277 }
Adam Powell539ee872012-02-03 19:00:49 -08006278 if (mTransientStateViews != null) {
6279 mTransientStateViews.clear();
6280 }
Chet Haase72871322013-02-26 16:12:13 -07006281 if (mTransientStateViewsById != null) {
6282 mTransientStateViewsById.clear();
6283 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006284 }
6285
6286 /**
6287 * Fill ActiveViews with all of the children of the AbsListView.
6288 *
6289 * @param childCount The minimum number of views mActiveViews should hold
6290 * @param firstActivePosition The position of the first view that will be stored in
6291 * mActiveViews
6292 */
6293 void fillActiveViews(int childCount, int firstActivePosition) {
6294 if (mActiveViews.length < childCount) {
6295 mActiveViews = new View[childCount];
6296 }
6297 mFirstActivePosition = firstActivePosition;
6298
6299 final View[] activeViews = mActiveViews;
6300 for (int i = 0; i < childCount; i++) {
6301 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006302 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006303 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006304 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006305 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6306 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006307 activeViews[i] = child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006308 }
6309 }
6310 }
6311
6312 /**
6313 * Get the view corresponding to the specified position. The view will be removed from
6314 * mActiveViews if it is found.
6315 *
6316 * @param position The position to look up in mActiveViews
6317 * @return The view if it is found, null otherwise
6318 */
6319 View getActiveView(int position) {
6320 int index = position - mFirstActivePosition;
6321 final View[] activeViews = mActiveViews;
6322 if (index >=0 && index < activeViews.length) {
6323 final View match = activeViews[index];
6324 activeViews[index] = null;
6325 return match;
6326 }
6327 return null;
6328 }
6329
Adam Powell539ee872012-02-03 19:00:49 -08006330 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006331 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6332 long id = mAdapter.getItemId(position);
6333 View result = mTransientStateViewsById.get(id);
6334 mTransientStateViewsById.remove(id);
6335 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006336 }
Chet Haase72871322013-02-26 16:12:13 -07006337 if (mTransientStateViews != null) {
6338 final int index = mTransientStateViews.indexOfKey(position);
6339 if (index >= 0) {
6340 View result = mTransientStateViews.valueAt(index);
6341 mTransientStateViews.removeAt(index);
6342 return result;
6343 }
Adam Powell539ee872012-02-03 19:00:49 -08006344 }
Chet Haase72871322013-02-26 16:12:13 -07006345 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006346 }
6347
6348 /**
6349 * Dump any currently saved views with transient state.
6350 */
6351 void clearTransientStateViews() {
6352 if (mTransientStateViews != null) {
6353 mTransientStateViews.clear();
6354 }
Chet Haase72871322013-02-26 16:12:13 -07006355 if (mTransientStateViewsById != null) {
6356 mTransientStateViewsById.clear();
6357 }
Adam Powell539ee872012-02-03 19:00:49 -08006358 }
6359
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006360 /**
6361 * @return A view from the ScrapViews collection. These are unordered.
6362 */
6363 View getScrapView(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006364 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006365 return retrieveFromScrap(mCurrentScrap, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006366 } else {
6367 int whichScrap = mAdapter.getItemViewType(position);
6368 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006369 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006370 }
6371 }
6372 return null;
6373 }
6374
6375 /**
Adam Powell539ee872012-02-03 19:00:49 -08006376 * Put a view into the ScrapViews list. These views are unordered.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006377 *
6378 * @param scrap The view to add
6379 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006380 void addScrapView(View scrap, int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006381 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
6382 if (lp == null) {
6383 return;
6384 }
6385
Adam Powell539ee872012-02-03 19:00:49 -08006386 lp.scrappedFromPosition = position;
6387
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006388 // Don't put header or footer views or views that should be ignored
6389 // into the scrap heap
6390 int viewType = lp.viewType;
Adam Powell539ee872012-02-03 19:00:49 -08006391 final boolean scrapHasTransientState = scrap.hasTransientState();
6392 if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
Romain Guyb7e0f792013-06-07 15:36:49 -07006393 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && scrapHasTransientState) {
Adam Powell539ee872012-02-03 19:00:49 -08006394 if (mSkippedScrap == null) {
6395 mSkippedScrap = new ArrayList<View>();
6396 }
6397 mSkippedScrap.add(scrap);
6398 }
6399 if (scrapHasTransientState) {
Adam Powell057a5852012-05-11 10:28:38 -07006400 scrap.dispatchStartTemporaryDetach();
Chet Haase72871322013-02-26 16:12:13 -07006401 if (mAdapter != null && mAdapterHasStableIds) {
6402 if (mTransientStateViewsById == null) {
6403 mTransientStateViewsById = new LongSparseArray<View>();
6404 }
6405 mTransientStateViewsById.put(lp.itemId, scrap);
Chet Haasea2230e12013-03-18 14:52:37 -07006406 } else if (!mDataChanged) {
6407 // avoid putting views on transient state list during a data change;
6408 // the layout positions may be out of sync with the adapter positions
Chet Haase72871322013-02-26 16:12:13 -07006409 if (mTransientStateViews == null) {
6410 mTransientStateViews = new SparseArray<View>();
6411 }
6412 mTransientStateViews.put(position, scrap);
6413 }
Romain Guy9b1bb812010-02-26 14:14:13 -08006414 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006415 return;
6416 }
6417
Adam Powell539ee872012-02-03 19:00:49 -08006418 scrap.dispatchStartTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006419 if (mViewTypeCount == 1) {
6420 mCurrentScrap.add(scrap);
6421 } else {
6422 mScrapViews[viewType].add(scrap);
6423 }
6424
alanvc1d7e772012-05-08 14:47:24 -07006425 scrap.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006426 if (mRecyclerListener != null) {
6427 mRecyclerListener.onMovedToScrapHeap(scrap);
6428 }
6429 }
6430
6431 /**
Adam Powell539ee872012-02-03 19:00:49 -08006432 * Finish the removal of any views that skipped the scrap heap.
6433 */
6434 void removeSkippedScrap() {
6435 if (mSkippedScrap == null) {
6436 return;
6437 }
6438 final int count = mSkippedScrap.size();
6439 for (int i = 0; i < count; i++) {
6440 removeDetachedView(mSkippedScrap.get(i), false);
6441 }
6442 mSkippedScrap.clear();
6443 }
6444
6445 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006446 * Move all views remaining in mActiveViews to mScrapViews.
6447 */
6448 void scrapActiveViews() {
6449 final View[] activeViews = mActiveViews;
6450 final boolean hasListener = mRecyclerListener != null;
6451 final boolean multipleScraps = mViewTypeCount > 1;
6452
6453 ArrayList<View> scrapViews = mCurrentScrap;
6454 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006455 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006456 final View victim = activeViews[i];
6457 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006458 final AbsListView.LayoutParams lp
6459 = (AbsListView.LayoutParams) victim.getLayoutParams();
6460 int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006461
6462 activeViews[i] = null;
6463
Adam Powell539ee872012-02-03 19:00:49 -08006464 final boolean scrapHasTransientState = victim.hasTransientState();
6465 if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006466 // Do not move views that should be ignored
Romain Guyb7e0f792013-06-07 15:36:49 -07006467 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
Adam Powell539ee872012-02-03 19:00:49 -08006468 scrapHasTransientState) {
Romain Guy9b1bb812010-02-26 14:14:13 -08006469 removeDetachedView(victim, false);
6470 }
Adam Powell539ee872012-02-03 19:00:49 -08006471 if (scrapHasTransientState) {
Chet Haase72871322013-02-26 16:12:13 -07006472 if (mAdapter != null && mAdapterHasStableIds) {
6473 if (mTransientStateViewsById == null) {
6474 mTransientStateViewsById = new LongSparseArray<View>();
6475 }
6476 long id = mAdapter.getItemId(mFirstActivePosition + i);
6477 mTransientStateViewsById.put(id, victim);
6478 } else {
6479 if (mTransientStateViews == null) {
6480 mTransientStateViews = new SparseArray<View>();
6481 }
6482 mTransientStateViews.put(mFirstActivePosition + i, victim);
Adam Powell539ee872012-02-03 19:00:49 -08006483 }
Adam Powell539ee872012-02-03 19:00:49 -08006484 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006485 continue;
6486 }
6487
6488 if (multipleScraps) {
6489 scrapViews = mScrapViews[whichScrap];
6490 }
Romain Guya440b002010-02-24 15:57:54 -08006491 victim.dispatchStartTemporaryDetach();
Dianne Hackborn079e2352010-10-18 17:02:43 -07006492 lp.scrappedFromPosition = mFirstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006493 scrapViews.add(victim);
6494
alanvc1d7e772012-05-08 14:47:24 -07006495 victim.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006496 if (hasListener) {
6497 mRecyclerListener.onMovedToScrapHeap(victim);
6498 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006499 }
6500 }
6501
6502 pruneScrapViews();
6503 }
6504
6505 /**
6506 * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
6507 * (This can happen if an adapter does not recycle its views).
6508 */
6509 private void pruneScrapViews() {
6510 final int maxViews = mActiveViews.length;
6511 final int viewTypeCount = mViewTypeCount;
6512 final ArrayList<View>[] scrapViews = mScrapViews;
6513 for (int i = 0; i < viewTypeCount; ++i) {
6514 final ArrayList<View> scrapPile = scrapViews[i];
6515 int size = scrapPile.size();
6516 final int extras = size - maxViews;
6517 size--;
6518 for (int j = 0; j < extras; j++) {
6519 removeDetachedView(scrapPile.remove(size--), false);
6520 }
6521 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006522
6523 if (mTransientStateViews != null) {
6524 for (int i = 0; i < mTransientStateViews.size(); i++) {
6525 final View v = mTransientStateViews.valueAt(i);
6526 if (!v.hasTransientState()) {
6527 mTransientStateViews.removeAt(i);
6528 i--;
6529 }
6530 }
6531 }
Chet Haase72871322013-02-26 16:12:13 -07006532 if (mTransientStateViewsById != null) {
6533 for (int i = 0; i < mTransientStateViewsById.size(); i++) {
6534 final View v = mTransientStateViewsById.valueAt(i);
6535 if (!v.hasTransientState()) {
6536 mTransientStateViewsById.removeAt(i);
6537 i--;
6538 }
6539 }
6540 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006541 }
6542
6543 /**
6544 * Puts all views in the scrap heap into the supplied list.
6545 */
6546 void reclaimScrapViews(List<View> views) {
6547 if (mViewTypeCount == 1) {
6548 views.addAll(mCurrentScrap);
6549 } else {
6550 final int viewTypeCount = mViewTypeCount;
6551 final ArrayList<View>[] scrapViews = mScrapViews;
6552 for (int i = 0; i < viewTypeCount; ++i) {
6553 final ArrayList<View> scrapPile = scrapViews[i];
6554 views.addAll(scrapPile);
6555 }
6556 }
6557 }
Romain Guy52e2ef82010-01-14 12:11:48 -08006558
6559 /**
6560 * Updates the cache color hint of all known views.
6561 *
6562 * @param color The new cache color hint.
6563 */
6564 void setCacheColorHint(int color) {
6565 if (mViewTypeCount == 1) {
6566 final ArrayList<View> scrap = mCurrentScrap;
6567 final int scrapCount = scrap.size();
6568 for (int i = 0; i < scrapCount; i++) {
6569 scrap.get(i).setDrawingCacheBackgroundColor(color);
6570 }
6571 } else {
6572 final int typeCount = mViewTypeCount;
6573 for (int i = 0; i < typeCount; i++) {
6574 final ArrayList<View> scrap = mScrapViews[i];
6575 final int scrapCount = scrap.size();
6576 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07006577 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08006578 }
6579 }
6580 }
6581 // Just in case this is called during a layout pass
6582 final View[] activeViews = mActiveViews;
6583 final int count = activeViews.length;
6584 for (int i = 0; i < count; ++i) {
6585 final View victim = activeViews[i];
6586 if (victim != null) {
6587 victim.setDrawingCacheBackgroundColor(color);
6588 }
6589 }
6590 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006591 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006592
6593 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6594 int size = scrapViews.size();
6595 if (size > 0) {
6596 // See if we still have a view for this position.
6597 for (int i=0; i<size; i++) {
6598 View view = scrapViews.get(i);
6599 if (((AbsListView.LayoutParams)view.getLayoutParams())
6600 .scrappedFromPosition == position) {
6601 scrapViews.remove(i);
6602 return view;
6603 }
6604 }
6605 return scrapViews.remove(size - 1);
6606 } else {
6607 return null;
6608 }
6609 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006610}