2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.widget;
19 import android.annotation.ColorInt;
20 import android.annotation.DrawableRes;
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.content.res.TypedArray;
26 import android.graphics.Canvas;
27 import android.graphics.Rect;
28 import android.graphics.drawable.Drawable;
29 import android.graphics.drawable.TransitionDrawable;
30 import android.os.Bundle;
31 import android.os.Debug;
32 import android.os.Handler;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.os.StrictMode;
36 import android.os.Trace;
37 import android.text.Editable;
38 import android.text.InputType;
39 import android.text.TextUtils;
40 import android.text.TextWatcher;
41 import android.util.AttributeSet;
42 import android.util.Log;
43 import android.util.LongSparseArray;
44 import android.util.SparseArray;
45 import android.util.SparseBooleanArray;
46 import android.util.StateSet;
47 import android.view.ActionMode;
48 import android.view.ContextMenu.ContextMenuInfo;
49 import android.view.Gravity;
50 import android.view.HapticFeedbackConstants;
51 import android.view.InputDevice;
52 import android.view.KeyEvent;
53 import android.view.LayoutInflater;
54 import android.view.Menu;
55 import android.view.MenuItem;
56 import android.view.MotionEvent;
57 import android.view.PointerIcon;
58 import android.view.VelocityTracker;
59 import android.view.View;
60 import android.view.ViewConfiguration;
61 import android.view.ViewDebug;
62 import android.view.ViewGroup;
63 import android.view.ViewHierarchyEncoder;
64 import android.view.ViewParent;
65 import android.view.ViewTreeObserver;
66 import android.view.accessibility.AccessibilityEvent;
67 import android.view.accessibility.AccessibilityManager;
68 import android.view.accessibility.AccessibilityNodeInfo;
69 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
70 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
71 import android.view.animation.Interpolator;
72 import android.view.animation.LinearInterpolator;
73 import android.view.inputmethod.BaseInputConnection;
74 import android.view.inputmethod.CompletionInfo;
75 import android.view.inputmethod.CorrectionInfo;
76 import android.view.inputmethod.EditorInfo;
77 import android.view.inputmethod.ExtractedText;
78 import android.view.inputmethod.ExtractedTextRequest;
79 import android.view.inputmethod.InputConnection;
80 import android.view.inputmethod.InputContentInfo;
81 import android.view.inputmethod.InputMethodManager;
82 import android.widget.RemoteViews.OnClickHandler;
84 import com.android.internal.R;
86 import java.util.ArrayList;
87 import java.util.List;
90 * Base class that can be used to implement virtualized lists of items. A list does
91 * not have a spatial definition here. For instance, subclases of this class can
92 * display the content of the list in a grid, in a carousel, as stack, etc.
94 * @attr ref android.R.styleable#AbsListView_listSelector
95 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
96 * @attr ref android.R.styleable#AbsListView_stackFromBottom
97 * @attr ref android.R.styleable#AbsListView_scrollingCache
98 * @attr ref android.R.styleable#AbsListView_textFilterEnabled
99 * @attr ref android.R.styleable#AbsListView_transcriptMode
100 * @attr ref android.R.styleable#AbsListView_cacheColorHint
101 * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
102 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
103 * @attr ref android.R.styleable#AbsListView_choiceMode
105 public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
106 ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
107 ViewTreeObserver.OnTouchModeChangeListener,
108 RemoteViewsAdapter.RemoteAdapterConnectionCallback {
110 @SuppressWarnings("UnusedDeclaration")
111 private static final String TAG = "AbsListView";
114 * Disables the transcript mode.
116 * @see #setTranscriptMode(int)
118 public static final int TRANSCRIPT_MODE_DISABLED = 0;
121 * The list will automatically scroll to the bottom when a data set change
122 * notification is received and only if the last item is already visible
125 * @see #setTranscriptMode(int)
127 public static final int TRANSCRIPT_MODE_NORMAL = 1;
130 * The list will automatically scroll to the bottom, no matter what items
131 * are currently visible.
133 * @see #setTranscriptMode(int)
135 public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
138 * Indicates that we are not in the middle of a touch gesture
140 static final int TOUCH_MODE_REST = -1;
143 * Indicates we just received the touch event and we are waiting to see if the it is a tap or a
146 static final int TOUCH_MODE_DOWN = 0;
149 * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch
152 static final int TOUCH_MODE_TAP = 1;
155 * Indicates we have waited for everything we can wait for, but the user's finger is still down
157 static final int TOUCH_MODE_DONE_WAITING = 2;
160 * Indicates the touch gesture is a scroll
162 static final int TOUCH_MODE_SCROLL = 3;
165 * Indicates the view is in the process of being flung
167 static final int TOUCH_MODE_FLING = 4;
170 * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
172 static final int TOUCH_MODE_OVERSCROLL = 5;
175 * Indicates the view is being flung outside of normal content bounds
176 * and will spring back.
178 static final int TOUCH_MODE_OVERFLING = 6;
181 * Regular layout - usually an unsolicited layout from the view system
183 static final int LAYOUT_NORMAL = 0;
186 * Show the first item
188 static final int LAYOUT_FORCE_TOP = 1;
191 * Force the selected item to be on somewhere on the screen
193 static final int LAYOUT_SET_SELECTION = 2;
198 static final int LAYOUT_FORCE_BOTTOM = 3;
201 * Make a mSelectedItem appear in a specific location and build the rest of
202 * the views from there. The top is specified by mSpecificTop.
204 static final int LAYOUT_SPECIFIC = 4;
207 * Layout to sync as a result of a data change. Restore mSyncPosition to have its top
210 static final int LAYOUT_SYNC = 5;
213 * Layout as a result of using the navigation keys
215 static final int LAYOUT_MOVE_SELECTION = 6;
218 * Normal list that does not indicate choices
220 public static final int CHOICE_MODE_NONE = 0;
223 * The list allows up to one choice
225 public static final int CHOICE_MODE_SINGLE = 1;
228 * The list allows multiple choices
230 public static final int CHOICE_MODE_MULTIPLE = 2;
233 * The list allows multiple choices in a modal selection mode
235 public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
238 * The thread that created this view.
240 private final Thread mOwnerThread;
243 * Controls if/how the user may choose/check items in the list
245 int mChoiceMode = CHOICE_MODE_NONE;
248 * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
250 ActionMode mChoiceActionMode;
253 * Wrapper for the multiple choice mode callback; AbsListView needs to perform
254 * a few extra actions around what application code does.
256 MultiChoiceModeWrapper mMultiChoiceModeCallback;
259 * Running count of how many items are currently checked
261 int mCheckedItemCount;
264 * Running state of which positions are currently checked
266 SparseBooleanArray mCheckStates;
269 * Running state of which IDs are currently checked.
270 * If there is a value for a given key, the checked state for that ID is true
271 * and the value holds the last known position in the adapter for that id.
273 LongSparseArray<Integer> mCheckedIdStates;
276 * Controls how the next layout will happen
278 int mLayoutMode = LAYOUT_NORMAL;
281 * Should be used by subclasses to listen to changes in the dataset
283 AdapterDataSetObserver mDataSetObserver;
286 * The adapter containing the data to be displayed by this view
288 ListAdapter mAdapter;
291 * The remote adapter containing the data to be displayed by this view to be set
293 private RemoteViewsAdapter mRemoteAdapter;
296 * If mAdapter != null, whenever this is true the adapter has stable IDs.
298 boolean mAdapterHasStableIds;
301 * This flag indicates the a full notify is required when the RemoteViewsAdapter connects
303 private boolean mDeferNotifyDataSetChanged = false;
306 * Indicates whether the list selector should be drawn on top of the children or behind
308 boolean mDrawSelectorOnTop = false;
311 * The drawable used to draw the selector
316 * The current position of the selector in the list.
318 int mSelectorPosition = INVALID_POSITION;
321 * Defines the selector's location and dimension at drawing time
323 Rect mSelectorRect = new Rect();
326 * The data set used to store unused views that should be reused during the next layout
327 * to avoid creating new ones
329 final RecycleBin mRecycler = new RecycleBin();
332 * The selection's left padding
334 int mSelectionLeftPadding = 0;
337 * The selection's top padding
339 int mSelectionTopPadding = 0;
342 * The selection's right padding
344 int mSelectionRightPadding = 0;
347 * The selection's bottom padding
349 int mSelectionBottomPadding = 0;
352 * This view's padding
354 Rect mListPadding = new Rect();
357 * Subclasses must retain their measure spec from onMeasure() into this member
359 int mWidthMeasureSpec = 0;
362 * The top scroll indicator
367 * The down scroll indicator
372 * When the view is scrolling, this flag is set to true to indicate subclasses that
373 * the drawing cache was enabled on the children
375 boolean mCachingStarted;
376 boolean mCachingActive;
379 * The position of the view that received the down motion event
384 * The offset to the top of the mMotionPosition view when the down motion event was received
386 int mMotionViewOriginalTop;
389 * The desired offset to the top of the mMotionPosition view after a scroll
391 int mMotionViewNewTop;
394 * The X value associated with the the down motion event
399 * The Y value associated with the the down motion event
404 * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or
405 * TOUCH_MODE_DONE_WAITING
407 int mTouchMode = TOUCH_MODE_REST;
410 * Y value from on the previous motion event (if any)
415 * How far the finger moved before we started scrolling
417 int mMotionCorrection;
420 * Determines speed during touch scrolling
422 private VelocityTracker mVelocityTracker;
425 * Handles one frame of a fling
427 private FlingRunnable mFlingRunnable;
430 * Handles scrolling between positions within the list.
432 AbsPositionScroller mPositionScroller;
435 * The offset in pixels form the top of the AdapterView to the top
436 * of the currently selected view. Used to save and restore state.
438 int mSelectedTop = 0;
441 * Indicates whether the list is stacked from the bottom edge or
444 boolean mStackFromBottom;
447 * When set to true, the list automatically discards the children's
448 * bitmap cache after scrolling.
450 boolean mScrollingCacheEnabled;
453 * Whether or not to enable the fast scroll feature on this list
455 boolean mFastScrollEnabled;
458 * Whether or not to always show the fast scroll feature on this list
460 boolean mFastScrollAlwaysVisible;
463 * Optional callback to notify client when scroll position has changed
465 private OnScrollListener mOnScrollListener;
468 * Keeps track of our accessory window
473 * Used with type filter window
475 EditText mTextFilter;
478 * Indicates whether to use pixels-based or position-based scrollbar
481 private boolean mSmoothScrollbarEnabled = true;
484 * Indicates that this view supports filtering
486 private boolean mTextFilterEnabled;
489 * Indicates that this view is currently displaying a filtered view of the data
491 private boolean mFiltered;
494 * Rectangle used for hit testing children
496 private Rect mTouchFrame;
499 * The position to resurrect the selected position to.
501 int mResurrectToPosition = INVALID_POSITION;
503 private ContextMenuInfo mContextMenuInfo = null;
506 * Maximum distance to record overscroll
511 * Content height divided by this is the overscroll limit.
513 static final int OVERSCROLL_LIMIT_DIVISOR = 3;
516 * How many positions in either direction we will search to try to
517 * find a checked item with a stable ID that moved position across
518 * a data set change. If the item isn't found it will be unselected.
520 private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
523 * Used to request a layout when we changed touch mode
525 private static final int TOUCH_MODE_UNKNOWN = -1;
526 private static final int TOUCH_MODE_ON = 0;
527 private static final int TOUCH_MODE_OFF = 1;
529 private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
531 private static final boolean PROFILE_SCROLLING = false;
532 private boolean mScrollProfilingStarted = false;
534 private static final boolean PROFILE_FLINGING = false;
535 private boolean mFlingProfilingStarted = false;
538 * The StrictMode "critical time span" objects to catch animation
539 * stutters. Non-null when a time-sensitive animation is
540 * in-flight. Must call finish() on them when done animating.
541 * These are no-ops on user builds.
543 private StrictMode.Span mScrollStrictSpan = null;
544 private StrictMode.Span mFlingStrictSpan = null;
547 * The last CheckForLongPress runnable we posted, if any
549 private CheckForLongPress mPendingCheckForLongPress;
552 * The last CheckForTap runnable we posted, if any
554 private CheckForTap mPendingCheckForTap;
557 * The last CheckForKeyLongPress runnable we posted, if any
559 private CheckForKeyLongPress mPendingCheckForKeyLongPress;
564 private AbsListView.PerformClick mPerformClick;
567 * Delayed action for touch mode.
569 private Runnable mTouchModeReset;
572 * Whether the most recent touch event stream resulted in a successful
573 * long-press action. This is reset on TOUCH_DOWN.
575 private boolean mHasPerformedLongPress;
578 * This view is in transcript mode -- it shows the bottom of the list when the data
581 private int mTranscriptMode;
584 * Indicates that this list is always drawn on top of a solid, single-color, opaque
587 private int mCacheColorHint;
590 * The select child's view (from the adapter's getView) is enabled.
592 private boolean mIsChildViewEnabled;
595 * The cached drawable state for the selector. Accounts for child enabled
596 * state, but otherwise identical to the view's own drawable state.
598 private int[] mSelectorState;
601 * The last scroll state reported to clients through {@link OnScrollListener}.
603 private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
606 * Helper object that renders and controls the fast scroll thumb.
608 private FastScroller mFastScroll;
611 * Temporary holder for fast scroller style until a FastScroller object
614 private int mFastScrollStyle;
616 private boolean mGlobalLayoutListenerAddedFilter;
618 private int mTouchSlop;
619 private float mDensityScale;
621 private float mVerticalScrollFactor;
623 private InputConnection mDefInputConnection;
624 private InputConnectionWrapper mPublicInputConnection;
626 private Runnable mClearScrollingCache;
627 Runnable mPositionScrollAfterLayout;
628 private int mMinimumVelocity;
629 private int mMaximumVelocity;
630 private float mVelocityScale = 1.0f;
632 final boolean[] mIsScrap = new boolean[1];
634 private final int[] mScrollOffset = new int[2];
635 private final int[] mScrollConsumed = new int[2];
637 private final float[] mTmpPoint = new float[2];
639 // Used for offsetting MotionEvents that we feed to the VelocityTracker.
640 // In the future it would be nice to be able to give this to the VelocityTracker
641 // directly, or alternatively put a VT into absolute-positioning mode that only
642 // reads the raw screen-coordinate x/y values.
643 private int mNestedYOffset = 0;
645 // True when the popup should be hidden because of a call to
646 // dispatchDisplayHint()
647 private boolean mPopupHidden;
650 * ID of the active pointer. This is used to retain consistency during
651 * drags/flings if multiple pointers are used.
653 private int mActivePointerId = INVALID_POINTER;
656 * Sentinel value for no current active pointer.
657 * Used by {@link #mActivePointerId}.
659 private static final int INVALID_POINTER = -1;
662 * Maximum distance to overscroll by during edge effects
664 int mOverscrollDistance;
667 * Maximum distance to overfling during edge effects
669 int mOverflingDistance;
671 // These two EdgeGlows are always set and used together.
672 // Checking one for null is as good as checking both.
675 * Tracks the state of the top edge glow.
677 private EdgeEffect mEdgeGlowTop;
680 * Tracks the state of the bottom edge glow.
682 private EdgeEffect mEdgeGlowBottom;
685 * An estimate of how many pixels are between the top of the list and
686 * the top of the first position in the adapter, based on the last time
687 * we saw it. Used to hint where to draw edge glows.
689 private int mFirstPositionDistanceGuess;
692 * An estimate of how many pixels are between the bottom of the list and
693 * the bottom of the last position in the adapter, based on the last time
694 * we saw it. Used to hint where to draw edge glows.
696 private int mLastPositionDistanceGuess;
699 * Used for determining when to cancel out of overscroll.
701 private int mDirection = 0;
704 * Tracked on measurement in transcript mode. Makes sure that we can still pin to
705 * the bottom correctly on resizes.
707 private boolean mForceTranscriptScroll;
710 * Used for interacting with list items from an accessibility service.
712 private ListItemAccessibilityDelegate mAccessibilityDelegate;
714 private int mLastAccessibilityScrollEventFromIndex;
715 private int mLastAccessibilityScrollEventToIndex;
718 * Track the item count from the last time we handled a data change.
720 private int mLastHandledItemCount;
723 * Used for smooth scrolling at a consistent rate
725 static final Interpolator sLinearInterpolator = new LinearInterpolator();
728 * The saved state that we will be restoring from when we next sync.
729 * Kept here so that if we happen to be asked to save our state before
730 * the sync happens, we can return this existing data rather than losing
733 private SavedState mPendingSync;
736 * Whether the view is in the process of detaching from its window.
738 private boolean mIsDetaching;
741 * Interface definition for a callback to be invoked when the list or grid
744 public interface OnScrollListener {
747 * The view is not scrolling. Note navigating the list using the trackball counts as
748 * being in the idle state since these transitions are not animated.
750 public static int SCROLL_STATE_IDLE = 0;
753 * The user is scrolling using touch, and their finger is still on the screen
755 public static int SCROLL_STATE_TOUCH_SCROLL = 1;
758 * The user had previously been scrolling using touch and had performed a fling. The
759 * animation is now coasting to a stop
761 public static int SCROLL_STATE_FLING = 2;
764 * Callback method to be invoked while the list view or grid view is being scrolled. If the
765 * view is being scrolled, this method will be called before the next frame of the scroll is
766 * rendered. In particular, it will be called before any calls to
767 * {@link Adapter#getView(int, View, ViewGroup)}.
769 * @param view The view whose scroll state is being reported
771 * @param scrollState The current scroll state. One of
772 * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
774 public void onScrollStateChanged(AbsListView view, int scrollState);
777 * Callback method to be invoked when the list or grid has been scrolled. This will be
778 * called after the scroll has completed
779 * @param view The view whose scroll state is being reported
780 * @param firstVisibleItem the index of the first visible cell (ignore if
781 * visibleItemCount == 0)
782 * @param visibleItemCount the number of visible cells
783 * @param totalItemCount the number of items in the list adaptor
785 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
790 * The top-level view of a list item can implement this interface to allow
791 * itself to modify the bounds of the selection shown for that item.
793 public interface SelectionBoundsAdjuster {
795 * Called to allow the list item to adjust the bounds shown for
798 * @param bounds On call, this contains the bounds the list has
799 * selected for the item (that is the bounds of the entire view). The
800 * values can be modified as desired.
802 public void adjustListItemSelectionBounds(Rect bounds);
805 public AbsListView(Context context) {
809 mOwnerThread = Thread.currentThread();
811 setVerticalScrollBarEnabled(true);
812 TypedArray a = context.obtainStyledAttributes(R.styleable.View);
813 initializeScrollbarsInternal(a);
817 public AbsListView(Context context, AttributeSet attrs) {
818 this(context, attrs, com.android.internal.R.attr.absListViewStyle);
821 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
822 this(context, attrs, defStyleAttr, 0);
825 public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
826 super(context, attrs, defStyleAttr, defStyleRes);
829 mOwnerThread = Thread.currentThread();
831 final TypedArray a = context.obtainStyledAttributes(
832 attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes);
834 final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector);
835 if (selector != null) {
836 setSelector(selector);
839 mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false);
841 setStackFromBottom(a.getBoolean(
842 R.styleable.AbsListView_stackFromBottom, false));
843 setScrollingCacheEnabled(a.getBoolean(
844 R.styleable.AbsListView_scrollingCache, true));
845 setTextFilterEnabled(a.getBoolean(
846 R.styleable.AbsListView_textFilterEnabled, false));
847 setTranscriptMode(a.getInt(
848 R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED));
849 setCacheColorHint(a.getColor(
850 R.styleable.AbsListView_cacheColorHint, 0));
851 setSmoothScrollbarEnabled(a.getBoolean(
852 R.styleable.AbsListView_smoothScrollbar, true));
853 setChoiceMode(a.getInt(
854 R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE));
856 setFastScrollEnabled(a.getBoolean(
857 R.styleable.AbsListView_fastScrollEnabled, false));
858 setFastScrollStyle(a.getResourceId(
859 R.styleable.AbsListView_fastScrollStyle, 0));
860 setFastScrollAlwaysVisible(a.getBoolean(
861 R.styleable.AbsListView_fastScrollAlwaysVisible, false));
865 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
866 setRevealOnFocusHint(false);
870 private void initAbsListView() {
871 // Setting focusable in touch mode will set the focusable property to true
873 setFocusableInTouchMode(true);
874 setWillNotDraw(false);
875 setAlwaysDrawnWithCacheEnabled(false);
876 setScrollingCacheEnabled(true);
878 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
879 mTouchSlop = configuration.getScaledTouchSlop();
880 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
881 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
882 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
883 mOverscrollDistance = configuration.getScaledOverscrollDistance();
884 mOverflingDistance = configuration.getScaledOverflingDistance();
886 mDensityScale = getContext().getResources().getDisplayMetrics().density;
890 public void setOverScrollMode(int mode) {
891 if (mode != OVER_SCROLL_NEVER) {
892 if (mEdgeGlowTop == null) {
893 Context context = getContext();
894 mEdgeGlowTop = new EdgeEffect(context);
895 mEdgeGlowBottom = new EdgeEffect(context);
899 mEdgeGlowBottom = null;
901 super.setOverScrollMode(mode);
908 public void setAdapter(ListAdapter adapter) {
909 if (adapter != null) {
910 mAdapterHasStableIds = mAdapter.hasStableIds();
911 if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds &&
912 mCheckedIdStates == null) {
913 mCheckedIdStates = new LongSparseArray<Integer>();
920 * Returns the number of items currently selected. This will only be valid
921 * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
923 * <p>To determine the specific items that are currently selected, use one of
924 * the <code>getChecked*</code> methods.
926 * @return The number of items currently selected
928 * @see #getCheckedItemPosition()
929 * @see #getCheckedItemPositions()
930 * @see #getCheckedItemIds()
932 public int getCheckedItemCount() {
933 return mCheckedItemCount;
937 * Returns the checked state of the specified position. The result is only
938 * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
939 * or {@link #CHOICE_MODE_MULTIPLE}.
941 * @param position The item whose checked state to return
942 * @return The item's checked state or <code>false</code> if choice mode
945 * @see #setChoiceMode(int)
947 public boolean isItemChecked(int position) {
948 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
949 return mCheckStates.get(position);
956 * Returns the currently checked item. The result is only valid if the choice
957 * mode has been set to {@link #CHOICE_MODE_SINGLE}.
959 * @return The position of the currently checked item or
960 * {@link #INVALID_POSITION} if nothing is selected
962 * @see #setChoiceMode(int)
964 public int getCheckedItemPosition() {
965 if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
966 return mCheckStates.keyAt(0);
969 return INVALID_POSITION;
973 * Returns the set of checked items in the list. The result is only valid if
974 * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
976 * @return A SparseBooleanArray which will return true for each call to
977 * get(int position) where position is a checked position in the
978 * list and false otherwise, or <code>null</code> if the choice
979 * mode is set to {@link #CHOICE_MODE_NONE}.
981 public SparseBooleanArray getCheckedItemPositions() {
982 if (mChoiceMode != CHOICE_MODE_NONE) {
989 * Returns the set of checked items ids. The result is only valid if the
990 * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
991 * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
993 * @return A new array which contains the id of each checked item in the
996 public long[] getCheckedItemIds() {
997 if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
1001 final LongSparseArray<Integer> idStates = mCheckedIdStates;
1002 final int count = idStates.size();
1003 final long[] ids = new long[count];
1005 for (int i = 0; i < count; i++) {
1006 ids[i] = idStates.keyAt(i);
1013 * Clear any choices previously set
1015 public void clearChoices() {
1016 if (mCheckStates != null) {
1017 mCheckStates.clear();
1019 if (mCheckedIdStates != null) {
1020 mCheckedIdStates.clear();
1022 mCheckedItemCount = 0;
1026 * Sets the checked state of the specified position. The is only valid if
1027 * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
1028 * {@link #CHOICE_MODE_MULTIPLE}.
1030 * @param position The item whose checked state is to be checked
1031 * @param value The new checked state for the item
1033 public void setItemChecked(int position, boolean value) {
1034 if (mChoiceMode == CHOICE_MODE_NONE) {
1038 // Start selection mode if needed. We don't need to if we're unchecking something.
1039 if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
1040 if (mMultiChoiceModeCallback == null ||
1041 !mMultiChoiceModeCallback.hasWrappedCallback()) {
1042 throw new IllegalStateException("AbsListView: attempted to start selection mode " +
1043 "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
1044 "supplied. Call setMultiChoiceModeListener to set a callback.");
1046 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1049 final boolean itemCheckChanged;
1050 if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1051 boolean oldValue = mCheckStates.get(position);
1052 mCheckStates.put(position, value);
1053 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1055 mCheckedIdStates.put(mAdapter.getItemId(position), position);
1057 mCheckedIdStates.delete(mAdapter.getItemId(position));
1060 itemCheckChanged = oldValue != value;
1061 if (itemCheckChanged) {
1063 mCheckedItemCount++;
1065 mCheckedItemCount--;
1068 if (mChoiceActionMode != null) {
1069 final long id = mAdapter.getItemId(position);
1070 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1071 position, id, value);
1074 boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
1075 // Clear all values if we're checking something, or unchecking the currently
1077 itemCheckChanged = isItemChecked(position) != value;
1078 if (value || isItemChecked(position)) {
1079 mCheckStates.clear();
1081 mCheckedIdStates.clear();
1084 // this may end up selecting the value we just cleared but this way
1085 // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
1087 mCheckStates.put(position, true);
1089 mCheckedIdStates.put(mAdapter.getItemId(position), position);
1091 mCheckedItemCount = 1;
1092 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1093 mCheckedItemCount = 0;
1097 // Do not generate a data change while we are in the layout phase or data has not changed
1098 if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) {
1099 mDataChanged = true;
1100 rememberSyncState();
1106 public boolean performItemClick(View view, int position, long id) {
1107 boolean handled = false;
1108 boolean dispatchItemClick = true;
1110 if (mChoiceMode != CHOICE_MODE_NONE) {
1112 boolean checkedStateChanged = false;
1114 if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
1115 (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
1116 boolean checked = !mCheckStates.get(position, false);
1117 mCheckStates.put(position, checked);
1118 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1120 mCheckedIdStates.put(mAdapter.getItemId(position), position);
1122 mCheckedIdStates.delete(mAdapter.getItemId(position));
1126 mCheckedItemCount++;
1128 mCheckedItemCount--;
1130 if (mChoiceActionMode != null) {
1131 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
1132 position, id, checked);
1133 dispatchItemClick = false;
1135 checkedStateChanged = true;
1136 } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
1137 boolean checked = !mCheckStates.get(position, false);
1139 mCheckStates.clear();
1140 mCheckStates.put(position, true);
1141 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
1142 mCheckedIdStates.clear();
1143 mCheckedIdStates.put(mAdapter.getItemId(position), position);
1145 mCheckedItemCount = 1;
1146 } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
1147 mCheckedItemCount = 0;
1149 checkedStateChanged = true;
1152 if (checkedStateChanged) {
1153 updateOnScreenCheckedViews();
1157 if (dispatchItemClick) {
1158 handled |= super.performItemClick(view, position, id);
1165 * Perform a quick, in-place update of the checked or activated state
1166 * on all visible item views. This should only be called when a valid
1167 * choice mode is active.
1169 private void updateOnScreenCheckedViews() {
1170 final int firstPos = mFirstPosition;
1171 final int count = getChildCount();
1172 final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion
1173 >= android.os.Build.VERSION_CODES.HONEYCOMB;
1174 for (int i = 0; i < count; i++) {
1175 final View child = getChildAt(i);
1176 final int position = firstPos + i;
1178 if (child instanceof Checkable) {
1179 ((Checkable) child).setChecked(mCheckStates.get(position));
1180 } else if (useActivated) {
1181 child.setActivated(mCheckStates.get(position));
1187 * @see #setChoiceMode(int)
1189 * @return The current choice mode
1191 public int getChoiceMode() {
1196 * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
1197 * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
1198 * List allows up to one item to be in a chosen state. By setting the choiceMode to
1199 * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen.
1201 * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or
1202 * {@link #CHOICE_MODE_MULTIPLE}
1204 public void setChoiceMode(int choiceMode) {
1205 mChoiceMode = choiceMode;
1206 if (mChoiceActionMode != null) {
1207 mChoiceActionMode.finish();
1208 mChoiceActionMode = null;
1210 if (mChoiceMode != CHOICE_MODE_NONE) {
1211 if (mCheckStates == null) {
1212 mCheckStates = new SparseBooleanArray(0);
1214 if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
1215 mCheckedIdStates = new LongSparseArray<Integer>(0);
1217 // Modal multi-choice mode only has choices when the mode is active. Clear them.
1218 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
1220 setLongClickable(true);
1226 * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
1227 * selection {@link ActionMode}. Only used when the choice mode is set to
1228 * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
1230 * @param listener Listener that will manage the selection mode
1232 * @see #setChoiceMode(int)
1234 public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1235 if (mMultiChoiceModeCallback == null) {
1236 mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
1238 mMultiChoiceModeCallback.setWrapped(listener);
1242 * @return true if all list content currently fits within the view boundaries
1244 private boolean contentFits() {
1245 final int childCount = getChildCount();
1246 if (childCount == 0) return true;
1247 if (childCount != mItemCount) return false;
1249 return getChildAt(0).getTop() >= mListPadding.top &&
1250 getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom;
1254 * Specifies whether fast scrolling is enabled or disabled.
1256 * When fast scrolling is enabled, the user can quickly scroll through lists
1257 * by dragging the fast scroll thumb.
1259 * If the adapter backing this list implements {@link SectionIndexer}, the
1260 * fast scroller will display section header previews as the user scrolls.
1261 * Additionally, the user will be able to quickly jump between sections by
1262 * tapping along the length of the scroll bar.
1264 * @see SectionIndexer
1265 * @see #isFastScrollEnabled()
1266 * @param enabled true to enable fast scrolling, false otherwise
1268 public void setFastScrollEnabled(final boolean enabled) {
1269 if (mFastScrollEnabled != enabled) {
1270 mFastScrollEnabled = enabled;
1272 if (isOwnerThread()) {
1273 setFastScrollerEnabledUiThread(enabled);
1275 post(new Runnable() {
1278 setFastScrollerEnabledUiThread(enabled);
1285 private void setFastScrollerEnabledUiThread(boolean enabled) {
1286 if (mFastScroll != null) {
1287 mFastScroll.setEnabled(enabled);
1288 } else if (enabled) {
1289 mFastScroll = new FastScroller(this, mFastScrollStyle);
1290 mFastScroll.setEnabled(true);
1295 if (mFastScroll != null) {
1296 mFastScroll.updateLayout();
1301 * Specifies the style of the fast scroller decorations.
1303 * @param styleResId style resource containing fast scroller properties
1304 * @see android.R.styleable#FastScroll
1306 public void setFastScrollStyle(int styleResId) {
1307 if (mFastScroll == null) {
1308 mFastScrollStyle = styleResId;
1310 mFastScroll.setStyle(styleResId);
1315 * Set whether or not the fast scroller should always be shown in place of
1316 * the standard scroll bars. This will enable fast scrolling if it is not
1319 * Fast scrollers shown in this way will not fade out and will be a
1320 * permanent fixture within the list. This is best combined with an inset
1321 * scroll bar style to ensure the scroll bar does not overlap content.
1323 * @param alwaysShow true if the fast scroller should always be displayed,
1325 * @see #setScrollBarStyle(int)
1326 * @see #setFastScrollEnabled(boolean)
1328 public void setFastScrollAlwaysVisible(final boolean alwaysShow) {
1329 if (mFastScrollAlwaysVisible != alwaysShow) {
1330 if (alwaysShow && !mFastScrollEnabled) {
1331 setFastScrollEnabled(true);
1334 mFastScrollAlwaysVisible = alwaysShow;
1336 if (isOwnerThread()) {
1337 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1339 post(new Runnable() {
1342 setFastScrollerAlwaysVisibleUiThread(alwaysShow);
1349 private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
1350 if (mFastScroll != null) {
1351 mFastScroll.setAlwaysShow(alwaysShow);
1356 * @return whether the current thread is the one that created the view
1358 private boolean isOwnerThread() {
1359 return mOwnerThread == Thread.currentThread();
1363 * Returns true if the fast scroller is set to always show on this view.
1365 * @return true if the fast scroller will always show
1366 * @see #setFastScrollAlwaysVisible(boolean)
1368 public boolean isFastScrollAlwaysVisible() {
1369 if (mFastScroll == null) {
1370 return mFastScrollEnabled && mFastScrollAlwaysVisible;
1372 return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
1377 public int getVerticalScrollbarWidth() {
1378 if (mFastScroll != null && mFastScroll.isEnabled()) {
1379 return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
1381 return super.getVerticalScrollbarWidth();
1385 * Returns true if the fast scroller is enabled.
1387 * @see #setFastScrollEnabled(boolean)
1388 * @return true if fast scroll is enabled, false otherwise
1390 @ViewDebug.ExportedProperty
1391 public boolean isFastScrollEnabled() {
1392 if (mFastScroll == null) {
1393 return mFastScrollEnabled;
1395 return mFastScroll.isEnabled();
1400 public void setVerticalScrollbarPosition(int position) {
1401 super.setVerticalScrollbarPosition(position);
1402 if (mFastScroll != null) {
1403 mFastScroll.setScrollbarPosition(position);
1408 public void setScrollBarStyle(int style) {
1409 super.setScrollBarStyle(style);
1410 if (mFastScroll != null) {
1411 mFastScroll.setScrollBarStyle(style);
1416 * If fast scroll is enabled, then don't draw the vertical scrollbar.
1420 protected boolean isVerticalScrollBarHidden() {
1421 return isFastScrollEnabled();
1425 * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
1426 * is computed based on the number of visible pixels in the visible items. This
1427 * however assumes that all list items have the same height. If you use a list in
1428 * which items have different heights, the scrollbar will change appearance as the
1429 * user scrolls through the list. To avoid this issue, you need to disable this
1432 * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
1433 * is based solely on the number of items in the adapter and the position of the
1434 * visible items inside the adapter. This provides a stable scrollbar as the user
1435 * navigates through a list of items with varying heights.
1437 * @param enabled Whether or not to enable smooth scrollbar.
1439 * @see #setSmoothScrollbarEnabled(boolean)
1440 * @attr ref android.R.styleable#AbsListView_smoothScrollbar
1442 public void setSmoothScrollbarEnabled(boolean enabled) {
1443 mSmoothScrollbarEnabled = enabled;
1447 * Returns the current state of the fast scroll feature.
1449 * @return True if smooth scrollbar is enabled is enabled, false otherwise.
1451 * @see #setSmoothScrollbarEnabled(boolean)
1453 @ViewDebug.ExportedProperty
1454 public boolean isSmoothScrollbarEnabled() {
1455 return mSmoothScrollbarEnabled;
1459 * Set the listener that will receive notifications every time the list scrolls.
1461 * @param l the scroll listener
1463 public void setOnScrollListener(OnScrollListener l) {
1464 mOnScrollListener = l;
1465 invokeOnItemScrollListener();
1469 * Notify our scroll listener (if there is one) of a change in scroll state
1471 void invokeOnItemScrollListener() {
1472 if (mFastScroll != null) {
1473 mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
1475 if (mOnScrollListener != null) {
1476 mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
1478 onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
1483 public void sendAccessibilityEventInternal(int eventType) {
1484 // Since this class calls onScrollChanged even if the mFirstPosition and the
1485 // child count have not changed we will avoid sending duplicate accessibility
1487 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
1488 final int firstVisiblePosition = getFirstVisiblePosition();
1489 final int lastVisiblePosition = getLastVisiblePosition();
1490 if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
1491 && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
1494 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
1495 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
1498 super.sendAccessibilityEventInternal(eventType);
1502 public CharSequence getAccessibilityClassName() {
1503 return AbsListView.class.getName();
1508 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1509 super.onInitializeAccessibilityNodeInfoInternal(info);
1511 if (canScrollUp()) {
1512 info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
1513 info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
1514 info.setScrollable(true);
1516 if (canScrollDown()) {
1517 info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
1518 info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN);
1519 info.setScrollable(true);
1523 info.removeAction(AccessibilityAction.ACTION_CLICK);
1524 info.setClickable(false);
1527 int getSelectionModeForAccessibility() {
1528 final int choiceMode = getChoiceMode();
1529 switch (choiceMode) {
1530 case CHOICE_MODE_NONE:
1531 return CollectionInfo.SELECTION_MODE_NONE;
1532 case CHOICE_MODE_SINGLE:
1533 return CollectionInfo.SELECTION_MODE_SINGLE;
1534 case CHOICE_MODE_MULTIPLE:
1535 case CHOICE_MODE_MULTIPLE_MODAL:
1536 return CollectionInfo.SELECTION_MODE_MULTIPLE;
1538 return CollectionInfo.SELECTION_MODE_NONE;
1544 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1545 if (super.performAccessibilityActionInternal(action, arguments)) {
1549 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1550 case R.id.accessibilityActionScrollDown: {
1551 if (isEnabled() && canScrollDown()) {
1552 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1553 smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1557 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1558 case R.id.accessibilityActionScrollUp: {
1559 if (isEnabled() && canScrollUp()) {
1560 final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1561 smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1571 public View findViewByAccessibilityIdTraversal(int accessibilityId) {
1572 if (accessibilityId == getAccessibilityViewId()) {
1575 return super.findViewByAccessibilityIdTraversal(accessibilityId);
1579 * Indicates whether the children's drawing cache is used during a scroll.
1580 * By default, the drawing cache is enabled but this will consume more memory.
1582 * @return true if the scrolling cache is enabled, false otherwise
1584 * @see #setScrollingCacheEnabled(boolean)
1585 * @see View#setDrawingCacheEnabled(boolean)
1587 @ViewDebug.ExportedProperty
1588 public boolean isScrollingCacheEnabled() {
1589 return mScrollingCacheEnabled;
1593 * Enables or disables the children's drawing cache during a scroll.
1594 * By default, the drawing cache is enabled but this will use more memory.
1596 * When the scrolling cache is enabled, the caches are kept after the
1597 * first scrolling. You can manually clear the cache by calling
1598 * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
1600 * @param enabled true to enable the scroll cache, false otherwise
1602 * @see #isScrollingCacheEnabled()
1603 * @see View#setDrawingCacheEnabled(boolean)
1605 public void setScrollingCacheEnabled(boolean enabled) {
1606 if (mScrollingCacheEnabled && !enabled) {
1607 clearScrollingCache();
1609 mScrollingCacheEnabled = enabled;
1613 * Enables or disables the type filter window. If enabled, typing when
1614 * this view has focus will filter the children to match the users input.
1615 * Note that the {@link Adapter} used by this view must implement the
1616 * {@link Filterable} interface.
1618 * @param textFilterEnabled true to enable type filtering, false otherwise
1622 public void setTextFilterEnabled(boolean textFilterEnabled) {
1623 mTextFilterEnabled = textFilterEnabled;
1627 * Indicates whether type filtering is enabled for this view
1629 * @return true if type filtering is enabled, false otherwise
1631 * @see #setTextFilterEnabled(boolean)
1634 @ViewDebug.ExportedProperty
1635 public boolean isTextFilterEnabled() {
1636 return mTextFilterEnabled;
1640 public void getFocusedRect(Rect r) {
1641 View view = getSelectedView();
1642 if (view != null && view.getParent() == this) {
1643 // the focused rectangle of the selected view offset into the
1644 // coordinate space of this view.
1645 view.getFocusedRect(r);
1646 offsetDescendantRectToMyCoords(view, r);
1648 // otherwise, just the norm
1649 super.getFocusedRect(r);
1653 private void useDefaultSelector() {
1654 setSelector(getContext().getDrawable(
1655 com.android.internal.R.drawable.list_selector_background));
1659 * Indicates whether the content of this view is pinned to, or stacked from,
1662 * @return true if the content is stacked from the bottom edge, false otherwise
1664 @ViewDebug.ExportedProperty
1665 public boolean isStackFromBottom() {
1666 return mStackFromBottom;
1670 * When stack from bottom is set to true, the list fills its content starting from
1671 * the bottom of the view.
1673 * @param stackFromBottom true to pin the view's content to the bottom edge,
1674 * false to pin the view's content to the top edge
1676 public void setStackFromBottom(boolean stackFromBottom) {
1677 if (mStackFromBottom != stackFromBottom) {
1678 mStackFromBottom = stackFromBottom;
1679 requestLayoutIfNecessary();
1683 void requestLayoutIfNecessary() {
1684 if (getChildCount() > 0) {
1691 static class SavedState extends BaseSavedState {
1698 boolean inActionMode;
1699 int checkedItemCount;
1700 SparseBooleanArray checkState;
1701 LongSparseArray<Integer> checkIdState;
1704 * Constructor called from {@link AbsListView#onSaveInstanceState()}
1706 SavedState(Parcelable superState) {
1711 * Constructor called from {@link #CREATOR}
1713 private SavedState(Parcel in) {
1715 selectedId = in.readLong();
1716 firstId = in.readLong();
1717 viewTop = in.readInt();
1718 position = in.readInt();
1719 height = in.readInt();
1720 filter = in.readString();
1721 inActionMode = in.readByte() != 0;
1722 checkedItemCount = in.readInt();
1723 checkState = in.readSparseBooleanArray();
1724 final int N = in.readInt();
1726 checkIdState = new LongSparseArray<Integer>();
1727 for (int i=0; i<N; i++) {
1728 final long key = in.readLong();
1729 final int value = in.readInt();
1730 checkIdState.put(key, value);
1736 public void writeToParcel(Parcel out, int flags) {
1737 super.writeToParcel(out, flags);
1738 out.writeLong(selectedId);
1739 out.writeLong(firstId);
1740 out.writeInt(viewTop);
1741 out.writeInt(position);
1742 out.writeInt(height);
1743 out.writeString(filter);
1744 out.writeByte((byte) (inActionMode ? 1 : 0));
1745 out.writeInt(checkedItemCount);
1746 out.writeSparseBooleanArray(checkState);
1747 final int N = checkIdState != null ? checkIdState.size() : 0;
1749 for (int i=0; i<N; i++) {
1750 out.writeLong(checkIdState.keyAt(i));
1751 out.writeInt(checkIdState.valueAt(i));
1756 public String toString() {
1757 return "AbsListView.SavedState{"
1758 + Integer.toHexString(System.identityHashCode(this))
1759 + " selectedId=" + selectedId
1760 + " firstId=" + firstId
1761 + " viewTop=" + viewTop
1762 + " position=" + position
1763 + " height=" + height
1764 + " filter=" + filter
1765 + " checkState=" + checkState + "}";
1768 public static final Parcelable.Creator<SavedState> CREATOR
1769 = new Parcelable.Creator<SavedState>() {
1771 public SavedState createFromParcel(Parcel in) {
1772 return new SavedState(in);
1776 public SavedState[] newArray(int size) {
1777 return new SavedState[size];
1783 public Parcelable onSaveInstanceState() {
1785 * This doesn't really make sense as the place to dismiss the
1786 * popups, but there don't seem to be any other useful hooks
1787 * that happen early enough to keep from getting complaints
1788 * about having leaked the window.
1792 Parcelable superState = super.onSaveInstanceState();
1794 SavedState ss = new SavedState(superState);
1796 if (mPendingSync != null) {
1797 // Just keep what we last restored.
1798 ss.selectedId = mPendingSync.selectedId;
1799 ss.firstId = mPendingSync.firstId;
1800 ss.viewTop = mPendingSync.viewTop;
1801 ss.position = mPendingSync.position;
1802 ss.height = mPendingSync.height;
1803 ss.filter = mPendingSync.filter;
1804 ss.inActionMode = mPendingSync.inActionMode;
1805 ss.checkedItemCount = mPendingSync.checkedItemCount;
1806 ss.checkState = mPendingSync.checkState;
1807 ss.checkIdState = mPendingSync.checkIdState;
1811 boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
1812 long selectedId = getSelectedItemId();
1813 ss.selectedId = selectedId;
1814 ss.height = getHeight();
1816 if (selectedId >= 0) {
1817 // Remember the selection
1818 ss.viewTop = mSelectedTop;
1819 ss.position = getSelectedItemPosition();
1820 ss.firstId = INVALID_POSITION;
1822 if (haveChildren && mFirstPosition > 0) {
1823 // Remember the position of the first child.
1824 // We only do this if we are not currently at the top of
1825 // the list, for two reasons:
1826 // (1) The list may be in the process of becoming empty, in
1827 // which case mItemCount may not be 0, but if we try to
1828 // ask for any information about position 0 we will crash.
1829 // (2) Being "at the top" seems like a special case, anyway,
1830 // and the user wouldn't expect to end up somewhere else when
1831 // they revisit the list even if its content has changed.
1832 View v = getChildAt(0);
1833 ss.viewTop = v.getTop();
1834 int firstPos = mFirstPosition;
1835 if (firstPos >= mItemCount) {
1836 firstPos = mItemCount - 1;
1838 ss.position = firstPos;
1839 ss.firstId = mAdapter.getItemId(firstPos);
1842 ss.firstId = INVALID_POSITION;
1849 final EditText textFilter = mTextFilter;
1850 if (textFilter != null) {
1851 Editable filterText = textFilter.getText();
1852 if (filterText != null) {
1853 ss.filter = filterText.toString();
1858 ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null;
1860 if (mCheckStates != null) {
1861 ss.checkState = mCheckStates.clone();
1863 if (mCheckedIdStates != null) {
1864 final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
1865 final int count = mCheckedIdStates.size();
1866 for (int i = 0; i < count; i++) {
1867 idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
1869 ss.checkIdState = idState;
1871 ss.checkedItemCount = mCheckedItemCount;
1873 if (mRemoteAdapter != null) {
1874 mRemoteAdapter.saveRemoteViewsCache();
1881 public void onRestoreInstanceState(Parcelable state) {
1882 SavedState ss = (SavedState) state;
1884 super.onRestoreInstanceState(ss.getSuperState());
1885 mDataChanged = true;
1887 mSyncHeight = ss.height;
1889 if (ss.selectedId >= 0) {
1892 mSyncRowId = ss.selectedId;
1893 mSyncPosition = ss.position;
1894 mSpecificTop = ss.viewTop;
1895 mSyncMode = SYNC_SELECTED_POSITION;
1896 } else if (ss.firstId >= 0) {
1897 setSelectedPositionInt(INVALID_POSITION);
1898 // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
1899 setNextSelectedPositionInt(INVALID_POSITION);
1900 mSelectorPosition = INVALID_POSITION;
1903 mSyncRowId = ss.firstId;
1904 mSyncPosition = ss.position;
1905 mSpecificTop = ss.viewTop;
1906 mSyncMode = SYNC_FIRST_POSITION;
1909 setFilterText(ss.filter);
1911 if (ss.checkState != null) {
1912 mCheckStates = ss.checkState;
1915 if (ss.checkIdState != null) {
1916 mCheckedIdStates = ss.checkIdState;
1919 mCheckedItemCount = ss.checkedItemCount;
1921 if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL &&
1922 mMultiChoiceModeCallback != null) {
1923 mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
1929 private boolean acceptFilter() {
1930 return mTextFilterEnabled && getAdapter() instanceof Filterable &&
1931 ((Filterable) getAdapter()).getFilter() != null;
1935 * Sets the initial value for the text filter.
1936 * @param filterText The text to use for the filter.
1938 * @see #setTextFilterEnabled
1940 public void setFilterText(String filterText) {
1941 // TODO: Should we check for acceptFilter()?
1942 if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
1943 createTextFilter(false);
1944 // This is going to call our listener onTextChanged, but we might not
1945 // be ready to bring up a window yet
1946 mTextFilter.setText(filterText);
1947 mTextFilter.setSelection(filterText.length());
1948 if (mAdapter instanceof Filterable) {
1949 // if mPopup is non-null, then onTextChanged will do the filtering
1950 if (mPopup == null) {
1951 Filter f = ((Filterable) mAdapter).getFilter();
1952 f.filter(filterText);
1954 // Set filtered to true so we will display the filter window when our main
1957 mDataSetObserver.clearSavedState();
1963 * Returns the list's text filter, if available.
1964 * @return the list's text filter or null if filtering isn't enabled
1966 public CharSequence getTextFilter() {
1967 if (mTextFilterEnabled && mTextFilter != null) {
1968 return mTextFilter.getText();
1974 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
1975 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1976 if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
1977 if (!isAttachedToWindow() && mAdapter != null) {
1978 // Data may have changed while we were detached and it's valid
1979 // to change focus while detached. Refresh so we don't die.
1980 mDataChanged = true;
1981 mOldItemCount = mItemCount;
1982 mItemCount = mAdapter.getCount();
1984 resurrectSelection();
1989 public void requestLayout() {
1990 if (!mBlockLayoutRequests && !mInLayout) {
1991 super.requestLayout();
1996 * The list is empty. Clear everything out.
1999 removeAllViewsInLayout();
2001 mDataChanged = false;
2002 mPositionScrollAfterLayout = null;
2004 mPendingSync = null;
2005 mOldSelectedPosition = INVALID_POSITION;
2006 mOldSelectedRowId = INVALID_ROW_ID;
2007 setSelectedPositionInt(INVALID_POSITION);
2008 setNextSelectedPositionInt(INVALID_POSITION);
2010 mSelectorPosition = INVALID_POSITION;
2011 mSelectorRect.setEmpty();
2016 protected int computeVerticalScrollExtent() {
2017 final int count = getChildCount();
2019 if (mSmoothScrollbarEnabled) {
2020 int extent = count * 100;
2022 View view = getChildAt(0);
2023 final int top = view.getTop();
2024 int height = view.getHeight();
2026 extent += (top * 100) / height;
2029 view = getChildAt(count - 1);
2030 final int bottom = view.getBottom();
2031 height = view.getHeight();
2033 extent -= ((bottom - getHeight()) * 100) / height;
2045 protected int computeVerticalScrollOffset() {
2046 final int firstPosition = mFirstPosition;
2047 final int childCount = getChildCount();
2048 if (firstPosition >= 0 && childCount > 0) {
2049 if (mSmoothScrollbarEnabled) {
2050 final View view = getChildAt(0);
2051 final int top = view.getTop();
2052 int height = view.getHeight();
2054 return Math.max(firstPosition * 100 - (top * 100) / height +
2055 (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
2059 final int count = mItemCount;
2060 if (firstPosition == 0) {
2062 } else if (firstPosition + childCount == count) {
2065 index = firstPosition + childCount / 2;
2067 return (int) (firstPosition + childCount * (index / (float) count));
2074 protected int computeVerticalScrollRange() {
2076 if (mSmoothScrollbarEnabled) {
2077 result = Math.max(mItemCount * 100, 0);
2078 if (mScrollY != 0) {
2079 // Compensate for overscroll
2080 result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
2083 result = mItemCount;
2089 protected float getTopFadingEdgeStrength() {
2090 final int count = getChildCount();
2091 final float fadeEdge = super.getTopFadingEdgeStrength();
2095 if (mFirstPosition > 0) {
2099 final int top = getChildAt(0).getTop();
2100 final float fadeLength = getVerticalFadingEdgeLength();
2101 return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge;
2106 protected float getBottomFadingEdgeStrength() {
2107 final int count = getChildCount();
2108 final float fadeEdge = super.getBottomFadingEdgeStrength();
2112 if (mFirstPosition + count - 1 < mItemCount - 1) {
2116 final int bottom = getChildAt(count - 1).getBottom();
2117 final int height = getHeight();
2118 final float fadeLength = getVerticalFadingEdgeLength();
2119 return bottom > height - mPaddingBottom ?
2120 (bottom - height + mPaddingBottom) / fadeLength : fadeEdge;
2125 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2126 if (mSelector == null) {
2127 useDefaultSelector();
2129 final Rect listPadding = mListPadding;
2130 listPadding.left = mSelectionLeftPadding + mPaddingLeft;
2131 listPadding.top = mSelectionTopPadding + mPaddingTop;
2132 listPadding.right = mSelectionRightPadding + mPaddingRight;
2133 listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
2135 // Check if our previous measured size was at a point where we should scroll later.
2136 if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
2137 final int childCount = getChildCount();
2138 final int listBottom = getHeight() - getPaddingBottom();
2139 final View lastChild = getChildAt(childCount - 1);
2140 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
2141 mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount &&
2142 lastBottom <= listBottom;
2147 * Subclasses should NOT override this method but
2148 * {@link #layoutChildren()} instead.
2151 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2152 super.onLayout(changed, l, t, r, b);
2156 final int childCount = getChildCount();
2158 for (int i = 0; i < childCount; i++) {
2159 getChildAt(i).forceLayout();
2161 mRecycler.markChildrenDirty();
2166 mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
2168 // TODO: Move somewhere sane. This doesn't belong in onLayout().
2169 if (mFastScroll != null) {
2170 mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
2179 protected boolean setFrame(int left, int top, int right, int bottom) {
2180 final boolean changed = super.setFrame(left, top, right, bottom);
2183 // Reposition the popup when the frame has changed. This includes
2184 // translating the widget, not just changing its dimension. The
2185 // filter popup needs to follow the widget.
2186 final boolean visible = getWindowVisibility() == View.VISIBLE;
2187 if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
2196 * Subclasses must override this method to layout their children.
2198 protected void layoutChildren() {
2202 * @param focusedView view that holds accessibility focus
2203 * @return direct child that contains accessibility focus, or null if no
2204 * child contains accessibility focus
2206 View getAccessibilityFocusedChild(View focusedView) {
2207 ViewParent viewParent = focusedView.getParent();
2208 while ((viewParent instanceof View) && (viewParent != this)) {
2209 focusedView = (View) viewParent;
2210 viewParent = viewParent.getParent();
2213 if (!(viewParent instanceof View)) {
2220 void updateScrollIndicators() {
2221 if (mScrollUp != null) {
2222 mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE);
2225 if (mScrollDown != null) {
2226 mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE);
2230 private boolean canScrollUp() {
2231 boolean canScrollUp;
2232 // 0th element is not visible
2233 canScrollUp = mFirstPosition > 0;
2235 // ... Or top of 0th element is not visible
2237 if (getChildCount() > 0) {
2238 View child = getChildAt(0);
2239 canScrollUp = child.getTop() < mListPadding.top;
2246 private boolean canScrollDown() {
2247 boolean canScrollDown;
2248 int count = getChildCount();
2250 // Last item is not visible
2251 canScrollDown = (mFirstPosition + count) < mItemCount;
2253 // ... Or bottom of the last element is not visible
2254 if (!canScrollDown && count > 0) {
2255 View child = getChildAt(count - 1);
2256 canScrollDown = child.getBottom() > mBottom - mListPadding.bottom;
2259 return canScrollDown;
2263 @ViewDebug.ExportedProperty
2264 public View getSelectedView() {
2265 if (mItemCount > 0 && mSelectedPosition >= 0) {
2266 return getChildAt(mSelectedPosition - mFirstPosition);
2273 * List padding is the maximum of the normal view's padding and the padding of the selector.
2275 * @see android.view.View#getPaddingTop()
2276 * @see #getSelector()
2278 * @return The top list padding.
2280 public int getListPaddingTop() {
2281 return mListPadding.top;
2285 * List padding is the maximum of the normal view's padding and the padding of the selector.
2287 * @see android.view.View#getPaddingBottom()
2288 * @see #getSelector()
2290 * @return The bottom list padding.
2292 public int getListPaddingBottom() {
2293 return mListPadding.bottom;
2297 * List padding is the maximum of the normal view's padding and the padding of the selector.
2299 * @see android.view.View#getPaddingLeft()
2300 * @see #getSelector()
2302 * @return The left list padding.
2304 public int getListPaddingLeft() {
2305 return mListPadding.left;
2309 * List padding is the maximum of the normal view's padding and the padding of the selector.
2311 * @see android.view.View#getPaddingRight()
2312 * @see #getSelector()
2314 * @return The right list padding.
2316 public int getListPaddingRight() {
2317 return mListPadding.right;
2321 * Gets a view and have it show the data associated with the specified
2322 * position. This is called when we have already discovered that the view
2323 * is not available for reuse in the recycle bin. The only choices left are
2324 * converting an old view or making a new one.
2326 * @param position the position to display
2327 * @param outMetadata an array of at least 1 boolean where the first entry
2328 * will be set {@code true} if the view is currently
2329 * attached to the window, {@code false} otherwise (e.g.
2330 * newly-inflated or remained scrap for multiple layout
2333 * @return A view displaying the data associated with the specified position
2335 View obtainView(int position, boolean[] outMetadata) {
2336 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
2338 outMetadata[0] = false;
2340 // Check whether we have a transient state view. Attempt to re-bind the
2341 // data and discard the view if we fail.
2342 final View transientView = mRecycler.getTransientStateView(position);
2343 if (transientView != null) {
2344 final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
2346 // If the view type hasn't changed, attempt to re-bind the data.
2347 if (params.viewType == mAdapter.getItemViewType(position)) {
2348 final View updatedView = mAdapter.getView(position, transientView, this);
2350 // If we failed to re-bind the data, scrap the obtained view.
2351 if (updatedView != transientView) {
2352 setItemViewLayoutParams(updatedView, position);
2353 mRecycler.addScrapView(updatedView, position);
2357 outMetadata[0] = true;
2359 // Finish the temporary detach started in addScrapView().
2360 transientView.dispatchFinishTemporaryDetach();
2361 return transientView;
2364 final View scrapView = mRecycler.getScrapView(position);
2365 final View child = mAdapter.getView(position, scrapView, this);
2366 if (scrapView != null) {
2367 if (child != scrapView) {
2368 // Failed to re-bind the data, return scrap to the heap.
2369 mRecycler.addScrapView(scrapView, position);
2370 } else if (child.isTemporarilyDetached()) {
2371 outMetadata[0] = true;
2373 // Finish the temporary detach started in addScrapView().
2374 child.dispatchFinishTemporaryDetach();
2378 if (mCacheColorHint != 0) {
2379 child.setDrawingCacheBackgroundColor(mCacheColorHint);
2382 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
2383 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
2386 setItemViewLayoutParams(child, position);
2388 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2389 if (mAccessibilityDelegate == null) {
2390 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
2392 if (child.getAccessibilityDelegate() == null) {
2393 child.setAccessibilityDelegate(mAccessibilityDelegate);
2397 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
2402 private void setItemViewLayoutParams(View child, int position) {
2403 final ViewGroup.LayoutParams vlp = child.getLayoutParams();
2406 lp = (LayoutParams) generateDefaultLayoutParams();
2407 } else if (!checkLayoutParams(vlp)) {
2408 lp = (LayoutParams) generateLayoutParams(vlp);
2410 lp = (LayoutParams) vlp;
2413 if (mAdapterHasStableIds) {
2414 lp.itemId = mAdapter.getItemId(position);
2416 lp.viewType = mAdapter.getItemViewType(position);
2417 lp.isEnabled = mAdapter.isEnabled(position);
2419 child.setLayoutParams(lp);
2423 class ListItemAccessibilityDelegate extends AccessibilityDelegate {
2425 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2426 super.onInitializeAccessibilityNodeInfo(host, info);
2428 final int position = getPositionForView(host);
2429 onInitializeAccessibilityNodeInfoForItem(host, position, info);
2433 public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
2434 if (super.performAccessibilityAction(host, action, arguments)) {
2438 final int position = getPositionForView(host);
2439 if (position == INVALID_POSITION || mAdapter == null) {
2440 // Cannot perform actions on invalid items.
2444 if (position >= mAdapter.getCount()) {
2445 // The position is no longer valid, likely due to a data set
2446 // change. We could fail here for all data set changes, since
2447 // there is a chance that the data bound to the view may no
2448 // longer exist at the same position within the adapter, but
2449 // it's more consistent with the standard touch interaction to
2450 // click at whatever may have moved into that position.
2454 final boolean isItemEnabled;
2455 final ViewGroup.LayoutParams lp = host.getLayoutParams();
2456 if (lp instanceof AbsListView.LayoutParams) {
2457 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2459 isItemEnabled = false;
2462 if (!isEnabled() || !isItemEnabled) {
2463 // Cannot perform actions on disabled items.
2468 case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
2469 if (getSelectedItemPosition() == position) {
2470 setSelection(INVALID_POSITION);
2474 case AccessibilityNodeInfo.ACTION_SELECT: {
2475 if (getSelectedItemPosition() != position) {
2476 setSelection(position);
2480 case AccessibilityNodeInfo.ACTION_CLICK: {
2481 if (isItemClickable(host)) {
2482 final long id = getItemIdAtPosition(position);
2483 return performItemClick(host, position, id);
2486 case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
2487 if (isLongClickable()) {
2488 final long id = getItemIdAtPosition(position);
2489 return performLongPress(host, position, id);
2499 * Initializes an {@link AccessibilityNodeInfo} with information about a
2500 * particular item in the list.
2502 * @param view View representing the list item.
2503 * @param position Position of the list item within the adapter.
2504 * @param info Node info to populate.
2506 public void onInitializeAccessibilityNodeInfoForItem(
2507 View view, int position, AccessibilityNodeInfo info) {
2508 if (position == INVALID_POSITION) {
2509 // The item doesn't exist, so there's not much we can do here.
2513 final boolean isItemEnabled;
2514 final ViewGroup.LayoutParams lp = view.getLayoutParams();
2515 if (lp instanceof AbsListView.LayoutParams) {
2516 isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled;
2518 isItemEnabled = false;
2521 if (!isEnabled() || !isItemEnabled) {
2522 info.setEnabled(false);
2526 if (position == getSelectedItemPosition()) {
2527 info.setSelected(true);
2528 info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
2530 info.addAction(AccessibilityAction.ACTION_SELECT);
2533 if (isItemClickable(view)) {
2534 info.addAction(AccessibilityAction.ACTION_CLICK);
2535 info.setClickable(true);
2538 if (isLongClickable()) {
2539 info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
2540 info.setLongClickable(true);
2544 private boolean isItemClickable(View view) {
2545 return !view.hasExplicitFocusable();
2549 * Positions the selector in a way that mimics touch.
2551 void positionSelectorLikeTouch(int position, View sel, float x, float y) {
2552 positionSelector(position, sel, true, x, y);
2556 * Positions the selector in a way that mimics keyboard focus.
2558 void positionSelectorLikeFocus(int position, View sel) {
2559 if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) {
2560 final Rect bounds = mSelectorRect;
2561 final float x = bounds.exactCenterX();
2562 final float y = bounds.exactCenterY();
2563 positionSelector(position, sel, true, x, y);
2565 positionSelector(position, sel);
2569 void positionSelector(int position, View sel) {
2570 positionSelector(position, sel, false, -1, -1);
2573 private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2574 final boolean positionChanged = position != mSelectorPosition;
2575 if (position != INVALID_POSITION) {
2576 mSelectorPosition = position;
2579 final Rect selectorRect = mSelectorRect;
2580 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
2581 if (sel instanceof SelectionBoundsAdjuster) {
2582 ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
2585 // Adjust for selection padding.
2586 selectorRect.left -= mSelectionLeftPadding;
2587 selectorRect.top -= mSelectionTopPadding;
2588 selectorRect.right += mSelectionRightPadding;
2589 selectorRect.bottom += mSelectionBottomPadding;
2591 // Update the child enabled state prior to updating the selector.
2592 final boolean isChildViewEnabled = sel.isEnabled();
2593 if (mIsChildViewEnabled != isChildViewEnabled) {
2594 mIsChildViewEnabled = isChildViewEnabled;
2597 // Update the selector drawable's state and position.
2598 final Drawable selector = mSelector;
2599 if (selector != null) {
2600 if (positionChanged) {
2601 // Wipe out the current selector state so that we can start
2602 // over in the new position with a fresh state.
2603 selector.setVisible(false, false);
2604 selector.setState(StateSet.NOTHING);
2606 selector.setBounds(selectorRect);
2607 if (positionChanged) {
2608 if (getVisibility() == VISIBLE) {
2609 selector.setVisible(true, false);
2611 updateSelectorState();
2613 if (manageHotspot) {
2614 selector.setHotspot(x, y);
2620 protected void dispatchDraw(Canvas canvas) {
2622 final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
2623 if (clipToPadding) {
2624 saveCount = canvas.save();
2625 final int scrollX = mScrollX;
2626 final int scrollY = mScrollY;
2627 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
2628 scrollX + mRight - mLeft - mPaddingRight,
2629 scrollY + mBottom - mTop - mPaddingBottom);
2630 mGroupFlags &= ~CLIP_TO_PADDING_MASK;
2633 final boolean drawSelectorOnTop = mDrawSelectorOnTop;
2634 if (!drawSelectorOnTop) {
2635 drawSelector(canvas);
2638 super.dispatchDraw(canvas);
2640 if (drawSelectorOnTop) {
2641 drawSelector(canvas);
2644 if (clipToPadding) {
2645 canvas.restoreToCount(saveCount);
2646 mGroupFlags |= CLIP_TO_PADDING_MASK;
2651 protected boolean isPaddingOffsetRequired() {
2652 return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK;
2656 protected int getLeftPaddingOffset() {
2657 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft;
2661 protected int getTopPaddingOffset() {
2662 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop;
2666 protected int getRightPaddingOffset() {
2667 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight;
2671 protected int getBottomPaddingOffset() {
2672 return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom;
2679 protected void internalSetPadding(int left, int top, int right, int bottom) {
2680 super.internalSetPadding(left, top, right, bottom);
2681 if (isLayoutRequested()) {
2682 handleBoundsChange();
2687 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
2688 handleBoundsChange();
2689 if (mFastScroll != null) {
2690 mFastScroll.onSizeChanged(w, h, oldw, oldh);
2695 * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed
2696 * and force layouts all children that don't have exact measure specs.
2698 * This invalidation is necessary, otherwise, AbsListView may think the children are valid and
2699 * fail to relayout them properly to accommodate for new bounds.
2701 void handleBoundsChange() {
2705 final int childCount = getChildCount();
2706 if (childCount > 0) {
2707 mDataChanged = true;
2708 rememberSyncState();
2709 for (int i = 0; i < childCount; i++) {
2710 final View child = getChildAt(i);
2711 final ViewGroup.LayoutParams lp = child.getLayoutParams();
2712 // force layout child unless it has exact specs
2713 if (lp == null || lp.width < 1 || lp.height < 1) {
2714 child.forceLayout();
2721 * @return True if the current touch mode requires that we draw the selector in the pressed
2724 boolean touchModeDrawsInPressedState() {
2725 // FIXME use isPressed for this
2726 switch (mTouchMode) {
2727 case TOUCH_MODE_TAP:
2728 case TOUCH_MODE_DONE_WAITING:
2736 * Indicates whether this view is in a state where the selector should be drawn. This will
2737 * happen if we have focus but are not in touch mode, or we are in the middle of displaying
2738 * the pressed state for an item.
2740 * @return True if the selector should be shown
2742 boolean shouldShowSelector() {
2743 return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed());
2746 private void drawSelector(Canvas canvas) {
2747 if (!mSelectorRect.isEmpty()) {
2748 final Drawable selector = mSelector;
2749 selector.setBounds(mSelectorRect);
2750 selector.draw(canvas);
2755 * Controls whether the selection highlight drawable should be drawn on top of the item or
2758 * @param onTop If true, the selector will be drawn on the item it is highlighting. The default
2761 * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
2763 public void setDrawSelectorOnTop(boolean onTop) {
2764 mDrawSelectorOnTop = onTop;
2768 * Set a Drawable that should be used to highlight the currently selected item.
2770 * @param resID A Drawable resource to use as the selection highlight.
2772 * @attr ref android.R.styleable#AbsListView_listSelector
2774 public void setSelector(@DrawableRes int resID) {
2775 setSelector(getContext().getDrawable(resID));
2778 public void setSelector(Drawable sel) {
2779 if (mSelector != null) {
2780 mSelector.setCallback(null);
2781 unscheduleDrawable(mSelector);
2784 Rect padding = new Rect();
2785 sel.getPadding(padding);
2786 mSelectionLeftPadding = padding.left;
2787 mSelectionTopPadding = padding.top;
2788 mSelectionRightPadding = padding.right;
2789 mSelectionBottomPadding = padding.bottom;
2790 sel.setCallback(this);
2791 updateSelectorState();
2795 * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
2796 * selection in the list.
2798 * @return the drawable used to display the selector
2800 public Drawable getSelector() {
2805 * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
2806 * this is a long press.
2809 if (!isEnabled() || !isClickable()) {
2813 Drawable selector = mSelector;
2814 Rect selectorRect = mSelectorRect;
2815 if (selector != null && (isFocused() || touchModeDrawsInPressedState())
2816 && !selectorRect.isEmpty()) {
2818 final View v = getChildAt(mSelectedPosition - mFirstPosition);
2821 if (v.hasExplicitFocusable()) return;
2826 final boolean longClickable = isLongClickable();
2827 Drawable d = selector.getCurrent();
2828 if (d != null && d instanceof TransitionDrawable) {
2829 if (longClickable) {
2830 ((TransitionDrawable) d).startTransition(
2831 ViewConfiguration.getLongPressTimeout());
2833 ((TransitionDrawable) d).resetTransition();
2836 if (longClickable && !mDataChanged) {
2837 if (mPendingCheckForKeyLongPress == null) {
2838 mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
2840 mPendingCheckForKeyLongPress.rememberWindowAttachCount();
2841 postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
2846 public void setScrollIndicators(View up, View down) {
2851 void updateSelectorState() {
2852 final Drawable selector = mSelector;
2853 if (selector != null && selector.isStateful()) {
2854 if (shouldShowSelector()) {
2855 if (selector.setState(getDrawableStateForSelector())) {
2856 invalidateDrawable(selector);
2859 selector.setState(StateSet.NOTHING);
2865 protected void drawableStateChanged() {
2866 super.drawableStateChanged();
2867 updateSelectorState();
2870 private int[] getDrawableStateForSelector() {
2871 // If the child view is enabled then do the default behavior.
2872 if (mIsChildViewEnabled) {
2874 return super.getDrawableState();
2877 // The selector uses this View's drawable state. The selected child view
2878 // is disabled, so we need to remove the enabled state from the drawable
2880 final int enabledState = ENABLED_STATE_SET[0];
2882 // If we don't have any extra space, it will return one of the static
2883 // state arrays, and clearing the enabled state on those arrays is a
2884 // bad thing! If we specify we need extra space, it will create+copy
2885 // into a new array that is safely mutable.
2886 final int[] state = onCreateDrawableState(1);
2888 int enabledPos = -1;
2889 for (int i = state.length - 1; i >= 0; i--) {
2890 if (state[i] == enabledState) {
2896 // Remove the enabled state
2897 if (enabledPos >= 0) {
2898 System.arraycopy(state, enabledPos + 1, state, enabledPos,
2899 state.length - enabledPos - 1);
2906 public boolean verifyDrawable(@NonNull Drawable dr) {
2907 return mSelector == dr || super.verifyDrawable(dr);
2911 public void jumpDrawablesToCurrentState() {
2912 super.jumpDrawablesToCurrentState();
2913 if (mSelector != null) mSelector.jumpToCurrentState();
2917 protected void onAttachedToWindow() {
2918 super.onAttachedToWindow();
2920 final ViewTreeObserver treeObserver = getViewTreeObserver();
2921 treeObserver.addOnTouchModeChangeListener(this);
2922 if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
2923 treeObserver.addOnGlobalLayoutListener(this);
2926 if (mAdapter != null && mDataSetObserver == null) {
2927 mDataSetObserver = new AdapterDataSetObserver();
2928 mAdapter.registerDataSetObserver(mDataSetObserver);
2930 // Data may have changed while we were detached. Refresh.
2931 mDataChanged = true;
2932 mOldItemCount = mItemCount;
2933 mItemCount = mAdapter.getCount();
2938 protected void onDetachedFromWindow() {
2939 super.onDetachedFromWindow();
2941 mIsDetaching = true;
2943 // Dismiss the popup in case onSaveInstanceState() was not invoked
2946 // Detach any view left in the scrap heap
2949 final ViewTreeObserver treeObserver = getViewTreeObserver();
2950 treeObserver.removeOnTouchModeChangeListener(this);
2951 if (mTextFilterEnabled && mPopup != null) {
2952 treeObserver.removeOnGlobalLayoutListener(this);
2953 mGlobalLayoutListenerAddedFilter = false;
2956 if (mAdapter != null && mDataSetObserver != null) {
2957 mAdapter.unregisterDataSetObserver(mDataSetObserver);
2958 mDataSetObserver = null;
2961 if (mScrollStrictSpan != null) {
2962 mScrollStrictSpan.finish();
2963 mScrollStrictSpan = null;
2966 if (mFlingStrictSpan != null) {
2967 mFlingStrictSpan.finish();
2968 mFlingStrictSpan = null;
2971 if (mFlingRunnable != null) {
2972 removeCallbacks(mFlingRunnable);
2975 if (mPositionScroller != null) {
2976 mPositionScroller.stop();
2979 if (mClearScrollingCache != null) {
2980 removeCallbacks(mClearScrollingCache);
2983 if (mPerformClick != null) {
2984 removeCallbacks(mPerformClick);
2987 if (mTouchModeReset != null) {
2988 removeCallbacks(mTouchModeReset);
2989 mTouchModeReset.run();
2992 mIsDetaching = false;
2996 public void onWindowFocusChanged(boolean hasWindowFocus) {
2997 super.onWindowFocusChanged(hasWindowFocus);
2999 final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
3001 if (!hasWindowFocus) {
3002 setChildrenDrawingCacheEnabled(false);
3003 if (mFlingRunnable != null) {
3004 removeCallbacks(mFlingRunnable);
3005 // let the fling runnable report its new state which
3007 mFlingRunnable.mSuppressIdleStateChangeCall = false;
3008 mFlingRunnable.endFling();
3009 if (mPositionScroller != null) {
3010 mPositionScroller.stop();
3012 if (mScrollY != 0) {
3014 invalidateParentCaches();
3019 // Always hide the type filter
3022 if (touchMode == TOUCH_MODE_OFF) {
3023 // Remember the last selected element
3024 mResurrectToPosition = mSelectedPosition;
3027 if (mFiltered && !mPopupHidden) {
3028 // Show the type filter only if a filter is in effect
3032 // If we changed touch mode since the last time we had focus
3033 if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
3034 // If we come back in trackball mode, we bring the selection back
3035 if (touchMode == TOUCH_MODE_OFF) {
3036 // This will trigger a layout
3037 resurrectSelection();
3039 // If we come back in touch mode, then we want to hide the selector
3042 mLayoutMode = LAYOUT_NORMAL;
3048 mLastTouchMode = touchMode;
3052 public void onRtlPropertiesChanged(int layoutDirection) {
3053 super.onRtlPropertiesChanged(layoutDirection);
3054 if (mFastScroll != null) {
3055 mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
3060 * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
3061 * methods knows the view, position and ID of the item that received the
3064 * @param view The view that received the long press.
3065 * @param position The position of the item that received the long press.
3066 * @param id The ID of the item that received the long press.
3067 * @return The extra information that should be returned by
3068 * {@link #getContextMenuInfo()}.
3070 ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
3071 return new AdapterContextMenuInfo(view, position, id);
3075 public void onCancelPendingInputEvents() {
3076 super.onCancelPendingInputEvents();
3077 if (mPerformClick != null) {
3078 removeCallbacks(mPerformClick);
3080 if (mPendingCheckForTap != null) {
3081 removeCallbacks(mPendingCheckForTap);
3083 if (mPendingCheckForLongPress != null) {
3084 removeCallbacks(mPendingCheckForLongPress);
3086 if (mPendingCheckForKeyLongPress != null) {
3087 removeCallbacks(mPendingCheckForKeyLongPress);
3092 * A base class for Runnables that will check that their view is still attached to
3093 * the original window as when the Runnable was created.
3096 private class WindowRunnnable {
3097 private int mOriginalAttachCount;
3099 public void rememberWindowAttachCount() {
3100 mOriginalAttachCount = getWindowAttachCount();
3103 public boolean sameWindow() {
3104 return getWindowAttachCount() == mOriginalAttachCount;
3108 private class PerformClick extends WindowRunnnable implements Runnable {
3109 int mClickMotionPosition;
3113 // The data has changed since we posted this action in the event queue,
3114 // bail out before bad things happen
3115 if (mDataChanged) return;
3117 final ListAdapter adapter = mAdapter;
3118 final int motionPosition = mClickMotionPosition;
3119 if (adapter != null && mItemCount > 0 &&
3120 motionPosition != INVALID_POSITION &&
3121 motionPosition < adapter.getCount() && sameWindow() &&
3122 adapter.isEnabled(motionPosition)) {
3123 final View view = getChildAt(motionPosition - mFirstPosition);
3124 // If there is no view, something bad happened (the view scrolled off the
3125 // screen, etc.) and we should cancel the click
3127 performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
3133 private class CheckForLongPress extends WindowRunnnable implements Runnable {
3134 private static final int INVALID_COORD = -1;
3135 private float mX = INVALID_COORD;
3136 private float mY = INVALID_COORD;
3138 private void setCoords(float x, float y) {
3145 final int motionPosition = mMotionPosition;
3146 final View child = getChildAt(motionPosition - mFirstPosition);
3147 if (child != null) {
3148 final int longPressPosition = mMotionPosition;
3149 final long longPressId = mAdapter.getItemId(mMotionPosition);
3151 boolean handled = false;
3152 if (sameWindow() && !mDataChanged) {
3153 if (mX != INVALID_COORD && mY != INVALID_COORD) {
3154 handled = performLongPress(child, longPressPosition, longPressId, mX, mY);
3156 handled = performLongPress(child, longPressPosition, longPressId);
3161 mHasPerformedLongPress = true;
3162 mTouchMode = TOUCH_MODE_REST;
3164 child.setPressed(false);
3166 mTouchMode = TOUCH_MODE_DONE_WAITING;
3172 private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
3175 if (isPressed() && mSelectedPosition >= 0) {
3176 int index = mSelectedPosition - mFirstPosition;
3177 View v = getChildAt(index);
3179 if (!mDataChanged) {
3180 boolean handled = false;
3182 handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
3186 v.setPressed(false);
3190 if (v != null) v.setPressed(false);
3196 private boolean performStylusButtonPressAction(MotionEvent ev) {
3197 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
3198 final View child = getChildAt(mMotionPosition - mFirstPosition);
3199 if (child != null) {
3200 final int longPressPosition = mMotionPosition;
3201 final long longPressId = mAdapter.getItemId(mMotionPosition);
3202 if (performLongPress(child, longPressPosition, longPressId)) {
3203 mTouchMode = TOUCH_MODE_REST;
3205 child.setPressed(false);
3213 boolean performLongPress(final View child,
3214 final int longPressPosition, final long longPressId) {
3215 return performLongPress(
3219 CheckForLongPress.INVALID_COORD,
3220 CheckForLongPress.INVALID_COORD);
3223 boolean performLongPress(final View child,
3224 final int longPressPosition, final long longPressId, float x, float y) {
3225 // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
3226 if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
3227 if (mChoiceActionMode == null &&
3228 (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) {
3229 setItemChecked(longPressPosition, true);
3230 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3235 boolean handled = false;
3236 if (mOnItemLongClickListener != null) {
3237 handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child,
3238 longPressPosition, longPressId);
3241 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3242 if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) {
3243 handled = super.showContextMenuForChild(AbsListView.this, x, y);
3245 handled = super.showContextMenuForChild(AbsListView.this);
3249 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
3255 protected ContextMenuInfo getContextMenuInfo() {
3256 return mContextMenuInfo;
3260 public boolean showContextMenu() {
3261 return showContextMenuInternal(0, 0, false);
3265 public boolean showContextMenu(float x, float y) {
3266 return showContextMenuInternal(x, y, true);
3269 private boolean showContextMenuInternal(float x, float y, boolean useOffsets) {
3270 final int position = pointToPosition((int)x, (int)y);
3271 if (position != INVALID_POSITION) {
3272 final long id = mAdapter.getItemId(position);
3273 View child = getChildAt(position - mFirstPosition);
3274 if (child != null) {
3275 mContextMenuInfo = createContextMenuInfo(child, position, id);
3277 return super.showContextMenuForChild(this, x, y);
3279 return super.showContextMenuForChild(this);
3284 return super.showContextMenu(x, y);
3286 return super.showContextMenu();
3291 public boolean showContextMenuForChild(View originalView) {
3292 if (isShowingContextMenuWithCoords()) {
3295 return showContextMenuForChildInternal(originalView, 0, 0, false);
3299 public boolean showContextMenuForChild(View originalView, float x, float y) {
3300 return showContextMenuForChildInternal(originalView,x, y, true);
3303 private boolean showContextMenuForChildInternal(View originalView, float x, float y,
3304 boolean useOffsets) {
3305 final int longPressPosition = getPositionForView(originalView);
3306 if (longPressPosition < 0) {
3310 final long longPressId = mAdapter.getItemId(longPressPosition);
3311 boolean handled = false;
3313 if (mOnItemLongClickListener != null) {
3314 handled = mOnItemLongClickListener.onItemLongClick(this, originalView,
3315 longPressPosition, longPressId);
3319 final View child = getChildAt(longPressPosition - mFirstPosition);
3320 mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
3323 handled = super.showContextMenuForChild(originalView, x, y);
3325 handled = super.showContextMenuForChild(originalView);
3333 public boolean onKeyDown(int keyCode, KeyEvent event) {
3338 public boolean onKeyUp(int keyCode, KeyEvent event) {
3339 if (KeyEvent.isConfirmKey(keyCode)) {
3343 if (isClickable() && isPressed() &&
3344 mSelectedPosition >= 0 && mAdapter != null &&
3345 mSelectedPosition < mAdapter.getCount()) {
3347 final View view = getChildAt(mSelectedPosition - mFirstPosition);
3349 performItemClick(view, mSelectedPosition, mSelectedRowId);
3350 view.setPressed(false);
3356 return super.onKeyUp(keyCode, event);
3360 protected void dispatchSetPressed(boolean pressed) {
3361 // Don't dispatch setPressed to our children. We call setPressed on ourselves to
3362 // get the selector in the right state, but we don't want to press each child.
3366 public void dispatchDrawableHotspotChanged(float x, float y) {
3367 // Don't dispatch hotspot changes to children. We'll manually handle
3368 // calling drawableHotspotChanged on the correct child.
3372 * Maps a point to a position in the list.
3374 * @param x X in local coordinate
3375 * @param y Y in local coordinate
3376 * @return The position of the item which contains the specified point, or
3377 * {@link #INVALID_POSITION} if the point does not intersect an item.
3379 public int pointToPosition(int x, int y) {
3380 Rect frame = mTouchFrame;
3381 if (frame == null) {
3382 mTouchFrame = new Rect();
3383 frame = mTouchFrame;
3386 final int count = getChildCount();
3387 for (int i = count - 1; i >= 0; i--) {
3388 final View child = getChildAt(i);
3389 if (child.getVisibility() == View.VISIBLE) {
3390 child.getHitRect(frame);
3391 if (frame.contains(x, y)) {
3392 return mFirstPosition + i;
3396 return INVALID_POSITION;
3401 * Maps a point to a the rowId of the item which intersects that point.
3403 * @param x X in local coordinate
3404 * @param y Y in local coordinate
3405 * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID}
3406 * if the point does not intersect an item.
3408 public long pointToRowId(int x, int y) {
3409 int position = pointToPosition(x, y);
3410 if (position >= 0) {
3411 return mAdapter.getItemId(position);
3413 return INVALID_ROW_ID;
3416 private final class CheckForTap implements Runnable {
3422 if (mTouchMode == TOUCH_MODE_DOWN) {
3423 mTouchMode = TOUCH_MODE_TAP;
3424 final View child = getChildAt(mMotionPosition - mFirstPosition);
3425 if (child != null && !child.hasExplicitFocusable()) {
3426 mLayoutMode = LAYOUT_NORMAL;
3428 if (!mDataChanged) {
3429 final float[] point = mTmpPoint;
3432 transformPointToViewLocal(point, child);
3433 child.drawableHotspotChanged(point[0], point[1]);
3434 child.setPressed(true);
3437 positionSelector(mMotionPosition, child);
3438 refreshDrawableState();
3440 final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
3441 final boolean longClickable = isLongClickable();
3443 if (mSelector != null) {
3444 final Drawable d = mSelector.getCurrent();
3445 if (d != null && d instanceof TransitionDrawable) {
3446 if (longClickable) {
3447 ((TransitionDrawable) d).startTransition(longPressTimeout);
3449 ((TransitionDrawable) d).resetTransition();
3452 mSelector.setHotspot(x, y);
3455 if (longClickable) {
3456 if (mPendingCheckForLongPress == null) {
3457 mPendingCheckForLongPress = new CheckForLongPress();
3459 mPendingCheckForLongPress.setCoords(x, y);
3460 mPendingCheckForLongPress.rememberWindowAttachCount();
3461 postDelayed(mPendingCheckForLongPress, longPressTimeout);
3463 mTouchMode = TOUCH_MODE_DONE_WAITING;
3466 mTouchMode = TOUCH_MODE_DONE_WAITING;
3473 private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
3474 // Check if we have moved far enough that it looks more like a
3475 // scroll than a tap
3476 final int deltaY = y - mMotionY;
3477 final int distance = Math.abs(deltaY);
3478 final boolean overscroll = mScrollY != 0;
3479 if ((overscroll || distance > mTouchSlop) &&
3480 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
3481 createScrollingCache();
3483 mTouchMode = TOUCH_MODE_OVERSCROLL;
3484 mMotionCorrection = 0;
3486 mTouchMode = TOUCH_MODE_SCROLL;
3487 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
3489 removeCallbacks(mPendingCheckForLongPress);
3491 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3492 if (motionView != null) {
3493 motionView.setPressed(false);
3495 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
3496 // Time to start stealing events! Once we've stolen them, don't let anyone
3498 final ViewParent parent = getParent();
3499 if (parent != null) {
3500 parent.requestDisallowInterceptTouchEvent(true);
3502 scrollIfNeeded(x, y, vtev);
3509 private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
3510 int rawDeltaY = y - mMotionY;
3511 int scrollOffsetCorrection = 0;
3512 int scrollConsumedCorrection = 0;
3513 if (mLastY == Integer.MIN_VALUE) {
3514 rawDeltaY -= mMotionCorrection;
3516 if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY,
3517 mScrollConsumed, mScrollOffset)) {
3518 rawDeltaY += mScrollConsumed[1];
3519 scrollOffsetCorrection = -mScrollOffset[1];
3520 scrollConsumedCorrection = mScrollConsumed[1];
3522 vtev.offsetLocation(0, mScrollOffset[1]);
3523 mNestedYOffset += mScrollOffset[1];
3526 final int deltaY = rawDeltaY;
3527 int incrementalDeltaY =
3528 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
3529 int lastYCorrection = 0;
3531 if (mTouchMode == TOUCH_MODE_SCROLL) {
3532 if (PROFILE_SCROLLING) {
3533 if (!mScrollProfilingStarted) {
3534 Debug.startMethodTracing("AbsListViewScroll");
3535 mScrollProfilingStarted = true;
3539 if (mScrollStrictSpan == null) {
3540 // If it's non-null, we're already in a scroll.
3541 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
3545 // We may be here after stopping a fling and continuing to scroll.
3546 // If so, we haven't disallowed intercepting touch events yet.
3547 // Make sure that we do so in case we're in a parent that can intercept.
3548 if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
3549 Math.abs(rawDeltaY) > mTouchSlop) {
3550 final ViewParent parent = getParent();
3551 if (parent != null) {
3552 parent.requestDisallowInterceptTouchEvent(true);
3556 final int motionIndex;
3557 if (mMotionPosition >= 0) {
3558 motionIndex = mMotionPosition - mFirstPosition;
3560 // If we don't have a motion position that we can reliably track,
3561 // pick something in the middle to make a best guess at things below.
3562 motionIndex = getChildCount() / 2;
3565 int motionViewPrevTop = 0;
3566 View motionView = this.getChildAt(motionIndex);
3567 if (motionView != null) {
3568 motionViewPrevTop = motionView.getTop();
3571 // No need to do all this work if we're not going to move anyway
3572 boolean atEdge = false;
3573 if (incrementalDeltaY != 0) {
3574 atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
3577 // Check to see if we have bumped into the scroll limit
3578 motionView = this.getChildAt(motionIndex);
3579 if (motionView != null) {
3580 // Check if the top of the motion view is where it is
3582 final int motionViewRealTop = motionView.getTop();
3586 int overscroll = -incrementalDeltaY -
3587 (motionViewRealTop - motionViewPrevTop);
3588 if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
3590 lastYCorrection -= mScrollOffset[1];
3592 vtev.offsetLocation(0, mScrollOffset[1]);
3593 mNestedYOffset += mScrollOffset[1];
3596 final boolean atOverscrollEdge = overScrollBy(0, overscroll,
3597 0, mScrollY, 0, 0, 0, mOverscrollDistance, true);
3599 if (atOverscrollEdge && mVelocityTracker != null) {
3600 // Don't allow overfling if we're at the edge
3601 mVelocityTracker.clear();
3604 final int overscrollMode = getOverScrollMode();
3605 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3606 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3608 if (!atOverscrollEdge) {
3609 mDirection = 0; // Reset when entering overscroll.
3610 mTouchMode = TOUCH_MODE_OVERSCROLL;
3612 if (incrementalDeltaY > 0) {
3613 mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
3614 (float) x / getWidth());
3615 if (!mEdgeGlowBottom.isFinished()) {
3616 mEdgeGlowBottom.onRelease();
3618 invalidateTopGlow();
3619 } else if (incrementalDeltaY < 0) {
3620 mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
3621 1.f - (float) x / getWidth());
3622 if (!mEdgeGlowTop.isFinished()) {
3623 mEdgeGlowTop.onRelease();
3625 invalidateBottomGlow();
3630 mMotionY = y + lastYCorrection + scrollOffsetCorrection;
3632 mLastY = y + lastYCorrection + scrollOffsetCorrection;
3634 } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
3636 final int oldScroll = mScrollY;
3637 final int newScroll = oldScroll - incrementalDeltaY;
3638 int newDirection = y > mLastY ? 1 : -1;
3640 if (mDirection == 0) {
3641 mDirection = newDirection;
3644 int overScrollDistance = -incrementalDeltaY;
3645 if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
3646 overScrollDistance = -oldScroll;
3647 incrementalDeltaY += overScrollDistance;
3649 incrementalDeltaY = 0;
3652 if (overScrollDistance != 0) {
3653 overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
3654 0, mOverscrollDistance, true);
3655 final int overscrollMode = getOverScrollMode();
3656 if (overscrollMode == OVER_SCROLL_ALWAYS ||
3657 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
3659 if (rawDeltaY > 0) {
3660 mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
3661 (float) x / getWidth());
3662 if (!mEdgeGlowBottom.isFinished()) {
3663 mEdgeGlowBottom.onRelease();
3665 invalidateTopGlow();
3666 } else if (rawDeltaY < 0) {
3667 mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
3668 1.f - (float) x / getWidth());
3669 if (!mEdgeGlowTop.isFinished()) {
3670 mEdgeGlowTop.onRelease();
3672 invalidateBottomGlow();
3677 if (incrementalDeltaY != 0) {
3678 // Coming back to 'real' list scrolling
3679 if (mScrollY != 0) {
3681 invalidateParentIfNeeded();
3684 trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
3686 mTouchMode = TOUCH_MODE_SCROLL;
3688 // We did not scroll the full amount. Treat this essentially like the
3689 // start of a new touch scroll
3690 final int motionPosition = findClosestMotionRow(y);
3692 mMotionCorrection = 0;
3693 View motionView = getChildAt(motionPosition - mFirstPosition);
3694 mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
3695 mMotionY = y + scrollOffsetCorrection;
3696 mMotionPosition = motionPosition;
3698 mLastY = y + lastYCorrection + scrollOffsetCorrection;
3699 mDirection = newDirection;
3704 private void invalidateTopGlow() {
3705 if (mEdgeGlowTop == null) {
3708 final boolean clipToPadding = getClipToPadding();
3709 final int top = clipToPadding ? mPaddingTop : 0;
3710 final int left = clipToPadding ? mPaddingLeft : 0;
3711 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3712 invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
3715 private void invalidateBottomGlow() {
3716 if (mEdgeGlowBottom == null) {
3719 final boolean clipToPadding = getClipToPadding();
3720 final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
3721 final int left = clipToPadding ? mPaddingLeft : 0;
3722 final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
3723 invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
3727 public void onTouchModeChanged(boolean isInTouchMode) {
3728 if (isInTouchMode) {
3729 // Get rid of the selection when we enter touch mode
3731 // Layout, but only if we already have done so previously.
3732 // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
3734 if (getHeight() > 0 && getChildCount() > 0) {
3735 // We do not lose focus initiating a touch (since AbsListView is focusable in
3736 // touch mode). Force an initial layout to get rid of the selection.
3739 updateSelectorState();
3741 int touchMode = mTouchMode;
3742 if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
3743 if (mFlingRunnable != null) {
3744 mFlingRunnable.endFling();
3746 if (mPositionScroller != null) {
3747 mPositionScroller.stop();
3750 if (mScrollY != 0) {
3752 invalidateParentCaches();
3762 protected boolean handleScrollBarDragging(MotionEvent event) {
3763 // Doesn't support normal scroll bar dragging. Use FastScroller.
3768 public boolean onTouchEvent(MotionEvent ev) {
3770 // A disabled view that is clickable still consumes the touch
3771 // events, it just doesn't respond to them.
3772 return isClickable() || isLongClickable();
3775 if (mPositionScroller != null) {
3776 mPositionScroller.stop();
3779 if (mIsDetaching || !isAttachedToWindow()) {
3780 // Something isn't right.
3781 // Since we rely on being attached to get data set change notifications,
3782 // don't risk doing anything where we might try to resync and find things
3783 // in a bogus state.
3787 startNestedScroll(SCROLL_AXIS_VERTICAL);
3789 if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
3793 initVelocityTrackerIfNotExists();
3794 final MotionEvent vtev = MotionEvent.obtain(ev);
3796 final int actionMasked = ev.getActionMasked();
3797 if (actionMasked == MotionEvent.ACTION_DOWN) {
3800 vtev.offsetLocation(0, mNestedYOffset);
3801 switch (actionMasked) {
3802 case MotionEvent.ACTION_DOWN: {
3807 case MotionEvent.ACTION_MOVE: {
3808 onTouchMove(ev, vtev);
3812 case MotionEvent.ACTION_UP: {
3817 case MotionEvent.ACTION_CANCEL: {
3822 case MotionEvent.ACTION_POINTER_UP: {
3823 onSecondaryPointerUp(ev);
3824 final int x = mMotionX;
3825 final int y = mMotionY;
3826 final int motionPosition = pointToPosition(x, y);
3827 if (motionPosition >= 0) {
3828 // Remember where the motion event started
3829 final View child = getChildAt(motionPosition - mFirstPosition);
3830 mMotionViewOriginalTop = child.getTop();
3831 mMotionPosition = motionPosition;
3837 case MotionEvent.ACTION_POINTER_DOWN: {
3838 // New pointers take over dragging duties
3839 final int index = ev.getActionIndex();
3840 final int id = ev.getPointerId(index);
3841 final int x = (int) ev.getX(index);
3842 final int y = (int) ev.getY(index);
3843 mMotionCorrection = 0;
3844 mActivePointerId = id;
3847 final int motionPosition = pointToPosition(x, y);
3848 if (motionPosition >= 0) {
3849 // Remember where the motion event started
3850 final View child = getChildAt(motionPosition - mFirstPosition);
3851 mMotionViewOriginalTop = child.getTop();
3852 mMotionPosition = motionPosition;
3859 if (mVelocityTracker != null) {
3860 mVelocityTracker.addMovement(vtev);
3866 private void onTouchDown(MotionEvent ev) {
3867 mHasPerformedLongPress = false;
3868 mActivePointerId = ev.getPointerId(0);
3870 if (mTouchMode == TOUCH_MODE_OVERFLING) {
3871 // Stopped the fling. It is a scroll.
3872 mFlingRunnable.endFling();
3873 if (mPositionScroller != null) {
3874 mPositionScroller.stop();
3876 mTouchMode = TOUCH_MODE_OVERSCROLL;
3877 mMotionX = (int) ev.getX();
3878 mMotionY = (int) ev.getY();
3880 mMotionCorrection = 0;
3883 final int x = (int) ev.getX();
3884 final int y = (int) ev.getY();
3885 int motionPosition = pointToPosition(x, y);
3887 if (!mDataChanged) {
3888 if (mTouchMode == TOUCH_MODE_FLING) {
3889 // Stopped a fling. It is a scroll.
3890 createScrollingCache();
3891 mTouchMode = TOUCH_MODE_SCROLL;
3892 mMotionCorrection = 0;
3893 motionPosition = findMotionRow(y);
3894 mFlingRunnable.flywheelTouch();
3895 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
3896 // User clicked on an actual view (and was not stopping a
3897 // fling). It might be a click or a scroll. Assume it is a
3898 // click until proven otherwise.
3899 mTouchMode = TOUCH_MODE_DOWN;
3902 if (mPendingCheckForTap == null) {
3903 mPendingCheckForTap = new CheckForTap();
3906 mPendingCheckForTap.x = ev.getX();
3907 mPendingCheckForTap.y = ev.getY();
3908 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
3912 if (motionPosition >= 0) {
3913 // Remember where the motion event started
3914 final View v = getChildAt(motionPosition - mFirstPosition);
3915 mMotionViewOriginalTop = v.getTop();
3920 mMotionPosition = motionPosition;
3921 mLastY = Integer.MIN_VALUE;
3924 if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
3925 && performButtonActionOnTouchDown(ev)) {
3926 removeCallbacks(mPendingCheckForTap);
3930 private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
3931 if (mHasPerformedLongPress) {
3932 // Consume all move events following a successful long press.
3936 int pointerIndex = ev.findPointerIndex(mActivePointerId);
3937 if (pointerIndex == -1) {
3939 mActivePointerId = ev.getPointerId(pointerIndex);
3943 // Re-sync everything if data has been changed
3944 // since the scroll operation can query the adapter.
3948 final int y = (int) ev.getY(pointerIndex);
3950 switch (mTouchMode) {
3951 case TOUCH_MODE_DOWN:
3952 case TOUCH_MODE_TAP:
3953 case TOUCH_MODE_DONE_WAITING:
3954 // Check if we have moved far enough that it looks more like a
3955 // scroll than a tap. If so, we'll enter scrolling mode.
3956 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
3959 // Otherwise, check containment within list bounds. If we're
3960 // outside bounds, cancel any active presses.
3961 final View motionView = getChildAt(mMotionPosition - mFirstPosition);
3962 final float x = ev.getX(pointerIndex);
3963 if (!pointInView(x, y, mTouchSlop)) {
3965 if (motionView != null) {
3966 motionView.setPressed(false);
3968 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
3969 mPendingCheckForTap : mPendingCheckForLongPress);
3970 mTouchMode = TOUCH_MODE_DONE_WAITING;
3971 updateSelectorState();
3972 } else if (motionView != null) {
3973 // Still within bounds, update the hotspot.
3974 final float[] point = mTmpPoint;
3977 transformPointToViewLocal(point, motionView);
3978 motionView.drawableHotspotChanged(point[0], point[1]);
3981 case TOUCH_MODE_SCROLL:
3982 case TOUCH_MODE_OVERSCROLL:
3983 scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
3988 private void onTouchUp(MotionEvent ev) {
3989 switch (mTouchMode) {
3990 case TOUCH_MODE_DOWN:
3991 case TOUCH_MODE_TAP:
3992 case TOUCH_MODE_DONE_WAITING:
3993 final int motionPosition = mMotionPosition;
3994 final View child = getChildAt(motionPosition - mFirstPosition);
3995 if (child != null) {
3996 if (mTouchMode != TOUCH_MODE_DOWN) {
3997 child.setPressed(false);
4000 final float x = ev.getX();
4001 final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
4002 if (inList && !child.hasExplicitFocusable()) {
4003 if (mPerformClick == null) {
4004 mPerformClick = new PerformClick();
4007 final AbsListView.PerformClick performClick = mPerformClick;
4008 performClick.mClickMotionPosition = motionPosition;
4009 performClick.rememberWindowAttachCount();
4011 mResurrectToPosition = motionPosition;
4013 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
4014 removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
4015 mPendingCheckForTap : mPendingCheckForLongPress);
4016 mLayoutMode = LAYOUT_NORMAL;
4017 if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4018 mTouchMode = TOUCH_MODE_TAP;
4019 setSelectedPositionInt(mMotionPosition);
4021 child.setPressed(true);
4022 positionSelector(mMotionPosition, child);
4024 if (mSelector != null) {
4025 Drawable d = mSelector.getCurrent();
4026 if (d != null && d instanceof TransitionDrawable) {
4027 ((TransitionDrawable) d).resetTransition();
4029 mSelector.setHotspot(x, ev.getY());
4031 if (mTouchModeReset != null) {
4032 removeCallbacks(mTouchModeReset);
4034 mTouchModeReset = new Runnable() {
4037 mTouchModeReset = null;
4038 mTouchMode = TOUCH_MODE_REST;
4039 child.setPressed(false);
4041 if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
4046 postDelayed(mTouchModeReset,
4047 ViewConfiguration.getPressedStateDuration());
4049 mTouchMode = TOUCH_MODE_REST;
4050 updateSelectorState();
4053 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
4058 mTouchMode = TOUCH_MODE_REST;
4059 updateSelectorState();
4061 case TOUCH_MODE_SCROLL:
4062 final int childCount = getChildCount();
4063 if (childCount > 0) {
4064 final int firstChildTop = getChildAt(0).getTop();
4065 final int lastChildBottom = getChildAt(childCount - 1).getBottom();
4066 final int contentTop = mListPadding.top;
4067 final int contentBottom = getHeight() - mListPadding.bottom;
4068 if (mFirstPosition == 0 && firstChildTop >= contentTop &&
4069 mFirstPosition + childCount < mItemCount &&
4070 lastChildBottom <= getHeight() - contentBottom) {
4071 mTouchMode = TOUCH_MODE_REST;
4072 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4074 final VelocityTracker velocityTracker = mVelocityTracker;
4075 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4077 final int initialVelocity = (int)
4078 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
4079 // Fling if we have enough velocity and we aren't at a boundary.
4080 // Since we can potentially overfling more than we can overscroll, don't
4081 // allow the weird behavior where you can scroll to a boundary then
4083 boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
4084 if (flingVelocity &&
4085 !((mFirstPosition == 0 &&
4086 firstChildTop == contentTop - mOverscrollDistance) ||
4087 (mFirstPosition + childCount == mItemCount &&
4088 lastChildBottom == contentBottom + mOverscrollDistance))) {
4089 if (!dispatchNestedPreFling(0, -initialVelocity)) {
4090 if (mFlingRunnable == null) {
4091 mFlingRunnable = new FlingRunnable();
4093 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4094 mFlingRunnable.start(-initialVelocity);
4095 dispatchNestedFling(0, -initialVelocity, true);
4097 mTouchMode = TOUCH_MODE_REST;
4098 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4101 mTouchMode = TOUCH_MODE_REST;
4102 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4103 if (mFlingRunnable != null) {
4104 mFlingRunnable.endFling();
4106 if (mPositionScroller != null) {
4107 mPositionScroller.stop();
4109 if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) {
4110 dispatchNestedFling(0, -initialVelocity, false);
4115 mTouchMode = TOUCH_MODE_REST;
4116 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4120 case TOUCH_MODE_OVERSCROLL:
4121 if (mFlingRunnable == null) {
4122 mFlingRunnable = new FlingRunnable();
4124 final VelocityTracker velocityTracker = mVelocityTracker;
4125 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4126 final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
4128 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4129 if (Math.abs(initialVelocity) > mMinimumVelocity) {
4130 mFlingRunnable.startOverfling(-initialVelocity);
4132 mFlingRunnable.startSpringback();
4140 if (mEdgeGlowTop != null) {
4141 mEdgeGlowTop.onRelease();
4142 mEdgeGlowBottom.onRelease();
4145 // Need to redraw since we probably aren't drawing the selector anymore
4147 removeCallbacks(mPendingCheckForLongPress);
4148 recycleVelocityTracker();
4150 mActivePointerId = INVALID_POINTER;
4152 if (PROFILE_SCROLLING) {
4153 if (mScrollProfilingStarted) {
4154 Debug.stopMethodTracing();
4155 mScrollProfilingStarted = false;
4159 if (mScrollStrictSpan != null) {
4160 mScrollStrictSpan.finish();
4161 mScrollStrictSpan = null;
4165 private void onTouchCancel() {
4166 switch (mTouchMode) {
4167 case TOUCH_MODE_OVERSCROLL:
4168 if (mFlingRunnable == null) {
4169 mFlingRunnable = new FlingRunnable();
4171 mFlingRunnable.startSpringback();
4174 case TOUCH_MODE_OVERFLING:
4175 // Do nothing - let it play out.
4179 mTouchMode = TOUCH_MODE_REST;
4181 final View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
4182 if (motionView != null) {
4183 motionView.setPressed(false);
4185 clearScrollingCache();
4186 removeCallbacks(mPendingCheckForLongPress);
4187 recycleVelocityTracker();
4190 if (mEdgeGlowTop != null) {
4191 mEdgeGlowTop.onRelease();
4192 mEdgeGlowBottom.onRelease();
4194 mActivePointerId = INVALID_POINTER;
4198 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
4199 if (mScrollY != scrollY) {
4200 onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY);
4202 invalidateParentIfNeeded();
4209 public boolean onGenericMotionEvent(MotionEvent event) {
4210 switch (event.getAction()) {
4211 case MotionEvent.ACTION_SCROLL:
4212 final float axisValue;
4213 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4214 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
4215 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
4216 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
4221 final int delta = Math.round(axisValue * mVerticalScrollFactor);
4223 if (!trackMotionScroll(delta, delta)) {
4228 case MotionEvent.ACTION_BUTTON_PRESS:
4229 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
4230 int actionButton = event.getActionButton();
4231 if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
4232 || actionButton == MotionEvent.BUTTON_SECONDARY)
4233 && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) {
4234 if (performStylusButtonPressAction(event)) {
4235 removeCallbacks(mPendingCheckForLongPress);
4236 removeCallbacks(mPendingCheckForTap);
4243 return super.onGenericMotionEvent(event);
4247 * Initiate a fling with the given velocity.
4249 * <p>Applications can use this method to manually initiate a fling as if the user
4250 * initiated it via touch interaction.</p>
4252 * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of
4253 * content, not velocity of a touch that initiated the fling.
4255 public void fling(int velocityY) {
4256 if (mFlingRunnable == null) {
4257 mFlingRunnable = new FlingRunnable();
4259 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4260 mFlingRunnable.start(velocityY);
4264 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
4265 return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0);
4269 public void onNestedScrollAccepted(View child, View target, int axes) {
4270 super.onNestedScrollAccepted(child, target, axes);
4271 startNestedScroll(SCROLL_AXIS_VERTICAL);
4275 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
4276 int dxUnconsumed, int dyUnconsumed) {
4277 final int motionIndex = getChildCount() / 2;
4278 final View motionView = getChildAt(motionIndex);
4279 final int oldTop = motionView != null ? motionView.getTop() : 0;
4280 if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) {
4281 int myUnconsumed = dyUnconsumed;
4283 if (motionView != null) {
4284 myConsumed = motionView.getTop() - oldTop;
4285 myUnconsumed -= myConsumed;
4287 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
4292 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
4293 final int childCount = getChildCount();
4294 if (!consumed && childCount > 0 && canScrollList((int) velocityY) &&
4295 Math.abs(velocityY) > mMinimumVelocity) {
4296 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4297 if (mFlingRunnable == null) {
4298 mFlingRunnable = new FlingRunnable();
4300 if (!dispatchNestedPreFling(0, velocityY)) {
4301 mFlingRunnable.start((int) velocityY);
4305 return dispatchNestedFling(velocityX, velocityY, consumed);
4309 public void draw(Canvas canvas) {
4311 if (mEdgeGlowTop != null) {
4312 final int scrollY = mScrollY;
4313 final boolean clipToPadding = getClipToPadding();
4316 final int translateX;
4317 final int translateY;
4319 if (clipToPadding) {
4320 width = getWidth() - mPaddingLeft - mPaddingRight;
4321 height = getHeight() - mPaddingTop - mPaddingBottom;
4322 translateX = mPaddingLeft;
4323 translateY = mPaddingTop;
4326 height = getHeight();
4330 if (!mEdgeGlowTop.isFinished()) {
4331 final int restoreCount = canvas.save();
4332 canvas.clipRect(translateX, translateY,
4333 translateX + width ,translateY + mEdgeGlowTop.getMaxHeight());
4334 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
4335 canvas.translate(translateX, edgeY);
4336 mEdgeGlowTop.setSize(width, height);
4337 if (mEdgeGlowTop.draw(canvas)) {
4338 invalidateTopGlow();
4340 canvas.restoreToCount(restoreCount);
4342 if (!mEdgeGlowBottom.isFinished()) {
4343 final int restoreCount = canvas.save();
4344 canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(),
4345 translateX + width, translateY + height);
4346 final int edgeX = -width + translateX;
4347 final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess)
4348 - (clipToPadding ? mPaddingBottom : 0);
4349 canvas.translate(edgeX, edgeY);
4350 canvas.rotate(180, width, 0);
4351 mEdgeGlowBottom.setSize(width, height);
4352 if (mEdgeGlowBottom.draw(canvas)) {
4353 invalidateBottomGlow();
4355 canvas.restoreToCount(restoreCount);
4360 private void initOrResetVelocityTracker() {
4361 if (mVelocityTracker == null) {
4362 mVelocityTracker = VelocityTracker.obtain();
4364 mVelocityTracker.clear();
4368 private void initVelocityTrackerIfNotExists() {
4369 if (mVelocityTracker == null) {
4370 mVelocityTracker = VelocityTracker.obtain();
4374 private void recycleVelocityTracker() {
4375 if (mVelocityTracker != null) {
4376 mVelocityTracker.recycle();
4377 mVelocityTracker = null;
4382 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4383 if (disallowIntercept) {
4384 recycleVelocityTracker();
4386 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4390 public boolean onInterceptHoverEvent(MotionEvent event) {
4391 if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
4395 return super.onInterceptHoverEvent(event);
4399 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
4400 if (mFastScroll != null) {
4401 PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
4402 if (pointerIcon != null) {
4406 return super.onResolvePointerIcon(event, pointerIndex);
4410 public boolean onInterceptTouchEvent(MotionEvent ev) {
4411 final int actionMasked = ev.getActionMasked();
4414 if (mPositionScroller != null) {
4415 mPositionScroller.stop();
4418 if (mIsDetaching || !isAttachedToWindow()) {
4419 // Something isn't right.
4420 // Since we rely on being attached to get data set change notifications,
4421 // don't risk doing anything where we might try to resync and find things
4422 // in a bogus state.
4426 if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
4430 switch (actionMasked) {
4431 case MotionEvent.ACTION_DOWN: {
4432 int touchMode = mTouchMode;
4433 if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
4434 mMotionCorrection = 0;
4438 final int x = (int) ev.getX();
4439 final int y = (int) ev.getY();
4440 mActivePointerId = ev.getPointerId(0);
4442 int motionPosition = findMotionRow(y);
4443 if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
4444 // User clicked on an actual view (and was not stopping a fling).
4445 // Remember where the motion event started
4446 v = getChildAt(motionPosition - mFirstPosition);
4447 mMotionViewOriginalTop = v.getTop();
4450 mMotionPosition = motionPosition;
4451 mTouchMode = TOUCH_MODE_DOWN;
4452 clearScrollingCache();
4454 mLastY = Integer.MIN_VALUE;
4455 initOrResetVelocityTracker();
4456 mVelocityTracker.addMovement(ev);
4458 startNestedScroll(SCROLL_AXIS_VERTICAL);
4459 if (touchMode == TOUCH_MODE_FLING) {
4465 case MotionEvent.ACTION_MOVE: {
4466 switch (mTouchMode) {
4467 case TOUCH_MODE_DOWN:
4468 int pointerIndex = ev.findPointerIndex(mActivePointerId);
4469 if (pointerIndex == -1) {
4471 mActivePointerId = ev.getPointerId(pointerIndex);
4473 final int y = (int) ev.getY(pointerIndex);
4474 initVelocityTrackerIfNotExists();
4475 mVelocityTracker.addMovement(ev);
4476 if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
4484 case MotionEvent.ACTION_CANCEL:
4485 case MotionEvent.ACTION_UP: {
4486 mTouchMode = TOUCH_MODE_REST;
4487 mActivePointerId = INVALID_POINTER;
4488 recycleVelocityTracker();
4489 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4494 case MotionEvent.ACTION_POINTER_UP: {
4495 onSecondaryPointerUp(ev);
4503 private void onSecondaryPointerUp(MotionEvent ev) {
4504 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4505 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4506 final int pointerId = ev.getPointerId(pointerIndex);
4507 if (pointerId == mActivePointerId) {
4508 // This was our active pointer going up. Choose a new
4509 // active pointer and adjust accordingly.
4510 // TODO: Make this decision more intelligent.
4511 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4512 mMotionX = (int) ev.getX(newPointerIndex);
4513 mMotionY = (int) ev.getY(newPointerIndex);
4514 mMotionCorrection = 0;
4515 mActivePointerId = ev.getPointerId(newPointerIndex);
4523 public void addTouchables(ArrayList<View> views) {
4524 final int count = getChildCount();
4525 final int firstPosition = mFirstPosition;
4526 final ListAdapter adapter = mAdapter;
4528 if (adapter == null) {
4532 for (int i = 0; i < count; i++) {
4533 final View child = getChildAt(i);
4534 if (adapter.isEnabled(firstPosition + i)) {
4537 child.addTouchables(views);
4542 * Fires an "on scroll state changed" event to the registered
4543 * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
4544 * is fired only if the specified state is different from the previously known state.
4546 * @param newState The new scroll state.
4548 void reportScrollStateChange(int newState) {
4549 if (newState != mLastScrollState) {
4550 if (mOnScrollListener != null) {
4551 mLastScrollState = newState;
4552 mOnScrollListener.onScrollStateChanged(this, newState);
4558 * Responsible for fling behavior. Use {@link #start(int)} to
4559 * initiate a fling. Each frame of the fling is handled in {@link #run()}.
4560 * A FlingRunnable will keep re-posting itself until the fling is done.
4563 private class FlingRunnable implements Runnable {
4565 * Tracks the decay of a fling scroll
4567 private final OverScroller mScroller;
4570 * Y value reported by mScroller on the previous fling
4572 private int mLastFlingY;
4575 * If true, {@link #endFling()} will not report scroll state change to
4576 * {@link OnScrollListener#SCROLL_STATE_IDLE}.
4578 private boolean mSuppressIdleStateChangeCall;
4580 private final Runnable mCheckFlywheel = new Runnable() {
4583 final int activeId = mActivePointerId;
4584 final VelocityTracker vt = mVelocityTracker;
4585 final OverScroller scroller = mScroller;
4586 if (vt == null || activeId == INVALID_POINTER) {
4590 vt.computeCurrentVelocity(1000, mMaximumVelocity);
4591 final float yvel = -vt.getYVelocity(activeId);
4593 if (Math.abs(yvel) >= mMinimumVelocity
4594 && scroller.isScrollingInDirection(0, yvel)) {
4595 // Keep the fling alive a little longer
4596 postDelayed(this, FLYWHEEL_TIMEOUT);
4599 mTouchMode = TOUCH_MODE_SCROLL;
4600 reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
4605 private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
4608 mScroller = new OverScroller(getContext());
4611 void start(int initialVelocity) {
4612 int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
4613 mLastFlingY = initialY;
4614 mScroller.setInterpolator(null);
4615 mScroller.fling(0, initialY, 0, initialVelocity,
4616 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
4617 mTouchMode = TOUCH_MODE_FLING;
4618 mSuppressIdleStateChangeCall = false;
4619 postOnAnimation(this);
4621 if (PROFILE_FLINGING) {
4622 if (!mFlingProfilingStarted) {
4623 Debug.startMethodTracing("AbsListViewFling");
4624 mFlingProfilingStarted = true;
4628 if (mFlingStrictSpan == null) {
4629 mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling");
4633 void startSpringback() {
4634 mSuppressIdleStateChangeCall = false;
4635 if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
4636 mTouchMode = TOUCH_MODE_OVERFLING;
4638 postOnAnimation(this);
4640 mTouchMode = TOUCH_MODE_REST;
4641 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4645 void startOverfling(int initialVelocity) {
4646 mScroller.setInterpolator(null);
4647 mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0,
4648 Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
4649 mTouchMode = TOUCH_MODE_OVERFLING;
4650 mSuppressIdleStateChangeCall = false;
4652 postOnAnimation(this);
4655 void edgeReached(int delta) {
4656 mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
4657 final int overscrollMode = getOverScrollMode();
4658 if (overscrollMode == OVER_SCROLL_ALWAYS ||
4659 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
4660 mTouchMode = TOUCH_MODE_OVERFLING;
4661 final int vel = (int) mScroller.getCurrVelocity();
4663 mEdgeGlowTop.onAbsorb(vel);
4665 mEdgeGlowBottom.onAbsorb(vel);
4668 mTouchMode = TOUCH_MODE_REST;
4669 if (mPositionScroller != null) {
4670 mPositionScroller.stop();
4674 postOnAnimation(this);
4677 void startScroll(int distance, int duration, boolean linear,
4678 boolean suppressEndFlingStateChangeCall) {
4679 int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
4680 mLastFlingY = initialY;
4681 mScroller.setInterpolator(linear ? sLinearInterpolator : null);
4682 mScroller.startScroll(0, initialY, 0, distance, duration);
4683 mTouchMode = TOUCH_MODE_FLING;
4684 mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall;
4685 postOnAnimation(this);
4689 mTouchMode = TOUCH_MODE_REST;
4691 removeCallbacks(this);
4692 removeCallbacks(mCheckFlywheel);
4694 if (!mSuppressIdleStateChangeCall) {
4695 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
4697 clearScrollingCache();
4698 mScroller.abortAnimation();
4700 if (mFlingStrictSpan != null) {
4701 mFlingStrictSpan.finish();
4702 mFlingStrictSpan = null;
4706 void flywheelTouch() {
4707 postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
4712 switch (mTouchMode) {
4717 case TOUCH_MODE_SCROLL:
4718 if (mScroller.isFinished()) {
4722 case TOUCH_MODE_FLING: {
4727 if (mItemCount == 0 || getChildCount() == 0) {
4732 final OverScroller scroller = mScroller;
4733 boolean more = scroller.computeScrollOffset();
4734 final int y = scroller.getCurrY();
4736 // Flip sign to convert finger direction to list items direction
4737 // (e.g. finger moving down means list is moving towards the top)
4738 int delta = mLastFlingY - y;
4740 // Pretend that each frame of a fling scroll is a touch scroll
4742 // List is moving towards the top. Use first view as mMotionPosition
4743 mMotionPosition = mFirstPosition;
4744 final View firstView = getChildAt(0);
4745 mMotionViewOriginalTop = firstView.getTop();
4747 // Don't fling more than 1 screen
4748 delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
4750 // List is moving towards the bottom. Use last view as mMotionPosition
4751 int offsetToLast = getChildCount() - 1;
4752 mMotionPosition = mFirstPosition + offsetToLast;
4754 final View lastView = getChildAt(offsetToLast);
4755 mMotionViewOriginalTop = lastView.getTop();
4757 // Don't fling more than 1 screen
4758 delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
4761 // Check to see if we have bumped into the scroll limit
4762 View motionView = getChildAt(mMotionPosition - mFirstPosition);
4764 if (motionView != null) {
4765 oldTop = motionView.getTop();
4768 // Don't stop just because delta is zero (it could have been rounded)
4769 final boolean atEdge = trackMotionScroll(delta, delta);
4770 final boolean atEnd = atEdge && (delta != 0);
4772 if (motionView != null) {
4773 // Tweak the scroll for how far we overshot
4774 int overshoot = -(delta - (motionView.getTop() - oldTop));
4775 overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
4776 0, mOverflingDistance, false);
4784 if (more && !atEnd) {
4785 if (atEdge) invalidate();
4787 postOnAnimation(this);
4791 if (PROFILE_FLINGING) {
4792 if (mFlingProfilingStarted) {
4793 Debug.stopMethodTracing();
4794 mFlingProfilingStarted = false;
4797 if (mFlingStrictSpan != null) {
4798 mFlingStrictSpan.finish();
4799 mFlingStrictSpan = null;
4806 case TOUCH_MODE_OVERFLING: {
4807 final OverScroller scroller = mScroller;
4808 if (scroller.computeScrollOffset()) {
4809 final int scrollY = mScrollY;
4810 final int currY = scroller.getCurrY();
4811 final int deltaY = currY - scrollY;
4812 if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
4813 0, mOverflingDistance, false)) {
4814 final boolean crossDown = scrollY <= 0 && currY > 0;
4815 final boolean crossUp = scrollY >= 0 && currY < 0;
4816 if (crossDown || crossUp) {
4817 int velocity = (int) scroller.getCurrVelocity();
4818 if (crossUp) velocity = -velocity;
4820 // Don't flywheel from this; we're just continuing things.
4821 scroller.abortAnimation();
4828 postOnAnimation(this);
4840 * The amount of friction applied to flings. The default value
4841 * is {@link ViewConfiguration#getScrollFriction}.
4843 public void setFriction(float friction) {
4844 if (mFlingRunnable == null) {
4845 mFlingRunnable = new FlingRunnable();
4847 mFlingRunnable.mScroller.setFriction(friction);
4851 * Sets a scale factor for the fling velocity. The initial scale
4854 * @param scale The scale factor to multiply the velocity by.
4856 public void setVelocityScale(float scale) {
4857 mVelocityScale = scale;
4861 * Override this for better control over position scrolling.
4863 AbsPositionScroller createPositionScroller() {
4864 return new PositionScroller();
4868 * Smoothly scroll to the specified adapter position. The view will
4869 * scroll such that the indicated position is displayed.
4870 * @param position Scroll to this adapter position.
4872 public void smoothScrollToPosition(int position) {
4873 if (mPositionScroller == null) {
4874 mPositionScroller = createPositionScroller();
4876 mPositionScroller.start(position);
4880 * Smoothly scroll to the specified adapter position. The view will scroll
4881 * such that the indicated position is displayed <code>offset</code> pixels below
4882 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4883 * the first or last item beyond the boundaries of the list) it will get as close
4884 * as possible. The scroll will take <code>duration</code> milliseconds to complete.
4886 * @param position Position to scroll to
4887 * @param offset Desired distance in pixels of <code>position</code> from the top
4888 * of the view when scrolling is finished
4889 * @param duration Number of milliseconds to use for the scroll
4891 public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
4892 if (mPositionScroller == null) {
4893 mPositionScroller = createPositionScroller();
4895 mPositionScroller.startWithOffset(position, offset, duration);
4899 * Smoothly scroll to the specified adapter position. The view will scroll
4900 * such that the indicated position is displayed <code>offset</code> pixels below
4901 * the top edge of the view. If this is impossible, (e.g. the offset would scroll
4902 * the first or last item beyond the boundaries of the list) it will get as close
4905 * @param position Position to scroll to
4906 * @param offset Desired distance in pixels of <code>position</code> from the top
4907 * of the view when scrolling is finished
4909 public void smoothScrollToPositionFromTop(int position, int offset) {
4910 if (mPositionScroller == null) {
4911 mPositionScroller = createPositionScroller();
4913 mPositionScroller.startWithOffset(position, offset);
4917 * Smoothly scroll to the specified adapter position. The view will
4918 * scroll such that the indicated position is displayed, but it will
4919 * stop early if scrolling further would scroll boundPosition out of
4922 * @param position Scroll to this adapter position.
4923 * @param boundPosition Do not scroll if it would move this adapter
4924 * position out of view.
4926 public void smoothScrollToPosition(int position, int boundPosition) {
4927 if (mPositionScroller == null) {
4928 mPositionScroller = createPositionScroller();
4930 mPositionScroller.start(position, boundPosition);
4934 * Smoothly scroll by distance pixels over duration milliseconds.
4935 * @param distance Distance to scroll in pixels.
4936 * @param duration Duration of the scroll animation in milliseconds.
4938 public void smoothScrollBy(int distance, int duration) {
4939 smoothScrollBy(distance, duration, false, false);
4942 void smoothScrollBy(int distance, int duration, boolean linear,
4943 boolean suppressEndFlingStateChangeCall) {
4944 if (mFlingRunnable == null) {
4945 mFlingRunnable = new FlingRunnable();
4948 // No sense starting to scroll if we're not going anywhere
4949 final int firstPos = mFirstPosition;
4950 final int childCount = getChildCount();
4951 final int lastPos = firstPos + childCount;
4952 final int topLimit = getPaddingTop();
4953 final int bottomLimit = getHeight() - getPaddingBottom();
4955 if (distance == 0 || mItemCount == 0 || childCount == 0 ||
4956 (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) ||
4957 (lastPos == mItemCount &&
4958 getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) {
4959 mFlingRunnable.endFling();
4960 if (mPositionScroller != null) {
4961 mPositionScroller.stop();
4964 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
4965 mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall);
4970 * Allows RemoteViews to scroll relatively to a position.
4972 void smoothScrollByOffset(int position) {
4975 index = getFirstVisiblePosition();
4976 } else if (position > 0) {
4977 index = getLastVisiblePosition();
4981 View child = getChildAt(index - getFirstVisiblePosition());
4982 if (child != null) {
4983 Rect visibleRect = new Rect();
4984 if (child.getGlobalVisibleRect(visibleRect)) {
4985 // the child is partially visible
4986 int childRectArea = child.getWidth() * child.getHeight();
4987 int visibleRectArea = visibleRect.width() * visibleRect.height();
4988 float visibleArea = (visibleRectArea / (float) childRectArea);
4989 final float visibleThreshold = 0.75f;
4990 if ((position < 0) && (visibleArea < visibleThreshold)) {
4991 // the top index is not perceivably visible so offset
4992 // to account for showing that top index as well
4994 } else if ((position > 0) && (visibleArea < visibleThreshold)) {
4995 // the bottom index is not perceivably visible so offset
4996 // to account for showing that bottom index as well
5000 smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
5005 private void createScrollingCache() {
5006 if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
5007 setChildrenDrawnWithCacheEnabled(true);
5008 setChildrenDrawingCacheEnabled(true);
5009 mCachingStarted = mCachingActive = true;
5013 private void clearScrollingCache() {
5014 if (!isHardwareAccelerated()) {
5015 if (mClearScrollingCache == null) {
5016 mClearScrollingCache = new Runnable() {
5019 if (mCachingStarted) {
5020 mCachingStarted = mCachingActive = false;
5021 setChildrenDrawnWithCacheEnabled(false);
5022 if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
5023 setChildrenDrawingCacheEnabled(false);
5025 if (!isAlwaysDrawnWithCacheEnabled()) {
5032 post(mClearScrollingCache);
5037 * Scrolls the list items within the view by a specified number of pixels.
5039 * <p>The actual amount of scroll is capped by the list content viewport height
5040 * which is the list height minus top and bottom paddings minus one pixel.</p>
5042 * @param y the amount of pixels to scroll by vertically
5043 * @see #canScrollList(int)
5045 public void scrollListBy(int y) {
5046 trackMotionScroll(-y, -y);
5050 * Check if the items in the list can be scrolled in a certain direction.
5052 * @param direction Negative to check scrolling up, positive to check
5054 * @return true if the list can be scrolled in the specified direction,
5056 * @see #scrollListBy(int)
5058 public boolean canScrollList(int direction) {
5059 final int childCount = getChildCount();
5060 if (childCount == 0) {
5064 final int firstPosition = mFirstPosition;
5065 final Rect listPadding = mListPadding;
5066 if (direction > 0) {
5067 final int lastBottom = getChildAt(childCount - 1).getBottom();
5068 final int lastPosition = firstPosition + childCount;
5069 return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
5071 final int firstTop = getChildAt(0).getTop();
5072 return firstPosition > 0 || firstTop < listPadding.top;
5077 * Track a motion scroll
5079 * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
5080 * began. Positive numbers mean the user's finger is moving down the screen.
5081 * @param incrementalDeltaY Change in deltaY from the previous event.
5082 * @return true if we're already at the beginning/end of the list and have nothing to do.
5084 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
5085 final int childCount = getChildCount();
5086 if (childCount == 0) {
5090 final int firstTop = getChildAt(0).getTop();
5091 final int lastBottom = getChildAt(childCount - 1).getBottom();
5093 final Rect listPadding = mListPadding;
5095 // "effective padding" In this case is the amount of padding that affects
5096 // how much space should not be filled by items. If we don't clip to padding
5097 // there is no effective padding.
5098 int effectivePaddingTop = 0;
5099 int effectivePaddingBottom = 0;
5100 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5101 effectivePaddingTop = listPadding.top;
5102 effectivePaddingBottom = listPadding.bottom;
5105 // FIXME account for grid vertical spacing too?
5106 final int spaceAbove = effectivePaddingTop - firstTop;
5107 final int end = getHeight() - effectivePaddingBottom;
5108 final int spaceBelow = lastBottom - end;
5110 final int height = getHeight() - mPaddingBottom - mPaddingTop;
5112 deltaY = Math.max(-(height - 1), deltaY);
5114 deltaY = Math.min(height - 1, deltaY);
5117 if (incrementalDeltaY < 0) {
5118 incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
5120 incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
5123 final int firstPosition = mFirstPosition;
5125 // Update our guesses for where the first and last views are
5126 if (firstPosition == 0) {
5127 mFirstPositionDistanceGuess = firstTop - listPadding.top;
5129 mFirstPositionDistanceGuess += incrementalDeltaY;
5131 if (firstPosition + childCount == mItemCount) {
5132 mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
5134 mLastPositionDistanceGuess += incrementalDeltaY;
5137 final boolean cannotScrollDown = (firstPosition == 0 &&
5138 firstTop >= listPadding.top && incrementalDeltaY >= 0);
5139 final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
5140 lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
5142 if (cannotScrollDown || cannotScrollUp) {
5143 return incrementalDeltaY != 0;
5146 final boolean down = incrementalDeltaY < 0;
5148 final boolean inTouchMode = isInTouchMode();
5153 final int headerViewsCount = getHeaderViewsCount();
5154 final int footerViewsStart = mItemCount - getFooterViewsCount();
5160 int top = -incrementalDeltaY;
5161 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5162 top += listPadding.top;
5164 for (int i = 0; i < childCount; i++) {
5165 final View child = getChildAt(i);
5166 if (child.getBottom() >= top) {
5170 int position = firstPosition + i;
5171 if (position >= headerViewsCount && position < footerViewsStart) {
5172 // The view will be rebound to new data, clear any
5173 // system-managed transient state.
5174 child.clearAccessibilityFocus();
5175 mRecycler.addScrapView(child, position);
5180 int bottom = getHeight() - incrementalDeltaY;
5181 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
5182 bottom -= listPadding.bottom;
5184 for (int i = childCount - 1; i >= 0; i--) {
5185 final View child = getChildAt(i);
5186 if (child.getTop() <= bottom) {
5191 int position = firstPosition + i;
5192 if (position >= headerViewsCount && position < footerViewsStart) {
5193 // The view will be rebound to new data, clear any
5194 // system-managed transient state.
5195 child.clearAccessibilityFocus();
5196 mRecycler.addScrapView(child, position);
5202 mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
5204 mBlockLayoutRequests = true;
5207 detachViewsFromParent(start, count);
5208 mRecycler.removeSkippedScrap();
5211 // invalidate before moving the children to avoid unnecessary invalidate
5212 // calls to bubble up from the children all the way to the top
5213 if (!awakenScrollBars()) {
5217 offsetChildrenTopAndBottom(incrementalDeltaY);
5220 mFirstPosition += count;
5223 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
5224 if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
5228 mRecycler.fullyDetachScrapViews();
5229 if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
5230 final int childIndex = mSelectedPosition - mFirstPosition;
5231 if (childIndex >= 0 && childIndex < getChildCount()) {
5232 positionSelector(mSelectedPosition, getChildAt(childIndex));
5234 } else if (mSelectorPosition != INVALID_POSITION) {
5235 final int childIndex = mSelectorPosition - mFirstPosition;
5236 if (childIndex >= 0 && childIndex < getChildCount()) {
5237 positionSelector(INVALID_POSITION, getChildAt(childIndex));
5240 mSelectorRect.setEmpty();
5243 mBlockLayoutRequests = false;
5245 invokeOnItemScrollListener();
5251 * Returns the number of header views in the list. Header views are special views
5252 * at the top of the list that should not be recycled during a layout.
5254 * @return The number of header views, 0 in the default implementation.
5256 int getHeaderViewsCount() {
5261 * Returns the number of footer views in the list. Footer views are special views
5262 * at the bottom of the list that should not be recycled during a layout.
5264 * @return The number of footer views, 0 in the default implementation.
5266 int getFooterViewsCount() {
5271 * Fills the gap left open by a touch-scroll. During a touch scroll, children that
5272 * remain on screen are shifted and the other ones are discarded. The role of this
5273 * method is to fill the gap thus created by performing a partial layout in the
5276 * @param down true if the scroll is going down, false if it is going up
5278 abstract void fillGap(boolean down);
5280 void hideSelector() {
5281 if (mSelectedPosition != INVALID_POSITION) {
5282 if (mLayoutMode != LAYOUT_SPECIFIC) {
5283 mResurrectToPosition = mSelectedPosition;
5285 if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
5286 mResurrectToPosition = mNextSelectedPosition;
5288 setSelectedPositionInt(INVALID_POSITION);
5289 setNextSelectedPositionInt(INVALID_POSITION);
5295 * @return A position to select. First we try mSelectedPosition. If that has been clobbered by
5296 * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range
5297 * of items available in the adapter
5299 int reconcileSelectedPosition() {
5300 int position = mSelectedPosition;
5302 position = mResurrectToPosition;
5304 position = Math.max(0, position);
5305 position = Math.min(position, mItemCount - 1);
5310 * Find the row closest to y. This row will be used as the motion row when scrolling
5312 * @param y Where the user touched
5313 * @return The position of the first (or only) item in the row containing y
5315 abstract int findMotionRow(int y);
5318 * Find the row closest to y. This row will be used as the motion row when scrolling.
5320 * @param y Where the user touched
5321 * @return The position of the first (or only) item in the row closest to y
5323 int findClosestMotionRow(int y) {
5324 final int childCount = getChildCount();
5325 if (childCount == 0) {
5326 return INVALID_POSITION;
5329 final int motionRow = findMotionRow(y);
5330 return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
5334 * Causes all the views to be rebuilt and redrawn.
5336 public void invalidateViews() {
5337 mDataChanged = true;
5338 rememberSyncState();
5344 * If there is a selection returns false.
5345 * Otherwise resurrects the selection and returns true if resurrected.
5347 boolean resurrectSelectionIfNeeded() {
5348 if (mSelectedPosition < 0 && resurrectSelection()) {
5349 updateSelectorState();
5356 * Makes the item at the supplied position selected.
5358 * @param position the position of the new selection
5360 abstract void setSelectionInt(int position);
5363 * Attempt to bring the selection back if the user is switching from touch
5365 * @return Whether selection was set to something.
5367 boolean resurrectSelection() {
5368 final int childCount = getChildCount();
5370 if (childCount <= 0) {
5374 int selectedTop = 0;
5376 int childrenTop = mListPadding.top;
5377 int childrenBottom = mBottom - mTop - mListPadding.bottom;
5378 final int firstPosition = mFirstPosition;
5379 final int toPosition = mResurrectToPosition;
5380 boolean down = true;
5382 if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
5383 selectedPos = toPosition;
5385 final View selected = getChildAt(selectedPos - mFirstPosition);
5386 selectedTop = selected.getTop();
5387 int selectedBottom = selected.getBottom();
5389 // We are scrolled, don't get in the fade
5390 if (selectedTop < childrenTop) {
5391 selectedTop = childrenTop + getVerticalFadingEdgeLength();
5392 } else if (selectedBottom > childrenBottom) {
5393 selectedTop = childrenBottom - selected.getMeasuredHeight()
5394 - getVerticalFadingEdgeLength();
5397 if (toPosition < firstPosition) {
5398 // Default to selecting whatever is first
5399 selectedPos = firstPosition;
5400 for (int i = 0; i < childCount; i++) {
5401 final View v = getChildAt(i);
5402 final int top = v.getTop();
5405 // Remember the position of the first item
5407 // See if we are scrolled at all
5408 if (firstPosition > 0 || top < childrenTop) {
5409 // If we are scrolled, don't select anything that is
5410 // in the fade region
5411 childrenTop += getVerticalFadingEdgeLength();
5414 if (top >= childrenTop) {
5415 // Found a view whose top is fully visisble
5416 selectedPos = firstPosition + i;
5422 final int itemCount = mItemCount;
5424 selectedPos = firstPosition + childCount - 1;
5426 for (int i = childCount - 1; i >= 0; i--) {
5427 final View v = getChildAt(i);
5428 final int top = v.getTop();
5429 final int bottom = v.getBottom();
5431 if (i == childCount - 1) {
5433 if (firstPosition + childCount < itemCount || bottom > childrenBottom) {
5434 childrenBottom -= getVerticalFadingEdgeLength();
5438 if (bottom <= childrenBottom) {
5439 selectedPos = firstPosition + i;
5447 mResurrectToPosition = INVALID_POSITION;
5448 removeCallbacks(mFlingRunnable);
5449 if (mPositionScroller != null) {
5450 mPositionScroller.stop();
5452 mTouchMode = TOUCH_MODE_REST;
5453 clearScrollingCache();
5454 mSpecificTop = selectedTop;
5455 selectedPos = lookForSelectablePosition(selectedPos, down);
5456 if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
5457 mLayoutMode = LAYOUT_SPECIFIC;
5458 updateSelectorState();
5459 setSelectionInt(selectedPos);
5460 invokeOnItemScrollListener();
5462 selectedPos = INVALID_POSITION;
5464 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
5466 return selectedPos >= 0;
5469 void confirmCheckedPositionsById() {
5470 // Clear out the positional check states, we'll rebuild it below from IDs.
5471 mCheckStates.clear();
5473 boolean checkedCountChanged = false;
5474 for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
5475 final long id = mCheckedIdStates.keyAt(checkedIndex);
5476 final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
5478 final long lastPosId = mAdapter.getItemId(lastPos);
5479 if (id != lastPosId) {
5480 // Look around to see if the ID is nearby. If not, uncheck it.
5481 final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
5482 final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
5483 boolean found = false;
5484 for (int searchPos = start; searchPos < end; searchPos++) {
5485 final long searchId = mAdapter.getItemId(searchPos);
5486 if (id == searchId) {
5488 mCheckStates.put(searchPos, true);
5489 mCheckedIdStates.setValueAt(checkedIndex, searchPos);
5495 mCheckedIdStates.delete(id);
5497 mCheckedItemCount--;
5498 checkedCountChanged = true;
5499 if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
5500 mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
5501 lastPos, id, false);
5505 mCheckStates.put(lastPos, true);
5509 if (checkedCountChanged && mChoiceActionMode != null) {
5510 mChoiceActionMode.invalidate();
5515 protected void handleDataChanged() {
5516 int count = mItemCount;
5517 int lastHandledItemCount = mLastHandledItemCount;
5518 mLastHandledItemCount = mItemCount;
5520 if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
5521 confirmCheckedPositionsById();
5524 // TODO: In the future we can recycle these views based on stable ID instead.
5525 mRecycler.clearTransientStateViews();
5531 // Find the row we are supposed to sync to
5533 // Update this first, since setNextSelectedPositionInt inspects it
5535 mPendingSync = null;
5537 if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
5538 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5540 } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) {
5541 if (mForceTranscriptScroll) {
5542 mForceTranscriptScroll = false;
5543 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5546 final int childCount = getChildCount();
5547 final int listBottom = getHeight() - getPaddingBottom();
5548 final View lastChild = getChildAt(childCount - 1);
5549 final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom;
5550 if (mFirstPosition + childCount >= lastHandledItemCount &&
5551 lastBottom <= listBottom) {
5552 mLayoutMode = LAYOUT_FORCE_BOTTOM;
5555 // Something new came in and we didn't scroll; give the user a clue that
5556 // there's something new.
5560 switch (mSyncMode) {
5561 case SYNC_SELECTED_POSITION:
5562 if (isInTouchMode()) {
5563 // We saved our state when not in touch mode. (We know this because
5564 // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
5565 // restore in touch mode. Just leave mSyncPosition as it is (possibly
5566 // adjusting if the available range changed) and return.
5567 mLayoutMode = LAYOUT_SYNC;
5568 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5572 // See if we can find a position in the new data with the same
5573 // id as the old selection. This will change mSyncPosition.
5574 newPos = findSyncPosition();
5576 // Found it. Now verify that new selection is still selectable
5577 selectablePos = lookForSelectablePosition(newPos, true);
5578 if (selectablePos == newPos) {
5579 // Same row id is selected
5580 mSyncPosition = newPos;
5582 if (mSyncHeight == getHeight()) {
5583 // If we are at the same height as when we saved state, try
5584 // to restore the scroll position too.
5585 mLayoutMode = LAYOUT_SYNC;
5587 // We are not the same height as when the selection was saved, so
5588 // don't try to restore the exact position
5589 mLayoutMode = LAYOUT_SET_SELECTION;
5592 // Restore selection
5593 setNextSelectedPositionInt(newPos);
5599 case SYNC_FIRST_POSITION:
5600 // Leave mSyncPosition as it is -- just pin to available range
5601 mLayoutMode = LAYOUT_SYNC;
5602 mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1);
5608 if (!isInTouchMode()) {
5609 // We couldn't find matching data -- try to use the same position
5610 newPos = getSelectedItemPosition();
5612 // Pin position to the available range
5613 if (newPos >= count) {
5620 // Make sure we select something selectable -- first look down
5621 selectablePos = lookForSelectablePosition(newPos, true);
5623 if (selectablePos >= 0) {
5624 setNextSelectedPositionInt(selectablePos);
5627 // Looking down didn't work -- try looking up
5628 selectablePos = lookForSelectablePosition(newPos, false);
5629 if (selectablePos >= 0) {
5630 setNextSelectedPositionInt(selectablePos);
5636 // We already know where we want to resurrect the selection
5637 if (mResurrectToPosition >= 0) {
5644 // Nothing is selected. Give up and reset everything.
5645 mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
5646 mSelectedPosition = INVALID_POSITION;
5647 mSelectedRowId = INVALID_ROW_ID;
5648 mNextSelectedPosition = INVALID_POSITION;
5649 mNextSelectedRowId = INVALID_ROW_ID;
5651 mPendingSync = null;
5652 mSelectorPosition = INVALID_POSITION;
5653 checkSelectionChanged();
5657 protected void onDisplayHint(int hint) {
5658 super.onDisplayHint(hint);
5661 if (mPopup != null && mPopup.isShowing()) {
5666 if (mFiltered && mPopup != null && !mPopup.isShowing()) {
5671 mPopupHidden = hint == INVISIBLE;
5675 * Removes the filter window
5677 private void dismissPopup() {
5678 if (mPopup != null) {
5684 * Shows the filter window
5686 private void showPopup() {
5687 // Make sure we have a window before showing the popup
5688 if (getWindowVisibility() == View.VISIBLE) {
5689 createTextFilter(true);
5691 // Make sure we get focus if we are showing the popup
5696 private void positionPopup() {
5697 int screenHeight = getResources().getDisplayMetrics().heightPixels;
5698 final int[] xy = new int[2];
5699 getLocationOnScreen(xy);
5700 // TODO: The 20 below should come from the theme
5701 // TODO: And the gravity should be defined in the theme as well
5702 final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
5703 if (!mPopup.isShowing()) {
5704 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
5707 mPopup.update(xy[0], bottomGap, -1, -1);
5712 * What is the distance between the source and destination rectangles given the direction of
5713 * focus navigation between them? The direction basically helps figure out more quickly what is
5714 * self evident by the relationship between the rects...
5716 * @param source the source rectangle
5717 * @param dest the destination rectangle
5718 * @param direction the direction
5719 * @return the distance between the rectangles
5721 static int getDistance(Rect source, Rect dest, int direction) {
5722 int sX, sY; // source x, y
5723 int dX, dY; // dest x, y
5724 switch (direction) {
5725 case View.FOCUS_RIGHT:
5727 sY = source.top + source.height() / 2;
5729 dY = dest.top + dest.height() / 2;
5731 case View.FOCUS_DOWN:
5732 sX = source.left + source.width() / 2;
5734 dX = dest.left + dest.width() / 2;
5737 case View.FOCUS_LEFT:
5739 sY = source.top + source.height() / 2;
5741 dY = dest.top + dest.height() / 2;
5744 sX = source.left + source.width() / 2;
5746 dX = dest.left + dest.width() / 2;
5749 case View.FOCUS_FORWARD:
5750 case View.FOCUS_BACKWARD:
5751 sX = source.right + source.width() / 2;
5752 sY = source.top + source.height() / 2;
5753 dX = dest.left + dest.width() / 2;
5754 dY = dest.top + dest.height() / 2;
5757 throw new IllegalArgumentException("direction must be one of "
5758 + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
5759 + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
5761 int deltaX = dX - sX;
5762 int deltaY = dY - sY;
5763 return deltaY * deltaY + deltaX * deltaX;
5767 protected boolean isInFilterMode() {
5772 * Sends a key to the text filter window
5774 * @param keyCode The keycode for the event
5775 * @param event The actual key event
5777 * @return True if the text filter handled the event, false otherwise.
5779 boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
5780 if (!acceptFilter()) {
5784 boolean handled = false;
5785 boolean okToSend = true;
5787 case KeyEvent.KEYCODE_DPAD_UP:
5788 case KeyEvent.KEYCODE_DPAD_DOWN:
5789 case KeyEvent.KEYCODE_DPAD_LEFT:
5790 case KeyEvent.KEYCODE_DPAD_RIGHT:
5791 case KeyEvent.KEYCODE_DPAD_CENTER:
5792 case KeyEvent.KEYCODE_ENTER:
5795 case KeyEvent.KEYCODE_BACK:
5796 if (mFiltered && mPopup != null && mPopup.isShowing()) {
5797 if (event.getAction() == KeyEvent.ACTION_DOWN
5798 && event.getRepeatCount() == 0) {
5799 KeyEvent.DispatcherState state = getKeyDispatcherState();
5800 if (state != null) {
5801 state.startTracking(event, this);
5804 } else if (event.getAction() == KeyEvent.ACTION_UP
5805 && event.isTracking() && !event.isCanceled()) {
5807 mTextFilter.setText("");
5812 case KeyEvent.KEYCODE_SPACE:
5813 // Only send spaces once we are filtered
5814 okToSend = mFiltered;
5819 createTextFilter(true);
5821 KeyEvent forwardEvent = event;
5822 if (forwardEvent.getRepeatCount() > 0) {
5823 forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0);
5826 int action = event.getAction();
5828 case KeyEvent.ACTION_DOWN:
5829 handled = mTextFilter.onKeyDown(keyCode, forwardEvent);
5832 case KeyEvent.ACTION_UP:
5833 handled = mTextFilter.onKeyUp(keyCode, forwardEvent);
5836 case KeyEvent.ACTION_MULTIPLE:
5837 handled = mTextFilter.onKeyMultiple(keyCode, count, event);
5845 * Return an InputConnection for editing of the filter text.
5848 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5849 if (isTextFilterEnabled()) {
5850 if (mPublicInputConnection == null) {
5851 mDefInputConnection = new BaseInputConnection(this, false);
5852 mPublicInputConnection = new InputConnectionWrapper(outAttrs);
5854 outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER;
5855 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
5856 return mPublicInputConnection;
5861 private class InputConnectionWrapper implements InputConnection {
5862 private final EditorInfo mOutAttrs;
5863 private InputConnection mTarget;
5865 public InputConnectionWrapper(EditorInfo outAttrs) {
5866 mOutAttrs = outAttrs;
5869 private InputConnection getTarget() {
5870 if (mTarget == null) {
5871 mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs);
5877 public boolean reportFullscreenMode(boolean enabled) {
5878 // Use our own input connection, since it is
5879 // the "real" one the IME is talking with.
5880 return mDefInputConnection.reportFullscreenMode(enabled);
5884 public boolean performEditorAction(int editorAction) {
5885 // The editor is off in its own window; we need to be
5886 // the one that does this.
5887 if (editorAction == EditorInfo.IME_ACTION_DONE) {
5888 InputMethodManager imm =
5889 getContext().getSystemService(InputMethodManager.class);
5891 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5899 public boolean sendKeyEvent(KeyEvent event) {
5900 // Use our own input connection, since the filter
5901 // text view may not be shown in a window so has
5902 // no ViewAncestor to dispatch events with.
5903 return mDefInputConnection.sendKeyEvent(event);
5907 public CharSequence getTextBeforeCursor(int n, int flags) {
5908 if (mTarget == null) return "";
5909 return mTarget.getTextBeforeCursor(n, flags);
5913 public CharSequence getTextAfterCursor(int n, int flags) {
5914 if (mTarget == null) return "";
5915 return mTarget.getTextAfterCursor(n, flags);
5919 public CharSequence getSelectedText(int flags) {
5920 if (mTarget == null) return "";
5921 return mTarget.getSelectedText(flags);
5925 public int getCursorCapsMode(int reqModes) {
5926 if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
5927 return mTarget.getCursorCapsMode(reqModes);
5931 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
5932 return getTarget().getExtractedText(request, flags);
5936 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
5937 return getTarget().deleteSurroundingText(beforeLength, afterLength);
5941 public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
5942 return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
5946 public boolean setComposingText(CharSequence text, int newCursorPosition) {
5947 return getTarget().setComposingText(text, newCursorPosition);
5951 public boolean setComposingRegion(int start, int end) {
5952 return getTarget().setComposingRegion(start, end);
5956 public boolean finishComposingText() {
5957 return mTarget == null || mTarget.finishComposingText();
5961 public boolean commitText(CharSequence text, int newCursorPosition) {
5962 return getTarget().commitText(text, newCursorPosition);
5966 public boolean commitCompletion(CompletionInfo text) {
5967 return getTarget().commitCompletion(text);
5971 public boolean commitCorrection(CorrectionInfo correctionInfo) {
5972 return getTarget().commitCorrection(correctionInfo);
5976 public boolean setSelection(int start, int end) {
5977 return getTarget().setSelection(start, end);
5981 public boolean performContextMenuAction(int id) {
5982 return getTarget().performContextMenuAction(id);
5986 public boolean beginBatchEdit() {
5987 return getTarget().beginBatchEdit();
5991 public boolean endBatchEdit() {
5992 return getTarget().endBatchEdit();
5996 public boolean clearMetaKeyStates(int states) {
5997 return getTarget().clearMetaKeyStates(states);
6001 public boolean performPrivateCommand(String action, Bundle data) {
6002 return getTarget().performPrivateCommand(action, data);
6006 public boolean requestCursorUpdates(int cursorUpdateMode) {
6007 return getTarget().requestCursorUpdates(cursorUpdateMode);
6011 public Handler getHandler() {
6012 return getTarget().getHandler();
6016 public void closeConnection() {
6017 getTarget().closeConnection();
6021 public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
6022 return getTarget().commitContent(inputContentInfo, flags, opts);
6027 * For filtering we proxy an input connection to an internal text editor,
6028 * and this allows the proxying to happen.
6031 public boolean checkInputConnectionProxy(View view) {
6032 return view == mTextFilter;
6036 * Creates the window for the text filter and populates it with an EditText field;
6038 * @param animateEntrance true if the window should appear with an animation
6040 private void createTextFilter(boolean animateEntrance) {
6041 if (mPopup == null) {
6042 PopupWindow p = new PopupWindow(getContext());
6043 p.setFocusable(false);
6044 p.setTouchable(false);
6045 p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
6046 p.setContentView(getTextFilterInput());
6047 p.setWidth(LayoutParams.WRAP_CONTENT);
6048 p.setHeight(LayoutParams.WRAP_CONTENT);
6049 p.setBackgroundDrawable(null);
6051 getViewTreeObserver().addOnGlobalLayoutListener(this);
6052 mGlobalLayoutListenerAddedFilter = true;
6054 if (animateEntrance) {
6055 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
6057 mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore);
6061 private EditText getTextFilterInput() {
6062 if (mTextFilter == null) {
6063 final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
6064 mTextFilter = (EditText) layoutInflater.inflate(
6065 com.android.internal.R.layout.typing_filter, null);
6066 // For some reason setting this as the "real" input type changes
6067 // the text view in some way that it doesn't work, and I don't
6068 // want to figure out why this is.
6069 mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
6070 | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
6071 mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
6072 mTextFilter.addTextChangedListener(this);
6078 * Clear the text filter.
6080 public void clearTextFilter() {
6082 getTextFilterInput().setText("");
6084 if (mPopup != null && mPopup.isShowing()) {
6091 * Returns if the ListView currently has a text filter.
6093 public boolean hasTextFilter() {
6098 public void onGlobalLayout() {
6100 // Show the popup if we are filtered
6101 if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
6105 // Hide the popup when we are no longer visible
6106 if (mPopup != null && mPopup.isShowing()) {
6114 * For our text watcher that is associated with the text filter. Does
6118 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6122 * For our text watcher that is associated with the text filter. Performs
6123 * the actual filtering as the text changes, and takes care of hiding and
6124 * showing the popup displaying the currently entered filter text.
6127 public void onTextChanged(CharSequence s, int start, int before, int count) {
6128 if (isTextFilterEnabled()) {
6129 createTextFilter(true);
6130 int length = s.length();
6131 boolean showing = mPopup.isShowing();
6132 if (!showing && length > 0) {
6133 // Show the filter popup if necessary
6136 } else if (showing && length == 0) {
6137 // Remove the filter popup if the user has cleared all text
6141 if (mAdapter instanceof Filterable) {
6142 Filter f = ((Filterable) mAdapter).getFilter();
6143 // Filter should not be null when we reach this part
6147 throw new IllegalStateException("You cannot call onTextChanged with a non "
6148 + "filterable adapter");
6155 * For our text watcher that is associated with the text filter. Does
6159 public void afterTextChanged(Editable s) {
6163 public void onFilterComplete(int count) {
6164 if (mSelectedPosition < 0 && count > 0) {
6165 mResurrectToPosition = INVALID_POSITION;
6166 resurrectSelection();
6171 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
6172 return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
6173 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
6177 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
6178 return new LayoutParams(p);
6182 public LayoutParams generateLayoutParams(AttributeSet attrs) {
6183 return new AbsListView.LayoutParams(getContext(), attrs);
6187 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
6188 return p instanceof AbsListView.LayoutParams;
6192 * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll
6193 * to the bottom to show new items.
6195 * @param mode the transcript mode to set
6197 * @see #TRANSCRIPT_MODE_DISABLED
6198 * @see #TRANSCRIPT_MODE_NORMAL
6199 * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL
6201 public void setTranscriptMode(int mode) {
6202 mTranscriptMode = mode;
6206 * Returns the current transcript mode.
6208 * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or
6209 * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL}
6211 public int getTranscriptMode() {
6212 return mTranscriptMode;
6216 public int getSolidColor() {
6217 return mCacheColorHint;
6221 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6222 * on top of a solid, single-color, opaque background.
6224 * Zero means that what's behind this object is translucent (non solid) or is not made of a
6225 * single color. This hint will not affect any existing background drawable set on this view (
6226 * typically set via {@link #setBackgroundDrawable(Drawable)}).
6228 * @param color The background color
6230 public void setCacheColorHint(@ColorInt int color) {
6231 if (color != mCacheColorHint) {
6232 mCacheColorHint = color;
6233 int count = getChildCount();
6234 for (int i = 0; i < count; i++) {
6235 getChildAt(i).setDrawingCacheBackgroundColor(color);
6237 mRecycler.setCacheColorHint(color);
6242 * When set to a non-zero value, the cache color hint indicates that this list is always drawn
6243 * on top of a solid, single-color, opaque background
6245 * @return The cache color hint
6247 @ViewDebug.ExportedProperty(category = "drawing")
6249 public int getCacheColorHint() {
6250 return mCacheColorHint;
6254 * Move all views (excluding headers and footers) held by this AbsListView into the supplied
6255 * List. This includes views displayed on the screen as well as views stored in AbsListView's
6256 * internal view recycler.
6258 * @param views A list into which to put the reclaimed views
6260 public void reclaimViews(List<View> views) {
6261 int childCount = getChildCount();
6262 RecyclerListener listener = mRecycler.mRecyclerListener;
6264 // Reclaim views on screen
6265 for (int i = 0; i < childCount; i++) {
6266 View child = getChildAt(i);
6267 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6268 // Don't reclaim header or footer views, or views that should be ignored
6269 if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) {
6271 child.setAccessibilityDelegate(null);
6272 if (listener != null) {
6273 // Pretend they went through the scrap heap
6274 listener.onMovedToScrapHeap(child);
6278 mRecycler.reclaimScrapViews(views);
6279 removeAllViewsInLayout();
6282 private void finishGlows() {
6283 if (mEdgeGlowTop != null) {
6284 mEdgeGlowTop.finish();
6285 mEdgeGlowBottom.finish();
6290 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
6291 * through the specified intent.
6292 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
6294 public void setRemoteViewsAdapter(Intent intent) {
6295 setRemoteViewsAdapter(intent, false);
6299 public Runnable setRemoteViewsAdapterAsync(final Intent intent) {
6300 return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent);
6305 public void setRemoteViewsAdapter(Intent intent, boolean isAsync) {
6306 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6307 // service handling the specified intent.
6308 if (mRemoteAdapter != null) {
6309 Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
6310 Intent.FilterComparison fcOld = new Intent.FilterComparison(
6311 mRemoteAdapter.getRemoteViewsServiceIntent());
6312 if (fcNew.equals(fcOld)) {
6316 mDeferNotifyDataSetChanged = false;
6317 // Otherwise, create a new RemoteViewsAdapter for binding
6318 mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync);
6319 if (mRemoteAdapter.isDataReady()) {
6320 setAdapter(mRemoteAdapter);
6325 * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
6327 * @param handler The OnClickHandler to use when inflating RemoteViews.
6331 public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
6332 // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
6333 // service handling the specified intent.
6334 if (mRemoteAdapter != null) {
6335 mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
6340 * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
6344 public void deferNotifyDataSetChanged() {
6345 mDeferNotifyDataSetChanged = true;
6349 * Called back when the adapter connects to the RemoteViewsService.
6352 public boolean onRemoteAdapterConnected() {
6353 if (mRemoteAdapter != mAdapter) {
6354 setAdapter(mRemoteAdapter);
6355 if (mDeferNotifyDataSetChanged) {
6356 mRemoteAdapter.notifyDataSetChanged();
6357 mDeferNotifyDataSetChanged = false;
6360 } else if (mRemoteAdapter != null) {
6361 mRemoteAdapter.superNotifyDataSetChanged();
6368 * Called back when the adapter disconnects from the RemoteViewsService.
6371 public void onRemoteAdapterDisconnected() {
6372 // If the remote adapter disconnects, we keep it around
6373 // since the currently displayed items are still cached.
6374 // Further, we want the service to eventually reconnect
6375 // when necessary, as triggered by this view requesting
6376 // items from the Adapter.
6380 * Hints the RemoteViewsAdapter, if it exists, about which views are currently
6381 * being displayed by the AbsListView.
6383 void setVisibleRangeHint(int start, int end) {
6384 if (mRemoteAdapter != null) {
6385 mRemoteAdapter.setVisibleRangeHint(start, end);
6390 * Sets the recycler listener to be notified whenever a View is set aside in
6391 * the recycler for later reuse. This listener can be used to free resources
6392 * associated to the View.
6394 * @param listener The recycler listener to be notified of views set aside
6397 * @see android.widget.AbsListView.RecycleBin
6398 * @see android.widget.AbsListView.RecyclerListener
6400 public void setRecyclerListener(RecyclerListener listener) {
6401 mRecycler.mRecyclerListener = listener;
6404 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
6406 public void onChanged() {
6408 if (mFastScroll != null) {
6409 mFastScroll.onSectionsChanged();
6414 public void onInvalidated() {
6415 super.onInvalidated();
6416 if (mFastScroll != null) {
6417 mFastScroll.onSectionsChanged();
6423 * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
6424 * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
6425 * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
6426 * selects and deselects list items.
6428 public interface MultiChoiceModeListener extends ActionMode.Callback {
6430 * Called when an item is checked or unchecked during selection mode.
6432 * @param mode The {@link ActionMode} providing the selection mode
6433 * @param position Adapter position of the item that was checked or unchecked
6434 * @param id Adapter ID of the item that was checked or unchecked
6435 * @param checked <code>true</code> if the item is now checked, <code>false</code>
6436 * if the item is now unchecked.
6438 public void onItemCheckedStateChanged(ActionMode mode,
6439 int position, long id, boolean checked);
6442 class MultiChoiceModeWrapper implements MultiChoiceModeListener {
6443 private MultiChoiceModeListener mWrapped;
6445 public void setWrapped(MultiChoiceModeListener wrapped) {
6449 public boolean hasWrappedCallback() {
6450 return mWrapped != null;
6454 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6455 if (mWrapped.onCreateActionMode(mode, menu)) {
6456 // Initialize checked graphic state?
6457 setLongClickable(false);
6464 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6465 return mWrapped.onPrepareActionMode(mode, menu);
6469 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6470 return mWrapped.onActionItemClicked(mode, item);
6474 public void onDestroyActionMode(ActionMode mode) {
6475 mWrapped.onDestroyActionMode(mode);
6476 mChoiceActionMode = null;
6478 // Ending selection mode means deselecting everything.
6481 mDataChanged = true;
6482 rememberSyncState();
6485 setLongClickable(true);
6489 public void onItemCheckedStateChanged(ActionMode mode,
6490 int position, long id, boolean checked) {
6491 mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
6493 // If there are no items selected we no longer need the selection mode.
6494 if (getCheckedItemCount() == 0) {
6501 * AbsListView extends LayoutParams to provide a place to hold the view type.
6503 public static class LayoutParams extends ViewGroup.LayoutParams {
6505 * View type for this view, as returned by
6506 * {@link android.widget.Adapter#getItemViewType(int) }
6508 @ViewDebug.ExportedProperty(category = "list", mapping = {
6509 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
6510 @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
6515 * When this boolean is set, the view has been added to the AbsListView
6516 * at least once. It is used to know whether headers/footers have already
6517 * been added to the list view and whether they should be treated as
6518 * recycled views or not.
6520 @ViewDebug.ExportedProperty(category = "list")
6521 boolean recycledHeaderFooter;
6524 * When an AbsListView is measured with an AT_MOST measure spec, it needs
6525 * to obtain children views to measure itself. When doing so, the children
6526 * are not attached to the window, but put in the recycler which assumes
6527 * they've been attached before. Setting this flag will force the reused
6528 * view to be attached to the window rather than just attached to the
6531 @ViewDebug.ExportedProperty(category = "list")
6535 * The position the view was removed from when pulled out of the
6539 int scrappedFromPosition;
6542 * The ID the view represents
6546 /** Whether the adapter considers the item enabled. */
6549 public LayoutParams(Context c, AttributeSet attrs) {
6553 public LayoutParams(int w, int h) {
6557 public LayoutParams(int w, int h, int viewType) {
6559 this.viewType = viewType;
6562 public LayoutParams(ViewGroup.LayoutParams source) {
6568 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
6569 super.encodeProperties(encoder);
6571 encoder.addProperty("list:viewType", viewType);
6572 encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter);
6573 encoder.addProperty("list:forceAdd", forceAdd);
6574 encoder.addProperty("list:isEnabled", isEnabled);
6579 * A RecyclerListener is used to receive a notification whenever a View is placed
6580 * inside the RecycleBin's scrap heap. This listener is used to free resources
6581 * associated to Views placed in the RecycleBin.
6583 * @see android.widget.AbsListView.RecycleBin
6584 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6586 public static interface RecyclerListener {
6588 * Indicates that the specified View was moved into the recycler's scrap heap.
6589 * The view is not displayed on screen any more and any expensive resource
6590 * associated with the view should be discarded.
6594 void onMovedToScrapHeap(View view);
6598 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
6599 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
6600 * start of a layout. By construction, they are displaying current information. At the end of
6601 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
6602 * could potentially be used by the adapter to avoid allocating views unnecessarily.
6604 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
6605 * @see android.widget.AbsListView.RecyclerListener
6608 private RecyclerListener mRecyclerListener;
6611 * The position of the first view stored in mActiveViews.
6613 private int mFirstActivePosition;
6616 * Views that were on screen at the start of layout. This array is populated at the start of
6617 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
6618 * Views in mActiveViews represent a contiguous range of Views, with position of the first
6619 * view store in mFirstActivePosition.
6621 private View[] mActiveViews = new View[0];
6624 * Unsorted views that can be used by the adapter as a convert view.
6626 private ArrayList<View>[] mScrapViews;
6628 private int mViewTypeCount;
6630 private ArrayList<View> mCurrentScrap;
6632 private ArrayList<View> mSkippedScrap;
6634 private SparseArray<View> mTransientStateViews;
6635 private LongSparseArray<View> mTransientStateViewsById;
6637 public void setViewTypeCount(int viewTypeCount) {
6638 if (viewTypeCount < 1) {
6639 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
6641 //noinspection unchecked
6642 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
6643 for (int i = 0; i < viewTypeCount; i++) {
6644 scrapViews[i] = new ArrayList<View>();
6646 mViewTypeCount = viewTypeCount;
6647 mCurrentScrap = scrapViews[0];
6648 mScrapViews = scrapViews;
6651 public void markChildrenDirty() {
6652 if (mViewTypeCount == 1) {
6653 final ArrayList<View> scrap = mCurrentScrap;
6654 final int scrapCount = scrap.size();
6655 for (int i = 0; i < scrapCount; i++) {
6656 scrap.get(i).forceLayout();
6659 final int typeCount = mViewTypeCount;
6660 for (int i = 0; i < typeCount; i++) {
6661 final ArrayList<View> scrap = mScrapViews[i];
6662 final int scrapCount = scrap.size();
6663 for (int j = 0; j < scrapCount; j++) {
6664 scrap.get(j).forceLayout();
6668 if (mTransientStateViews != null) {
6669 final int count = mTransientStateViews.size();
6670 for (int i = 0; i < count; i++) {
6671 mTransientStateViews.valueAt(i).forceLayout();
6674 if (mTransientStateViewsById != null) {
6675 final int count = mTransientStateViewsById.size();
6676 for (int i = 0; i < count; i++) {
6677 mTransientStateViewsById.valueAt(i).forceLayout();
6682 public boolean shouldRecycleViewType(int viewType) {
6683 return viewType >= 0;
6687 * Clears the scrap heap.
6690 if (mViewTypeCount == 1) {
6691 final ArrayList<View> scrap = mCurrentScrap;
6694 final int typeCount = mViewTypeCount;
6695 for (int i = 0; i < typeCount; i++) {
6696 final ArrayList<View> scrap = mScrapViews[i];
6701 clearTransientStateViews();
6705 * Fill ActiveViews with all of the children of the AbsListView.
6707 * @param childCount The minimum number of views mActiveViews should hold
6708 * @param firstActivePosition The position of the first view that will be stored in
6711 void fillActiveViews(int childCount, int firstActivePosition) {
6712 if (mActiveViews.length < childCount) {
6713 mActiveViews = new View[childCount];
6715 mFirstActivePosition = firstActivePosition;
6717 //noinspection MismatchedReadAndWriteOfArray
6718 final View[] activeViews = mActiveViews;
6719 for (int i = 0; i < childCount; i++) {
6720 View child = getChildAt(i);
6721 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
6722 // Don't put header or footer views into the scrap heap
6723 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6724 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
6725 // However, we will NOT place them into scrap views.
6726 activeViews[i] = child;
6727 // Remember the position so that setupChild() doesn't reset state.
6728 lp.scrappedFromPosition = firstActivePosition + i;
6734 * Get the view corresponding to the specified position. The view will be removed from
6735 * mActiveViews if it is found.
6737 * @param position The position to look up in mActiveViews
6738 * @return The view if it is found, null otherwise
6740 View getActiveView(int position) {
6741 int index = position - mFirstActivePosition;
6742 final View[] activeViews = mActiveViews;
6743 if (index >=0 && index < activeViews.length) {
6744 final View match = activeViews[index];
6745 activeViews[index] = null;
6751 View getTransientStateView(int position) {
6752 if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
6753 long id = mAdapter.getItemId(position);
6754 View result = mTransientStateViewsById.get(id);
6755 mTransientStateViewsById.remove(id);
6758 if (mTransientStateViews != null) {
6759 final int index = mTransientStateViews.indexOfKey(position);
6761 View result = mTransientStateViews.valueAt(index);
6762 mTransientStateViews.removeAt(index);
6770 * Dumps and fully detaches any currently saved views with transient
6773 void clearTransientStateViews() {
6774 final SparseArray<View> viewsByPos = mTransientStateViews;
6775 if (viewsByPos != null) {
6776 final int N = viewsByPos.size();
6777 for (int i = 0; i < N; i++) {
6778 removeDetachedView(viewsByPos.valueAt(i), false);
6783 final LongSparseArray<View> viewsById = mTransientStateViewsById;
6784 if (viewsById != null) {
6785 final int N = viewsById.size();
6786 for (int i = 0; i < N; i++) {
6787 removeDetachedView(viewsById.valueAt(i), false);
6794 * @return A view from the ScrapViews collection. These are unordered.
6796 View getScrapView(int position) {
6797 final int whichScrap = mAdapter.getItemViewType(position);
6798 if (whichScrap < 0) {
6801 if (mViewTypeCount == 1) {
6802 return retrieveFromScrap(mCurrentScrap, position);
6803 } else if (whichScrap < mScrapViews.length) {
6804 return retrieveFromScrap(mScrapViews[whichScrap], position);
6810 * Puts a view into the list of scrap views.
6812 * If the list data hasn't changed or the adapter has stable IDs, views
6813 * with transient state will be preserved for later retrieval.
6815 * @param scrap The view to add
6816 * @param position The view's position within its parent
6818 void addScrapView(View scrap, int position) {
6819 final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
6821 // Can't recycle, but we don't know anything about the view.
6822 // Ignore it completely.
6826 lp.scrappedFromPosition = position;
6828 // Remove but don't scrap header or footer views, or views that
6829 // should otherwise not be recycled.
6830 final int viewType = lp.viewType;
6831 if (!shouldRecycleViewType(viewType)) {
6832 // Can't recycle. If it's not a header or footer, which have
6833 // special handling and should be ignored, then skip the scrap
6834 // heap and we'll fully detach the view later.
6835 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6836 getSkippedScrap().add(scrap);
6841 scrap.dispatchStartTemporaryDetach();
6843 // The the accessibility state of the view may change while temporary
6844 // detached and we do not allow detached views to fire accessibility
6845 // events. So we are announcing that the subtree changed giving a chance
6846 // to clients holding on to a view in this subtree to refresh it.
6847 notifyViewAccessibilityStateChangedIfNeeded(
6848 AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
6850 // Don't scrap views that have transient state.
6851 final boolean scrapHasTransientState = scrap.hasTransientState();
6852 if (scrapHasTransientState) {
6853 if (mAdapter != null && mAdapterHasStableIds) {
6854 // If the adapter has stable IDs, we can reuse the view for
6856 if (mTransientStateViewsById == null) {
6857 mTransientStateViewsById = new LongSparseArray<>();
6859 mTransientStateViewsById.put(lp.itemId, scrap);
6860 } else if (!mDataChanged) {
6861 // If the data hasn't changed, we can reuse the views at
6862 // their old positions.
6863 if (mTransientStateViews == null) {
6864 mTransientStateViews = new SparseArray<>();
6866 mTransientStateViews.put(position, scrap);
6868 // Otherwise, we'll have to remove the view and start over.
6869 clearScrapForRebind(scrap);
6870 getSkippedScrap().add(scrap);
6873 clearScrapForRebind(scrap);
6874 if (mViewTypeCount == 1) {
6875 mCurrentScrap.add(scrap);
6877 mScrapViews[viewType].add(scrap);
6880 if (mRecyclerListener != null) {
6881 mRecyclerListener.onMovedToScrapHeap(scrap);
6886 private ArrayList<View> getSkippedScrap() {
6887 if (mSkippedScrap == null) {
6888 mSkippedScrap = new ArrayList<>();
6890 return mSkippedScrap;
6894 * Finish the removal of any views that skipped the scrap heap.
6896 void removeSkippedScrap() {
6897 if (mSkippedScrap == null) {
6900 final int count = mSkippedScrap.size();
6901 for (int i = 0; i < count; i++) {
6902 removeDetachedView(mSkippedScrap.get(i), false);
6904 mSkippedScrap.clear();
6908 * Move all views remaining in mActiveViews to mScrapViews.
6910 void scrapActiveViews() {
6911 final View[] activeViews = mActiveViews;
6912 final boolean hasListener = mRecyclerListener != null;
6913 final boolean multipleScraps = mViewTypeCount > 1;
6915 ArrayList<View> scrapViews = mCurrentScrap;
6916 final int count = activeViews.length;
6917 for (int i = count - 1; i >= 0; i--) {
6918 final View victim = activeViews[i];
6919 if (victim != null) {
6920 final AbsListView.LayoutParams lp
6921 = (AbsListView.LayoutParams) victim.getLayoutParams();
6922 final int whichScrap = lp.viewType;
6924 activeViews[i] = null;
6926 if (victim.hasTransientState()) {
6927 // Store views with transient state for later use.
6928 victim.dispatchStartTemporaryDetach();
6930 if (mAdapter != null && mAdapterHasStableIds) {
6931 if (mTransientStateViewsById == null) {
6932 mTransientStateViewsById = new LongSparseArray<View>();
6934 long id = mAdapter.getItemId(mFirstActivePosition + i);
6935 mTransientStateViewsById.put(id, victim);
6936 } else if (!mDataChanged) {
6937 if (mTransientStateViews == null) {
6938 mTransientStateViews = new SparseArray<View>();
6940 mTransientStateViews.put(mFirstActivePosition + i, victim);
6941 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6942 // The data has changed, we can't keep this view.
6943 removeDetachedView(victim, false);
6945 } else if (!shouldRecycleViewType(whichScrap)) {
6946 // Discard non-recyclable views except headers/footers.
6947 if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
6948 removeDetachedView(victim, false);
6951 // Store everything else on the appropriate scrap heap.
6952 if (multipleScraps) {
6953 scrapViews = mScrapViews[whichScrap];
6956 lp.scrappedFromPosition = mFirstActivePosition + i;
6957 removeDetachedView(victim, false);
6958 scrapViews.add(victim);
6961 mRecyclerListener.onMovedToScrapHeap(victim);
6970 * At the end of a layout pass, all temp detached views should either be re-attached or
6971 * completely detached. This method ensures that any remaining view in the scrap list is
6974 void fullyDetachScrapViews() {
6975 final int viewTypeCount = mViewTypeCount;
6976 final ArrayList<View>[] scrapViews = mScrapViews;
6977 for (int i = 0; i < viewTypeCount; ++i) {
6978 final ArrayList<View> scrapPile = scrapViews[i];
6979 for (int j = scrapPile.size() - 1; j >= 0; j--) {
6980 final View view = scrapPile.get(j);
6981 if (view.isTemporarilyDetached()) {
6982 removeDetachedView(view, false);
6989 * Makes sure that the size of mScrapViews does not exceed the size of
6990 * mActiveViews, which can happen if an adapter does not recycle its
6991 * views. Removes cached transient state views that no longer have
6994 private void pruneScrapViews() {
6995 final int maxViews = mActiveViews.length;
6996 final int viewTypeCount = mViewTypeCount;
6997 final ArrayList<View>[] scrapViews = mScrapViews;
6998 for (int i = 0; i < viewTypeCount; ++i) {
6999 final ArrayList<View> scrapPile = scrapViews[i];
7000 int size = scrapPile.size();
7001 while (size > maxViews) {
7002 scrapPile.remove(--size);
7006 final SparseArray<View> transViewsByPos = mTransientStateViews;
7007 if (transViewsByPos != null) {
7008 for (int i = 0; i < transViewsByPos.size(); i++) {
7009 final View v = transViewsByPos.valueAt(i);
7010 if (!v.hasTransientState()) {
7011 removeDetachedView(v, false);
7012 transViewsByPos.removeAt(i);
7018 final LongSparseArray<View> transViewsById = mTransientStateViewsById;
7019 if (transViewsById != null) {
7020 for (int i = 0; i < transViewsById.size(); i++) {
7021 final View v = transViewsById.valueAt(i);
7022 if (!v.hasTransientState()) {
7023 removeDetachedView(v, false);
7024 transViewsById.removeAt(i);
7032 * Puts all views in the scrap heap into the supplied list.
7034 void reclaimScrapViews(List<View> views) {
7035 if (mViewTypeCount == 1) {
7036 views.addAll(mCurrentScrap);
7038 final int viewTypeCount = mViewTypeCount;
7039 final ArrayList<View>[] scrapViews = mScrapViews;
7040 for (int i = 0; i < viewTypeCount; ++i) {
7041 final ArrayList<View> scrapPile = scrapViews[i];
7042 views.addAll(scrapPile);
7048 * Updates the cache color hint of all known views.
7050 * @param color The new cache color hint.
7052 void setCacheColorHint(int color) {
7053 if (mViewTypeCount == 1) {
7054 final ArrayList<View> scrap = mCurrentScrap;
7055 final int scrapCount = scrap.size();
7056 for (int i = 0; i < scrapCount; i++) {
7057 scrap.get(i).setDrawingCacheBackgroundColor(color);
7060 final int typeCount = mViewTypeCount;
7061 for (int i = 0; i < typeCount; i++) {
7062 final ArrayList<View> scrap = mScrapViews[i];
7063 final int scrapCount = scrap.size();
7064 for (int j = 0; j < scrapCount; j++) {
7065 scrap.get(j).setDrawingCacheBackgroundColor(color);
7069 // Just in case this is called during a layout pass
7070 final View[] activeViews = mActiveViews;
7071 final int count = activeViews.length;
7072 for (int i = 0; i < count; ++i) {
7073 final View victim = activeViews[i];
7074 if (victim != null) {
7075 victim.setDrawingCacheBackgroundColor(color);
7080 private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
7081 final int size = scrapViews.size();
7083 // See if we still have a view for this position or ID.
7084 // Traverse backwards to find the most recently used scrap view
7085 for (int i = size - 1; i >= 0; i--) {
7086 final View view = scrapViews.get(i);
7087 final AbsListView.LayoutParams params =
7088 (AbsListView.LayoutParams) view.getLayoutParams();
7090 if (mAdapterHasStableIds) {
7091 final long id = mAdapter.getItemId(position);
7092 if (id == params.itemId) {
7093 return scrapViews.remove(i);
7095 } else if (params.scrappedFromPosition == position) {
7096 final View scrap = scrapViews.remove(i);
7097 clearScrapForRebind(scrap);
7101 final View scrap = scrapViews.remove(size - 1);
7102 clearScrapForRebind(scrap);
7109 private void clearScrap(final ArrayList<View> scrap) {
7110 final int scrapCount = scrap.size();
7111 for (int j = 0; j < scrapCount; j++) {
7112 removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
7116 private void clearScrapForRebind(View view) {
7117 view.clearAccessibilityFocus();
7118 view.setAccessibilityDelegate(null);
7121 private void removeDetachedView(View child, boolean animate) {
7122 child.setAccessibilityDelegate(null);
7123 AbsListView.this.removeDetachedView(child, animate);
7128 * Returns the height of the view for the specified position.
7130 * @param position the item position
7131 * @return view height in pixels
7133 int getHeightForPosition(int position) {
7134 final int firstVisiblePosition = getFirstVisiblePosition();
7135 final int childCount = getChildCount();
7136 final int index = position - firstVisiblePosition;
7137 if (index >= 0 && index < childCount) {
7138 // Position is on-screen, use existing view.
7139 final View view = getChildAt(index);
7140 return view.getHeight();
7142 // Position is off-screen, obtain & recycle view.
7143 final View view = obtainView(position, mIsScrap);
7144 view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
7145 final int height = view.getMeasuredHeight();
7146 mRecycler.addScrapView(view, position);
7152 * Sets the selected item and positions the selection y pixels from the top edge
7153 * of the ListView. (If in touch mode, the item will not be selected but it will
7154 * still be positioned appropriately.)
7156 * @param position Index (starting at 0) of the data item to be selected.
7157 * @param y The distance from the top edge of the ListView (plus padding) that the
7158 * item will be positioned.
7160 public void setSelectionFromTop(int position, int y) {
7161 if (mAdapter == null) {
7165 if (!isInTouchMode()) {
7166 position = lookForSelectablePosition(position, true);
7167 if (position >= 0) {
7168 setNextSelectedPositionInt(position);
7171 mResurrectToPosition = position;
7174 if (position >= 0) {
7175 mLayoutMode = LAYOUT_SPECIFIC;
7176 mSpecificTop = mListPadding.top + y;
7179 mSyncPosition = position;
7180 mSyncRowId = mAdapter.getItemId(position);
7183 if (mPositionScroller != null) {
7184 mPositionScroller.stop();
7192 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
7193 super.encodeProperties(encoder);
7195 encoder.addProperty("drawing:cacheColorHint", getCacheColorHint());
7196 encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled());
7197 encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled());
7198 encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled());
7199 encoder.addProperty("list:stackFromBottom", isStackFromBottom());
7200 encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled());
7202 View selectedView = getSelectedView();
7203 if (selectedView != null) {
7204 encoder.addPropertyKey("selectedView");
7205 selectedView.encode(encoder);
7210 * Abstract positon scroller used to handle smooth scrolling.
7212 static abstract class AbsPositionScroller {
7213 public abstract void start(int position);
7214 public abstract void start(int position, int boundPosition);
7215 public abstract void startWithOffset(int position, int offset);
7216 public abstract void startWithOffset(int position, int offset, int duration);
7217 public abstract void stop();
7221 * Default position scroller that simulates a fling.
7223 class PositionScroller extends AbsPositionScroller implements Runnable {
7224 private static final int SCROLL_DURATION = 200;
7226 private static final int MOVE_DOWN_POS = 1;
7227 private static final int MOVE_UP_POS = 2;
7228 private static final int MOVE_DOWN_BOUND = 3;
7229 private static final int MOVE_UP_BOUND = 4;
7230 private static final int MOVE_OFFSET = 5;
7233 private int mTargetPos;
7234 private int mBoundPos;
7235 private int mLastSeenPos;
7236 private int mScrollDuration;
7237 private final int mExtraScroll;
7239 private int mOffsetFromTop;
7241 PositionScroller() {
7242 mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
7246 public void start(final int position) {
7250 // Wait until we're back in a stable state to try this.
7251 mPositionScrollAfterLayout = new Runnable() {
7252 @Override public void run() {
7259 final int childCount = getChildCount();
7260 if (childCount == 0) {
7261 // Can't scroll without children.
7265 final int firstPos = mFirstPosition;
7266 final int lastPos = firstPos + childCount - 1;
7268 int viewTravelCount;
7269 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7270 if (clampedPosition < firstPos) {
7271 viewTravelCount = firstPos - clampedPosition + 1;
7272 mMode = MOVE_UP_POS;
7273 } else if (clampedPosition > lastPos) {
7274 viewTravelCount = clampedPosition - lastPos + 1;
7275 mMode = MOVE_DOWN_POS;
7277 scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
7281 if (viewTravelCount > 0) {
7282 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7284 mScrollDuration = SCROLL_DURATION;
7286 mTargetPos = clampedPosition;
7287 mBoundPos = INVALID_POSITION;
7288 mLastSeenPos = INVALID_POSITION;
7290 postOnAnimation(this);
7294 public void start(final int position, final int boundPosition) {
7297 if (boundPosition == INVALID_POSITION) {
7303 // Wait until we're back in a stable state to try this.
7304 mPositionScrollAfterLayout = new Runnable() {
7305 @Override public void run() {
7306 start(position, boundPosition);
7312 final int childCount = getChildCount();
7313 if (childCount == 0) {
7314 // Can't scroll without children.
7318 final int firstPos = mFirstPosition;
7319 final int lastPos = firstPos + childCount - 1;
7321 int viewTravelCount;
7322 int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
7323 if (clampedPosition < firstPos) {
7324 final int boundPosFromLast = lastPos - boundPosition;
7325 if (boundPosFromLast < 1) {
7326 // Moving would shift our bound position off the screen. Abort.
7330 final int posTravel = firstPos - clampedPosition + 1;
7331 final int boundTravel = boundPosFromLast - 1;
7332 if (boundTravel < posTravel) {
7333 viewTravelCount = boundTravel;
7334 mMode = MOVE_UP_BOUND;
7336 viewTravelCount = posTravel;
7337 mMode = MOVE_UP_POS;
7339 } else if (clampedPosition > lastPos) {
7340 final int boundPosFromFirst = boundPosition - firstPos;
7341 if (boundPosFromFirst < 1) {
7342 // Moving would shift our bound position off the screen. Abort.
7346 final int posTravel = clampedPosition - lastPos + 1;
7347 final int boundTravel = boundPosFromFirst - 1;
7348 if (boundTravel < posTravel) {
7349 viewTravelCount = boundTravel;
7350 mMode = MOVE_DOWN_BOUND;
7352 viewTravelCount = posTravel;
7353 mMode = MOVE_DOWN_POS;
7356 scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
7360 if (viewTravelCount > 0) {
7361 mScrollDuration = SCROLL_DURATION / viewTravelCount;
7363 mScrollDuration = SCROLL_DURATION;
7365 mTargetPos = clampedPosition;
7366 mBoundPos = boundPosition;
7367 mLastSeenPos = INVALID_POSITION;
7369 postOnAnimation(this);
7373 public void startWithOffset(int position, int offset) {
7374 startWithOffset(position, offset, SCROLL_DURATION);
7378 public void startWithOffset(final int position, int offset, final int duration) {
7382 // Wait until we're back in a stable state to try this.
7383 final int postOffset = offset;
7384 mPositionScrollAfterLayout = new Runnable() {
7385 @Override public void run() {
7386 startWithOffset(position, postOffset, duration);
7392 final int childCount = getChildCount();
7393 if (childCount == 0) {
7394 // Can't scroll without children.
7398 offset += getPaddingTop();
7400 mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
7401 mOffsetFromTop = offset;
7402 mBoundPos = INVALID_POSITION;
7403 mLastSeenPos = INVALID_POSITION;
7404 mMode = MOVE_OFFSET;
7406 final int firstPos = mFirstPosition;
7407 final int lastPos = firstPos + childCount - 1;
7409 int viewTravelCount;
7410 if (mTargetPos < firstPos) {
7411 viewTravelCount = firstPos - mTargetPos;
7412 } else if (mTargetPos > lastPos) {
7413 viewTravelCount = mTargetPos - lastPos;
7415 // On-screen, just scroll.
7416 final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
7417 smoothScrollBy(targetTop - offset, duration, true, false);
7421 // Estimate how many screens we should travel
7422 final float screenTravelCount = (float) viewTravelCount / childCount;
7423 mScrollDuration = screenTravelCount < 1 ?
7424 duration : (int) (duration / screenTravelCount);
7425 mLastSeenPos = INVALID_POSITION;
7427 postOnAnimation(this);
7431 * Scroll such that targetPos is in the visible padded region without scrolling
7432 * boundPos out of view. Assumes targetPos is onscreen.
7434 private void scrollToVisible(int targetPos, int boundPos, int duration) {
7435 final int firstPos = mFirstPosition;
7436 final int childCount = getChildCount();
7437 final int lastPos = firstPos + childCount - 1;
7438 final int paddedTop = mListPadding.top;
7439 final int paddedBottom = getHeight() - mListPadding.bottom;
7441 if (targetPos < firstPos || targetPos > lastPos) {
7442 Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
7443 " not visible [" + firstPos + ", " + lastPos + "]");
7445 if (boundPos < firstPos || boundPos > lastPos) {
7446 // boundPos doesn't matter, it's already offscreen.
7447 boundPos = INVALID_POSITION;
7450 final View targetChild = getChildAt(targetPos - firstPos);
7451 final int targetTop = targetChild.getTop();
7452 final int targetBottom = targetChild.getBottom();
7455 if (targetBottom > paddedBottom) {
7456 scrollBy = targetBottom - paddedBottom;
7458 if (targetTop < paddedTop) {
7459 scrollBy = targetTop - paddedTop;
7462 if (scrollBy == 0) {
7466 if (boundPos >= 0) {
7467 final View boundChild = getChildAt(boundPos - firstPos);
7468 final int boundTop = boundChild.getTop();
7469 final int boundBottom = boundChild.getBottom();
7470 final int absScroll = Math.abs(scrollBy);
7472 if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
7473 // Don't scroll the bound view off the bottom of the screen.
7474 scrollBy = Math.max(0, boundBottom - paddedBottom);
7475 } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
7476 // Don't scroll the bound view off the top of the screen.
7477 scrollBy = Math.min(0, boundTop - paddedTop);
7481 smoothScrollBy(scrollBy, duration);
7485 public void stop() {
7486 removeCallbacks(this);
7491 final int listHeight = getHeight();
7492 final int firstPos = mFirstPosition;
7495 case MOVE_DOWN_POS: {
7496 final int lastViewIndex = getChildCount() - 1;
7497 final int lastPos = firstPos + lastViewIndex;
7499 if (lastViewIndex < 0) {
7503 if (lastPos == mLastSeenPos) {
7504 // No new views, let things keep going.
7505 postOnAnimation(this);
7509 final View lastView = getChildAt(lastViewIndex);
7510 final int lastViewHeight = lastView.getHeight();
7511 final int lastViewTop = lastView.getTop();
7512 final int lastViewPixelsShowing = listHeight - lastViewTop;
7513 final int extraScroll = lastPos < mItemCount - 1 ?
7514 Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
7516 final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
7517 smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos);
7519 mLastSeenPos = lastPos;
7520 if (lastPos < mTargetPos) {
7521 postOnAnimation(this);
7526 case MOVE_DOWN_BOUND: {
7527 final int nextViewIndex = 1;
7528 final int childCount = getChildCount();
7530 if (firstPos == mBoundPos || childCount <= nextViewIndex
7531 || firstPos + childCount >= mItemCount) {
7532 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
7535 final int nextPos = firstPos + nextViewIndex;
7537 if (nextPos == mLastSeenPos) {
7538 // No new views, let things keep going.
7539 postOnAnimation(this);
7543 final View nextView = getChildAt(nextViewIndex);
7544 final int nextViewHeight = nextView.getHeight();
7545 final int nextViewTop = nextView.getTop();
7546 final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
7547 if (nextPos < mBoundPos) {
7548 smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
7549 mScrollDuration, true, true);
7551 mLastSeenPos = nextPos;
7553 postOnAnimation(this);
7555 if (nextViewTop > extraScroll) {
7556 smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false);
7558 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
7565 if (firstPos == mLastSeenPos) {
7566 // No new views, let things keep going.
7567 postOnAnimation(this);
7571 final View firstView = getChildAt(0);
7572 if (firstView == null) {
7575 final int firstViewTop = firstView.getTop();
7576 final int extraScroll = firstPos > 0 ?
7577 Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
7579 smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true,
7580 firstPos > mTargetPos);
7582 mLastSeenPos = firstPos;
7584 if (firstPos > mTargetPos) {
7585 postOnAnimation(this);
7590 case MOVE_UP_BOUND: {
7591 final int lastViewIndex = getChildCount() - 2;
7592 if (lastViewIndex < 0) {
7595 final int lastPos = firstPos + lastViewIndex;
7597 if (lastPos == mLastSeenPos) {
7598 // No new views, let things keep going.
7599 postOnAnimation(this);
7603 final View lastView = getChildAt(lastViewIndex);
7604 final int lastViewHeight = lastView.getHeight();
7605 final int lastViewTop = lastView.getTop();
7606 final int lastViewPixelsShowing = listHeight - lastViewTop;
7607 final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
7608 mLastSeenPos = lastPos;
7609 if (lastPos > mBoundPos) {
7610 smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true,
7612 postOnAnimation(this);
7614 final int bottom = listHeight - extraScroll;
7615 final int lastViewBottom = lastViewTop + lastViewHeight;
7616 if (bottom > lastViewBottom) {
7617 smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false);
7619 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
7626 if (mLastSeenPos == firstPos) {
7627 // No new views, let things keep going.
7628 postOnAnimation(this);
7632 mLastSeenPos = firstPos;
7634 final int childCount = getChildCount();
7635 final int position = mTargetPos;
7636 final int lastPos = firstPos + childCount - 1;
7638 // Account for the visible "portion" of the first / last child when we estimate
7639 // how many screens we should travel to reach our target
7640 final View firstChild = getChildAt(0);
7641 final int firstChildHeight = firstChild.getHeight();
7642 final View lastChild = getChildAt(childCount - 1);
7643 final int lastChildHeight = lastChild.getHeight();
7644 final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f
7645 : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight;
7646 final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f
7647 : (float) (lastChildHeight + getHeight() - lastChild.getBottom())
7650 float viewTravelCount = 0;
7651 if (position < firstPos) {
7652 viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1;
7653 } else if (position > lastPos) {
7654 viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart);
7657 // Estimate how many screens we should travel
7658 final float screenTravelCount = viewTravelCount / childCount;
7660 final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
7661 if (position < firstPos) {
7662 final int distance = (int) (-getHeight() * modifier);
7663 final int duration = (int) (mScrollDuration * modifier);
7664 smoothScrollBy(distance, duration, true, true);
7665 postOnAnimation(this);
7666 } else if (position > lastPos) {
7667 final int distance = (int) (getHeight() * modifier);
7668 final int duration = (int) (mScrollDuration * modifier);
7669 smoothScrollBy(distance, duration, true, true);
7670 postOnAnimation(this);
7672 // On-screen, just scroll.
7673 final int targetTop = getChildAt(position - firstPos).getTop();
7674 final int distance = targetTop - mOffsetFromTop;
7675 final int duration = (int) (mScrollDuration *
7676 ((float) Math.abs(distance) / getHeight()));
7677 smoothScrollBy(distance, duration, true, false);