blob: 94dadb4379b624ab04cac478ac95484e583de0c3 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070020import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.graphics.drawable.TransitionDrawable;
alanvc1d7e772012-05-08 14:47:24 -070026import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.Debug;
28import android.os.Handler;
29import android.os.Parcel;
30import android.os.Parcelable;
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -080031import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.text.Editable;
33import android.text.TextUtils;
34import android.text.TextWatcher;
35import android.util.AttributeSet;
Gilles Debunne52964242010-02-24 11:05:19 -080036import android.util.Log;
Adam Powellf343e1b2010-08-13 18:27:04 -070037import android.util.LongSparseArray;
Adam Powell539ee872012-02-03 19:00:49 -080038import android.util.SparseArray;
Adam Powellf343e1b2010-08-13 18:27:04 -070039import android.util.SparseBooleanArray;
Dianne Hackborn079e2352010-10-18 17:02:43 -070040import android.util.StateSet;
Adam Powellf343e1b2010-08-13 18:27:04 -070041import android.view.ActionMode;
Adam Powell637d3372010-08-25 14:37:03 -070042import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.Gravity;
44import android.view.HapticFeedbackConstants;
Jeff Brown33bbfd22011-02-24 20:55:35 -080045import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.view.KeyEvent;
47import android.view.LayoutInflater;
Adam Powellf343e1b2010-08-13 18:27:04 -070048import android.view.Menu;
49import android.view.MenuItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.view.MotionEvent;
51import android.view.VelocityTracker;
52import android.view.View;
53import android.view.ViewConfiguration;
54import android.view.ViewDebug;
55import android.view.ViewGroup;
Michael Jurka13451a42011-08-22 15:54:21 -070056import android.view.ViewParent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.view.ViewTreeObserver;
Svetoslav Ganova0156172011-06-26 17:55:44 -070058import android.view.accessibility.AccessibilityEvent;
alanvc1d7e772012-05-08 14:47:24 -070059import android.view.accessibility.AccessibilityManager;
Svetoslav Ganova0156172011-06-26 17:55:44 -070060import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell0b8acd82012-04-25 20:29:23 -070061import android.view.animation.Interpolator;
62import android.view.animation.LinearInterpolator;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070063import android.view.inputmethod.BaseInputConnection;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070064import android.view.inputmethod.EditorInfo;
65import android.view.inputmethod.InputConnection;
Dianne Hackborn1bf5e222009-03-24 19:11:58 -070066import android.view.inputmethod.InputConnectionWrapper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import android.view.inputmethod.InputMethodManager;
Adam Cohena6a4cbc2012-09-26 17:36:40 -070068import android.widget.RemoteViews.OnClickHandler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
Adam Cohen335c3b62012-07-24 17:18:16 -070070import com.android.internal.R;
71
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072import java.util.ArrayList;
73import java.util.List;
74
75/**
Romain Guyd6a463a2009-05-21 23:10:10 -070076 * Base class that can be used to implement virtualized lists of items. A list does
77 * not have a spatial definition here. For instance, subclases of this class can
78 * display the content of the list in a grid, in a carousel, as stack, etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 *
80 * @attr ref android.R.styleable#AbsListView_listSelector
81 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
82 * @attr ref android.R.styleable#AbsListView_stackFromBottom
83 * @attr ref android.R.styleable#AbsListView_scrollingCache
84 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
85 * @attr ref android.R.styleable#AbsListView_transcriptMode
86 * @attr ref android.R.styleable#AbsListView_cacheColorHint
87 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
88 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
Adam Powellf343e1b2010-08-13 18:27:04 -070089 * @attr ref android.R.styleable#AbsListView_choiceMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 */
91public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
92 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
Winson Chung499cb9f2010-07-16 11:18:17 -070093 ViewTreeObserver.OnTouchModeChangeListener,
94 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095
Romain Guy9d849a22012-03-14 16:41:42 -070096 @SuppressWarnings("UnusedDeclaration")
Adam Powell539ee872012-02-03 19:00:49 -080097 private static final String TAG = "AbsListView";
98
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 /**
100 * Disables the transcript mode.
101 *
102 * @see #setTranscriptMode(int)
103 */
104 public static final int TRANSCRIPT_MODE_DISABLED = 0;
105 /**
106 * The list will automatically scroll to the bottom when a data set change
107 * notification is received and only if the last item is already visible
108 * on screen.
109 *
110 * @see #setTranscriptMode(int)
111 */
112 public static final int TRANSCRIPT_MODE_NORMAL = 1;
113 /**
114 * The list will automatically scroll to the bottom, no matter what items
Romain Guy0a637162009-05-29 14:43:54 -0700115 * are currently visible.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 *
117 * @see #setTranscriptMode(int)
118 */
119 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
120
121 /**
122 * Indicates that we are not in the middle of a touch gesture
123 */
124 static final int TOUCH_MODE_REST = -1;
125
126 /**
127 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
128 * scroll gesture.
129 */
130 static final int TOUCH_MODE_DOWN = 0;
131
132 /**
133 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
134 * is a longpress
135 */
136 static final int TOUCH_MODE_TAP = 1;
137
138 /**
139 * Indicates we have waited for everything we can wait for, but the user's finger is still down
140 */
141 static final int TOUCH_MODE_DONE_WAITING = 2;
142
143 /**
144 * Indicates the touch gesture is a scroll
145 */
146 static final int TOUCH_MODE_SCROLL = 3;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800147
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 /**
149 * Indicates the view is in the process of being flung
150 */
151 static final int TOUCH_MODE_FLING = 4;
Romain Guy0a637162009-05-29 14:43:54 -0700152
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 /**
Adam Powell637d3372010-08-25 14:37:03 -0700154 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
155 */
156 static final int TOUCH_MODE_OVERSCROLL = 5;
157
158 /**
159 * Indicates the view is being flung outside of normal content bounds
160 * and will spring back.
161 */
162 static final int TOUCH_MODE_OVERFLING = 6;
163
164 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 * Regular layout - usually an unsolicited layout from the view system
166 */
167 static final int LAYOUT_NORMAL = 0;
168
169 /**
170 * Show the first item
171 */
172 static final int LAYOUT_FORCE_TOP = 1;
173
174 /**
175 * Force the selected item to be on somewhere on the screen
176 */
177 static final int LAYOUT_SET_SELECTION = 2;
178
179 /**
180 * Show the last item
181 */
182 static final int LAYOUT_FORCE_BOTTOM = 3;
183
184 /**
185 * Make a mSelectedItem appear in a specific location and build the rest of
186 * the views from there. The top is specified by mSpecificTop.
187 */
188 static final int LAYOUT_SPECIFIC = 4;
189
190 /**
191 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
192 * at mSpecificTop
193 */
194 static final int LAYOUT_SYNC = 5;
195
196 /**
197 * Layout as a result of using the navigation keys
198 */
199 static final int LAYOUT_MOVE_SELECTION = 6;
200
201 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700202 * Normal list that does not indicate choices
203 */
204 public static final int CHOICE_MODE_NONE = 0;
205
206 /**
207 * The list allows up to one choice
208 */
209 public static final int CHOICE_MODE_SINGLE = 1;
210
211 /**
212 * The list allows multiple choices
213 */
214 public static final int CHOICE_MODE_MULTIPLE = 2;
215
216 /**
217 * The list allows multiple choices in a modal selection mode
218 */
219 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
220
221 /**
222 * Controls if/how the user may choose/check items in the list
223 */
224 int mChoiceMode = CHOICE_MODE_NONE;
225
226 /**
227 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
228 */
229 ActionMode mChoiceActionMode;
230
231 /**
232 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
233 * a few extra actions around what application code does.
234 */
235 MultiChoiceModeWrapper mMultiChoiceModeCallback;
236
237 /**
238 * Running count of how many items are currently checked
239 */
240 int mCheckedItemCount;
241
242 /**
243 * Running state of which positions are currently checked
244 */
245 SparseBooleanArray mCheckStates;
246
247 /**
Adam Powell14c08042011-10-06 19:46:18 -0700248 * Running state of which IDs are currently checked.
249 * If there is a value for a given key, the checked state for that ID is true
250 * and the value holds the last known position in the adapter for that id.
Adam Powellf343e1b2010-08-13 18:27:04 -0700251 */
Adam Powell14c08042011-10-06 19:46:18 -0700252 LongSparseArray<Integer> mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700253
254 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 * Controls how the next layout will happen
256 */
257 int mLayoutMode = LAYOUT_NORMAL;
258
259 /**
260 * Should be used by subclasses to listen to changes in the dataset
261 */
262 AdapterDataSetObserver mDataSetObserver;
263
264 /**
265 * The adapter containing the data to be displayed by this view
266 */
267 ListAdapter mAdapter;
268
269 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700270 * The remote adapter containing the data to be displayed by this view to be set
271 */
272 private RemoteViewsAdapter mRemoteAdapter;
273
274 /**
Adam Powell539ee872012-02-03 19:00:49 -0800275 * If mAdapter != null, whenever this is true the adapter has stable IDs.
276 */
277 boolean mAdapterHasStableIds;
278
279 /**
Adam Cohen2148d432011-07-28 14:59:54 -0700280 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
281 */
282 private boolean mDeferNotifyDataSetChanged = false;
283
284 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 * Indicates whether the list selector should be drawn on top of the children or behind
286 */
287 boolean mDrawSelectorOnTop = false;
288
289 /**
290 * The drawable used to draw the selector
291 */
292 Drawable mSelector;
293
294 /**
Dianne Hackborn079e2352010-10-18 17:02:43 -0700295 * The current position of the selector in the list.
296 */
297 int mSelectorPosition = INVALID_POSITION;
298
299 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 * Defines the selector's location and dimension at drawing time
301 */
302 Rect mSelectorRect = new Rect();
303
304 /**
305 * The data set used to store unused views that should be reused during the next layout
306 * to avoid creating new ones
307 */
308 final RecycleBin mRecycler = new RecycleBin();
309
310 /**
311 * The selection's left padding
312 */
313 int mSelectionLeftPadding = 0;
314
315 /**
316 * The selection's top padding
317 */
318 int mSelectionTopPadding = 0;
319
320 /**
321 * The selection's right padding
322 */
323 int mSelectionRightPadding = 0;
324
325 /**
326 * The selection's bottom padding
327 */
328 int mSelectionBottomPadding = 0;
329
330 /**
331 * This view's padding
332 */
333 Rect mListPadding = new Rect();
334
335 /**
336 * Subclasses must retain their measure spec from onMeasure() into this member
337 */
338 int mWidthMeasureSpec = 0;
339
340 /**
341 * The top scroll indicator
342 */
343 View mScrollUp;
344
345 /**
346 * The down scroll indicator
347 */
348 View mScrollDown;
349
350 /**
351 * When the view is scrolling, this flag is set to true to indicate subclasses that
352 * the drawing cache was enabled on the children
353 */
354 boolean mCachingStarted;
Romain Guy0211a0a2011-02-14 16:34:59 -0800355 boolean mCachingActive;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356
357 /**
358 * The position of the view that received the down motion event
359 */
360 int mMotionPosition;
361
362 /**
363 * The offset to the top of the mMotionPosition view when the down motion event was received
364 */
365 int mMotionViewOriginalTop;
366
367 /**
368 * The desired offset to the top of the mMotionPosition view after a scroll
369 */
370 int mMotionViewNewTop;
371
372 /**
373 * The X value associated with the the down motion event
374 */
375 int mMotionX;
376
377 /**
378 * The Y value associated with the the down motion event
379 */
380 int mMotionY;
381
382 /**
383 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
384 * TOUCH_MODE_DONE_WAITING
385 */
386 int mTouchMode = TOUCH_MODE_REST;
387
388 /**
389 * Y value from on the previous motion event (if any)
390 */
391 int mLastY;
392
393 /**
394 * How far the finger moved before we started scrolling
395 */
396 int mMotionCorrection;
397
398 /**
399 * Determines speed during touch scrolling
400 */
401 private VelocityTracker mVelocityTracker;
402
403 /**
404 * Handles one frame of a fling
405 */
406 private FlingRunnable mFlingRunnable;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800407
Adam Powell45803472010-01-25 15:10:44 -0800408 /**
409 * Handles scrolling between positions within the list.
410 */
Adam Powell1fa179ef2012-04-12 15:01:40 -0700411 PositionScroller mPositionScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412
413 /**
414 * The offset in pixels form the top of the AdapterView to the top
415 * of the currently selected view. Used to save and restore state.
416 */
417 int mSelectedTop = 0;
418
419 /**
420 * Indicates whether the list is stacked from the bottom edge or
421 * the top edge.
422 */
423 boolean mStackFromBottom;
424
425 /**
426 * When set to true, the list automatically discards the children's
427 * bitmap cache after scrolling.
428 */
429 boolean mScrollingCacheEnabled;
Romain Guy0a637162009-05-29 14:43:54 -0700430
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 /**
432 * Whether or not to enable the fast scroll feature on this list
433 */
434 boolean mFastScrollEnabled;
435
436 /**
437 * Optional callback to notify client when scroll position has changed
438 */
439 private OnScrollListener mOnScrollListener;
440
441 /**
442 * Keeps track of our accessory window
443 */
444 PopupWindow mPopup;
445
446 /**
447 * Used with type filter window
448 */
449 EditText mTextFilter;
450
451 /**
452 * Indicates whether to use pixels-based or position-based scrollbar
453 * properties.
454 */
455 private boolean mSmoothScrollbarEnabled = true;
456
457 /**
458 * Indicates that this view supports filtering
459 */
460 private boolean mTextFilterEnabled;
461
462 /**
463 * Indicates that this view is currently displaying a filtered view of the data
464 */
465 private boolean mFiltered;
466
467 /**
468 * Rectangle used for hit testing children
469 */
470 private Rect mTouchFrame;
471
472 /**
473 * The position to resurrect the selected position to.
474 */
475 int mResurrectToPosition = INVALID_POSITION;
476
477 private ContextMenuInfo mContextMenuInfo = null;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800478
Adam Powell0b8bb422010-02-08 14:30:45 -0800479 /**
Adam Powell637d3372010-08-25 14:37:03 -0700480 * Maximum distance to record overscroll
481 */
482 int mOverscrollMax;
483
484 /**
485 * Content height divided by this is the overscroll limit.
486 */
487 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
488
489 /**
Adam Powell14c08042011-10-06 19:46:18 -0700490 * How many positions in either direction we will search to try to
491 * find a checked item with a stable ID that moved position across
492 * a data set change. If the item isn't found it will be unselected.
493 */
494 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
495
496 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 * Used to request a layout when we changed touch mode
498 */
499 private static final int TOUCH_MODE_UNKNOWN = -1;
500 private static final int TOUCH_MODE_ON = 0;
501 private static final int TOUCH_MODE_OFF = 1;
502
503 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
504
505 private static final boolean PROFILE_SCROLLING = false;
506 private boolean mScrollProfilingStarted = false;
507
508 private static final boolean PROFILE_FLINGING = false;
509 private boolean mFlingProfilingStarted = false;
510
511 /**
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -0800512 * The StrictMode "critical time span" objects to catch animation
513 * stutters. Non-null when a time-sensitive animation is
514 * in-flight. Must call finish() on them when done animating.
515 * These are no-ops on user builds.
516 */
517 private StrictMode.Span mScrollStrictSpan = null;
518 private StrictMode.Span mFlingStrictSpan = null;
519
520 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 * The last CheckForLongPress runnable we posted, if any
522 */
523 private CheckForLongPress mPendingCheckForLongPress;
524
525 /**
526 * The last CheckForTap runnable we posted, if any
527 */
528 private Runnable mPendingCheckForTap;
Romain Guy0a637162009-05-29 14:43:54 -0700529
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 /**
531 * The last CheckForKeyLongPress runnable we posted, if any
532 */
533 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
534
535 /**
536 * Acts upon click
537 */
538 private AbsListView.PerformClick mPerformClick;
539
540 /**
Dianne Hackbornd173fa32010-12-23 13:58:22 -0800541 * Delayed action for touch mode.
542 */
543 private Runnable mTouchModeReset;
544
545 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 * This view is in transcript mode -- it shows the bottom of the list when the data
547 * changes
548 */
549 private int mTranscriptMode;
550
551 /**
552 * Indicates that this list is always drawn on top of a solid, single-color, opaque
553 * background
554 */
555 private int mCacheColorHint;
556
557 /**
558 * The select child's view (from the adapter's getView) is enabled.
559 */
560 private boolean mIsChildViewEnabled;
561
562 /**
563 * The last scroll state reported to clients through {@link OnScrollListener}.
564 */
565 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
566
567 /**
568 * Helper object that renders and controls the fast scroll thumb.
569 */
570 private FastScroller mFastScroller;
571
Romain Guyd6a463a2009-05-21 23:10:10 -0700572 private boolean mGlobalLayoutListenerAddedFilter;
573
574 private int mTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 private float mDensityScale;
576
Dianne Hackborn1bf5e222009-03-24 19:11:58 -0700577 private InputConnection mDefInputConnection;
578 private InputConnectionWrapper mPublicInputConnection;
Romain Guy6dfed242009-05-11 18:25:05 -0700579
580 private Runnable mClearScrollingCache;
Adam Powell161abf32012-05-23 17:22:49 -0700581 Runnable mPositionScrollAfterLayout;
Romain Guy4296fc42009-07-06 11:48:52 -0700582 private int mMinimumVelocity;
583 private int mMaximumVelocity;
Romain Guy21317d12010-10-12 13:32:31 -0700584 private float mVelocityScale = 1.0f;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800585
Romain Guy21875052010-01-06 18:48:08 -0800586 final boolean[] mIsScrap = new boolean[1];
Mindy Pereira4e30d892010-11-24 15:32:39 -0800587
Romain Guy24562482010-02-01 14:56:19 -0800588 // True when the popup should be hidden because of a call to
589 // dispatchDisplayHint()
590 private boolean mPopupHidden;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800591
Adam Powell4cd47702010-02-25 11:21:14 -0800592 /**
593 * ID of the active pointer. This is used to retain consistency during
594 * drags/flings if multiple pointers are used.
595 */
596 private int mActivePointerId = INVALID_POINTER;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800597
Adam Powell4cd47702010-02-25 11:21:14 -0800598 /**
599 * Sentinel value for no current active pointer.
600 * Used by {@link #mActivePointerId}.
601 */
602 private static final int INVALID_POINTER = -1;
Romain Guy6dfed242009-05-11 18:25:05 -0700603
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 /**
Adam Powell637d3372010-08-25 14:37:03 -0700605 * Maximum distance to overscroll by during edge effects
606 */
607 int mOverscrollDistance;
608
609 /**
610 * Maximum distance to overfling during edge effects
611 */
612 int mOverflingDistance;
613
614 // These two EdgeGlows are always set and used together.
615 // Checking one for null is as good as checking both.
616
617 /**
618 * Tracks the state of the top edge glow.
619 */
Adam Powell89935e42011-08-31 14:26:12 -0700620 private EdgeEffect mEdgeGlowTop;
Adam Powell637d3372010-08-25 14:37:03 -0700621
622 /**
623 * Tracks the state of the bottom edge glow.
624 */
Adam Powell89935e42011-08-31 14:26:12 -0700625 private EdgeEffect mEdgeGlowBottom;
Adam Powell637d3372010-08-25 14:37:03 -0700626
627 /**
628 * An estimate of how many pixels are between the top of the list and
629 * the top of the first position in the adapter, based on the last time
630 * we saw it. Used to hint where to draw edge glows.
631 */
632 private int mFirstPositionDistanceGuess;
633
634 /**
635 * An estimate of how many pixels are between the bottom of the list and
636 * the bottom of the last position in the adapter, based on the last time
637 * we saw it. Used to hint where to draw edge glows.
638 */
639 private int mLastPositionDistanceGuess;
640
641 /**
642 * Used for determining when to cancel out of overscroll.
643 */
644 private int mDirection = 0;
645
646 /**
Adam Powellda13dba2010-12-05 13:47:23 -0800647 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
648 * the bottom correctly on resizes.
649 */
650 private boolean mForceTranscriptScroll;
651
Adam Powell07d6f7b2011-03-02 14:27:30 -0800652 private int mGlowPaddingLeft;
653 private int mGlowPaddingRight;
654
alanvc1d7e772012-05-08 14:47:24 -0700655 /**
656 * Used for interacting with list items from an accessibility service.
657 */
658 private ListItemAccessibilityDelegate mAccessibilityDelegate;
659
Svetoslav Ganov4e03f592011-07-29 22:17:14 -0700660 private int mLastAccessibilityScrollEventFromIndex;
661 private int mLastAccessibilityScrollEventToIndex;
662
Adam Powellda13dba2010-12-05 13:47:23 -0800663 /**
Adam Powellb3750132011-08-08 23:29:12 -0700664 * Track if we are currently attached to a window.
665 */
Adam Powella2b986e2011-09-14 14:21:33 -0700666 boolean mIsAttached;
Adam Powellb3750132011-08-08 23:29:12 -0700667
668 /**
Adam Powellee78b172011-08-16 16:39:20 -0700669 * Track the item count from the last time we handled a data change.
670 */
671 private int mLastHandledItemCount;
672
673 /**
Adam Powell0b8acd82012-04-25 20:29:23 -0700674 * Used for smooth scrolling at a consistent rate
675 */
676 static final Interpolator sLinearInterpolator = new LinearInterpolator();
677
678 /**
Dianne Hackborne181bd92012-09-25 14:15:15 -0700679 * The saved state that we will be restoring from when we next sync.
680 * Kept here so that if we happen to be asked to save our state before
681 * the sync happens, we can return this existing data rather than losing
682 * it.
683 */
684 private SavedState mPendingSync;
685
686 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 * Interface definition for a callback to be invoked when the list or grid
688 * has been scrolled.
689 */
690 public interface OnScrollListener {
691
692 /**
693 * The view is not scrolling. Note navigating the list using the trackball counts as
694 * being in the idle state since these transitions are not animated.
695 */
696 public static int SCROLL_STATE_IDLE = 0;
697
698 /**
699 * The user is scrolling using touch, and their finger is still on the screen
700 */
701 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
702
703 /**
704 * The user had previously been scrolling using touch and had performed a fling. The
705 * animation is now coasting to a stop
706 */
707 public static int SCROLL_STATE_FLING = 2;
708
709 /**
710 * Callback method to be invoked while the list view or grid view is being scrolled. If the
711 * view is being scrolled, this method will be called before the next frame of the scroll is
712 * rendered. In particular, it will be called before any calls to
713 * {@link Adapter#getView(int, View, ViewGroup)}.
714 *
715 * @param view The view whose scroll state is being reported
716 *
717 * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
718 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
719 */
720 public void onScrollStateChanged(AbsListView view, int scrollState);
721
722 /**
723 * Callback method to be invoked when the list or grid has been scrolled. This will be
724 * called after the scroll has completed
725 * @param view The view whose scroll state is being reported
726 * @param firstVisibleItem the index of the first visible cell (ignore if
727 * visibleItemCount == 0)
728 * @param visibleItemCount the number of visible cells
729 * @param totalItemCount the number of items in the list adaptor
730 */
731 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
732 int totalItemCount);
733 }
734
Dianne Hackborne2136772010-11-04 15:08:59 -0700735 /**
736 * The top-level view of a list item can implement this interface to allow
737 * itself to modify the bounds of the selection shown for that item.
738 */
739 public interface SelectionBoundsAdjuster {
740 /**
741 * Called to allow the list item to adjust the bounds shown for
742 * its selection.
743 *
744 * @param bounds On call, this contains the bounds the list has
745 * selected for the item (that is the bounds of the entire view). The
746 * values can be modified as desired.
747 */
748 public void adjustListItemSelectionBounds(Rect bounds);
749 }
750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 public AbsListView(Context context) {
752 super(context);
753 initAbsListView();
754
755 setVerticalScrollBarEnabled(true);
756 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
757 initializeScrollbars(a);
758 a.recycle();
759 }
760
761 public AbsListView(Context context, AttributeSet attrs) {
762 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
763 }
764
765 public AbsListView(Context context, AttributeSet attrs, int defStyle) {
766 super(context, attrs, defStyle);
767 initAbsListView();
768
769 TypedArray a = context.obtainStyledAttributes(attrs,
770 com.android.internal.R.styleable.AbsListView, defStyle, 0);
771
772 Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
773 if (d != null) {
774 setSelector(d);
775 }
776
777 mDrawSelectorOnTop = a.getBoolean(
778 com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false);
779
780 boolean stackFromBottom = a.getBoolean(R.styleable.AbsListView_stackFromBottom, false);
781 setStackFromBottom(stackFromBottom);
782
783 boolean scrollingCacheEnabled = a.getBoolean(R.styleable.AbsListView_scrollingCache, true);
784 setScrollingCacheEnabled(scrollingCacheEnabled);
785
786 boolean useTextFilter = a.getBoolean(R.styleable.AbsListView_textFilterEnabled, false);
787 setTextFilterEnabled(useTextFilter);
788
789 int transcriptMode = a.getInt(R.styleable.AbsListView_transcriptMode,
790 TRANSCRIPT_MODE_DISABLED);
791 setTranscriptMode(transcriptMode);
792
793 int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
794 setCacheColorHint(color);
Romain Guy0a637162009-05-29 14:43:54 -0700795
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
797 setFastScrollEnabled(enableFastScroll);
798
799 boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
800 setSmoothScrollbarEnabled(smoothScrollbar);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800801
Adam Powellf343e1b2010-08-13 18:27:04 -0700802 setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
Adam Powell20232d02010-12-08 21:08:53 -0800803 setFastScrollAlwaysVisible(
804 a.getBoolean(R.styleable.AbsListView_fastScrollAlwaysVisible, false));
Adam Powellf343e1b2010-08-13 18:27:04 -0700805
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800806 a.recycle();
807 }
808
Romain Guyd6a463a2009-05-21 23:10:10 -0700809 private void initAbsListView() {
810 // Setting focusable in touch mode will set the focusable property to true
Romain Guydf016072009-08-17 12:51:30 -0700811 setClickable(true);
Romain Guyd6a463a2009-05-21 23:10:10 -0700812 setFocusableInTouchMode(true);
813 setWillNotDraw(false);
814 setAlwaysDrawnWithCacheEnabled(false);
815 setScrollingCacheEnabled(true);
816
Romain Guy4296fc42009-07-06 11:48:52 -0700817 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
818 mTouchSlop = configuration.getScaledTouchSlop();
819 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
820 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700821 mOverscrollDistance = configuration.getScaledOverscrollDistance();
822 mOverflingDistance = configuration.getScaledOverflingDistance();
823
Romain Guyd6a463a2009-05-21 23:10:10 -0700824 mDensityScale = getContext().getResources().getDisplayMetrics().density;
825 }
Romain Guy0a637162009-05-29 14:43:54 -0700826
Adam Powell637d3372010-08-25 14:37:03 -0700827 @Override
828 public void setOverScrollMode(int mode) {
829 if (mode != OVER_SCROLL_NEVER) {
830 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -0800831 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -0700832 mEdgeGlowTop = new EdgeEffect(context);
833 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -0700834 }
835 } else {
836 mEdgeGlowTop = null;
837 mEdgeGlowBottom = null;
838 }
839 super.setOverScrollMode(mode);
840 }
841
Romain Guyd6a463a2009-05-21 23:10:10 -0700842 /**
Adam Powellf343e1b2010-08-13 18:27:04 -0700843 * {@inheritDoc}
844 */
845 @Override
846 public void setAdapter(ListAdapter adapter) {
847 if (adapter != null) {
Adam Powell539ee872012-02-03 19:00:49 -0800848 mAdapterHasStableIds = mAdapter.hasStableIds();
849 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
Adam Powellf343e1b2010-08-13 18:27:04 -0700850 mCheckedIdStates == null) {
Adam Powell14c08042011-10-06 19:46:18 -0700851 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -0700852 }
853 }
854
855 if (mCheckStates != null) {
856 mCheckStates.clear();
857 }
858
859 if (mCheckedIdStates != null) {
860 mCheckedIdStates.clear();
861 }
862 }
863
864 /**
865 * Returns the number of items currently selected. This will only be valid
866 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
867 *
868 * <p>To determine the specific items that are currently selected, use one of
869 * the <code>getChecked*</code> methods.
870 *
871 * @return The number of items currently selected
872 *
873 * @see #getCheckedItemPosition()
874 * @see #getCheckedItemPositions()
875 * @see #getCheckedItemIds()
876 */
877 public int getCheckedItemCount() {
878 return mCheckedItemCount;
879 }
880
881 /**
882 * Returns the checked state of the specified position. The result is only
883 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
884 * or {@link #CHOICE_MODE_MULTIPLE}.
885 *
886 * @param position The item whose checked state to return
887 * @return The item's checked state or <code>false</code> if choice mode
888 * is invalid
889 *
890 * @see #setChoiceMode(int)
891 */
892 public boolean isItemChecked(int position) {
893 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
894 return mCheckStates.get(position);
895 }
896
897 return false;
898 }
899
900 /**
901 * Returns the currently checked item. The result is only valid if the choice
902 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
903 *
904 * @return The position of the currently checked item or
905 * {@link #INVALID_POSITION} if nothing is selected
906 *
907 * @see #setChoiceMode(int)
908 */
909 public int getCheckedItemPosition() {
910 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
911 return mCheckStates.keyAt(0);
912 }
913
914 return INVALID_POSITION;
915 }
916
917 /**
918 * Returns the set of checked items in the list. The result is only valid if
919 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
920 *
921 * @return A SparseBooleanArray which will return true for each call to
922 * get(int position) where position is a position in the list,
923 * or <code>null</code> if the choice mode is set to
924 * {@link #CHOICE_MODE_NONE}.
925 */
926 public SparseBooleanArray getCheckedItemPositions() {
927 if (mChoiceMode != CHOICE_MODE_NONE) {
928 return mCheckStates;
929 }
930 return null;
931 }
932
933 /**
934 * Returns the set of checked items ids. The result is only valid if the
935 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
936 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
937 *
938 * @return A new array which contains the id of each checked item in the
939 * list.
940 */
941 public long[] getCheckedItemIds() {
942 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
943 return new long[0];
944 }
945
Adam Powell14c08042011-10-06 19:46:18 -0700946 final LongSparseArray<Integer> idStates = mCheckedIdStates;
Adam Powellf343e1b2010-08-13 18:27:04 -0700947 final int count = idStates.size();
948 final long[] ids = new long[count];
949
950 for (int i = 0; i < count; i++) {
951 ids[i] = idStates.keyAt(i);
952 }
953
954 return ids;
955 }
956
957 /**
958 * Clear any choices previously set
959 */
960 public void clearChoices() {
961 if (mCheckStates != null) {
962 mCheckStates.clear();
963 }
964 if (mCheckedIdStates != null) {
965 mCheckedIdStates.clear();
966 }
967 mCheckedItemCount = 0;
968 }
969
970 /**
971 * Sets the checked state of the specified position. The is only valid if
972 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
973 * {@link #CHOICE_MODE_MULTIPLE}.
974 *
975 * @param position The item whose checked state is to be checked
976 * @param value The new checked state for the item
977 */
978 public void setItemChecked(int position, boolean value) {
979 if (mChoiceMode == CHOICE_MODE_NONE) {
980 return;
981 }
982
983 // Start selection mode if needed. We don't need to if we're unchecking something.
984 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
Adam Powella7981702012-08-24 12:43:41 -0700985 if (mMultiChoiceModeCallback == null ||
986 !mMultiChoiceModeCallback.hasWrappedCallback()) {
987 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
988 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
989 "supplied. Call setMultiChoiceModeListener to set a callback.");
990 }
Adam Powellf343e1b2010-08-13 18:27:04 -0700991 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
992 }
993
994 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
995 boolean oldValue = mCheckStates.get(position);
996 mCheckStates.put(position, value);
997 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
998 if (value) {
Adam Powell14c08042011-10-06 19:46:18 -0700999 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001000 } else {
1001 mCheckedIdStates.delete(mAdapter.getItemId(position));
1002 }
1003 }
1004 if (oldValue != value) {
1005 if (value) {
1006 mCheckedItemCount++;
1007 } else {
1008 mCheckedItemCount--;
1009 }
1010 }
1011 if (mChoiceActionMode != null) {
1012 final long id = mAdapter.getItemId(position);
1013 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1014 position, id, value);
1015 }
1016 } else {
1017 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1018 // Clear all values if we're checking something, or unchecking the currently
1019 // selected item
1020 if (value || isItemChecked(position)) {
1021 mCheckStates.clear();
1022 if (updateIds) {
1023 mCheckedIdStates.clear();
1024 }
1025 }
1026 // this may end up selecting the value we just cleared but this way
1027 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1028 if (value) {
1029 mCheckStates.put(position, true);
1030 if (updateIds) {
Adam Powell14c08042011-10-06 19:46:18 -07001031 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001032 }
1033 mCheckedItemCount = 1;
1034 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1035 mCheckedItemCount = 0;
1036 }
1037 }
1038
1039 // Do not generate a data change while we are in the layout phase
1040 if (!mInLayout && !mBlockLayoutRequests) {
1041 mDataChanged = true;
1042 rememberSyncState();
1043 requestLayout();
1044 }
1045 }
1046
1047 @Override
1048 public boolean performItemClick(View view, int position, long id) {
1049 boolean handled = false;
Adam Powellbf5f2b32010-10-24 16:45:44 -07001050 boolean dispatchItemClick = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001051
1052 if (mChoiceMode != CHOICE_MODE_NONE) {
1053 handled = true;
Adam Powell29382d92012-02-23 11:03:22 -08001054 boolean checkedStateChanged = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001055
1056 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1057 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001058 boolean checked = !mCheckStates.get(position, false);
1059 mCheckStates.put(position, checked);
Adam Powellf343e1b2010-08-13 18:27:04 -07001060 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001061 if (checked) {
Adam Powell14c08042011-10-06 19:46:18 -07001062 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001063 } else {
1064 mCheckedIdStates.delete(mAdapter.getItemId(position));
1065 }
1066 }
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001067 if (checked) {
Adam Powellf343e1b2010-08-13 18:27:04 -07001068 mCheckedItemCount++;
1069 } else {
1070 mCheckedItemCount--;
1071 }
1072 if (mChoiceActionMode != null) {
1073 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001074 position, id, checked);
Adam Powellbf5f2b32010-10-24 16:45:44 -07001075 dispatchItemClick = false;
Adam Powellf343e1b2010-08-13 18:27:04 -07001076 }
Adam Powell29382d92012-02-23 11:03:22 -08001077 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001078 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001079 boolean checked = !mCheckStates.get(position, false);
Fabrice Di Meglio5bb4e292012-10-02 15:53:00 -07001080 if (checked) {
Adam Powellf3b8e6f2012-10-04 14:53:36 -07001081 mCheckStates.clear();
Adam Powellf343e1b2010-08-13 18:27:04 -07001082 mCheckStates.put(position, true);
1083 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1084 mCheckedIdStates.clear();
Adam Powell14c08042011-10-06 19:46:18 -07001085 mCheckedIdStates.put(mAdapter.getItemId(position), position);
Adam Powellf343e1b2010-08-13 18:27:04 -07001086 }
1087 mCheckedItemCount = 1;
1088 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1089 mCheckedItemCount = 0;
1090 }
Adam Powell29382d92012-02-23 11:03:22 -08001091 checkedStateChanged = true;
Adam Powellf343e1b2010-08-13 18:27:04 -07001092 }
1093
Adam Powell29382d92012-02-23 11:03:22 -08001094 if (checkedStateChanged) {
1095 updateOnScreenCheckedViews();
1096 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001097 }
1098
Adam Powellbf5f2b32010-10-24 16:45:44 -07001099 if (dispatchItemClick) {
1100 handled |= super.performItemClick(view, position, id);
1101 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001102
1103 return handled;
1104 }
1105
1106 /**
Adam Powell29382d92012-02-23 11:03:22 -08001107 * Perform a quick, in-place update of the checked or activated state
1108 * on all visible item views. This should only be called when a valid
1109 * choice mode is active.
1110 */
1111 private void updateOnScreenCheckedViews() {
1112 final int firstPos = mFirstPosition;
1113 final int count = getChildCount();
1114 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1115 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1116 for (int i = 0; i < count; i++) {
1117 final View child = getChildAt(i);
1118 final int position = firstPos + i;
1119
1120 if (child instanceof Checkable) {
1121 ((Checkable) child).setChecked(mCheckStates.get(position));
1122 } else if (useActivated) {
1123 child.setActivated(mCheckStates.get(position));
1124 }
1125 }
1126 }
1127
1128 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07001129 * @see #setChoiceMode(int)
1130 *
1131 * @return The current choice mode
1132 */
1133 public int getChoiceMode() {
1134 return mChoiceMode;
1135 }
1136
1137 /**
1138 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1139 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1140 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1141 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1142 *
1143 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1144 * {@link #CHOICE_MODE_MULTIPLE}
1145 */
1146 public void setChoiceMode(int choiceMode) {
1147 mChoiceMode = choiceMode;
1148 if (mChoiceActionMode != null) {
1149 mChoiceActionMode.finish();
1150 mChoiceActionMode = null;
1151 }
1152 if (mChoiceMode != CHOICE_MODE_NONE) {
1153 if (mCheckStates == null) {
1154 mCheckStates = new SparseBooleanArray();
1155 }
1156 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
Adam Powell14c08042011-10-06 19:46:18 -07001157 mCheckedIdStates = new LongSparseArray<Integer>();
Adam Powellf343e1b2010-08-13 18:27:04 -07001158 }
1159 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1160 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1161 clearChoices();
1162 setLongClickable(true);
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1169 * selection {@link ActionMode}. Only used when the choice mode is set to
1170 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1171 *
1172 * @param listener Listener that will manage the selection mode
1173 *
1174 * @see #setChoiceMode(int)
1175 */
1176 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1177 if (mMultiChoiceModeCallback == null) {
1178 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1179 }
1180 mMultiChoiceModeCallback.setWrapped(listener);
1181 }
1182
1183 /**
Adam Powell637d3372010-08-25 14:37:03 -07001184 * @return true if all list content currently fits within the view boundaries
1185 */
1186 private boolean contentFits() {
1187 final int childCount = getChildCount();
Adam Powell2bed5702011-01-23 19:17:53 -08001188 if (childCount == 0) return true;
1189 if (childCount != mItemCount) return false;
Adam Powell637d3372010-08-25 14:37:03 -07001190
Adam Powell4ce35412011-01-24 14:55:00 -08001191 return getChildAt(0).getTop() >= mListPadding.top &&
1192 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07001193 }
1194
1195 /**
Romain Guy0a637162009-05-29 14:43:54 -07001196 * Enables fast scrolling by letting the user quickly scroll through lists by
1197 * dragging the fast scroll thumb. The adapter attached to the list may want
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198 * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
Romain Guy0a637162009-05-29 14:43:54 -07001199 * jump between sections of the list.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001200 * @see SectionIndexer
1201 * @see #isFastScrollEnabled()
1202 * @param enabled whether or not to enable fast scrolling
1203 */
1204 public void setFastScrollEnabled(boolean enabled) {
1205 mFastScrollEnabled = enabled;
1206 if (enabled) {
1207 if (mFastScroller == null) {
1208 mFastScroller = new FastScroller(getContext(), this);
1209 }
1210 } else {
1211 if (mFastScroller != null) {
1212 mFastScroller.stop();
1213 mFastScroller = null;
1214 }
1215 }
1216 }
Romain Guy0a637162009-05-29 14:43:54 -07001217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 /**
Adam Powell20232d02010-12-08 21:08:53 -08001219 * Set whether or not the fast scroller should always be shown in place of the
1220 * standard scrollbars. Fast scrollers shown in this way will not fade out and will
1221 * be a permanent fixture within the list. Best combined with an inset scroll bar style
1222 * that will ensure enough padding. This will enable fast scrolling if it is not
1223 * already enabled.
1224 *
1225 * @param alwaysShow true if the fast scroller should always be displayed.
1226 * @see #setScrollBarStyle(int)
1227 * @see #setFastScrollEnabled(boolean)
1228 */
1229 public void setFastScrollAlwaysVisible(boolean alwaysShow) {
1230 if (alwaysShow && !mFastScrollEnabled) {
1231 setFastScrollEnabled(true);
1232 }
1233
1234 if (mFastScroller != null) {
1235 mFastScroller.setAlwaysShow(alwaysShow);
1236 }
1237
1238 computeOpaqueFlags();
1239 recomputePadding();
1240 }
1241
1242 /**
1243 * Returns true if the fast scroller is set to always show on this view rather than
1244 * fade out when not in use.
1245 *
1246 * @return true if the fast scroller will always show.
1247 * @see #setFastScrollAlwaysVisible(boolean)
1248 */
1249 public boolean isFastScrollAlwaysVisible() {
1250 return mFastScrollEnabled && mFastScroller.isAlwaysShowEnabled();
1251 }
1252
1253 @Override
1254 public int getVerticalScrollbarWidth() {
Adam Powell16bb80a2010-12-09 23:30:50 -08001255 if (isFastScrollAlwaysVisible()) {
Adam Powell20232d02010-12-08 21:08:53 -08001256 return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth());
1257 }
1258 return super.getVerticalScrollbarWidth();
1259 }
1260
1261 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 * Returns the current state of the fast scroll feature.
1263 * @see #setFastScrollEnabled(boolean)
1264 * @return true if fast scroll is enabled, false otherwise
1265 */
1266 @ViewDebug.ExportedProperty
1267 public boolean isFastScrollEnabled() {
1268 return mFastScrollEnabled;
1269 }
Romain Guy0a637162009-05-29 14:43:54 -07001270
Adam Powell20232d02010-12-08 21:08:53 -08001271 @Override
1272 public void setVerticalScrollbarPosition(int position) {
1273 super.setVerticalScrollbarPosition(position);
1274 if (mFastScroller != null) {
1275 mFastScroller.setScrollbarPosition(position);
1276 }
1277 }
1278
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279 /**
1280 * If fast scroll is visible, then don't draw the vertical scrollbar.
Romain Guy0a637162009-05-29 14:43:54 -07001281 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 */
1283 @Override
1284 protected boolean isVerticalScrollBarHidden() {
1285 return mFastScroller != null && mFastScroller.isVisible();
1286 }
1287
1288 /**
1289 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1290 * is computed based on the number of visible pixels in the visible items. This
1291 * however assumes that all list items have the same height. If you use a list in
1292 * which items have different heights, the scrollbar will change appearance as the
1293 * user scrolls through the list. To avoid this issue, you need to disable this
1294 * property.
1295 *
1296 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1297 * is based solely on the number of items in the adapter and the position of the
1298 * visible items inside the adapter. This provides a stable scrollbar as the user
Romain Guy0a637162009-05-29 14:43:54 -07001299 * navigates through a list of items with varying heights.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 *
1301 * @param enabled Whether or not to enable smooth scrollbar.
1302 *
Romain Guy0a637162009-05-29 14:43:54 -07001303 * @see #setSmoothScrollbarEnabled(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1305 */
1306 public void setSmoothScrollbarEnabled(boolean enabled) {
1307 mSmoothScrollbarEnabled = enabled;
1308 }
1309
1310 /**
1311 * Returns the current state of the fast scroll feature.
1312 *
1313 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1314 *
1315 * @see #setSmoothScrollbarEnabled(boolean)
1316 */
1317 @ViewDebug.ExportedProperty
1318 public boolean isSmoothScrollbarEnabled() {
1319 return mSmoothScrollbarEnabled;
1320 }
1321
1322 /**
1323 * Set the listener that will receive notifications every time the list scrolls.
1324 *
1325 * @param l the scroll listener
1326 */
1327 public void setOnScrollListener(OnScrollListener l) {
1328 mOnScrollListener = l;
1329 invokeOnItemScrollListener();
1330 }
1331
1332 /**
1333 * Notify our scroll listener (if there is one) of a change in scroll state
1334 */
1335 void invokeOnItemScrollListener() {
1336 if (mFastScroller != null) {
1337 mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1338 }
1339 if (mOnScrollListener != null) {
1340 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1341 }
Gilles Debunne0a1b8182011-02-28 16:01:09 -08001342 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343 }
1344
Svetoslav Ganova0156172011-06-26 17:55:44 -07001345 @Override
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001346 public void sendAccessibilityEvent(int eventType) {
1347 // Since this class calls onScrollChanged even if the mFirstPosition and the
1348 // child count have not changed we will avoid sending duplicate accessibility
1349 // events.
1350 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001351 final int firstVisiblePosition = getFirstVisiblePosition();
1352 final int lastVisiblePosition = getLastVisiblePosition();
1353 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1354 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
alanv9c3e0e62012-05-18 17:43:35 -07001355 return;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001356 } else {
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001357 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1358 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07001359 }
1360 }
1361 super.sendAccessibilityEvent(eventType);
1362 }
1363
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001364 @Override
1365 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1366 super.onInitializeAccessibilityEvent(event);
1367 event.setClassName(AbsListView.class.getName());
1368 }
1369
1370 @Override
1371 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1372 super.onInitializeAccessibilityNodeInfo(info);
1373 info.setClassName(AbsListView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001374 if (isEnabled()) {
1375 if (getFirstVisiblePosition() > 0) {
1376 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001377 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001378 }
1379 if (getLastVisiblePosition() < getCount() - 1) {
1380 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001381 info.setScrollable(true);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001382 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001383 }
1384 }
1385
1386 @Override
1387 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001388 if (super.performAccessibilityAction(action, arguments)) {
1389 return true;
1390 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001391 switch (action) {
1392 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001393 if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001394 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1395 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1396 return true;
1397 }
1398 } return false;
1399 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001400 if (isEnabled() && mFirstPosition > 0) {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001401 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1402 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1403 return true;
1404 }
1405 } return false;
1406 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -07001407 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001408 }
1409
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001410 /**
1411 * Indicates whether the children's drawing cache is used during a scroll.
1412 * By default, the drawing cache is enabled but this will consume more memory.
1413 *
1414 * @return true if the scrolling cache is enabled, false otherwise
1415 *
1416 * @see #setScrollingCacheEnabled(boolean)
1417 * @see View#setDrawingCacheEnabled(boolean)
1418 */
1419 @ViewDebug.ExportedProperty
1420 public boolean isScrollingCacheEnabled() {
1421 return mScrollingCacheEnabled;
1422 }
1423
1424 /**
1425 * Enables or disables the children's drawing cache during a scroll.
1426 * By default, the drawing cache is enabled but this will use more memory.
1427 *
1428 * When the scrolling cache is enabled, the caches are kept after the
1429 * first scrolling. You can manually clear the cache by calling
1430 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1431 *
1432 * @param enabled true to enable the scroll cache, false otherwise
1433 *
1434 * @see #isScrollingCacheEnabled()
1435 * @see View#setDrawingCacheEnabled(boolean)
1436 */
1437 public void setScrollingCacheEnabled(boolean enabled) {
1438 if (mScrollingCacheEnabled && !enabled) {
1439 clearScrollingCache();
1440 }
1441 mScrollingCacheEnabled = enabled;
1442 }
1443
1444 /**
1445 * Enables or disables the type filter window. If enabled, typing when
1446 * this view has focus will filter the children to match the users input.
1447 * Note that the {@link Adapter} used by this view must implement the
1448 * {@link Filterable} interface.
1449 *
1450 * @param textFilterEnabled true to enable type filtering, false otherwise
1451 *
1452 * @see Filterable
1453 */
1454 public void setTextFilterEnabled(boolean textFilterEnabled) {
1455 mTextFilterEnabled = textFilterEnabled;
1456 }
1457
1458 /**
1459 * Indicates whether type filtering is enabled for this view
1460 *
1461 * @return true if type filtering is enabled, false otherwise
1462 *
1463 * @see #setTextFilterEnabled(boolean)
1464 * @see Filterable
1465 */
1466 @ViewDebug.ExportedProperty
1467 public boolean isTextFilterEnabled() {
1468 return mTextFilterEnabled;
1469 }
1470
1471 @Override
1472 public void getFocusedRect(Rect r) {
1473 View view = getSelectedView();
Romain Guy6bdbfcf2009-07-16 17:05:36 -07001474 if (view != null && view.getParent() == this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001475 // the focused rectangle of the selected view offset into the
1476 // coordinate space of this view.
1477 view.getFocusedRect(r);
1478 offsetDescendantRectToMyCoords(view, r);
1479 } else {
1480 // otherwise, just the norm
1481 super.getFocusedRect(r);
1482 }
1483 }
1484
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485 private void useDefaultSelector() {
1486 setSelector(getResources().getDrawable(
1487 com.android.internal.R.drawable.list_selector_background));
1488 }
1489
1490 /**
1491 * Indicates whether the content of this view is pinned to, or stacked from,
1492 * the bottom edge.
1493 *
1494 * @return true if the content is stacked from the bottom edge, false otherwise
1495 */
1496 @ViewDebug.ExportedProperty
1497 public boolean isStackFromBottom() {
1498 return mStackFromBottom;
1499 }
1500
1501 /**
1502 * When stack from bottom is set to true, the list fills its content starting from
1503 * the bottom of the view.
1504 *
1505 * @param stackFromBottom true to pin the view's content to the bottom edge,
1506 * false to pin the view's content to the top edge
1507 */
1508 public void setStackFromBottom(boolean stackFromBottom) {
1509 if (mStackFromBottom != stackFromBottom) {
1510 mStackFromBottom = stackFromBottom;
1511 requestLayoutIfNecessary();
1512 }
1513 }
1514
1515 void requestLayoutIfNecessary() {
1516 if (getChildCount() > 0) {
1517 resetList();
1518 requestLayout();
1519 invalidate();
1520 }
1521 }
1522
1523 static class SavedState extends BaseSavedState {
1524 long selectedId;
1525 long firstId;
1526 int viewTop;
1527 int position;
1528 int height;
1529 String filter;
Adam Powella0eeeac2010-11-05 11:55:05 -07001530 boolean inActionMode;
Adam Powell2614c6c2010-11-04 17:54:45 -07001531 int checkedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001532 SparseBooleanArray checkState;
Adam Powell14c08042011-10-06 19:46:18 -07001533 LongSparseArray<Integer> checkIdState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001534
1535 /**
1536 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1537 */
1538 SavedState(Parcelable superState) {
1539 super(superState);
1540 }
1541
1542 /**
1543 * Constructor called from {@link #CREATOR}
1544 */
1545 private SavedState(Parcel in) {
1546 super(in);
1547 selectedId = in.readLong();
1548 firstId = in.readLong();
1549 viewTop = in.readInt();
1550 position = in.readInt();
1551 height = in.readInt();
1552 filter = in.readString();
Adam Powella0eeeac2010-11-05 11:55:05 -07001553 inActionMode = in.readByte() != 0;
Adam Powell2614c6c2010-11-04 17:54:45 -07001554 checkedItemCount = in.readInt();
Adam Powellf343e1b2010-08-13 18:27:04 -07001555 checkState = in.readSparseBooleanArray();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001556 final int N = in.readInt();
1557 if (N > 0) {
Adam Powell14c08042011-10-06 19:46:18 -07001558 checkIdState = new LongSparseArray<Integer>();
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001559 for (int i=0; i<N; i++) {
1560 final long key = in.readLong();
1561 final int value = in.readInt();
1562 checkIdState.put(key, value);
Adam Powell14c08042011-10-06 19:46:18 -07001563 }
Adam Powellf343e1b2010-08-13 18:27:04 -07001564 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001565 }
1566
1567 @Override
1568 public void writeToParcel(Parcel out, int flags) {
1569 super.writeToParcel(out, flags);
1570 out.writeLong(selectedId);
1571 out.writeLong(firstId);
1572 out.writeInt(viewTop);
1573 out.writeInt(position);
1574 out.writeInt(height);
1575 out.writeString(filter);
Adam Powella0eeeac2010-11-05 11:55:05 -07001576 out.writeByte((byte) (inActionMode ? 1 : 0));
Adam Powell2614c6c2010-11-04 17:54:45 -07001577 out.writeInt(checkedItemCount);
Adam Powellf343e1b2010-08-13 18:27:04 -07001578 out.writeSparseBooleanArray(checkState);
Dianne Hackborn43ee0ab2011-10-09 14:11:05 -07001579 final int N = checkIdState != null ? checkIdState.size() : 0;
1580 out.writeInt(N);
1581 for (int i=0; i<N; i++) {
1582 out.writeLong(checkIdState.keyAt(i));
1583 out.writeInt(checkIdState.valueAt(i));
Adam Powell14c08042011-10-06 19:46:18 -07001584 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001585 }
1586
1587 @Override
1588 public String toString() {
1589 return "AbsListView.SavedState{"
1590 + Integer.toHexString(System.identityHashCode(this))
1591 + " selectedId=" + selectedId
1592 + " firstId=" + firstId
1593 + " viewTop=" + viewTop
1594 + " position=" + position
1595 + " height=" + height
Adam Powellf343e1b2010-08-13 18:27:04 -07001596 + " filter=" + filter
1597 + " checkState=" + checkState + "}";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001598 }
1599
1600 public static final Parcelable.Creator<SavedState> CREATOR
1601 = new Parcelable.Creator<SavedState>() {
1602 public SavedState createFromParcel(Parcel in) {
1603 return new SavedState(in);
1604 }
1605
1606 public SavedState[] newArray(int size) {
1607 return new SavedState[size];
1608 }
1609 };
1610 }
1611
1612 @Override
1613 public Parcelable onSaveInstanceState() {
1614 /*
1615 * This doesn't really make sense as the place to dismiss the
Romain Guyf993ad52009-06-04 13:26:52 -07001616 * popups, but there don't seem to be any other useful hooks
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001617 * that happen early enough to keep from getting complaints
1618 * about having leaked the window.
1619 */
1620 dismissPopup();
1621
1622 Parcelable superState = super.onSaveInstanceState();
1623
1624 SavedState ss = new SavedState(superState);
1625
Dianne Hackborne181bd92012-09-25 14:15:15 -07001626 if (mPendingSync != null) {
1627 // Just keep what we last restored.
1628 ss.selectedId = mPendingSync.selectedId;
1629 ss.firstId = mPendingSync.firstId;
1630 ss.viewTop = mPendingSync.viewTop;
1631 ss.position = mPendingSync.position;
1632 ss.height = mPendingSync.height;
1633 ss.filter = mPendingSync.filter;
1634 ss.inActionMode = mPendingSync.inActionMode;
1635 ss.checkedItemCount = mPendingSync.checkedItemCount;
1636 ss.checkState = mPendingSync.checkState;
1637 ss.checkIdState = mPendingSync.checkIdState;
1638 return ss;
1639 }
1640
Dianne Hackborn99441c42010-12-15 11:02:55 -08001641 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001642 long selectedId = getSelectedItemId();
1643 ss.selectedId = selectedId;
1644 ss.height = getHeight();
1645
1646 if (selectedId >= 0) {
1647 // Remember the selection
1648 ss.viewTop = mSelectedTop;
1649 ss.position = getSelectedItemPosition();
1650 ss.firstId = INVALID_POSITION;
1651 } else {
Dianne Hackborn7becaee2010-12-22 18:29:32 -08001652 if (haveChildren && mFirstPosition > 0) {
1653 // Remember the position of the first child.
1654 // We only do this if we are not currently at the top of
1655 // the list, for two reasons:
1656 // (1) The list may be in the process of becoming empty, in
1657 // which case mItemCount may not be 0, but if we try to
1658 // ask for any information about position 0 we will crash.
1659 // (2) Being "at the top" seems like a special case, anyway,
1660 // and the user wouldn't expect to end up somewhere else when
1661 // they revisit the list even if its content has changed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001662 View v = getChildAt(0);
1663 ss.viewTop = v.getTop();
Dianne Hackborn99441c42010-12-15 11:02:55 -08001664 int firstPos = mFirstPosition;
1665 if (firstPos >= mItemCount) {
1666 firstPos = mItemCount - 1;
1667 }
1668 ss.position = firstPos;
1669 ss.firstId = mAdapter.getItemId(firstPos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001670 } else {
1671 ss.viewTop = 0;
1672 ss.firstId = INVALID_POSITION;
1673 ss.position = 0;
1674 }
1675 }
1676
1677 ss.filter = null;
1678 if (mFiltered) {
1679 final EditText textFilter = mTextFilter;
1680 if (textFilter != null) {
1681 Editable filterText = textFilter.getText();
1682 if (filterText != null) {
1683 ss.filter = filterText.toString();
1684 }
1685 }
1686 }
1687
Adam Powella0eeeac2010-11-05 11:55:05 -07001688 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1689
Adam Powell9a5cc282011-08-28 16:18:16 -07001690 if (mCheckStates != null) {
1691 ss.checkState = mCheckStates.clone();
1692 }
1693 if (mCheckedIdStates != null) {
Adam Powell14c08042011-10-06 19:46:18 -07001694 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
Adam Powell9a5cc282011-08-28 16:18:16 -07001695 final int count = mCheckedIdStates.size();
1696 for (int i = 0; i < count; i++) {
1697 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1698 }
1699 ss.checkIdState = idState;
1700 }
Adam Powell2614c6c2010-11-04 17:54:45 -07001701 ss.checkedItemCount = mCheckedItemCount;
Adam Powellf343e1b2010-08-13 18:27:04 -07001702
Adam Cohen335c3b62012-07-24 17:18:16 -07001703 if (mRemoteAdapter != null) {
1704 mRemoteAdapter.saveRemoteViewsCache();
1705 }
1706
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001707 return ss;
1708 }
1709
1710 @Override
1711 public void onRestoreInstanceState(Parcelable state) {
1712 SavedState ss = (SavedState) state;
1713
1714 super.onRestoreInstanceState(ss.getSuperState());
1715 mDataChanged = true;
1716
1717 mSyncHeight = ss.height;
1718
1719 if (ss.selectedId >= 0) {
1720 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001721 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001722 mSyncRowId = ss.selectedId;
1723 mSyncPosition = ss.position;
1724 mSpecificTop = ss.viewTop;
1725 mSyncMode = SYNC_SELECTED_POSITION;
1726 } else if (ss.firstId >= 0) {
1727 setSelectedPositionInt(INVALID_POSITION);
1728 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1729 setNextSelectedPositionInt(INVALID_POSITION);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001730 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001731 mNeedSync = true;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001732 mPendingSync = ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001733 mSyncRowId = ss.firstId;
1734 mSyncPosition = ss.position;
1735 mSpecificTop = ss.viewTop;
1736 mSyncMode = SYNC_FIRST_POSITION;
1737 }
1738
1739 setFilterText(ss.filter);
1740
Adam Powellf343e1b2010-08-13 18:27:04 -07001741 if (ss.checkState != null) {
1742 mCheckStates = ss.checkState;
1743 }
1744
1745 if (ss.checkIdState != null) {
1746 mCheckedIdStates = ss.checkIdState;
1747 }
1748
Adam Powell2614c6c2010-11-04 17:54:45 -07001749 mCheckedItemCount = ss.checkedItemCount;
1750
Adam Powella0eeeac2010-11-05 11:55:05 -07001751 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1752 mMultiChoiceModeCallback != null) {
1753 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1754 }
1755
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001756 requestLayout();
1757 }
1758
1759 private boolean acceptFilter() {
Romain Guyd6a463a2009-05-21 23:10:10 -07001760 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1761 ((Filterable) getAdapter()).getFilter() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001762 }
1763
1764 /**
1765 * Sets the initial value for the text filter.
1766 * @param filterText The text to use for the filter.
Romain Guy0a637162009-05-29 14:43:54 -07001767 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001768 * @see #setTextFilterEnabled
1769 */
1770 public void setFilterText(String filterText) {
1771 // TODO: Should we check for acceptFilter()?
1772 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1773 createTextFilter(false);
1774 // This is going to call our listener onTextChanged, but we might not
1775 // be ready to bring up a window yet
1776 mTextFilter.setText(filterText);
1777 mTextFilter.setSelection(filterText.length());
1778 if (mAdapter instanceof Filterable) {
1779 // if mPopup is non-null, then onTextChanged will do the filtering
1780 if (mPopup == null) {
1781 Filter f = ((Filterable) mAdapter).getFilter();
1782 f.filter(filterText);
1783 }
1784 // Set filtered to true so we will display the filter window when our main
1785 // window is ready
1786 mFiltered = true;
1787 mDataSetObserver.clearSavedState();
1788 }
1789 }
1790 }
1791
1792 /**
Romain Guy0a637162009-05-29 14:43:54 -07001793 * Returns the list's text filter, if available.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001794 * @return the list's text filter or null if filtering isn't enabled
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001795 */
1796 public CharSequence getTextFilter() {
1797 if (mTextFilterEnabled && mTextFilter != null) {
1798 return mTextFilter.getText();
1799 }
1800 return null;
1801 }
Romain Guy0a637162009-05-29 14:43:54 -07001802
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001803 @Override
1804 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1805 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1806 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
Adam Powellb3750132011-08-08 23:29:12 -07001807 if (!mIsAttached && mAdapter != null) {
1808 // Data may have changed while we were detached and it's valid
1809 // to change focus while detached. Refresh so we don't die.
1810 mDataChanged = true;
1811 mOldItemCount = mItemCount;
1812 mItemCount = mAdapter.getCount();
1813 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001814 resurrectSelection();
1815 }
1816 }
1817
1818 @Override
1819 public void requestLayout() {
1820 if (!mBlockLayoutRequests && !mInLayout) {
1821 super.requestLayout();
1822 }
1823 }
1824
1825 /**
1826 * The list is empty. Clear everything out.
1827 */
1828 void resetList() {
1829 removeAllViewsInLayout();
1830 mFirstPosition = 0;
1831 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001832 mPositionScrollAfterLayout = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001833 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07001834 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001835 mOldSelectedPosition = INVALID_POSITION;
1836 mOldSelectedRowId = INVALID_ROW_ID;
1837 setSelectedPositionInt(INVALID_POSITION);
1838 setNextSelectedPositionInt(INVALID_POSITION);
1839 mSelectedTop = 0;
Dianne Hackborn079e2352010-10-18 17:02:43 -07001840 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001841 mSelectorRect.setEmpty();
1842 invalidate();
1843 }
1844
1845 @Override
1846 protected int computeVerticalScrollExtent() {
1847 final int count = getChildCount();
1848 if (count > 0) {
1849 if (mSmoothScrollbarEnabled) {
1850 int extent = count * 100;
1851
1852 View view = getChildAt(0);
1853 final int top = view.getTop();
1854 int height = view.getHeight();
1855 if (height > 0) {
1856 extent += (top * 100) / height;
1857 }
1858
1859 view = getChildAt(count - 1);
1860 final int bottom = view.getBottom();
1861 height = view.getHeight();
1862 if (height > 0) {
1863 extent -= ((bottom - getHeight()) * 100) / height;
1864 }
1865
1866 return extent;
1867 } else {
1868 return 1;
1869 }
1870 }
1871 return 0;
1872 }
1873
1874 @Override
1875 protected int computeVerticalScrollOffset() {
1876 final int firstPosition = mFirstPosition;
1877 final int childCount = getChildCount();
1878 if (firstPosition >= 0 && childCount > 0) {
1879 if (mSmoothScrollbarEnabled) {
1880 final View view = getChildAt(0);
1881 final int top = view.getTop();
1882 int height = view.getHeight();
1883 if (height > 0) {
Adam Powell0b8bb422010-02-08 14:30:45 -08001884 return Math.max(firstPosition * 100 - (top * 100) / height +
1885 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001886 }
1887 } else {
1888 int index;
1889 final int count = mItemCount;
1890 if (firstPosition == 0) {
1891 index = 0;
1892 } else if (firstPosition + childCount == count) {
1893 index = count;
1894 } else {
1895 index = firstPosition + childCount / 2;
1896 }
1897 return (int) (firstPosition + childCount * (index / (float) count));
1898 }
1899 }
1900 return 0;
1901 }
1902
1903 @Override
1904 protected int computeVerticalScrollRange() {
Adam Powell0b8bb422010-02-08 14:30:45 -08001905 int result;
1906 if (mSmoothScrollbarEnabled) {
1907 result = Math.max(mItemCount * 100, 0);
Adam Powell637d3372010-08-25 14:37:03 -07001908 if (mScrollY != 0) {
1909 // Compensate for overscroll
1910 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
1911 }
Adam Powell0b8bb422010-02-08 14:30:45 -08001912 } else {
1913 result = mItemCount;
1914 }
1915 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001916 }
1917
1918 @Override
1919 protected float getTopFadingEdgeStrength() {
1920 final int count = getChildCount();
1921 final float fadeEdge = super.getTopFadingEdgeStrength();
1922 if (count == 0) {
1923 return fadeEdge;
1924 } else {
1925 if (mFirstPosition > 0) {
1926 return 1.0f;
1927 }
1928
1929 final int top = getChildAt(0).getTop();
1930 final float fadeLength = (float) getVerticalFadingEdgeLength();
1931 return top < mPaddingTop ? (float) -(top - mPaddingTop) / fadeLength : fadeEdge;
1932 }
1933 }
1934
1935 @Override
1936 protected float getBottomFadingEdgeStrength() {
1937 final int count = getChildCount();
1938 final float fadeEdge = super.getBottomFadingEdgeStrength();
1939 if (count == 0) {
1940 return fadeEdge;
1941 } else {
1942 if (mFirstPosition + count - 1 < mItemCount - 1) {
1943 return 1.0f;
1944 }
1945
1946 final int bottom = getChildAt(count - 1).getBottom();
1947 final int height = getHeight();
1948 final float fadeLength = (float) getVerticalFadingEdgeLength();
1949 return bottom > height - mPaddingBottom ?
1950 (float) (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
1951 }
1952 }
1953
1954 @Override
1955 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1956 if (mSelector == null) {
1957 useDefaultSelector();
1958 }
1959 final Rect listPadding = mListPadding;
1960 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
1961 listPadding.top = mSelectionTopPadding + mPaddingTop;
1962 listPadding.right = mSelectionRightPadding + mPaddingRight;
1963 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
Adam Powellda13dba2010-12-05 13:47:23 -08001964
1965 // Check if our previous measured size was at a point where we should scroll later.
1966 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
1967 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07001968 final int listBottom = getHeight() - getPaddingBottom();
Adam Powellda13dba2010-12-05 13:47:23 -08001969 final View lastChild = getChildAt(childCount - 1);
1970 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07001971 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
Adam Powellda13dba2010-12-05 13:47:23 -08001972 lastBottom <= listBottom;
1973 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001974 }
1975
Romain Guyd6a463a2009-05-21 23:10:10 -07001976 /**
1977 * Subclasses should NOT override this method but
1978 * {@link #layoutChildren()} instead.
1979 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001980 @Override
1981 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1982 super.onLayout(changed, l, t, r, b);
1983 mInLayout = true;
Adam Powellf3c2eda2010-03-16 17:31:01 -07001984 if (changed) {
1985 int childCount = getChildCount();
1986 for (int i = 0; i < childCount; i++) {
1987 getChildAt(i).forceLayout();
1988 }
1989 mRecycler.markChildrenDirty();
1990 }
Adam Powell2c6196a2010-12-10 14:31:54 -08001991
1992 if (mFastScroller != null && mItemCount != mOldItemCount) {
1993 mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
1994 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07001995
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001996 layoutChildren();
1997 mInLayout = false;
Adam Powell637d3372010-08-25 14:37:03 -07001998
1999 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002000 }
2001
2002 /**
2003 * @hide
2004 */
2005 @Override
2006 protected boolean setFrame(int left, int top, int right, int bottom) {
2007 final boolean changed = super.setFrame(left, top, right, bottom);
2008
Romain Guyd6a463a2009-05-21 23:10:10 -07002009 if (changed) {
2010 // Reposition the popup when the frame has changed. This includes
2011 // translating the widget, not just changing its dimension. The
2012 // filter popup needs to follow the widget.
2013 final boolean visible = getWindowVisibility() == View.VISIBLE;
2014 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2015 positionPopup();
2016 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002017 }
2018
2019 return changed;
2020 }
2021
Romain Guyd6a463a2009-05-21 23:10:10 -07002022 /**
2023 * Subclasses must override this method to layout their children.
2024 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002025 protected void layoutChildren() {
2026 }
2027
2028 void updateScrollIndicators() {
2029 if (mScrollUp != null) {
2030 boolean canScrollUp;
2031 // 0th element is not visible
2032 canScrollUp = mFirstPosition > 0;
2033
2034 // ... Or top of 0th element is not visible
2035 if (!canScrollUp) {
2036 if (getChildCount() > 0) {
2037 View child = getChildAt(0);
2038 canScrollUp = child.getTop() < mListPadding.top;
2039 }
2040 }
2041
2042 mScrollUp.setVisibility(canScrollUp ? View.VISIBLE : View.INVISIBLE);
2043 }
2044
2045 if (mScrollDown != null) {
2046 boolean canScrollDown;
2047 int count = getChildCount();
2048
2049 // Last item is not visible
2050 canScrollDown = (mFirstPosition + count) < mItemCount;
2051
2052 // ... Or bottom of the last element is not visible
2053 if (!canScrollDown && count > 0) {
2054 View child = getChildAt(count - 1);
2055 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2056 }
2057
2058 mScrollDown.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
2059 }
2060 }
2061
2062 @Override
2063 @ViewDebug.ExportedProperty
2064 public View getSelectedView() {
2065 if (mItemCount > 0 && mSelectedPosition >= 0) {
2066 return getChildAt(mSelectedPosition - mFirstPosition);
2067 } else {
2068 return null;
2069 }
2070 }
2071
2072 /**
2073 * List padding is the maximum of the normal view's padding and the padding of the selector.
2074 *
2075 * @see android.view.View#getPaddingTop()
2076 * @see #getSelector()
2077 *
2078 * @return The top list padding.
2079 */
2080 public int getListPaddingTop() {
2081 return mListPadding.top;
2082 }
2083
2084 /**
2085 * List padding is the maximum of the normal view's padding and the padding of the selector.
2086 *
2087 * @see android.view.View#getPaddingBottom()
2088 * @see #getSelector()
2089 *
2090 * @return The bottom list padding.
2091 */
2092 public int getListPaddingBottom() {
2093 return mListPadding.bottom;
2094 }
2095
2096 /**
2097 * List padding is the maximum of the normal view's padding and the padding of the selector.
2098 *
2099 * @see android.view.View#getPaddingLeft()
2100 * @see #getSelector()
2101 *
2102 * @return The left list padding.
2103 */
2104 public int getListPaddingLeft() {
2105 return mListPadding.left;
2106 }
2107
2108 /**
2109 * List padding is the maximum of the normal view's padding and the padding of the selector.
2110 *
2111 * @see android.view.View#getPaddingRight()
2112 * @see #getSelector()
2113 *
2114 * @return The right list padding.
2115 */
2116 public int getListPaddingRight() {
2117 return mListPadding.right;
2118 }
2119
2120 /**
2121 * Get a view and have it show the data associated with the specified
2122 * position. This is called when we have already discovered that the view is
2123 * not available for reuse in the recycle bin. The only choices left are
2124 * converting an old view or making a new one.
2125 *
2126 * @param position The position to display
Romain Guy21875052010-01-06 18:48:08 -08002127 * @param isScrap Array of at least 1 boolean, the first entry will become true if
2128 * the returned view was taken from the scrap heap, false if otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -08002129 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002130 * @return A view displaying the data associated with the specified position
2131 */
Romain Guy21875052010-01-06 18:48:08 -08002132 View obtainView(int position, boolean[] isScrap) {
2133 isScrap[0] = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002134 View scrapView;
2135
Adam Powell539ee872012-02-03 19:00:49 -08002136 scrapView = mRecycler.getTransientStateView(position);
2137 if (scrapView != null) {
2138 return scrapView;
2139 }
2140
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002141 scrapView = mRecycler.getScrapView(position);
2142
2143 View child;
2144 if (scrapView != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002145 child = mAdapter.getView(position, scrapView, this);
2146
Svetoslav Ganov42138042012-03-20 11:51:39 -07002147 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2148 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2149 }
2150
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002151 if (child != scrapView) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002152 mRecycler.addScrapView(scrapView, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002153 if (mCacheColorHint != 0) {
2154 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2155 }
Romain Guy21875052010-01-06 18:48:08 -08002156 } else {
Romain Guya440b002010-02-24 15:57:54 -08002157 isScrap[0] = true;
2158 child.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002159 }
2160 } else {
2161 child = mAdapter.getView(position, null, this);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002162
2163 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2164 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2165 }
2166
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002167 if (mCacheColorHint != 0) {
2168 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2169 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002170 }
2171
Adam Powellaebd28f2012-02-22 10:31:16 -08002172 if (mAdapterHasStableIds) {
2173 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2174 LayoutParams lp;
2175 if (vlp == null) {
2176 lp = (LayoutParams) generateDefaultLayoutParams();
2177 } else if (!checkLayoutParams(vlp)) {
2178 lp = (LayoutParams) generateLayoutParams(vlp);
2179 } else {
2180 lp = (LayoutParams) vlp;
2181 }
2182 lp.itemId = mAdapter.getItemId(position);
2183 child.setLayoutParams(lp);
2184 }
2185
alanvc1d7e772012-05-08 14:47:24 -07002186 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2187 if (mAccessibilityDelegate == null) {
2188 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2189 }
alanvb72fe7a2012-08-27 16:44:25 -07002190 if (child.getAccessibilityDelegate() == null) {
2191 child.setAccessibilityDelegate(mAccessibilityDelegate);
2192 }
alanvc1d7e772012-05-08 14:47:24 -07002193 }
2194
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002195 return child;
2196 }
2197
alanvc1d7e772012-05-08 14:47:24 -07002198 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2199 @Override
2200 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2201 super.onInitializeAccessibilityNodeInfo(host, info);
2202
2203 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002204 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002205
alanv9c3e0e62012-05-18 17:43:35 -07002206 if ((position == INVALID_POSITION) || (adapter == null)) {
alanvc1d7e772012-05-08 14:47:24 -07002207 return;
2208 }
2209
alanv9c3e0e62012-05-18 17:43:35 -07002210 if (!isEnabled() || !adapter.isEnabled(position)) {
alanv9c3e0e62012-05-18 17:43:35 -07002211 return;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002212 }
alanvc1d7e772012-05-08 14:47:24 -07002213
2214 if (position == getSelectedItemPosition()) {
2215 info.setSelected(true);
alanv9c3e0e62012-05-18 17:43:35 -07002216 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
2217 } else {
2218 info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
alanvc1d7e772012-05-08 14:47:24 -07002219 }
alanv9c3e0e62012-05-18 17:43:35 -07002220
2221 if (isClickable()) {
2222 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
2223 info.setClickable(true);
2224 }
2225
2226 if (isLongClickable()) {
2227 info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
2228 info.setLongClickable(true);
2229 }
2230
alanvc1d7e772012-05-08 14:47:24 -07002231 }
2232
2233 @Override
2234 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002235 if (super.performAccessibilityAction(host, action, arguments)) {
2236 return true;
2237 }
2238
alanvc1d7e772012-05-08 14:47:24 -07002239 final int position = getPositionForView(host);
alanv9c3e0e62012-05-18 17:43:35 -07002240 final ListAdapter adapter = getAdapter();
alanvc1d7e772012-05-08 14:47:24 -07002241
alanv9c3e0e62012-05-18 17:43:35 -07002242 if ((position == INVALID_POSITION) || (adapter == null)) {
2243 // Cannot perform actions on invalid items.
alanvc1d7e772012-05-08 14:47:24 -07002244 return false;
2245 }
2246
alanv9c3e0e62012-05-18 17:43:35 -07002247 if (!isEnabled() || !adapter.isEnabled(position)) {
2248 // Cannot perform actions on disabled items.
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002249 return false;
2250 }
2251
alanvc1d7e772012-05-08 14:47:24 -07002252 final long id = getItemIdAtPosition(position);
2253
2254 switch (action) {
alanv9c3e0e62012-05-18 17:43:35 -07002255 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2256 if (getSelectedItemPosition() == position) {
2257 setSelection(INVALID_POSITION);
2258 return true;
2259 }
2260 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002261 case AccessibilityNodeInfo.ACTION_SELECT: {
alanv9c3e0e62012-05-18 17:43:35 -07002262 if (getSelectedItemPosition() != position) {
2263 setSelection(position);
2264 return true;
2265 }
2266 } return false;
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002267 case AccessibilityNodeInfo.ACTION_CLICK: {
2268 if (isClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002269 return performItemClick(host, position, id);
2270 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002271 } return false;
2272 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2273 if (isLongClickable()) {
alanvc1d7e772012-05-08 14:47:24 -07002274 return performLongPress(host, position, id);
2275 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002276 } return false;
alanvc1d7e772012-05-08 14:47:24 -07002277 }
2278
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07002279 return false;
alanvc1d7e772012-05-08 14:47:24 -07002280 }
2281 }
2282
Dianne Hackborn079e2352010-10-18 17:02:43 -07002283 void positionSelector(int position, View sel) {
2284 if (position != INVALID_POSITION) {
2285 mSelectorPosition = position;
2286 }
2287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002288 final Rect selectorRect = mSelectorRect;
2289 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
Dianne Hackborne2136772010-11-04 15:08:59 -07002290 if (sel instanceof SelectionBoundsAdjuster) {
2291 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2292 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002293 positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
2294 selectorRect.bottom);
2295
2296 final boolean isChildViewEnabled = mIsChildViewEnabled;
2297 if (sel.isEnabled() != isChildViewEnabled) {
2298 mIsChildViewEnabled = !isChildViewEnabled;
Jason Bayera79f4b72011-03-22 20:03:31 -07002299 if (getSelectedItemPosition() != INVALID_POSITION) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002300 refreshDrawableState();
2301 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002302 }
2303 }
2304
2305 private void positionSelector(int l, int t, int r, int b) {
2306 mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
2307 + mSelectionRightPadding, b + mSelectionBottomPadding);
2308 }
2309
2310 @Override
2311 protected void dispatchDraw(Canvas canvas) {
2312 int saveCount = 0;
2313 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2314 if (clipToPadding) {
2315 saveCount = canvas.save();
2316 final int scrollX = mScrollX;
2317 final int scrollY = mScrollY;
2318 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2319 scrollX + mRight - mLeft - mPaddingRight,
2320 scrollY + mBottom - mTop - mPaddingBottom);
2321 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2322 }
2323
2324 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2325 if (!drawSelectorOnTop) {
2326 drawSelector(canvas);
2327 }
2328
2329 super.dispatchDraw(canvas);
2330
2331 if (drawSelectorOnTop) {
2332 drawSelector(canvas);
2333 }
2334
2335 if (clipToPadding) {
2336 canvas.restoreToCount(saveCount);
2337 mGroupFlags |= CLIP_TO_PADDING_MASK;
2338 }
2339 }
2340
2341 @Override
Adam Powell20232d02010-12-08 21:08:53 -08002342 protected boolean isPaddingOffsetRequired() {
2343 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2344 }
2345
2346 @Override
2347 protected int getLeftPaddingOffset() {
2348 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2349 }
2350
2351 @Override
2352 protected int getTopPaddingOffset() {
2353 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2354 }
2355
2356 @Override
2357 protected int getRightPaddingOffset() {
2358 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2359 }
2360
2361 @Override
2362 protected int getBottomPaddingOffset() {
2363 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2364 }
2365
2366 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002367 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2368 if (getChildCount() > 0) {
2369 mDataChanged = true;
2370 rememberSyncState();
2371 }
Romain Guyd6a463a2009-05-21 23:10:10 -07002372
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002373 if (mFastScroller != null) {
2374 mFastScroller.onSizeChanged(w, h, oldw, oldh);
2375 }
2376 }
2377
2378 /**
2379 * @return True if the current touch mode requires that we draw the selector in the pressed
2380 * state.
2381 */
2382 boolean touchModeDrawsInPressedState() {
2383 // FIXME use isPressed for this
2384 switch (mTouchMode) {
2385 case TOUCH_MODE_TAP:
2386 case TOUCH_MODE_DONE_WAITING:
2387 return true;
2388 default:
2389 return false;
2390 }
2391 }
2392
2393 /**
2394 * Indicates whether this view is in a state where the selector should be drawn. This will
2395 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2396 * the pressed state for an item.
2397 *
2398 * @return True if the selector should be shown
2399 */
2400 boolean shouldShowSelector() {
2401 return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
2402 }
2403
2404 private void drawSelector(Canvas canvas) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002405 if (!mSelectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002406 final Drawable selector = mSelector;
2407 selector.setBounds(mSelectorRect);
2408 selector.draw(canvas);
2409 }
2410 }
2411
2412 /**
2413 * Controls whether the selection highlight drawable should be drawn on top of the item or
2414 * behind it.
2415 *
2416 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2417 * is false.
2418 *
2419 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2420 */
2421 public void setDrawSelectorOnTop(boolean onTop) {
2422 mDrawSelectorOnTop = onTop;
2423 }
2424
2425 /**
2426 * Set a Drawable that should be used to highlight the currently selected item.
2427 *
2428 * @param resID A Drawable resource to use as the selection highlight.
2429 *
2430 * @attr ref android.R.styleable#AbsListView_listSelector
2431 */
2432 public void setSelector(int resID) {
2433 setSelector(getResources().getDrawable(resID));
2434 }
2435
2436 public void setSelector(Drawable sel) {
2437 if (mSelector != null) {
2438 mSelector.setCallback(null);
2439 unscheduleDrawable(mSelector);
2440 }
2441 mSelector = sel;
2442 Rect padding = new Rect();
2443 sel.getPadding(padding);
2444 mSelectionLeftPadding = padding.left;
2445 mSelectionTopPadding = padding.top;
2446 mSelectionRightPadding = padding.right;
2447 mSelectionBottomPadding = padding.bottom;
2448 sel.setCallback(this);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002449 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002450 }
2451
2452 /**
2453 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2454 * selection in the list.
2455 *
2456 * @return the drawable used to display the selector
2457 */
2458 public Drawable getSelector() {
2459 return mSelector;
2460 }
2461
2462 /**
2463 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2464 * this is a long press.
2465 */
2466 void keyPressed() {
Romain Guydf016072009-08-17 12:51:30 -07002467 if (!isEnabled() || !isClickable()) {
2468 return;
2469 }
2470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002471 Drawable selector = mSelector;
2472 Rect selectorRect = mSelectorRect;
2473 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
Dianne Hackborn079e2352010-10-18 17:02:43 -07002474 && !selectorRect.isEmpty()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002475
2476 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2477
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07002478 if (v != null) {
2479 if (v.hasFocusable()) return;
2480 v.setPressed(true);
2481 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002482 setPressed(true);
2483
2484 final boolean longClickable = isLongClickable();
2485 Drawable d = selector.getCurrent();
2486 if (d != null && d instanceof TransitionDrawable) {
2487 if (longClickable) {
Romain Guydf016072009-08-17 12:51:30 -07002488 ((TransitionDrawable) d).startTransition(
2489 ViewConfiguration.getLongPressTimeout());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002490 } else {
2491 ((TransitionDrawable) d).resetTransition();
2492 }
2493 }
2494 if (longClickable && !mDataChanged) {
2495 if (mPendingCheckForKeyLongPress == null) {
2496 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2497 }
2498 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2499 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2500 }
2501 }
2502 }
2503
2504 public void setScrollIndicators(View up, View down) {
2505 mScrollUp = up;
2506 mScrollDown = down;
2507 }
2508
Dianne Hackborn079e2352010-10-18 17:02:43 -07002509 void updateSelectorState() {
2510 if (mSelector != null) {
2511 if (shouldShowSelector()) {
2512 mSelector.setState(getDrawableState());
2513 } else {
2514 mSelector.setState(StateSet.NOTHING);
2515 }
2516 }
2517 }
2518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002519 @Override
2520 protected void drawableStateChanged() {
2521 super.drawableStateChanged();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002522 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002523 }
2524
2525 @Override
2526 protected int[] onCreateDrawableState(int extraSpace) {
2527 // If the child view is enabled then do the default behavior.
2528 if (mIsChildViewEnabled) {
2529 // Common case
2530 return super.onCreateDrawableState(extraSpace);
2531 }
2532
2533 // The selector uses this View's drawable state. The selected child view
2534 // is disabled, so we need to remove the enabled state from the drawable
2535 // states.
2536 final int enabledState = ENABLED_STATE_SET[0];
2537
2538 // If we don't have any extra space, it will return one of the static state arrays,
2539 // and clearing the enabled state on those arrays is a bad thing! If we specify
2540 // we need extra space, it will create+copy into a new array that safely mutable.
2541 int[] state = super.onCreateDrawableState(extraSpace + 1);
2542 int enabledPos = -1;
2543 for (int i = state.length - 1; i >= 0; i--) {
2544 if (state[i] == enabledState) {
2545 enabledPos = i;
2546 break;
2547 }
2548 }
2549
2550 // Remove the enabled state
2551 if (enabledPos >= 0) {
2552 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2553 state.length - enabledPos - 1);
2554 }
Romain Guy0a637162009-05-29 14:43:54 -07002555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002556 return state;
2557 }
2558
2559 @Override
2560 public boolean verifyDrawable(Drawable dr) {
2561 return mSelector == dr || super.verifyDrawable(dr);
2562 }
2563
2564 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07002565 public void jumpDrawablesToCurrentState() {
2566 super.jumpDrawablesToCurrentState();
2567 if (mSelector != null) mSelector.jumpToCurrentState();
2568 }
2569
2570 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002571 protected void onAttachedToWindow() {
2572 super.onAttachedToWindow();
2573
2574 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002575 treeObserver.addOnTouchModeChangeListener(this);
2576 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2577 treeObserver.addOnGlobalLayoutListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002578 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08002579
Romain Guy82afc7b2010-05-13 11:52:37 -07002580 if (mAdapter != null && mDataSetObserver == null) {
2581 mDataSetObserver = new AdapterDataSetObserver();
2582 mAdapter.registerDataSetObserver(mDataSetObserver);
Adam Powell6a0d0992010-10-24 16:29:46 -07002583
2584 // Data may have changed while we were detached. Refresh.
2585 mDataChanged = true;
2586 mOldItemCount = mItemCount;
2587 mItemCount = mAdapter.getCount();
Romain Guy82afc7b2010-05-13 11:52:37 -07002588 }
Adam Powellb3750132011-08-08 23:29:12 -07002589 mIsAttached = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002590 }
2591
2592 @Override
2593 protected void onDetachedFromWindow() {
2594 super.onDetachedFromWindow();
2595
Romain Guy1f7f3c32009-07-22 11:25:42 -07002596 // Dismiss the popup in case onSaveInstanceState() was not invoked
2597 dismissPopup();
2598
Romain Guy21875052010-01-06 18:48:08 -08002599 // Detach any view left in the scrap heap
2600 mRecycler.clear();
2601
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002602 final ViewTreeObserver treeObserver = getViewTreeObserver();
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002603 treeObserver.removeOnTouchModeChangeListener(this);
2604 if (mTextFilterEnabled && mPopup != null) {
Romain Guy9d849a22012-03-14 16:41:42 -07002605 treeObserver.removeOnGlobalLayoutListener(this);
Gilles Debunne0e7d652d2011-02-22 15:26:14 -08002606 mGlobalLayoutListenerAddedFilter = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002607 }
Romain Guy82afc7b2010-05-13 11:52:37 -07002608
Adam Powellbd1dd0d2013-04-09 17:46:15 -07002609 if (mAdapter != null && mDataSetObserver != null) {
Romain Guy82afc7b2010-05-13 11:52:37 -07002610 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2611 mDataSetObserver = null;
2612 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08002613
2614 if (mScrollStrictSpan != null) {
2615 mScrollStrictSpan.finish();
2616 mScrollStrictSpan = null;
2617 }
2618
2619 if (mFlingStrictSpan != null) {
2620 mFlingStrictSpan.finish();
2621 mFlingStrictSpan = null;
2622 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002623
2624 if (mFlingRunnable != null) {
2625 removeCallbacks(mFlingRunnable);
2626 }
2627
2628 if (mPositionScroller != null) {
Adam Powell40322522011-01-12 21:58:20 -08002629 mPositionScroller.stop();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002630 }
2631
2632 if (mClearScrollingCache != null) {
2633 removeCallbacks(mClearScrollingCache);
2634 }
2635
2636 if (mPerformClick != null) {
2637 removeCallbacks(mPerformClick);
2638 }
2639
2640 if (mTouchModeReset != null) {
2641 removeCallbacks(mTouchModeReset);
Sangkyu Leea6072232012-12-07 17:06:15 +09002642 mTouchModeReset.run();
Dianne Hackbornd173fa32010-12-23 13:58:22 -08002643 }
Adam Powellb3750132011-08-08 23:29:12 -07002644 mIsAttached = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002645 }
2646
2647 @Override
2648 public void onWindowFocusChanged(boolean hasWindowFocus) {
2649 super.onWindowFocusChanged(hasWindowFocus);
2650
2651 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
2652
2653 if (!hasWindowFocus) {
2654 setChildrenDrawingCacheEnabled(false);
Mark Wagner670dd812010-01-13 16:17:47 -08002655 if (mFlingRunnable != null) {
2656 removeCallbacks(mFlingRunnable);
2657 // let the fling runnable report it's new state which
2658 // should be idle
2659 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08002660 if (mPositionScroller != null) {
2661 mPositionScroller.stop();
2662 }
Adam Powell45803472010-01-25 15:10:44 -08002663 if (mScrollY != 0) {
2664 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08002665 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07002666 finishGlows();
Adam Powell45803472010-01-25 15:10:44 -08002667 invalidate();
2668 }
Mark Wagner670dd812010-01-13 16:17:47 -08002669 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002670 // Always hide the type filter
2671 dismissPopup();
2672
2673 if (touchMode == TOUCH_MODE_OFF) {
2674 // Remember the last selected element
2675 mResurrectToPosition = mSelectedPosition;
2676 }
2677 } else {
Adam Powell97566042010-03-09 15:34:09 -08002678 if (mFiltered && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002679 // Show the type filter only if a filter is in effect
2680 showPopup();
2681 }
2682
2683 // If we changed touch mode since the last time we had focus
2684 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
2685 // If we come back in trackball mode, we bring the selection back
2686 if (touchMode == TOUCH_MODE_OFF) {
2687 // This will trigger a layout
2688 resurrectSelection();
2689
2690 // If we come back in touch mode, then we want to hide the selector
2691 } else {
2692 hideSelector();
2693 mLayoutMode = LAYOUT_NORMAL;
2694 layoutChildren();
2695 }
2696 }
2697 }
2698
2699 mLastTouchMode = touchMode;
2700 }
2701
2702 /**
2703 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
2704 * methods knows the view, position and ID of the item that received the
2705 * long press.
2706 *
2707 * @param view The view that received the long press.
2708 * @param position The position of the item that received the long press.
2709 * @param id The ID of the item that received the long press.
2710 * @return The extra information that should be returned by
2711 * {@link #getContextMenuInfo()}.
2712 */
2713 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
2714 return new AdapterContextMenuInfo(view, position, id);
2715 }
2716
2717 /**
2718 * A base class for Runnables that will check that their view is still attached to
2719 * the original window as when the Runnable was created.
2720 *
2721 */
2722 private class WindowRunnnable {
2723 private int mOriginalAttachCount;
Romain Guy0a637162009-05-29 14:43:54 -07002724
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002725 public void rememberWindowAttachCount() {
2726 mOriginalAttachCount = getWindowAttachCount();
2727 }
Romain Guy0a637162009-05-29 14:43:54 -07002728
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002729 public boolean sameWindow() {
2730 return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
2731 }
2732 }
Romain Guy0a637162009-05-29 14:43:54 -07002733
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002734 private class PerformClick extends WindowRunnnable implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002735 int mClickMotionPosition;
2736
2737 public void run() {
2738 // The data has changed since we posted this action in the event queue,
2739 // bail out before bad things happen
2740 if (mDataChanged) return;
2741
Adam Powell005c0a42010-03-30 16:26:36 -07002742 final ListAdapter adapter = mAdapter;
2743 final int motionPosition = mClickMotionPosition;
2744 if (adapter != null && mItemCount > 0 &&
2745 motionPosition != INVALID_POSITION &&
2746 motionPosition < adapter.getCount() && sameWindow()) {
Romain Guy7890fe22011-01-18 20:24:18 -08002747 final View view = getChildAt(motionPosition - mFirstPosition);
2748 // If there is no view, something bad happened (the view scrolled off the
2749 // screen, etc.) and we should cancel the click
2750 if (view != null) {
2751 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
2752 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002753 }
2754 }
2755 }
2756
2757 private class CheckForLongPress extends WindowRunnnable implements Runnable {
2758 public void run() {
2759 final int motionPosition = mMotionPosition;
2760 final View child = getChildAt(motionPosition - mFirstPosition);
2761 if (child != null) {
2762 final int longPressPosition = mMotionPosition;
2763 final long longPressId = mAdapter.getItemId(mMotionPosition);
2764
2765 boolean handled = false;
Romain Guy0a637162009-05-29 14:43:54 -07002766 if (sameWindow() && !mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002767 handled = performLongPress(child, longPressPosition, longPressId);
2768 }
2769 if (handled) {
2770 mTouchMode = TOUCH_MODE_REST;
2771 setPressed(false);
2772 child.setPressed(false);
2773 } else {
2774 mTouchMode = TOUCH_MODE_DONE_WAITING;
2775 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002776 }
2777 }
2778 }
Romain Guy0a637162009-05-29 14:43:54 -07002779
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002780 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
2781 public void run() {
2782 if (isPressed() && mSelectedPosition >= 0) {
2783 int index = mSelectedPosition - mFirstPosition;
2784 View v = getChildAt(index);
2785
2786 if (!mDataChanged) {
2787 boolean handled = false;
2788 if (sameWindow()) {
2789 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
2790 }
2791 if (handled) {
2792 setPressed(false);
2793 v.setPressed(false);
2794 }
2795 } else {
2796 setPressed(false);
2797 if (v != null) v.setPressed(false);
2798 }
2799 }
2800 }
2801 }
2802
Adam Powell8350f7d2010-07-28 14:27:28 -07002803 boolean performLongPress(final View child,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002804 final int longPressPosition, final long longPressId) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002805 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
2806 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
Adam Powell1e83b3e2011-09-13 18:09:21 -07002807 if (mChoiceActionMode == null &&
2808 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
Adam Powellf343e1b2010-08-13 18:27:04 -07002809 setItemChecked(longPressPosition, true);
Adam Powell1e83b3e2011-09-13 18:09:21 -07002810 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Adam Powellf343e1b2010-08-13 18:27:04 -07002811 }
Adam Powellf343e1b2010-08-13 18:27:04 -07002812 return true;
2813 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002814
Adam Powellf343e1b2010-08-13 18:27:04 -07002815 boolean handled = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002816 if (mOnItemLongClickListener != null) {
2817 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
2818 longPressPosition, longPressId);
2819 }
2820 if (!handled) {
2821 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
2822 handled = super.showContextMenuForChild(AbsListView.this);
2823 }
2824 if (handled) {
2825 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2826 }
2827 return handled;
2828 }
2829
2830 @Override
2831 protected ContextMenuInfo getContextMenuInfo() {
2832 return mContextMenuInfo;
2833 }
2834
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07002835 /** @hide */
2836 @Override
2837 public boolean showContextMenu(float x, float y, int metaState) {
2838 final int position = pointToPosition((int)x, (int)y);
2839 if (position != INVALID_POSITION) {
2840 final long id = mAdapter.getItemId(position);
2841 View child = getChildAt(position - mFirstPosition);
2842 if (child != null) {
2843 mContextMenuInfo = createContextMenuInfo(child, position, id);
2844 return super.showContextMenuForChild(AbsListView.this);
2845 }
2846 }
2847 return super.showContextMenu(x, y, metaState);
2848 }
2849
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002850 @Override
2851 public boolean showContextMenuForChild(View originalView) {
2852 final int longPressPosition = getPositionForView(originalView);
2853 if (longPressPosition >= 0) {
2854 final long longPressId = mAdapter.getItemId(longPressPosition);
2855 boolean handled = false;
2856
2857 if (mOnItemLongClickListener != null) {
2858 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView,
2859 longPressPosition, longPressId);
2860 }
2861 if (!handled) {
2862 mContextMenuInfo = createContextMenuInfo(
2863 getChildAt(longPressPosition - mFirstPosition),
2864 longPressPosition, longPressId);
2865 handled = super.showContextMenuForChild(originalView);
2866 }
2867
2868 return handled;
2869 }
2870 return false;
2871 }
2872
2873 @Override
Romain Guydf016072009-08-17 12:51:30 -07002874 public boolean onKeyDown(int keyCode, KeyEvent event) {
2875 return false;
2876 }
2877
2878 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002879 public boolean onKeyUp(int keyCode, KeyEvent event) {
2880 switch (keyCode) {
2881 case KeyEvent.KEYCODE_DPAD_CENTER:
2882 case KeyEvent.KEYCODE_ENTER:
Romain Guydd753ae2009-08-17 10:36:23 -07002883 if (!isEnabled()) {
2884 return true;
2885 }
Romain Guydf016072009-08-17 12:51:30 -07002886 if (isClickable() && isPressed() &&
Romain Guydd753ae2009-08-17 10:36:23 -07002887 mSelectedPosition >= 0 && mAdapter != null &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002888 mSelectedPosition < mAdapter.getCount()) {
Romain Guydd753ae2009-08-17 10:36:23 -07002889
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002890 final View view = getChildAt(mSelectedPosition - mFirstPosition);
Romain Guy45b3dcd2010-03-22 14:12:43 -07002891 if (view != null) {
2892 performItemClick(view, mSelectedPosition, mSelectedRowId);
2893 view.setPressed(false);
2894 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002895 setPressed(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002896 return true;
2897 }
Romain Guydd753ae2009-08-17 10:36:23 -07002898 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002899 }
2900 return super.onKeyUp(keyCode, event);
2901 }
2902
2903 @Override
2904 protected void dispatchSetPressed(boolean pressed) {
2905 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
2906 // get the selector in the right state, but we don't want to press each child.
2907 }
2908
2909 /**
2910 * Maps a point to a position in the list.
2911 *
2912 * @param x X in local coordinate
2913 * @param y Y in local coordinate
2914 * @return The position of the item which contains the specified point, or
2915 * {@link #INVALID_POSITION} if the point does not intersect an item.
2916 */
2917 public int pointToPosition(int x, int y) {
2918 Rect frame = mTouchFrame;
2919 if (frame == null) {
2920 mTouchFrame = new Rect();
2921 frame = mTouchFrame;
2922 }
2923
2924 final int count = getChildCount();
2925 for (int i = count - 1; i >= 0; i--) {
2926 final View child = getChildAt(i);
2927 if (child.getVisibility() == View.VISIBLE) {
2928 child.getHitRect(frame);
2929 if (frame.contains(x, y)) {
2930 return mFirstPosition + i;
2931 }
2932 }
2933 }
2934 return INVALID_POSITION;
2935 }
2936
2937
2938 /**
2939 * Maps a point to a the rowId of the item which intersects that point.
2940 *
2941 * @param x X in local coordinate
2942 * @param y Y in local coordinate
2943 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
2944 * if the point does not intersect an item.
2945 */
2946 public long pointToRowId(int x, int y) {
2947 int position = pointToPosition(x, y);
2948 if (position >= 0) {
2949 return mAdapter.getItemId(position);
2950 }
2951 return INVALID_ROW_ID;
2952 }
2953
2954 final class CheckForTap implements Runnable {
2955 public void run() {
2956 if (mTouchMode == TOUCH_MODE_DOWN) {
2957 mTouchMode = TOUCH_MODE_TAP;
2958 final View child = getChildAt(mMotionPosition - mFirstPosition);
2959 if (child != null && !child.hasFocusable()) {
2960 mLayoutMode = LAYOUT_NORMAL;
2961
2962 if (!mDataChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002963 child.setPressed(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002964 setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002965 layoutChildren();
2966 positionSelector(mMotionPosition, child);
Adam Powelle0fd2eb2011-01-17 18:37:42 -08002967 refreshDrawableState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002968
2969 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
2970 final boolean longClickable = isLongClickable();
2971
2972 if (mSelector != null) {
2973 Drawable d = mSelector.getCurrent();
2974 if (d != null && d instanceof TransitionDrawable) {
2975 if (longClickable) {
2976 ((TransitionDrawable) d).startTransition(longPressTimeout);
2977 } else {
2978 ((TransitionDrawable) d).resetTransition();
2979 }
2980 }
2981 }
2982
2983 if (longClickable) {
2984 if (mPendingCheckForLongPress == null) {
2985 mPendingCheckForLongPress = new CheckForLongPress();
2986 }
2987 mPendingCheckForLongPress.rememberWindowAttachCount();
2988 postDelayed(mPendingCheckForLongPress, longPressTimeout);
2989 } else {
2990 mTouchMode = TOUCH_MODE_DONE_WAITING;
2991 }
2992 } else {
Romain Guy0a637162009-05-29 14:43:54 -07002993 mTouchMode = TOUCH_MODE_DONE_WAITING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002994 }
2995 }
2996 }
2997 }
2998 }
2999
Jeff Brown78f6e632011-09-09 17:15:31 -07003000 private boolean startScrollIfNeeded(int y) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003001 // Check if we have moved far enough that it looks more like a
3002 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003003 final int deltaY = y - mMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003004 final int distance = Math.abs(deltaY);
Adam Powell637d3372010-08-25 14:37:03 -07003005 final boolean overscroll = mScrollY != 0;
3006 if (overscroll || distance > mTouchSlop) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003007 createScrollingCache();
Jeff Brown78f6e632011-09-09 17:15:31 -07003008 if (overscroll) {
3009 mTouchMode = TOUCH_MODE_OVERSCROLL;
3010 mMotionCorrection = 0;
3011 } else {
3012 mTouchMode = TOUCH_MODE_SCROLL;
3013 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3014 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003015 final Handler handler = getHandler();
3016 // Handler should not be null unless the AbsListView is not attached to a
3017 // window, which would make it very hard to scroll it... but the monkeys
3018 // say it's possible.
3019 if (handler != null) {
3020 handler.removeCallbacks(mPendingCheckForLongPress);
3021 }
3022 setPressed(false);
3023 View motionView = getChildAt(mMotionPosition - mFirstPosition);
3024 if (motionView != null) {
3025 motionView.setPressed(false);
3026 }
3027 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3028 // Time to start stealing events! Once we've stolen them, don't let anyone
3029 // steal from us
Michael Jurka13451a42011-08-22 15:54:21 -07003030 final ViewParent parent = getParent();
3031 if (parent != null) {
3032 parent.requestDisallowInterceptTouchEvent(true);
3033 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003034 scrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003035 return true;
3036 }
3037
3038 return false;
3039 }
3040
Jeff Brown78f6e632011-09-09 17:15:31 -07003041 private void scrollIfNeeded(int y) {
3042 final int rawDeltaY = y - mMotionY;
3043 final int deltaY = rawDeltaY - mMotionCorrection;
3044 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
3045
3046 if (mTouchMode == TOUCH_MODE_SCROLL) {
3047 if (PROFILE_SCROLLING) {
3048 if (!mScrollProfilingStarted) {
3049 Debug.startMethodTracing("AbsListViewScroll");
3050 mScrollProfilingStarted = true;
3051 }
3052 }
3053
3054 if (mScrollStrictSpan == null) {
3055 // If it's non-null, we're already in a scroll.
3056 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3057 }
3058
3059 if (y != mLastY) {
3060 // We may be here after stopping a fling and continuing to scroll.
3061 // If so, we haven't disallowed intercepting touch events yet.
3062 // Make sure that we do so in case we're in a parent that can intercept.
3063 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3064 Math.abs(rawDeltaY) > mTouchSlop) {
3065 final ViewParent parent = getParent();
3066 if (parent != null) {
3067 parent.requestDisallowInterceptTouchEvent(true);
3068 }
3069 }
3070
3071 final int motionIndex;
3072 if (mMotionPosition >= 0) {
3073 motionIndex = mMotionPosition - mFirstPosition;
3074 } else {
3075 // If we don't have a motion position that we can reliably track,
3076 // pick something in the middle to make a best guess at things below.
3077 motionIndex = getChildCount() / 2;
3078 }
3079
3080 int motionViewPrevTop = 0;
3081 View motionView = this.getChildAt(motionIndex);
3082 if (motionView != null) {
3083 motionViewPrevTop = motionView.getTop();
3084 }
3085
3086 // No need to do all this work if we're not going to move anyway
3087 boolean atEdge = false;
3088 if (incrementalDeltaY != 0) {
3089 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3090 }
3091
3092 // Check to see if we have bumped into the scroll limit
3093 motionView = this.getChildAt(motionIndex);
3094 if (motionView != null) {
3095 // Check if the top of the motion view is where it is
3096 // supposed to be
3097 final int motionViewRealTop = motionView.getTop();
3098 if (atEdge) {
3099 // Apply overscroll
3100
3101 int overscroll = -incrementalDeltaY -
3102 (motionViewRealTop - motionViewPrevTop);
3103 overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
3104 0, mOverscrollDistance, true);
3105 if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
3106 // Don't allow overfling if we're at the edge.
3107 if (mVelocityTracker != null) {
3108 mVelocityTracker.clear();
3109 }
3110 }
3111
3112 final int overscrollMode = getOverScrollMode();
3113 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3114 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3115 !contentFits())) {
3116 mDirection = 0; // Reset when entering overscroll.
3117 mTouchMode = TOUCH_MODE_OVERSCROLL;
3118 if (rawDeltaY > 0) {
3119 mEdgeGlowTop.onPull((float) overscroll / getHeight());
3120 if (!mEdgeGlowBottom.isFinished()) {
3121 mEdgeGlowBottom.onRelease();
3122 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003123 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003124 } else if (rawDeltaY < 0) {
3125 mEdgeGlowBottom.onPull((float) overscroll / getHeight());
3126 if (!mEdgeGlowTop.isFinished()) {
3127 mEdgeGlowTop.onRelease();
3128 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003129 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003130 }
3131 }
3132 }
3133 mMotionY = y;
Jeff Brown78f6e632011-09-09 17:15:31 -07003134 }
3135 mLastY = y;
3136 }
3137 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3138 if (y != mLastY) {
3139 final int oldScroll = mScrollY;
3140 final int newScroll = oldScroll - incrementalDeltaY;
3141 int newDirection = y > mLastY ? 1 : -1;
3142
3143 if (mDirection == 0) {
3144 mDirection = newDirection;
3145 }
3146
3147 int overScrollDistance = -incrementalDeltaY;
3148 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3149 overScrollDistance = -oldScroll;
3150 incrementalDeltaY += overScrollDistance;
3151 } else {
3152 incrementalDeltaY = 0;
3153 }
3154
3155 if (overScrollDistance != 0) {
3156 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3157 0, mOverscrollDistance, true);
3158 final int overscrollMode = getOverScrollMode();
3159 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3160 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3161 !contentFits())) {
3162 if (rawDeltaY > 0) {
3163 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
3164 if (!mEdgeGlowBottom.isFinished()) {
3165 mEdgeGlowBottom.onRelease();
3166 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003167 invalidate(mEdgeGlowTop.getBounds(false));
Jeff Brown78f6e632011-09-09 17:15:31 -07003168 } else if (rawDeltaY < 0) {
3169 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
3170 if (!mEdgeGlowTop.isFinished()) {
3171 mEdgeGlowTop.onRelease();
3172 }
Romain Guya8bfeaf2012-03-15 13:14:14 -07003173 invalidate(mEdgeGlowBottom.getBounds(true));
Jeff Brown78f6e632011-09-09 17:15:31 -07003174 }
Jeff Brown78f6e632011-09-09 17:15:31 -07003175 }
3176 }
3177
3178 if (incrementalDeltaY != 0) {
3179 // Coming back to 'real' list scrolling
Romain Guy9d849a22012-03-14 16:41:42 -07003180 if (mScrollY != 0) {
3181 mScrollY = 0;
3182 invalidateParentIfNeeded();
Jeff Brown78f6e632011-09-09 17:15:31 -07003183 }
3184
Romain Guy9d849a22012-03-14 16:41:42 -07003185 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3186
Jeff Brown78f6e632011-09-09 17:15:31 -07003187 mTouchMode = TOUCH_MODE_SCROLL;
3188
3189 // We did not scroll the full amount. Treat this essentially like the
3190 // start of a new touch scroll
3191 final int motionPosition = findClosestMotionRow(y);
3192
3193 mMotionCorrection = 0;
3194 View motionView = getChildAt(motionPosition - mFirstPosition);
3195 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3196 mMotionY = y;
3197 mMotionPosition = motionPosition;
3198 }
3199 mLastY = y;
3200 mDirection = newDirection;
3201 }
3202 }
3203 }
3204
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003205 public void onTouchModeChanged(boolean isInTouchMode) {
3206 if (isInTouchMode) {
3207 // Get rid of the selection when we enter touch mode
3208 hideSelector();
3209 // Layout, but only if we already have done so previously.
3210 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3211 // state.)
3212 if (getHeight() > 0 && getChildCount() > 0) {
3213 // We do not lose focus initiating a touch (since AbsListView is focusable in
3214 // touch mode). Force an initial layout to get rid of the selection.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003215 layoutChildren();
3216 }
Jeff Brown1e209462011-07-14 22:19:19 -07003217 updateSelectorState();
Adam Powell637d3372010-08-25 14:37:03 -07003218 } else {
3219 int touchMode = mTouchMode;
3220 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3221 if (mFlingRunnable != null) {
3222 mFlingRunnable.endFling();
3223 }
Adam Powell40322522011-01-12 21:58:20 -08003224 if (mPositionScroller != null) {
3225 mPositionScroller.stop();
3226 }
Adam Powell637d3372010-08-25 14:37:03 -07003227
3228 if (mScrollY != 0) {
3229 mScrollY = 0;
Romain Guy0fd89bf2011-01-26 15:41:30 -08003230 invalidateParentCaches();
Adam Powell637d3372010-08-25 14:37:03 -07003231 finishGlows();
3232 invalidate();
3233 }
3234 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003235 }
3236 }
3237
3238 @Override
3239 public boolean onTouchEvent(MotionEvent ev) {
Romain Guydd753ae2009-08-17 10:36:23 -07003240 if (!isEnabled()) {
3241 // A disabled view that is clickable still consumes the touch
3242 // events, it just doesn't respond to them.
3243 return isClickable() || isLongClickable();
3244 }
3245
Adam Powell1fa179ef2012-04-12 15:01:40 -07003246 if (mPositionScroller != null) {
3247 mPositionScroller.stop();
3248 }
3249
Adam Powell28048d02012-06-06 22:46:42 -07003250 if (!mIsAttached) {
3251 // Something isn't right.
3252 // Since we rely on being attached to get data set change notifications,
3253 // don't risk doing anything where we might try to resync and find things
3254 // in a bogus state.
3255 return false;
3256 }
3257
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003258 if (mFastScroller != null) {
3259 boolean intercepted = mFastScroller.onTouchEvent(ev);
3260 if (intercepted) {
3261 return true;
Romain Guy0a637162009-05-29 14:43:54 -07003262 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003263 }
Romain Guy82f34952009-05-24 18:40:45 -07003264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003265 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003266
3267 View v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003268
Michael Jurka13451a42011-08-22 15:54:21 -07003269 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003270 mVelocityTracker.addMovement(ev);
3271
Adam Powell4cd47702010-02-25 11:21:14 -08003272 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003273 case MotionEvent.ACTION_DOWN: {
Adam Powell637d3372010-08-25 14:37:03 -07003274 switch (mTouchMode) {
3275 case TOUCH_MODE_OVERFLING: {
3276 mFlingRunnable.endFling();
Adam Powell40322522011-01-12 21:58:20 -08003277 if (mPositionScroller != null) {
3278 mPositionScroller.stop();
3279 }
Adam Powell637d3372010-08-25 14:37:03 -07003280 mTouchMode = TOUCH_MODE_OVERSCROLL;
Adam Powell044a46b2011-07-25 19:07:06 -07003281 mMotionX = (int) ev.getX();
Adam Powell637d3372010-08-25 14:37:03 -07003282 mMotionY = mLastY = (int) ev.getY();
3283 mMotionCorrection = 0;
3284 mActivePointerId = ev.getPointerId(0);
Adam Powell044a46b2011-07-25 19:07:06 -07003285 mDirection = 0;
Adam Powell637d3372010-08-25 14:37:03 -07003286 break;
3287 }
Adam Powell9d32d242010-03-29 16:02:07 -07003288
Adam Powell637d3372010-08-25 14:37:03 -07003289 default: {
3290 mActivePointerId = ev.getPointerId(0);
3291 final int x = (int) ev.getX();
3292 final int y = (int) ev.getY();
3293 int motionPosition = pointToPosition(x, y);
3294 if (!mDataChanged) {
3295 if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
3296 && (getAdapter().isEnabled(motionPosition))) {
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003297 // User clicked on an actual view (and was not stopping a fling).
3298 // It might be a click or a scroll. Assume it is a click until
3299 // proven otherwise
Adam Powell637d3372010-08-25 14:37:03 -07003300 mTouchMode = TOUCH_MODE_DOWN;
3301 // FIXME Debounce
3302 if (mPendingCheckForTap == null) {
3303 mPendingCheckForTap = new CheckForTap();
3304 }
3305 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
3306 } else {
Adam Powell637d3372010-08-25 14:37:03 -07003307 if (mTouchMode == TOUCH_MODE_FLING) {
3308 // Stopped a fling. It is a scroll.
3309 createScrollingCache();
3310 mTouchMode = TOUCH_MODE_SCROLL;
3311 mMotionCorrection = 0;
3312 motionPosition = findMotionRow(y);
3313 mFlingRunnable.flywheelTouch();
3314 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003315 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003316 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003317
Adam Powell637d3372010-08-25 14:37:03 -07003318 if (motionPosition >= 0) {
3319 // Remember where the motion event started
3320 v = getChildAt(motionPosition - mFirstPosition);
3321 mMotionViewOriginalTop = v.getTop();
3322 }
3323 mMotionX = x;
3324 mMotionY = y;
3325 mMotionPosition = motionPosition;
3326 mLastY = Integer.MIN_VALUE;
3327 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003328 }
Adam Powell637d3372010-08-25 14:37:03 -07003329 }
Jeff Brownfe9f8ab2011-05-06 18:20:01 -07003330
3331 if (performButtonActionOnTouchDown(ev)) {
3332 if (mTouchMode == TOUCH_MODE_DOWN) {
3333 removeCallbacks(mPendingCheckForTap);
3334 }
3335 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003336 break;
3337 }
3338
3339 case MotionEvent.ACTION_MOVE: {
Justin Koh2585e9b2011-06-30 17:11:26 -07003340 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3341 if (pointerIndex == -1) {
3342 pointerIndex = 0;
3343 mActivePointerId = ev.getPointerId(pointerIndex);
3344 }
Adam Powell4cd47702010-02-25 11:21:14 -08003345 final int y = (int) ev.getY(pointerIndex);
Adam Powell6f663c12012-04-30 16:59:02 -07003346
3347 if (mDataChanged) {
3348 // Re-sync everything if data has been changed
3349 // since the scroll operation can query the adapter.
3350 layoutChildren();
3351 }
3352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353 switch (mTouchMode) {
3354 case TOUCH_MODE_DOWN:
3355 case TOUCH_MODE_TAP:
3356 case TOUCH_MODE_DONE_WAITING:
3357 // Check if we have moved far enough that it looks more like a
3358 // scroll than a tap
Jeff Brown78f6e632011-09-09 17:15:31 -07003359 startScrollIfNeeded(y);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003360 break;
3361 case TOUCH_MODE_SCROLL:
Adam Powell637d3372010-08-25 14:37:03 -07003362 case TOUCH_MODE_OVERSCROLL:
Jeff Brown78f6e632011-09-09 17:15:31 -07003363 scrollIfNeeded(y);
Adam Powell637d3372010-08-25 14:37:03 -07003364 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003365 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003366 break;
3367 }
3368
3369 case MotionEvent.ACTION_UP: {
3370 switch (mTouchMode) {
3371 case TOUCH_MODE_DOWN:
3372 case TOUCH_MODE_TAP:
3373 case TOUCH_MODE_DONE_WAITING:
3374 final int motionPosition = mMotionPosition;
3375 final View child = getChildAt(motionPosition - mFirstPosition);
Adam Powell498e43d2011-03-01 15:39:53 -08003376
3377 final float x = ev.getX();
3378 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
3379
3380 if (child != null && !child.hasFocusable() && inList) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003381 if (mTouchMode != TOUCH_MODE_DOWN) {
3382 child.setPressed(false);
3383 }
3384
3385 if (mPerformClick == null) {
3386 mPerformClick = new PerformClick();
3387 }
3388
3389 final AbsListView.PerformClick performClick = mPerformClick;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003390 performClick.mClickMotionPosition = motionPosition;
3391 performClick.rememberWindowAttachCount();
3392
3393 mResurrectToPosition = motionPosition;
3394
3395 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
3396 final Handler handler = getHandler();
3397 if (handler != null) {
3398 handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3399 mPendingCheckForTap : mPendingCheckForLongPress);
3400 }
3401 mLayoutMode = LAYOUT_NORMAL;
Adam Powell005c0a42010-03-30 16:26:36 -07003402 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
3403 mTouchMode = TOUCH_MODE_TAP;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003404 setSelectedPositionInt(mMotionPosition);
3405 layoutChildren();
3406 child.setPressed(true);
Dianne Hackborn079e2352010-10-18 17:02:43 -07003407 positionSelector(mMotionPosition, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003408 setPressed(true);
3409 if (mSelector != null) {
3410 Drawable d = mSelector.getCurrent();
3411 if (d != null && d instanceof TransitionDrawable) {
Romain Guy6198ae82009-08-31 17:45:55 -07003412 ((TransitionDrawable) d).resetTransition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003413 }
3414 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003415 if (mTouchModeReset != null) {
3416 removeCallbacks(mTouchModeReset);
3417 }
3418 mTouchModeReset = new Runnable() {
3419 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003420 public void run() {
Adam Powell847be742012-12-11 15:41:17 -08003421 mTouchModeReset = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003422 mTouchMode = TOUCH_MODE_REST;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003423 child.setPressed(false);
3424 setPressed(false);
3425 if (!mDataChanged) {
Romain Guyd0b83652011-01-09 15:26:13 -08003426 performClick.run();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003428 }
Dianne Hackbornd173fa32010-12-23 13:58:22 -08003429 };
3430 postDelayed(mTouchModeReset,
3431 ViewConfiguration.getPressedStateDuration());
Adam Powell005c0a42010-03-30 16:26:36 -07003432 } else {
3433 mTouchMode = TOUCH_MODE_REST;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003434 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003435 }
3436 return true;
Adam Powell005c0a42010-03-30 16:26:36 -07003437 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
Romain Guyd0b83652011-01-09 15:26:13 -08003438 performClick.run();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003439 }
3440 }
3441 mTouchMode = TOUCH_MODE_REST;
Dianne Hackborn079e2352010-10-18 17:02:43 -07003442 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003443 break;
3444 case TOUCH_MODE_SCROLL:
Romain Guy6198ae82009-08-31 17:45:55 -07003445 final int childCount = getChildCount();
3446 if (childCount > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003447 final int firstChildTop = getChildAt(0).getTop();
3448 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
3449 final int contentTop = mListPadding.top;
3450 final int contentBottom = getHeight() - mListPadding.bottom;
3451 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
Romain Guy6198ae82009-08-31 17:45:55 -07003452 mFirstPosition + childCount < mItemCount &&
Adam Powell637d3372010-08-25 14:37:03 -07003453 lastChildBottom <= getHeight() - contentBottom) {
Romain Guy6198ae82009-08-31 17:45:55 -07003454 mTouchMode = TOUCH_MODE_REST;
3455 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3456 } else {
3457 final VelocityTracker velocityTracker = mVelocityTracker;
3458 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powell637d3372010-08-25 14:37:03 -07003459
Romain Guy21317d12010-10-12 13:32:31 -07003460 final int initialVelocity = (int)
3461 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
Adam Powell637d3372010-08-25 14:37:03 -07003462 // Fling if we have enough velocity and we aren't at a boundary.
3463 // Since we can potentially overfling more than we can overscroll, don't
3464 // allow the weird behavior where you can scroll to a boundary then
3465 // fling further.
3466 if (Math.abs(initialVelocity) > mMinimumVelocity &&
3467 !((mFirstPosition == 0 &&
3468 firstChildTop == contentTop - mOverscrollDistance) ||
3469 (mFirstPosition + childCount == mItemCount &&
3470 lastChildBottom == contentBottom + mOverscrollDistance))) {
Romain Guy6198ae82009-08-31 17:45:55 -07003471 if (mFlingRunnable == null) {
3472 mFlingRunnable = new FlingRunnable();
3473 }
3474 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003475
Romain Guy6198ae82009-08-31 17:45:55 -07003476 mFlingRunnable.start(-initialVelocity);
Romain Guyf3c7d422009-12-04 15:38:22 -08003477 } else {
3478 mTouchMode = TOUCH_MODE_REST;
3479 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Gilles Debunned348bb42010-11-15 12:19:35 -08003480 if (mFlingRunnable != null) {
3481 mFlingRunnable.endFling();
3482 }
Adam Powell40322522011-01-12 21:58:20 -08003483 if (mPositionScroller != null) {
3484 mPositionScroller.stop();
3485 }
Romain Guy6198ae82009-08-31 17:45:55 -07003486 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003488 } else {
3489 mTouchMode = TOUCH_MODE_REST;
3490 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3491 }
Adam Powell79ac3392010-01-28 21:22:20 -08003492 break;
Adam Powell637d3372010-08-25 14:37:03 -07003493
3494 case TOUCH_MODE_OVERSCROLL:
3495 if (mFlingRunnable == null) {
3496 mFlingRunnable = new FlingRunnable();
3497 }
3498 final VelocityTracker velocityTracker = mVelocityTracker;
3499 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3500 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3501
3502 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
3503 if (Math.abs(initialVelocity) > mMinimumVelocity) {
3504 mFlingRunnable.startOverfling(-initialVelocity);
3505 } else {
3506 mFlingRunnable.startSpringback();
3507 }
3508
3509 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003510 }
3511
3512 setPressed(false);
Romain Guy0a637162009-05-29 14:43:54 -07003513
Adam Powell637d3372010-08-25 14:37:03 -07003514 if (mEdgeGlowTop != null) {
3515 mEdgeGlowTop.onRelease();
3516 mEdgeGlowBottom.onRelease();
3517 }
3518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003519 // Need to redraw since we probably aren't drawing the selector anymore
3520 invalidate();
Romain Guy0a637162009-05-29 14:43:54 -07003521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003522 final Handler handler = getHandler();
3523 if (handler != null) {
3524 handler.removeCallbacks(mPendingCheckForLongPress);
3525 }
3526
Michael Jurka13451a42011-08-22 15:54:21 -07003527 recycleVelocityTracker();
Mindy Pereira4e30d892010-11-24 15:32:39 -08003528
Adam Powell4cd47702010-02-25 11:21:14 -08003529 mActivePointerId = INVALID_POINTER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003530
3531 if (PROFILE_SCROLLING) {
3532 if (mScrollProfilingStarted) {
3533 Debug.stopMethodTracing();
3534 mScrollProfilingStarted = false;
3535 }
3536 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003537
3538 if (mScrollStrictSpan != null) {
3539 mScrollStrictSpan.finish();
3540 mScrollStrictSpan = null;
3541 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003542 break;
3543 }
3544
3545 case MotionEvent.ACTION_CANCEL: {
Adam Powell637d3372010-08-25 14:37:03 -07003546 switch (mTouchMode) {
3547 case TOUCH_MODE_OVERSCROLL:
3548 if (mFlingRunnable == null) {
3549 mFlingRunnable = new FlingRunnable();
3550 }
3551 mFlingRunnable.startSpringback();
3552 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003553
Adam Powell637d3372010-08-25 14:37:03 -07003554 case TOUCH_MODE_OVERFLING:
3555 // Do nothing - let it play out.
3556 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003557
Adam Powell637d3372010-08-25 14:37:03 -07003558 default:
3559 mTouchMode = TOUCH_MODE_REST;
3560 setPressed(false);
3561 View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
3562 if (motionView != null) {
3563 motionView.setPressed(false);
3564 }
3565 clearScrollingCache();
3566
3567 final Handler handler = getHandler();
3568 if (handler != null) {
3569 handler.removeCallbacks(mPendingCheckForLongPress);
3570 }
3571
Michael Jurka13451a42011-08-22 15:54:21 -07003572 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003573 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003574
Adam Powell637d3372010-08-25 14:37:03 -07003575 if (mEdgeGlowTop != null) {
3576 mEdgeGlowTop.onRelease();
3577 mEdgeGlowBottom.onRelease();
3578 }
Adam Powell4cd47702010-02-25 11:21:14 -08003579 mActivePointerId = INVALID_POINTER;
3580 break;
3581 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003582
Adam Powell4cd47702010-02-25 11:21:14 -08003583 case MotionEvent.ACTION_POINTER_UP: {
3584 onSecondaryPointerUp(ev);
3585 final int x = mMotionX;
3586 final int y = mMotionY;
3587 final int motionPosition = pointToPosition(x, y);
3588 if (motionPosition >= 0) {
3589 // Remember where the motion event started
3590 v = getChildAt(motionPosition - mFirstPosition);
3591 mMotionViewOriginalTop = v.getTop();
3592 mMotionPosition = motionPosition;
3593 }
3594 mLastY = y;
3595 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003596 }
Adam Powell9bc30d32011-02-28 10:27:49 -08003597
3598 case MotionEvent.ACTION_POINTER_DOWN: {
3599 // New pointers take over dragging duties
3600 final int index = ev.getActionIndex();
3601 final int id = ev.getPointerId(index);
3602 final int x = (int) ev.getX(index);
3603 final int y = (int) ev.getY(index);
3604 mMotionCorrection = 0;
3605 mActivePointerId = id;
3606 mMotionX = x;
3607 mMotionY = y;
3608 final int motionPosition = pointToPosition(x, y);
3609 if (motionPosition >= 0) {
3610 // Remember where the motion event started
3611 v = getChildAt(motionPosition - mFirstPosition);
3612 mMotionViewOriginalTop = v.getTop();
3613 mMotionPosition = motionPosition;
3614 }
3615 mLastY = y;
3616 break;
3617 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003618 }
3619
3620 return true;
3621 }
Romain Guy0a637162009-05-29 14:43:54 -07003622
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003623 @Override
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003624 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
3625 if (mScrollY != scrollY) {
3626 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
3627 mScrollY = scrollY;
3628 invalidateParentIfNeeded();
Adam Powell637d3372010-08-25 14:37:03 -07003629
Gilles Debunne0a1b8182011-02-28 16:01:09 -08003630 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -07003631 }
Adam Powell637d3372010-08-25 14:37:03 -07003632 }
3633
3634 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -08003635 public boolean onGenericMotionEvent(MotionEvent event) {
3636 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3637 switch (event.getAction()) {
3638 case MotionEvent.ACTION_SCROLL: {
3639 if (mTouchMode == TOUCH_MODE_REST) {
3640 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3641 if (vscroll != 0) {
3642 final int delta = (int) (vscroll * getVerticalScrollFactor());
Jeff Brown275d8232011-02-28 14:50:02 -08003643 if (!trackMotionScroll(delta, delta)) {
Jeff Brown33bbfd22011-02-24 20:55:35 -08003644 return true;
3645 }
3646 }
3647 }
3648 }
3649 }
3650 }
3651 return super.onGenericMotionEvent(event);
3652 }
3653
3654 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003655 public void draw(Canvas canvas) {
3656 super.draw(canvas);
Adam Powell637d3372010-08-25 14:37:03 -07003657 if (mEdgeGlowTop != null) {
3658 final int scrollY = mScrollY;
3659 if (!mEdgeGlowTop.isFinished()) {
3660 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003661 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3662 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3663 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003664
Romain Guy9d849a22012-03-14 16:41:42 -07003665 int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
3666 canvas.translate(leftPadding, edgeY);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003667 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07003668 if (mEdgeGlowTop.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003669 mEdgeGlowTop.setPosition(leftPadding, edgeY);
Romain Guya8bfeaf2012-03-15 13:14:14 -07003670 invalidate(mEdgeGlowTop.getBounds(false));
Adam Powell637d3372010-08-25 14:37:03 -07003671 }
3672 canvas.restoreToCount(restoreCount);
3673 }
3674 if (!mEdgeGlowBottom.isFinished()) {
3675 final int restoreCount = canvas.save();
Adam Powell07d6f7b2011-03-02 14:27:30 -08003676 final int leftPadding = mListPadding.left + mGlowPaddingLeft;
3677 final int rightPadding = mListPadding.right + mGlowPaddingRight;
3678 final int width = getWidth() - leftPadding - rightPadding;
Adam Powell637d3372010-08-25 14:37:03 -07003679 final int height = getHeight();
3680
Romain Guy9d849a22012-03-14 16:41:42 -07003681 int edgeX = -width + leftPadding;
3682 int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
3683 canvas.translate(edgeX, edgeY);
Mindy Pereirae1be66c2010-12-09 10:23:59 -08003684 canvas.rotate(180, width, 0);
Mindy Pereiraa5531d72010-11-23 11:07:30 -08003685 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07003686 if (mEdgeGlowBottom.draw(canvas)) {
Romain Guy9d849a22012-03-14 16:41:42 -07003687 // Account for the rotation
Romain Guya8bfeaf2012-03-15 13:14:14 -07003688 mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
3689 invalidate(mEdgeGlowBottom.getBounds(true));
Adam Powell637d3372010-08-25 14:37:03 -07003690 }
3691 canvas.restoreToCount(restoreCount);
3692 }
3693 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003694 if (mFastScroller != null) {
Adam Powell637d3372010-08-25 14:37:03 -07003695 final int scrollY = mScrollY;
3696 if (scrollY != 0) {
3697 // Pin to the top/bottom during overscroll
3698 int restoreCount = canvas.save();
3699 canvas.translate(0, (float) scrollY);
3700 mFastScroller.draw(canvas);
3701 canvas.restoreToCount(restoreCount);
3702 } else {
3703 mFastScroller.draw(canvas);
3704 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003705 }
3706 }
3707
Adam Powell07d6f7b2011-03-02 14:27:30 -08003708 /**
3709 * @hide
3710 */
3711 public void setOverScrollEffectPadding(int leftPadding, int rightPadding) {
3712 mGlowPaddingLeft = leftPadding;
3713 mGlowPaddingRight = rightPadding;
3714 }
3715
Michael Jurka13451a42011-08-22 15:54:21 -07003716 private void initOrResetVelocityTracker() {
3717 if (mVelocityTracker == null) {
3718 mVelocityTracker = VelocityTracker.obtain();
3719 } else {
3720 mVelocityTracker.clear();
3721 }
3722 }
3723
3724 private void initVelocityTrackerIfNotExists() {
3725 if (mVelocityTracker == null) {
3726 mVelocityTracker = VelocityTracker.obtain();
3727 }
3728 }
3729
3730 private void recycleVelocityTracker() {
3731 if (mVelocityTracker != null) {
3732 mVelocityTracker.recycle();
3733 mVelocityTracker = null;
3734 }
3735 }
3736
3737 @Override
3738 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3739 if (disallowIntercept) {
3740 recycleVelocityTracker();
3741 }
3742 super.requestDisallowInterceptTouchEvent(disallowIntercept);
3743 }
3744
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003745 @Override
3746 public boolean onInterceptTouchEvent(MotionEvent ev) {
3747 int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003748 View v;
Romain Guy0a637162009-05-29 14:43:54 -07003749
Adam Powell1fa179ef2012-04-12 15:01:40 -07003750 if (mPositionScroller != null) {
3751 mPositionScroller.stop();
3752 }
3753
Adam Powell28048d02012-06-06 22:46:42 -07003754 if (!mIsAttached) {
3755 // Something isn't right.
3756 // Since we rely on being attached to get data set change notifications,
3757 // don't risk doing anything where we might try to resync and find things
3758 // in a bogus state.
3759 return false;
3760 }
3761
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003762 if (mFastScroller != null) {
3763 boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
3764 if (intercepted) {
3765 return true;
3766 }
3767 }
Romain Guy0a637162009-05-29 14:43:54 -07003768
Adam Powell4cd47702010-02-25 11:21:14 -08003769 switch (action & MotionEvent.ACTION_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003770 case MotionEvent.ACTION_DOWN: {
Adam Powell79ac3392010-01-28 21:22:20 -08003771 int touchMode = mTouchMode;
Adam Powell637d3372010-08-25 14:37:03 -07003772 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
3773 mMotionCorrection = 0;
3774 return true;
3775 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003776
Adam Powell4cd47702010-02-25 11:21:14 -08003777 final int x = (int) ev.getX();
3778 final int y = (int) ev.getY();
3779 mActivePointerId = ev.getPointerId(0);
Mindy Pereira4e30d892010-11-24 15:32:39 -08003780
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003781 int motionPosition = findMotionRow(y);
Adam Powell79ac3392010-01-28 21:22:20 -08003782 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003783 // User clicked on an actual view (and was not stopping a fling).
3784 // Remember where the motion event started
3785 v = getChildAt(motionPosition - mFirstPosition);
3786 mMotionViewOriginalTop = v.getTop();
3787 mMotionX = x;
3788 mMotionY = y;
3789 mMotionPosition = motionPosition;
3790 mTouchMode = TOUCH_MODE_DOWN;
3791 clearScrollingCache();
3792 }
3793 mLastY = Integer.MIN_VALUE;
Michael Jurka13451a42011-08-22 15:54:21 -07003794 initOrResetVelocityTracker();
3795 mVelocityTracker.addMovement(ev);
Adam Powell79ac3392010-01-28 21:22:20 -08003796 if (touchMode == TOUCH_MODE_FLING) {
Romain Guy4b4f40f2009-11-06 17:41:43 -08003797 return true;
3798 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003799 break;
3800 }
3801
3802 case MotionEvent.ACTION_MOVE: {
3803 switch (mTouchMode) {
3804 case TOUCH_MODE_DOWN:
Justin Koh2585e9b2011-06-30 17:11:26 -07003805 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3806 if (pointerIndex == -1) {
3807 pointerIndex = 0;
3808 mActivePointerId = ev.getPointerId(pointerIndex);
3809 }
Adam Powell4cd47702010-02-25 11:21:14 -08003810 final int y = (int) ev.getY(pointerIndex);
Michael Jurka13451a42011-08-22 15:54:21 -07003811 initVelocityTrackerIfNotExists();
3812 mVelocityTracker.addMovement(ev);
Jeff Brown78f6e632011-09-09 17:15:31 -07003813 if (startScrollIfNeeded(y)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003814 return true;
3815 }
3816 break;
3817 }
3818 break;
3819 }
3820
Michael Jurka13451a42011-08-22 15:54:21 -07003821 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003822 case MotionEvent.ACTION_UP: {
3823 mTouchMode = TOUCH_MODE_REST;
Adam Powell4cd47702010-02-25 11:21:14 -08003824 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -07003825 recycleVelocityTracker();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003826 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
3827 break;
3828 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003829
Adam Powell4cd47702010-02-25 11:21:14 -08003830 case MotionEvent.ACTION_POINTER_UP: {
3831 onSecondaryPointerUp(ev);
3832 break;
3833 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003834 }
3835
3836 return false;
3837 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08003838
Adam Powell4cd47702010-02-25 11:21:14 -08003839 private void onSecondaryPointerUp(MotionEvent ev) {
3840 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3841 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3842 final int pointerId = ev.getPointerId(pointerIndex);
3843 if (pointerId == mActivePointerId) {
3844 // This was our active pointer going up. Choose a new
3845 // active pointer and adjust accordingly.
3846 // TODO: Make this decision more intelligent.
3847 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3848 mMotionX = (int) ev.getX(newPointerIndex);
3849 mMotionY = (int) ev.getY(newPointerIndex);
Adam Powell637d3372010-08-25 14:37:03 -07003850 mMotionCorrection = 0;
Adam Powell4cd47702010-02-25 11:21:14 -08003851 mActivePointerId = ev.getPointerId(newPointerIndex);
Adam Powell4cd47702010-02-25 11:21:14 -08003852 }
3853 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003854
3855 /**
3856 * {@inheritDoc}
3857 */
3858 @Override
3859 public void addTouchables(ArrayList<View> views) {
3860 final int count = getChildCount();
3861 final int firstPosition = mFirstPosition;
3862 final ListAdapter adapter = mAdapter;
3863
3864 if (adapter == null) {
3865 return;
3866 }
3867
3868 for (int i = 0; i < count; i++) {
3869 final View child = getChildAt(i);
3870 if (adapter.isEnabled(firstPosition + i)) {
3871 views.add(child);
3872 }
3873 child.addTouchables(views);
3874 }
3875 }
3876
3877 /**
3878 * Fires an "on scroll state changed" event to the registered
3879 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
3880 * is fired only if the specified state is different from the previously known state.
3881 *
3882 * @param newState The new scroll state.
3883 */
3884 void reportScrollStateChange(int newState) {
3885 if (newState != mLastScrollState) {
3886 if (mOnScrollListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003887 mLastScrollState = newState;
Adam Powell0046bd8e2010-11-16 11:37:36 -08003888 mOnScrollListener.onScrollStateChanged(this, newState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003889 }
3890 }
3891 }
3892
3893 /**
3894 * Responsible for fling behavior. Use {@link #start(int)} to
3895 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
3896 * A FlingRunnable will keep re-posting itself until the fling is done.
3897 *
3898 */
3899 private class FlingRunnable implements Runnable {
3900 /**
3901 * Tracks the decay of a fling scroll
3902 */
Adam Powell637d3372010-08-25 14:37:03 -07003903 private final OverScroller mScroller;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003904
3905 /**
3906 * Y value reported by mScroller on the previous fling
3907 */
3908 private int mLastFlingY;
3909
Gilles Debunned348bb42010-11-15 12:19:35 -08003910 private final Runnable mCheckFlywheel = new Runnable() {
3911 public void run() {
3912 final int activeId = mActivePointerId;
3913 final VelocityTracker vt = mVelocityTracker;
Adam Powell637d3372010-08-25 14:37:03 -07003914 final OverScroller scroller = mScroller;
Gilles Debunned348bb42010-11-15 12:19:35 -08003915 if (vt == null || activeId == INVALID_POINTER) {
3916 return;
3917 }
3918
3919 vt.computeCurrentVelocity(1000, mMaximumVelocity);
3920 final float yvel = -vt.getYVelocity(activeId);
3921
Jeff Brownb0c71eb2011-09-16 21:40:49 -07003922 if (Math.abs(yvel) >= mMinimumVelocity
3923 && scroller.isScrollingInDirection(0, yvel)) {
Gilles Debunned348bb42010-11-15 12:19:35 -08003924 // Keep the fling alive a little longer
3925 postDelayed(this, FLYWHEEL_TIMEOUT);
3926 } else {
3927 endFling();
3928 mTouchMode = TOUCH_MODE_SCROLL;
Erikb43d6a32010-11-19 16:41:20 -08003929 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
Gilles Debunned348bb42010-11-15 12:19:35 -08003930 }
3931 }
3932 };
3933
3934 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
3935
Adam Powell79ac3392010-01-28 21:22:20 -08003936 FlingRunnable() {
Adam Powell637d3372010-08-25 14:37:03 -07003937 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003938 }
3939
Adam Powell79ac3392010-01-28 21:22:20 -08003940 void start(int initialVelocity) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003941 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
3942 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07003943 mScroller.setInterpolator(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003944 mScroller.fling(0, initialY, 0, initialVelocity,
3945 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
3946 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07003947 postOnAnimation(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003948
3949 if (PROFILE_FLINGING) {
3950 if (!mFlingProfilingStarted) {
3951 Debug.startMethodTracing("AbsListViewFling");
3952 mFlingProfilingStarted = true;
3953 }
3954 }
Brad Fitzpatrick1cc13b62010-11-16 15:35:58 -08003955
3956 if (mFlingStrictSpan == null) {
3957 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
3958 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003959 }
Adam Powell45803472010-01-25 15:10:44 -08003960
Adam Powell637d3372010-08-25 14:37:03 -07003961 void startSpringback() {
3962 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
3963 mTouchMode = TOUCH_MODE_OVERFLING;
3964 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07003965 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07003966 } else {
3967 mTouchMode = TOUCH_MODE_REST;
Gilles Debunnee20a1932011-02-25 14:54:11 -08003968 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
Adam Powell637d3372010-08-25 14:37:03 -07003969 }
3970 }
3971
3972 void startOverfling(int initialVelocity) {
Adam Powell0b8acd82012-04-25 20:29:23 -07003973 mScroller.setInterpolator(null);
Adam Powell044a46b2011-07-25 19:07:06 -07003974 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
3975 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07003976 mTouchMode = TOUCH_MODE_OVERFLING;
3977 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07003978 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07003979 }
3980
3981 void edgeReached(int delta) {
3982 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
3983 final int overscrollMode = getOverScrollMode();
3984 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3985 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
3986 mTouchMode = TOUCH_MODE_OVERFLING;
3987 final int vel = (int) mScroller.getCurrVelocity();
3988 if (delta > 0) {
3989 mEdgeGlowTop.onAbsorb(vel);
3990 } else {
3991 mEdgeGlowBottom.onAbsorb(vel);
3992 }
Adam Powell40322522011-01-12 21:58:20 -08003993 } else {
3994 mTouchMode = TOUCH_MODE_REST;
3995 if (mPositionScroller != null) {
3996 mPositionScroller.stop();
3997 }
Adam Powell637d3372010-08-25 14:37:03 -07003998 }
3999 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004000 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004001 }
4002
Adam Powell0b8acd82012-04-25 20:29:23 -07004003 void startScroll(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004004 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4005 mLastFlingY = initialY;
Adam Powell0b8acd82012-04-25 20:29:23 -07004006 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
Adam Powell45803472010-01-25 15:10:44 -08004007 mScroller.startScroll(0, initialY, 0, distance, duration);
4008 mTouchMode = TOUCH_MODE_FLING;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004009 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004010 }
4011
Gilles Debunned348bb42010-11-15 12:19:35 -08004012 void endFling() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004013 mTouchMode = TOUCH_MODE_REST;
Adam Powell45803472010-01-25 15:10:44 -08004014
Adam Powell79ac3392010-01-28 21:22:20 -08004015 removeCallbacks(this);
Gilles Debunned348bb42010-11-15 12:19:35 -08004016 removeCallbacks(mCheckFlywheel);
Romain Guy21317d12010-10-12 13:32:31 -07004017
4018 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4019 clearScrollingCache();
Gilles Debunned348bb42010-11-15 12:19:35 -08004020 mScroller.abortAnimation();
Brad Fitzpatrick5e9d94502010-11-19 12:03:22 -08004021
4022 if (mFlingStrictSpan != null) {
4023 mFlingStrictSpan.finish();
4024 mFlingStrictSpan = null;
4025 }
Gilles Debunned348bb42010-11-15 12:19:35 -08004026 }
4027
4028 void flywheelTouch() {
4029 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004030 }
4031
4032 public void run() {
Adam Powell79ac3392010-01-28 21:22:20 -08004033 switch (mTouchMode) {
4034 default:
Gilles Debunned348bb42010-11-15 12:19:35 -08004035 endFling();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004036 return;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004037
Gilles Debunned348bb42010-11-15 12:19:35 -08004038 case TOUCH_MODE_SCROLL:
4039 if (mScroller.isFinished()) {
4040 return;
4041 }
4042 // Fall through
Adam Powell637d3372010-08-25 14:37:03 -07004043 case TOUCH_MODE_FLING: {
Jeff Sharkey7f2202b2011-09-12 17:05:18 -07004044 if (mDataChanged) {
4045 layoutChildren();
4046 }
4047
Adam Powell79ac3392010-01-28 21:22:20 -08004048 if (mItemCount == 0 || getChildCount() == 0) {
4049 endFling();
4050 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004051 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004052
Adam Powell637d3372010-08-25 14:37:03 -07004053 final OverScroller scroller = mScroller;
4054 boolean more = scroller.computeScrollOffset();
4055 final int y = scroller.getCurrY();
Adam Powell79ac3392010-01-28 21:22:20 -08004056
Adam Powell637d3372010-08-25 14:37:03 -07004057 // Flip sign to convert finger direction to list items direction
4058 // (e.g. finger moving down means list is moving towards the top)
4059 int delta = mLastFlingY - y;
Adam Powell79ac3392010-01-28 21:22:20 -08004060
Adam Powell637d3372010-08-25 14:37:03 -07004061 // Pretend that each frame of a fling scroll is a touch scroll
4062 if (delta > 0) {
4063 // List is moving towards the top. Use first view as mMotionPosition
4064 mMotionPosition = mFirstPosition;
4065 final View firstView = getChildAt(0);
4066 mMotionViewOriginalTop = firstView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004067
Adam Powell637d3372010-08-25 14:37:03 -07004068 // Don't fling more than 1 screen
4069 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4070 } else {
4071 // List is moving towards the bottom. Use last view as mMotionPosition
4072 int offsetToLast = getChildCount() - 1;
4073 mMotionPosition = mFirstPosition + offsetToLast;
Adam Powell79ac3392010-01-28 21:22:20 -08004074
Adam Powell637d3372010-08-25 14:37:03 -07004075 final View lastView = getChildAt(offsetToLast);
4076 mMotionViewOriginalTop = lastView.getTop();
Adam Powell79ac3392010-01-28 21:22:20 -08004077
Adam Powell637d3372010-08-25 14:37:03 -07004078 // Don't fling more than 1 screen
4079 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4080 }
Adam Powell79ac3392010-01-28 21:22:20 -08004081
Adam Powell637d3372010-08-25 14:37:03 -07004082 // Check to see if we have bumped into the scroll limit
4083 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4084 int oldTop = 0;
4085 if (motionView != null) {
4086 oldTop = motionView.getTop();
4087 }
Adam Powell9d32d242010-03-29 16:02:07 -07004088
Adam Powell637d3372010-08-25 14:37:03 -07004089 // Don't stop just because delta is zero (it could have been rounded)
Romain Guy9d849a22012-03-14 16:41:42 -07004090 final boolean atEdge = trackMotionScroll(delta, delta);
4091 final boolean atEnd = atEdge && (delta != 0);
Adam Powell637d3372010-08-25 14:37:03 -07004092 if (atEnd) {
4093 if (motionView != null) {
4094 // Tweak the scroll for how far we overshot
4095 int overshoot = -(delta - (motionView.getTop() - oldTop));
4096 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4097 0, mOverflingDistance, false);
4098 }
Adam Powell3cd0c572010-10-25 11:08:06 -07004099 if (more) {
4100 edgeReached(delta);
4101 }
Adam Powell637d3372010-08-25 14:37:03 -07004102 break;
4103 }
4104
4105 if (more && !atEnd) {
Romain Guy9d849a22012-03-14 16:41:42 -07004106 if (atEdge) invalidate();
Adam Powell637d3372010-08-25 14:37:03 -07004107 mLastFlingY = y;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004108 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004109 } else {
4110 endFling();
4111
4112 if (PROFILE_FLINGING) {
4113 if (mFlingProfilingStarted) {
4114 Debug.stopMethodTracing();
4115 mFlingProfilingStarted = false;
4116 }
4117
4118 if (mFlingStrictSpan != null) {
4119 mFlingStrictSpan.finish();
4120 mFlingStrictSpan = null;
4121 }
Adam Powell79ac3392010-01-28 21:22:20 -08004122 }
4123 }
Adam Powell637d3372010-08-25 14:37:03 -07004124 break;
4125 }
4126
4127 case TOUCH_MODE_OVERFLING: {
4128 final OverScroller scroller = mScroller;
4129 if (scroller.computeScrollOffset()) {
4130 final int scrollY = mScrollY;
Adam Powell044a46b2011-07-25 19:07:06 -07004131 final int currY = scroller.getCurrY();
4132 final int deltaY = currY - scrollY;
Adam Powell637d3372010-08-25 14:37:03 -07004133 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4134 0, mOverflingDistance, false)) {
Adam Powell044a46b2011-07-25 19:07:06 -07004135 final boolean crossDown = scrollY <= 0 && currY > 0;
4136 final boolean crossUp = scrollY >= 0 && currY < 0;
4137 if (crossDown || crossUp) {
4138 int velocity = (int) scroller.getCurrVelocity();
4139 if (crossUp) velocity = -velocity;
4140
4141 // Don't flywheel from this; we're just continuing things.
4142 scroller.abortAnimation();
4143 start(velocity);
4144 } else {
4145 startSpringback();
4146 }
Adam Powell637d3372010-08-25 14:37:03 -07004147 } else {
4148 invalidate();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004149 postOnAnimation(this);
Adam Powell637d3372010-08-25 14:37:03 -07004150 }
4151 } else {
4152 endFling();
4153 }
4154 break;
4155 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004156 }
4157 }
4158 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004159
Adam Powell45803472010-01-25 15:10:44 -08004160 class PositionScroller implements Runnable {
Adam Powelle69370e2012-05-07 15:17:20 -07004161 private static final int SCROLL_DURATION = 200;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004162
Adam Powell45803472010-01-25 15:10:44 -08004163 private static final int MOVE_DOWN_POS = 1;
4164 private static final int MOVE_UP_POS = 2;
4165 private static final int MOVE_DOWN_BOUND = 3;
4166 private static final int MOVE_UP_BOUND = 4;
Adam Powelle44afae2010-07-01 10:10:35 -07004167 private static final int MOVE_OFFSET = 5;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004168
Adam Powell45803472010-01-25 15:10:44 -08004169 private int mMode;
4170 private int mTargetPos;
4171 private int mBoundPos;
4172 private int mLastSeenPos;
4173 private int mScrollDuration;
Gilles Debunne52964242010-02-24 11:05:19 -08004174 private final int mExtraScroll;
Adam Powelle44afae2010-07-01 10:10:35 -07004175
4176 private int mOffsetFromTop;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004177
Adam Powell45803472010-01-25 15:10:44 -08004178 PositionScroller() {
4179 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
4180 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004181
Adam Powelle69370e2012-05-07 15:17:20 -07004182 void start(final int position) {
Adam Powell40322522011-01-12 21:58:20 -08004183 stop();
4184
Adam Powellaadf4fb2012-05-08 15:42:13 -07004185 if (mDataChanged) {
4186 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004187 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004188 @Override public void run() {
4189 start(position);
4190 }
Adam Powell161abf32012-05-23 17:22:49 -07004191 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004192 return;
4193 }
4194
Adam Powelle69370e2012-05-07 15:17:20 -07004195 final int childCount = getChildCount();
4196 if (childCount == 0) {
4197 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004198 return;
4199 }
4200
Adam Powell45803472010-01-25 15:10:44 -08004201 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004202 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004203
Romain Guy4bede9e2010-10-11 19:36:59 -07004204 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004205 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4206 if (clampedPosition < firstPos) {
4207 viewTravelCount = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004208 mMode = MOVE_UP_POS;
Chet Haase0061e162012-06-08 15:01:56 -07004209 } else if (clampedPosition > lastPos) {
4210 viewTravelCount = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004211 mMode = MOVE_DOWN_POS;
4212 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004213 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004214 return;
4215 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004216
Adam Powell45803472010-01-25 15:10:44 -08004217 if (viewTravelCount > 0) {
4218 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4219 } else {
4220 mScrollDuration = SCROLL_DURATION;
4221 }
Chet Haase0061e162012-06-08 15:01:56 -07004222 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004223 mBoundPos = INVALID_POSITION;
4224 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004225
Adam Powell1fa179ef2012-04-12 15:01:40 -07004226 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004227 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004228
Adam Powelle69370e2012-05-07 15:17:20 -07004229 void start(final int position, final int boundPosition) {
Adam Powell40322522011-01-12 21:58:20 -08004230 stop();
4231
Adam Powell45803472010-01-25 15:10:44 -08004232 if (boundPosition == INVALID_POSITION) {
4233 start(position);
4234 return;
4235 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004236
Adam Powellaadf4fb2012-05-08 15:42:13 -07004237 if (mDataChanged) {
4238 // Wait until we're back in a stable state to try this.
Adam Powell161abf32012-05-23 17:22:49 -07004239 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004240 @Override public void run() {
4241 start(position, boundPosition);
4242 }
Adam Powell161abf32012-05-23 17:22:49 -07004243 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004244 return;
4245 }
4246
Adam Powelle69370e2012-05-07 15:17:20 -07004247 final int childCount = getChildCount();
4248 if (childCount == 0) {
4249 // Can't scroll without children.
Adam Powelle69370e2012-05-07 15:17:20 -07004250 return;
4251 }
4252
Adam Powell45803472010-01-25 15:10:44 -08004253 final int firstPos = mFirstPosition;
Adam Powelle69370e2012-05-07 15:17:20 -07004254 final int lastPos = firstPos + childCount - 1;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004255
Romain Guy4bede9e2010-10-11 19:36:59 -07004256 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004257 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
4258 if (clampedPosition < firstPos) {
Adam Powell45803472010-01-25 15:10:44 -08004259 final int boundPosFromLast = lastPos - boundPosition;
4260 if (boundPosFromLast < 1) {
4261 // Moving would shift our bound position off the screen. Abort.
4262 return;
4263 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004264
Chet Haase0061e162012-06-08 15:01:56 -07004265 final int posTravel = firstPos - clampedPosition + 1;
Adam Powell45803472010-01-25 15:10:44 -08004266 final int boundTravel = boundPosFromLast - 1;
4267 if (boundTravel < posTravel) {
4268 viewTravelCount = boundTravel;
4269 mMode = MOVE_UP_BOUND;
4270 } else {
4271 viewTravelCount = posTravel;
4272 mMode = MOVE_UP_POS;
4273 }
Chet Haase0061e162012-06-08 15:01:56 -07004274 } else if (clampedPosition > lastPos) {
Adam Powell45803472010-01-25 15:10:44 -08004275 final int boundPosFromFirst = boundPosition - firstPos;
4276 if (boundPosFromFirst < 1) {
4277 // Moving would shift our bound position off the screen. Abort.
4278 return;
4279 }
4280
Chet Haase0061e162012-06-08 15:01:56 -07004281 final int posTravel = clampedPosition - lastPos + 1;
Adam Powell45803472010-01-25 15:10:44 -08004282 final int boundTravel = boundPosFromFirst - 1;
4283 if (boundTravel < posTravel) {
4284 viewTravelCount = boundTravel;
4285 mMode = MOVE_DOWN_BOUND;
4286 } else {
4287 viewTravelCount = posTravel;
4288 mMode = MOVE_DOWN_POS;
4289 }
4290 } else {
Chet Haase0061e162012-06-08 15:01:56 -07004291 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
Adam Powell45803472010-01-25 15:10:44 -08004292 return;
4293 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004294
Adam Powell45803472010-01-25 15:10:44 -08004295 if (viewTravelCount > 0) {
4296 mScrollDuration = SCROLL_DURATION / viewTravelCount;
4297 } else {
4298 mScrollDuration = SCROLL_DURATION;
4299 }
Chet Haase0061e162012-06-08 15:01:56 -07004300 mTargetPos = clampedPosition;
Adam Powell45803472010-01-25 15:10:44 -08004301 mBoundPos = boundPosition;
4302 mLastSeenPos = INVALID_POSITION;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004303
Adam Powell1fa179ef2012-04-12 15:01:40 -07004304 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004305 }
Adam Powelle44afae2010-07-01 10:10:35 -07004306
4307 void startWithOffset(int position, int offset) {
Erik322171b2010-10-13 15:46:00 -07004308 startWithOffset(position, offset, SCROLL_DURATION);
4309 }
4310
Adam Powellaadf4fb2012-05-08 15:42:13 -07004311 void startWithOffset(final int position, int offset, final int duration) {
Adam Powell40322522011-01-12 21:58:20 -08004312 stop();
4313
Adam Powellaadf4fb2012-05-08 15:42:13 -07004314 if (mDataChanged) {
4315 // Wait until we're back in a stable state to try this.
4316 final int postOffset = offset;
Adam Powell161abf32012-05-23 17:22:49 -07004317 mPositionScrollAfterLayout = new Runnable() {
Adam Powellaadf4fb2012-05-08 15:42:13 -07004318 @Override public void run() {
4319 startWithOffset(position, postOffset, duration);
4320 }
Adam Powell161abf32012-05-23 17:22:49 -07004321 };
Adam Powellaadf4fb2012-05-08 15:42:13 -07004322 return;
4323 }
4324
4325 final int childCount = getChildCount();
4326 if (childCount == 0) {
4327 // Can't scroll without children.
4328 return;
4329 }
4330
Adam Powell1fa179ef2012-04-12 15:01:40 -07004331 offset += getPaddingTop();
4332
Chet Haase0061e162012-06-08 15:01:56 -07004333 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
Adam Powelle44afae2010-07-01 10:10:35 -07004334 mOffsetFromTop = offset;
4335 mBoundPos = INVALID_POSITION;
4336 mLastSeenPos = INVALID_POSITION;
4337 mMode = MOVE_OFFSET;
4338
4339 final int firstPos = mFirstPosition;
Adam Powell37113312010-07-08 18:21:48 -07004340 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004341
Romain Guy4bede9e2010-10-11 19:36:59 -07004342 int viewTravelCount;
Chet Haase0061e162012-06-08 15:01:56 -07004343 if (mTargetPos < firstPos) {
4344 viewTravelCount = firstPos - mTargetPos;
4345 } else if (mTargetPos > lastPos) {
4346 viewTravelCount = mTargetPos - lastPos;
Adam Powelle44afae2010-07-01 10:10:35 -07004347 } else {
4348 // On-screen, just scroll.
Chet Haase0061e162012-06-08 15:01:56 -07004349 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
Adam Powell0b8acd82012-04-25 20:29:23 -07004350 smoothScrollBy(targetTop - offset, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004351 return;
4352 }
4353
Adam Powell37113312010-07-08 18:21:48 -07004354 // Estimate how many screens we should travel
Daniel Lehmann4ef1da32010-08-27 16:40:59 -07004355 final float screenTravelCount = (float) viewTravelCount / childCount;
Adam Powell0b8acd82012-04-25 20:29:23 -07004356 mScrollDuration = screenTravelCount < 1 ?
4357 duration : (int) (duration / screenTravelCount);
Adam Powell37113312010-07-08 18:21:48 -07004358 mLastSeenPos = INVALID_POSITION;
Adam Powell234a5712010-09-14 10:34:56 -07004359
Adam Powell1fa179ef2012-04-12 15:01:40 -07004360 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004361 }
4362
Adam Powelle69370e2012-05-07 15:17:20 -07004363 /**
4364 * Scroll such that targetPos is in the visible padded region without scrolling
4365 * boundPos out of view. Assumes targetPos is onscreen.
4366 */
4367 void scrollToVisible(int targetPos, int boundPos, int duration) {
4368 final int firstPos = mFirstPosition;
4369 final int childCount = getChildCount();
4370 final int lastPos = firstPos + childCount - 1;
4371 final int paddedTop = mListPadding.top;
4372 final int paddedBottom = getHeight() - mListPadding.bottom;
4373
4374 if (targetPos < firstPos || targetPos > lastPos) {
4375 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
4376 " not visible [" + firstPos + ", " + lastPos + "]");
4377 }
4378 if (boundPos < firstPos || boundPos > lastPos) {
4379 // boundPos doesn't matter, it's already offscreen.
4380 boundPos = INVALID_POSITION;
4381 }
4382
4383 final View targetChild = getChildAt(targetPos - firstPos);
4384 final int targetTop = targetChild.getTop();
4385 final int targetBottom = targetChild.getBottom();
4386 int scrollBy = 0;
4387
4388 if (targetBottom > paddedBottom) {
4389 scrollBy = targetBottom - paddedBottom;
4390 }
4391 if (targetTop < paddedTop) {
4392 scrollBy = targetTop - paddedTop;
4393 }
4394
4395 if (scrollBy == 0) {
4396 return;
4397 }
4398
4399 if (boundPos >= 0) {
4400 final View boundChild = getChildAt(boundPos - firstPos);
4401 final int boundTop = boundChild.getTop();
4402 final int boundBottom = boundChild.getBottom();
4403 final int absScroll = Math.abs(scrollBy);
4404
4405 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
4406 // Don't scroll the bound view off the bottom of the screen.
4407 scrollBy = Math.max(0, boundBottom - paddedBottom);
4408 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
4409 // Don't scroll the bound view off the top of the screen.
4410 scrollBy = Math.min(0, boundTop - paddedTop);
4411 }
4412 }
4413
4414 smoothScrollBy(scrollBy, duration);
4415 }
4416
Adam Powell45803472010-01-25 15:10:44 -08004417 void stop() {
4418 removeCallbacks(this);
4419 }
Adam Powelle44afae2010-07-01 10:10:35 -07004420
Adam Powell45803472010-01-25 15:10:44 -08004421 public void run() {
4422 final int listHeight = getHeight();
4423 final int firstPos = mFirstPosition;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004424
Adam Powell45803472010-01-25 15:10:44 -08004425 switch (mMode) {
4426 case MOVE_DOWN_POS: {
4427 final int lastViewIndex = getChildCount() - 1;
4428 final int lastPos = firstPos + lastViewIndex;
Mindy Pereira4e30d892010-11-24 15:32:39 -08004429
Adam Powell0b8bb422010-02-08 14:30:45 -08004430 if (lastViewIndex < 0) {
4431 return;
4432 }
Adam Powell45803472010-01-25 15:10:44 -08004433
4434 if (lastPos == mLastSeenPos) {
4435 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004436 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004437 return;
4438 }
4439
4440 final View lastView = getChildAt(lastViewIndex);
4441 final int lastViewHeight = lastView.getHeight();
4442 final int lastViewTop = lastView.getTop();
4443 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004444 final int extraScroll = lastPos < mItemCount - 1 ?
4445 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
Adam Powell45803472010-01-25 15:10:44 -08004446
Adam Powell1fa179ef2012-04-12 15:01:40 -07004447 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
Adam Powell0b8acd82012-04-25 20:29:23 -07004448 smoothScrollBy(scrollBy, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004449
4450 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004451 if (lastPos < mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004452 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004453 }
4454 break;
4455 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004456
Adam Powell45803472010-01-25 15:10:44 -08004457 case MOVE_DOWN_BOUND: {
4458 final int nextViewIndex = 1;
Adam Powell029cfbd2010-03-08 19:03:54 -08004459 final int childCount = getChildCount();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004460
Adam Powell029cfbd2010-03-08 19:03:54 -08004461 if (firstPos == mBoundPos || childCount <= nextViewIndex
4462 || firstPos + childCount >= mItemCount) {
Adam Powell45803472010-01-25 15:10:44 -08004463 return;
4464 }
4465 final int nextPos = firstPos + nextViewIndex;
4466
4467 if (nextPos == mLastSeenPos) {
4468 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004469 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004470 return;
4471 }
4472
4473 final View nextView = getChildAt(nextViewIndex);
4474 final int nextViewHeight = nextView.getHeight();
4475 final int nextViewTop = nextView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004476 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
Adam Powell7e5e3742010-05-24 15:13:41 -07004477 if (nextPos < mBoundPos) {
Adam Powell45803472010-01-25 15:10:44 -08004478 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
Adam Powell0b8acd82012-04-25 20:29:23 -07004479 mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004480
4481 mLastSeenPos = nextPos;
4482
Adam Powell1fa179ef2012-04-12 15:01:40 -07004483 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004484 } else {
Mindy Pereira4e30d892010-11-24 15:32:39 -08004485 if (nextViewTop > extraScroll) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004486 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004487 }
4488 }
4489 break;
4490 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004491
Adam Powell45803472010-01-25 15:10:44 -08004492 case MOVE_UP_POS: {
4493 if (firstPos == mLastSeenPos) {
4494 // No new views, let things keep going.
Adam Powell1fa179ef2012-04-12 15:01:40 -07004495 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004496 return;
4497 }
4498
4499 final View firstView = getChildAt(0);
Adam Powell0b8bb422010-02-08 14:30:45 -08004500 if (firstView == null) {
4501 return;
4502 }
Adam Powell45803472010-01-25 15:10:44 -08004503 final int firstViewTop = firstView.getTop();
Adam Powell1fa179ef2012-04-12 15:01:40 -07004504 final int extraScroll = firstPos > 0 ?
4505 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
Adam Powell45803472010-01-25 15:10:44 -08004506
Adam Powell0b8acd82012-04-25 20:29:23 -07004507 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004508
4509 mLastSeenPos = firstPos;
4510
Adam Powell7e5e3742010-05-24 15:13:41 -07004511 if (firstPos > mTargetPos) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004512 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004513 }
4514 break;
4515 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004516
Adam Powell45803472010-01-25 15:10:44 -08004517 case MOVE_UP_BOUND: {
4518 final int lastViewIndex = getChildCount() - 2;
4519 if (lastViewIndex < 0) {
4520 return;
4521 }
4522 final int lastPos = firstPos + lastViewIndex;
4523
4524 if (lastPos == mLastSeenPos) {
4525 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004526 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004527 return;
4528 }
4529
4530 final View lastView = getChildAt(lastViewIndex);
4531 final int lastViewHeight = lastView.getHeight();
4532 final int lastViewTop = lastView.getTop();
4533 final int lastViewPixelsShowing = listHeight - lastViewTop;
Adam Powell1fa179ef2012-04-12 15:01:40 -07004534 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
Adam Powell45803472010-01-25 15:10:44 -08004535 mLastSeenPos = lastPos;
Adam Powell7e5e3742010-05-24 15:13:41 -07004536 if (lastPos > mBoundPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004537 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004538 postOnAnimation(this);
Adam Powell45803472010-01-25 15:10:44 -08004539 } else {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004540 final int bottom = listHeight - extraScroll;
Adam Powell45803472010-01-25 15:10:44 -08004541 final int lastViewBottom = lastViewTop + lastViewHeight;
4542 if (bottom > lastViewBottom) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004543 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
Adam Powell45803472010-01-25 15:10:44 -08004544 }
4545 }
4546 break;
4547 }
4548
Adam Powelle44afae2010-07-01 10:10:35 -07004549 case MOVE_OFFSET: {
Adam Powell234a5712010-09-14 10:34:56 -07004550 if (mLastSeenPos == firstPos) {
4551 // No new views, let things keep going.
Adam Powell0b8acd82012-04-25 20:29:23 -07004552 postOnAnimation(this);
Adam Powell234a5712010-09-14 10:34:56 -07004553 return;
4554 }
Adam Powelle44afae2010-07-01 10:10:35 -07004555
Adam Powell37113312010-07-08 18:21:48 -07004556 mLastSeenPos = firstPos;
Adam Powell234a5712010-09-14 10:34:56 -07004557
4558 final int childCount = getChildCount();
Adam Powelle44afae2010-07-01 10:10:35 -07004559 final int position = mTargetPos;
Adam Powell37113312010-07-08 18:21:48 -07004560 final int lastPos = firstPos + childCount - 1;
Adam Powelle44afae2010-07-01 10:10:35 -07004561
Adam Powell40322522011-01-12 21:58:20 -08004562 int viewTravelCount = 0;
Adam Powelle44afae2010-07-01 10:10:35 -07004563 if (position < firstPos) {
Adam Powell40322522011-01-12 21:58:20 -08004564 viewTravelCount = firstPos - position + 1;
4565 } else if (position > lastPos) {
4566 viewTravelCount = position - lastPos;
4567 }
4568
4569 // Estimate how many screens we should travel
4570 final float screenTravelCount = (float) viewTravelCount / childCount;
4571
4572 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
4573 if (position < firstPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004574 final int distance = (int) (-getHeight() * modifier);
4575 final int duration = (int) (mScrollDuration * modifier);
4576 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004577 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004578 } else if (position > lastPos) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004579 final int distance = (int) (getHeight() * modifier);
4580 final int duration = (int) (mScrollDuration * modifier);
4581 smoothScrollBy(distance, duration, true);
Adam Powell1fa179ef2012-04-12 15:01:40 -07004582 postOnAnimation(this);
Adam Powelle44afae2010-07-01 10:10:35 -07004583 } else {
4584 // On-screen, just scroll.
4585 final int targetTop = getChildAt(position - firstPos).getTop();
Adam Powell234a5712010-09-14 10:34:56 -07004586 final int distance = targetTop - mOffsetFromTop;
Adam Powell0b8acd82012-04-25 20:29:23 -07004587 final int duration = (int) (mScrollDuration *
4588 ((float) Math.abs(distance) / getHeight()));
4589 smoothScrollBy(distance, duration, true);
Adam Powelle44afae2010-07-01 10:10:35 -07004590 }
4591 break;
4592 }
4593
Adam Powell45803472010-01-25 15:10:44 -08004594 default:
4595 break;
4596 }
4597 }
4598 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004599
Adam Powell45803472010-01-25 15:10:44 -08004600 /**
Romain Guy4bede9e2010-10-11 19:36:59 -07004601 * The amount of friction applied to flings. The default value
4602 * is {@link ViewConfiguration#getScrollFriction}.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004603 *
Romain Guy4bede9e2010-10-11 19:36:59 -07004604 * @return A scalar dimensionless value representing the coefficient of
4605 * friction.
4606 */
4607 public void setFriction(float friction) {
4608 if (mFlingRunnable == null) {
4609 mFlingRunnable = new FlingRunnable();
4610 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004611 mFlingRunnable.mScroller.setFriction(friction);
Romain Guy4bede9e2010-10-11 19:36:59 -07004612 }
Romain Guy21317d12010-10-12 13:32:31 -07004613
4614 /**
4615 * Sets a scale factor for the fling velocity. The initial scale
4616 * factor is 1.0.
Mindy Pereira4e30d892010-11-24 15:32:39 -08004617 *
Romain Guy21317d12010-10-12 13:32:31 -07004618 * @param scale The scale factor to multiply the velocity by.
4619 */
4620 public void setVelocityScale(float scale) {
4621 mVelocityScale = scale;
4622 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004623
Romain Guy4bede9e2010-10-11 19:36:59 -07004624 /**
Adam Powell45803472010-01-25 15:10:44 -08004625 * Smoothly scroll to the specified adapter position. The view will
4626 * scroll such that the indicated position is displayed.
4627 * @param position Scroll to this adapter position.
4628 */
4629 public void smoothScrollToPosition(int position) {
4630 if (mPositionScroller == null) {
4631 mPositionScroller = new PositionScroller();
4632 }
4633 mPositionScroller.start(position);
4634 }
Erik322171b2010-10-13 15:46:00 -07004635
4636 /**
4637 * Smoothly scroll to the specified adapter position. The view will scroll
4638 * such that the indicated position is displayed <code>offset</code> pixels from
4639 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4640 * the first or last item beyond the boundaries of the list) it will get as close
4641 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4642 *
4643 * @param position Position to scroll to
4644 * @param offset Desired distance in pixels of <code>position</code> from the top
4645 * of the view when scrolling is finished
4646 * @param duration Number of milliseconds to use for the scroll
4647 */
4648 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4649 if (mPositionScroller == null) {
4650 mPositionScroller = new PositionScroller();
4651 }
4652 mPositionScroller.startWithOffset(position, offset, duration);
4653 }
4654
Adam Powell45803472010-01-25 15:10:44 -08004655 /**
Adam Powelle44afae2010-07-01 10:10:35 -07004656 * Smoothly scroll to the specified adapter position. The view will scroll
4657 * such that the indicated position is displayed <code>offset</code> pixels from
4658 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4659 * the first or last item beyond the boundaries of the list) it will get as close
4660 * as possible.
4661 *
4662 * @param position Position to scroll to
4663 * @param offset Desired distance in pixels of <code>position</code> from the top
4664 * of the view when scrolling is finished
4665 */
4666 public void smoothScrollToPositionFromTop(int position, int offset) {
4667 if (mPositionScroller == null) {
4668 mPositionScroller = new PositionScroller();
4669 }
4670 mPositionScroller.startWithOffset(position, offset);
4671 }
4672
4673 /**
Adam Powell45803472010-01-25 15:10:44 -08004674 * Smoothly scroll to the specified adapter position. The view will
4675 * scroll such that the indicated position is displayed, but it will
4676 * stop early if scrolling further would scroll boundPosition out of
Mindy Pereira4e30d892010-11-24 15:32:39 -08004677 * view.
Adam Powell45803472010-01-25 15:10:44 -08004678 * @param position Scroll to this adapter position.
4679 * @param boundPosition Do not scroll if it would move this adapter
4680 * position out of view.
4681 */
4682 public void smoothScrollToPosition(int position, int boundPosition) {
4683 if (mPositionScroller == null) {
4684 mPositionScroller = new PositionScroller();
4685 }
4686 mPositionScroller.start(position, boundPosition);
4687 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004688
Adam Powell45803472010-01-25 15:10:44 -08004689 /**
4690 * Smoothly scroll by distance pixels over duration milliseconds.
4691 * @param distance Distance to scroll in pixels.
4692 * @param duration Duration of the scroll animation in milliseconds.
4693 */
4694 public void smoothScrollBy(int distance, int duration) {
Adam Powell0b8acd82012-04-25 20:29:23 -07004695 smoothScrollBy(distance, duration, false);
4696 }
4697
4698 void smoothScrollBy(int distance, int duration, boolean linear) {
Adam Powell45803472010-01-25 15:10:44 -08004699 if (mFlingRunnable == null) {
4700 mFlingRunnable = new FlingRunnable();
Adam Powell45803472010-01-25 15:10:44 -08004701 }
Adam Powell40322522011-01-12 21:58:20 -08004702
Marc Blank299acb52010-10-21 11:03:53 -07004703 // No sense starting to scroll if we're not going anywhere
Adam Powell40322522011-01-12 21:58:20 -08004704 final int firstPos = mFirstPosition;
4705 final int childCount = getChildCount();
4706 final int lastPos = firstPos + childCount;
4707 final int topLimit = getPaddingTop();
4708 final int bottomLimit = getHeight() - getPaddingBottom();
4709
Adam Powell79303752011-01-13 22:06:49 -08004710 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
Adam Powell40322522011-01-12 21:58:20 -08004711 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
Adam Powellaadf4fb2012-05-08 15:42:13 -07004712 (lastPos == mItemCount &&
Adam Powell40322522011-01-12 21:58:20 -08004713 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4714 mFlingRunnable.endFling();
4715 if (mPositionScroller != null) {
4716 mPositionScroller.stop();
4717 }
4718 } else {
Adam Powell0046bd8e2010-11-16 11:37:36 -08004719 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
Adam Powell0b8acd82012-04-25 20:29:23 -07004720 mFlingRunnable.startScroll(distance, duration, linear);
Marc Blank299acb52010-10-21 11:03:53 -07004721 }
Adam Powell45803472010-01-25 15:10:44 -08004722 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004723
Winson Chung499cb9f2010-07-16 11:18:17 -07004724 /**
4725 * Allows RemoteViews to scroll relatively to a position.
4726 */
4727 void smoothScrollByOffset(int position) {
4728 int index = -1;
4729 if (position < 0) {
4730 index = getFirstVisiblePosition();
4731 } else if (position > 0) {
4732 index = getLastVisiblePosition();
4733 }
4734
4735 if (index > -1) {
4736 View child = getChildAt(index - getFirstVisiblePosition());
4737 if (child != null) {
4738 Rect visibleRect = new Rect();
4739 if (child.getGlobalVisibleRect(visibleRect)) {
4740 // the child is partially visible
4741 int childRectArea = child.getWidth() * child.getHeight();
4742 int visibleRectArea = visibleRect.width() * visibleRect.height();
4743 float visibleArea = (visibleRectArea / (float) childRectArea);
4744 final float visibleThreshold = 0.75f;
4745 if ((position < 0) && (visibleArea < visibleThreshold)) {
4746 // the top index is not perceivably visible so offset
4747 // to account for showing that top index as well
4748 ++index;
4749 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4750 // the bottom index is not perceivably visible so offset
4751 // to account for showing that bottom index as well
4752 --index;
4753 }
4754 }
4755 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
4756 }
4757 }
4758 }
4759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004760 private void createScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004761 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004762 setChildrenDrawnWithCacheEnabled(true);
4763 setChildrenDrawingCacheEnabled(true);
Romain Guy0211a0a2011-02-14 16:34:59 -08004764 mCachingStarted = mCachingActive = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004765 }
4766 }
4767
4768 private void clearScrollingCache() {
Romain Guy9d849a22012-03-14 16:41:42 -07004769 if (!isHardwareAccelerated()) {
4770 if (mClearScrollingCache == null) {
4771 mClearScrollingCache = new Runnable() {
4772 public void run() {
4773 if (mCachingStarted) {
4774 mCachingStarted = mCachingActive = false;
4775 setChildrenDrawnWithCacheEnabled(false);
4776 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
4777 setChildrenDrawingCacheEnabled(false);
4778 }
4779 if (!isAlwaysDrawnWithCacheEnabled()) {
4780 invalidate();
4781 }
Romain Guy6dfed242009-05-11 18:25:05 -07004782 }
4783 }
Romain Guy9d849a22012-03-14 16:41:42 -07004784 };
4785 }
4786 post(mClearScrollingCache);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004787 }
4788 }
4789
4790 /**
4791 * Track a motion scroll
4792 *
4793 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
4794 * began. Positive numbers mean the user's finger is moving down the screen.
4795 * @param incrementalDeltaY Change in deltaY from the previous event.
Adam Powell45803472010-01-25 15:10:44 -08004796 * @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 -08004797 */
Adam Powell45803472010-01-25 15:10:44 -08004798 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004799 final int childCount = getChildCount();
4800 if (childCount == 0) {
Adam Powell45803472010-01-25 15:10:44 -08004801 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004802 }
4803
4804 final int firstTop = getChildAt(0).getTop();
4805 final int lastBottom = getChildAt(childCount - 1).getBottom();
4806
4807 final Rect listPadding = mListPadding;
4808
Adam Powellbdccc2d2010-12-14 17:34:27 -08004809 // "effective padding" In this case is the amount of padding that affects
4810 // how much space should not be filled by items. If we don't clip to padding
4811 // there is no effective padding.
4812 int effectivePaddingTop = 0;
4813 int effectivePaddingBottom = 0;
4814 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4815 effectivePaddingTop = listPadding.top;
4816 effectivePaddingBottom = listPadding.bottom;
4817 }
4818
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004819 // FIXME account for grid vertical spacing too?
Adam Powellbdccc2d2010-12-14 17:34:27 -08004820 final int spaceAbove = effectivePaddingTop - firstTop;
4821 final int end = getHeight() - effectivePaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004822 final int spaceBelow = lastBottom - end;
4823
4824 final int height = getHeight() - mPaddingBottom - mPaddingTop;
4825 if (deltaY < 0) {
4826 deltaY = Math.max(-(height - 1), deltaY);
4827 } else {
4828 deltaY = Math.min(height - 1, deltaY);
4829 }
4830
4831 if (incrementalDeltaY < 0) {
4832 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
4833 } else {
4834 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
4835 }
4836
Adam Powell45803472010-01-25 15:10:44 -08004837 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004838
Adam Powell637d3372010-08-25 14:37:03 -07004839 // Update our guesses for where the first and last views are
4840 if (firstPosition == 0) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004841 mFirstPositionDistanceGuess = firstTop - listPadding.top;
Adam Powell637d3372010-08-25 14:37:03 -07004842 } else {
4843 mFirstPositionDistanceGuess += incrementalDeltaY;
4844 }
4845 if (firstPosition + childCount == mItemCount) {
Adam Powellbdccc2d2010-12-14 17:34:27 -08004846 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
Adam Powell637d3372010-08-25 14:37:03 -07004847 } else {
4848 mLastPositionDistanceGuess += incrementalDeltaY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004849 }
Adam Powell45803472010-01-25 15:10:44 -08004850
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004851 final boolean cannotScrollDown = (firstPosition == 0 &&
4852 firstTop >= listPadding.top && incrementalDeltaY >= 0);
4853 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
4854 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
Adam Powell637d3372010-08-25 14:37:03 -07004855
Gilles Debunne0a1b8182011-02-28 16:01:09 -08004856 if (cannotScrollDown || cannotScrollUp) {
Adam Powell637d3372010-08-25 14:37:03 -07004857 return incrementalDeltaY != 0;
Adam Powell45803472010-01-25 15:10:44 -08004858 }
4859
4860 final boolean down = incrementalDeltaY < 0;
4861
Adam Powell029cfbd2010-03-08 19:03:54 -08004862 final boolean inTouchMode = isInTouchMode();
4863 if (inTouchMode) {
4864 hideSelector();
4865 }
Adam Powell45803472010-01-25 15:10:44 -08004866
4867 final int headerViewsCount = getHeaderViewsCount();
4868 final int footerViewsStart = mItemCount - getFooterViewsCount();
4869
4870 int start = 0;
4871 int count = 0;
4872
4873 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004874 int top = -incrementalDeltaY;
4875 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4876 top += listPadding.top;
4877 }
Adam Powell45803472010-01-25 15:10:44 -08004878 for (int i = 0; i < childCount; i++) {
4879 final View child = getChildAt(i);
4880 if (child.getBottom() >= top) {
4881 break;
4882 } else {
4883 count++;
4884 int position = firstPosition + i;
4885 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004886 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004887 }
4888 }
4889 }
4890 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -08004891 int bottom = getHeight() - incrementalDeltaY;
4892 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
4893 bottom -= listPadding.bottom;
4894 }
Adam Powell45803472010-01-25 15:10:44 -08004895 for (int i = childCount - 1; i >= 0; i--) {
4896 final View child = getChildAt(i);
4897 if (child.getTop() <= bottom) {
4898 break;
4899 } else {
4900 start = i;
4901 count++;
4902 int position = firstPosition + i;
4903 if (position >= headerViewsCount && position < footerViewsStart) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004904 mRecycler.addScrapView(child, position);
Adam Powell45803472010-01-25 15:10:44 -08004905 }
4906 }
4907 }
4908 }
4909
4910 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
4911
4912 mBlockLayoutRequests = true;
4913
4914 if (count > 0) {
4915 detachViewsFromParent(start, count);
Adam Powell539ee872012-02-03 19:00:49 -08004916 mRecycler.removeSkippedScrap();
Adam Powell45803472010-01-25 15:10:44 -08004917 }
Adam Powell539ee872012-02-03 19:00:49 -08004918
Romain Guy9d849a22012-03-14 16:41:42 -07004919 // invalidate before moving the children to avoid unnecessary invalidate
4920 // calls to bubble up from the children all the way to the top
4921 if (!awakenScrollBars()) {
Adam Powell1fa179ef2012-04-12 15:01:40 -07004922 invalidate();
Romain Guy9d849a22012-03-14 16:41:42 -07004923 }
4924
Adam Powell45803472010-01-25 15:10:44 -08004925 offsetChildrenTopAndBottom(incrementalDeltaY);
4926
4927 if (down) {
4928 mFirstPosition += count;
4929 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08004930
Adam Powell45803472010-01-25 15:10:44 -08004931 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
4932 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
4933 fillGap(down);
4934 }
4935
Adam Powell029cfbd2010-03-08 19:03:54 -08004936 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
Adam Powell2a20ddd2010-03-11 18:09:59 -08004937 final int childIndex = mSelectedPosition - mFirstPosition;
4938 if (childIndex >= 0 && childIndex < getChildCount()) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07004939 positionSelector(mSelectedPosition, getChildAt(childIndex));
Adam Powell2a20ddd2010-03-11 18:09:59 -08004940 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07004941 } else if (mSelectorPosition != INVALID_POSITION) {
4942 final int childIndex = mSelectorPosition - mFirstPosition;
4943 if (childIndex >= 0 && childIndex < getChildCount()) {
4944 positionSelector(INVALID_POSITION, getChildAt(childIndex));
4945 }
4946 } else {
4947 mSelectorRect.setEmpty();
Adam Powell029cfbd2010-03-08 19:03:54 -08004948 }
4949
Adam Powell45803472010-01-25 15:10:44 -08004950 mBlockLayoutRequests = false;
4951
4952 invokeOnItemScrollListener();
Mindy Pereira4e30d892010-11-24 15:32:39 -08004953
Adam Powell45803472010-01-25 15:10:44 -08004954 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004955 }
4956
4957 /**
4958 * Returns the number of header views in the list. Header views are special views
4959 * at the top of the list that should not be recycled during a layout.
4960 *
4961 * @return The number of header views, 0 in the default implementation.
4962 */
4963 int getHeaderViewsCount() {
4964 return 0;
4965 }
4966
4967 /**
4968 * Returns the number of footer views in the list. Footer views are special views
4969 * at the bottom of the list that should not be recycled during a layout.
4970 *
4971 * @return The number of footer views, 0 in the default implementation.
4972 */
4973 int getFooterViewsCount() {
4974 return 0;
4975 }
4976
4977 /**
4978 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
4979 * remain on screen are shifted and the other ones are discarded. The role of this
4980 * method is to fill the gap thus created by performing a partial layout in the
4981 * empty space.
4982 *
4983 * @param down true if the scroll is going down, false if it is going up
4984 */
4985 abstract void fillGap(boolean down);
4986
4987 void hideSelector() {
4988 if (mSelectedPosition != INVALID_POSITION) {
Adam Powellab3e1052010-02-18 10:35:05 -08004989 if (mLayoutMode != LAYOUT_SPECIFIC) {
4990 mResurrectToPosition = mSelectedPosition;
4991 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004992 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
4993 mResurrectToPosition = mNextSelectedPosition;
4994 }
4995 setSelectedPositionInt(INVALID_POSITION);
4996 setNextSelectedPositionInt(INVALID_POSITION);
4997 mSelectedTop = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004998 }
4999 }
5000
5001 /**
5002 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5003 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5004 * of items available in the adapter
5005 */
5006 int reconcileSelectedPosition() {
5007 int position = mSelectedPosition;
5008 if (position < 0) {
5009 position = mResurrectToPosition;
5010 }
5011 position = Math.max(0, position);
5012 position = Math.min(position, mItemCount - 1);
5013 return position;
5014 }
5015
5016 /**
5017 * Find the row closest to y. This row will be used as the motion row when scrolling
5018 *
5019 * @param y Where the user touched
Adam Powell4cd47702010-02-25 11:21:14 -08005020 * @return The position of the first (or only) item in the row containing y
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005021 */
5022 abstract int findMotionRow(int y);
5023
5024 /**
Adam Powell637d3372010-08-25 14:37:03 -07005025 * Find the row closest to y. This row will be used as the motion row when scrolling.
5026 *
5027 * @param y Where the user touched
5028 * @return The position of the first (or only) item in the row closest to y
5029 */
5030 int findClosestMotionRow(int y) {
5031 final int childCount = getChildCount();
5032 if (childCount == 0) {
5033 return INVALID_POSITION;
5034 }
5035
5036 final int motionRow = findMotionRow(y);
5037 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5038 }
5039
5040 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005041 * Causes all the views to be rebuilt and redrawn.
5042 */
5043 public void invalidateViews() {
5044 mDataChanged = true;
5045 rememberSyncState();
5046 requestLayout();
5047 invalidate();
5048 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005049
5050 /**
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005051 * If there is a selection returns false.
5052 * Otherwise resurrects the selection and returns true if resurrected.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005053 */
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005054 boolean resurrectSelectionIfNeeded() {
Adam Powellbecb0be2011-03-22 00:06:28 -07005055 if (mSelectedPosition < 0 && resurrectSelection()) {
5056 updateSelectorState();
5057 return true;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005058 }
Jeff Brown8d6d3b82011-01-26 19:31:18 -08005059 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005060 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005061
5062 /**
5063 * Makes the item at the supplied position selected.
5064 *
5065 * @param position the position of the new selection
5066 */
5067 abstract void setSelectionInt(int position);
5068
5069 /**
5070 * Attempt to bring the selection back if the user is switching from touch
5071 * to trackball mode
5072 * @return Whether selection was set to something.
5073 */
5074 boolean resurrectSelection() {
5075 final int childCount = getChildCount();
5076
5077 if (childCount <= 0) {
5078 return false;
5079 }
5080
5081 int selectedTop = 0;
5082 int selectedPos;
5083 int childrenTop = mListPadding.top;
5084 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5085 final int firstPosition = mFirstPosition;
5086 final int toPosition = mResurrectToPosition;
5087 boolean down = true;
5088
5089 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5090 selectedPos = toPosition;
5091
5092 final View selected = getChildAt(selectedPos - mFirstPosition);
5093 selectedTop = selected.getTop();
5094 int selectedBottom = selected.getBottom();
5095
5096 // We are scrolled, don't get in the fade
5097 if (selectedTop < childrenTop) {
5098 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5099 } else if (selectedBottom > childrenBottom) {
5100 selectedTop = childrenBottom - selected.getMeasuredHeight()
5101 - getVerticalFadingEdgeLength();
5102 }
5103 } else {
5104 if (toPosition < firstPosition) {
5105 // Default to selecting whatever is first
5106 selectedPos = firstPosition;
5107 for (int i = 0; i < childCount; i++) {
5108 final View v = getChildAt(i);
5109 final int top = v.getTop();
5110
5111 if (i == 0) {
5112 // Remember the position of the first item
5113 selectedTop = top;
5114 // See if we are scrolled at all
5115 if (firstPosition > 0 || top < childrenTop) {
5116 // If we are scrolled, don't select anything that is
5117 // in the fade region
5118 childrenTop += getVerticalFadingEdgeLength();
5119 }
5120 }
5121 if (top >= childrenTop) {
5122 // Found a view whose top is fully visisble
5123 selectedPos = firstPosition + i;
5124 selectedTop = top;
5125 break;
5126 }
5127 }
5128 } else {
5129 final int itemCount = mItemCount;
5130 down = false;
5131 selectedPos = firstPosition + childCount - 1;
5132
5133 for (int i = childCount - 1; i >= 0; i--) {
5134 final View v = getChildAt(i);
5135 final int top = v.getTop();
5136 final int bottom = v.getBottom();
5137
5138 if (i == childCount - 1) {
5139 selectedTop = top;
5140 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5141 childrenBottom -= getVerticalFadingEdgeLength();
5142 }
5143 }
5144
5145 if (bottom <= childrenBottom) {
5146 selectedPos = firstPosition + i;
5147 selectedTop = top;
5148 break;
5149 }
5150 }
5151 }
5152 }
5153
5154 mResurrectToPosition = INVALID_POSITION;
5155 removeCallbacks(mFlingRunnable);
Adam Powell40322522011-01-12 21:58:20 -08005156 if (mPositionScroller != null) {
5157 mPositionScroller.stop();
5158 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005159 mTouchMode = TOUCH_MODE_REST;
5160 clearScrollingCache();
5161 mSpecificTop = selectedTop;
5162 selectedPos = lookForSelectablePosition(selectedPos, down);
5163 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5164 mLayoutMode = LAYOUT_SPECIFIC;
Justin Koh3c7b96a2011-05-31 18:51:51 -07005165 updateSelectorState();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005166 setSelectionInt(selectedPos);
5167 invokeOnItemScrollListener();
5168 } else {
5169 selectedPos = INVALID_POSITION;
5170 }
5171 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5172
5173 return selectedPos >= 0;
5174 }
5175
Adam Powell14c08042011-10-06 19:46:18 -07005176 void confirmCheckedPositionsById() {
5177 // Clear out the positional check states, we'll rebuild it below from IDs.
5178 mCheckStates.clear();
5179
5180 boolean checkedCountChanged = false;
5181 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5182 final long id = mCheckedIdStates.keyAt(checkedIndex);
5183 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5184
5185 final long lastPosId = mAdapter.getItemId(lastPos);
5186 if (id != lastPosId) {
5187 // Look around to see if the ID is nearby. If not, uncheck it.
5188 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5189 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5190 boolean found = false;
5191 for (int searchPos = start; searchPos < end; searchPos++) {
5192 final long searchId = mAdapter.getItemId(searchPos);
5193 if (id == searchId) {
5194 found = true;
5195 mCheckStates.put(searchPos, true);
5196 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5197 break;
5198 }
5199 }
5200
5201 if (!found) {
5202 mCheckedIdStates.delete(id);
5203 checkedIndex--;
5204 mCheckedItemCount--;
5205 checkedCountChanged = true;
5206 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5207 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5208 lastPos, id, false);
5209 }
5210 }
5211 } else {
5212 mCheckStates.put(lastPos, true);
5213 }
5214 }
5215
5216 if (checkedCountChanged && mChoiceActionMode != null) {
5217 mChoiceActionMode.invalidate();
5218 }
5219 }
5220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005221 @Override
5222 protected void handleDataChanged() {
5223 int count = mItemCount;
Adam Powellee78b172011-08-16 16:39:20 -07005224 int lastHandledItemCount = mLastHandledItemCount;
5225 mLastHandledItemCount = mItemCount;
Adam Powell14c08042011-10-06 19:46:18 -07005226
5227 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5228 confirmCheckedPositionsById();
5229 }
5230
Adam Powell539ee872012-02-03 19:00:49 -08005231 // TODO: In the future we can recycle these views based on stable ID instead.
5232 mRecycler.clearTransientStateViews();
5233
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005234 if (count > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005235 int newPos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005236 int selectablePos;
5237
5238 // Find the row we are supposed to sync to
5239 if (mNeedSync) {
5240 // Update this first, since setNextSelectedPositionInt inspects it
5241 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005242 mPendingSync = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005243
Adam Powell07852792010-11-10 16:57:05 -08005244 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005245 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5246 return;
Adam Powellda13dba2010-12-05 13:47:23 -08005247 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5248 if (mForceTranscriptScroll) {
5249 mForceTranscriptScroll = false;
5250 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5251 return;
5252 }
Adam Powell07852792010-11-10 16:57:05 -08005253 final int childCount = getChildCount();
Adam Powellee78b172011-08-16 16:39:20 -07005254 final int listBottom = getHeight() - getPaddingBottom();
Adam Powell07852792010-11-10 16:57:05 -08005255 final View lastChild = getChildAt(childCount - 1);
5256 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
Adam Powellee78b172011-08-16 16:39:20 -07005257 if (mFirstPosition + childCount >= lastHandledItemCount &&
5258 lastBottom <= listBottom) {
Adam Powell07852792010-11-10 16:57:05 -08005259 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5260 return;
5261 }
5262 // Something new came in and we didn't scroll; give the user a clue that
5263 // there's something new.
5264 awakenScrollBars();
5265 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005266
5267 switch (mSyncMode) {
5268 case SYNC_SELECTED_POSITION:
5269 if (isInTouchMode()) {
5270 // We saved our state when not in touch mode. (We know this because
5271 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5272 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5273 // adjusting if the available range changed) and return.
5274 mLayoutMode = LAYOUT_SYNC;
5275 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5276
5277 return;
5278 } else {
5279 // See if we can find a position in the new data with the same
5280 // id as the old selection. This will change mSyncPosition.
5281 newPos = findSyncPosition();
5282 if (newPos >= 0) {
5283 // Found it. Now verify that new selection is still selectable
5284 selectablePos = lookForSelectablePosition(newPos, true);
5285 if (selectablePos == newPos) {
5286 // Same row id is selected
5287 mSyncPosition = newPos;
5288
5289 if (mSyncHeight == getHeight()) {
5290 // If we are at the same height as when we saved state, try
5291 // to restore the scroll position too.
5292 mLayoutMode = LAYOUT_SYNC;
5293 } else {
5294 // We are not the same height as when the selection was saved, so
5295 // don't try to restore the exact position
5296 mLayoutMode = LAYOUT_SET_SELECTION;
5297 }
5298
5299 // Restore selection
5300 setNextSelectedPositionInt(newPos);
5301 return;
5302 }
5303 }
5304 }
5305 break;
5306 case SYNC_FIRST_POSITION:
5307 // Leave mSyncPosition as it is -- just pin to available range
5308 mLayoutMode = LAYOUT_SYNC;
5309 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5310
5311 return;
5312 }
5313 }
5314
5315 if (!isInTouchMode()) {
5316 // We couldn't find matching data -- try to use the same position
5317 newPos = getSelectedItemPosition();
5318
5319 // Pin position to the available range
5320 if (newPos >= count) {
5321 newPos = count - 1;
5322 }
5323 if (newPos < 0) {
5324 newPos = 0;
5325 }
5326
5327 // Make sure we select something selectable -- first look down
5328 selectablePos = lookForSelectablePosition(newPos, true);
5329
5330 if (selectablePos >= 0) {
5331 setNextSelectedPositionInt(selectablePos);
5332 return;
5333 } else {
5334 // Looking down didn't work -- try looking up
5335 selectablePos = lookForSelectablePosition(newPos, false);
5336 if (selectablePos >= 0) {
5337 setNextSelectedPositionInt(selectablePos);
5338 return;
5339 }
5340 }
5341 } else {
5342
5343 // We already know where we want to resurrect the selection
5344 if (mResurrectToPosition >= 0) {
5345 return;
5346 }
5347 }
5348
5349 }
5350
5351 // Nothing is selected. Give up and reset everything.
5352 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5353 mSelectedPosition = INVALID_POSITION;
5354 mSelectedRowId = INVALID_ROW_ID;
5355 mNextSelectedPosition = INVALID_POSITION;
5356 mNextSelectedRowId = INVALID_ROW_ID;
5357 mNeedSync = false;
Dianne Hackborne181bd92012-09-25 14:15:15 -07005358 mPendingSync = null;
Dianne Hackborn079e2352010-10-18 17:02:43 -07005359 mSelectorPosition = INVALID_POSITION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005360 checkSelectionChanged();
5361 }
5362
Romain Guy43c9cdf2010-01-27 13:53:55 -08005363 @Override
5364 protected void onDisplayHint(int hint) {
5365 super.onDisplayHint(hint);
5366 switch (hint) {
5367 case INVISIBLE:
5368 if (mPopup != null && mPopup.isShowing()) {
5369 dismissPopup();
5370 }
5371 break;
5372 case VISIBLE:
5373 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5374 showPopup();
5375 }
5376 break;
5377 }
Romain Guy24562482010-02-01 14:56:19 -08005378 mPopupHidden = hint == INVISIBLE;
Romain Guy43c9cdf2010-01-27 13:53:55 -08005379 }
5380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005381 /**
5382 * Removes the filter window
5383 */
Romain Guyd6a463a2009-05-21 23:10:10 -07005384 private void dismissPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005385 if (mPopup != null) {
5386 mPopup.dismiss();
5387 }
5388 }
5389
5390 /**
5391 * Shows the filter window
5392 */
5393 private void showPopup() {
5394 // Make sure we have a window before showing the popup
5395 if (getWindowVisibility() == View.VISIBLE) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08005396 createTextFilter(true);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005397 positionPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005398 // Make sure we get focus if we are showing the popup
5399 checkFocus();
5400 }
5401 }
5402
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005403 private void positionPopup() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005404 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5405 final int[] xy = new int[2];
5406 getLocationOnScreen(xy);
Romain Guy24443ea2009-05-11 11:56:30 -07005407 // TODO: The 20 below should come from the theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005408 // TODO: And the gravity should be defined in the theme as well
5409 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005410 if (!mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005411 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5412 xy[0], bottomGap);
5413 } else {
5414 mPopup.update(xy[0], bottomGap, -1, -1);
5415 }
5416 }
5417
5418 /**
5419 * What is the distance between the source and destination rectangles given the direction of
5420 * focus navigation between them? The direction basically helps figure out more quickly what is
5421 * self evident by the relationship between the rects...
5422 *
5423 * @param source the source rectangle
5424 * @param dest the destination rectangle
5425 * @param direction the direction
5426 * @return the distance between the rectangles
5427 */
5428 static int getDistance(Rect source, Rect dest, int direction) {
5429 int sX, sY; // source x, y
5430 int dX, dY; // dest x, y
5431 switch (direction) {
5432 case View.FOCUS_RIGHT:
5433 sX = source.right;
5434 sY = source.top + source.height() / 2;
5435 dX = dest.left;
5436 dY = dest.top + dest.height() / 2;
5437 break;
5438 case View.FOCUS_DOWN:
5439 sX = source.left + source.width() / 2;
5440 sY = source.bottom;
5441 dX = dest.left + dest.width() / 2;
5442 dY = dest.top;
5443 break;
5444 case View.FOCUS_LEFT:
5445 sX = source.left;
5446 sY = source.top + source.height() / 2;
5447 dX = dest.right;
5448 dY = dest.top + dest.height() / 2;
5449 break;
5450 case View.FOCUS_UP:
5451 sX = source.left + source.width() / 2;
5452 sY = source.top;
5453 dX = dest.left + dest.width() / 2;
5454 dY = dest.bottom;
5455 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005456 case View.FOCUS_FORWARD:
5457 case View.FOCUS_BACKWARD:
5458 sX = source.right + source.width() / 2;
5459 sY = source.top + source.height() / 2;
5460 dX = dest.left + dest.width() / 2;
5461 dY = dest.top + dest.height() / 2;
5462 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005463 default:
5464 throw new IllegalArgumentException("direction must be one of "
Jeff Brown4e6319b2010-12-13 10:36:51 -08005465 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5466 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005467 }
5468 int deltaX = dX - sX;
5469 int deltaY = dY - sY;
5470 return deltaY * deltaY + deltaX * deltaX;
5471 }
5472
5473 @Override
5474 protected boolean isInFilterMode() {
5475 return mFiltered;
5476 }
5477
5478 /**
5479 * Sends a key to the text filter window
5480 *
5481 * @param keyCode The keycode for the event
5482 * @param event The actual key event
5483 *
5484 * @return True if the text filter handled the event, false otherwise.
5485 */
5486 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005487 if (!acceptFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005488 return false;
5489 }
5490
5491 boolean handled = false;
5492 boolean okToSend = true;
5493 switch (keyCode) {
5494 case KeyEvent.KEYCODE_DPAD_UP:
5495 case KeyEvent.KEYCODE_DPAD_DOWN:
5496 case KeyEvent.KEYCODE_DPAD_LEFT:
5497 case KeyEvent.KEYCODE_DPAD_RIGHT:
5498 case KeyEvent.KEYCODE_DPAD_CENTER:
5499 case KeyEvent.KEYCODE_ENTER:
5500 okToSend = false;
5501 break;
5502 case KeyEvent.KEYCODE_BACK:
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005503 if (mFiltered && mPopup != null && mPopup.isShowing()) {
Dianne Hackborn8d374262009-09-14 21:21:52 -07005504 if (event.getAction() == KeyEvent.ACTION_DOWN
5505 && event.getRepeatCount() == 0) {
Jeff Brownb3ea9222011-01-10 16:26:36 -08005506 KeyEvent.DispatcherState state = getKeyDispatcherState();
5507 if (state != null) {
5508 state.startTracking(event, this);
5509 }
Dianne Hackborn83fe3f52009-09-12 23:38:30 -07005510 handled = true;
5511 } else if (event.getAction() == KeyEvent.ACTION_UP
5512 && event.isTracking() && !event.isCanceled()) {
5513 handled = true;
5514 mTextFilter.setText("");
5515 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005516 }
5517 okToSend = false;
5518 break;
5519 case KeyEvent.KEYCODE_SPACE:
5520 // Only send spaces once we are filtered
Romain Guycf6c5722010-01-04 14:34:08 -08005521 okToSend = mFiltered;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005522 break;
5523 }
5524
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005525 if (okToSend) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005526 createTextFilter(true);
5527
5528 KeyEvent forwardEvent = event;
5529 if (forwardEvent.getRepeatCount() > 0) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005530 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005531 }
5532
5533 int action = event.getAction();
5534 switch (action) {
5535 case KeyEvent.ACTION_DOWN:
5536 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5537 break;
5538
5539 case KeyEvent.ACTION_UP:
5540 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5541 break;
5542
5543 case KeyEvent.ACTION_MULTIPLE:
5544 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5545 break;
5546 }
5547 }
5548 return handled;
5549 }
5550
5551 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005552 * Return an InputConnection for editing of the filter text.
5553 */
5554 @Override
5555 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005556 if (isTextFilterEnabled()) {
5557 // XXX we need to have the text filter created, so we can get an
5558 // InputConnection to proxy to. Unfortunately this means we pretty
5559 // much need to make it as soon as a list view gets focus.
5560 createTextFilter(false);
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005561 if (mPublicInputConnection == null) {
5562 mDefInputConnection = new BaseInputConnection(this, false);
5563 mPublicInputConnection = new InputConnectionWrapper(
5564 mTextFilter.onCreateInputConnection(outAttrs), true) {
5565 @Override
5566 public boolean reportFullscreenMode(boolean enabled) {
5567 // Use our own input connection, since it is
5568 // the "real" one the IME is talking with.
5569 return mDefInputConnection.reportFullscreenMode(enabled);
5570 }
5571
5572 @Override
5573 public boolean performEditorAction(int editorAction) {
5574 // The editor is off in its own window; we need to be
5575 // the one that does this.
5576 if (editorAction == EditorInfo.IME_ACTION_DONE) {
5577 InputMethodManager imm = (InputMethodManager)
5578 getContext().getSystemService(
5579 Context.INPUT_METHOD_SERVICE);
5580 if (imm != null) {
5581 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5582 }
5583 return true;
5584 }
5585 return false;
5586 }
5587
5588 @Override
5589 public boolean sendKeyEvent(KeyEvent event) {
5590 // Use our own input connection, since the filter
5591 // text view may not be shown in a window so has
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07005592 // no ViewAncestor to dispatch events with.
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005593 return mDefInputConnection.sendKeyEvent(event);
5594 }
5595 };
5596 }
5597 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
5598 | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5599 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5600 return mPublicInputConnection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005601 }
5602 return null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005603 }
Romain Guy0a637162009-05-29 14:43:54 -07005604
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005605 /**
5606 * For filtering we proxy an input connection to an internal text editor,
5607 * and this allows the proxying to happen.
5608 */
5609 @Override
5610 public boolean checkInputConnectionProxy(View view) {
5611 return view == mTextFilter;
5612 }
Romain Guy0a637162009-05-29 14:43:54 -07005613
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005614 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005615 * Creates the window for the text filter and populates it with an EditText field;
5616 *
5617 * @param animateEntrance true if the window should appear with an animation
5618 */
5619 private void createTextFilter(boolean animateEntrance) {
5620 if (mPopup == null) {
5621 Context c = getContext();
5622 PopupWindow p = new PopupWindow(c);
The Android Open Source Project4df24232009-03-05 14:34:35 -08005623 LayoutInflater layoutInflater = (LayoutInflater)
5624 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005625 mTextFilter = (EditText) layoutInflater.inflate(
5626 com.android.internal.R.layout.typing_filter, null);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005627 // For some reason setting this as the "real" input type changes
5628 // the text view in some way that it doesn't work, and I don't
5629 // want to figure out why this is.
5630 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
5631 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
The Android Open Source Project10592532009-03-18 17:39:46 -07005632 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005633 mTextFilter.addTextChangedListener(this);
5634 p.setFocusable(false);
5635 p.setTouchable(false);
5636 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
5637 p.setContentView(mTextFilter);
5638 p.setWidth(LayoutParams.WRAP_CONTENT);
5639 p.setHeight(LayoutParams.WRAP_CONTENT);
5640 p.setBackgroundDrawable(null);
5641 mPopup = p;
5642 getViewTreeObserver().addOnGlobalLayoutListener(this);
Romain Guyd6a463a2009-05-21 23:10:10 -07005643 mGlobalLayoutListenerAddedFilter = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005644 }
5645 if (animateEntrance) {
5646 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
5647 } else {
5648 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
5649 }
5650 }
5651
5652 /**
5653 * Clear the text filter.
5654 */
5655 public void clearTextFilter() {
5656 if (mFiltered) {
5657 mTextFilter.setText("");
5658 mFiltered = false;
5659 if (mPopup != null && mPopup.isShowing()) {
5660 dismissPopup();
5661 }
5662 }
5663 }
5664
5665 /**
5666 * Returns if the ListView currently has a text filter.
5667 */
5668 public boolean hasTextFilter() {
5669 return mFiltered;
5670 }
5671
5672 public void onGlobalLayout() {
5673 if (isShown()) {
5674 // Show the popup if we are filtered
Romain Guy24562482010-02-01 14:56:19 -08005675 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005676 showPopup();
5677 }
5678 } else {
5679 // Hide the popup when we are no longer visible
Romain Guy43c9cdf2010-01-27 13:53:55 -08005680 if (mPopup != null && mPopup.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005681 dismissPopup();
5682 }
5683 }
5684
5685 }
5686
5687 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005688 * For our text watcher that is associated with the text filter. Does
5689 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005690 */
5691 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
5692 }
5693
5694 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005695 * For our text watcher that is associated with the text filter. Performs
5696 * the actual filtering as the text changes, and takes care of hiding and
5697 * showing the popup displaying the currently entered filter text.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005698 */
5699 public void onTextChanged(CharSequence s, int start, int before, int count) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07005700 if (mPopup != null && isTextFilterEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005701 int length = s.length();
5702 boolean showing = mPopup.isShowing();
5703 if (!showing && length > 0) {
5704 // Show the filter popup if necessary
5705 showPopup();
5706 mFiltered = true;
5707 } else if (showing && length == 0) {
5708 // Remove the filter popup if the user has cleared all text
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005709 dismissPopup();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005710 mFiltered = false;
5711 }
5712 if (mAdapter instanceof Filterable) {
5713 Filter f = ((Filterable) mAdapter).getFilter();
5714 // Filter should not be null when we reach this part
5715 if (f != null) {
5716 f.filter(s, this);
5717 } else {
5718 throw new IllegalStateException("You cannot call onTextChanged with a non "
5719 + "filterable adapter");
5720 }
5721 }
5722 }
5723 }
5724
5725 /**
Dianne Hackborn1bf5e222009-03-24 19:11:58 -07005726 * For our text watcher that is associated with the text filter. Does
5727 * nothing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005728 */
5729 public void afterTextChanged(Editable s) {
5730 }
5731
5732 public void onFilterComplete(int count) {
5733 if (mSelectedPosition < 0 && count > 0) {
5734 mResurrectToPosition = INVALID_POSITION;
5735 resurrectSelection();
5736 }
5737 }
5738
5739 @Override
Adam Powellaebd28f2012-02-22 10:31:16 -08005740 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
5741 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
5742 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
5743 }
5744
5745 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005746 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
5747 return new LayoutParams(p);
5748 }
5749
5750 @Override
5751 public LayoutParams generateLayoutParams(AttributeSet attrs) {
5752 return new AbsListView.LayoutParams(getContext(), attrs);
5753 }
5754
5755 @Override
5756 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
5757 return p instanceof AbsListView.LayoutParams;
5758 }
5759
5760 /**
5761 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
5762 * to the bottom to show new items.
5763 *
5764 * @param mode the transcript mode to set
5765 *
5766 * @see #TRANSCRIPT_MODE_DISABLED
5767 * @see #TRANSCRIPT_MODE_NORMAL
5768 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
5769 */
5770 public void setTranscriptMode(int mode) {
5771 mTranscriptMode = mode;
5772 }
5773
5774 /**
5775 * Returns the current transcript mode.
5776 *
5777 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
5778 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
5779 */
5780 public int getTranscriptMode() {
5781 return mTranscriptMode;
5782 }
5783
5784 @Override
5785 public int getSolidColor() {
5786 return mCacheColorHint;
5787 }
5788
5789 /**
5790 * 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 -07005791 * on top of a solid, single-color, opaque background.
5792 *
5793 * Zero means that what's behind this object is translucent (non solid) or is not made of a
5794 * single color. This hint will not affect any existing background drawable set on this view (
5795 * typically set via {@link #setBackgroundDrawable(Drawable)}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005796 *
5797 * @param color The background color
5798 */
5799 public void setCacheColorHint(int color) {
Romain Guy52e2ef82010-01-14 12:11:48 -08005800 if (color != mCacheColorHint) {
5801 mCacheColorHint = color;
5802 int count = getChildCount();
5803 for (int i = 0; i < count; i++) {
5804 getChildAt(i).setDrawingCacheBackgroundColor(color);
5805 }
5806 mRecycler.setCacheColorHint(color);
5807 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005808 }
5809
5810 /**
5811 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
5812 * on top of a solid, single-color, opaque background
5813 *
5814 * @return The cache color hint
5815 */
Romain Guy7b5b6ab2011-03-14 18:05:08 -07005816 @ViewDebug.ExportedProperty(category = "drawing")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005817 public int getCacheColorHint() {
5818 return mCacheColorHint;
5819 }
5820
5821 /**
5822 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
5823 * List. This includes views displayed on the screen as well as views stored in AbsListView's
5824 * internal view recycler.
5825 *
5826 * @param views A list into which to put the reclaimed views
5827 */
5828 public void reclaimViews(List<View> views) {
5829 int childCount = getChildCount();
5830 RecyclerListener listener = mRecycler.mRecyclerListener;
5831
5832 // Reclaim views on screen
5833 for (int i = 0; i < childCount; i++) {
5834 View child = getChildAt(i);
Romain Guy13922e02009-05-12 17:56:14 -07005835 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005836 // Don't reclaim header or footer views, or views that should be ignored
5837 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
5838 views.add(child);
alanvc1d7e772012-05-08 14:47:24 -07005839 child.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005840 if (listener != null) {
5841 // Pretend they went through the scrap heap
5842 listener.onMovedToScrapHeap(child);
5843 }
5844 }
5845 }
5846 mRecycler.reclaimScrapViews(views);
5847 removeAllViewsInLayout();
5848 }
5849
Adam Powell637d3372010-08-25 14:37:03 -07005850 private void finishGlows() {
5851 if (mEdgeGlowTop != null) {
5852 mEdgeGlowTop.finish();
5853 mEdgeGlowBottom.finish();
5854 }
5855 }
5856
Romain Guy13922e02009-05-12 17:56:14 -07005857 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07005858 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
5859 * through the specified intent.
5860 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
5861 */
5862 public void setRemoteViewsAdapter(Intent intent) {
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005863 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
5864 // service handling the specified intent.
Winson Chung3ec9a452010-09-23 16:40:28 -07005865 if (mRemoteAdapter != null) {
5866 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
5867 Intent.FilterComparison fcOld = new Intent.FilterComparison(
5868 mRemoteAdapter.getRemoteViewsServiceIntent());
5869 if (fcNew.equals(fcOld)) {
5870 return;
5871 }
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005872 }
Adam Cohen2148d432011-07-28 14:59:54 -07005873 mDeferNotifyDataSetChanged = false;
Winson Chung9b3a2cf2010-09-16 14:45:32 -07005874 // Otherwise, create a new RemoteViewsAdapter for binding
Winson Chung499cb9f2010-07-16 11:18:17 -07005875 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
Adam Cohen335c3b62012-07-24 17:18:16 -07005876 if (mRemoteAdapter.isDataReady()) {
5877 setAdapter(mRemoteAdapter);
5878 }
Winson Chung499cb9f2010-07-16 11:18:17 -07005879 }
5880
5881 /**
Adam Cohena6a4cbc2012-09-26 17:36:40 -07005882 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
5883 *
5884 * @param handler The OnClickHandler to use when inflating RemoteViews.
5885 *
5886 * @hide
5887 */
5888 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
5889 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
5890 // service handling the specified intent.
5891 if (mRemoteAdapter != null) {
5892 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
5893 }
5894 }
5895
5896 /**
Adam Cohen2148d432011-07-28 14:59:54 -07005897 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
5898 * connected yet.
5899 */
5900 public void deferNotifyDataSetChanged() {
5901 mDeferNotifyDataSetChanged = true;
5902 }
5903
5904 /**
Winson Chung499cb9f2010-07-16 11:18:17 -07005905 * Called back when the adapter connects to the RemoteViewsService.
5906 */
Winson Chung16c8d8a2011-01-20 16:19:33 -08005907 public boolean onRemoteAdapterConnected() {
Winson Chung499cb9f2010-07-16 11:18:17 -07005908 if (mRemoteAdapter != mAdapter) {
5909 setAdapter(mRemoteAdapter);
Adam Cohen2148d432011-07-28 14:59:54 -07005910 if (mDeferNotifyDataSetChanged) {
5911 mRemoteAdapter.notifyDataSetChanged();
5912 mDeferNotifyDataSetChanged = false;
5913 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08005914 return false;
Adam Cohenfb603862010-12-17 12:03:17 -08005915 } else if (mRemoteAdapter != null) {
5916 mRemoteAdapter.superNotifyDataSetChanged();
Winson Chung16c8d8a2011-01-20 16:19:33 -08005917 return true;
Winson Chung499cb9f2010-07-16 11:18:17 -07005918 }
Winson Chung16c8d8a2011-01-20 16:19:33 -08005919 return false;
Winson Chung499cb9f2010-07-16 11:18:17 -07005920 }
5921
5922 /**
5923 * Called back when the adapter disconnects from the RemoteViewsService.
5924 */
5925 public void onRemoteAdapterDisconnected() {
Adam Cohenfb603862010-12-17 12:03:17 -08005926 // If the remote adapter disconnects, we keep it around
5927 // since the currently displayed items are still cached.
5928 // Further, we want the service to eventually reconnect
5929 // when necessary, as triggered by this view requesting
5930 // items from the Adapter.
Winson Chung499cb9f2010-07-16 11:18:17 -07005931 }
5932
5933 /**
Adam Cohenb9673922012-01-05 13:58:47 -08005934 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
5935 * being displayed by the AbsListView.
5936 */
5937 void setVisibleRangeHint(int start, int end) {
5938 if (mRemoteAdapter != null) {
5939 mRemoteAdapter.setVisibleRangeHint(start, end);
5940 }
5941 }
5942
5943 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005944 * Sets the recycler listener to be notified whenever a View is set aside in
5945 * the recycler for later reuse. This listener can be used to free resources
5946 * associated to the View.
5947 *
5948 * @param listener The recycler listener to be notified of views set aside
5949 * in the recycler.
5950 *
5951 * @see android.widget.AbsListView.RecycleBin
5952 * @see android.widget.AbsListView.RecyclerListener
5953 */
5954 public void setRecyclerListener(RecyclerListener listener) {
5955 mRecycler.mRecyclerListener = listener;
5956 }
5957
Adam Powellb1f498a2011-01-18 20:43:23 -08005958 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
5959 @Override
5960 public void onChanged() {
5961 super.onChanged();
5962 if (mFastScroller != null) {
5963 mFastScroller.onSectionsChanged();
5964 }
5965 }
5966
5967 @Override
5968 public void onInvalidated() {
5969 super.onInvalidated();
5970 if (mFastScroller != null) {
5971 mFastScroller.onSectionsChanged();
5972 }
5973 }
5974 }
5975
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005976 /**
Adam Powellf343e1b2010-08-13 18:27:04 -07005977 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
5978 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
5979 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
5980 * selects and deselects list items.
5981 */
5982 public interface MultiChoiceModeListener extends ActionMode.Callback {
5983 /**
5984 * Called when an item is checked or unchecked during selection mode.
5985 *
5986 * @param mode The {@link ActionMode} providing the selection mode
5987 * @param position Adapter position of the item that was checked or unchecked
5988 * @param id Adapter ID of the item that was checked or unchecked
5989 * @param checked <code>true</code> if the item is now checked, <code>false</code>
5990 * if the item is now unchecked.
5991 */
5992 public void onItemCheckedStateChanged(ActionMode mode,
5993 int position, long id, boolean checked);
5994 }
5995
5996 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
5997 private MultiChoiceModeListener mWrapped;
5998
5999 public void setWrapped(MultiChoiceModeListener wrapped) {
6000 mWrapped = wrapped;
6001 }
6002
Adam Powella7981702012-08-24 12:43:41 -07006003 public boolean hasWrappedCallback() {
6004 return mWrapped != null;
6005 }
6006
Adam Powellf343e1b2010-08-13 18:27:04 -07006007 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6008 if (mWrapped.onCreateActionMode(mode, menu)) {
6009 // Initialize checked graphic state?
6010 setLongClickable(false);
6011 return true;
6012 }
6013 return false;
6014 }
6015
6016 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6017 return mWrapped.onPrepareActionMode(mode, menu);
6018 }
6019
6020 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6021 return mWrapped.onActionItemClicked(mode, item);
6022 }
6023
6024 public void onDestroyActionMode(ActionMode mode) {
6025 mWrapped.onDestroyActionMode(mode);
6026 mChoiceActionMode = null;
6027
6028 // Ending selection mode means deselecting everything.
6029 clearChoices();
6030
6031 mDataChanged = true;
6032 rememberSyncState();
6033 requestLayout();
6034
6035 setLongClickable(true);
6036 }
6037
6038 public void onItemCheckedStateChanged(ActionMode mode,
6039 int position, long id, boolean checked) {
6040 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6041
6042 // If there are no items selected we no longer need the selection mode.
6043 if (getCheckedItemCount() == 0) {
6044 mode.finish();
6045 }
6046 }
6047 }
6048
6049 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006050 * AbsListView extends LayoutParams to provide a place to hold the view type.
6051 */
6052 public static class LayoutParams extends ViewGroup.LayoutParams {
6053 /**
6054 * View type for this view, as returned by
6055 * {@link android.widget.Adapter#getItemViewType(int) }
6056 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006057 @ViewDebug.ExportedProperty(category = "list", mapping = {
Adam Powell9bf3c122010-02-26 11:32:07 -08006058 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6059 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6060 })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006061 int viewType;
6062
The Android Open Source Project4df24232009-03-05 14:34:35 -08006063 /**
6064 * When this boolean is set, the view has been added to the AbsListView
6065 * at least once. It is used to know whether headers/footers have already
6066 * been added to the list view and whether they should be treated as
6067 * recycled views or not.
6068 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006069 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project4df24232009-03-05 14:34:35 -08006070 boolean recycledHeaderFooter;
6071
Romain Guy0bf88592010-03-02 13:38:44 -08006072 /**
6073 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6074 * to obtain children views to measure itself. When doing so, the children
6075 * are not attached to the window, but put in the recycler which assumes
6076 * they've been attached before. Setting this flag will force the reused
6077 * view to be attached to the window rather than just attached to the
6078 * parent.
6079 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07006080 @ViewDebug.ExportedProperty(category = "list")
Romain Guy0bf88592010-03-02 13:38:44 -08006081 boolean forceAdd;
6082
Dianne Hackborn079e2352010-10-18 17:02:43 -07006083 /**
6084 * The position the view was removed from when pulled out of the
6085 * scrap heap.
6086 * @hide
6087 */
6088 int scrappedFromPosition;
6089
Adam Powell539ee872012-02-03 19:00:49 -08006090 /**
6091 * The ID the view represents
6092 */
6093 long itemId = -1;
6094
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006095 public LayoutParams(Context c, AttributeSet attrs) {
6096 super(c, attrs);
6097 }
6098
6099 public LayoutParams(int w, int h) {
6100 super(w, h);
6101 }
6102
6103 public LayoutParams(int w, int h, int viewType) {
6104 super(w, h);
6105 this.viewType = viewType;
6106 }
6107
6108 public LayoutParams(ViewGroup.LayoutParams source) {
6109 super(source);
6110 }
6111 }
6112
6113 /**
6114 * A RecyclerListener is used to receive a notification whenever a View is placed
6115 * inside the RecycleBin's scrap heap. This listener is used to free resources
6116 * associated to Views placed in the RecycleBin.
6117 *
6118 * @see android.widget.AbsListView.RecycleBin
6119 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6120 */
6121 public static interface RecyclerListener {
6122 /**
6123 * Indicates that the specified View was moved into the recycler's scrap heap.
6124 * The view is not displayed on screen any more and any expensive resource
6125 * associated with the view should be discarded.
6126 *
6127 * @param view
6128 */
6129 void onMovedToScrapHeap(View view);
6130 }
6131
6132 /**
6133 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6134 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6135 * start of a layout. By construction, they are displaying current information. At the end of
6136 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6137 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6138 *
6139 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6140 * @see android.widget.AbsListView.RecyclerListener
6141 */
6142 class RecycleBin {
6143 private RecyclerListener mRecyclerListener;
6144
6145 /**
6146 * The position of the first view stored in mActiveViews.
6147 */
6148 private int mFirstActivePosition;
6149
6150 /**
6151 * Views that were on screen at the start of layout. This array is populated at the start of
6152 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6153 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6154 * view store in mFirstActivePosition.
6155 */
6156 private View[] mActiveViews = new View[0];
6157
6158 /**
6159 * Unsorted views that can be used by the adapter as a convert view.
6160 */
6161 private ArrayList<View>[] mScrapViews;
6162
6163 private int mViewTypeCount;
6164
6165 private ArrayList<View> mCurrentScrap;
6166
Adam Powell539ee872012-02-03 19:00:49 -08006167 private ArrayList<View> mSkippedScrap;
6168
6169 private SparseArray<View> mTransientStateViews;
Chet Haase72871322013-02-26 16:12:13 -07006170 private LongSparseArray<View> mTransientStateViewsById;
Adam Powell539ee872012-02-03 19:00:49 -08006171
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006172 public void setViewTypeCount(int viewTypeCount) {
6173 if (viewTypeCount < 1) {
6174 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6175 }
6176 //noinspection unchecked
6177 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6178 for (int i = 0; i < viewTypeCount; i++) {
6179 scrapViews[i] = new ArrayList<View>();
6180 }
6181 mViewTypeCount = viewTypeCount;
6182 mCurrentScrap = scrapViews[0];
6183 mScrapViews = scrapViews;
6184 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08006185
Adam Powellf3c2eda2010-03-16 17:31:01 -07006186 public void markChildrenDirty() {
6187 if (mViewTypeCount == 1) {
6188 final ArrayList<View> scrap = mCurrentScrap;
6189 final int scrapCount = scrap.size();
6190 for (int i = 0; i < scrapCount; i++) {
6191 scrap.get(i).forceLayout();
6192 }
6193 } else {
6194 final int typeCount = mViewTypeCount;
6195 for (int i = 0; i < typeCount; i++) {
6196 final ArrayList<View> scrap = mScrapViews[i];
6197 final int scrapCount = scrap.size();
6198 for (int j = 0; j < scrapCount; j++) {
6199 scrap.get(j).forceLayout();
6200 }
6201 }
6202 }
Adam Powell539ee872012-02-03 19:00:49 -08006203 if (mTransientStateViews != null) {
6204 final int count = mTransientStateViews.size();
6205 for (int i = 0; i < count; i++) {
6206 mTransientStateViews.valueAt(i).forceLayout();
6207 }
6208 }
Chet Haase72871322013-02-26 16:12:13 -07006209 if (mTransientStateViewsById != null) {
6210 final int count = mTransientStateViewsById.size();
6211 for (int i = 0; i < count; i++) {
6212 mTransientStateViewsById.valueAt(i).forceLayout();
6213 }
6214 }
Adam Powellf3c2eda2010-03-16 17:31:01 -07006215 }
Romain Guy0a637162009-05-29 14:43:54 -07006216
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006217 public boolean shouldRecycleViewType(int viewType) {
6218 return viewType >= 0;
6219 }
6220
6221 /**
6222 * Clears the scrap heap.
6223 */
6224 void clear() {
6225 if (mViewTypeCount == 1) {
6226 final ArrayList<View> scrap = mCurrentScrap;
6227 final int scrapCount = scrap.size();
6228 for (int i = 0; i < scrapCount; i++) {
6229 removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
6230 }
6231 } else {
6232 final int typeCount = mViewTypeCount;
6233 for (int i = 0; i < typeCount; i++) {
6234 final ArrayList<View> scrap = mScrapViews[i];
6235 final int scrapCount = scrap.size();
6236 for (int j = 0; j < scrapCount; j++) {
6237 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
6238 }
6239 }
6240 }
Adam Powell539ee872012-02-03 19:00:49 -08006241 if (mTransientStateViews != null) {
6242 mTransientStateViews.clear();
6243 }
Chet Haase72871322013-02-26 16:12:13 -07006244 if (mTransientStateViewsById != null) {
6245 mTransientStateViewsById.clear();
6246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006247 }
6248
6249 /**
6250 * Fill ActiveViews with all of the children of the AbsListView.
6251 *
6252 * @param childCount The minimum number of views mActiveViews should hold
6253 * @param firstActivePosition The position of the first view that will be stored in
6254 * mActiveViews
6255 */
6256 void fillActiveViews(int childCount, int firstActivePosition) {
6257 if (mActiveViews.length < childCount) {
6258 mActiveViews = new View[childCount];
6259 }
6260 mFirstActivePosition = firstActivePosition;
6261
6262 final View[] activeViews = mActiveViews;
6263 for (int i = 0; i < childCount; i++) {
6264 View child = getChildAt(i);
Romain Guy9c3184cc2010-02-25 17:32:54 -08006265 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006266 // Don't put header or footer views into the scrap heap
Romain Guy9c3184cc2010-02-25 17:32:54 -08006267 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006268 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6269 // However, we will NOT place them into scrap views.
The Android Open Source Project4df24232009-03-05 14:34:35 -08006270 activeViews[i] = child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006271 }
6272 }
6273 }
6274
6275 /**
6276 * Get the view corresponding to the specified position. The view will be removed from
6277 * mActiveViews if it is found.
6278 *
6279 * @param position The position to look up in mActiveViews
6280 * @return The view if it is found, null otherwise
6281 */
6282 View getActiveView(int position) {
6283 int index = position - mFirstActivePosition;
6284 final View[] activeViews = mActiveViews;
6285 if (index >=0 && index < activeViews.length) {
6286 final View match = activeViews[index];
6287 activeViews[index] = null;
6288 return match;
6289 }
6290 return null;
6291 }
6292
Adam Powell539ee872012-02-03 19:00:49 -08006293 View getTransientStateView(int position) {
Chet Haase72871322013-02-26 16:12:13 -07006294 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6295 long id = mAdapter.getItemId(position);
6296 View result = mTransientStateViewsById.get(id);
6297 mTransientStateViewsById.remove(id);
6298 return result;
Adam Powell539ee872012-02-03 19:00:49 -08006299 }
Chet Haase72871322013-02-26 16:12:13 -07006300 if (mTransientStateViews != null) {
6301 final int index = mTransientStateViews.indexOfKey(position);
6302 if (index >= 0) {
6303 View result = mTransientStateViews.valueAt(index);
6304 mTransientStateViews.removeAt(index);
6305 return result;
6306 }
Adam Powell539ee872012-02-03 19:00:49 -08006307 }
Chet Haase72871322013-02-26 16:12:13 -07006308 return null;
Adam Powell539ee872012-02-03 19:00:49 -08006309 }
6310
6311 /**
6312 * Dump any currently saved views with transient state.
6313 */
6314 void clearTransientStateViews() {
6315 if (mTransientStateViews != null) {
6316 mTransientStateViews.clear();
6317 }
Chet Haase72871322013-02-26 16:12:13 -07006318 if (mTransientStateViewsById != null) {
6319 mTransientStateViewsById.clear();
6320 }
Adam Powell539ee872012-02-03 19:00:49 -08006321 }
6322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006323 /**
6324 * @return A view from the ScrapViews collection. These are unordered.
6325 */
6326 View getScrapView(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006327 if (mViewTypeCount == 1) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006328 return retrieveFromScrap(mCurrentScrap, position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006329 } else {
6330 int whichScrap = mAdapter.getItemViewType(position);
6331 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006332 return retrieveFromScrap(mScrapViews[whichScrap], position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006333 }
6334 }
6335 return null;
6336 }
6337
6338 /**
Adam Powell539ee872012-02-03 19:00:49 -08006339 * Put a view into the ScrapViews list. These views are unordered.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006340 *
6341 * @param scrap The view to add
6342 */
Dianne Hackborn079e2352010-10-18 17:02:43 -07006343 void addScrapView(View scrap, int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006344 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
6345 if (lp == null) {
6346 return;
6347 }
6348
Adam Powell539ee872012-02-03 19:00:49 -08006349 lp.scrappedFromPosition = position;
6350
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006351 // Don't put header or footer views or views that should be ignored
6352 // into the scrap heap
6353 int viewType = lp.viewType;
Adam Powell539ee872012-02-03 19:00:49 -08006354 final boolean scrapHasTransientState = scrap.hasTransientState();
6355 if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
6356 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
6357 if (mSkippedScrap == null) {
6358 mSkippedScrap = new ArrayList<View>();
6359 }
6360 mSkippedScrap.add(scrap);
6361 }
6362 if (scrapHasTransientState) {
Adam Powell057a5852012-05-11 10:28:38 -07006363 scrap.dispatchStartTemporaryDetach();
Chet Haase72871322013-02-26 16:12:13 -07006364 if (mAdapter != null && mAdapterHasStableIds) {
6365 if (mTransientStateViewsById == null) {
6366 mTransientStateViewsById = new LongSparseArray<View>();
6367 }
6368 mTransientStateViewsById.put(lp.itemId, scrap);
Chet Haasea2230e12013-03-18 14:52:37 -07006369 } else if (!mDataChanged) {
6370 // avoid putting views on transient state list during a data change;
6371 // the layout positions may be out of sync with the adapter positions
Chet Haase72871322013-02-26 16:12:13 -07006372 if (mTransientStateViews == null) {
6373 mTransientStateViews = new SparseArray<View>();
6374 }
6375 mTransientStateViews.put(position, scrap);
6376 }
Romain Guy9b1bb812010-02-26 14:14:13 -08006377 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006378 return;
6379 }
6380
Adam Powell539ee872012-02-03 19:00:49 -08006381 scrap.dispatchStartTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006382 if (mViewTypeCount == 1) {
6383 mCurrentScrap.add(scrap);
6384 } else {
6385 mScrapViews[viewType].add(scrap);
6386 }
6387
alanvc1d7e772012-05-08 14:47:24 -07006388 scrap.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006389 if (mRecyclerListener != null) {
6390 mRecyclerListener.onMovedToScrapHeap(scrap);
6391 }
6392 }
6393
6394 /**
Adam Powell539ee872012-02-03 19:00:49 -08006395 * Finish the removal of any views that skipped the scrap heap.
6396 */
6397 void removeSkippedScrap() {
6398 if (mSkippedScrap == null) {
6399 return;
6400 }
6401 final int count = mSkippedScrap.size();
6402 for (int i = 0; i < count; i++) {
6403 removeDetachedView(mSkippedScrap.get(i), false);
6404 }
6405 mSkippedScrap.clear();
6406 }
6407
6408 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006409 * Move all views remaining in mActiveViews to mScrapViews.
6410 */
6411 void scrapActiveViews() {
6412 final View[] activeViews = mActiveViews;
6413 final boolean hasListener = mRecyclerListener != null;
6414 final boolean multipleScraps = mViewTypeCount > 1;
6415
6416 ArrayList<View> scrapViews = mCurrentScrap;
6417 final int count = activeViews.length;
Romain Guya440b002010-02-24 15:57:54 -08006418 for (int i = count - 1; i >= 0; i--) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006419 final View victim = activeViews[i];
6420 if (victim != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07006421 final AbsListView.LayoutParams lp
6422 = (AbsListView.LayoutParams) victim.getLayoutParams();
6423 int whichScrap = lp.viewType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006424
6425 activeViews[i] = null;
6426
Adam Powell539ee872012-02-03 19:00:49 -08006427 final boolean scrapHasTransientState = victim.hasTransientState();
6428 if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006429 // Do not move views that should be ignored
Adam Powell539ee872012-02-03 19:00:49 -08006430 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
6431 scrapHasTransientState) {
Romain Guy9b1bb812010-02-26 14:14:13 -08006432 removeDetachedView(victim, false);
6433 }
Adam Powell539ee872012-02-03 19:00:49 -08006434 if (scrapHasTransientState) {
Chet Haase72871322013-02-26 16:12:13 -07006435 if (mAdapter != null && mAdapterHasStableIds) {
6436 if (mTransientStateViewsById == null) {
6437 mTransientStateViewsById = new LongSparseArray<View>();
6438 }
6439 long id = mAdapter.getItemId(mFirstActivePosition + i);
6440 mTransientStateViewsById.put(id, victim);
6441 } else {
6442 if (mTransientStateViews == null) {
6443 mTransientStateViews = new SparseArray<View>();
6444 }
6445 mTransientStateViews.put(mFirstActivePosition + i, victim);
Adam Powell539ee872012-02-03 19:00:49 -08006446 }
Adam Powell539ee872012-02-03 19:00:49 -08006447 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006448 continue;
6449 }
6450
6451 if (multipleScraps) {
6452 scrapViews = mScrapViews[whichScrap];
6453 }
Romain Guya440b002010-02-24 15:57:54 -08006454 victim.dispatchStartTemporaryDetach();
Dianne Hackborn079e2352010-10-18 17:02:43 -07006455 lp.scrappedFromPosition = mFirstActivePosition + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006456 scrapViews.add(victim);
6457
alanvc1d7e772012-05-08 14:47:24 -07006458 victim.setAccessibilityDelegate(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006459 if (hasListener) {
6460 mRecyclerListener.onMovedToScrapHeap(victim);
6461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006462 }
6463 }
6464
6465 pruneScrapViews();
6466 }
6467
6468 /**
6469 * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
6470 * (This can happen if an adapter does not recycle its views).
6471 */
6472 private void pruneScrapViews() {
6473 final int maxViews = mActiveViews.length;
6474 final int viewTypeCount = mViewTypeCount;
6475 final ArrayList<View>[] scrapViews = mScrapViews;
6476 for (int i = 0; i < viewTypeCount; ++i) {
6477 final ArrayList<View> scrapPile = scrapViews[i];
6478 int size = scrapPile.size();
6479 final int extras = size - maxViews;
6480 size--;
6481 for (int j = 0; j < extras; j++) {
6482 removeDetachedView(scrapPile.remove(size--), false);
6483 }
6484 }
Adam Powellbf1b81f2012-05-07 18:14:10 -07006485
6486 if (mTransientStateViews != null) {
6487 for (int i = 0; i < mTransientStateViews.size(); i++) {
6488 final View v = mTransientStateViews.valueAt(i);
6489 if (!v.hasTransientState()) {
6490 mTransientStateViews.removeAt(i);
6491 i--;
6492 }
6493 }
6494 }
Chet Haase72871322013-02-26 16:12:13 -07006495 if (mTransientStateViewsById != null) {
6496 for (int i = 0; i < mTransientStateViewsById.size(); i++) {
6497 final View v = mTransientStateViewsById.valueAt(i);
6498 if (!v.hasTransientState()) {
6499 mTransientStateViewsById.removeAt(i);
6500 i--;
6501 }
6502 }
6503 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006504 }
6505
6506 /**
6507 * Puts all views in the scrap heap into the supplied list.
6508 */
6509 void reclaimScrapViews(List<View> views) {
6510 if (mViewTypeCount == 1) {
6511 views.addAll(mCurrentScrap);
6512 } else {
6513 final int viewTypeCount = mViewTypeCount;
6514 final ArrayList<View>[] scrapViews = mScrapViews;
6515 for (int i = 0; i < viewTypeCount; ++i) {
6516 final ArrayList<View> scrapPile = scrapViews[i];
6517 views.addAll(scrapPile);
6518 }
6519 }
6520 }
Romain Guy52e2ef82010-01-14 12:11:48 -08006521
6522 /**
6523 * Updates the cache color hint of all known views.
6524 *
6525 * @param color The new cache color hint.
6526 */
6527 void setCacheColorHint(int color) {
6528 if (mViewTypeCount == 1) {
6529 final ArrayList<View> scrap = mCurrentScrap;
6530 final int scrapCount = scrap.size();
6531 for (int i = 0; i < scrapCount; i++) {
6532 scrap.get(i).setDrawingCacheBackgroundColor(color);
6533 }
6534 } else {
6535 final int typeCount = mViewTypeCount;
6536 for (int i = 0; i < typeCount; i++) {
6537 final ArrayList<View> scrap = mScrapViews[i];
6538 final int scrapCount = scrap.size();
6539 for (int j = 0; j < scrapCount; j++) {
Romain Guy266e0512010-07-14 11:08:02 -07006540 scrap.get(j).setDrawingCacheBackgroundColor(color);
Romain Guy52e2ef82010-01-14 12:11:48 -08006541 }
6542 }
6543 }
6544 // Just in case this is called during a layout pass
6545 final View[] activeViews = mActiveViews;
6546 final int count = activeViews.length;
6547 for (int i = 0; i < count; ++i) {
6548 final View victim = activeViews[i];
6549 if (victim != null) {
6550 victim.setDrawingCacheBackgroundColor(color);
6551 }
6552 }
6553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006554 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07006555
6556 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
6557 int size = scrapViews.size();
6558 if (size > 0) {
6559 // See if we still have a view for this position.
6560 for (int i=0; i<size; i++) {
6561 View view = scrapViews.get(i);
6562 if (((AbsListView.LayoutParams)view.getLayoutParams())
6563 .scrappedFromPosition == position) {
6564 scrapViews.remove(i);
6565 return view;
6566 }
6567 }
6568 return scrapViews.remove(size - 1);
6569 } else {
6570 return null;
6571 }
6572 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006573}