blob: 5f6073522381d63510e59fc2d2faa95f4c9b0cb6 [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
Adam Powell234a5712010-09-14 10:34:56 -070019import com.android.internal.R;
20
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070022import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070028import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.os.Debug;
30import android.os.Handler;
31import android.os.Parcel;
32import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080033import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.text.Editable;
35import android.text.TextUtils;
36import android.text.TextWatcher;
37import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080038import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070039import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080040import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070041import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070042import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070043import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070044import android.view.ContextMenu.ContextMenuInfo;
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -070045import android.view.FocusFinder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.view.Gravity;
47import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080048import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.view.KeyEvent;
50import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070051import android.view.Menu;
52import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.view.MotionEvent;
54import android.view.VelocityTracker;
55import android.view.View;
56import android.view.ViewConfiguration;
57import android.view.ViewDebug;
58import android.view.ViewGroup;
Michael Jurka13451a42011-08-22 15:54:21 -070059import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070061import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070062import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070063import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070064import android.view.animation.Interpolator;
65import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070066import android.view.inputmethod.BaseInputConnection;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070067import android.view.inputmethod.EditorInfo;
68import android.view.inputmethod.InputConnection;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070069import android.view.inputmethod.InputConnectionWrapper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
72import 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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679 * Interface definition for a callback to be invoked when the list or grid
680 * has been scrolled.
681 */
682 public interface OnScrollListener {
683
684 /**
685 * The view is not scrolling. Note navigating the list using the trackball counts as
686 * being in the idle state since these transitions are not animated.
687 */
688 public static int SCROLL_STATE_IDLE = 0;
689
690 /**
691 * The user is scrolling using touch, and their finger is still on the screen
692 */
693 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
694
695 /**
696 * The user had previously been scrolling using touch and had performed a fling. The
697 * animation is now coasting to a stop
698 */
699 public static int SCROLL_STATE_FLING = 2;
700
701 /**
702 * Callback method to be invoked while the list view or grid view is being scrolled. If the
703 * view is being scrolled, this method will be called before the next frame of the scroll is
704 * rendered. In particular, it will be called before any calls to
705 * {@link Adapter#getView(int, View, ViewGroup)}.
706 *
707 * @param view The view whose scroll state is being reported
708 *
709 * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
710 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
711 */
712 public void onScrollStateChanged(AbsListView view, int scrollState);
713
714 /**
715 * Callback method to be invoked when the list or grid has been scrolled. This will be
716 * called after the scroll has completed
717 * @param view The view whose scroll state is being reported
718 * @param firstVisibleItem the index of the first visible cell (ignore if
719 * visibleItemCount == 0)
720 * @param visibleItemCount the number of visible cells
721 * @param totalItemCount the number of items in the list adaptor
722 */
723 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
724 int totalItemCount);
725 }
726
Dianne Hackborne2136772010-11-04 15:08:59 -0700727 /**
728 * The top-level view of a list item can implement this interface to allow
729 * itself to modify the bounds of the selection shown for that item.
730 */
731 public interface SelectionBoundsAdjuster {
732 /**
733 * Called to allow the list item to adjust the bounds shown for
734 * its selection.
735 *
736 * @param bounds On call, this contains the bounds the list has
737 * selected for the item (that is the bounds of the entire view). The
738 * values can be modified as desired.
739 */
740 public void adjustListItemSelectionBounds(Rect bounds);
741 }
742
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743 public AbsListView(Context context) {
744 super(context);
745 initAbsListView();
746
747 setVerticalScrollBarEnabled(true);
748 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
749 initializeScrollbars(a);
750 a.recycle();
751 }
752
753 public AbsListView(Context context, AttributeSet attrs) {
754 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
755 }
756
757 public AbsListView(Context context, AttributeSet attrs, int defStyle) {
758 super(context, attrs, defStyle);
759 initAbsListView();
760
761 TypedArray a = context.obtainStyledAttributes(attrs,
762 com.android.internal.R.styleable.AbsListView, defStyle, 0);
763
764 Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
765 if (d != null) {
766 setSelector(d);
767 }
768
769 mDrawSelectorOnTop = a.getBoolean(
770 com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
771
772 boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
773 setStackFromBottom(stackFromBottom);
774
775 boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
776 setScrollingCacheEnabled(scrollingCacheEnabled);
777
778 boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
779 setTextFilterEnabled(useTextFilter);
780
781 int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
782 TRANSCRIPT_MODE_DISABLED);
783 setTranscriptMode(transcriptMode);
784
785 int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
786 setCacheColorHint(color);
Romain Guy0a637162009-05-29 14:43:54 -0700787
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
789 setFastScrollEnabled(enableFastScroll);
790
791 boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
792 setSmoothScrollbarEnabled(smoothScrollbar);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800793
Adam Powellf343e1b2010-08-13 18:27:04 -0700794 setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
Adam Powell20232d02010-12-08 21:08:53 -0800795 setFastScrollAlwaysVisible(
796 a.getBoolean(R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 a.recycle();
799 }
800
Romain Guyd6a463a2009-05-21 23:10:10 -0700801 private void initAbsListView() {
802 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700803 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700804 setFocusableInTouchMode(true);
805 setWillNotDraw(false);
806 setAlwaysDrawnWithCacheEnabled(false);
807 setScrollingCacheEnabled(true);
808
Romain Guy4296fc42009-07-06 11:48:52 -0700809 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
810 mTouchSlop = configuration.getScaledTouchSlop();
811 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
812 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700813 mOverscrollDistance = configuration.getScaledOverscrollDistance();
814 mOverflingDistance = configuration.getScaledOverflingDistance();
815
Romain Guyd6a463a2009-05-21 23:10:10 -0700816 mDensityScale = getContext().getResources().getDisplayMetrics().density;
817 }
Romain Guy0a637162009-05-29 14:43:54 -0700818
Adam Powell637d3372010-08-25 14:37:03 -0700819 @Override
820 public void setOverScrollMode(int mode) {
821 if (mode != OVER_SCROLL_NEVER) {
822 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800823 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700824 mEdgeGlowTop = new EdgeEffect(context);
825 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700826 }
827 } else {
828 mEdgeGlowTop = null;
829 mEdgeGlowBottom = null;
830 }
831 super.setOverScrollMode(mode);
832 }
833
Romain Guyd6a463a2009-05-21 23:10:10 -0700834 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700835 * {@inheritDoc}
836 */
837 @Override
838 public void setAdapter(ListAdapter adapter) {
839 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800840 mAdapterHasStableIds = mAdapter.hasStableIds();
841 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700842 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700843 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700844 }
845 }
846
847 if (mCheckStates != null) {
848 mCheckStates.clear();
849 }
850
851 if (mCheckedIdStates != null) {
852 mCheckedIdStates.clear();
853 }
854 }
855
856 /**
857 * Returns the number of items currently selected. This will only be valid
858 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
859 *
860 * <p>To determine the specific items that are currently selected, use one of
861 * the <code>getChecked*</code> methods.
862 *
863 * @return The number of items currently selected
864 *
865 * @see #getCheckedItemPosition()
866 * @see #getCheckedItemPositions()
867 * @see #getCheckedItemIds()
868 */
869 public int getCheckedItemCount() {
870 return mCheckedItemCount;
871 }
872
873 /**
874 * Returns the checked state of the specified position. The result is only
875 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
876 * or {@link #CHOICE_MODE_MULTIPLE}.
877 *
878 * @param position The item whose checked state to return
879 * @return The item's checked state or <code>false</code> if choice mode
880 * is invalid
881 *
882 * @see #setChoiceMode(int)
883 */
884 public boolean isItemChecked(int position) {
885 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
886 return mCheckStates.get(position);
887 }
888
889 return false;
890 }
891
892 /**
893 * Returns the currently checked item. The result is only valid if the choice
894 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
895 *
896 * @return The position of the currently checked item or
897 * {@link #INVALID_POSITION} if nothing is selected
898 *
899 * @see #setChoiceMode(int)
900 */
901 public int getCheckedItemPosition() {
902 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
903 return mCheckStates.keyAt(0);
904 }
905
906 return INVALID_POSITION;
907 }
908
909 /**
910 * Returns the set of checked items in the list. The result is only valid if
911 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
912 *
913 * @return A SparseBooleanArray which will return true for each call to
914 * get(int position) where position is a position in the list,
915 * or <code>null</code> if the choice mode is set to
916 * {@link #CHOICE_MODE_NONE}.
917 */
918 public SparseBooleanArray getCheckedItemPositions() {
919 if (mChoiceMode != CHOICE_MODE_NONE) {
920 return mCheckStates;
921 }
922 return null;
923 }
924
925 /**
926 * Returns the set of checked items ids. The result is only valid if the
927 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
928 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
929 *
930 * @return A new array which contains the id of each checked item in the
931 * list.
932 */
933 public long[] getCheckedItemIds() {
934 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
935 return new long[0];
936 }
937
Adam Powell14c08042011-10-06 19:46:18 -0700938 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700939 final int count = idStates.size();
940 final long[] ids = new long[count];
941
942 for (int i = 0; i < count; i++) {
943 ids[i] = idStates.keyAt(i);
944 }
945
946 return ids;
947 }
948
949 /**
950 * Clear any choices previously set
951 */
952 public void clearChoices() {
953 if (mCheckStates != null) {
954 mCheckStates.clear();
955 }
956 if (mCheckedIdStates != null) {
957 mCheckedIdStates.clear();
958 }
959 mCheckedItemCount = 0;
960 }
961
962 /**
963 * Sets the checked state of the specified position. The is only valid if
964 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
965 * {@link #CHOICE_MODE_MULTIPLE}.
966 *
967 * @param position The item whose checked state is to be checked
968 * @param value The new checked state for the item
969 */
970 public void setItemChecked(int position, boolean value) {
971 if (mChoiceMode == CHOICE_MODE_NONE) {
972 return;
973 }
974
975 // Start selection mode if needed. We don't need to if we're unchecking something.
976 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
977 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
978 }
979
980 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
981 boolean oldValue = mCheckStates.get(position);
982 mCheckStates.put(position, value);
983 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
984 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -0700985 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -0700986 } else {
987 mCheckedIdStates.delete(mAdapter.getItemId(position));
988 }
989 }
990 if (oldValue != value) {
991 if (value) {
992 mCheckedItemCount++;
993 } else {
994 mCheckedItemCount--;
995 }
996 }
997 if (mChoiceActionMode != null) {
998 final long id = mAdapter.getItemId(position);
999 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1000 position, id, value);
1001 }
1002 } else {
1003 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1004 // Clear all values if we're checking something, or unchecking the currently
1005 // selected item
1006 if (value || isItemChecked(position)) {
1007 mCheckStates.clear();
1008 if (updateIds) {
1009 mCheckedIdStates.clear();
1010 }
1011 }
1012 // this may end up selecting the value we just cleared but this way
1013 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1014 if (value) {
1015 mCheckStates.put(position, true);
1016 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001017 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001018 }
1019 mCheckedItemCount = 1;
1020 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1021 mCheckedItemCount = 0;
1022 }
1023 }
1024
1025 // Do not generate a data change while we are in the layout phase
1026 if (!mInLayout && !mBlockLayoutRequests) {
1027 mDataChanged = true;
1028 rememberSyncState();
1029 requestLayout();
1030 }
1031 }
1032
1033 @Override
1034 public boolean performItemClick(View view, int position, long id) {
1035 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001036 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001037
1038 if (mChoiceMode != CHOICE_MODE_NONE) {
1039 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001040 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001041
1042 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1043 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
1044 boolean newValue = !mCheckStates.get(position, false);
1045 mCheckStates.put(position, newValue);
1046 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1047 if (newValue) {
Adam Powell14c08042011-10-06 19:46:18 -07001048 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001049 } else {
1050 mCheckedIdStates.delete(mAdapter.getItemId(position));
1051 }
1052 }
1053 if (newValue) {
1054 mCheckedItemCount++;
1055 } else {
1056 mCheckedItemCount--;
1057 }
1058 if (mChoiceActionMode != null) {
1059 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1060 position, id, newValue);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001061 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001062 }
Adam Powell29382d92012-02-23 11:03:22 -08001063 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001064 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
1065 boolean newValue = !mCheckStates.get(position, false);
1066 if (newValue) {
1067 mCheckStates.clear();
1068 mCheckStates.put(position, true);
1069 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1070 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001071 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001072 }
1073 mCheckedItemCount = 1;
1074 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1075 mCheckedItemCount = 0;
1076 }
Adam Powell29382d92012-02-23 11:03:22 -08001077 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001078 }
1079
Adam Powell29382d92012-02-23 11:03:22 -08001080 if (checkedStateChanged) {
1081 updateOnScreenCheckedViews();
1082 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001083 }
1084
Adam Powellbf5f2b32010-10-24 16:45:44 -07001085 if (dispatchItemClick) {
1086 handled |= super.performItemClick(view, position, id);
1087 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001088
1089 return handled;
1090 }
1091
1092 /**
Adam Powell29382d92012-02-23 11:03:22 -08001093 * Perform a quick, in-place update of the checked or activated state
1094 * on all visible item views. This should only be called when a valid
1095 * choice mode is active.
1096 */
1097 private void updateOnScreenCheckedViews() {
1098 final int firstPos = mFirstPosition;
1099 final int count = getChildCount();
1100 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1101 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1102 for (int i = 0; i < count; i++) {
1103 final View child = getChildAt(i);
1104 final int position = firstPos + i;
1105
1106 if (child instanceof Checkable) {
1107 ((Checkable) child).setChecked(mCheckStates.get(position));
1108 } else if (useActivated) {
1109 child.setActivated(mCheckStates.get(position));
1110 }
1111 }
1112 }
1113
1114 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001115 * @see #setChoiceMode(int)
1116 *
1117 * @return The current choice mode
1118 */
1119 public int getChoiceMode() {
1120 return mChoiceMode;
1121 }
1122
1123 /**
1124 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1125 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1126 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1127 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1128 *
1129 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1130 * {@link #CHOICE_MODE_MULTIPLE}
1131 */
1132 public void setChoiceMode(int choiceMode) {
1133 mChoiceMode = choiceMode;
1134 if (mChoiceActionMode != null) {
1135 mChoiceActionMode.finish();
1136 mChoiceActionMode = null;
1137 }
1138 if (mChoiceMode != CHOICE_MODE_NONE) {
1139 if (mCheckStates == null) {
1140 mCheckStates = new SparseBooleanArray();
1141 }
1142 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Adam Powell14c08042011-10-06 19:46:18 -07001143 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -07001144 }
1145 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1146 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1147 clearChoices();
1148 setLongClickable(true);
1149 }
1150 }
1151 }
1152
1153 /**
1154 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1155 * selection {@link ActionMode}. Only used when the choice mode is set to
1156 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1157 *
1158 * @param listener Listener that will manage the selection mode
1159 *
1160 * @see #setChoiceMode(int)
1161 */
1162 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1163 if (mMultiChoiceModeCallback == null) {
1164 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1165 }
1166 mMultiChoiceModeCallback.setWrapped(listener);
1167 }
1168
1169 /**
Adam Powell637d3372010-08-25 14:37:03 -07001170 * @return true if all list content currently fits within the view boundaries
1171 */
1172 private boolean contentFits() {
1173 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001174 if (childCount == 0) return true;
1175 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001176
Adam Powell4ce35412011-01-24 14:55:00 -08001177 return getChildAt(0).getTop() >= mListPadding.top &&
1178 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001179 }
1180
1181 /**
Romain Guy0a637162009-05-29 14:43:54 -07001182 * Enables fast scrolling by letting the user quickly scroll through lists by
1183 * dragging the fast scroll thumb. The adapter attached to the list may want
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184 * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
Romain Guy0a637162009-05-29 14:43:54 -07001185 * jump between sections of the list.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 * @see SectionIndexer
1187 * @see #isFastScrollEnabled()
1188 * @param enabled whether or not to enable fast scrolling
1189 */
1190 public void setFastScrollEnabled(boolean enabled) {
1191 mFastScrollEnabled = enabled;
1192 if (enabled) {
1193 if (mFastScroller == null) {
1194 mFastScroller = new FastScroller(getContext(), this);
1195 }
1196 } else {
1197 if (mFastScroller != null) {
1198 mFastScroller.stop();
1199 mFastScroller = null;
1200 }
1201 }
1202 }
Romain Guy0a637162009-05-29 14:43:54 -07001203
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 /**
Adam Powell20232d02010-12-08 21:08:53 -08001205 * Set whether or not the fast scroller should always be shown in place of the
1206 * standard scrollbars. Fast scrollers shown in this way will not fade out and will
1207 * be a permanent fixture within the list. Best combined with an inset scroll bar style
1208 * that will ensure enough padding. This will enable fast scrolling if it is not
1209 * already enabled.
1210 *
1211 * @param alwaysShow true if the fast scroller should always be displayed.
1212 * @see #setScrollBarStyle(int)
1213 * @see #setFastScrollEnabled(boolean)
1214 */
1215 public void setFastScrollAlwaysVisible(boolean alwaysShow) {
1216 if (alwaysShow && !mFastScrollEnabled) {
1217 setFastScrollEnabled(true);
1218 }
1219
1220 if (mFastScroller != null) {
1221 mFastScroller.setAlwaysShow(alwaysShow);
1222 }
1223
1224 computeOpaqueFlags();
1225 recomputePadding();
1226 }
1227
1228 /**
1229 * Returns true if the fast scroller is set to always show on this view rather than
1230 * fade out when not in use.
1231 *
1232 * @return true if the fast scroller will always show.
1233 * @see #setFastScrollAlwaysVisible(boolean)
1234 */
1235 public boolean isFastScrollAlwaysVisible() {
1236 return mFastScrollEnabled && mFastScroller.isAlwaysShowEnabled();
1237 }
1238
1239 @Override
1240 public int getVerticalScrollbarWidth() {
Adam Powell16bb80a2010-12-09 23:30:50 -08001241 if (isFastScrollAlwaysVisible()) {
Adam Powell20232d02010-12-08 21:08:53 -08001242 return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth());
1243 }
1244 return super.getVerticalScrollbarWidth();
1245 }
1246
1247 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 * Returns the current state of the fast scroll feature.
1249 * @see #setFastScrollEnabled(boolean)
1250 * @return true if fast scroll is enabled, false otherwise
1251 */
1252 @ViewDebug.ExportedProperty
1253 public boolean isFastScrollEnabled() {
1254 return mFastScrollEnabled;
1255 }
Romain Guy0a637162009-05-29 14:43:54 -07001256
Adam Powell20232d02010-12-08 21:08:53 -08001257 @Override
1258 public void setVerticalScrollbarPosition(int position) {
1259 super.setVerticalScrollbarPosition(position);
1260 if (mFastScroller != null) {
1261 mFastScroller.setScrollbarPosition(position);
1262 }
1263 }
1264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001265 /**
1266 * If fast scroll is visible, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001267 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268 */
1269 @Override
1270 protected boolean isVerticalScrollBarHidden() {
1271 return mFastScroller != null && mFastScroller.isVisible();
1272 }
1273
1274 /**
1275 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1276 * is computed based on the number of visible pixels in the visible items. This
1277 * however assumes that all list items have the same height. If you use a list in
1278 * which items have different heights, the scrollbar will change appearance as the
1279 * user scrolls through the list. To avoid this issue, you need to disable this
1280 * property.
1281 *
1282 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1283 * is based solely on the number of items in the adapter and the position of the
1284 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001285 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001286 *
1287 * @param enabled Whether or not to enable smooth scrollbar.
1288 *
Romain Guy0a637162009-05-29 14:43:54 -07001289 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1291 */
1292 public void setSmoothScrollbarEnabled(boolean enabled) {
1293 mSmoothScrollbarEnabled = enabled;
1294 }
1295
1296 /**
1297 * Returns the current state of the fast scroll feature.
1298 *
1299 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1300 *
1301 * @see #setSmoothScrollbarEnabled(boolean)
1302 */
1303 @ViewDebug.ExportedProperty
1304 public boolean isSmoothScrollbarEnabled() {
1305 return mSmoothScrollbarEnabled;
1306 }
1307
1308 /**
1309 * Set the listener that will receive notifications every time the list scrolls.
1310 *
1311 * @param l the scroll listener
1312 */
1313 public void setOnScrollListener(OnScrollListener l) {
1314 mOnScrollListener = l;
1315 invokeOnItemScrollListener();
1316 }
1317
1318 /**
1319 * Notify our scroll listener (if there is one) of a change in scroll state
1320 */
1321 void invokeOnItemScrollListener() {
1322 if (mFastScroller != null) {
1323 mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1324 }
1325 if (mOnScrollListener != null) {
1326 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1327 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001328 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001329 }
1330
Svetoslav Ganova0156172011-06-26 17:55:44 -07001331 @Override
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001332 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001333 if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
1334 switch(direction) {
1335 case ACCESSIBILITY_FOCUS_BACKWARD: {
1336 View focusable = (getChildCount() > 0) ? getChildAt(getChildCount() - 1) : this;
Svetoslav Ganova90e4512012-06-01 19:02:32 -07001337 if (focusable.isAccessibilityFocusable()) {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001338 views.add(focusable);
1339 }
1340 } return;
1341 case ACCESSIBILITY_FOCUS_FORWARD: {
Svetoslav Ganova90e4512012-06-01 19:02:32 -07001342 if (isAccessibilityFocusable()) {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001343 views.add(this);
1344 }
1345 } return;
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001346 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001347 }
Svetoslav Ganovb552d892012-06-02 14:35:02 -07001348 super.addFocusables(views, direction, focusableMode);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001349 }
1350
1351 @Override
1352 public View focusSearch(int direction) {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001353 return focusSearch(this, direction);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001354 }
1355
1356 @Override
1357 public View focusSearch(View focused, int direction) {
1358 switch (direction) {
1359 case ACCESSIBILITY_FOCUS_FORWARD: {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001360 // If we are the focused view try giving it to the first child.
1361 if (focused == this) {
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001362 final int childCount = getChildCount();
1363 for (int i = 0; i < childCount; i++) {
1364 View child = getChildAt(i);
1365 if (child.getVisibility() == View.VISIBLE) {
1366 return child;
1367 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001368 }
Svetoslav Ganovf76a83c2012-05-21 15:32:17 -07001369 return super.focusSearch(this, direction);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001370 }
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001371 // Find the item that has the focused view.
1372 final int currentPosition = getPositionForView(focused);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001373 if (currentPosition < 0 || currentPosition >= getCount()) {
Svetoslav Ganovf76a83c2012-05-21 15:32:17 -07001374 return super.focusSearch(this, direction);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001375 }
1376 // Try to advance focus in the current item.
1377 View currentItem = getChildAt(currentPosition - getFirstVisiblePosition());
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001378 if (currentItem.getVisibility() == View.VISIBLE) {
1379 if (currentItem instanceof ViewGroup) {
1380 ViewGroup currentItemGroup = (ViewGroup) currentItem;
1381 View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup,
1382 focused, direction);
1383 if (nextFocus != null && nextFocus != currentItemGroup
1384 && nextFocus != focused) {
1385 return nextFocus;
1386 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001387 }
1388 }
1389 // Try to move focus to the next item.
1390 final int nextPosition = currentPosition - getFirstVisiblePosition() + 1;
Svetoslav Ganov3016c1a2012-06-04 19:47:40 -07001391 for (int i = nextPosition; i < getChildCount(); i++) {
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001392 View child = getChildAt(i);
1393 if (child.getVisibility() == View.VISIBLE) {
1394 return child;
1395 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001396 }
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001397 // No next item start searching from the list.
1398 return super.focusSearch(this, direction);
Svetoslav Ganovf76a83c2012-05-21 15:32:17 -07001399 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001400 case ACCESSIBILITY_FOCUS_BACKWARD: {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001401 // If we are the focused search from the view that is
1402 // as closer to the bottom as possible.
1403 if (focused == this) {
1404 final int childCount = getChildCount();
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001405 for (int i = childCount - 1; i >= 0; i--) {
1406 View child = getChildAt(i);
1407 if (child.getVisibility() == View.VISIBLE) {
1408 return super.focusSearch(child, direction);
1409 }
Svetoslav Ganovf76a83c2012-05-21 15:32:17 -07001410 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001411 return super.focusSearch(this, direction);
1412 }
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001413 // Find the item that has the focused view.
1414 final int currentPosition = getPositionForView(focused);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001415 if (currentPosition < 0 || currentPosition >= getCount()) {
Svetoslav Ganovf76a83c2012-05-21 15:32:17 -07001416 return super.focusSearch(this, direction);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001417 }
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001418
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001419 View currentItem = getChildAt(currentPosition - getFirstVisiblePosition());
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001420
1421 // If a list item is the focused view we try to find a view
1422 // in the previous item since in reverse the item contents
1423 // get accessibility focus before the item itself.
1424 if (currentItem == focused) {
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001425 currentItem = null;
1426 focused = null;
1427 // This list gets accessibility focus after the last item.
1428 final int previousPosition = currentPosition - getFirstVisiblePosition() - 1;
1429 for (int i = previousPosition; i >= 0; i--) {
1430 View child = getChildAt(i);
1431 if (child.getVisibility() == View.VISIBLE) {
1432 currentItem = child;
1433 break;
1434 }
1435 }
1436 if (currentItem == null) {
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001437 return this;
1438 }
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001439 }
1440
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001441 if (currentItem.getVisibility() == View.VISIBLE) {
1442 // Search into the item.
1443 if (currentItem instanceof ViewGroup) {
1444 ViewGroup currentItemGroup = (ViewGroup) currentItem;
1445 View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup,
1446 focused, direction);
1447 if (nextFocus != null && nextFocus != currentItemGroup
1448 && nextFocus != focused) {
1449 return nextFocus;
1450 }
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001451 }
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001452
1453 // If not item content wants focus we give it to the item.
1454 return currentItem;
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001455 }
Svetoslav Ganov2dda4682012-05-31 20:07:20 -07001456
Svetoslav Ganovb1fa98b2012-06-03 21:45:16 -07001457 return super.focusSearch(this, direction);
Svetoslav Ganove5dfa47d2012-05-08 15:58:32 -07001458 }
1459 }
1460 return super.focusSearch(focused, direction);
1461 }
1462
1463 /**
1464 * @hide
1465 */
1466 @Override
1467 public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
1468 final int position = getPositionForView(child);
1469 if (position != INVALID_POSITION) {
1470 return getChildAt(position - mFirstPosition);
1471 }
1472 return super.findViewToTakeAccessibilityFocusFromHover(child, descendant);
1473 }
1474
1475 @Override
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001476 public void sendAccessibilityEvent(int eventType) {
1477 // Since this class calls onScrollChanged even if the mFirstPosition and the
1478 // child count have not changed we will avoid sending duplicate accessibility
1479 // events.
1480 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001481 final int firstVisiblePosition = getFirstVisiblePosition();
1482 final int lastVisiblePosition = getLastVisiblePosition();
1483 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1484 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001485 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001486 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001487 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1488 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001489 }
1490 }
1491 super.sendAccessibilityEvent(eventType);
1492 }
1493
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001494 @Override
1495 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1496 super.onInitializeAccessibilityEvent(event);
1497 event.setClassName(AbsListView.class.getName());
1498 }
1499
1500 @Override
1501 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1502 super.onInitializeAccessibilityNodeInfo(info);
1503 info.setClassName(AbsListView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001504 if (isEnabled()) {
1505 if (getFirstVisiblePosition() > 0) {
1506 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1507 }
1508 if (getLastVisiblePosition() < getCount() - 1) {
1509 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1510 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001511 }
1512 }
1513
1514 @Override
1515 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001516 if (super.performAccessibilityAction(action, arguments)) {
1517 return true;
1518 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001519 switch (action) {
1520 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001521 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001522 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1523 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1524 return true;
1525 }
1526 } return false;
1527 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001528 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001529 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1530 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1531 return true;
1532 }
1533 } return false;
1534 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001535 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001536 }
1537
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001538 /**
1539 * Indicates whether the children's drawing cache is used during a scroll.
1540 * By default, the drawing cache is enabled but this will consume more memory.
1541 *
1542 * @return true if the scrolling cache is enabled, false otherwise
1543 *
1544 * @see #setScrollingCacheEnabled(boolean)
1545 * @see View#setDrawingCacheEnabled(boolean)
1546 */
1547 @ViewDebug.ExportedProperty
1548 public boolean isScrollingCacheEnabled() {
1549 return mScrollingCacheEnabled;
1550 }
1551
1552 /**
1553 * Enables or disables the children's drawing cache during a scroll.
1554 * By default, the drawing cache is enabled but this will use more memory.
1555 *
1556 * When the scrolling cache is enabled, the caches are kept after the
1557 * first scrolling. You can manually clear the cache by calling
1558 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1559 *
1560 * @param enabled true to enable the scroll cache, false otherwise
1561 *
1562 * @see #isScrollingCacheEnabled()
1563 * @see View#setDrawingCacheEnabled(boolean)
1564 */
1565 public void setScrollingCacheEnabled(boolean enabled) {
1566 if (mScrollingCacheEnabled && !enabled) {
1567 clearScrollingCache();
1568 }
1569 mScrollingCacheEnabled = enabled;
1570 }
1571
1572 /**
1573 * Enables or disables the type filter window. If enabled, typing when
1574 * this view has focus will filter the children to match the users input.
1575 * Note that the {@link Adapter} used by this view must implement the
1576 * {@link Filterable} interface.
1577 *
1578 * @param textFilterEnabled true to enable type filtering, false otherwise
1579 *
1580 * @see Filterable
1581 */
1582 public void setTextFilterEnabled(boolean textFilterEnabled) {
1583 mTextFilterEnabled = textFilterEnabled;
1584 }
1585
1586 /**
1587 * Indicates whether type filtering is enabled for this view
1588 *
1589 * @return true if type filtering is enabled, false otherwise
1590 *
1591 * @see #setTextFilterEnabled(boolean)
1592 * @see Filterable
1593 */
1594 @ViewDebug.ExportedProperty
1595 public boolean isTextFilterEnabled() {
1596 return mTextFilterEnabled;
1597 }
1598
1599 @Override
1600 public void getFocusedRect(Rect r) {
1601 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001602 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001603 // the focused rectangle of the selected view offset into the
1604 // coordinate space of this view.
1605 view.getFocusedRect(r);
1606 offsetDescendantRectToMyCoords(view, r);
1607 } else {
1608 // otherwise, just the norm
1609 super.getFocusedRect(r);
1610 }
1611 }
1612
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001613 private void useDefaultSelector() {
1614 setSelector(getResources().getDrawable(
1615 com.android.internal.R.drawable.list_selector_background));
1616 }
1617
1618 /**
1619 * Indicates whether the content of this view is pinned to, or stacked from,
1620 * the bottom edge.
1621 *
1622 * @return true if the content is stacked from the bottom edge, false otherwise
1623 */
1624 @ViewDebug.ExportedProperty
1625 public boolean isStackFromBottom() {
1626 return mStackFromBottom;
1627 }
1628
1629 /**
1630 * When stack from bottom is set to true, the list fills its content starting from
1631 * the bottom of the view.
1632 *
1633 * @param stackFromBottom true to pin the view's content to the bottom edge,
1634 * false to pin the view's content to the top edge
1635 */
1636 public void setStackFromBottom(boolean stackFromBottom) {
1637 if (mStackFromBottom != stackFromBottom) {
1638 mStackFromBottom = stackFromBottom;
1639 requestLayoutIfNecessary();
1640 }
1641 }
1642
1643 void requestLayoutIfNecessary() {
1644 if (getChildCount() > 0) {
1645 resetList();
1646 requestLayout();
1647 invalidate();
1648 }
1649 }
1650
1651 static class SavedState extends BaseSavedState {
1652 long selectedId;
1653 long firstId;
1654 int viewTop;
1655 int position;
1656 int height;
1657 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001658 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001659 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001660 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001661 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001662
1663 /**
1664 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1665 */
1666 SavedState(Parcelable superState) {
1667 super(superState);
1668 }
1669
1670 /**
1671 * Constructor called from {@link #CREATOR}
1672 */
1673 private SavedState(Parcel in) {
1674 super(in);
1675 selectedId = in.readLong();
1676 firstId = in.readLong();
1677 viewTop = in.readInt();
1678 position = in.readInt();
1679 height = in.readInt();
1680 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001681 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001682 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001683 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001684 final int N = in.readInt();
1685 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001686 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001687 for (int i=0; i<N; i++) {
1688 final long key = in.readLong();
1689 final int value = in.readInt();
1690 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001691 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001692 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 }
1694
1695 @Override
1696 public void writeToParcel(Parcel out, int flags) {
1697 super.writeToParcel(out, flags);
1698 out.writeLong(selectedId);
1699 out.writeLong(firstId);
1700 out.writeInt(viewTop);
1701 out.writeInt(position);
1702 out.writeInt(height);
1703 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001704 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001705 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001706 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001707 final int N = checkIdState != null ? checkIdState.size() : 0;
1708 out.writeInt(N);
1709 for (int i=0; i<N; i++) {
1710 out.writeLong(checkIdState.keyAt(i));
1711 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001712 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001713 }
1714
1715 @Override
1716 public String toString() {
1717 return "AbsListView.SavedState{"
1718 + Integer.toHexString(System.identityHashCode(this))
1719 + " selectedId=" + selectedId
1720 + " firstId=" + firstId
1721 + " viewTop=" + viewTop
1722 + " position=" + position
1723 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001724 + " filter=" + filter
1725 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001726 }
1727
1728 public static final Parcelable.Creator<SavedState> CREATOR
1729 = new Parcelable.Creator<SavedState>() {
1730 public SavedState createFromParcel(Parcel in) {
1731 return new SavedState(in);
1732 }
1733
1734 public SavedState[] newArray(int size) {
1735 return new SavedState[size];
1736 }
1737 };
1738 }
1739
1740 @Override
1741 public Parcelable onSaveInstanceState() {
1742 /*
1743 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001744 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001745 * that happen early enough to keep from getting complaints
1746 * about having leaked the window.
1747 */
1748 dismissPopup();
1749
1750 Parcelable superState = super.onSaveInstanceState();
1751
1752 SavedState ss = new SavedState(superState);
1753
Dianne Hackborn99441c42010-12-15 11:02:55 -08001754 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001755 long selectedId = getSelectedItemId();
1756 ss.selectedId = selectedId;
1757 ss.height = getHeight();
1758
1759 if (selectedId >= 0) {
1760 // Remember the selection
1761 ss.viewTop = mSelectedTop;
1762 ss.position = getSelectedItemPosition();
1763 ss.firstId = INVALID_POSITION;
1764 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001765 if (haveChildren && mFirstPosition > 0) {
1766 // Remember the position of the first child.
1767 // We only do this if we are not currently at the top of
1768 // the list, for two reasons:
1769 // (1) The list may be in the process of becoming empty, in
1770 // which case mItemCount may not be 0, but if we try to
1771 // ask for any information about position 0 we will crash.
1772 // (2) Being "at the top" seems like a special case, anyway,
1773 // and the user wouldn't expect to end up somewhere else when
1774 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001775 View v = getChildAt(0);
1776 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001777 int firstPos = mFirstPosition;
1778 if (firstPos >= mItemCount) {
1779 firstPos = mItemCount - 1;
1780 }
1781 ss.position = firstPos;
1782 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001783 } else {
1784 ss.viewTop = 0;
1785 ss.firstId = INVALID_POSITION;
1786 ss.position = 0;
1787 }
1788 }
1789
1790 ss.filter = null;
1791 if (mFiltered) {
1792 final EditText textFilter = mTextFilter;
1793 if (textFilter != null) {
1794 Editable filterText = textFilter.getText();
1795 if (filterText != null) {
1796 ss.filter = filterText.toString();
1797 }
1798 }
1799 }
1800
Adam Powella0eeeac2010-11-05 11:55:05 -07001801 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1802
Adam Powell9a5cc282011-08-28 16:18:16 -07001803 if (mCheckStates != null) {
1804 ss.checkState = mCheckStates.clone();
1805 }
1806 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001807 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001808 final int count = mCheckedIdStates.size();
1809 for (int i = 0; i < count; i++) {
1810 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1811 }
1812 ss.checkIdState = idState;
1813 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001814 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001815
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001816 return ss;
1817 }
1818
1819 @Override
1820 public void onRestoreInstanceState(Parcelable state) {
1821 SavedState ss = (SavedState) state;
1822
1823 super.onRestoreInstanceState(ss.getSuperState());
1824 mDataChanged = true;
1825
1826 mSyncHeight = ss.height;
1827
1828 if (ss.selectedId >= 0) {
1829 mNeedSync = true;
1830 mSyncRowId = ss.selectedId;
1831 mSyncPosition = ss.position;
1832 mSpecificTop = ss.viewTop;
1833 mSyncMode = SYNC_SELECTED_POSITION;
1834 } else if (ss.firstId >= 0) {
1835 setSelectedPositionInt(INVALID_POSITION);
1836 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1837 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001838 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001839 mNeedSync = true;
1840 mSyncRowId = ss.firstId;
1841 mSyncPosition = ss.position;
1842 mSpecificTop = ss.viewTop;
1843 mSyncMode = SYNC_FIRST_POSITION;
1844 }
1845
1846 setFilterText(ss.filter);
1847
Adam Powellf343e1b2010-08-13 18:27:04 -07001848 if (ss.checkState != null) {
1849 mCheckStates = ss.checkState;
1850 }
1851
1852 if (ss.checkIdState != null) {
1853 mCheckedIdStates = ss.checkIdState;
1854 }
1855
Adam Powell2614c6c2010-11-04 17:54:45 -07001856 mCheckedItemCount = ss.checkedItemCount;
1857
Adam Powella0eeeac2010-11-05 11:55:05 -07001858 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1859 mMultiChoiceModeCallback != null) {
1860 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1861 }
1862
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001863 requestLayout();
1864 }
1865
1866 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001867 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1868 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001869 }
1870
1871 /**
1872 * Sets the initial value for the text filter.
1873 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001874 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001875 * @see #setTextFilterEnabled
1876 */
1877 public void setFilterText(String filterText) {
1878 // TODO: Should we check for acceptFilter()?
1879 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1880 createTextFilter(false);
1881 // This is going to call our listener onTextChanged, but we might not
1882 // be ready to bring up a window yet
1883 mTextFilter.setText(filterText);
1884 mTextFilter.setSelection(filterText.length());
1885 if (mAdapter instanceof Filterable) {
1886 // if mPopup is non-null, then onTextChanged will do the filtering
1887 if (mPopup == null) {
1888 Filter f = ((Filterable) mAdapter).getFilter();
1889 f.filter(filterText);
1890 }
1891 // Set filtered to true so we will display the filter window when our main
1892 // window is ready
1893 mFiltered = true;
1894 mDataSetObserver.clearSavedState();
1895 }
1896 }
1897 }
1898
1899 /**
Romain Guy0a637162009-05-29 14:43:54 -07001900 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001901 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001902 */
1903 public CharSequence getTextFilter() {
1904 if (mTextFilterEnabled && mTextFilter != null) {
1905 return mTextFilter.getText();
1906 }
1907 return null;
1908 }
Romain Guy0a637162009-05-29 14:43:54 -07001909
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001910 @Override
1911 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1912 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1913 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powellb3750132011-08-08 23:29:12 -07001914 if (!mIsAttached && mAdapter != null) {
1915 // Data may have changed while we were detached and it's valid
1916 // to change focus while detached. Refresh so we don't die.
1917 mDataChanged = true;
1918 mOldItemCount = mItemCount;
1919 mItemCount = mAdapter.getCount();
1920 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001921 resurrectSelection();
1922 }
1923 }
1924
1925 @Override
1926 public void requestLayout() {
1927 if (!mBlockLayoutRequests && !mInLayout) {
1928 super.requestLayout();
1929 }
1930 }
1931
1932 /**
1933 * The list is empty. Clear everything out.
1934 */
1935 void resetList() {
1936 removeAllViewsInLayout();
1937 mFirstPosition = 0;
1938 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001939 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001940 mNeedSync = false;
1941 mOldSelectedPosition = INVALID_POSITION;
1942 mOldSelectedRowId = INVALID_ROW_ID;
1943 setSelectedPositionInt(INVALID_POSITION);
1944 setNextSelectedPositionInt(INVALID_POSITION);
1945 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07001946 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001947 mSelectorRect.setEmpty();
1948 invalidate();
1949 }
1950
1951 @Override
1952 protected int computeVerticalScrollExtent() {
1953 final int count = getChildCount();
1954 if (count > 0) {
1955 if (mSmoothScrollbarEnabled) {
1956 int extent = count * 100;
1957
1958 View view = getChildAt(0);
1959 final int top = view.getTop();
1960 int height = view.getHeight();
1961 if (height > 0) {
1962 extent += (top * 100) / height;
1963 }
1964
1965 view = getChildAt(count - 1);
1966 final int bottom = view.getBottom();
1967 height = view.getHeight();
1968 if (height > 0) {
1969 extent -= ((bottom - getHeight()) * 100) / height;
1970 }
1971
1972 return extent;
1973 } else {
1974 return 1;
1975 }
1976 }
1977 return 0;
1978 }
1979
1980 @Override
1981 protected int computeVerticalScrollOffset() {
1982 final int firstPosition = mFirstPosition;
1983 final int childCount = getChildCount();
1984 if (firstPosition >= 0 && childCount > 0) {
1985 if (mSmoothScrollbarEnabled) {
1986 final View view = getChildAt(0);
1987 final int top = view.getTop();
1988 int height = view.getHeight();
1989 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08001990 return Math.max(firstPosition * 100 - (top * 100) / height +
1991 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001992 }
1993 } else {
1994 int index;
1995 final int count = mItemCount;
1996 if (firstPosition == 0) {
1997 index = 0;
1998 } else if (firstPosition + childCount == count) {
1999 index = count;
2000 } else {
2001 index = firstPosition + childCount / 2;
2002 }
2003 return (int) (firstPosition + childCount * (index / (float) count));
2004 }
2005 }
2006 return 0;
2007 }
2008
2009 @Override
2010 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08002011 int result;
2012 if (mSmoothScrollbarEnabled) {
2013 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07002014 if (mScrollY != 0) {
2015 // Compensate for overscroll
2016 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2017 }
Adam Powell0b8bb422010-02-08 14:30:45 -08002018 } else {
2019 result = mItemCount;
2020 }
2021 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002022 }
2023
2024 @Override
2025 protected float getTopFadingEdgeStrength() {
2026 final int count = getChildCount();
2027 final float fadeEdge = super.getTopFadingEdgeStrength();
2028 if (count == 0) {
2029 return fadeEdge;
2030 } else {
2031 if (mFirstPosition > 0) {
2032 return 1.0f;
2033 }
2034
2035 final int top = getChildAt(0).getTop();
2036 final float fadeLength = (float) getVerticalFadingEdgeLength();
2037 return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
2038 }
2039 }
2040
2041 @Override
2042 protected float getBottomFadingEdgeStrength() {
2043 final int count = getChildCount();
2044 final float fadeEdge = super.getBottomFadingEdgeStrength();
2045 if (count == 0) {
2046 return fadeEdge;
2047 } else {
2048 if (mFirstPosition + count - 1 < mItemCount - 1) {
2049 return 1.0f;
2050 }
2051
2052 final int bottom = getChildAt(count - 1).getBottom();
2053 final int height = getHeight();
2054 final float fadeLength = (float) getVerticalFadingEdgeLength();
2055 return bottom > height - mPaddingBottom ?
2056 (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
2057 }
2058 }
2059
2060 @Override
2061 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2062 if (mSelector == null) {
2063 useDefaultSelector();
2064 }
2065 final Rect listPadding = mListPadding;
2066 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2067 listPadding.top = mSelectionTopPadding + mPaddingTop;
2068 listPadding.right = mSelectionRightPadding + mPaddingRight;
2069 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08002070
2071 // Check if our previous measured size was at a point where we should scroll later.
2072 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2073 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07002074 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08002075 final View lastChild = getChildAt(childCount - 1);
2076 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07002077 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08002078 lastBottom <= listBottom;
2079 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002080 }
2081
Romain Guyd6a463a2009-05-21 23:10:10 -07002082 /**
2083 * Subclasses should NOT override this method but
2084 * {@link #layoutChildren()} instead.
2085 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002086 @Override
2087 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2088 super.onLayout(changed, l, t, r, b);
2089 mInLayout = true;
Adam Powellf3c2eda2010-03-16 17:31:01 -07002090 if (changed) {
2091 int childCount = getChildCount();
2092 for (int i = 0; i < childCount; i++) {
2093 getChildAt(i).forceLayout();
2094 }
2095 mRecycler.markChildrenDirty();
2096 }
Adam Powell2c6196a2010-12-10 14:31:54 -08002097
2098 if (mFastScroller != null && mItemCount != mOldItemCount) {
2099 mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
2100 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07002101
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002102 layoutChildren();
2103 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07002104
2105 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002106 }
2107
2108 /**
2109 * @hide
2110 */
2111 @Override
2112 protected boolean setFrame(int left, int top, int right, int bottom) {
2113 final boolean changed = super.setFrame(left, top, right, bottom);
2114
Romain Guyd6a463a2009-05-21 23:10:10 -07002115 if (changed) {
2116 // Reposition the popup when the frame has changed. This includes
2117 // translating the widget, not just changing its dimension. The
2118 // filter popup needs to follow the widget.
2119 final boolean visible = getWindowVisibility() == View.VISIBLE;
2120 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2121 positionPopup();
2122 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002123 }
2124
2125 return changed;
2126 }
2127
Romain Guyd6a463a2009-05-21 23:10:10 -07002128 /**
2129 * Subclasses must override this method to layout their children.
2130 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002131 protected void layoutChildren() {
2132 }
2133
2134 void updateScrollIndicators() {
2135 if (mScrollUp != null) {
2136 boolean canScrollUp;
2137 // 0th element is not visible
2138 canScrollUp = mFirstPosition > 0;
2139
2140 // ... Or top of 0th element is not visible
2141 if (!canScrollUp) {
2142 if (getChildCount() > 0) {
2143 View child = getChildAt(0);
2144 canScrollUp = child.getTop() < mListPadding.top;
2145 }
2146 }
2147
2148 mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
2149 }
2150
2151 if (mScrollDown != null) {
2152 boolean canScrollDown;
2153 int count = getChildCount();
2154
2155 // Last item is not visible
2156 canScrollDown = (mFirstPosition + count) < mItemCount;
2157
2158 // ... Or bottom of the last element is not visible
2159 if (!canScrollDown && count > 0) {
2160 View child = getChildAt(count - 1);
2161 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2162 }
2163
2164 mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
2165 }
2166 }
2167
2168 @Override
2169 @ViewDebug.ExportedProperty
2170 public View getSelectedView() {
2171 if (mItemCount > 0 && mSelectedPosition >= 0) {
2172 return getChildAt(mSelectedPosition - mFirstPosition);
2173 } else {
2174 return null;
2175 }
2176 }
2177
2178 /**
2179 * List padding is the maximum of the normal view's padding and the padding of the selector.
2180 *
2181 * @see android.view.View#getPaddingTop()
2182 * @see #getSelector()
2183 *
2184 * @return The top list padding.
2185 */
2186 public int getListPaddingTop() {
2187 return mListPadding.top;
2188 }
2189
2190 /**
2191 * List padding is the maximum of the normal view's padding and the padding of the selector.
2192 *
2193 * @see android.view.View#getPaddingBottom()
2194 * @see #getSelector()
2195 *
2196 * @return The bottom list padding.
2197 */
2198 public int getListPaddingBottom() {
2199 return mListPadding.bottom;
2200 }
2201
2202 /**
2203 * List padding is the maximum of the normal view's padding and the padding of the selector.
2204 *
2205 * @see android.view.View#getPaddingLeft()
2206 * @see #getSelector()
2207 *
2208 * @return The left list padding.
2209 */
2210 public int getListPaddingLeft() {
2211 return mListPadding.left;
2212 }
2213
2214 /**
2215 * List padding is the maximum of the normal view's padding and the padding of the selector.
2216 *
2217 * @see android.view.View#getPaddingRight()
2218 * @see #getSelector()
2219 *
2220 * @return The right list padding.
2221 */
2222 public int getListPaddingRight() {
2223 return mListPadding.right;
2224 }
2225
2226 /**
2227 * Get a view and have it show the data associated with the specified
2228 * position. This is called when we have already discovered that the view is
2229 * not available for reuse in the recycle bin. The only choices left are
2230 * converting an old view or making a new one.
2231 *
2232 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002233 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2234 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002235 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002236 * @return A view displaying the data associated with the specified position
2237 */
Romain Guy21875052010-01-06 18:48:08 -08002238 View obtainView(int position, boolean[] isScrap) {
2239 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002240 View scrapView;
2241
Adam Powell539ee872012-02-03 19:00:49 -08002242 scrapView = mRecycler.getTransientStateView(position);
2243 if (scrapView != null) {
2244 return scrapView;
2245 }
2246
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002247 scrapView = mRecycler.getScrapView(position);
2248
2249 View child;
2250 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002251 child = mAdapter.getView(position, scrapView, this);
2252
Svetoslav Ganov42138042012-03-20 11:51:39 -07002253 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2254 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2255 }
2256
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002257 if (child != scrapView) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002258 mRecycler.addScrapView(scrapView, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002259 if (mCacheColorHint != 0) {
2260 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2261 }
Romain Guy21875052010-01-06 18:48:08 -08002262 } else {
Romain Guya440b002010-02-24 15:57:54 -08002263 isScrap[0] = true;
2264 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002265 }
2266 } else {
2267 child = mAdapter.getView(position, null, this);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002268
2269 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2270 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2271 }
2272
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002273 if (mCacheColorHint != 0) {
2274 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2275 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002276 }
2277
Adam Powellaebd28f2012-02-22 10:31:16 -08002278 if (mAdapterHasStableIds) {
2279 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2280 LayoutParams lp;
2281 if (vlp == null) {
2282 lp = (LayoutParams) generateDefaultLayoutParams();
2283 } else if (!checkLayoutParams(vlp)) {
2284 lp = (LayoutParams) generateLayoutParams(vlp);
2285 } else {
2286 lp = (LayoutParams) vlp;
2287 }
2288 lp.itemId = mAdapter.getItemId(position);
2289 child.setLayoutParams(lp);
2290 }
2291
alanvc1d7e772012-05-08 14:47:24 -07002292 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2293 if (mAccessibilityDelegate == null) {
2294 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2295 }
2296 child.setAccessibilityDelegate(mAccessibilityDelegate);
2297 }
2298
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002299 return child;
2300 }
2301
alanvc1d7e772012-05-08 14:47:24 -07002302 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2303 @Override
2304 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2305 super.onInitializeAccessibilityNodeInfo(host, info);
2306
2307 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002308 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002309
alanv9c3e0e62012-05-18 17:43:35 -07002310 if ((position == INVALID_POSITION) || (adapter == null)) {
alanvc1d7e772012-05-08 14:47:24 -07002311 return;
2312 }
2313
alanv9c3e0e62012-05-18 17:43:35 -07002314 if (!isEnabled() || !adapter.isEnabled(position)) {
alanv9c3e0e62012-05-18 17:43:35 -07002315 return;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002316 }
alanvc1d7e772012-05-08 14:47:24 -07002317
2318 if (position == getSelectedItemPosition()) {
2319 info.setSelected(true);
alanv9c3e0e62012-05-18 17:43:35 -07002320 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
2321 } else {
2322 info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
alanvc1d7e772012-05-08 14:47:24 -07002323 }
alanv9c3e0e62012-05-18 17:43:35 -07002324
2325 if (isClickable()) {
2326 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2327 info.setClickable(true);
2328 }
2329
2330 if (isLongClickable()) {
2331 info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
2332 info.setLongClickable(true);
2333 }
2334
alanvc1d7e772012-05-08 14:47:24 -07002335 }
2336
2337 @Override
2338 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002339 if (super.performAccessibilityAction(host, action, arguments)) {
2340 return true;
2341 }
2342
alanvc1d7e772012-05-08 14:47:24 -07002343 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002344 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002345
alanv9c3e0e62012-05-18 17:43:35 -07002346 if ((position == INVALID_POSITION) || (adapter == null)) {
2347 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002348 return false;
2349 }
2350
alanv9c3e0e62012-05-18 17:43:35 -07002351 if (!isEnabled() || !adapter.isEnabled(position)) {
2352 // Cannot perform actions on disabled items.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002353 return false;
2354 }
2355
alanvc1d7e772012-05-08 14:47:24 -07002356 final long id = getItemIdAtPosition(position);
2357
2358 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002359 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2360 if (getSelectedItemPosition() == position) {
2361 setSelection(INVALID_POSITION);
2362 return true;
2363 }
2364 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002365 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002366 if (getSelectedItemPosition() != position) {
2367 setSelection(position);
2368 return true;
2369 }
2370 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002371 case AccessibilityNodeInfo.ACTION_CLICK: {
2372 if (isClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002373 return performItemClick(host, position, id);
2374 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002375 } return false;
2376 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2377 if (isLongClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002378 return performLongPress(host, position, id);
2379 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002380 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002381 }
2382
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002383 return false;
alanvc1d7e772012-05-08 14:47:24 -07002384 }
2385 }
2386
Dianne Hackborn079e2352010-10-18 17:02:43 -07002387 void positionSelector(int position, View sel) {
2388 if (position != INVALID_POSITION) {
2389 mSelectorPosition = position;
2390 }
2391
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002392 final Rect selectorRect = mSelectorRect;
2393 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002394 if (sel instanceof SelectionBoundsAdjuster) {
2395 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2396 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002397 positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
2398 selectorRect.bottom);
2399
2400 final boolean isChildViewEnabled = mIsChildViewEnabled;
2401 if (sel.isEnabled() != isChildViewEnabled) {
2402 mIsChildViewEnabled = !isChildViewEnabled;
Jason Bayera79f4b72011-03-22 20:03:31 -07002403 if (getSelectedItemPosition() != INVALID_POSITION) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002404 refreshDrawableState();
2405 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002406 }
2407 }
2408
2409 private void positionSelector(int l, int t, int r, int b) {
2410 mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
2411 + mSelectionRightPadding, b + mSelectionBottomPadding);
2412 }
2413
2414 @Override
2415 protected void dispatchDraw(Canvas canvas) {
2416 int saveCount = 0;
2417 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2418 if (clipToPadding) {
2419 saveCount = canvas.save();
2420 final int scrollX = mScrollX;
2421 final int scrollY = mScrollY;
2422 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2423 scrollX + mRight - mLeft - mPaddingRight,
2424 scrollY + mBottom - mTop - mPaddingBottom);
2425 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2426 }
2427
2428 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2429 if (!drawSelectorOnTop) {
2430 drawSelector(canvas);
2431 }
2432
2433 super.dispatchDraw(canvas);
2434
2435 if (drawSelectorOnTop) {
2436 drawSelector(canvas);
2437 }
2438
2439 if (clipToPadding) {
2440 canvas.restoreToCount(saveCount);
2441 mGroupFlags |= CLIP_TO_PADDING_MASK;
2442 }
2443 }
2444
2445 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002446 protected boolean isPaddingOffsetRequired() {
2447 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2448 }
2449
2450 @Override
2451 protected int getLeftPaddingOffset() {
2452 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2453 }
2454
2455 @Override
2456 protected int getTopPaddingOffset() {
2457 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2458 }
2459
2460 @Override
2461 protected int getRightPaddingOffset() {
2462 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2463 }
2464
2465 @Override
2466 protected int getBottomPaddingOffset() {
2467 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2468 }
2469
2470 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002471 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2472 if (getChildCount() > 0) {
2473 mDataChanged = true;
2474 rememberSyncState();
2475 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002476
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002477 if (mFastScroller != null) {
2478 mFastScroller.onSizeChanged(w, h, oldw, oldh);
2479 }
2480 }
2481
2482 /**
2483 * @return True if the current touch mode requires that we draw the selector in the pressed
2484 * state.
2485 */
2486 boolean touchModeDrawsInPressedState() {
2487 // FIXME use isPressed for this
2488 switch (mTouchMode) {
2489 case TOUCH_MODE_TAP:
2490 case TOUCH_MODE_DONE_WAITING:
2491 return true;
2492 default:
2493 return false;
2494 }
2495 }
2496
2497 /**
2498 * Indicates whether this view is in a state where the selector should be drawn. This will
2499 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2500 * the pressed state for an item.
2501 *
2502 * @return True if the selector should be shown
2503 */
2504 boolean shouldShowSelector() {
2505 return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
2506 }
2507
2508 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002509 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002510 final Drawable selector = mSelector;
2511 selector.setBounds(mSelectorRect);
2512 selector.draw(canvas);
2513 }
2514 }
2515
2516 /**
2517 * Controls whether the selection highlight drawable should be drawn on top of the item or
2518 * behind it.
2519 *
2520 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2521 * is false.
2522 *
2523 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2524 */
2525 public void setDrawSelectorOnTop(boolean onTop) {
2526 mDrawSelectorOnTop = onTop;
2527 }
2528
2529 /**
2530 * Set a Drawable that should be used to highlight the currently selected item.
2531 *
2532 * @param resID A Drawable resource to use as the selection highlight.
2533 *
2534 * @attr ref android.R.styleable#AbsListView_listSelector
2535 */
2536 public void setSelector(int resID) {
2537 setSelector(getResources().getDrawable(resID));
2538 }
2539
2540 public void setSelector(Drawable sel) {
2541 if (mSelector != null) {
2542 mSelector.setCallback(null);
2543 unscheduleDrawable(mSelector);
2544 }
2545 mSelector = sel;
2546 Rect padding = new Rect();
2547 sel.getPadding(padding);
2548 mSelectionLeftPadding = padding.left;
2549 mSelectionTopPadding = padding.top;
2550 mSelectionRightPadding = padding.right;
2551 mSelectionBottomPadding = padding.bottom;
2552 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002553 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002554 }
2555
2556 /**
2557 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2558 * selection in the list.
2559 *
2560 * @return the drawable used to display the selector
2561 */
2562 public Drawable getSelector() {
2563 return mSelector;
2564 }
2565
2566 /**
2567 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2568 * this is a long press.
2569 */
2570 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002571 if (!isEnabled() || !isClickable()) {
2572 return;
2573 }
2574
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002575 Drawable selector = mSelector;
2576 Rect selectorRect = mSelectorRect;
2577 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002578 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002579
2580 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2581
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002582 if (v != null) {
2583 if (v.hasFocusable()) return;
2584 v.setPressed(true);
2585 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002586 setPressed(true);
2587
2588 final boolean longClickable = isLongClickable();
2589 Drawable d = selector.getCurrent();
2590 if (d != null && d instanceof TransitionDrawable) {
2591 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002592 ((TransitionDrawable) d).startTransition(
2593 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002594 } else {
2595 ((TransitionDrawable) d).resetTransition();
2596 }
2597 }
2598 if (longClickable && !mDataChanged) {
2599 if (mPendingCheckForKeyLongPress == null) {
2600 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2601 }
2602 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2603 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2604 }
2605 }
2606 }
2607
2608 public void setScrollIndicators(View up, View down) {
2609 mScrollUp = up;
2610 mScrollDown = down;
2611 }
2612
Dianne Hackborn079e2352010-10-18 17:02:43 -07002613 void updateSelectorState() {
2614 if (mSelector != null) {
2615 if (shouldShowSelector()) {
2616 mSelector.setState(getDrawableState());
2617 } else {
2618 mSelector.setState(StateSet.NOTHING);
2619 }
2620 }
2621 }
2622
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002623 @Override
2624 protected void drawableStateChanged() {
2625 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002626 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002627 }
2628
2629 @Override
2630 protected int[] onCreateDrawableState(int extraSpace) {
2631 // If the child view is enabled then do the default behavior.
2632 if (mIsChildViewEnabled) {
2633 // Common case
2634 return super.onCreateDrawableState(extraSpace);
2635 }
2636
2637 // The selector uses this View's drawable state. The selected child view
2638 // is disabled, so we need to remove the enabled state from the drawable
2639 // states.
2640 final int enabledState = ENABLED_STATE_SET[0];
2641
2642 // If we don't have any extra space, it will return one of the static state arrays,
2643 // and clearing the enabled state on those arrays is a bad thing! If we specify
2644 // we need extra space, it will create+copy into a new array that safely mutable.
2645 int[] state = super.onCreateDrawableState(extraSpace + 1);
2646 int enabledPos = -1;
2647 for (int i = state.length - 1; i >= 0; i--) {
2648 if (state[i] == enabledState) {
2649 enabledPos = i;
2650 break;
2651 }
2652 }
2653
2654 // Remove the enabled state
2655 if (enabledPos >= 0) {
2656 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2657 state.length - enabledPos - 1);
2658 }
Romain Guy0a637162009-05-29 14:43:54 -07002659
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002660 return state;
2661 }
2662
2663 @Override
2664 public boolean verifyDrawable(Drawable dr) {
2665 return mSelector == dr || super.verifyDrawable(dr);
2666 }
2667
2668 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002669 public void jumpDrawablesToCurrentState() {
2670 super.jumpDrawablesToCurrentState();
2671 if (mSelector != null) mSelector.jumpToCurrentState();
2672 }
2673
2674 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002675 protected void onAttachedToWindow() {
2676 super.onAttachedToWindow();
2677
2678 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002679 treeObserver.addOnTouchModeChangeListener(this);
2680 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2681 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002682 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002683
Romain Guy82afc7b2010-05-13 11:52:37 -07002684 if (mAdapter != null && mDataSetObserver == null) {
2685 mDataSetObserver = new AdapterDataSetObserver();
2686 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002687
2688 // Data may have changed while we were detached. Refresh.
2689 mDataChanged = true;
2690 mOldItemCount = mItemCount;
2691 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002692 }
Adam Powellb3750132011-08-08 23:29:12 -07002693 mIsAttached = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002694 }
2695
2696 @Override
2697 protected void onDetachedFromWindow() {
2698 super.onDetachedFromWindow();
2699
Romain Guy1f7f3c32009-07-22 11:25:42 -07002700 // Dismiss the popup in case onSaveInstanceState() was not invoked
2701 dismissPopup();
2702
Romain Guy21875052010-01-06 18:48:08 -08002703 // Detach any view left in the scrap heap
2704 mRecycler.clear();
2705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002706 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002707 treeObserver.removeOnTouchModeChangeListener(this);
2708 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002709 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002710 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002711 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002712
2713 if (mAdapter != null) {
2714 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2715 mDataSetObserver = null;
2716 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002717
2718 if (mScrollStrictSpan != null) {
2719 mScrollStrictSpan.finish();
2720 mScrollStrictSpan = null;
2721 }
2722
2723 if (mFlingStrictSpan != null) {
2724 mFlingStrictSpan.finish();
2725 mFlingStrictSpan = null;
2726 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002727
2728 if (mFlingRunnable != null) {
2729 removeCallbacks(mFlingRunnable);
2730 }
2731
2732 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002733 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002734 }
2735
2736 if (mClearScrollingCache != null) {
2737 removeCallbacks(mClearScrollingCache);
2738 }
2739
2740 if (mPerformClick != null) {
2741 removeCallbacks(mPerformClick);
2742 }
2743
2744 if (mTouchModeReset != null) {
2745 removeCallbacks(mTouchModeReset);
2746 mTouchModeReset = null;
2747 }
Adam Powellb3750132011-08-08 23:29:12 -07002748 mIsAttached = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002749 }
2750
2751 @Override
2752 public void onWindowFocusChanged(boolean hasWindowFocus) {
2753 super.onWindowFocusChanged(hasWindowFocus);
2754
2755 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2756
2757 if (!hasWindowFocus) {
2758 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002759 if (mFlingRunnable != null) {
2760 removeCallbacks(mFlingRunnable);
2761 // let the fling runnable report it's new state which
2762 // should be idle
2763 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002764 if (mPositionScroller != null) {
2765 mPositionScroller.stop();
2766 }
Adam Powell45803472010-01-25 15:10:44 -08002767 if (mScrollY != 0) {
2768 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002769 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002770 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002771 invalidate();
2772 }
Mark Wagner670dd812010-01-13 16:17:47 -08002773 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002774 // Always hide the type filter
2775 dismissPopup();
2776
2777 if (touchMode == TOUCH_MODE_OFF) {
2778 // Remember the last selected element
2779 mResurrectToPosition = mSelectedPosition;
2780 }
2781 } else {
Adam Powell97566042010-03-09 15:34:09 -08002782 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002783 // Show the type filter only if a filter is in effect
2784 showPopup();
2785 }
2786
2787 // If we changed touch mode since the last time we had focus
2788 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2789 // If we come back in trackball mode, we bring the selection back
2790 if (touchMode == TOUCH_MODE_OFF) {
2791 // This will trigger a layout
2792 resurrectSelection();
2793
2794 // If we come back in touch mode, then we want to hide the selector
2795 } else {
2796 hideSelector();
2797 mLayoutMode = LAYOUT_NORMAL;
2798 layoutChildren();
2799 }
2800 }
2801 }
2802
2803 mLastTouchMode = touchMode;
2804 }
2805
2806 /**
2807 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
2808 * methods knows the view, position and ID of the item that received the
2809 * long press.
2810 *
2811 * @param view The view that received the long press.
2812 * @param position The position of the item that received the long press.
2813 * @param id The ID of the item that received the long press.
2814 * @return The extra information that should be returned by
2815 * {@link #getContextMenuInfo()}.
2816 */
2817 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
2818 return new AdapterContextMenuInfo(view, position, id);
2819 }
2820
2821 /**
2822 * A base class for Runnables that will check that their view is still attached to
2823 * the original window as when the Runnable was created.
2824 *
2825 */
2826 private class WindowRunnnable {
2827 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07002828
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002829 public void rememberWindowAttachCount() {
2830 mOriginalAttachCount = getWindowAttachCount();
2831 }
Romain Guy0a637162009-05-29 14:43:54 -07002832
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002833 public boolean sameWindow() {
2834 return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
2835 }
2836 }
Romain Guy0a637162009-05-29 14:43:54 -07002837
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002838 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002839 int mClickMotionPosition;
2840
2841 public void run() {
2842 // The data has changed since we posted this action in the event queue,
2843 // bail out before bad things happen
2844 if (mDataChanged) return;
2845
Adam Powell005c0a42010-03-30 16:26:36 -07002846 final ListAdapter adapter = mAdapter;
2847 final int motionPosition = mClickMotionPosition;
2848 if (adapter != null && mItemCount > 0 &&
2849 motionPosition != INVALID_POSITION &&
2850 motionPosition < adapter.getCount() && sameWindow()) {
Romain Guy7890fe22011-01-18 20:24:18 -08002851 final View view = getChildAt(motionPosition - mFirstPosition);
2852 // If there is no view, something bad happened (the view scrolled off the
2853 // screen, etc.) and we should cancel the click
2854 if (view != null) {
2855 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2856 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002857 }
2858 }
2859 }
2860
2861 private class CheckForLongPress extends WindowRunnnable implements Runnable {
2862 public void run() {
2863 final int motionPosition = mMotionPosition;
2864 final View child = getChildAt(motionPosition - mFirstPosition);
2865 if (child != null) {
2866 final int longPressPosition = mMotionPosition;
2867 final long longPressId = mAdapter.getItemId(mMotionPosition);
2868
2869 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07002870 if (sameWindow() && !mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002871 handled = performLongPress(child, longPressPosition, longPressId);
2872 }
2873 if (handled) {
2874 mTouchMode = TOUCH_MODE_REST;
2875 setPressed(false);
2876 child.setPressed(false);
2877 } else {
2878 mTouchMode = TOUCH_MODE_DONE_WAITING;
2879 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002880 }
2881 }
2882 }
Romain Guy0a637162009-05-29 14:43:54 -07002883
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002884 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
2885 public void run() {
2886 if (isPressed() && mSelectedPosition >= 0) {
2887 int index = mSelectedPosition - mFirstPosition;
2888 View v = getChildAt(index);
2889
2890 if (!mDataChanged) {
2891 boolean handled = false;
2892 if (sameWindow()) {
2893 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2894 }
2895 if (handled) {
2896 setPressed(false);
2897 v.setPressed(false);
2898 }
2899 } else {
2900 setPressed(false);
2901 if (v != null) v.setPressed(false);
2902 }
2903 }
2904 }
2905 }
2906
Adam Powell8350f7d2010-07-28 14:27:28 -07002907 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002908 final int longPressPosition, final long longPressId) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002909 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2910 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07002911 if (mChoiceActionMode == null &&
2912 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002913 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07002914 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07002915 }
Adam Powellf343e1b2010-08-13 18:27:04 -07002916 return true;
2917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002918
Adam Powellf343e1b2010-08-13 18:27:04 -07002919 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002920 if (mOnItemLongClickListener != null) {
2921 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2922 longPressPosition, longPressId);
2923 }
2924 if (!handled) {
2925 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2926 handled = super.showContextMenuForChild(AbsListView.this);
2927 }
2928 if (handled) {
2929 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2930 }
2931 return handled;
2932 }
2933
2934 @Override
2935 protected ContextMenuInfo getContextMenuInfo() {
2936 return mContextMenuInfo;
2937 }
2938
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07002939 /** @hide */
2940 @Override
2941 public boolean showContextMenu(float x, float y, int metaState) {
2942 final int position = pointToPosition((int)x, (int)y);
2943 if (position != INVALID_POSITION) {
2944 final long id = mAdapter.getItemId(position);
2945 View child = getChildAt(position - mFirstPosition);
2946 if (child != null) {
2947 mContextMenuInfo = createContextMenuInfo(child, position, id);
2948 return super.showContextMenuForChild(AbsListView.this);
2949 }
2950 }
2951 return super.showContextMenu(x, y, metaState);
2952 }
2953
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002954 @Override
2955 public boolean showContextMenuForChild(View originalView) {
2956 final int longPressPosition = getPositionForView(originalView);
2957 if (longPressPosition >= 0) {
2958 final long longPressId = mAdapter.getItemId(longPressPosition);
2959 boolean handled = false;
2960
2961 if (mOnItemLongClickListener != null) {
2962 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
2963 longPressPosition, longPressId);
2964 }
2965 if (!handled) {
2966 mContextMenuInfo = createContextMenuInfo(
2967 getChildAt(longPressPosition - mFirstPosition),
2968 longPressPosition, longPressId);
2969 handled = super.showContextMenuForChild(originalView);
2970 }
2971
2972 return handled;
2973 }
2974 return false;
2975 }
2976
2977 @Override
Romain Guydf016072009-08-17 12:51:30 -07002978 public boolean onKeyDown(int keyCode, KeyEvent event) {
2979 return false;
2980 }
2981
2982 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002983 public boolean onKeyUp(int keyCode, KeyEvent event) {
2984 switch (keyCode) {
2985 case KeyEvent.KEYCODE_DPAD_CENTER:
2986 case KeyEvent.KEYCODE_ENTER:
Romain Guydd753ae2009-08-17 10:36:23 -07002987 if (!isEnabled()) {
2988 return true;
2989 }
Romain Guydf016072009-08-17 12:51:30 -07002990 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07002991 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002992 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07002993
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002994 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07002995 if (view != null) {
2996 performItemClick(view, mSelectedPosition, mSelectedRowId);
2997 view.setPressed(false);
2998 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002999 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003000 return true;
3001 }
Romain Guydd753ae2009-08-17 10:36:23 -07003002 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003003 }
3004 return super.onKeyUp(keyCode, event);
3005 }
3006
3007 @Override
3008 protected void dispatchSetPressed(boolean pressed) {
3009 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3010 // get the selector in the right state, but we don't want to press each child.
3011 }
3012
3013 /**
3014 * Maps a point to a position in the list.
3015 *
3016 * @param x X in local coordinate
3017 * @param y Y in local coordinate
3018 * @return The position of the item which contains the specified point, or
3019 * {@link #INVALID_POSITION} if the point does not intersect an item.
3020 */
3021 public int pointToPosition(int x, int y) {
3022 Rect frame = mTouchFrame;
3023 if (frame == null) {
3024 mTouchFrame = new Rect();
3025 frame = mTouchFrame;
3026 }
3027
3028 final int count = getChildCount();
3029 for (int i = count - 1; i >= 0; i--) {
3030 final View child = getChildAt(i);
3031 if (child.getVisibility() == View.VISIBLE) {
3032 child.getHitRect(frame);
3033 if (frame.contains(x, y)) {
3034 return mFirstPosition + i;
3035 }
3036 }
3037 }
3038 return INVALID_POSITION;
3039 }
3040
3041
3042 /**
3043 * Maps a point to a the rowId of the item which intersects that point.
3044 *
3045 * @param x X in local coordinate
3046 * @param y Y in local coordinate
3047 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3048 * if the point does not intersect an item.
3049 */
3050 public long pointToRowId(int x, int y) {
3051 int position = pointToPosition(x, y);
3052 if (position >= 0) {
3053 return mAdapter.getItemId(position);
3054 }
3055 return INVALID_ROW_ID;
3056 }
3057
3058 final class CheckForTap implements Runnable {
3059 public void run() {
3060 if (mTouchMode == TOUCH_MODE_DOWN) {
3061 mTouchMode = TOUCH_MODE_TAP;
3062 final View child = getChildAt(mMotionPosition - mFirstPosition);
3063 if (child != null && !child.hasFocusable()) {
3064 mLayoutMode = LAYOUT_NORMAL;
3065
3066 if (!mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003067 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003068 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003069 layoutChildren();
3070 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08003071 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003072
3073 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3074 final boolean longClickable = isLongClickable();
3075
3076 if (mSelector != null) {
3077 Drawable d = mSelector.getCurrent();
3078 if (d != null && d instanceof TransitionDrawable) {
3079 if (longClickable) {
3080 ((TransitionDrawable) d).startTransition(longPressTimeout);
3081 } else {
3082 ((TransitionDrawable) d).resetTransition();
3083 }
3084 }
3085 }
3086
3087 if (longClickable) {
3088 if (mPendingCheckForLongPress == null) {
3089 mPendingCheckForLongPress = new CheckForLongPress();
3090 }
3091 mPendingCheckForLongPress.rememberWindowAttachCount();
3092 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3093 } else {
3094 mTouchMode = TOUCH_MODE_DONE_WAITING;
3095 }
3096 } else {
Romain Guy0a637162009-05-29 14:43:54 -07003097 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003098 }
3099 }
3100 }
3101 }
3102 }
3103
Jeff Brown78f6e632011-09-09 17:15:31 -07003104 private boolean startScrollIfNeeded(int y) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003105 // Check if we have moved far enough that it looks more like a
3106 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003107 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003108 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003109 final boolean overscroll = mScrollY != 0;
3110 if (overscroll || distance > mTouchSlop) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003111 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003112 if (overscroll) {
3113 mTouchMode = TOUCH_MODE_OVERSCROLL;
3114 mMotionCorrection = 0;
3115 } else {
3116 mTouchMode = TOUCH_MODE_SCROLL;
3117 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3118 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003119 final Handler handler = getHandler();
3120 // Handler should not be null unless the AbsListView is not attached to a
3121 // window, which would make it very hard to scroll it... but the monkeys
3122 // say it's possible.
3123 if (handler != null) {
3124 handler.removeCallbacks(mPendingCheckForLongPress);
3125 }
3126 setPressed(false);
3127 View motionView = getChildAt(mMotionPosition - mFirstPosition);
3128 if (motionView != null) {
3129 motionView.setPressed(false);
3130 }
3131 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3132 // Time to start stealing events! Once we've stolen them, don't let anyone
3133 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003134 final ViewParent parent = getParent();
3135 if (parent != null) {
3136 parent.requestDisallowInterceptTouchEvent(true);
3137 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003138 scrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003139 return true;
3140 }
3141
3142 return false;
3143 }
3144
Jeff Brown78f6e632011-09-09 17:15:31 -07003145 private void scrollIfNeeded(int y) {
3146 final int rawDeltaY = y - mMotionY;
3147 final int deltaY = rawDeltaY - mMotionCorrection;
3148 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3149
3150 if (mTouchMode == TOUCH_MODE_SCROLL) {
3151 if (PROFILE_SCROLLING) {
3152 if (!mScrollProfilingStarted) {
3153 Debug.startMethodTracing("AbsListViewScroll");
3154 mScrollProfilingStarted = true;
3155 }
3156 }
3157
3158 if (mScrollStrictSpan == null) {
3159 // If it's non-null, we're already in a scroll.
3160 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3161 }
3162
3163 if (y != mLastY) {
3164 // We may be here after stopping a fling and continuing to scroll.
3165 // If so, we haven't disallowed intercepting touch events yet.
3166 // Make sure that we do so in case we're in a parent that can intercept.
3167 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3168 Math.abs(rawDeltaY) > mTouchSlop) {
3169 final ViewParent parent = getParent();
3170 if (parent != null) {
3171 parent.requestDisallowInterceptTouchEvent(true);
3172 }
3173 }
3174
3175 final int motionIndex;
3176 if (mMotionPosition >= 0) {
3177 motionIndex = mMotionPosition - mFirstPosition;
3178 } else {
3179 // If we don't have a motion position that we can reliably track,
3180 // pick something in the middle to make a best guess at things below.
3181 motionIndex = getChildCount() / 2;
3182 }
3183
3184 int motionViewPrevTop = 0;
3185 View motionView = this.getChildAt(motionIndex);
3186 if (motionView != null) {
3187 motionViewPrevTop = motionView.getTop();
3188 }
3189
3190 // No need to do all this work if we're not going to move anyway
3191 boolean atEdge = false;
3192 if (incrementalDeltaY != 0) {
3193 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3194 }
3195
3196 // Check to see if we have bumped into the scroll limit
3197 motionView = this.getChildAt(motionIndex);
3198 if (motionView != null) {
3199 // Check if the top of the motion view is where it is
3200 // supposed to be
3201 final int motionViewRealTop = motionView.getTop();
3202 if (atEdge) {
3203 // Apply overscroll
3204
3205 int overscroll = -incrementalDeltaY -
3206 (motionViewRealTop - motionViewPrevTop);
3207 overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3208 0, mOverscrollDistance, true);
3209 if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3210 // Don't allow overfling if we're at the edge.
3211 if (mVelocityTracker != null) {
3212 mVelocityTracker.clear();
3213 }
3214 }
3215
3216 final int overscrollMode = getOverScrollMode();
3217 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3218 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3219 !contentFits())) {
3220 mDirection = 0; // Reset when entering overscroll.
3221 mTouchMode = TOUCH_MODE_OVERSCROLL;
3222 if (rawDeltaY > 0) {
3223 mEdgeGlowTop.onPull((float) overscroll / getHeight());
3224 if (!mEdgeGlowBottom.isFinished()) {
3225 mEdgeGlowBottom.onRelease();
3226 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003227 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003228 } else if (rawDeltaY < 0) {
3229 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3230 if (!mEdgeGlowTop.isFinished()) {
3231 mEdgeGlowTop.onRelease();
3232 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003233 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003234 }
3235 }
3236 }
3237 mMotionY = y;
Jeff Brown78f6e632011-09-09 17:15:31 -07003238 }
3239 mLastY = y;
3240 }
3241 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3242 if (y != mLastY) {
3243 final int oldScroll = mScrollY;
3244 final int newScroll = oldScroll - incrementalDeltaY;
3245 int newDirection = y > mLastY ? 1 : -1;
3246
3247 if (mDirection == 0) {
3248 mDirection = newDirection;
3249 }
3250
3251 int overScrollDistance = -incrementalDeltaY;
3252 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3253 overScrollDistance = -oldScroll;
3254 incrementalDeltaY += overScrollDistance;
3255 } else {
3256 incrementalDeltaY = 0;
3257 }
3258
3259 if (overScrollDistance != 0) {
3260 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3261 0, mOverscrollDistance, true);
3262 final int overscrollMode = getOverScrollMode();
3263 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3264 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3265 !contentFits())) {
3266 if (rawDeltaY > 0) {
3267 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3268 if (!mEdgeGlowBottom.isFinished()) {
3269 mEdgeGlowBottom.onRelease();
3270 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003271 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003272 } else if (rawDeltaY < 0) {
3273 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3274 if (!mEdgeGlowTop.isFinished()) {
3275 mEdgeGlowTop.onRelease();
3276 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003277 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003278 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003279 }
3280 }
3281
3282 if (incrementalDeltaY != 0) {
3283 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003284 if (mScrollY != 0) {
3285 mScrollY = 0;
3286 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003287 }
3288
Romain Guy9d849a22012-03-14 16:41:42 -07003289 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3290
Jeff Brown78f6e632011-09-09 17:15:31 -07003291 mTouchMode = TOUCH_MODE_SCROLL;
3292
3293 // We did not scroll the full amount. Treat this essentially like the
3294 // start of a new touch scroll
3295 final int motionPosition = findClosestMotionRow(y);
3296
3297 mMotionCorrection = 0;
3298 View motionView = getChildAt(motionPosition - mFirstPosition);
3299 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3300 mMotionY = y;
3301 mMotionPosition = motionPosition;
3302 }
3303 mLastY = y;
3304 mDirection = newDirection;
3305 }
3306 }
3307 }
3308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003309 public void onTouchModeChanged(boolean isInTouchMode) {
3310 if (isInTouchMode) {
3311 // Get rid of the selection when we enter touch mode
3312 hideSelector();
3313 // Layout, but only if we already have done so previously.
3314 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3315 // state.)
3316 if (getHeight() > 0 && getChildCount() > 0) {
3317 // We do not lose focus initiating a touch (since AbsListView is focusable in
3318 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003319 layoutChildren();
3320 }
Jeff Brown1e209462011-07-14 22:19:19 -07003321 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003322 } else {
3323 int touchMode = mTouchMode;
3324 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3325 if (mFlingRunnable != null) {
3326 mFlingRunnable.endFling();
3327 }
Adam Powell40322522011-01-12 21:58:20 -08003328 if (mPositionScroller != null) {
3329 mPositionScroller.stop();
3330 }
Adam Powell637d3372010-08-25 14:37:03 -07003331
3332 if (mScrollY != 0) {
3333 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003334 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003335 finishGlows();
3336 invalidate();
3337 }
3338 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003339 }
3340 }
3341
3342 @Override
3343 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003344 if (!isEnabled()) {
3345 // A disabled view that is clickable still consumes the touch
3346 // events, it just doesn't respond to them.
3347 return isClickable() || isLongClickable();
3348 }
3349
Adam Powell1fa179ef2012-04-12 15:01:40 -07003350 if (mPositionScroller != null) {
3351 mPositionScroller.stop();
3352 }
3353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003354 if (mFastScroller != null) {
3355 boolean intercepted = mFastScroller.onTouchEvent(ev);
3356 if (intercepted) {
3357 return true;
Romain Guy0a637162009-05-29 14:43:54 -07003358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003359 }
Romain Guy82f34952009-05-24 18:40:45 -07003360
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003361 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003362
3363 View v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003364
Michael Jurka13451a42011-08-22 15:54:21 -07003365 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003366 mVelocityTracker.addMovement(ev);
3367
Adam Powell4cd47702010-02-25 11:21:14 -08003368 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003369 case MotionEvent.ACTION_DOWN: {
Adam Powell637d3372010-08-25 14:37:03 -07003370 switch (mTouchMode) {
3371 case TOUCH_MODE_OVERFLING: {
3372 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003373 if (mPositionScroller != null) {
3374 mPositionScroller.stop();
3375 }
Adam Powell637d3372010-08-25 14:37:03 -07003376 mTouchMode = TOUCH_MODE_OVERSCROLL;
Adam Powell044a46b2011-07-25 19:07:06 -07003377 mMotionX = (int) ev.getX();
Adam Powell637d3372010-08-25 14:37:03 -07003378 mMotionY = mLastY = (int) ev.getY();
3379 mMotionCorrection = 0;
3380 mActivePointerId = ev.getPointerId(0);
Adam Powell044a46b2011-07-25 19:07:06 -07003381 mDirection = 0;
Adam Powell637d3372010-08-25 14:37:03 -07003382 break;
3383 }
Adam Powell9d32d242010-03-29 16:02:07 -07003384
Adam Powell637d3372010-08-25 14:37:03 -07003385 default: {
3386 mActivePointerId = ev.getPointerId(0);
3387 final int x = (int) ev.getX();
3388 final int y = (int) ev.getY();
3389 int motionPosition = pointToPosition(x, y);
3390 if (!mDataChanged) {
3391 if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
3392 && (getAdapter().isEnabled(motionPosition))) {
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003393 // User clicked on an actual view (and was not stopping a fling).
3394 // It might be a click or a scroll. Assume it is a click until
3395 // proven otherwise
Adam Powell637d3372010-08-25 14:37:03 -07003396 mTouchMode = TOUCH_MODE_DOWN;
3397 // FIXME Debounce
3398 if (mPendingCheckForTap == null) {
3399 mPendingCheckForTap = new CheckForTap();
3400 }
3401 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
3402 } else {
Adam Powell637d3372010-08-25 14:37:03 -07003403 if (mTouchMode == TOUCH_MODE_FLING) {
3404 // Stopped a fling. It is a scroll.
3405 createScrollingCache();
3406 mTouchMode = TOUCH_MODE_SCROLL;
3407 mMotionCorrection = 0;
3408 motionPosition = findMotionRow(y);
3409 mFlingRunnable.flywheelTouch();
3410 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003411 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003412 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003413
Adam Powell637d3372010-08-25 14:37:03 -07003414 if (motionPosition >= 0) {
3415 // Remember where the motion event started
3416 v = getChildAt(motionPosition - mFirstPosition);
3417 mMotionViewOriginalTop = v.getTop();
3418 }
3419 mMotionX = x;
3420 mMotionY = y;
3421 mMotionPosition = motionPosition;
3422 mLastY = Integer.MIN_VALUE;
3423 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003424 }
Adam Powell637d3372010-08-25 14:37:03 -07003425 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003426
3427 if (performButtonActionOnTouchDown(ev)) {
3428 if (mTouchMode == TOUCH_MODE_DOWN) {
3429 removeCallbacks(mPendingCheckForTap);
3430 }
3431 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003432 break;
3433 }
3434
3435 case MotionEvent.ACTION_MOVE: {
Justin Koh2585e9b2011-06-30 17:11:26 -07003436 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3437 if (pointerIndex == -1) {
3438 pointerIndex = 0;
3439 mActivePointerId = ev.getPointerId(pointerIndex);
3440 }
Adam Powell4cd47702010-02-25 11:21:14 -08003441 final int y = (int) ev.getY(pointerIndex);
Adam Powell6f663c12012-04-30 16:59:02 -07003442
3443 if (mDataChanged) {
3444 // Re-sync everything if data has been changed
3445 // since the scroll operation can query the adapter.
3446 layoutChildren();
3447 }
3448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003449 switch (mTouchMode) {
3450 case TOUCH_MODE_DOWN:
3451 case TOUCH_MODE_TAP:
3452 case TOUCH_MODE_DONE_WAITING:
3453 // Check if we have moved far enough that it looks more like a
3454 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003455 startScrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003456 break;
3457 case TOUCH_MODE_SCROLL:
Adam Powell637d3372010-08-25 14:37:03 -07003458 case TOUCH_MODE_OVERSCROLL:
Jeff Brown78f6e632011-09-09 17:15:31 -07003459 scrollIfNeeded(y);
Adam Powell637d3372010-08-25 14:37:03 -07003460 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003462 break;
3463 }
3464
3465 case MotionEvent.ACTION_UP: {
3466 switch (mTouchMode) {
3467 case TOUCH_MODE_DOWN:
3468 case TOUCH_MODE_TAP:
3469 case TOUCH_MODE_DONE_WAITING:
3470 final int motionPosition = mMotionPosition;
3471 final View child = getChildAt(motionPosition - mFirstPosition);
Adam Powell498e43d2011-03-01 15:39:53 -08003472
3473 final float x = ev.getX();
3474 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3475
3476 if (child != null && !child.hasFocusable() && inList) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003477 if (mTouchMode != TOUCH_MODE_DOWN) {
3478 child.setPressed(false);
3479 }
3480
3481 if (mPerformClick == null) {
3482 mPerformClick = new PerformClick();
3483 }
3484
3485 final AbsListView.PerformClick performClick = mPerformClick;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003486 performClick.mClickMotionPosition = motionPosition;
3487 performClick.rememberWindowAttachCount();
3488
3489 mResurrectToPosition = motionPosition;
3490
3491 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3492 final Handler handler = getHandler();
3493 if (handler != null) {
3494 handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3495 mPendingCheckForTap : mPendingCheckForLongPress);
3496 }
3497 mLayoutMode = LAYOUT_NORMAL;
Adam Powell005c0a42010-03-30 16:26:36 -07003498 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3499 mTouchMode = TOUCH_MODE_TAP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003500 setSelectedPositionInt(mMotionPosition);
3501 layoutChildren();
3502 child.setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003503 positionSelector(mMotionPosition, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003504 setPressed(true);
3505 if (mSelector != null) {
3506 Drawable d = mSelector.getCurrent();
3507 if (d != null && d instanceof TransitionDrawable) {
Romain Guy6198ae82009-08-31 17:45:55 -07003508 ((TransitionDrawable) d).resetTransition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003509 }
3510 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003511 if (mTouchModeReset != null) {
3512 removeCallbacks(mTouchModeReset);
3513 }
3514 mTouchModeReset = new Runnable() {
3515 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003516 public void run() {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003517 mTouchMode = TOUCH_MODE_REST;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003518 child.setPressed(false);
3519 setPressed(false);
3520 if (!mDataChanged) {
Romain Guyd0b83652011-01-09 15:26:13 -08003521 performClick.run();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003522 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003523 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003524 };
3525 postDelayed(mTouchModeReset,
3526 ViewConfiguration.getPressedStateDuration());
Adam Powell005c0a42010-03-30 16:26:36 -07003527 } else {
3528 mTouchMode = TOUCH_MODE_REST;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003529 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003530 }
3531 return true;
Adam Powell005c0a42010-03-30 16:26:36 -07003532 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
Romain Guyd0b83652011-01-09 15:26:13 -08003533 performClick.run();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003534 }
3535 }
3536 mTouchMode = TOUCH_MODE_REST;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003537 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003538 break;
3539 case TOUCH_MODE_SCROLL:
Romain Guy6198ae82009-08-31 17:45:55 -07003540 final int childCount = getChildCount();
3541 if (childCount > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003542 final int firstChildTop = getChildAt(0).getTop();
3543 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3544 final int contentTop = mListPadding.top;
3545 final int contentBottom = getHeight() - mListPadding.bottom;
3546 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
Romain Guy6198ae82009-08-31 17:45:55 -07003547 mFirstPosition + childCount < mItemCount &&
Adam Powell637d3372010-08-25 14:37:03 -07003548 lastChildBottom <= getHeight() - contentBottom) {
Romain Guy6198ae82009-08-31 17:45:55 -07003549 mTouchMode = TOUCH_MODE_REST;
3550 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3551 } else {
3552 final VelocityTracker velocityTracker = mVelocityTracker;
3553 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powell637d3372010-08-25 14:37:03 -07003554
Romain Guy21317d12010-10-12 13:32:31 -07003555 final int initialVelocity = (int)
3556 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
Adam Powell637d3372010-08-25 14:37:03 -07003557 // Fling if we have enough velocity and we aren't at a boundary.
3558 // Since we can potentially overfling more than we can overscroll, don't
3559 // allow the weird behavior where you can scroll to a boundary then
3560 // fling further.
3561 if (Math.abs(initialVelocity) > mMinimumVelocity &&
3562 !((mFirstPosition == 0 &&
3563 firstChildTop == contentTop - mOverscrollDistance) ||
3564 (mFirstPosition + childCount == mItemCount &&
3565 lastChildBottom == contentBottom + mOverscrollDistance))) {
Romain Guy6198ae82009-08-31 17:45:55 -07003566 if (mFlingRunnable == null) {
3567 mFlingRunnable = new FlingRunnable();
3568 }
3569 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003570
Romain Guy6198ae82009-08-31 17:45:55 -07003571 mFlingRunnable.start(-initialVelocity);
Romain Guyf3c7d422009-12-04 15:38:22 -08003572 } else {
3573 mTouchMode = TOUCH_MODE_REST;
3574 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Gilles Debunned348bb42010-11-15 12:19:35 -08003575 if (mFlingRunnable != null) {
3576 mFlingRunnable.endFling();
3577 }
Adam Powell40322522011-01-12 21:58:20 -08003578 if (mPositionScroller != null) {
3579 mPositionScroller.stop();
3580 }
Romain Guy6198ae82009-08-31 17:45:55 -07003581 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003583 } else {
3584 mTouchMode = TOUCH_MODE_REST;
3585 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3586 }
Adam Powell79ac3392010-01-28 21:22:20 -08003587 break;
Adam Powell637d3372010-08-25 14:37:03 -07003588
3589 case TOUCH_MODE_OVERSCROLL:
3590 if (mFlingRunnable == null) {
3591 mFlingRunnable = new FlingRunnable();
3592 }
3593 final VelocityTracker velocityTracker = mVelocityTracker;
3594 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3595 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3596
3597 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3598 if (Math.abs(initialVelocity) > mMinimumVelocity) {
3599 mFlingRunnable.startOverfling(-initialVelocity);
3600 } else {
3601 mFlingRunnable.startSpringback();
3602 }
3603
3604 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003605 }
3606
3607 setPressed(false);
Romain Guy0a637162009-05-29 14:43:54 -07003608
Adam Powell637d3372010-08-25 14:37:03 -07003609 if (mEdgeGlowTop != null) {
3610 mEdgeGlowTop.onRelease();
3611 mEdgeGlowBottom.onRelease();
3612 }
3613
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003614 // Need to redraw since we probably aren't drawing the selector anymore
3615 invalidate();
Romain Guy0a637162009-05-29 14:43:54 -07003616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003617 final Handler handler = getHandler();
3618 if (handler != null) {
3619 handler.removeCallbacks(mPendingCheckForLongPress);
3620 }
3621
Michael Jurka13451a42011-08-22 15:54:21 -07003622 recycleVelocityTracker();
Mindy Pereira4e30d892010-11-24 15:32:39 -08003623
Adam Powell4cd47702010-02-25 11:21:14 -08003624 mActivePointerId = INVALID_POINTER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003625
3626 if (PROFILE_SCROLLING) {
3627 if (mScrollProfilingStarted) {
3628 Debug.stopMethodTracing();
3629 mScrollProfilingStarted = false;
3630 }
3631 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003632
3633 if (mScrollStrictSpan != null) {
3634 mScrollStrictSpan.finish();
3635 mScrollStrictSpan = null;
3636 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003637 break;
3638 }
3639
3640 case MotionEvent.ACTION_CANCEL: {
Adam Powell637d3372010-08-25 14:37:03 -07003641 switch (mTouchMode) {
3642 case TOUCH_MODE_OVERSCROLL:
3643 if (mFlingRunnable == null) {
3644 mFlingRunnable = new FlingRunnable();
3645 }
3646 mFlingRunnable.startSpringback();
3647 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003648
Adam Powell637d3372010-08-25 14:37:03 -07003649 case TOUCH_MODE_OVERFLING:
3650 // Do nothing - let it play out.
3651 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003652
Adam Powell637d3372010-08-25 14:37:03 -07003653 default:
3654 mTouchMode = TOUCH_MODE_REST;
3655 setPressed(false);
3656 View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
3657 if (motionView != null) {
3658 motionView.setPressed(false);
3659 }
3660 clearScrollingCache();
3661
3662 final Handler handler = getHandler();
3663 if (handler != null) {
3664 handler.removeCallbacks(mPendingCheckForLongPress);
3665 }
3666
Michael Jurka13451a42011-08-22 15:54:21 -07003667 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003668 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003669
Adam Powell637d3372010-08-25 14:37:03 -07003670 if (mEdgeGlowTop != null) {
3671 mEdgeGlowTop.onRelease();
3672 mEdgeGlowBottom.onRelease();
3673 }
Adam Powell4cd47702010-02-25 11:21:14 -08003674 mActivePointerId = INVALID_POINTER;
3675 break;
3676 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003677
Adam Powell4cd47702010-02-25 11:21:14 -08003678 case MotionEvent.ACTION_POINTER_UP: {
3679 onSecondaryPointerUp(ev);
3680 final int x = mMotionX;
3681 final int y = mMotionY;
3682 final int motionPosition = pointToPosition(x, y);
3683 if (motionPosition >= 0) {
3684 // Remember where the motion event started
3685 v = getChildAt(motionPosition - mFirstPosition);
3686 mMotionViewOriginalTop = v.getTop();
3687 mMotionPosition = motionPosition;
3688 }
3689 mLastY = y;
3690 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003691 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003692
3693 case MotionEvent.ACTION_POINTER_DOWN: {
3694 // New pointers take over dragging duties
3695 final int index = ev.getActionIndex();
3696 final int id = ev.getPointerId(index);
3697 final int x = (int) ev.getX(index);
3698 final int y = (int) ev.getY(index);
3699 mMotionCorrection = 0;
3700 mActivePointerId = id;
3701 mMotionX = x;
3702 mMotionY = y;
3703 final int motionPosition = pointToPosition(x, y);
3704 if (motionPosition >= 0) {
3705 // Remember where the motion event started
3706 v = getChildAt(motionPosition - mFirstPosition);
3707 mMotionViewOriginalTop = v.getTop();
3708 mMotionPosition = motionPosition;
3709 }
3710 mLastY = y;
3711 break;
3712 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003713 }
3714
3715 return true;
3716 }
Romain Guy0a637162009-05-29 14:43:54 -07003717
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003718 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003719 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3720 if (mScrollY != scrollY) {
3721 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3722 mScrollY = scrollY;
3723 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07003724
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003725 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07003726 }
Adam Powell637d3372010-08-25 14:37:03 -07003727 }
3728
3729 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08003730 public boolean onGenericMotionEvent(MotionEvent event) {
3731 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3732 switch (event.getAction()) {
3733 case MotionEvent.ACTION_SCROLL: {
3734 if (mTouchMode == TOUCH_MODE_REST) {
3735 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3736 if (vscroll != 0) {
3737 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08003738 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08003739 return true;
3740 }
3741 }
3742 }
3743 }
3744 }
3745 }
3746 return super.onGenericMotionEvent(event);
3747 }
3748
3749 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003750 public void draw(Canvas canvas) {
3751 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07003752 if (mEdgeGlowTop != null) {
3753 final int scrollY = mScrollY;
3754 if (!mEdgeGlowTop.isFinished()) {
3755 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003756 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3757 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3758 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003759
Romain Guy9d849a22012-03-14 16:41:42 -07003760 int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3761 canvas.translate(leftPadding, edgeY);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003762 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07003763 if (mEdgeGlowTop.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003764 mEdgeGlowTop.setPosition(leftPadding, edgeY);
Romain Guya8bfeaf2012-03-15 13:14:14 -07003765 invalidate(mEdgeGlowTop.getBounds(false));
Adam Powell637d3372010-08-25 14:37:03 -07003766 }
3767 canvas.restoreToCount(restoreCount);
3768 }
3769 if (!mEdgeGlowBottom.isFinished()) {
3770 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003771 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3772 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3773 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003774 final int height = getHeight();
3775
Romain Guy9d849a22012-03-14 16:41:42 -07003776 int edgeX = -width + leftPadding;
3777 int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3778 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08003779 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003780 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07003781 if (mEdgeGlowBottom.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003782 // Account for the rotation
Romain Guya8bfeaf2012-03-15 13:14:14 -07003783 mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
3784 invalidate(mEdgeGlowBottom.getBounds(true));
Adam Powell637d3372010-08-25 14:37:03 -07003785 }
3786 canvas.restoreToCount(restoreCount);
3787 }
3788 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003789 if (mFastScroller != null) {
Adam Powell637d3372010-08-25 14:37:03 -07003790 final int scrollY = mScrollY;
3791 if (scrollY != 0) {
3792 // Pin to the top/bottom during overscroll
3793 int restoreCount = canvas.save();
3794 canvas.translate(0, (float) scrollY);
3795 mFastScroller.draw(canvas);
3796 canvas.restoreToCount(restoreCount);
3797 } else {
3798 mFastScroller.draw(canvas);
3799 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003800 }
3801 }
3802
Adam Powell07d6f7b2011-03-02 14:27:30 -08003803 /**
3804 * @hide
3805 */
3806 public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3807 mGlowPaddingLeft = leftPadding;
3808 mGlowPaddingRight = rightPadding;
3809 }
3810
Michael Jurka13451a42011-08-22 15:54:21 -07003811 private void initOrResetVelocityTracker() {
3812 if (mVelocityTracker == null) {
3813 mVelocityTracker = VelocityTracker.obtain();
3814 } else {
3815 mVelocityTracker.clear();
3816 }
3817 }
3818
3819 private void initVelocityTrackerIfNotExists() {
3820 if (mVelocityTracker == null) {
3821 mVelocityTracker = VelocityTracker.obtain();
3822 }
3823 }
3824
3825 private void recycleVelocityTracker() {
3826 if (mVelocityTracker != null) {
3827 mVelocityTracker.recycle();
3828 mVelocityTracker = null;
3829 }
3830 }
3831
3832 @Override
3833 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3834 if (disallowIntercept) {
3835 recycleVelocityTracker();
3836 }
3837 super.requestDisallowInterceptTouchEvent(disallowIntercept);
3838 }
3839
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003840 @Override
3841 public boolean onInterceptTouchEvent(MotionEvent ev) {
3842 int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003843 View v;
Romain Guy0a637162009-05-29 14:43:54 -07003844
Adam Powell1fa179ef2012-04-12 15:01:40 -07003845 if (mPositionScroller != null) {
3846 mPositionScroller.stop();
3847 }
3848
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003849 if (mFastScroller != null) {
3850 boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
3851 if (intercepted) {
3852 return true;
3853 }
3854 }
Romain Guy0a637162009-05-29 14:43:54 -07003855
Adam Powell4cd47702010-02-25 11:21:14 -08003856 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003857 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08003858 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07003859 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3860 mMotionCorrection = 0;
3861 return true;
3862 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003863
Adam Powell4cd47702010-02-25 11:21:14 -08003864 final int x = (int) ev.getX();
3865 final int y = (int) ev.getY();
3866 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003868 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08003869 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003870 // User clicked on an actual view (and was not stopping a fling).
3871 // Remember where the motion event started
3872 v = getChildAt(motionPosition - mFirstPosition);
3873 mMotionViewOriginalTop = v.getTop();
3874 mMotionX = x;
3875 mMotionY = y;
3876 mMotionPosition = motionPosition;
3877 mTouchMode = TOUCH_MODE_DOWN;
3878 clearScrollingCache();
3879 }
3880 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07003881 initOrResetVelocityTracker();
3882 mVelocityTracker.addMovement(ev);
Adam Powell79ac3392010-01-28 21:22:20 -08003883 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08003884 return true;
3885 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003886 break;
3887 }
3888
3889 case MotionEvent.ACTION_MOVE: {
3890 switch (mTouchMode) {
3891 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07003892 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3893 if (pointerIndex == -1) {
3894 pointerIndex = 0;
3895 mActivePointerId = ev.getPointerId(pointerIndex);
3896 }
Adam Powell4cd47702010-02-25 11:21:14 -08003897 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07003898 initVelocityTrackerIfNotExists();
3899 mVelocityTracker.addMovement(ev);
Jeff Brown78f6e632011-09-09 17:15:31 -07003900 if (startScrollIfNeeded(y)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003901 return true;
3902 }
3903 break;
3904 }
3905 break;
3906 }
3907
Michael Jurka13451a42011-08-22 15:54:21 -07003908 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003909 case MotionEvent.ACTION_UP: {
3910 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08003911 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07003912 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003913 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3914 break;
3915 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003916
Adam Powell4cd47702010-02-25 11:21:14 -08003917 case MotionEvent.ACTION_POINTER_UP: {
3918 onSecondaryPointerUp(ev);
3919 break;
3920 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003921 }
3922
3923 return false;
3924 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003925
Adam Powell4cd47702010-02-25 11:21:14 -08003926 private void onSecondaryPointerUp(MotionEvent ev) {
3927 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3928 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3929 final int pointerId = ev.getPointerId(pointerIndex);
3930 if (pointerId == mActivePointerId) {
3931 // This was our active pointer going up. Choose a new
3932 // active pointer and adjust accordingly.
3933 // TODO: Make this decision more intelligent.
3934 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3935 mMotionX = (int) ev.getX(newPointerIndex);
3936 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07003937 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08003938 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08003939 }
3940 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003941
3942 /**
3943 * {@inheritDoc}
3944 */
3945 @Override
3946 public void addTouchables(ArrayList<View> views) {
3947 final int count = getChildCount();
3948 final int firstPosition = mFirstPosition;
3949 final ListAdapter adapter = mAdapter;
3950
3951 if (adapter == null) {
3952 return;
3953 }
3954
3955 for (int i = 0; i < count; i++) {
3956 final View child = getChildAt(i);
3957 if (adapter.isEnabled(firstPosition + i)) {
3958 views.add(child);
3959 }
3960 child.addTouchables(views);
3961 }
3962 }
3963
3964 /**
3965 * Fires an "on scroll state changed" event to the registered
3966 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
3967 * is fired only if the specified state is different from the previously known state.
3968 *
3969 * @param newState The new scroll state.
3970 */
3971 void reportScrollStateChange(int newState) {
3972 if (newState != mLastScrollState) {
3973 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003974 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08003975 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003976 }
3977 }
3978 }
3979
3980 /**
3981 * Responsible for fling behavior. Use {@link #start(int)} to
3982 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
3983 * A FlingRunnable will keep re-posting itself until the fling is done.
3984 *
3985 */
3986 private class FlingRunnable implements Runnable {
3987 /**
3988 * Tracks the decay of a fling scroll
3989 */
Adam Powell637d3372010-08-25 14:37:03 -07003990 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003991
3992 /**
3993 * Y value reported by mScroller on the previous fling
3994 */
3995 private int mLastFlingY;
3996
Gilles Debunned348bb42010-11-15 12:19:35 -08003997 private final Runnable mCheckFlywheel = new Runnable() {
3998 public void run() {
3999 final int activeId = mActivePointerId;
4000 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07004001 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08004002 if (vt == null || activeId == INVALID_POINTER) {
4003 return;
4004 }
4005
4006 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4007 final float yvel = -vt.getYVelocity(activeId);
4008
Jeff Brownb0c71eb2011-09-16 21:40:49 -07004009 if (Math.abs(yvel) >= mMinimumVelocity
4010 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08004011 // Keep the fling alive a little longer
4012 postDelayed(this, FLYWHEEL_TIMEOUT);
4013 } else {
4014 endFling();
4015 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08004016 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08004017 }
4018 }
4019 };
4020
4021 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4022
Adam Powell79ac3392010-01-28 21:22:20 -08004023 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07004024 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004025 }
4026
Adam Powell79ac3392010-01-28 21:22:20 -08004027 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004028 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4029 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004030 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004031 mScroller.fling(0, initialY, 0, initialVelocity,
4032 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4033 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004034 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004035
4036 if (PROFILE_FLINGING) {
4037 if (!mFlingProfilingStarted) {
4038 Debug.startMethodTracing("AbsListViewFling");
4039 mFlingProfilingStarted = true;
4040 }
4041 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08004042
4043 if (mFlingStrictSpan == null) {
4044 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4045 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004046 }
Adam Powell45803472010-01-25 15:10:44 -08004047
Adam Powell637d3372010-08-25 14:37:03 -07004048 void startSpringback() {
4049 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4050 mTouchMode = TOUCH_MODE_OVERFLING;
4051 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004052 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004053 } else {
4054 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08004055 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07004056 }
4057 }
4058
4059 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004060 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07004061 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4062 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07004063 mTouchMode = TOUCH_MODE_OVERFLING;
4064 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004065 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004066 }
4067
4068 void edgeReached(int delta) {
4069 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4070 final int overscrollMode = getOverScrollMode();
4071 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4072 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4073 mTouchMode = TOUCH_MODE_OVERFLING;
4074 final int vel = (int) mScroller.getCurrVelocity();
4075 if (delta > 0) {
4076 mEdgeGlowTop.onAbsorb(vel);
4077 } else {
4078 mEdgeGlowBottom.onAbsorb(vel);
4079 }
Adam Powell40322522011-01-12 21:58:20 -08004080 } else {
4081 mTouchMode = TOUCH_MODE_REST;
4082 if (mPositionScroller != null) {
4083 mPositionScroller.stop();
4084 }
Adam Powell637d3372010-08-25 14:37:03 -07004085 }
4086 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004087 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004088 }
4089
Adam Powell0b8acd82012-04-25 20:29:23 -07004090 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004091 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4092 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004093 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004094 mScroller.startScroll(0, initialY, 0, distance, duration);
4095 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004096 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004097 }
4098
Gilles Debunned348bb42010-11-15 12:19:35 -08004099 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004100 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004101
Adam Powell79ac3392010-01-28 21:22:20 -08004102 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004103 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004104
4105 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4106 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004107 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004108
4109 if (mFlingStrictSpan != null) {
4110 mFlingStrictSpan.finish();
4111 mFlingStrictSpan = null;
4112 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004113 }
4114
4115 void flywheelTouch() {
4116 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004117 }
4118
4119 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004120 switch (mTouchMode) {
4121 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004122 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004123 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004124
Gilles Debunned348bb42010-11-15 12:19:35 -08004125 case TOUCH_MODE_SCROLL:
4126 if (mScroller.isFinished()) {
4127 return;
4128 }
4129 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004130 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004131 if (mDataChanged) {
4132 layoutChildren();
4133 }
4134
Adam Powell79ac3392010-01-28 21:22:20 -08004135 if (mItemCount == 0 || getChildCount() == 0) {
4136 endFling();
4137 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004138 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004139
Adam Powell637d3372010-08-25 14:37:03 -07004140 final OverScroller scroller = mScroller;
4141 boolean more = scroller.computeScrollOffset();
4142 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004143
Adam Powell637d3372010-08-25 14:37:03 -07004144 // Flip sign to convert finger direction to list items direction
4145 // (e.g. finger moving down means list is moving towards the top)
4146 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004147
Adam Powell637d3372010-08-25 14:37:03 -07004148 // Pretend that each frame of a fling scroll is a touch scroll
4149 if (delta > 0) {
4150 // List is moving towards the top. Use first view as mMotionPosition
4151 mMotionPosition = mFirstPosition;
4152 final View firstView = getChildAt(0);
4153 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004154
Adam Powell637d3372010-08-25 14:37:03 -07004155 // Don't fling more than 1 screen
4156 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4157 } else {
4158 // List is moving towards the bottom. Use last view as mMotionPosition
4159 int offsetToLast = getChildCount() - 1;
4160 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004161
Adam Powell637d3372010-08-25 14:37:03 -07004162 final View lastView = getChildAt(offsetToLast);
4163 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004164
Adam Powell637d3372010-08-25 14:37:03 -07004165 // Don't fling more than 1 screen
4166 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4167 }
Adam Powell79ac3392010-01-28 21:22:20 -08004168
Adam Powell637d3372010-08-25 14:37:03 -07004169 // Check to see if we have bumped into the scroll limit
4170 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4171 int oldTop = 0;
4172 if (motionView != null) {
4173 oldTop = motionView.getTop();
4174 }
Adam Powell9d32d242010-03-29 16:02:07 -07004175
Adam Powell637d3372010-08-25 14:37:03 -07004176 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004177 final boolean atEdge = trackMotionScroll(delta, delta);
4178 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004179 if (atEnd) {
4180 if (motionView != null) {
4181 // Tweak the scroll for how far we overshot
4182 int overshoot = -(delta - (motionView.getTop() - oldTop));
4183 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4184 0, mOverflingDistance, false);
4185 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004186 if (more) {
4187 edgeReached(delta);
4188 }
Adam Powell637d3372010-08-25 14:37:03 -07004189 break;
4190 }
4191
4192 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004193 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004194 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004195 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004196 } else {
4197 endFling();
4198
4199 if (PROFILE_FLINGING) {
4200 if (mFlingProfilingStarted) {
4201 Debug.stopMethodTracing();
4202 mFlingProfilingStarted = false;
4203 }
4204
4205 if (mFlingStrictSpan != null) {
4206 mFlingStrictSpan.finish();
4207 mFlingStrictSpan = null;
4208 }
Adam Powell79ac3392010-01-28 21:22:20 -08004209 }
4210 }
Adam Powell637d3372010-08-25 14:37:03 -07004211 break;
4212 }
4213
4214 case TOUCH_MODE_OVERFLING: {
4215 final OverScroller scroller = mScroller;
4216 if (scroller.computeScrollOffset()) {
4217 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004218 final int currY = scroller.getCurrY();
4219 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004220 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4221 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004222 final boolean crossDown = scrollY <= 0 && currY > 0;
4223 final boolean crossUp = scrollY >= 0 && currY < 0;
4224 if (crossDown || crossUp) {
4225 int velocity = (int) scroller.getCurrVelocity();
4226 if (crossUp) velocity = -velocity;
4227
4228 // Don't flywheel from this; we're just continuing things.
4229 scroller.abortAnimation();
4230 start(velocity);
4231 } else {
4232 startSpringback();
4233 }
Adam Powell637d3372010-08-25 14:37:03 -07004234 } else {
4235 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004236 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004237 }
4238 } else {
4239 endFling();
4240 }
4241 break;
4242 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004243 }
4244 }
4245 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004246
Adam Powell45803472010-01-25 15:10:44 -08004247 class PositionScroller implements Runnable {
Adam Powelle69370e2012-05-07 15:17:20 -07004248 private static final int SCROLL_DURATION = 200;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004249
Adam Powell45803472010-01-25 15:10:44 -08004250 private static final int MOVE_DOWN_POS = 1;
4251 private static final int MOVE_UP_POS = 2;
4252 private static final int MOVE_DOWN_BOUND = 3;
4253 private static final int MOVE_UP_BOUND = 4;
Adam Powelle44afae2010-07-01 10:10:35 -07004254 private static final int MOVE_OFFSET = 5;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004255
Adam Powell45803472010-01-25 15:10:44 -08004256 private int mMode;
4257 private int mTargetPos;
4258 private int mBoundPos;
4259 private int mLastSeenPos;
4260 private int mScrollDuration;
Gilles Debunne52964242010-02-24 11:05:19 -08004261 private final int mExtraScroll;
Adam Powelle44afae2010-07-01 10:10:35 -07004262
4263 private int mOffsetFromTop;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004264
Adam Powell45803472010-01-25 15:10:44 -08004265 PositionScroller() {
4266 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
4267 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004268
Adam Powelle69370e2012-05-07 15:17:20 -07004269 void start(final int position) {
Adam Powell40322522011-01-12 21:58:20 -08004270 stop();
4271
Adam Powellaadf4fb2012-05-08 15:42:13 -07004272 if (mDataChanged) {
4273 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004274 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004275 @Override public void run() {
4276 start(position);
4277 }
Adam Powell161abf32012-05-23 17:22:49 -07004278 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004279 return;
4280 }
4281
Adam Powelle69370e2012-05-07 15:17:20 -07004282 final int childCount = getChildCount();
4283 if (childCount == 0) {
4284 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004285 return;
4286 }
4287
Adam Powell45803472010-01-25 15:10:44 -08004288 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004289 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004290
Romain Guy4bede9e2010-10-11 19:36:59 -07004291 int viewTravelCount;
Adam Powelle69370e2012-05-07 15:17:20 -07004292 if (position < firstPos) {
Adam Powell45803472010-01-25 15:10:44 -08004293 viewTravelCount = firstPos - position + 1;
4294 mMode = MOVE_UP_POS;
Adam Powelle69370e2012-05-07 15:17:20 -07004295 } else if (position > lastPos) {
Adam Powell45803472010-01-25 15:10:44 -08004296 viewTravelCount = position - lastPos + 1;
4297 mMode = MOVE_DOWN_POS;
4298 } else {
Adam Powelle69370e2012-05-07 15:17:20 -07004299 scrollToVisible(position, INVALID_POSITION, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004300 return;
4301 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004302
Adam Powell45803472010-01-25 15:10:44 -08004303 if (viewTravelCount > 0) {
4304 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4305 } else {
4306 mScrollDuration = SCROLL_DURATION;
4307 }
4308 mTargetPos = position;
4309 mBoundPos = INVALID_POSITION;
4310 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004311
Adam Powell1fa179ef2012-04-12 15:01:40 -07004312 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004313 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004314
Adam Powelle69370e2012-05-07 15:17:20 -07004315 void start(final int position, final int boundPosition) {
Adam Powell40322522011-01-12 21:58:20 -08004316 stop();
4317
Adam Powell45803472010-01-25 15:10:44 -08004318 if (boundPosition == INVALID_POSITION) {
4319 start(position);
4320 return;
4321 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004322
Adam Powellaadf4fb2012-05-08 15:42:13 -07004323 if (mDataChanged) {
4324 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004325 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004326 @Override public void run() {
4327 start(position, boundPosition);
4328 }
Adam Powell161abf32012-05-23 17:22:49 -07004329 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004330 return;
4331 }
4332
Adam Powelle69370e2012-05-07 15:17:20 -07004333 final int childCount = getChildCount();
4334 if (childCount == 0) {
4335 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004336 return;
4337 }
4338
Adam Powell45803472010-01-25 15:10:44 -08004339 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004340 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004341
Romain Guy4bede9e2010-10-11 19:36:59 -07004342 int viewTravelCount;
Adam Powelle69370e2012-05-07 15:17:20 -07004343 if (position < firstPos) {
Adam Powell45803472010-01-25 15:10:44 -08004344 final int boundPosFromLast = lastPos - boundPosition;
4345 if (boundPosFromLast < 1) {
4346 // Moving would shift our bound position off the screen. Abort.
4347 return;
4348 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004349
Adam Powell45803472010-01-25 15:10:44 -08004350 final int posTravel = firstPos - position + 1;
4351 final int boundTravel = boundPosFromLast - 1;
4352 if (boundTravel < posTravel) {
4353 viewTravelCount = boundTravel;
4354 mMode = MOVE_UP_BOUND;
4355 } else {
4356 viewTravelCount = posTravel;
4357 mMode = MOVE_UP_POS;
4358 }
Adam Powelle69370e2012-05-07 15:17:20 -07004359 } else if (position > lastPos) {
Adam Powell45803472010-01-25 15:10:44 -08004360 final int boundPosFromFirst = boundPosition - firstPos;
4361 if (boundPosFromFirst < 1) {
4362 // Moving would shift our bound position off the screen. Abort.
4363 return;
4364 }
4365
4366 final int posTravel = position - lastPos + 1;
4367 final int boundTravel = boundPosFromFirst - 1;
4368 if (boundTravel < posTravel) {
4369 viewTravelCount = boundTravel;
4370 mMode = MOVE_DOWN_BOUND;
4371 } else {
4372 viewTravelCount = posTravel;
4373 mMode = MOVE_DOWN_POS;
4374 }
4375 } else {
Adam Powelle69370e2012-05-07 15:17:20 -07004376 scrollToVisible(position, boundPosition, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004377 return;
4378 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004379
Adam Powell45803472010-01-25 15:10:44 -08004380 if (viewTravelCount > 0) {
4381 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4382 } else {
4383 mScrollDuration = SCROLL_DURATION;
4384 }
4385 mTargetPos = position;
4386 mBoundPos = boundPosition;
4387 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004388
Adam Powell1fa179ef2012-04-12 15:01:40 -07004389 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004390 }
Adam Powelle44afae2010-07-01 10:10:35 -07004391
4392 void startWithOffset(int position, int offset) {
Erik322171b2010-10-13 15:46:00 -07004393 startWithOffset(position, offset, SCROLL_DURATION);
4394 }
4395
Adam Powellaadf4fb2012-05-08 15:42:13 -07004396 void startWithOffset(final int position, int offset, final int duration) {
Adam Powell40322522011-01-12 21:58:20 -08004397 stop();
4398
Adam Powellaadf4fb2012-05-08 15:42:13 -07004399 if (mDataChanged) {
4400 // Wait until we're back in a stable state to try this.
4401 final int postOffset = offset;
Adam Powell161abf32012-05-23 17:22:49 -07004402 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004403 @Override public void run() {
4404 startWithOffset(position, postOffset, duration);
4405 }
Adam Powell161abf32012-05-23 17:22:49 -07004406 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004407 return;
4408 }
4409
4410 final int childCount = getChildCount();
4411 if (childCount == 0) {
4412 // Can't scroll without children.
4413 return;
4414 }
4415
Adam Powell1fa179ef2012-04-12 15:01:40 -07004416 offset += getPaddingTop();
4417
Adam Powelle44afae2010-07-01 10:10:35 -07004418 mTargetPos = position;
4419 mOffsetFromTop = offset;
4420 mBoundPos = INVALID_POSITION;
4421 mLastSeenPos = INVALID_POSITION;
4422 mMode = MOVE_OFFSET;
4423
4424 final int firstPos = mFirstPosition;
Adam Powell37113312010-07-08 18:21:48 -07004425 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004426
Romain Guy4bede9e2010-10-11 19:36:59 -07004427 int viewTravelCount;
Adam Powelle44afae2010-07-01 10:10:35 -07004428 if (position < firstPos) {
4429 viewTravelCount = firstPos - position;
4430 } else if (position > lastPos) {
4431 viewTravelCount = position - lastPos;
4432 } else {
4433 // On-screen, just scroll.
4434 final int targetTop = getChildAt(position - firstPos).getTop();
Adam Powell0b8acd82012-04-25 20:29:23 -07004435 smoothScrollBy(targetTop - offset, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004436 return;
4437 }
4438
Adam Powell37113312010-07-08 18:21:48 -07004439 // Estimate how many screens we should travel
Daniel Lehmann4ef1da32010-08-27 16:40:59 -07004440 final float screenTravelCount = (float) viewTravelCount / childCount;
Adam Powell0b8acd82012-04-25 20:29:23 -07004441 mScrollDuration = screenTravelCount < 1 ?
4442 duration : (int) (duration / screenTravelCount);
Adam Powell37113312010-07-08 18:21:48 -07004443 mLastSeenPos = INVALID_POSITION;
Adam Powell234a5712010-09-14 10:34:56 -07004444
Adam Powell1fa179ef2012-04-12 15:01:40 -07004445 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004446 }
4447
Adam Powelle69370e2012-05-07 15:17:20 -07004448 /**
4449 * Scroll such that targetPos is in the visible padded region without scrolling
4450 * boundPos out of view. Assumes targetPos is onscreen.
4451 */
4452 void scrollToVisible(int targetPos, int boundPos, int duration) {
4453 final int firstPos = mFirstPosition;
4454 final int childCount = getChildCount();
4455 final int lastPos = firstPos + childCount - 1;
4456 final int paddedTop = mListPadding.top;
4457 final int paddedBottom = getHeight() - mListPadding.bottom;
4458
4459 if (targetPos < firstPos || targetPos > lastPos) {
4460 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
4461 " not visible [" + firstPos + ", " + lastPos + "]");
4462 }
4463 if (boundPos < firstPos || boundPos > lastPos) {
4464 // boundPos doesn't matter, it's already offscreen.
4465 boundPos = INVALID_POSITION;
4466 }
4467
4468 final View targetChild = getChildAt(targetPos - firstPos);
4469 final int targetTop = targetChild.getTop();
4470 final int targetBottom = targetChild.getBottom();
4471 int scrollBy = 0;
4472
4473 if (targetBottom > paddedBottom) {
4474 scrollBy = targetBottom - paddedBottom;
4475 }
4476 if (targetTop < paddedTop) {
4477 scrollBy = targetTop - paddedTop;
4478 }
4479
4480 if (scrollBy == 0) {
4481 return;
4482 }
4483
4484 if (boundPos >= 0) {
4485 final View boundChild = getChildAt(boundPos - firstPos);
4486 final int boundTop = boundChild.getTop();
4487 final int boundBottom = boundChild.getBottom();
4488 final int absScroll = Math.abs(scrollBy);
4489
4490 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
4491 // Don't scroll the bound view off the bottom of the screen.
4492 scrollBy = Math.max(0, boundBottom - paddedBottom);
4493 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
4494 // Don't scroll the bound view off the top of the screen.
4495 scrollBy = Math.min(0, boundTop - paddedTop);
4496 }
4497 }
4498
4499 smoothScrollBy(scrollBy, duration);
4500 }
4501
Adam Powell45803472010-01-25 15:10:44 -08004502 void stop() {
4503 removeCallbacks(this);
4504 }
Adam Powelle44afae2010-07-01 10:10:35 -07004505
Adam Powell45803472010-01-25 15:10:44 -08004506 public void run() {
4507 final int listHeight = getHeight();
4508 final int firstPos = mFirstPosition;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004509
Adam Powell45803472010-01-25 15:10:44 -08004510 switch (mMode) {
4511 case MOVE_DOWN_POS: {
4512 final int lastViewIndex = getChildCount() - 1;
4513 final int lastPos = firstPos + lastViewIndex;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004514
Adam Powell0b8bb422010-02-08 14:30:45 -08004515 if (lastViewIndex < 0) {
4516 return;
4517 }
Adam Powell45803472010-01-25 15:10:44 -08004518
4519 if (lastPos == mLastSeenPos) {
4520 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004521 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004522 return;
4523 }
4524
4525 final View lastView = getChildAt(lastViewIndex);
4526 final int lastViewHeight = lastView.getHeight();
4527 final int lastViewTop = lastView.getTop();
4528 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004529 final int extraScroll = lastPos < mItemCount - 1 ?
4530 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
Adam Powell45803472010-01-25 15:10:44 -08004531
Adam Powell1fa179ef2012-04-12 15:01:40 -07004532 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Adam Powell0b8acd82012-04-25 20:29:23 -07004533 smoothScrollBy(scrollBy, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004534
4535 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004536 if (lastPos < mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004537 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004538 }
4539 break;
4540 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004541
Adam Powell45803472010-01-25 15:10:44 -08004542 case MOVE_DOWN_BOUND: {
4543 final int nextViewIndex = 1;
Adam Powell029cfbd2010-03-08 19:03:54 -08004544 final int childCount = getChildCount();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004545
Adam Powell029cfbd2010-03-08 19:03:54 -08004546 if (firstPos == mBoundPos || childCount <= nextViewIndex
4547 || firstPos + childCount >= mItemCount) {
Adam Powell45803472010-01-25 15:10:44 -08004548 return;
4549 }
4550 final int nextPos = firstPos + nextViewIndex;
4551
4552 if (nextPos == mLastSeenPos) {
4553 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004554 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004555 return;
4556 }
4557
4558 final View nextView = getChildAt(nextViewIndex);
4559 final int nextViewHeight = nextView.getHeight();
4560 final int nextViewTop = nextView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004561 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
Adam Powell7e5e3742010-05-24 15:13:41 -07004562 if (nextPos < mBoundPos) {
Adam Powell45803472010-01-25 15:10:44 -08004563 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Adam Powell0b8acd82012-04-25 20:29:23 -07004564 mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004565
4566 mLastSeenPos = nextPos;
4567
Adam Powell1fa179ef2012-04-12 15:01:40 -07004568 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004569 } else {
Mindy Pereira4e30d892010-11-24 15:32:39 -08004570 if (nextViewTop > extraScroll) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004571 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004572 }
4573 }
4574 break;
4575 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004576
Adam Powell45803472010-01-25 15:10:44 -08004577 case MOVE_UP_POS: {
4578 if (firstPos == mLastSeenPos) {
4579 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004580 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004581 return;
4582 }
4583
4584 final View firstView = getChildAt(0);
Adam Powell0b8bb422010-02-08 14:30:45 -08004585 if (firstView == null) {
4586 return;
4587 }
Adam Powell45803472010-01-25 15:10:44 -08004588 final int firstViewTop = firstView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004589 final int extraScroll = firstPos > 0 ?
4590 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
Adam Powell45803472010-01-25 15:10:44 -08004591
Adam Powell0b8acd82012-04-25 20:29:23 -07004592 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004593
4594 mLastSeenPos = firstPos;
4595
Adam Powell7e5e3742010-05-24 15:13:41 -07004596 if (firstPos > mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004597 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004598 }
4599 break;
4600 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004601
Adam Powell45803472010-01-25 15:10:44 -08004602 case MOVE_UP_BOUND: {
4603 final int lastViewIndex = getChildCount() - 2;
4604 if (lastViewIndex < 0) {
4605 return;
4606 }
4607 final int lastPos = firstPos + lastViewIndex;
4608
4609 if (lastPos == mLastSeenPos) {
4610 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004611 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004612 return;
4613 }
4614
4615 final View lastView = getChildAt(lastViewIndex);
4616 final int lastViewHeight = lastView.getHeight();
4617 final int lastViewTop = lastView.getTop();
4618 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004619 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
Adam Powell45803472010-01-25 15:10:44 -08004620 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004621 if (lastPos > mBoundPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004622 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004623 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004624 } else {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004625 final int bottom = listHeight - extraScroll;
Adam Powell45803472010-01-25 15:10:44 -08004626 final int lastViewBottom = lastViewTop + lastViewHeight;
4627 if (bottom > lastViewBottom) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004628 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004629 }
4630 }
4631 break;
4632 }
4633
Adam Powelle44afae2010-07-01 10:10:35 -07004634 case MOVE_OFFSET: {
Adam Powell234a5712010-09-14 10:34:56 -07004635 if (mLastSeenPos == firstPos) {
4636 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004637 postOnAnimation(this);
Adam Powell234a5712010-09-14 10:34:56 -07004638 return;
4639 }
Adam Powelle44afae2010-07-01 10:10:35 -07004640
Adam Powell37113312010-07-08 18:21:48 -07004641 mLastSeenPos = firstPos;
Adam Powell234a5712010-09-14 10:34:56 -07004642
4643 final int childCount = getChildCount();
Adam Powelle44afae2010-07-01 10:10:35 -07004644 final int position = mTargetPos;
Adam Powell37113312010-07-08 18:21:48 -07004645 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004646
Adam Powell40322522011-01-12 21:58:20 -08004647 int viewTravelCount = 0;
Adam Powelle44afae2010-07-01 10:10:35 -07004648 if (position < firstPos) {
Adam Powell40322522011-01-12 21:58:20 -08004649 viewTravelCount = firstPos - position + 1;
4650 } else if (position > lastPos) {
4651 viewTravelCount = position - lastPos;
4652 }
4653
4654 // Estimate how many screens we should travel
4655 final float screenTravelCount = (float) viewTravelCount / childCount;
4656
4657 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4658 if (position < firstPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004659 final int distance = (int) (-getHeight() * modifier);
4660 final int duration = (int) (mScrollDuration * modifier);
4661 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004662 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004663 } else if (position > lastPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004664 final int distance = (int) (getHeight() * modifier);
4665 final int duration = (int) (mScrollDuration * modifier);
4666 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004667 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004668 } else {
4669 // On-screen, just scroll.
4670 final int targetTop = getChildAt(position - firstPos).getTop();
Adam Powell234a5712010-09-14 10:34:56 -07004671 final int distance = targetTop - mOffsetFromTop;
Adam Powell0b8acd82012-04-25 20:29:23 -07004672 final int duration = (int) (mScrollDuration *
4673 ((float) Math.abs(distance) / getHeight()));
4674 smoothScrollBy(distance, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004675 }
4676 break;
4677 }
4678
Adam Powell45803472010-01-25 15:10:44 -08004679 default:
4680 break;
4681 }
4682 }
4683 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004684
Adam Powell45803472010-01-25 15:10:44 -08004685 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004686 * The amount of friction applied to flings. The default value
4687 * is {@link ViewConfiguration#getScrollFriction}.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004688 *
Romain Guy4bede9e2010-10-11 19:36:59 -07004689 * @return A scalar dimensionless value representing the coefficient of
4690 * friction.
4691 */
4692 public void setFriction(float friction) {
4693 if (mFlingRunnable == null) {
4694 mFlingRunnable = new FlingRunnable();
4695 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004696 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004697 }
Romain Guy21317d12010-10-12 13:32:31 -07004698
4699 /**
4700 * Sets a scale factor for the fling velocity. The initial scale
4701 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004702 *
Romain Guy21317d12010-10-12 13:32:31 -07004703 * @param scale The scale factor to multiply the velocity by.
4704 */
4705 public void setVelocityScale(float scale) {
4706 mVelocityScale = scale;
4707 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004708
Romain Guy4bede9e2010-10-11 19:36:59 -07004709 /**
Adam Powell45803472010-01-25 15:10:44 -08004710 * Smoothly scroll to the specified adapter position. The view will
4711 * scroll such that the indicated position is displayed.
4712 * @param position Scroll to this adapter position.
4713 */
4714 public void smoothScrollToPosition(int position) {
4715 if (mPositionScroller == null) {
4716 mPositionScroller = new PositionScroller();
4717 }
4718 mPositionScroller.start(position);
4719 }
Erik322171b2010-10-13 15:46:00 -07004720
4721 /**
4722 * Smoothly scroll to the specified adapter position. The view will scroll
4723 * such that the indicated position is displayed <code>offset</code> pixels from
4724 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4725 * the first or last item beyond the boundaries of the list) it will get as close
4726 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4727 *
4728 * @param position Position to scroll to
4729 * @param offset Desired distance in pixels of <code>position</code> from the top
4730 * of the view when scrolling is finished
4731 * @param duration Number of milliseconds to use for the scroll
4732 */
4733 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4734 if (mPositionScroller == null) {
4735 mPositionScroller = new PositionScroller();
4736 }
4737 mPositionScroller.startWithOffset(position, offset, duration);
4738 }
4739
Adam Powell45803472010-01-25 15:10:44 -08004740 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004741 * Smoothly scroll to the specified adapter position. The view will scroll
4742 * such that the indicated position is displayed <code>offset</code> pixels from
4743 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4744 * the first or last item beyond the boundaries of the list) it will get as close
4745 * as possible.
4746 *
4747 * @param position Position to scroll to
4748 * @param offset Desired distance in pixels of <code>position</code> from the top
4749 * of the view when scrolling is finished
4750 */
4751 public void smoothScrollToPositionFromTop(int position, int offset) {
4752 if (mPositionScroller == null) {
4753 mPositionScroller = new PositionScroller();
4754 }
4755 mPositionScroller.startWithOffset(position, offset);
4756 }
4757
4758 /**
Adam Powell45803472010-01-25 15:10:44 -08004759 * Smoothly scroll to the specified adapter position. The view will
4760 * scroll such that the indicated position is displayed, but it will
4761 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004762 * view.
Adam Powell45803472010-01-25 15:10:44 -08004763 * @param position Scroll to this adapter position.
4764 * @param boundPosition Do not scroll if it would move this adapter
4765 * position out of view.
4766 */
4767 public void smoothScrollToPosition(int position, int boundPosition) {
4768 if (mPositionScroller == null) {
4769 mPositionScroller = new PositionScroller();
4770 }
4771 mPositionScroller.start(position, boundPosition);
4772 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004773
Adam Powell45803472010-01-25 15:10:44 -08004774 /**
4775 * Smoothly scroll by distance pixels over duration milliseconds.
4776 * @param distance Distance to scroll in pixels.
4777 * @param duration Duration of the scroll animation in milliseconds.
4778 */
4779 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004780 smoothScrollBy(distance, duration, false);
4781 }
4782
4783 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004784 if (mFlingRunnable == null) {
4785 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004786 }
Adam Powell40322522011-01-12 21:58:20 -08004787
Marc Blank299acb52010-10-21 11:03:53 -07004788 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004789 final int firstPos = mFirstPosition;
4790 final int childCount = getChildCount();
4791 final int lastPos = firstPos + childCount;
4792 final int topLimit = getPaddingTop();
4793 final int bottomLimit = getHeight() - getPaddingBottom();
4794
Adam Powell79303752011-01-13 22:06:49 -08004795 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004796 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004797 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004798 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4799 mFlingRunnable.endFling();
4800 if (mPositionScroller != null) {
4801 mPositionScroller.stop();
4802 }
4803 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004804 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004805 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004806 }
Adam Powell45803472010-01-25 15:10:44 -08004807 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004808
Winson Chung499cb9f2010-07-16 11:18:17 -07004809 /**
4810 * Allows RemoteViews to scroll relatively to a position.
4811 */
4812 void smoothScrollByOffset(int position) {
4813 int index = -1;
4814 if (position < 0) {
4815 index = getFirstVisiblePosition();
4816 } else if (position > 0) {
4817 index = getLastVisiblePosition();
4818 }
4819
4820 if (index > -1) {
4821 View child = getChildAt(index - getFirstVisiblePosition());
4822 if (child != null) {
4823 Rect visibleRect = new Rect();
4824 if (child.getGlobalVisibleRect(visibleRect)) {
4825 // the child is partially visible
4826 int childRectArea = child.getWidth() * child.getHeight();
4827 int visibleRectArea = visibleRect.width() * visibleRect.height();
4828 float visibleArea = (visibleRectArea / (float) childRectArea);
4829 final float visibleThreshold = 0.75f;
4830 if ((position < 0) && (visibleArea < visibleThreshold)) {
4831 // the top index is not perceivably visible so offset
4832 // to account for showing that top index as well
4833 ++index;
4834 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4835 // the bottom index is not perceivably visible so offset
4836 // to account for showing that bottom index as well
4837 --index;
4838 }
4839 }
4840 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4841 }
4842 }
4843 }
4844
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004845 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004846 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004847 setChildrenDrawnWithCacheEnabled(true);
4848 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004849 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004850 }
4851 }
4852
4853 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004854 if (!isHardwareAccelerated()) {
4855 if (mClearScrollingCache == null) {
4856 mClearScrollingCache = new Runnable() {
4857 public void run() {
4858 if (mCachingStarted) {
4859 mCachingStarted = mCachingActive = false;
4860 setChildrenDrawnWithCacheEnabled(false);
4861 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4862 setChildrenDrawingCacheEnabled(false);
4863 }
4864 if (!isAlwaysDrawnWithCacheEnabled()) {
4865 invalidate();
4866 }
Romain Guy6dfed242009-05-11 18:25:05 -07004867 }
4868 }
Romain Guy9d849a22012-03-14 16:41:42 -07004869 };
4870 }
4871 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004872 }
4873 }
4874
4875 /**
4876 * Track a motion scroll
4877 *
4878 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4879 * began. Positive numbers mean the user's finger is moving down the screen.
4880 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08004881 * @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 -08004882 */
Adam Powell45803472010-01-25 15:10:44 -08004883 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004884 final int childCount = getChildCount();
4885 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08004886 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004887 }
4888
4889 final int firstTop = getChildAt(0).getTop();
4890 final int lastBottom = getChildAt(childCount - 1).getBottom();
4891
4892 final Rect listPadding = mListPadding;
4893
Adam Powellbdccc2d2010-12-14 17:34:27 -08004894 // "effective padding" In this case is the amount of padding that affects
4895 // how much space should not be filled by items. If we don't clip to padding
4896 // there is no effective padding.
4897 int effectivePaddingTop = 0;
4898 int effectivePaddingBottom = 0;
4899 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4900 effectivePaddingTop = listPadding.top;
4901 effectivePaddingBottom = listPadding.bottom;
4902 }
4903
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004904 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08004905 final int spaceAbove = effectivePaddingTop - firstTop;
4906 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004907 final int spaceBelow = lastBottom - end;
4908
4909 final int height = getHeight() - mPaddingBottom - mPaddingTop;
4910 if (deltaY < 0) {
4911 deltaY = Math.max(-(height - 1), deltaY);
4912 } else {
4913 deltaY = Math.min(height - 1, deltaY);
4914 }
4915
4916 if (incrementalDeltaY < 0) {
4917 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4918 } else {
4919 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4920 }
4921
Adam Powell45803472010-01-25 15:10:44 -08004922 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004923
Adam Powell637d3372010-08-25 14:37:03 -07004924 // Update our guesses for where the first and last views are
4925 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004926 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07004927 } else {
4928 mFirstPositionDistanceGuess += incrementalDeltaY;
4929 }
4930 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004931 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07004932 } else {
4933 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004934 }
Adam Powell45803472010-01-25 15:10:44 -08004935
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004936 final boolean cannotScrollDown = (firstPosition == 0 &&
4937 firstTop >= listPadding.top && incrementalDeltaY >= 0);
4938 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4939 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07004940
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004941 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07004942 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08004943 }
4944
4945 final boolean down = incrementalDeltaY < 0;
4946
Adam Powell029cfbd2010-03-08 19:03:54 -08004947 final boolean inTouchMode = isInTouchMode();
4948 if (inTouchMode) {
4949 hideSelector();
4950 }
Adam Powell45803472010-01-25 15:10:44 -08004951
4952 final int headerViewsCount = getHeaderViewsCount();
4953 final int footerViewsStart = mItemCount - getFooterViewsCount();
4954
4955 int start = 0;
4956 int count = 0;
4957
4958 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004959 int top = -incrementalDeltaY;
4960 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4961 top += listPadding.top;
4962 }
Adam Powell45803472010-01-25 15:10:44 -08004963 for (int i = 0; i < childCount; i++) {
4964 final View child = getChildAt(i);
4965 if (child.getBottom() >= top) {
4966 break;
4967 } else {
4968 count++;
4969 int position = firstPosition + i;
4970 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004971 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004972 }
4973 }
4974 }
4975 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004976 int bottom = getHeight() - incrementalDeltaY;
4977 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4978 bottom -= listPadding.bottom;
4979 }
Adam Powell45803472010-01-25 15:10:44 -08004980 for (int i = childCount - 1; i >= 0; i--) {
4981 final View child = getChildAt(i);
4982 if (child.getTop() <= bottom) {
4983 break;
4984 } else {
4985 start = i;
4986 count++;
4987 int position = firstPosition + i;
4988 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004989 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004990 }
4991 }
4992 }
4993 }
4994
4995 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
4996
4997 mBlockLayoutRequests = true;
4998
4999 if (count > 0) {
5000 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08005001 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08005002 }
Adam Powell539ee872012-02-03 19:00:49 -08005003
Romain Guy9d849a22012-03-14 16:41:42 -07005004 // invalidate before moving the children to avoid unnecessary invalidate
5005 // calls to bubble up from the children all the way to the top
5006 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07005007 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07005008 }
5009
Adam Powell45803472010-01-25 15:10:44 -08005010 offsetChildrenTopAndBottom(incrementalDeltaY);
5011
5012 if (down) {
5013 mFirstPosition += count;
5014 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08005015
Adam Powell45803472010-01-25 15:10:44 -08005016 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5017 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5018 fillGap(down);
5019 }
5020
Adam Powell029cfbd2010-03-08 19:03:54 -08005021 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08005022 final int childIndex = mSelectedPosition - mFirstPosition;
5023 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07005024 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08005025 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07005026 } else if (mSelectorPosition != INVALID_POSITION) {
5027 final int childIndex = mSelectorPosition - mFirstPosition;
5028 if (childIndex >= 0 && childIndex < getChildCount()) {
5029 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5030 }
5031 } else {
5032 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08005033 }
5034
Adam Powell45803472010-01-25 15:10:44 -08005035 mBlockLayoutRequests = false;
5036
5037 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08005038
Adam Powell45803472010-01-25 15:10:44 -08005039 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005040 }
5041
5042 /**
5043 * Returns the number of header views in the list. Header views are special views
5044 * at the top of the list that should not be recycled during a layout.
5045 *
5046 * @return The number of header views, 0 in the default implementation.
5047 */
5048 int getHeaderViewsCount() {
5049 return 0;
5050 }
5051
5052 /**
5053 * Returns the number of footer views in the list. Footer views are special views
5054 * at the bottom of the list that should not be recycled during a layout.
5055 *
5056 * @return The number of footer views, 0 in the default implementation.
5057 */
5058 int getFooterViewsCount() {
5059 return 0;
5060 }
5061
5062 /**
5063 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5064 * remain on screen are shifted and the other ones are discarded. The role of this
5065 * method is to fill the gap thus created by performing a partial layout in the
5066 * empty space.
5067 *
5068 * @param down true if the scroll is going down, false if it is going up
5069 */
5070 abstract void fillGap(boolean down);
5071
5072 void hideSelector() {
5073 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08005074 if (mLayoutMode != LAYOUT_SPECIFIC) {
5075 mResurrectToPosition = mSelectedPosition;
5076 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005077 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5078 mResurrectToPosition = mNextSelectedPosition;
5079 }
5080 setSelectedPositionInt(INVALID_POSITION);
5081 setNextSelectedPositionInt(INVALID_POSITION);
5082 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005083 }
5084 }
5085
5086 /**
5087 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5088 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5089 * of items available in the adapter
5090 */
5091 int reconcileSelectedPosition() {
5092 int position = mSelectedPosition;
5093 if (position < 0) {
5094 position = mResurrectToPosition;
5095 }
5096 position = Math.max(0, position);
5097 position = Math.min(position, mItemCount - 1);
5098 return position;
5099 }
5100
5101 /**
5102 * Find the row closest to y. This row will be used as the motion row when scrolling
5103 *
5104 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005105 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005106 */
5107 abstract int findMotionRow(int y);
5108
5109 /**
Adam Powell637d3372010-08-25 14:37:03 -07005110 * Find the row closest to y. This row will be used as the motion row when scrolling.
5111 *
5112 * @param y Where the user touched
5113 * @return The position of the first (or only) item in the row closest to y
5114 */
5115 int findClosestMotionRow(int y) {
5116 final int childCount = getChildCount();
5117 if (childCount == 0) {
5118 return INVALID_POSITION;
5119 }
5120
5121 final int motionRow = findMotionRow(y);
5122 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5123 }
5124
5125 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005126 * Causes all the views to be rebuilt and redrawn.
5127 */
5128 public void invalidateViews() {
5129 mDataChanged = true;
5130 rememberSyncState();
5131 requestLayout();
5132 invalidate();
5133 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005134
5135 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005136 * If there is a selection returns false.
5137 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005138 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005139 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005140 if (mSelectedPosition < 0 && resurrectSelection()) {
5141 updateSelectorState();
5142 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005143 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005144 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005145 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005146
5147 /**
5148 * Makes the item at the supplied position selected.
5149 *
5150 * @param position the position of the new selection
5151 */
5152 abstract void setSelectionInt(int position);
5153
5154 /**
5155 * Attempt to bring the selection back if the user is switching from touch
5156 * to trackball mode
5157 * @return Whether selection was set to something.
5158 */
5159 boolean resurrectSelection() {
5160 final int childCount = getChildCount();
5161
5162 if (childCount <= 0) {
5163 return false;
5164 }
5165
5166 int selectedTop = 0;
5167 int selectedPos;
5168 int childrenTop = mListPadding.top;
5169 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5170 final int firstPosition = mFirstPosition;
5171 final int toPosition = mResurrectToPosition;
5172 boolean down = true;
5173
5174 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5175 selectedPos = toPosition;
5176
5177 final View selected = getChildAt(selectedPos - mFirstPosition);
5178 selectedTop = selected.getTop();
5179 int selectedBottom = selected.getBottom();
5180
5181 // We are scrolled, don't get in the fade
5182 if (selectedTop < childrenTop) {
5183 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5184 } else if (selectedBottom > childrenBottom) {
5185 selectedTop = childrenBottom - selected.getMeasuredHeight()
5186 - getVerticalFadingEdgeLength();
5187 }
5188 } else {
5189 if (toPosition < firstPosition) {
5190 // Default to selecting whatever is first
5191 selectedPos = firstPosition;
5192 for (int i = 0; i < childCount; i++) {
5193 final View v = getChildAt(i);
5194 final int top = v.getTop();
5195
5196 if (i == 0) {
5197 // Remember the position of the first item
5198 selectedTop = top;
5199 // See if we are scrolled at all
5200 if (firstPosition > 0 || top < childrenTop) {
5201 // If we are scrolled, don't select anything that is
5202 // in the fade region
5203 childrenTop += getVerticalFadingEdgeLength();
5204 }
5205 }
5206 if (top >= childrenTop) {
5207 // Found a view whose top is fully visisble
5208 selectedPos = firstPosition + i;
5209 selectedTop = top;
5210 break;
5211 }
5212 }
5213 } else {
5214 final int itemCount = mItemCount;
5215 down = false;
5216 selectedPos = firstPosition + childCount - 1;
5217
5218 for (int i = childCount - 1; i >= 0; i--) {
5219 final View v = getChildAt(i);
5220 final int top = v.getTop();
5221 final int bottom = v.getBottom();
5222
5223 if (i == childCount - 1) {
5224 selectedTop = top;
5225 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5226 childrenBottom -= getVerticalFadingEdgeLength();
5227 }
5228 }
5229
5230 if (bottom <= childrenBottom) {
5231 selectedPos = firstPosition + i;
5232 selectedTop = top;
5233 break;
5234 }
5235 }
5236 }
5237 }
5238
5239 mResurrectToPosition = INVALID_POSITION;
5240 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005241 if (mPositionScroller != null) {
5242 mPositionScroller.stop();
5243 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005244 mTouchMode = TOUCH_MODE_REST;
5245 clearScrollingCache();
5246 mSpecificTop = selectedTop;
5247 selectedPos = lookForSelectablePosition(selectedPos, down);
5248 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5249 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005250 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005251 setSelectionInt(selectedPos);
5252 invokeOnItemScrollListener();
5253 } else {
5254 selectedPos = INVALID_POSITION;
5255 }
5256 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5257
5258 return selectedPos >= 0;
5259 }
5260
Adam Powell14c08042011-10-06 19:46:18 -07005261 void confirmCheckedPositionsById() {
5262 // Clear out the positional check states, we'll rebuild it below from IDs.
5263 mCheckStates.clear();
5264
5265 boolean checkedCountChanged = false;
5266 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5267 final long id = mCheckedIdStates.keyAt(checkedIndex);
5268 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5269
5270 final long lastPosId = mAdapter.getItemId(lastPos);
5271 if (id != lastPosId) {
5272 // Look around to see if the ID is nearby. If not, uncheck it.
5273 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5274 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5275 boolean found = false;
5276 for (int searchPos = start; searchPos < end; searchPos++) {
5277 final long searchId = mAdapter.getItemId(searchPos);
5278 if (id == searchId) {
5279 found = true;
5280 mCheckStates.put(searchPos, true);
5281 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5282 break;
5283 }
5284 }
5285
5286 if (!found) {
5287 mCheckedIdStates.delete(id);
5288 checkedIndex--;
5289 mCheckedItemCount--;
5290 checkedCountChanged = true;
5291 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5292 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5293 lastPos, id, false);
5294 }
5295 }
5296 } else {
5297 mCheckStates.put(lastPos, true);
5298 }
5299 }
5300
5301 if (checkedCountChanged && mChoiceActionMode != null) {
5302 mChoiceActionMode.invalidate();
5303 }
5304 }
5305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005306 @Override
5307 protected void handleDataChanged() {
5308 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005309 int lastHandledItemCount = mLastHandledItemCount;
5310 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005311
5312 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5313 confirmCheckedPositionsById();
5314 }
5315
Adam Powell539ee872012-02-03 19:00:49 -08005316 // TODO: In the future we can recycle these views based on stable ID instead.
5317 mRecycler.clearTransientStateViews();
5318
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005319 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005320 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005321 int selectablePos;
5322
5323 // Find the row we are supposed to sync to
5324 if (mNeedSync) {
5325 // Update this first, since setNextSelectedPositionInt inspects it
5326 mNeedSync = false;
5327
Adam Powell07852792010-11-10 16:57:05 -08005328 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005329 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5330 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005331 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5332 if (mForceTranscriptScroll) {
5333 mForceTranscriptScroll = false;
5334 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5335 return;
5336 }
Adam Powell07852792010-11-10 16:57:05 -08005337 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005338 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005339 final View lastChild = getChildAt(childCount - 1);
5340 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005341 if (mFirstPosition + childCount >= lastHandledItemCount &&
5342 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005343 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5344 return;
5345 }
5346 // Something new came in and we didn't scroll; give the user a clue that
5347 // there's something new.
5348 awakenScrollBars();
5349 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005350
5351 switch (mSyncMode) {
5352 case SYNC_SELECTED_POSITION:
5353 if (isInTouchMode()) {
5354 // We saved our state when not in touch mode. (We know this because
5355 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5356 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5357 // adjusting if the available range changed) and return.
5358 mLayoutMode = LAYOUT_SYNC;
5359 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5360
5361 return;
5362 } else {
5363 // See if we can find a position in the new data with the same
5364 // id as the old selection. This will change mSyncPosition.
5365 newPos = findSyncPosition();
5366 if (newPos >= 0) {
5367 // Found it. Now verify that new selection is still selectable
5368 selectablePos = lookForSelectablePosition(newPos, true);
5369 if (selectablePos == newPos) {
5370 // Same row id is selected
5371 mSyncPosition = newPos;
5372
5373 if (mSyncHeight == getHeight()) {
5374 // If we are at the same height as when we saved state, try
5375 // to restore the scroll position too.
5376 mLayoutMode = LAYOUT_SYNC;
5377 } else {
5378 // We are not the same height as when the selection was saved, so
5379 // don't try to restore the exact position
5380 mLayoutMode = LAYOUT_SET_SELECTION;
5381 }
5382
5383 // Restore selection
5384 setNextSelectedPositionInt(newPos);
5385 return;
5386 }
5387 }
5388 }
5389 break;
5390 case SYNC_FIRST_POSITION:
5391 // Leave mSyncPosition as it is -- just pin to available range
5392 mLayoutMode = LAYOUT_SYNC;
5393 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5394
5395 return;
5396 }
5397 }
5398
5399 if (!isInTouchMode()) {
5400 // We couldn't find matching data -- try to use the same position
5401 newPos = getSelectedItemPosition();
5402
5403 // Pin position to the available range
5404 if (newPos >= count) {
5405 newPos = count - 1;
5406 }
5407 if (newPos < 0) {
5408 newPos = 0;
5409 }
5410
5411 // Make sure we select something selectable -- first look down
5412 selectablePos = lookForSelectablePosition(newPos, true);
5413
5414 if (selectablePos >= 0) {
5415 setNextSelectedPositionInt(selectablePos);
5416 return;
5417 } else {
5418 // Looking down didn't work -- try looking up
5419 selectablePos = lookForSelectablePosition(newPos, false);
5420 if (selectablePos >= 0) {
5421 setNextSelectedPositionInt(selectablePos);
5422 return;
5423 }
5424 }
5425 } else {
5426
5427 // We already know where we want to resurrect the selection
5428 if (mResurrectToPosition >= 0) {
5429 return;
5430 }
5431 }
5432
5433 }
5434
5435 // Nothing is selected. Give up and reset everything.
5436 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5437 mSelectedPosition = INVALID_POSITION;
5438 mSelectedRowId = INVALID_ROW_ID;
5439 mNextSelectedPosition = INVALID_POSITION;
5440 mNextSelectedRowId = INVALID_ROW_ID;
5441 mNeedSync = false;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005442 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005443 checkSelectionChanged();
5444 }
5445
Romain Guy43c9cdf2010-01-27 13:53:55 -08005446 @Override
5447 protected void onDisplayHint(int hint) {
5448 super.onDisplayHint(hint);
5449 switch (hint) {
5450 case INVISIBLE:
5451 if (mPopup != null && mPopup.isShowing()) {
5452 dismissPopup();
5453 }
5454 break;
5455 case VISIBLE:
5456 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5457 showPopup();
5458 }
5459 break;
5460 }
Romain Guy24562482010-02-01 14:56:19 -08005461 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005462 }
5463
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005464 /**
5465 * Removes the filter window
5466 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005467 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005468 if (mPopup != null) {
5469 mPopup.dismiss();
5470 }
5471 }
5472
5473 /**
5474 * Shows the filter window
5475 */
5476 private void showPopup() {
5477 // Make sure we have a window before showing the popup
5478 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005479 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005480 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005481 // Make sure we get focus if we are showing the popup
5482 checkFocus();
5483 }
5484 }
5485
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005486 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005487 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5488 final int[] xy = new int[2];
5489 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005490 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005491 // TODO: And the gravity should be defined in the theme as well
5492 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005493 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005494 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5495 xy[0], bottomGap);
5496 } else {
5497 mPopup.update(xy[0], bottomGap, -1, -1);
5498 }
5499 }
5500
5501 /**
5502 * What is the distance between the source and destination rectangles given the direction of
5503 * focus navigation between them? The direction basically helps figure out more quickly what is
5504 * self evident by the relationship between the rects...
5505 *
5506 * @param source the source rectangle
5507 * @param dest the destination rectangle
5508 * @param direction the direction
5509 * @return the distance between the rectangles
5510 */
5511 static int getDistance(Rect source, Rect dest, int direction) {
5512 int sX, sY; // source x, y
5513 int dX, dY; // dest x, y
5514 switch (direction) {
5515 case View.FOCUS_RIGHT:
5516 sX = source.right;
5517 sY = source.top + source.height() / 2;
5518 dX = dest.left;
5519 dY = dest.top + dest.height() / 2;
5520 break;
5521 case View.FOCUS_DOWN:
5522 sX = source.left + source.width() / 2;
5523 sY = source.bottom;
5524 dX = dest.left + dest.width() / 2;
5525 dY = dest.top;
5526 break;
5527 case View.FOCUS_LEFT:
5528 sX = source.left;
5529 sY = source.top + source.height() / 2;
5530 dX = dest.right;
5531 dY = dest.top + dest.height() / 2;
5532 break;
5533 case View.FOCUS_UP:
5534 sX = source.left + source.width() / 2;
5535 sY = source.top;
5536 dX = dest.left + dest.width() / 2;
5537 dY = dest.bottom;
5538 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005539 case View.FOCUS_FORWARD:
5540 case View.FOCUS_BACKWARD:
5541 sX = source.right + source.width() / 2;
5542 sY = source.top + source.height() / 2;
5543 dX = dest.left + dest.width() / 2;
5544 dY = dest.top + dest.height() / 2;
5545 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005546 default:
5547 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005548 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5549 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005550 }
5551 int deltaX = dX - sX;
5552 int deltaY = dY - sY;
5553 return deltaY * deltaY + deltaX * deltaX;
5554 }
5555
5556 @Override
5557 protected boolean isInFilterMode() {
5558 return mFiltered;
5559 }
5560
5561 /**
5562 * Sends a key to the text filter window
5563 *
5564 * @param keyCode The keycode for the event
5565 * @param event The actual key event
5566 *
5567 * @return True if the text filter handled the event, false otherwise.
5568 */
5569 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005570 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005571 return false;
5572 }
5573
5574 boolean handled = false;
5575 boolean okToSend = true;
5576 switch (keyCode) {
5577 case KeyEvent.KEYCODE_DPAD_UP:
5578 case KeyEvent.KEYCODE_DPAD_DOWN:
5579 case KeyEvent.KEYCODE_DPAD_LEFT:
5580 case KeyEvent.KEYCODE_DPAD_RIGHT:
5581 case KeyEvent.KEYCODE_DPAD_CENTER:
5582 case KeyEvent.KEYCODE_ENTER:
5583 okToSend = false;
5584 break;
5585 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005586 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005587 if (event.getAction() == KeyEvent.ACTION_DOWN
5588 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005589 KeyEvent.DispatcherState state = getKeyDispatcherState();
5590 if (state != null) {
5591 state.startTracking(event, this);
5592 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005593 handled = true;
5594 } else if (event.getAction() == KeyEvent.ACTION_UP
5595 && event.isTracking() && !event.isCanceled()) {
5596 handled = true;
5597 mTextFilter.setText("");
5598 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005599 }
5600 okToSend = false;
5601 break;
5602 case KeyEvent.KEYCODE_SPACE:
5603 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005604 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005605 break;
5606 }
5607
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005608 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005609 createTextFilter(true);
5610
5611 KeyEvent forwardEvent = event;
5612 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005613 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005614 }
5615
5616 int action = event.getAction();
5617 switch (action) {
5618 case KeyEvent.ACTION_DOWN:
5619 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5620 break;
5621
5622 case KeyEvent.ACTION_UP:
5623 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5624 break;
5625
5626 case KeyEvent.ACTION_MULTIPLE:
5627 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5628 break;
5629 }
5630 }
5631 return handled;
5632 }
5633
5634 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005635 * Return an InputConnection for editing of the filter text.
5636 */
5637 @Override
5638 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005639 if (isTextFilterEnabled()) {
5640 // XXX we need to have the text filter created, so we can get an
5641 // InputConnection to proxy to. Unfortunately this means we pretty
5642 // much need to make it as soon as a list view gets focus.
5643 createTextFilter(false);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005644 if (mPublicInputConnection == null) {
5645 mDefInputConnection = new BaseInputConnection(this, false);
5646 mPublicInputConnection = new InputConnectionWrapper(
5647 mTextFilter.onCreateInputConnection(outAttrs), true) {
5648 @Override
5649 public boolean reportFullscreenMode(boolean enabled) {
5650 // Use our own input connection, since it is
5651 // the "real" one the IME is talking with.
5652 return mDefInputConnection.reportFullscreenMode(enabled);
5653 }
5654
5655 @Override
5656 public boolean performEditorAction(int editorAction) {
5657 // The editor is off in its own window; we need to be
5658 // the one that does this.
5659 if (editorAction == EditorInfo.IME_ACTION_DONE) {
5660 InputMethodManager imm = (InputMethodManager)
5661 getContext().getSystemService(
5662 Context.INPUT_METHOD_SERVICE);
5663 if (imm != null) {
5664 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5665 }
5666 return true;
5667 }
5668 return false;
5669 }
5670
5671 @Override
5672 public boolean sendKeyEvent(KeyEvent event) {
5673 // Use our own input connection, since the filter
5674 // text view may not be shown in a window so has
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07005675 // no ViewAncestor to dispatch events with.
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005676 return mDefInputConnection.sendKeyEvent(event);
5677 }
5678 };
5679 }
5680 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
5681 | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5682 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5683 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005684 }
5685 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005686 }
Romain Guy0a637162009-05-29 14:43:54 -07005687
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005688 /**
5689 * For filtering we proxy an input connection to an internal text editor,
5690 * and this allows the proxying to happen.
5691 */
5692 @Override
5693 public boolean checkInputConnectionProxy(View view) {
5694 return view == mTextFilter;
5695 }
Romain Guy0a637162009-05-29 14:43:54 -07005696
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005697 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005698 * Creates the window for the text filter and populates it with an EditText field;
5699 *
5700 * @param animateEntrance true if the window should appear with an animation
5701 */
5702 private void createTextFilter(boolean animateEntrance) {
5703 if (mPopup == null) {
5704 Context c = getContext();
5705 PopupWindow p = new PopupWindow(c);
The Android Open Source Project4df24232009-03-05 14:34:35 -08005706 LayoutInflater layoutInflater = (LayoutInflater)
5707 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005708 mTextFilter = (EditText) layoutInflater.inflate(
5709 com.android.internal.R.layout.typing_filter, null);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005710 // For some reason setting this as the "real" input type changes
5711 // the text view in some way that it doesn't work, and I don't
5712 // want to figure out why this is.
5713 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5714 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
The Android Open Source Project10592532009-03-18 17:39:46 -07005715 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005716 mTextFilter.addTextChangedListener(this);
5717 p.setFocusable(false);
5718 p.setTouchable(false);
5719 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
5720 p.setContentView(mTextFilter);
5721 p.setWidth(LayoutParams.WRAP_CONTENT);
5722 p.setHeight(LayoutParams.WRAP_CONTENT);
5723 p.setBackgroundDrawable(null);
5724 mPopup = p;
5725 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005726 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005727 }
5728 if (animateEntrance) {
5729 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5730 } else {
5731 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5732 }
5733 }
5734
5735 /**
5736 * Clear the text filter.
5737 */
5738 public void clearTextFilter() {
5739 if (mFiltered) {
5740 mTextFilter.setText("");
5741 mFiltered = false;
5742 if (mPopup != null && mPopup.isShowing()) {
5743 dismissPopup();
5744 }
5745 }
5746 }
5747
5748 /**
5749 * Returns if the ListView currently has a text filter.
5750 */
5751 public boolean hasTextFilter() {
5752 return mFiltered;
5753 }
5754
5755 public void onGlobalLayout() {
5756 if (isShown()) {
5757 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08005758 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005759 showPopup();
5760 }
5761 } else {
5762 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08005763 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005764 dismissPopup();
5765 }
5766 }
5767
5768 }
5769
5770 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005771 * For our text watcher that is associated with the text filter. Does
5772 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005773 */
5774 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5775 }
5776
5777 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005778 * For our text watcher that is associated with the text filter. Performs
5779 * the actual filtering as the text changes, and takes care of hiding and
5780 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005781 */
5782 public void onTextChanged(CharSequence s, int start, int before, int count) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005783 if (mPopup != null && isTextFilterEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005784 int length = s.length();
5785 boolean showing = mPopup.isShowing();
5786 if (!showing && length > 0) {
5787 // Show the filter popup if necessary
5788 showPopup();
5789 mFiltered = true;
5790 } else if (showing && length == 0) {
5791 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005792 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005793 mFiltered = false;
5794 }
5795 if (mAdapter instanceof Filterable) {
5796 Filter f = ((Filterable) mAdapter).getFilter();
5797 // Filter should not be null when we reach this part
5798 if (f != null) {
5799 f.filter(s, this);
5800 } else {
5801 throw new IllegalStateException("You cannot call onTextChanged with a non "
5802 + "filterable adapter");
5803 }
5804 }
5805 }
5806 }
5807
5808 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005809 * For our text watcher that is associated with the text filter. Does
5810 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005811 */
5812 public void afterTextChanged(Editable s) {
5813 }
5814
5815 public void onFilterComplete(int count) {
5816 if (mSelectedPosition < 0 && count > 0) {
5817 mResurrectToPosition = INVALID_POSITION;
5818 resurrectSelection();
5819 }
5820 }
5821
5822 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08005823 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
5824 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
5825 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
5826 }
5827
5828 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005829 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
5830 return new LayoutParams(p);
5831 }
5832
5833 @Override
5834 public LayoutParams generateLayoutParams(AttributeSet attrs) {
5835 return new AbsListView.LayoutParams(getContext(), attrs);
5836 }
5837
5838 @Override
5839 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
5840 return p instanceof AbsListView.LayoutParams;
5841 }
5842
5843 /**
5844 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
5845 * to the bottom to show new items.
5846 *
5847 * @param mode the transcript mode to set
5848 *
5849 * @see #TRANSCRIPT_MODE_DISABLED
5850 * @see #TRANSCRIPT_MODE_NORMAL
5851 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
5852 */
5853 public void setTranscriptMode(int mode) {
5854 mTranscriptMode = mode;
5855 }
5856
5857 /**
5858 * Returns the current transcript mode.
5859 *
5860 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
5861 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
5862 */
5863 public int getTranscriptMode() {
5864 return mTranscriptMode;
5865 }
5866
5867 @Override
5868 public int getSolidColor() {
5869 return mCacheColorHint;
5870 }
5871
5872 /**
5873 * 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 -07005874 * on top of a solid, single-color, opaque background.
5875 *
5876 * Zero means that what's behind this object is translucent (non solid) or is not made of a
5877 * single color. This hint will not affect any existing background drawable set on this view (
5878 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005879 *
5880 * @param color The background color
5881 */
5882 public void setCacheColorHint(int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08005883 if (color != mCacheColorHint) {
5884 mCacheColorHint = color;
5885 int count = getChildCount();
5886 for (int i = 0; i < count; i++) {
5887 getChildAt(i).setDrawingCacheBackgroundColor(color);
5888 }
5889 mRecycler.setCacheColorHint(color);
5890 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005891 }
5892
5893 /**
5894 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
5895 * on top of a solid, single-color, opaque background
5896 *
5897 * @return The cache color hint
5898 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07005899 @ViewDebug.ExportedProperty(category = "drawing")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005900 public int getCacheColorHint() {
5901 return mCacheColorHint;
5902 }
5903
5904 /**
5905 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
5906 * List. This includes views displayed on the screen as well as views stored in AbsListView's
5907 * internal view recycler.
5908 *
5909 * @param views A list into which to put the reclaimed views
5910 */
5911 public void reclaimViews(List<View> views) {
5912 int childCount = getChildCount();
5913 RecyclerListener listener = mRecycler.mRecyclerListener;
5914
5915 // Reclaim views on screen
5916 for (int i = 0; i < childCount; i++) {
5917 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07005918 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005919 // Don't reclaim header or footer views, or views that should be ignored
5920 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
5921 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07005922 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005923 if (listener != null) {
5924 // Pretend they went through the scrap heap
5925 listener.onMovedToScrapHeap(child);
5926 }
5927 }
5928 }
5929 mRecycler.reclaimScrapViews(views);
5930 removeAllViewsInLayout();
5931 }
5932
Adam Powell637d3372010-08-25 14:37:03 -07005933 private void finishGlows() {
5934 if (mEdgeGlowTop != null) {
5935 mEdgeGlowTop.finish();
5936 mEdgeGlowBottom.finish();
5937 }
5938 }
5939
Romain Guy13922e02009-05-12 17:56:14 -07005940 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07005941 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
5942 * through the specified intent.
5943 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
5944 */
5945 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005946 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
5947 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07005948 if (mRemoteAdapter != null) {
5949 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
5950 Intent.FilterComparison fcOld = new Intent.FilterComparison(
5951 mRemoteAdapter.getRemoteViewsServiceIntent());
5952 if (fcNew.equals(fcOld)) {
5953 return;
5954 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005955 }
Adam Cohen2148d432011-07-28 14:59:54 -07005956 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005957 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07005958 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
5959 }
5960
5961 /**
Adam Cohen2148d432011-07-28 14:59:54 -07005962 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
5963 * connected yet.
5964 */
5965 public void deferNotifyDataSetChanged() {
5966 mDeferNotifyDataSetChanged = true;
5967 }
5968
5969 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07005970 * Called back when the adapter connects to the RemoteViewsService.
5971 */
Winson Chung16c8d8a2011-01-20 16:19:33 -08005972 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07005973 if (mRemoteAdapter != mAdapter) {
5974 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07005975 if (mDeferNotifyDataSetChanged) {
5976 mRemoteAdapter.notifyDataSetChanged();
5977 mDeferNotifyDataSetChanged = false;
5978 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08005979 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08005980 } else if (mRemoteAdapter != null) {
5981 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08005982 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07005983 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08005984 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07005985 }
5986
5987 /**
5988 * Called back when the adapter disconnects from the RemoteViewsService.
5989 */
5990 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08005991 // If the remote adapter disconnects, we keep it around
5992 // since the currently displayed items are still cached.
5993 // Further, we want the service to eventually reconnect
5994 // when necessary, as triggered by this view requesting
5995 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07005996 }
5997
5998 /**
Adam Cohenb9673922012-01-05 13:58:47 -08005999 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6000 * being displayed by the AbsListView.
6001 */
6002 void setVisibleRangeHint(int start, int end) {
6003 if (mRemoteAdapter != null) {
6004 mRemoteAdapter.setVisibleRangeHint(start, end);
6005 }
6006 }
6007
6008 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006009 * Sets the recycler listener to be notified whenever a View is set aside in
6010 * the recycler for later reuse. This listener can be used to free resources
6011 * associated to the View.
6012 *
6013 * @param listener The recycler listener to be notified of views set aside
6014 * in the recycler.
6015 *
6016 * @see android.widget.AbsListView.RecycleBin
6017 * @see android.widget.AbsListView.RecyclerListener
6018 */
6019 public void setRecyclerListener(RecyclerListener listener) {
6020 mRecycler.mRecyclerListener = listener;
6021 }
6022
Adam Powellb1f498a2011-01-18 20:43:23 -08006023 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6024 @Override
6025 public void onChanged() {
6026 super.onChanged();
6027 if (mFastScroller != null) {
6028 mFastScroller.onSectionsChanged();
6029 }
6030 }
6031
6032 @Override
6033 public void onInvalidated() {
6034 super.onInvalidated();
6035 if (mFastScroller != null) {
6036 mFastScroller.onSectionsChanged();
6037 }
6038 }
6039 }
6040
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006041 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07006042 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6043 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6044 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6045 * selects and deselects list items.
6046 */
6047 public interface MultiChoiceModeListener extends ActionMode.Callback {
6048 /**
6049 * Called when an item is checked or unchecked during selection mode.
6050 *
6051 * @param mode The {@link ActionMode} providing the selection mode
6052 * @param position Adapter position of the item that was checked or unchecked
6053 * @param id Adapter ID of the item that was checked or unchecked
6054 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6055 * if the item is now unchecked.
6056 */
6057 public void onItemCheckedStateChanged(ActionMode mode,
6058 int position, long id, boolean checked);
6059 }
6060
6061 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6062 private MultiChoiceModeListener mWrapped;
6063
6064 public void setWrapped(MultiChoiceModeListener wrapped) {
6065 mWrapped = wrapped;
6066 }
6067
6068 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6069 if (mWrapped.onCreateActionMode(mode, menu)) {
6070 // Initialize checked graphic state?
6071 setLongClickable(false);
6072 return true;
6073 }
6074 return false;
6075 }
6076
6077 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6078 return mWrapped.onPrepareActionMode(mode, menu);
6079 }
6080
6081 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6082 return mWrapped.onActionItemClicked(mode, item);
6083 }
6084
6085 public void onDestroyActionMode(ActionMode mode) {
6086 mWrapped.onDestroyActionMode(mode);
6087 mChoiceActionMode = null;
6088
6089 // Ending selection mode means deselecting everything.
6090 clearChoices();
6091
6092 mDataChanged = true;
6093 rememberSyncState();
6094 requestLayout();
6095
6096 setLongClickable(true);
6097 }
6098
6099 public void onItemCheckedStateChanged(ActionMode mode,
6100 int position, long id, boolean checked) {
6101 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6102
6103 // If there are no items selected we no longer need the selection mode.
6104 if (getCheckedItemCount() == 0) {
6105 mode.finish();
6106 }
6107 }
6108 }
6109
6110 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006111 * AbsListView extends LayoutParams to provide a place to hold the view type.
6112 */
6113 public static class LayoutParams extends ViewGroup.LayoutParams {
6114 /**
6115 * View type for this view, as returned by
6116 * {@link android.widget.Adapter#getItemViewType(int) }
6117 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006118 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006119 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6120 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6121 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006122 int viewType;
6123
The Android Open Source Project4df24232009-03-05 14:34:35 -08006124 /**
6125 * When this boolean is set, the view has been added to the AbsListView
6126 * at least once. It is used to know whether headers/footers have already
6127 * been added to the list view and whether they should be treated as
6128 * recycled views or not.
6129 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006130 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006131 boolean recycledHeaderFooter;
6132
Romain Guy0bf88592010-03-02 13:38:44 -08006133 /**
6134 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6135 * to obtain children views to measure itself. When doing so, the children
6136 * are not attached to the window, but put in the recycler which assumes
6137 * they've been attached before. Setting this flag will force the reused
6138 * view to be attached to the window rather than just attached to the
6139 * parent.
6140 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006141 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006142 boolean forceAdd;
6143
Dianne Hackborn079e2352010-10-18 17:02:43 -07006144 /**
6145 * The position the view was removed from when pulled out of the
6146 * scrap heap.
6147 * @hide
6148 */
6149 int scrappedFromPosition;
6150
Adam Powell539ee872012-02-03 19:00:49 -08006151 /**
6152 * The ID the view represents
6153 */
6154 long itemId = -1;
6155
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006156 public LayoutParams(Context c, AttributeSet attrs) {
6157 super(c, attrs);
6158 }
6159
6160 public LayoutParams(int w, int h) {
6161 super(w, h);
6162 }
6163
6164 public LayoutParams(int w, int h, int viewType) {
6165 super(w, h);
6166 this.viewType = viewType;
6167 }
6168
6169 public LayoutParams(ViewGroup.LayoutParams source) {
6170 super(source);
6171 }
6172 }
6173
6174 /**
6175 * A RecyclerListener is used to receive a notification whenever a View is placed
6176 * inside the RecycleBin's scrap heap. This listener is used to free resources
6177 * associated to Views placed in the RecycleBin.
6178 *
6179 * @see android.widget.AbsListView.RecycleBin
6180 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6181 */
6182 public static interface RecyclerListener {
6183 /**
6184 * Indicates that the specified View was moved into the recycler's scrap heap.
6185 * The view is not displayed on screen any more and any expensive resource
6186 * associated with the view should be discarded.
6187 *
6188 * @param view
6189 */
6190 void onMovedToScrapHeap(View view);
6191 }
6192
6193 /**
6194 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6195 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6196 * start of a layout. By construction, they are displaying current information. At the end of
6197 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6198 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6199 *
6200 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6201 * @see android.widget.AbsListView.RecyclerListener
6202 */
6203 class RecycleBin {
6204 private RecyclerListener mRecyclerListener;
6205
6206 /**
6207 * The position of the first view stored in mActiveViews.
6208 */
6209 private int mFirstActivePosition;
6210
6211 /**
6212 * Views that were on screen at the start of layout. This array is populated at the start of
6213 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6214 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6215 * view store in mFirstActivePosition.
6216 */
6217 private View[] mActiveViews = new View[0];
6218
6219 /**
6220 * Unsorted views that can be used by the adapter as a convert view.
6221 */
6222 private ArrayList<View>[] mScrapViews;
6223
6224 private int mViewTypeCount;
6225
6226 private ArrayList<View> mCurrentScrap;
6227
Adam Powell539ee872012-02-03 19:00:49 -08006228 private ArrayList<View> mSkippedScrap;
6229
6230 private SparseArray<View> mTransientStateViews;
6231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006232 public void setViewTypeCount(int viewTypeCount) {
6233 if (viewTypeCount < 1) {
6234 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6235 }
6236 //noinspection unchecked
6237 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6238 for (int i = 0; i < viewTypeCount; i++) {
6239 scrapViews[i] = new ArrayList<View>();
6240 }
6241 mViewTypeCount = viewTypeCount;
6242 mCurrentScrap = scrapViews[0];
6243 mScrapViews = scrapViews;
6244 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006245
Adam Powellf3c2eda2010-03-16 17:31:01 -07006246 public void markChildrenDirty() {
6247 if (mViewTypeCount == 1) {
6248 final ArrayList<View> scrap = mCurrentScrap;
6249 final int scrapCount = scrap.size();
6250 for (int i = 0; i < scrapCount; i++) {
6251 scrap.get(i).forceLayout();
6252 }
6253 } else {
6254 final int typeCount = mViewTypeCount;
6255 for (int i = 0; i < typeCount; i++) {
6256 final ArrayList<View> scrap = mScrapViews[i];
6257 final int scrapCount = scrap.size();
6258 for (int j = 0; j < scrapCount; j++) {
6259 scrap.get(j).forceLayout();
6260 }
6261 }
6262 }
Adam Powell539ee872012-02-03 19:00:49 -08006263 if (mTransientStateViews != null) {
6264 final int count = mTransientStateViews.size();
6265 for (int i = 0; i < count; i++) {
6266 mTransientStateViews.valueAt(i).forceLayout();
6267 }
6268 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006269 }
Romain Guy0a637162009-05-29 14:43:54 -07006270
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006271 public boolean shouldRecycleViewType(int viewType) {
6272 return viewType >= 0;
6273 }
6274
6275 /**
6276 * Clears the scrap heap.
6277 */
6278 void clear() {
6279 if (mViewTypeCount == 1) {
6280 final ArrayList<View> scrap = mCurrentScrap;
6281 final int scrapCount = scrap.size();
6282 for (int i = 0; i < scrapCount; i++) {
6283 removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
6284 }
6285 } else {
6286 final int typeCount = mViewTypeCount;
6287 for (int i = 0; i < typeCount; i++) {
6288 final ArrayList<View> scrap = mScrapViews[i];
6289 final int scrapCount = scrap.size();
6290 for (int j = 0; j < scrapCount; j++) {
6291 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6292 }
6293 }
6294 }
Adam Powell539ee872012-02-03 19:00:49 -08006295 if (mTransientStateViews != null) {
6296 mTransientStateViews.clear();
6297 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006298 }
6299
6300 /**
6301 * Fill ActiveViews with all of the children of the AbsListView.
6302 *
6303 * @param childCount The minimum number of views mActiveViews should hold
6304 * @param firstActivePosition The position of the first view that will be stored in
6305 * mActiveViews
6306 */
6307 void fillActiveViews(int childCount, int firstActivePosition) {
6308 if (mActiveViews.length < childCount) {
6309 mActiveViews = new View[childCount];
6310 }
6311 mFirstActivePosition = firstActivePosition;
6312
6313 final View[] activeViews = mActiveViews;
6314 for (int i = 0; i < childCount; i++) {
6315 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006316 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006317 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006318 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006319 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6320 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006321 activeViews[i] = child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006322 }
6323 }
6324 }
6325
6326 /**
6327 * Get the view corresponding to the specified position. The view will be removed from
6328 * mActiveViews if it is found.
6329 *
6330 * @param position The position to look up in mActiveViews
6331 * @return The view if it is found, null otherwise
6332 */
6333 View getActiveView(int position) {
6334 int index = position - mFirstActivePosition;
6335 final View[] activeViews = mActiveViews;
6336 if (index >=0 && index < activeViews.length) {
6337 final View match = activeViews[index];
6338 activeViews[index] = null;
6339 return match;
6340 }
6341 return null;
6342 }
6343
Adam Powell539ee872012-02-03 19:00:49 -08006344 View getTransientStateView(int position) {
6345 if (mTransientStateViews == null) {
6346 return null;
6347 }
6348 final int index = mTransientStateViews.indexOfKey(position);
6349 if (index < 0) {
6350 return null;
6351 }
6352 final View result = mTransientStateViews.valueAt(index);
6353 mTransientStateViews.removeAt(index);
6354 return result;
6355 }
6356
6357 /**
6358 * Dump any currently saved views with transient state.
6359 */
6360 void clearTransientStateViews() {
6361 if (mTransientStateViews != null) {
6362 mTransientStateViews.clear();
6363 }
6364 }
6365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006366 /**
6367 * @return A view from the ScrapViews collection. These are unordered.
6368 */
6369 View getScrapView(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006370 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006371 return retrieveFromScrap(mCurrentScrap, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006372 } else {
6373 int whichScrap = mAdapter.getItemViewType(position);
6374 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006375 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006376 }
6377 }
6378 return null;
6379 }
6380
6381 /**
Adam Powell539ee872012-02-03 19:00:49 -08006382 * Put a view into the ScrapViews list. These views are unordered.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006383 *
6384 * @param scrap The view to add
6385 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006386 void addScrapView(View scrap, int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006387 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
6388 if (lp == null) {
6389 return;
6390 }
6391
Adam Powell539ee872012-02-03 19:00:49 -08006392 lp.scrappedFromPosition = position;
6393
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006394 // Don't put header or footer views or views that should be ignored
6395 // into the scrap heap
6396 int viewType = lp.viewType;
Adam Powell539ee872012-02-03 19:00:49 -08006397 final boolean scrapHasTransientState = scrap.hasTransientState();
6398 if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
6399 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
6400 if (mSkippedScrap == null) {
6401 mSkippedScrap = new ArrayList<View>();
6402 }
6403 mSkippedScrap.add(scrap);
6404 }
6405 if (scrapHasTransientState) {
6406 if (mTransientStateViews == null) {
6407 mTransientStateViews = new SparseArray<View>();
6408 }
Adam Powell057a5852012-05-11 10:28:38 -07006409 scrap.dispatchStartTemporaryDetach();
Adam Powell539ee872012-02-03 19:00:49 -08006410 mTransientStateViews.put(position, scrap);
Romain Guy9b1bb812010-02-26 14:14:13 -08006411 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006412 return;
6413 }
6414
Adam Powell539ee872012-02-03 19:00:49 -08006415 scrap.dispatchStartTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006416 if (mViewTypeCount == 1) {
6417 mCurrentScrap.add(scrap);
6418 } else {
6419 mScrapViews[viewType].add(scrap);
6420 }
6421
alanvc1d7e772012-05-08 14:47:24 -07006422 scrap.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006423 if (mRecyclerListener != null) {
6424 mRecyclerListener.onMovedToScrapHeap(scrap);
6425 }
6426 }
6427
6428 /**
Adam Powell539ee872012-02-03 19:00:49 -08006429 * Finish the removal of any views that skipped the scrap heap.
6430 */
6431 void removeSkippedScrap() {
6432 if (mSkippedScrap == null) {
6433 return;
6434 }
6435 final int count = mSkippedScrap.size();
6436 for (int i = 0; i < count; i++) {
6437 removeDetachedView(mSkippedScrap.get(i), false);
6438 }
6439 mSkippedScrap.clear();
6440 }
6441
6442 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006443 * Move all views remaining in mActiveViews to mScrapViews.
6444 */
6445 void scrapActiveViews() {
6446 final View[] activeViews = mActiveViews;
6447 final boolean hasListener = mRecyclerListener != null;
6448 final boolean multipleScraps = mViewTypeCount > 1;
6449
6450 ArrayList<View> scrapViews = mCurrentScrap;
6451 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006452 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006453 final View victim = activeViews[i];
6454 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006455 final AbsListView.LayoutParams lp
6456 = (AbsListView.LayoutParams) victim.getLayoutParams();
6457 int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006458
6459 activeViews[i] = null;
6460
Adam Powell539ee872012-02-03 19:00:49 -08006461 final boolean scrapHasTransientState = victim.hasTransientState();
6462 if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006463 // Do not move views that should be ignored
Adam Powell539ee872012-02-03 19:00:49 -08006464 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
6465 scrapHasTransientState) {
Romain Guy9b1bb812010-02-26 14:14:13 -08006466 removeDetachedView(victim, false);
6467 }
Adam Powell539ee872012-02-03 19:00:49 -08006468 if (scrapHasTransientState) {
6469 if (mTransientStateViews == null) {
6470 mTransientStateViews = new SparseArray<View>();
6471 }
6472 mTransientStateViews.put(mFirstActivePosition + i, victim);
6473 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006474 continue;
6475 }
6476
6477 if (multipleScraps) {
6478 scrapViews = mScrapViews[whichScrap];
6479 }
Romain Guya440b002010-02-24 15:57:54 -08006480 victim.dispatchStartTemporaryDetach();
Dianne Hackborn079e2352010-10-18 17:02:43 -07006481 lp.scrappedFromPosition = mFirstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006482 scrapViews.add(victim);
6483
alanvc1d7e772012-05-08 14:47:24 -07006484 victim.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006485 if (hasListener) {
6486 mRecyclerListener.onMovedToScrapHeap(victim);
6487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006488 }
6489 }
6490
6491 pruneScrapViews();
6492 }
6493
6494 /**
6495 * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
6496 * (This can happen if an adapter does not recycle its views).
6497 */
6498 private void pruneScrapViews() {
6499 final int maxViews = mActiveViews.length;
6500 final int viewTypeCount = mViewTypeCount;
6501 final ArrayList<View>[] scrapViews = mScrapViews;
6502 for (int i = 0; i < viewTypeCount; ++i) {
6503 final ArrayList<View> scrapPile = scrapViews[i];
6504 int size = scrapPile.size();
6505 final int extras = size - maxViews;
6506 size--;
6507 for (int j = 0; j < extras; j++) {
6508 removeDetachedView(scrapPile.remove(size--), false);
6509 }
6510 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006511
6512 if (mTransientStateViews != null) {
6513 for (int i = 0; i < mTransientStateViews.size(); i++) {
6514 final View v = mTransientStateViews.valueAt(i);
6515 if (!v.hasTransientState()) {
6516 mTransientStateViews.removeAt(i);
6517 i--;
6518 }
6519 }
6520 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006521 }
6522
6523 /**
6524 * Puts all views in the scrap heap into the supplied list.
6525 */
6526 void reclaimScrapViews(List<View> views) {
6527 if (mViewTypeCount == 1) {
6528 views.addAll(mCurrentScrap);
6529 } else {
6530 final int viewTypeCount = mViewTypeCount;
6531 final ArrayList<View>[] scrapViews = mScrapViews;
6532 for (int i = 0; i < viewTypeCount; ++i) {
6533 final ArrayList<View> scrapPile = scrapViews[i];
6534 views.addAll(scrapPile);
6535 }
6536 }
6537 }
Romain Guy52e2ef82010-01-14 12:11:48 -08006538
6539 /**
6540 * Updates the cache color hint of all known views.
6541 *
6542 * @param color The new cache color hint.
6543 */
6544 void setCacheColorHint(int color) {
6545 if (mViewTypeCount == 1) {
6546 final ArrayList<View> scrap = mCurrentScrap;
6547 final int scrapCount = scrap.size();
6548 for (int i = 0; i < scrapCount; i++) {
6549 scrap.get(i).setDrawingCacheBackgroundColor(color);
6550 }
6551 } else {
6552 final int typeCount = mViewTypeCount;
6553 for (int i = 0; i < typeCount; i++) {
6554 final ArrayList<View> scrap = mScrapViews[i];
6555 final int scrapCount = scrap.size();
6556 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07006557 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08006558 }
6559 }
6560 }
6561 // Just in case this is called during a layout pass
6562 final View[] activeViews = mActiveViews;
6563 final int count = activeViews.length;
6564 for (int i = 0; i < count; ++i) {
6565 final View victim = activeViews[i];
6566 if (victim != null) {
6567 victim.setDrawingCacheBackgroundColor(color);
6568 }
6569 }
6570 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006571 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006572
6573 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6574 int size = scrapViews.size();
6575 if (size > 0) {
6576 // See if we still have a view for this position.
6577 for (int i=0; i<size; i++) {
6578 View view = scrapViews.get(i);
6579 if (((AbsListView.LayoutParams)view.getLayoutParams())
6580 .scrappedFromPosition == position) {
6581 scrapViews.remove(i);
6582 return view;
6583 }
6584 }
6585 return scrapViews.remove(size - 1);
6586 } else {
6587 return null;
6588 }
6589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006590}