/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.Trace;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.StateSet;
import android.view.ActionMode;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.RemoteViews.OnClickHandler;

import com.android.internal.R;

import java.util.ArrayList;
import java.util.List;

Base class that can be used to implement virtualized lists of items. A list does not have a spatial definition here. For instance, subclasses of this class can display the content of the list in a grid, in a carousel, as stack, etc.
@attrref android.R.styleable#AbsListView_listSelector
@attrref android.R.styleable#AbsListView_drawSelectorOnTop
@attrref android.R.styleable#AbsListView_stackFromBottom
@attrref android.R.styleable#AbsListView_scrollingCache
@attrref android.R.styleable#AbsListView_textFilterEnabled
@attrref android.R.styleable#AbsListView_transcriptMode
@attrref android.R.styleable#AbsListView_cacheColorHint
@attrref android.R.styleable#AbsListView_fastScrollEnabled
@attrref android.R.styleable#AbsListView_smoothScrollbar
@attrref android.R.styleable#AbsListView_choiceMode
/** * Base class that can be used to implement virtualized lists of items. A list does * not have a spatial definition here. For instance, subclasses of this class can * display the content of the list in a grid, in a carousel, as stack, etc. * * @attr ref android.R.styleable#AbsListView_listSelector * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop * @attr ref android.R.styleable#AbsListView_stackFromBottom * @attr ref android.R.styleable#AbsListView_scrollingCache * @attr ref android.R.styleable#AbsListView_textFilterEnabled * @attr ref android.R.styleable#AbsListView_transcriptMode * @attr ref android.R.styleable#AbsListView_cacheColorHint * @attr ref android.R.styleable#AbsListView_fastScrollEnabled * @attr ref android.R.styleable#AbsListView_smoothScrollbar * @attr ref android.R.styleable#AbsListView_choiceMode */
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { @SuppressWarnings("UnusedDeclaration") private static final String TAG = "AbsListView";
Disables the transcript mode.
See Also:
  • setTranscriptMode(int)
/** * Disables the transcript mode. * * @see #setTranscriptMode(int) */
public static final int TRANSCRIPT_MODE_DISABLED = 0;
The list will automatically scroll to the bottom when a data set change notification is received and only if the last item is already visible on screen.
See Also:
  • setTranscriptMode(int)
/** * The list will automatically scroll to the bottom when a data set change * notification is received and only if the last item is already visible * on screen. * * @see #setTranscriptMode(int) */
public static final int TRANSCRIPT_MODE_NORMAL = 1;
The list will automatically scroll to the bottom, no matter what items are currently visible.
See Also:
  • setTranscriptMode(int)
/** * The list will automatically scroll to the bottom, no matter what items * are currently visible. * * @see #setTranscriptMode(int) */
public static final int TRANSCRIPT_MODE_ALWAYS_SCROLL = 2;
Indicates that we are not in the middle of a touch gesture
/** * Indicates that we are not in the middle of a touch gesture */
static final int TOUCH_MODE_REST = -1;
Indicates we just received the touch event and we are waiting to see if the it is a tap or a scroll gesture.
/** * Indicates we just received the touch event and we are waiting to see if the it is a tap or a * scroll gesture. */
static final int TOUCH_MODE_DOWN = 0;
Indicates the touch has been recognized as a tap and we are now waiting to see if the touch is a longpress
/** * Indicates the touch has been recognized as a tap and we are now waiting to see if the touch * is a longpress */
static final int TOUCH_MODE_TAP = 1;
Indicates we have waited for everything we can wait for, but the user's finger is still down
/** * Indicates we have waited for everything we can wait for, but the user's finger is still down */
static final int TOUCH_MODE_DONE_WAITING = 2;
Indicates the touch gesture is a scroll
/** * Indicates the touch gesture is a scroll */
static final int TOUCH_MODE_SCROLL = 3;
Indicates the view is in the process of being flung
/** * Indicates the view is in the process of being flung */
static final int TOUCH_MODE_FLING = 4;
Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
/** * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end. */
static final int TOUCH_MODE_OVERSCROLL = 5;
Indicates the view is being flung outside of normal content bounds and will spring back.
/** * Indicates the view is being flung outside of normal content bounds * and will spring back. */
static final int TOUCH_MODE_OVERFLING = 6;
Regular layout - usually an unsolicited layout from the view system
/** * Regular layout - usually an unsolicited layout from the view system */
static final int LAYOUT_NORMAL = 0;
Show the first item
/** * Show the first item */
static final int LAYOUT_FORCE_TOP = 1;
Force the selected item to be on somewhere on the screen
/** * Force the selected item to be on somewhere on the screen */
static final int LAYOUT_SET_SELECTION = 2;
Show the last item
/** * Show the last item */
static final int LAYOUT_FORCE_BOTTOM = 3;
Make a mSelectedItem appear in a specific location and build the rest of the views from there. The top is specified by mSpecificTop.
/** * Make a mSelectedItem appear in a specific location and build the rest of * the views from there. The top is specified by mSpecificTop. */
static final int LAYOUT_SPECIFIC = 4;
Layout to sync as a result of a data change. Restore mSyncPosition to have its top at mSpecificTop
/** * Layout to sync as a result of a data change. Restore mSyncPosition to have its top * at mSpecificTop */
static final int LAYOUT_SYNC = 5;
Layout as a result of using the navigation keys
/** * Layout as a result of using the navigation keys */
static final int LAYOUT_MOVE_SELECTION = 6;
Normal list that does not indicate choices
/** * Normal list that does not indicate choices */
public static final int CHOICE_MODE_NONE = 0;
The list allows up to one choice
/** * The list allows up to one choice */
public static final int CHOICE_MODE_SINGLE = 1;
The list allows multiple choices
/** * The list allows multiple choices */
public static final int CHOICE_MODE_MULTIPLE = 2;
The list allows multiple choices in a modal selection mode
/** * The list allows multiple choices in a modal selection mode */
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
The thread that created this view.
/** * The thread that created this view. */
private final Thread mOwnerThread;
Controls if/how the user may choose/check items in the list
/** * Controls if/how the user may choose/check items in the list */
int mChoiceMode = CHOICE_MODE_NONE;
Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
/** * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive. */
ActionMode mChoiceActionMode;
Wrapper for the multiple choice mode callback; AbsListView needs to perform a few extra actions around what application code does.
/** * Wrapper for the multiple choice mode callback; AbsListView needs to perform * a few extra actions around what application code does. */
MultiChoiceModeWrapper mMultiChoiceModeCallback;
Running count of how many items are currently checked
/** * Running count of how many items are currently checked */
int mCheckedItemCount;
Running state of which positions are currently checked
/** * Running state of which positions are currently checked */
SparseBooleanArray mCheckStates;
Running state of which IDs are currently checked. If there is a value for a given key, the checked state for that ID is true and the value holds the last known position in the adapter for that id.
/** * Running state of which IDs are currently checked. * If there is a value for a given key, the checked state for that ID is true * and the value holds the last known position in the adapter for that id. */
LongSparseArray<Integer> mCheckedIdStates;
Controls how the next layout will happen
/** * Controls how the next layout will happen */
int mLayoutMode = LAYOUT_NORMAL;
Should be used by subclasses to listen to changes in the dataset
/** * Should be used by subclasses to listen to changes in the dataset */
AdapterDataSetObserver mDataSetObserver;
The adapter containing the data to be displayed by this view
/** * The adapter containing the data to be displayed by this view */
ListAdapter mAdapter;
The remote adapter containing the data to be displayed by this view to be set
/** * The remote adapter containing the data to be displayed by this view to be set */
private RemoteViewsAdapter mRemoteAdapter;
If mAdapter != null, whenever this is true the adapter has stable IDs.
/** * If mAdapter != null, whenever this is true the adapter has stable IDs. */
boolean mAdapterHasStableIds;
This flag indicates the a full notify is required when the RemoteViewsAdapter connects
/** * This flag indicates the a full notify is required when the RemoteViewsAdapter connects */
private boolean mDeferNotifyDataSetChanged = false;
Indicates whether the list selector should be drawn on top of the children or behind
/** * Indicates whether the list selector should be drawn on top of the children or behind */
boolean mDrawSelectorOnTop = false;
The drawable used to draw the selector
/** * The drawable used to draw the selector */
Drawable mSelector;
The current position of the selector in the list.
/** * The current position of the selector in the list. */
int mSelectorPosition = INVALID_POSITION;
Defines the selector's location and dimension at drawing time
/** * Defines the selector's location and dimension at drawing time */
Rect mSelectorRect = new Rect();
The data set used to store unused views that should be reused during the next layout to avoid creating new ones
/** * The data set used to store unused views that should be reused during the next layout * to avoid creating new ones */
final RecycleBin mRecycler = new RecycleBin();
The selection's left padding
/** * The selection's left padding */
int mSelectionLeftPadding = 0;
The selection's top padding
/** * The selection's top padding */
int mSelectionTopPadding = 0;
The selection's right padding
/** * The selection's right padding */
int mSelectionRightPadding = 0;
The selection's bottom padding
/** * The selection's bottom padding */
int mSelectionBottomPadding = 0;
This view's padding
/** * This view's padding */
Rect mListPadding = new Rect();
Subclasses must retain their measure spec from onMeasure() into this member
/** * Subclasses must retain their measure spec from onMeasure() into this member */
int mWidthMeasureSpec = 0;
The top scroll indicator
/** * The top scroll indicator */
View mScrollUp;
The down scroll indicator
/** * The down scroll indicator */
View mScrollDown;
When the view is scrolling, this flag is set to true to indicate subclasses that the drawing cache was enabled on the children
/** * When the view is scrolling, this flag is set to true to indicate subclasses that * the drawing cache was enabled on the children */
boolean mCachingStarted; boolean mCachingActive;
The position of the view that received the down motion event
/** * The position of the view that received the down motion event */
int mMotionPosition;
The offset to the top of the mMotionPosition view when the down motion event was received
/** * The offset to the top of the mMotionPosition view when the down motion event was received */
int mMotionViewOriginalTop;
The desired offset to the top of the mMotionPosition view after a scroll
/** * The desired offset to the top of the mMotionPosition view after a scroll */
int mMotionViewNewTop;
The X value associated with the the down motion event
/** * The X value associated with the the down motion event */
int mMotionX;
The Y value associated with the the down motion event
/** * The Y value associated with the the down motion event */
int mMotionY;
One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or TOUCH_MODE_DONE_WAITING
/** * One of TOUCH_MODE_REST, TOUCH_MODE_DOWN, TOUCH_MODE_TAP, TOUCH_MODE_SCROLL, or * TOUCH_MODE_DONE_WAITING */
int mTouchMode = TOUCH_MODE_REST;
Y value from on the previous motion event (if any)
/** * Y value from on the previous motion event (if any) */
int mLastY;
How far the finger moved before we started scrolling
/** * How far the finger moved before we started scrolling */
int mMotionCorrection;
Determines speed during touch scrolling
/** * Determines speed during touch scrolling */
private VelocityTracker mVelocityTracker;
Handles one frame of a fling
/** * Handles one frame of a fling */
private FlingRunnable mFlingRunnable;
Handles scrolling between positions within the list.
/** * Handles scrolling between positions within the list. */
AbsPositionScroller mPositionScroller;
The offset in pixels form the top of the AdapterView to the top of the currently selected view. Used to save and restore state.
/** * The offset in pixels form the top of the AdapterView to the top * of the currently selected view. Used to save and restore state. */
int mSelectedTop = 0;
Indicates whether the list is stacked from the bottom edge or the top edge.
/** * Indicates whether the list is stacked from the bottom edge or * the top edge. */
boolean mStackFromBottom;
When set to true, the list automatically discards the children's bitmap cache after scrolling.
/** * When set to true, the list automatically discards the children's * bitmap cache after scrolling. */
boolean mScrollingCacheEnabled;
Whether or not to enable the fast scroll feature on this list
/** * Whether or not to enable the fast scroll feature on this list */
boolean mFastScrollEnabled;
Whether or not to always show the fast scroll feature on this list
/** * Whether or not to always show the fast scroll feature on this list */
boolean mFastScrollAlwaysVisible;
Optional callback to notify client when scroll position has changed
/** * Optional callback to notify client when scroll position has changed */
private OnScrollListener mOnScrollListener;
Keeps track of our accessory window
/** * Keeps track of our accessory window */
PopupWindow mPopup;
Used with type filter window
/** * Used with type filter window */
EditText mTextFilter;
Indicates whether to use pixels-based or position-based scrollbar properties.
/** * Indicates whether to use pixels-based or position-based scrollbar * properties. */
private boolean mSmoothScrollbarEnabled = true;
Indicates that this view supports filtering
/** * Indicates that this view supports filtering */
private boolean mTextFilterEnabled;
Indicates that this view is currently displaying a filtered view of the data
/** * Indicates that this view is currently displaying a filtered view of the data */
private boolean mFiltered;
Rectangle used for hit testing children
/** * Rectangle used for hit testing children */
private Rect mTouchFrame;
The position to resurrect the selected position to.
/** * The position to resurrect the selected position to. */
int mResurrectToPosition = INVALID_POSITION; private ContextMenuInfo mContextMenuInfo = null;
Maximum distance to record overscroll
/** * Maximum distance to record overscroll */
int mOverscrollMax;
Content height divided by this is the overscroll limit.
/** * Content height divided by this is the overscroll limit. */
static final int OVERSCROLL_LIMIT_DIVISOR = 3;
How many positions in either direction we will search to try to find a checked item with a stable ID that moved position across a data set change. If the item isn't found it will be unselected.
/** * How many positions in either direction we will search to try to * find a checked item with a stable ID that moved position across * a data set change. If the item isn't found it will be unselected. */
private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
Used to request a layout when we changed touch mode
/** * Used to request a layout when we changed touch mode */
private static final int TOUCH_MODE_UNKNOWN = -1; private static final int TOUCH_MODE_ON = 0; private static final int TOUCH_MODE_OFF = 1; private int mLastTouchMode = TOUCH_MODE_UNKNOWN; private static final boolean PROFILE_SCROLLING = false; private boolean mScrollProfilingStarted = false; private static final boolean PROFILE_FLINGING = false; private boolean mFlingProfilingStarted = false;
The StrictMode "critical time span" objects to catch animation stutters. Non-null when a time-sensitive animation is in-flight. Must call finish() on them when done animating. These are no-ops on user builds.
/** * The StrictMode "critical time span" objects to catch animation * stutters. Non-null when a time-sensitive animation is * in-flight. Must call finish() on them when done animating. * These are no-ops on user builds. */
private StrictMode.Span mScrollStrictSpan = null; private StrictMode.Span mFlingStrictSpan = null;
The last CheckForLongPress runnable we posted, if any
/** * The last CheckForLongPress runnable we posted, if any */
private CheckForLongPress mPendingCheckForLongPress;
The last CheckForTap runnable we posted, if any
/** * The last CheckForTap runnable we posted, if any */
private CheckForTap mPendingCheckForTap;
The last CheckForKeyLongPress runnable we posted, if any
/** * The last CheckForKeyLongPress runnable we posted, if any */
private CheckForKeyLongPress mPendingCheckForKeyLongPress;
Acts upon click
/** * Acts upon click */
private AbsListView.PerformClick mPerformClick;
Delayed action for touch mode.
/** * Delayed action for touch mode. */
private Runnable mTouchModeReset;
Whether the most recent touch event stream resulted in a successful long-press action. This is reset on TOUCH_DOWN.
/** * Whether the most recent touch event stream resulted in a successful * long-press action. This is reset on TOUCH_DOWN. */
private boolean mHasPerformedLongPress;
This view is in transcript mode -- it shows the bottom of the list when the data changes
/** * This view is in transcript mode -- it shows the bottom of the list when the data * changes */
private int mTranscriptMode;
Indicates that this list is always drawn on top of a solid, single-color, opaque background
/** * Indicates that this list is always drawn on top of a solid, single-color, opaque * background */
private int mCacheColorHint;
The select child's view (from the adapter's getView) is enabled.
/** * The select child's view (from the adapter's getView) is enabled. */
private boolean mIsChildViewEnabled;
The cached drawable state for the selector. Accounts for child enabled state, but otherwise identical to the view's own drawable state.
/** * The cached drawable state for the selector. Accounts for child enabled * state, but otherwise identical to the view's own drawable state. */
private int[] mSelectorState;
The last scroll state reported to clients through OnScrollListener.
/** * The last scroll state reported to clients through {@link OnScrollListener}. */
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
Helper object that renders and controls the fast scroll thumb.
/** * Helper object that renders and controls the fast scroll thumb. */
private FastScroller mFastScroll;
Temporary holder for fast scroller style until a FastScroller object is created.
/** * Temporary holder for fast scroller style until a FastScroller object * is created. */
private int mFastScrollStyle; private boolean mGlobalLayoutListenerAddedFilter; private int mTouchSlop; private float mDensityScale; private float mVerticalScrollFactor; private InputConnection mDefInputConnection; private InputConnectionWrapper mPublicInputConnection; private Runnable mClearScrollingCache; Runnable mPositionScrollAfterLayout; private int mMinimumVelocity; private int mMaximumVelocity; private float mVelocityScale = 1.0f; final boolean[] mIsScrap = new boolean[1]; private final int[] mScrollOffset = new int[2]; private final int[] mScrollConsumed = new int[2]; private final float[] mTmpPoint = new float[2]; // Used for offsetting MotionEvents that we feed to the VelocityTracker. // In the future it would be nice to be able to give this to the VelocityTracker // directly, or alternatively put a VT into absolute-positioning mode that only // reads the raw screen-coordinate x/y values. private int mNestedYOffset = 0; // True when the popup should be hidden because of a call to // dispatchDisplayHint() private boolean mPopupHidden;
ID of the active pointer. This is used to retain consistency during drags/flings if multiple pointers are used.
/** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */
private int mActivePointerId = INVALID_POINTER;
Sentinel value for no current active pointer. Used by mActivePointerId.
/** * Sentinel value for no current active pointer. * Used by {@link #mActivePointerId}. */
private static final int INVALID_POINTER = -1;
Maximum distance to overscroll by during edge effects
/** * Maximum distance to overscroll by during edge effects */
int mOverscrollDistance;
Maximum distance to overfling during edge effects
/** * Maximum distance to overfling during edge effects */
int mOverflingDistance; // These two EdgeGlows are always set and used together. // Checking one for null is as good as checking both.
Tracks the state of the top edge glow.
/** * Tracks the state of the top edge glow. */
private EdgeEffect mEdgeGlowTop;
Tracks the state of the bottom edge glow.
/** * Tracks the state of the bottom edge glow. */
private EdgeEffect mEdgeGlowBottom;
An estimate of how many pixels are between the top of the list and the top of the first position in the adapter, based on the last time we saw it. Used to hint where to draw edge glows.
/** * An estimate of how many pixels are between the top of the list and * the top of the first position in the adapter, based on the last time * we saw it. Used to hint where to draw edge glows. */
private int mFirstPositionDistanceGuess;
An estimate of how many pixels are between the bottom of the list and the bottom of the last position in the adapter, based on the last time we saw it. Used to hint where to draw edge glows.
/** * An estimate of how many pixels are between the bottom of the list and * the bottom of the last position in the adapter, based on the last time * we saw it. Used to hint where to draw edge glows. */
private int mLastPositionDistanceGuess;
Used for determining when to cancel out of overscroll.
/** * Used for determining when to cancel out of overscroll. */
private int mDirection = 0;
Tracked on measurement in transcript mode. Makes sure that we can still pin to the bottom correctly on resizes.
/** * Tracked on measurement in transcript mode. Makes sure that we can still pin to * the bottom correctly on resizes. */
private boolean mForceTranscriptScroll;
Used for interacting with list items from an accessibility service.
/** * Used for interacting with list items from an accessibility service. */
private ListItemAccessibilityDelegate mAccessibilityDelegate; private int mLastAccessibilityScrollEventFromIndex; private int mLastAccessibilityScrollEventToIndex;
Track the item count from the last time we handled a data change.
/** * Track the item count from the last time we handled a data change. */
private int mLastHandledItemCount;
Used for smooth scrolling at a consistent rate
/** * Used for smooth scrolling at a consistent rate */
static final Interpolator sLinearInterpolator = new LinearInterpolator();
The saved state that we will be restoring from when we next sync. Kept here so that if we happen to be asked to save our state before the sync happens, we can return this existing data rather than losing it.
/** * The saved state that we will be restoring from when we next sync. * Kept here so that if we happen to be asked to save our state before * the sync happens, we can return this existing data rather than losing * it. */
private SavedState mPendingSync;
Whether the view is in the process of detaching from its window.
/** * Whether the view is in the process of detaching from its window. */
private boolean mIsDetaching;
Interface definition for a callback to be invoked when the list or grid has been scrolled.
/** * Interface definition for a callback to be invoked when the list or grid * has been scrolled. */
public interface OnScrollListener {
The view is not scrolling. Note navigating the list using the trackball counts as being in the idle state since these transitions are not animated.
/** * The view is not scrolling. Note navigating the list using the trackball counts as * being in the idle state since these transitions are not animated. */
public static int SCROLL_STATE_IDLE = 0;
The user is scrolling using touch, and their finger is still on the screen
/** * The user is scrolling using touch, and their finger is still on the screen */
public static int SCROLL_STATE_TOUCH_SCROLL = 1;
The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop
/** * The user had previously been scrolling using touch and had performed a fling. The * animation is now coasting to a stop */
public static int SCROLL_STATE_FLING = 2;
Callback method to be invoked while the list view or grid view is being scrolled. If the view is being scrolled, this method will be called before the next frame of the scroll is rendered. In particular, it will be called before any calls to Adapter.getView(int, View, ViewGroup).
Params:
/** * Callback method to be invoked while the list view or grid view is being scrolled. If the * view is being scrolled, this method will be called before the next frame of the scroll is * rendered. In particular, it will be called before any calls to * {@link Adapter#getView(int, View, ViewGroup)}. * * @param view The view whose scroll state is being reported * * @param scrollState The current scroll state. One of * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}. */
public void onScrollStateChanged(AbsListView view, int scrollState);
Callback method to be invoked when the list or grid has been scrolled. This will be called after the scroll has completed
Params:
  • view – The view whose scroll state is being reported
  • firstVisibleItem – the index of the first visible cell (ignore if visibleItemCount == 0)
  • visibleItemCount – the number of visible cells
  • totalItemCount – the number of items in the list adapter
/** * Callback method to be invoked when the list or grid has been scrolled. This will be * called after the scroll has completed * @param view The view whose scroll state is being reported * @param firstVisibleItem the index of the first visible cell (ignore if * visibleItemCount == 0) * @param visibleItemCount the number of visible cells * @param totalItemCount the number of items in the list adapter */
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount); }
The top-level view of a list item can implement this interface to allow itself to modify the bounds of the selection shown for that item.
/** * The top-level view of a list item can implement this interface to allow * itself to modify the bounds of the selection shown for that item. */
public interface SelectionBoundsAdjuster {
Called to allow the list item to adjust the bounds shown for its selection.
Params:
  • bounds – On call, this contains the bounds the list has selected for the item (that is the bounds of the entire view). The values can be modified as desired.
/** * Called to allow the list item to adjust the bounds shown for * its selection. * * @param bounds On call, this contains the bounds the list has * selected for the item (that is the bounds of the entire view). The * values can be modified as desired. */
public void adjustListItemSelectionBounds(Rect bounds); } public AbsListView(Context context) { super(context); initAbsListView(); mOwnerThread = Thread.currentThread(); setVerticalScrollBarEnabled(true); TypedArray a = context.obtainStyledAttributes(R.styleable.View); initializeScrollbarsInternal(a); a.recycle(); } public AbsListView(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.absListViewStyle); } public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initAbsListView(); mOwnerThread = Thread.currentThread(); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.AbsListView, defStyleAttr, defStyleRes); final Drawable selector = a.getDrawable(R.styleable.AbsListView_listSelector); if (selector != null) { setSelector(selector); } mDrawSelectorOnTop = a.getBoolean(R.styleable.AbsListView_drawSelectorOnTop, false); setStackFromBottom(a.getBoolean( R.styleable.AbsListView_stackFromBottom, false)); setScrollingCacheEnabled(a.getBoolean( R.styleable.AbsListView_scrollingCache, true)); setTextFilterEnabled(a.getBoolean( R.styleable.AbsListView_textFilterEnabled, false)); setTranscriptMode(a.getInt( R.styleable.AbsListView_transcriptMode, TRANSCRIPT_MODE_DISABLED)); setCacheColorHint(a.getColor( R.styleable.AbsListView_cacheColorHint, 0)); setSmoothScrollbarEnabled(a.getBoolean( R.styleable.AbsListView_smoothScrollbar, true)); setChoiceMode(a.getInt( R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE)); setFastScrollEnabled(a.getBoolean( R.styleable.AbsListView_fastScrollEnabled, false)); setFastScrollStyle(a.getResourceId( R.styleable.AbsListView_fastScrollStyle, 0)); setFastScrollAlwaysVisible(a.getBoolean( R.styleable.AbsListView_fastScrollAlwaysVisible, false)); a.recycle(); if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) { setRevealOnFocusHint(false); } } private void initAbsListView() { // Setting focusable in touch mode will set the focusable property to true setClickable(true); setFocusableInTouchMode(true); setWillNotDraw(false); setAlwaysDrawnWithCacheEnabled(false); setScrollingCacheEnabled(true); final ViewConfiguration configuration = ViewConfiguration.get(mContext); mTouchSlop = configuration.getScaledTouchSlop(); mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); mDensityScale = getContext().getResources().getDisplayMetrics().density; } @Override public void setOverScrollMode(int mode) { if (mode != OVER_SCROLL_NEVER) { if (mEdgeGlowTop == null) { Context context = getContext(); mEdgeGlowTop = new EdgeEffect(context); mEdgeGlowBottom = new EdgeEffect(context); } } else { mEdgeGlowTop = null; mEdgeGlowBottom = null; } super.setOverScrollMode(mode); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void setAdapter(ListAdapter adapter) { if (adapter != null) { mAdapterHasStableIds = mAdapter.hasStableIds(); if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds && mCheckedIdStates == null) { mCheckedIdStates = new LongSparseArray<Integer>(); } } clearChoices(); }
Returns the number of items currently selected. This will only be valid if the choice mode is not CHOICE_MODE_NONE (default).

To determine the specific items that are currently selected, use one of the getChecked* methods.

See Also:
Returns:The number of items currently selected
/** * Returns the number of items currently selected. This will only be valid * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). * * <p>To determine the specific items that are currently selected, use one of * the <code>getChecked*</code> methods. * * @return The number of items currently selected * * @see #getCheckedItemPosition() * @see #getCheckedItemPositions() * @see #getCheckedItemIds() */
public int getCheckedItemCount() { return mCheckedItemCount; }
Returns the checked state of the specified position. The result is only valid if the choice mode has been set to CHOICE_MODE_SINGLE or CHOICE_MODE_MULTIPLE.
Params:
  • position – The item whose checked state to return
See Also:
Returns:The item's checked state or false if choice mode is invalid
/** * Returns the checked state of the specified position. The result is only * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} * or {@link #CHOICE_MODE_MULTIPLE}. * * @param position The item whose checked state to return * @return The item's checked state or <code>false</code> if choice mode * is invalid * * @see #setChoiceMode(int) */
public boolean isItemChecked(int position) { if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { return mCheckStates.get(position); } return false; }
Returns the currently checked item. The result is only valid if the choice mode has been set to CHOICE_MODE_SINGLE.
See Also:
Returns:The position of the currently checked item or AdapterView<ListAdapter>.INVALID_POSITION if nothing is selected
/** * Returns the currently checked item. The result is only valid if the choice * mode has been set to {@link #CHOICE_MODE_SINGLE}. * * @return The position of the currently checked item or * {@link #INVALID_POSITION} if nothing is selected * * @see #setChoiceMode(int) */
public int getCheckedItemPosition() { if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) { return mCheckStates.keyAt(0); } return INVALID_POSITION; }
Returns the set of checked items in the list. The result is only valid if the choice mode has not been set to CHOICE_MODE_NONE.
Returns: A SparseBooleanArray which will return true for each call to get(int position) where position is a checked position in the list and false otherwise, or null if the choice mode is set to CHOICE_MODE_NONE.
/** * Returns the set of checked items in the list. The result is only valid if * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. * * @return A SparseBooleanArray which will return true for each call to * get(int position) where position is a checked position in the * list and false otherwise, or <code>null</code> if the choice * mode is set to {@link #CHOICE_MODE_NONE}. */
public SparseBooleanArray getCheckedItemPositions() { if (mChoiceMode != CHOICE_MODE_NONE) { return mCheckStates; } return null; }
Returns the set of checked items ids. The result is only valid if the choice mode has not been set to CHOICE_MODE_NONE and the adapter has stable IDs. (Adapter.hasStableIds() == true)
Returns:A new array which contains the id of each checked item in the list.
/** * Returns the set of checked items ids. The result is only valid if the * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) * * @return A new array which contains the id of each checked item in the * list. */
public long[] getCheckedItemIds() { if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) { return new long[0]; } final LongSparseArray<Integer> idStates = mCheckedIdStates; final int count = idStates.size(); final long[] ids = new long[count]; for (int i = 0; i < count; i++) { ids[i] = idStates.keyAt(i); } return ids; }
Clear any choices previously set
/** * Clear any choices previously set */
public void clearChoices() { if (mCheckStates != null) { mCheckStates.clear(); } if (mCheckedIdStates != null) { mCheckedIdStates.clear(); } mCheckedItemCount = 0; }
Sets the checked state of the specified position. The is only valid if the choice mode has been set to CHOICE_MODE_SINGLE or CHOICE_MODE_MULTIPLE.
Params:
  • position – The item whose checked state is to be checked
  • value – The new checked state for the item
/** * Sets the checked state of the specified position. The is only valid if * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or * {@link #CHOICE_MODE_MULTIPLE}. * * @param position The item whose checked state is to be checked * @param value The new checked state for the item */
public void setItemChecked(int position, boolean value) { if (mChoiceMode == CHOICE_MODE_NONE) { return; } // Start selection mode if needed. We don't need to if we're unchecking something. if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { if (mMultiChoiceModeCallback == null || !mMultiChoiceModeCallback.hasWrappedCallback()) { throw new IllegalStateException("AbsListView: attempted to start selection mode " + "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " + "supplied. Call setMultiChoiceModeListener to set a callback."); } mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); } final boolean itemCheckChanged; if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { boolean oldValue = mCheckStates.get(position); mCheckStates.put(position, value); if (mCheckedIdStates != null && mAdapter.hasStableIds()) { if (value) { mCheckedIdStates.put(mAdapter.getItemId(position), position); } else { mCheckedIdStates.delete(mAdapter.getItemId(position)); } } itemCheckChanged = oldValue != value; if (itemCheckChanged) { if (value) { mCheckedItemCount++; } else { mCheckedItemCount--; } } if (mChoiceActionMode != null) { final long id = mAdapter.getItemId(position); mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, position, id, value); } } else { boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds(); // Clear all values if we're checking something, or unchecking the currently // selected item itemCheckChanged = isItemChecked(position) != value; if (value || isItemChecked(position)) { mCheckStates.clear(); if (updateIds) { mCheckedIdStates.clear(); } } // this may end up selecting the value we just cleared but this way // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on if (value) { mCheckStates.put(position, true); if (updateIds) { mCheckedIdStates.put(mAdapter.getItemId(position), position); } mCheckedItemCount = 1; } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { mCheckedItemCount = 0; } } // Do not generate a data change while we are in the layout phase or data has not changed if (!mInLayout && !mBlockLayoutRequests && itemCheckChanged) { mDataChanged = true; rememberSyncState(); requestLayout(); } } @Override public boolean performItemClick(View view, int position, long id) { boolean handled = false; boolean dispatchItemClick = true; if (mChoiceMode != CHOICE_MODE_NONE) { handled = true; boolean checkedStateChanged = false; if (mChoiceMode == CHOICE_MODE_MULTIPLE || (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) { boolean checked = !mCheckStates.get(position, false); mCheckStates.put(position, checked); if (mCheckedIdStates != null && mAdapter.hasStableIds()) { if (checked) { mCheckedIdStates.put(mAdapter.getItemId(position), position); } else { mCheckedIdStates.delete(mAdapter.getItemId(position)); } } if (checked) { mCheckedItemCount++; } else { mCheckedItemCount--; } if (mChoiceActionMode != null) { mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, position, id, checked); dispatchItemClick = false; } checkedStateChanged = true; } else if (mChoiceMode == CHOICE_MODE_SINGLE) { boolean checked = !mCheckStates.get(position, false); if (checked) { mCheckStates.clear(); mCheckStates.put(position, true); if (mCheckedIdStates != null && mAdapter.hasStableIds()) { mCheckedIdStates.clear(); mCheckedIdStates.put(mAdapter.getItemId(position), position); } mCheckedItemCount = 1; } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { mCheckedItemCount = 0; } checkedStateChanged = true; } if (checkedStateChanged) { updateOnScreenCheckedViews(); } } if (dispatchItemClick) { handled |= super.performItemClick(view, position, id); } return handled; }
Perform a quick, in-place update of the checked or activated state on all visible item views. This should only be called when a valid choice mode is active.
/** * Perform a quick, in-place update of the checked or activated state * on all visible item views. This should only be called when a valid * choice mode is active. */
private void updateOnScreenCheckedViews() { final int firstPos = mFirstPosition; final int count = getChildCount(); final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final int position = firstPos + i; if (child instanceof Checkable) { ((Checkable) child).setChecked(mCheckStates.get(position)); } else if (useActivated) { child.setActivated(mCheckStates.get(position)); } } }
See Also:
  • setChoiceMode(int)
Returns:The current choice mode
/** * @see #setChoiceMode(int) * * @return The current choice mode */
public int getChoiceMode() { return mChoiceMode; }
Defines the choice behavior for the List. By default, Lists do not have any choice behavior (CHOICE_MODE_NONE). By setting the choiceMode to CHOICE_MODE_SINGLE, the List allows up to one item to be in a chosen state. By setting the choiceMode to CHOICE_MODE_MULTIPLE, the list allows any number of items to be chosen.
Params:
/** * Defines the choice behavior for the List. By default, Lists do not have any choice behavior * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the * List allows up to one item to be in a chosen state. By setting the choiceMode to * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. * * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or * {@link #CHOICE_MODE_MULTIPLE} */
public void setChoiceMode(int choiceMode) { mChoiceMode = choiceMode; if (mChoiceActionMode != null) { mChoiceActionMode.finish(); mChoiceActionMode = null; } if (mChoiceMode != CHOICE_MODE_NONE) { if (mCheckStates == null) { mCheckStates = new SparseBooleanArray(0); } if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { mCheckedIdStates = new LongSparseArray<Integer>(0); } // Modal multi-choice mode only has choices when the mode is active. Clear them. if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { clearChoices(); setLongClickable(true); } } }
Set a MultiChoiceModeListener that will manage the lifecycle of the selection ActionMode. Only used when the choice mode is set to CHOICE_MODE_MULTIPLE_MODAL.
Params:
  • listener – Listener that will manage the selection mode
See Also:
/** * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the * selection {@link ActionMode}. Only used when the choice mode is set to * {@link #CHOICE_MODE_MULTIPLE_MODAL}. * * @param listener Listener that will manage the selection mode * * @see #setChoiceMode(int) */
public void setMultiChoiceModeListener(MultiChoiceModeListener listener) { if (mMultiChoiceModeCallback == null) { mMultiChoiceModeCallback = new MultiChoiceModeWrapper(); } mMultiChoiceModeCallback.setWrapped(listener); }
Returns:true if all list content currently fits within the view boundaries
/** * @return true if all list content currently fits within the view boundaries */
private boolean contentFits() { final int childCount = getChildCount(); if (childCount == 0) return true; if (childCount != mItemCount) return false; return getChildAt(0).getTop() >= mListPadding.top && getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom; }
Specifies whether fast scrolling is enabled or disabled.

When fast scrolling is enabled, the user can quickly scroll through lists by dragging the fast scroll thumb.

If the adapter backing this list implements SectionIndexer, the fast scroller will display section header previews as the user scrolls. Additionally, the user will be able to quickly jump between sections by tapping along the length of the scroll bar.

Params:
  • enabled – true to enable fast scrolling, false otherwise
See Also:
/** * Specifies whether fast scrolling is enabled or disabled. * <p> * When fast scrolling is enabled, the user can quickly scroll through lists * by dragging the fast scroll thumb. * <p> * If the adapter backing this list implements {@link SectionIndexer}, the * fast scroller will display section header previews as the user scrolls. * Additionally, the user will be able to quickly jump between sections by * tapping along the length of the scroll bar. * * @see SectionIndexer * @see #isFastScrollEnabled() * @param enabled true to enable fast scrolling, false otherwise */
public void setFastScrollEnabled(final boolean enabled) { if (mFastScrollEnabled != enabled) { mFastScrollEnabled = enabled; if (isOwnerThread()) { setFastScrollerEnabledUiThread(enabled); } else { post(new Runnable() { @Override public void run() { setFastScrollerEnabledUiThread(enabled); } }); } } } private void setFastScrollerEnabledUiThread(boolean enabled) { if (mFastScroll != null) { mFastScroll.setEnabled(enabled); } else if (enabled) { mFastScroll = new FastScroller(this, mFastScrollStyle); mFastScroll.setEnabled(true); } resolvePadding(); if (mFastScroll != null) { mFastScroll.updateLayout(); } }
Specifies the style of the fast scroller decorations.
Params:
  • styleResId – style resource containing fast scroller properties
See Also:
  • FastScroll.FastScroll
/** * Specifies the style of the fast scroller decorations. * * @param styleResId style resource containing fast scroller properties * @see android.R.styleable#FastScroll */
public void setFastScrollStyle(int styleResId) { if (mFastScroll == null) { mFastScrollStyle = styleResId; } else { mFastScroll.setStyle(styleResId); } }
Set whether or not the fast scroller should always be shown in place of the standard scroll bars. This will enable fast scrolling if it is not already enabled.

Fast scrollers shown in this way will not fade out and will be a permanent fixture within the list. This is best combined with an inset scroll bar style to ensure the scroll bar does not overlap content.

Params:
  • alwaysShow – true if the fast scroller should always be displayed, false otherwise
See Also:
/** * Set whether or not the fast scroller should always be shown in place of * the standard scroll bars. This will enable fast scrolling if it is not * already enabled. * <p> * Fast scrollers shown in this way will not fade out and will be a * permanent fixture within the list. This is best combined with an inset * scroll bar style to ensure the scroll bar does not overlap content. * * @param alwaysShow true if the fast scroller should always be displayed, * false otherwise * @see #setScrollBarStyle(int) * @see #setFastScrollEnabled(boolean) */
public void setFastScrollAlwaysVisible(final boolean alwaysShow) { if (mFastScrollAlwaysVisible != alwaysShow) { if (alwaysShow && !mFastScrollEnabled) { setFastScrollEnabled(true); } mFastScrollAlwaysVisible = alwaysShow; if (isOwnerThread()) { setFastScrollerAlwaysVisibleUiThread(alwaysShow); } else { post(new Runnable() { @Override public void run() { setFastScrollerAlwaysVisibleUiThread(alwaysShow); } }); } } } private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) { if (mFastScroll != null) { mFastScroll.setAlwaysShow(alwaysShow); } }
Returns:whether the current thread is the one that created the view
/** * @return whether the current thread is the one that created the view */
private boolean isOwnerThread() { return mOwnerThread == Thread.currentThread(); }
Returns true if the fast scroller is set to always show on this view.
See Also:
Returns:true if the fast scroller will always show
/** * Returns true if the fast scroller is set to always show on this view. * * @return true if the fast scroller will always show * @see #setFastScrollAlwaysVisible(boolean) */
public boolean isFastScrollAlwaysVisible() { if (mFastScroll == null) { return mFastScrollEnabled && mFastScrollAlwaysVisible; } else { return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled(); } } @Override public int getVerticalScrollbarWidth() { if (mFastScroll != null && mFastScroll.isEnabled()) { return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth()); } return super.getVerticalScrollbarWidth(); }
Returns true if the fast scroller is enabled.
See Also:
  • setFastScrollEnabled(boolean)
Returns:true if fast scroll is enabled, false otherwise
/** * Returns true if the fast scroller is enabled. * * @see #setFastScrollEnabled(boolean) * @return true if fast scroll is enabled, false otherwise */
@ViewDebug.ExportedProperty public boolean isFastScrollEnabled() { if (mFastScroll == null) { return mFastScrollEnabled; } else { return mFastScroll.isEnabled(); } } @Override public void setVerticalScrollbarPosition(int position) { super.setVerticalScrollbarPosition(position); if (mFastScroll != null) { mFastScroll.setScrollbarPosition(position); } } @Override public void setScrollBarStyle(int style) { super.setScrollBarStyle(style); if (mFastScroll != null) { mFastScroll.setScrollBarStyle(style); } }
If fast scroll is enabled, then don't draw the vertical scrollbar.
@hide
/** * If fast scroll is enabled, then don't draw the vertical scrollbar. * @hide */
@Override protected boolean isVerticalScrollBarHidden() { return isFastScrollEnabled(); }
When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed based on the number of visible pixels in the visible items. This however assumes that all list items have the same height. If you use a list in which items have different heights, the scrollbar will change appearance as the user scrolls through the list. To avoid this issue, you need to disable this property. When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based solely on the number of items in the adapter and the position of the visible items inside the adapter. This provides a stable scrollbar as the user navigates through a list of items with varying heights.
Params:
  • enabled – Whether or not to enable smooth scrollbar.
See Also:
@attrref android.R.styleable#AbsListView_smoothScrollbar
/** * When smooth scrollbar is enabled, the position and size of the scrollbar thumb * is computed based on the number of visible pixels in the visible items. This * however assumes that all list items have the same height. If you use a list in * which items have different heights, the scrollbar will change appearance as the * user scrolls through the list. To avoid this issue, you need to disable this * property. * * When smooth scrollbar is disabled, the position and size of the scrollbar thumb * is based solely on the number of items in the adapter and the position of the * visible items inside the adapter. This provides a stable scrollbar as the user * navigates through a list of items with varying heights. * * @param enabled Whether or not to enable smooth scrollbar. * * @see #setSmoothScrollbarEnabled(boolean) * @attr ref android.R.styleable#AbsListView_smoothScrollbar */
public void setSmoothScrollbarEnabled(boolean enabled) { mSmoothScrollbarEnabled = enabled; }
Returns the current state of the fast scroll feature.
See Also:
Returns:True if smooth scrollbar is enabled is enabled, false otherwise.
/** * Returns the current state of the fast scroll feature. * * @return True if smooth scrollbar is enabled is enabled, false otherwise. * * @see #setSmoothScrollbarEnabled(boolean) */
@ViewDebug.ExportedProperty public boolean isSmoothScrollbarEnabled() { return mSmoothScrollbarEnabled; }
Set the listener that will receive notifications every time the list scrolls.
Params:
  • l – the scroll listener
/** * Set the listener that will receive notifications every time the list scrolls. * * @param l the scroll listener */
public void setOnScrollListener(OnScrollListener l) { mOnScrollListener = l; invokeOnItemScrollListener(); }
Notify our scroll listener (if there is one) of a change in scroll state
/** * Notify our scroll listener (if there is one) of a change in scroll state */
void invokeOnItemScrollListener() { if (mFastScroll != null) { mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount); } if (mOnScrollListener != null) { mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount); } onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. }
@hide
/** @hide */
@Override public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { // Since this class calls onScrollChanged even if the mFirstPosition and the // child count have not changed we will avoid sending duplicate accessibility // events. if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { final int firstVisiblePosition = getFirstVisiblePosition(); final int lastVisiblePosition = getLastVisiblePosition(); if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) { return; } else { mLastAccessibilityScrollEventFromIndex = firstVisiblePosition; mLastAccessibilityScrollEventToIndex = lastVisiblePosition; } } super.sendAccessibilityEventUnchecked(event); } @Override public CharSequence getAccessibilityClassName() { return AbsListView.class.getName(); }
@hide
/** @hide */
@Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); if (isEnabled()) { if (canScrollUp()) { info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD); info.addAction(AccessibilityAction.ACTION_SCROLL_UP); info.setScrollable(true); } if (canScrollDown()) { info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD); info.addAction(AccessibilityAction.ACTION_SCROLL_DOWN); info.setScrollable(true); } } info.removeAction(AccessibilityAction.ACTION_CLICK); info.setClickable(false); } int getSelectionModeForAccessibility() { final int choiceMode = getChoiceMode(); switch (choiceMode) { case CHOICE_MODE_NONE: return CollectionInfo.SELECTION_MODE_NONE; case CHOICE_MODE_SINGLE: return CollectionInfo.SELECTION_MODE_SINGLE; case CHOICE_MODE_MULTIPLE: case CHOICE_MODE_MULTIPLE_MODAL: return CollectionInfo.SELECTION_MODE_MULTIPLE; default: return CollectionInfo.SELECTION_MODE_NONE; } }
@hide
/** @hide */
@Override public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (super.performAccessibilityActionInternal(action, arguments)) { return true; } switch (action) { case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: case R.id.accessibilityActionScrollDown: { if (isEnabled() && canScrollDown()) { final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION); return true; } } return false; case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: case R.id.accessibilityActionScrollUp: { if (isEnabled() && canScrollUp()) { final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION); return true; } } return false; } return false; }
@hide
/** @hide */
@Override public View findViewByAccessibilityIdTraversal(int accessibilityId) { if (accessibilityId == getAccessibilityViewId()) { return this; } return super.findViewByAccessibilityIdTraversal(accessibilityId); }
Indicates whether the children's drawing cache is used during a scroll. By default, the drawing cache is enabled but this will consume more memory.
See Also:
Returns:true if the scrolling cache is enabled, false otherwise
/** * Indicates whether the children's drawing cache is used during a scroll. * By default, the drawing cache is enabled but this will consume more memory. * * @return true if the scrolling cache is enabled, false otherwise * * @see #setScrollingCacheEnabled(boolean) * @see View#setDrawingCacheEnabled(boolean) */
@ViewDebug.ExportedProperty public boolean isScrollingCacheEnabled() { return mScrollingCacheEnabled; }
Enables or disables the children's drawing cache during a scroll. By default, the drawing cache is enabled but this will use more memory. When the scrolling cache is enabled, the caches are kept after the first scrolling. You can manually clear the cache by calling ViewGroup.setChildrenDrawingCacheEnabled(boolean).
Params:
  • enabled – true to enable the scroll cache, false otherwise
See Also:
/** * Enables or disables the children's drawing cache during a scroll. * By default, the drawing cache is enabled but this will use more memory. * * When the scrolling cache is enabled, the caches are kept after the * first scrolling. You can manually clear the cache by calling * {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}. * * @param enabled true to enable the scroll cache, false otherwise * * @see #isScrollingCacheEnabled() * @see View#setDrawingCacheEnabled(boolean) */
public void setScrollingCacheEnabled(boolean enabled) { if (mScrollingCacheEnabled && !enabled) { clearScrollingCache(); } mScrollingCacheEnabled = enabled; }
Enables or disables the type filter window. If enabled, typing when this view has focus will filter the children to match the users input. Note that the Adapter used by this view must implement the Filterable interface.
Params:
  • textFilterEnabled – true to enable type filtering, false otherwise
See Also:
/** * Enables or disables the type filter window. If enabled, typing when * this view has focus will filter the children to match the users input. * Note that the {@link Adapter} used by this view must implement the * {@link Filterable} interface. * * @param textFilterEnabled true to enable type filtering, false otherwise * * @see Filterable */
public void setTextFilterEnabled(boolean textFilterEnabled) { mTextFilterEnabled = textFilterEnabled; }
Indicates whether type filtering is enabled for this view
See Also:
Returns:true if type filtering is enabled, false otherwise
/** * Indicates whether type filtering is enabled for this view * * @return true if type filtering is enabled, false otherwise * * @see #setTextFilterEnabled(boolean) * @see Filterable */
@ViewDebug.ExportedProperty public boolean isTextFilterEnabled() { return mTextFilterEnabled; } @Override public void getFocusedRect(Rect r) { View view = getSelectedView(); if (view != null && view.getParent() == this) { // the focused rectangle of the selected view offset into the // coordinate space of this view. view.getFocusedRect(r); offsetDescendantRectToMyCoords(view, r); } else { // otherwise, just the norm super.getFocusedRect(r); } } private void useDefaultSelector() { setSelector(getContext().getDrawable( com.android.internal.R.drawable.list_selector_background)); }
Indicates whether the content of this view is pinned to, or stacked from, the bottom edge.
Returns:true if the content is stacked from the bottom edge, false otherwise
/** * Indicates whether the content of this view is pinned to, or stacked from, * the bottom edge. * * @return true if the content is stacked from the bottom edge, false otherwise */
@ViewDebug.ExportedProperty public boolean isStackFromBottom() { return mStackFromBottom; }
When stack from bottom is set to true, the list fills its content starting from the bottom of the view.
Params:
  • stackFromBottom – true to pin the view's content to the bottom edge, false to pin the view's content to the top edge
/** * When stack from bottom is set to true, the list fills its content starting from * the bottom of the view. * * @param stackFromBottom true to pin the view's content to the bottom edge, * false to pin the view's content to the top edge */
public void setStackFromBottom(boolean stackFromBottom) { if (mStackFromBottom != stackFromBottom) { mStackFromBottom = stackFromBottom; requestLayoutIfNecessary(); } } void requestLayoutIfNecessary() { if (getChildCount() > 0) { resetList(); requestLayout(); invalidate(); } } static class SavedState extends BaseSavedState { long selectedId; long firstId; int viewTop; int position; int height; String filter; boolean inActionMode; int checkedItemCount; SparseBooleanArray checkState; LongSparseArray<Integer> checkIdState;
Constructor called from AbsListView.onSaveInstanceState()
/** * Constructor called from {@link AbsListView#onSaveInstanceState()} */
SavedState(Parcelable superState) { super(superState); }
Constructor called from CREATOR
/** * Constructor called from {@link #CREATOR} */
private SavedState(Parcel in) { super(in); selectedId = in.readLong(); firstId = in.readLong(); viewTop = in.readInt(); position = in.readInt(); height = in.readInt(); filter = in.readString(); inActionMode = in.readByte() != 0; checkedItemCount = in.readInt(); checkState = in.readSparseBooleanArray(); final int N = in.readInt(); if (N > 0) { checkIdState = new LongSparseArray<Integer>(); for (int i=0; i<N; i++) { final long key = in.readLong(); final int value = in.readInt(); checkIdState.put(key, value); } } } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeLong(selectedId); out.writeLong(firstId); out.writeInt(viewTop); out.writeInt(position); out.writeInt(height); out.writeString(filter); out.writeByte((byte) (inActionMode ? 1 : 0)); out.writeInt(checkedItemCount); out.writeSparseBooleanArray(checkState); final int N = checkIdState != null ? checkIdState.size() : 0; out.writeInt(N); for (int i=0; i<N; i++) { out.writeLong(checkIdState.keyAt(i)); out.writeInt(checkIdState.valueAt(i)); } } @Override public String toString() { return "AbsListView.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " selectedId=" + selectedId + " firstId=" + firstId + " viewTop=" + viewTop + " position=" + position + " height=" + height + " filter=" + filter + " checkState=" + checkState + "}"; } public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; } @Override public Parcelable onSaveInstanceState() { /* * This doesn't really make sense as the place to dismiss the * popups, but there don't seem to be any other useful hooks * that happen early enough to keep from getting complaints * about having leaked the window. */ dismissPopup(); Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); if (mPendingSync != null) { // Just keep what we last restored. ss.selectedId = mPendingSync.selectedId; ss.firstId = mPendingSync.firstId; ss.viewTop = mPendingSync.viewTop; ss.position = mPendingSync.position; ss.height = mPendingSync.height; ss.filter = mPendingSync.filter; ss.inActionMode = mPendingSync.inActionMode; ss.checkedItemCount = mPendingSync.checkedItemCount; ss.checkState = mPendingSync.checkState; ss.checkIdState = mPendingSync.checkIdState; return ss; } boolean haveChildren = getChildCount() > 0 && mItemCount > 0; long selectedId = getSelectedItemId(); ss.selectedId = selectedId; ss.height = getHeight(); if (selectedId >= 0) { // Remember the selection ss.viewTop = mSelectedTop; ss.position = getSelectedItemPosition(); ss.firstId = INVALID_POSITION; } else { if (haveChildren && mFirstPosition > 0) { // Remember the position of the first child. // We only do this if we are not currently at the top of // the list, for two reasons: // (1) The list may be in the process of becoming empty, in // which case mItemCount may not be 0, but if we try to // ask for any information about position 0 we will crash. // (2) Being "at the top" seems like a special case, anyway, // and the user wouldn't expect to end up somewhere else when // they revisit the list even if its content has changed. View v = getChildAt(0); ss.viewTop = v.getTop(); int firstPos = mFirstPosition; if (firstPos >= mItemCount) { firstPos = mItemCount - 1; } ss.position = firstPos; ss.firstId = mAdapter.getItemId(firstPos); } else { ss.viewTop = 0; ss.firstId = INVALID_POSITION; ss.position = 0; } } ss.filter = null; if (mFiltered) { final EditText textFilter = mTextFilter; if (textFilter != null) { Editable filterText = textFilter.getText(); if (filterText != null) { ss.filter = filterText.toString(); } } } ss.inActionMode = mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null; if (mCheckStates != null) { ss.checkState = mCheckStates.clone(); } if (mCheckedIdStates != null) { final LongSparseArray<Integer> idState = new LongSparseArray<Integer>(); final int count = mCheckedIdStates.size(); for (int i = 0; i < count; i++) { idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i)); } ss.checkIdState = idState; } ss.checkedItemCount = mCheckedItemCount; if (mRemoteAdapter != null) { mRemoteAdapter.saveRemoteViewsCache(); } return ss; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); mDataChanged = true; mSyncHeight = ss.height; if (ss.selectedId >= 0) { mNeedSync = true; mPendingSync = ss; mSyncRowId = ss.selectedId; mSyncPosition = ss.position; mSpecificTop = ss.viewTop; mSyncMode = SYNC_SELECTED_POSITION; } else if (ss.firstId >= 0) { setSelectedPositionInt(INVALID_POSITION); // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync setNextSelectedPositionInt(INVALID_POSITION); mSelectorPosition = INVALID_POSITION; mNeedSync = true; mPendingSync = ss; mSyncRowId = ss.firstId; mSyncPosition = ss.position; mSpecificTop = ss.viewTop; mSyncMode = SYNC_FIRST_POSITION; } setFilterText(ss.filter); if (ss.checkState != null) { mCheckStates = ss.checkState; } if (ss.checkIdState != null) { mCheckedIdStates = ss.checkIdState; } mCheckedItemCount = ss.checkedItemCount; if (ss.inActionMode && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mMultiChoiceModeCallback != null) { mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); } requestLayout(); } private boolean acceptFilter() { return mTextFilterEnabled && getAdapter() instanceof Filterable && ((Filterable) getAdapter()).getFilter() != null; }
Sets the initial value for the text filter.
Params:
  • filterText – The text to use for the filter.
See Also:
/** * Sets the initial value for the text filter. * @param filterText The text to use for the filter. * * @see #setTextFilterEnabled */
public void setFilterText(String filterText) { // TODO: Should we check for acceptFilter()? if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) { createTextFilter(false); // This is going to call our listener onTextChanged, but we might not // be ready to bring up a window yet mTextFilter.setText(filterText); mTextFilter.setSelection(filterText.length()); if (mAdapter instanceof Filterable) { // if mPopup is non-null, then onTextChanged will do the filtering if (mPopup == null) { Filter f = ((Filterable) mAdapter).getFilter(); f.filter(filterText); } // Set filtered to true so we will display the filter window when our main // window is ready mFiltered = true; mDataSetObserver.clearSavedState(); } } }
Returns the list's text filter, if available.
Returns:the list's text filter or null if filtering isn't enabled
/** * Returns the list's text filter, if available. * @return the list's text filter or null if filtering isn't enabled */
public CharSequence getTextFilter() { if (mTextFilterEnabled && mTextFilter != null) { return mTextFilter.getText(); } return null; } @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) { if (!isAttachedToWindow() && mAdapter != null) { // Data may have changed while we were detached and it's valid // to change focus while detached. Refresh so we don't die. mDataChanged = true; mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); } resurrectSelection(); } } @Override public void requestLayout() { if (!mBlockLayoutRequests && !mInLayout) { super.requestLayout(); } }
The list is empty. Clear everything out.
/** * The list is empty. Clear everything out. */
void resetList() { removeAllViewsInLayout(); mFirstPosition = 0; mDataChanged = false; mPositionScrollAfterLayout = null; mNeedSync = false; mPendingSync = null; mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; setSelectedPositionInt(INVALID_POSITION); setNextSelectedPositionInt(INVALID_POSITION); mSelectedTop = 0; mSelectorPosition = INVALID_POSITION; mSelectorRect.setEmpty(); invalidate(); } @Override protected int computeVerticalScrollExtent() { final int count = getChildCount(); if (count > 0) { if (mSmoothScrollbarEnabled) { int extent = count * 100; View view = getChildAt(0); final int top = view.getTop(); int height = view.getHeight(); if (height > 0) { extent += (top * 100) / height; } view = getChildAt(count - 1); final int bottom = view.getBottom(); height = view.getHeight(); if (height > 0) { extent -= ((bottom - getHeight()) * 100) / height; } return extent; } else { return 1; } } return 0; } @Override protected int computeVerticalScrollOffset() { final int firstPosition = mFirstPosition; final int childCount = getChildCount(); if (firstPosition >= 0 && childCount > 0) { if (mSmoothScrollbarEnabled) { final View view = getChildAt(0); final int top = view.getTop(); int height = view.getHeight(); if (height > 0) { return Math.max(firstPosition * 100 - (top * 100) / height + (int)((float)mScrollY / getHeight() * mItemCount * 100), 0); } } else { int index; final int count = mItemCount; if (firstPosition == 0) { index = 0; } else if (firstPosition + childCount == count) { index = count; } else { index = firstPosition + childCount / 2; } return (int) (firstPosition + childCount * (index / (float) count)); } } return 0; } @Override protected int computeVerticalScrollRange() { int result; if (mSmoothScrollbarEnabled) { result = Math.max(mItemCount * 100, 0); if (mScrollY != 0) { // Compensate for overscroll result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100)); } } else { result = mItemCount; } return result; } @Override protected float getTopFadingEdgeStrength() { final int count = getChildCount(); final float fadeEdge = super.getTopFadingEdgeStrength(); if (count == 0) { return fadeEdge; } else { if (mFirstPosition > 0) { return 1.0f; } final int top = getChildAt(0).getTop(); final float fadeLength = getVerticalFadingEdgeLength(); return top < mPaddingTop ? -(top - mPaddingTop) / fadeLength : fadeEdge; } } @Override protected float getBottomFadingEdgeStrength() { final int count = getChildCount(); final float fadeEdge = super.getBottomFadingEdgeStrength(); if (count == 0) { return fadeEdge; } else { if (mFirstPosition + count - 1 < mItemCount - 1) { return 1.0f; } final int bottom = getChildAt(count - 1).getBottom(); final int height = getHeight(); final float fadeLength = getVerticalFadingEdgeLength(); return bottom > height - mPaddingBottom ? (bottom - height + mPaddingBottom) / fadeLength : fadeEdge; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mSelector == null) { useDefaultSelector(); } final Rect listPadding = mListPadding; listPadding.left = mSelectionLeftPadding + mPaddingLeft; listPadding.top = mSelectionTopPadding + mPaddingTop; listPadding.right = mSelectionRightPadding + mPaddingRight; listPadding.bottom = mSelectionBottomPadding + mPaddingBottom; // Check if our previous measured size was at a point where we should scroll later. if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) { final int childCount = getChildCount(); final int listBottom = getHeight() - getPaddingBottom(); final View lastChild = getChildAt(childCount - 1); final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom; mForceTranscriptScroll = mFirstPosition + childCount >= mLastHandledItemCount && lastBottom <= listBottom; } }
Subclasses should NOT override this method but layoutChildren() instead.
/** * Subclasses should NOT override this method but * {@link #layoutChildren()} instead. */
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; final int childCount = getChildCount(); if (changed) { for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } layoutChildren(); mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; // TODO: Move somewhere sane. This doesn't belong in onLayout(). if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } mInLayout = false; }
@hide
/** * @hide */
@Override protected boolean setFrame(int left, int top, int right, int bottom) { final boolean changed = super.setFrame(left, top, right, bottom); if (changed) { // Reposition the popup when the frame has changed. This includes // translating the widget, not just changing its dimension. The // filter popup needs to follow the widget. final boolean visible = getWindowVisibility() == View.VISIBLE; if (mFiltered && visible && mPopup != null && mPopup.isShowing()) { positionPopup(); } } return changed; }
Subclasses must override this method to layout their children.
/** * Subclasses must override this method to layout their children. */
protected void layoutChildren() { }
Params:
  • focusedView – view that holds accessibility focus
Returns:direct child that contains accessibility focus, or null if no child contains accessibility focus
/** * @param focusedView view that holds accessibility focus * @return direct child that contains accessibility focus, or null if no * child contains accessibility focus */
View getAccessibilityFocusedChild(View focusedView) { ViewParent viewParent = focusedView.getParent(); while ((viewParent instanceof View) && (viewParent != this)) { focusedView = (View) viewParent; viewParent = viewParent.getParent(); } if (!(viewParent instanceof View)) { return null; } return focusedView; } void updateScrollIndicators() { if (mScrollUp != null) { mScrollUp.setVisibility(canScrollUp() ? View.VISIBLE : View.INVISIBLE); } if (mScrollDown != null) { mScrollDown.setVisibility(canScrollDown() ? View.VISIBLE : View.INVISIBLE); } } private boolean canScrollUp() { boolean canScrollUp; // 0th element is not visible canScrollUp = mFirstPosition > 0; // ... Or top of 0th element is not visible if (!canScrollUp) { if (getChildCount() > 0) { View child = getChildAt(0); canScrollUp = child.getTop() < mListPadding.top; } } return canScrollUp; } private boolean canScrollDown() { boolean canScrollDown; int count = getChildCount(); // Last item is not visible canScrollDown = (mFirstPosition + count) < mItemCount; // ... Or bottom of the last element is not visible if (!canScrollDown && count > 0) { View child = getChildAt(count - 1); canScrollDown = child.getBottom() > mBottom - mListPadding.bottom; } return canScrollDown; } @Override @ViewDebug.ExportedProperty public View getSelectedView() { if (mItemCount > 0 && mSelectedPosition >= 0) { return getChildAt(mSelectedPosition - mFirstPosition); } else { return null; } }
List padding is the maximum of the normal view's padding and the padding of the selector.
See Also:
Returns:The top list padding.
/** * List padding is the maximum of the normal view's padding and the padding of the selector. * * @see android.view.View#getPaddingTop() * @see #getSelector() * * @return The top list padding. */
public int getListPaddingTop() { return mListPadding.top; }
List padding is the maximum of the normal view's padding and the padding of the selector.
See Also:
Returns:The bottom list padding.
/** * List padding is the maximum of the normal view's padding and the padding of the selector. * * @see android.view.View#getPaddingBottom() * @see #getSelector() * * @return The bottom list padding. */
public int getListPaddingBottom() { return mListPadding.bottom; }
List padding is the maximum of the normal view's padding and the padding of the selector.
See Also:
Returns:The left list padding.
/** * List padding is the maximum of the normal view's padding and the padding of the selector. * * @see android.view.View#getPaddingLeft() * @see #getSelector() * * @return The left list padding. */
public int getListPaddingLeft() { return mListPadding.left; }
List padding is the maximum of the normal view's padding and the padding of the selector.
See Also:
Returns:The right list padding.
/** * List padding is the maximum of the normal view's padding and the padding of the selector. * * @see android.view.View#getPaddingRight() * @see #getSelector() * * @return The right list padding. */
public int getListPaddingRight() { return mListPadding.right; }
Gets a view and have it show the data associated with the specified position. This is called when we have already discovered that the view is not available for reuse in the recycle bin. The only choices left are converting an old view or making a new one.
Params:
  • position – the position to display
  • outMetadata – an array of at least 1 boolean where the first entry will be set true if the view is currently attached to the window, false otherwise (e.g. newly-inflated or remained scrap for multiple layout passes)
Returns:A view displaying the data associated with the specified position
/** * Gets a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view * is not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position the position to display * @param outMetadata an array of at least 1 boolean where the first entry * will be set {@code true} if the view is currently * attached to the window, {@code false} otherwise (e.g. * newly-inflated or remained scrap for multiple layout * passes) * * @return A view displaying the data associated with the specified position */
View obtainView(int position, boolean[] outMetadata) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); outMetadata[0] = false; // Check whether we have a transient state view. Attempt to re-bind the // data and discard the view if we fail. final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) { final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); // If the view type hasn't changed, attempt to re-bind the data. if (params.viewType == mAdapter.getItemViewType(position)) { final View updatedView = mAdapter.getView(position, transientView, this); // If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) { setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position); } } outMetadata[0] = true; // Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; } final View scrapView = mRecycler.getScrapView(position); final View child = mAdapter.getView(position, scrapView, this); if (scrapView != null) { if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); } else if (child.isTemporarilyDetached()) { outMetadata[0] = true; // Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); } } if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } setItemViewLayoutParams(child, position); if (AccessibilityManager.getInstance(mContext).isEnabled()) { if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new ListItemAccessibilityDelegate(); } if (child.getAccessibilityDelegate() == null) { child.setAccessibilityDelegate(mAccessibilityDelegate); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return child; } private void setItemViewLayoutParams(View child, int position) { final ViewGroup.LayoutParams vlp = child.getLayoutParams(); LayoutParams lp; if (vlp == null) { lp = (LayoutParams) generateDefaultLayoutParams(); } else if (!checkLayoutParams(vlp)) { lp = (LayoutParams) generateLayoutParams(vlp); } else { lp = (LayoutParams) vlp; } if (mAdapterHasStableIds) { lp.itemId = mAdapter.getItemId(position); } lp.viewType = mAdapter.getItemViewType(position); lp.isEnabled = mAdapter.isEnabled(position); if (lp != vlp) { child.setLayoutParams(lp); } } class ListItemAccessibilityDelegate extends AccessibilityDelegate { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); final int position = getPositionForView(host); onInitializeAccessibilityNodeInfoForItem(host, position, info); } @Override public boolean performAccessibilityAction(View host, int action, Bundle arguments) { if (super.performAccessibilityAction(host, action, arguments)) { return true; } final int position = getPositionForView(host); if (position == INVALID_POSITION || mAdapter == null) { // Cannot perform actions on invalid items. return false; } if (position >= mAdapter.getCount()) { // The position is no longer valid, likely due to a data set // change. We could fail here for all data set changes, since // there is a chance that the data bound to the view may no // longer exist at the same position within the adapter, but // it's more consistent with the standard touch interaction to // click at whatever may have moved into that position. return false; } final boolean isItemEnabled; final ViewGroup.LayoutParams lp = host.getLayoutParams(); if (lp instanceof AbsListView.LayoutParams) { isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled; } else { isItemEnabled = false; } if (!isEnabled() || !isItemEnabled) { // Cannot perform actions on disabled items. return false; } switch (action) { case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: { if (getSelectedItemPosition() == position) { setSelection(INVALID_POSITION); return true; } } return false; case AccessibilityNodeInfo.ACTION_SELECT: { if (getSelectedItemPosition() != position) { setSelection(position); return true; } } return false; case AccessibilityNodeInfo.ACTION_CLICK: { if (isItemClickable(host)) { final long id = getItemIdAtPosition(position); return performItemClick(host, position, id); } } return false; case AccessibilityNodeInfo.ACTION_LONG_CLICK: { if (isLongClickable()) { final long id = getItemIdAtPosition(position); return performLongPress(host, position, id); } } return false; } return false; } }
Initializes an AccessibilityNodeInfo with information about a particular item in the list.
Params:
  • view – View representing the list item.
  • position – Position of the list item within the adapter.
  • info – Node info to populate.
/** * Initializes an {@link AccessibilityNodeInfo} with information about a * particular item in the list. * * @param view View representing the list item. * @param position Position of the list item within the adapter. * @param info Node info to populate. */
public void onInitializeAccessibilityNodeInfoForItem( View view, int position, AccessibilityNodeInfo info) { if (position == INVALID_POSITION) { // The item doesn't exist, so there's not much we can do here. return; } final boolean isItemEnabled; final ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp instanceof AbsListView.LayoutParams) { isItemEnabled = ((AbsListView.LayoutParams) lp).isEnabled; } else { isItemEnabled = false; } if (!isEnabled() || !isItemEnabled) { info.setEnabled(false); return; } if (position == getSelectedItemPosition()) { info.setSelected(true); info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION); } else { info.addAction(AccessibilityAction.ACTION_SELECT); } if (isItemClickable(view)) { info.addAction(AccessibilityAction.ACTION_CLICK); info.setClickable(true); } if (isLongClickable()) { info.addAction(AccessibilityAction.ACTION_LONG_CLICK); info.setLongClickable(true); } } private boolean isItemClickable(View view) { return !view.hasExplicitFocusable(); }
Positions the selector in a way that mimics touch.
/** * Positions the selector in a way that mimics touch. */
void positionSelectorLikeTouch(int position, View sel, float x, float y) { positionSelector(position, sel, true, x, y); }
Positions the selector in a way that mimics keyboard focus.
/** * Positions the selector in a way that mimics keyboard focus. */
void positionSelectorLikeFocus(int position, View sel) { if (mSelector != null && mSelectorPosition != position && position != INVALID_POSITION) { final Rect bounds = mSelectorRect; final float x = bounds.exactCenterX(); final float y = bounds.exactCenterY(); positionSelector(position, sel, true, x, y); } else { positionSelector(position, sel); } } void positionSelector(int position, View sel) { positionSelector(position, sel, false, -1, -1); } private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) { final boolean positionChanged = position != mSelectorPosition; if (position != INVALID_POSITION) { mSelectorPosition = position; } final Rect selectorRect = mSelectorRect; selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); if (sel instanceof SelectionBoundsAdjuster) { ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect); } // Adjust for selection padding. selectorRect.left -= mSelectionLeftPadding; selectorRect.top -= mSelectionTopPadding; selectorRect.right += mSelectionRightPadding; selectorRect.bottom += mSelectionBottomPadding; // Update the child enabled state prior to updating the selector. final boolean isChildViewEnabled = sel.isEnabled(); if (mIsChildViewEnabled != isChildViewEnabled) { mIsChildViewEnabled = isChildViewEnabled; } // Update the selector drawable's state and position. final Drawable selector = mSelector; if (selector != null) { if (positionChanged) { // Wipe out the current selector state so that we can start // over in the new position with a fresh state. selector.setVisible(false, false); selector.setState(StateSet.NOTHING); } selector.setBounds(selectorRect); if (positionChanged) { if (getVisibility() == VISIBLE) { selector.setVisible(true, false); } updateSelectorState(); } if (manageHotspot) { selector.setHotspot(x, y); } } } @Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { saveCount = canvas.save(); final int scrollX = mScrollX; final int scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY + mBottom - mTop - mPaddingBottom); mGroupFlags &= ~CLIP_TO_PADDING_MASK; } final boolean drawSelectorOnTop = mDrawSelectorOnTop; if (!drawSelectorOnTop) { drawSelector(canvas); } super.dispatchDraw(canvas); if (drawSelectorOnTop) { drawSelector(canvas); } if (clipToPadding) { canvas.restoreToCount(saveCount); mGroupFlags |= CLIP_TO_PADDING_MASK; } } @Override protected boolean isPaddingOffsetRequired() { return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK; } @Override protected int getLeftPaddingOffset() { return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft; } @Override protected int getTopPaddingOffset() { return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop; } @Override protected int getRightPaddingOffset() { return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight; } @Override protected int getBottomPaddingOffset() { return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom; }
@hide
/** * @hide */
@Override protected void internalSetPadding(int left, int top, int right, int bottom) { super.internalSetPadding(left, top, right, bottom); if (isLayoutRequested()) { handleBoundsChange(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { handleBoundsChange(); if (mFastScroll != null) { mFastScroll.onSizeChanged(w, h, oldw, oldh); } }
Called when bounds of the AbsListView are changed. AbsListView marks data set as changed and force layouts all children that don't have exact measure specs.

This invalidation is necessary, otherwise, AbsListView may think the children are valid and fail to relayout them properly to accommodate for new bounds.

/** * Called when bounds of the AbsListView are changed. AbsListView marks data set as changed * and force layouts all children that don't have exact measure specs. * <p> * This invalidation is necessary, otherwise, AbsListView may think the children are valid and * fail to relayout them properly to accommodate for new bounds. */
void handleBoundsChange() { if (mInLayout) { return; } final int childCount = getChildCount(); if (childCount > 0) { mDataChanged = true; rememberSyncState(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final ViewGroup.LayoutParams lp = child.getLayoutParams(); // force layout child unless it has exact specs if (lp == null || lp.width < 1 || lp.height < 1) { child.forceLayout(); } } } }
Returns:True if the current touch mode requires that we draw the selector in the pressed state.
/** * @return True if the current touch mode requires that we draw the selector in the pressed * state. */
boolean touchModeDrawsInPressedState() { // FIXME use isPressed for this switch (mTouchMode) { case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: return true; default: return false; } }
Indicates whether this view is in a state where the selector should be drawn. This will happen if we have focus but are not in touch mode, or we are in the middle of displaying the pressed state for an item.
Returns:True if the selector should be shown
/** * Indicates whether this view is in a state where the selector should be drawn. This will * happen if we have focus but are not in touch mode, or we are in the middle of displaying * the pressed state for an item. * * @return True if the selector should be shown */
boolean shouldShowSelector() { return (isFocused() && !isInTouchMode()) || (touchModeDrawsInPressedState() && isPressed()); } private void drawSelector(Canvas canvas) { if (shouldDrawSelector()) { final Drawable selector = mSelector; selector.setBounds(mSelectorRect); selector.draw(canvas); } }
@hide
/** * @hide */
@TestApi public final boolean shouldDrawSelector() { return !mSelectorRect.isEmpty(); }
Controls whether the selection highlight drawable should be drawn on top of the item or behind it.
Params:
  • onTop – If true, the selector will be drawn on the item it is highlighting. The default is false.
@attrref android.R.styleable#AbsListView_drawSelectorOnTop
/** * Controls whether the selection highlight drawable should be drawn on top of the item or * behind it. * * @param onTop If true, the selector will be drawn on the item it is highlighting. The default * is false. * * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop */
public void setDrawSelectorOnTop(boolean onTop) { mDrawSelectorOnTop = onTop; }
Set a Drawable that should be used to highlight the currently selected item.
Params:
  • resID – A Drawable resource to use as the selection highlight.
@attrref android.R.styleable#AbsListView_listSelector
/** * Set a Drawable that should be used to highlight the currently selected item. * * @param resID A Drawable resource to use as the selection highlight. * * @attr ref android.R.styleable#AbsListView_listSelector */
public void setSelector(@DrawableRes int resID) { setSelector(getContext().getDrawable(resID)); } public void setSelector(Drawable sel) { if (mSelector != null) { mSelector.setCallback(null); unscheduleDrawable(mSelector); } mSelector = sel; Rect padding = new Rect(); sel.getPadding(padding); mSelectionLeftPadding = padding.left; mSelectionTopPadding = padding.top; mSelectionRightPadding = padding.right; mSelectionBottomPadding = padding.bottom; sel.setCallback(this); updateSelectorState(); }
Returns the selector Drawable that is used to draw the selection in the list.
Returns:the drawable used to display the selector
/** * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the * selection in the list. * * @return the drawable used to display the selector */
public Drawable getSelector() { return mSelector; }
Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if this is a long press.
/** * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if * this is a long press. */
void keyPressed() { if (!isEnabled() || !isClickable()) { return; } Drawable selector = mSelector; Rect selectorRect = mSelectorRect; if (selector != null && (isFocused() || touchModeDrawsInPressedState()) && !selectorRect.isEmpty()) { final View v = getChildAt(mSelectedPosition - mFirstPosition); if (v != null) { if (v.hasExplicitFocusable()) return; v.setPressed(true); } setPressed(true); final boolean longClickable = isLongClickable(); Drawable d = selector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { if (longClickable) { ((TransitionDrawable) d).startTransition( ViewConfiguration.getLongPressTimeout()); } else { ((TransitionDrawable) d).resetTransition(); } } if (longClickable && !mDataChanged) { if (mPendingCheckForKeyLongPress == null) { mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); } mPendingCheckForKeyLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); } } } public void setScrollIndicators(View up, View down) { mScrollUp = up; mScrollDown = down; } void updateSelectorState() { final Drawable selector = mSelector; if (selector != null && selector.isStateful()) { if (shouldShowSelector()) { if (selector.setState(getDrawableStateForSelector())) { invalidateDrawable(selector); } } else { selector.setState(StateSet.NOTHING); } } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); updateSelectorState(); } private int[] getDrawableStateForSelector() { // If the child view is enabled then do the default behavior. if (mIsChildViewEnabled) { // Common case return super.getDrawableState(); } // The selector uses this View's drawable state. The selected child view // is disabled, so we need to remove the enabled state from the drawable // states. final int enabledState = ENABLED_STATE_SET[0]; // If we don't have any extra space, it will return one of the static // state arrays, and clearing the enabled state on those arrays is a // bad thing! If we specify we need extra space, it will create+copy // into a new array that is safely mutable. final int[] state = onCreateDrawableState(1); int enabledPos = -1; for (int i = state.length - 1; i >= 0; i--) { if (state[i] == enabledState) { enabledPos = i; break; } } // Remove the enabled state if (enabledPos >= 0) { System.arraycopy(state, enabledPos + 1, state, enabledPos, state.length - enabledPos - 1); } return state; } @Override public boolean verifyDrawable(@NonNull Drawable dr) { return mSelector == dr || super.verifyDrawable(dr); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mSelector != null) mSelector.jumpToCurrentState(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); final ViewTreeObserver treeObserver = getViewTreeObserver(); treeObserver.addOnTouchModeChangeListener(this); if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) { treeObserver.addOnGlobalLayoutListener(this); } if (mAdapter != null && mDataSetObserver == null) { mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); // Data may have changed while we were detached. Refresh. mDataChanged = true; mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mIsDetaching = true; // Dismiss the popup in case onSaveInstanceState() was not invoked dismissPopup(); // Detach any view left in the scrap heap mRecycler.clear(); final ViewTreeObserver treeObserver = getViewTreeObserver(); treeObserver.removeOnTouchModeChangeListener(this); if (mTextFilterEnabled && mPopup != null) { treeObserver.removeOnGlobalLayoutListener(this); mGlobalLayoutListenerAddedFilter = false; } if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); mDataSetObserver = null; } if (mScrollStrictSpan != null) { mScrollStrictSpan.finish(); mScrollStrictSpan = null; } if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } if (mFlingRunnable != null) { removeCallbacks(mFlingRunnable); } if (mPositionScroller != null) { mPositionScroller.stop(); } if (mClearScrollingCache != null) { removeCallbacks(mClearScrollingCache); } if (mPerformClick != null) { removeCallbacks(mPerformClick); } if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); mTouchModeReset.run(); } mIsDetaching = false; } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF; if (!hasWindowFocus) { setChildrenDrawingCacheEnabled(false); if (mFlingRunnable != null) { removeCallbacks(mFlingRunnable); // let the fling runnable report its new state which // should be idle mFlingRunnable.mSuppressIdleStateChangeCall = false; mFlingRunnable.endFling(); if (mPositionScroller != null) { mPositionScroller.stop(); } if (mScrollY != 0) { mScrollY = 0; invalidateParentCaches(); finishGlows(); invalidate(); } } // Always hide the type filter dismissPopup(); if (touchMode == TOUCH_MODE_OFF) { // Remember the last selected element mResurrectToPosition = mSelectedPosition; } } else { if (mFiltered && !mPopupHidden) { // Show the type filter only if a filter is in effect showPopup(); } // If we changed touch mode since the last time we had focus if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) { // If we come back in trackball mode, we bring the selection back if (touchMode == TOUCH_MODE_OFF) { // This will trigger a layout resurrectSelection(); // If we come back in touch mode, then we want to hide the selector } else { hideSelector(); mLayoutMode = LAYOUT_NORMAL; layoutChildren(); } } } mLastTouchMode = touchMode; } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); if (mFastScroll != null) { mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition()); } }
Creates the ContextMenuInfo returned from getContextMenuInfo(). This methods knows the view, position and ID of the item that received the long press.
Params:
  • view – The view that received the long press.
  • position – The position of the item that received the long press.
  • id – The ID of the item that received the long press.
Returns:The extra information that should be returned by getContextMenuInfo().
/** * Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This * methods knows the view, position and ID of the item that received the * long press. * * @param view The view that received the long press. * @param position The position of the item that received the long press. * @param id The ID of the item that received the long press. * @return The extra information that should be returned by * {@link #getContextMenuInfo()}. */
ContextMenuInfo createContextMenuInfo(View view, int position, long id) { return new AdapterContextMenuInfo(view, position, id); } @Override public void onCancelPendingInputEvents() { super.onCancelPendingInputEvents(); if (mPerformClick != null) { removeCallbacks(mPerformClick); } if (mPendingCheckForTap != null) { removeCallbacks(mPendingCheckForTap); } if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } if (mPendingCheckForKeyLongPress != null) { removeCallbacks(mPendingCheckForKeyLongPress); } }
A base class for Runnables that will check that their view is still attached to the original window as when the Runnable was created.
/** * A base class for Runnables that will check that their view is still attached to * the original window as when the Runnable was created. * */
private class WindowRunnnable { private int mOriginalAttachCount; public void rememberWindowAttachCount() { mOriginalAttachCount = getWindowAttachCount(); } public boolean sameWindow() { return getWindowAttachCount() == mOriginalAttachCount; } } private class PerformClick extends WindowRunnnable implements Runnable { int mClickMotionPosition; @Override public void run() { // The data has changed since we posted this action in the event queue, // bail out before bad things happen if (mDataChanged) return; final ListAdapter adapter = mAdapter; final int motionPosition = mClickMotionPosition; if (adapter != null && mItemCount > 0 && motionPosition != INVALID_POSITION && motionPosition < adapter.getCount() && sameWindow() && adapter.isEnabled(motionPosition)) { final View view = getChildAt(motionPosition - mFirstPosition); // If there is no view, something bad happened (the view scrolled off the // screen, etc.) and we should cancel the click if (view != null) { performItemClick(view, motionPosition, adapter.getItemId(motionPosition)); } } } } private class CheckForLongPress extends WindowRunnnable implements Runnable { private static final int INVALID_COORD = -1; private float mX = INVALID_COORD; private float mY = INVALID_COORD; private void setCoords(float x, float y) { mX = x; mY = y; } @Override public void run() { final int motionPosition = mMotionPosition; final View child = getChildAt(motionPosition - mFirstPosition); if (child != null) { final int longPressPosition = mMotionPosition; final long longPressId = mAdapter.getItemId(mMotionPosition); boolean handled = false; if (sameWindow() && !mDataChanged) { if (mX != INVALID_COORD && mY != INVALID_COORD) { handled = performLongPress(child, longPressPosition, longPressId, mX, mY); } else { handled = performLongPress(child, longPressPosition, longPressId); } } if (handled) { mHasPerformedLongPress = true; mTouchMode = TOUCH_MODE_REST; setPressed(false); child.setPressed(false); } else { mTouchMode = TOUCH_MODE_DONE_WAITING; } } } } private class CheckForKeyLongPress extends WindowRunnnable implements Runnable { @Override public void run() { if (isPressed() && mSelectedPosition >= 0) { int index = mSelectedPosition - mFirstPosition; View v = getChildAt(index); if (!mDataChanged) { boolean handled = false; if (sameWindow()) { handled = performLongPress(v, mSelectedPosition, mSelectedRowId); } if (handled) { setPressed(false); v.setPressed(false); } } else { setPressed(false); if (v != null) v.setPressed(false); } } } } private boolean performStylusButtonPressAction(MotionEvent ev) { if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { final View child = getChildAt(mMotionPosition - mFirstPosition); if (child != null) { final int longPressPosition = mMotionPosition; final long longPressId = mAdapter.getItemId(mMotionPosition); if (performLongPress(child, longPressPosition, longPressId)) { mTouchMode = TOUCH_MODE_REST; setPressed(false); child.setPressed(false); return true; } } } return false; } boolean performLongPress(final View child, final int longPressPosition, final long longPressId) { return performLongPress( child, longPressPosition, longPressId, CheckForLongPress.INVALID_COORD, CheckForLongPress.INVALID_COORD); } boolean performLongPress(final View child, final int longPressPosition, final long longPressId, float x, float y) { // CHOICE_MODE_MULTIPLE_MODAL takes over long press. if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { if (mChoiceActionMode == null && (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) { setItemChecked(longPressPosition, true); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return true; } boolean handled = false; if (mOnItemLongClickListener != null) { handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, child, longPressPosition, longPressId); } if (!handled) { mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); if (x != CheckForLongPress.INVALID_COORD && y != CheckForLongPress.INVALID_COORD) { handled = super.showContextMenuForChild(AbsListView.this, x, y); } else { handled = super.showContextMenuForChild(AbsListView.this); } } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; } @Override protected ContextMenuInfo getContextMenuInfo() { return mContextMenuInfo; } @Override public boolean showContextMenu() { return showContextMenuInternal(0, 0, false); } @Override public boolean showContextMenu(float x, float y) { return showContextMenuInternal(x, y, true); } private boolean showContextMenuInternal(float x, float y, boolean useOffsets) { final int position = pointToPosition((int)x, (int)y); if (position != INVALID_POSITION) { final long id = mAdapter.getItemId(position); View child = getChildAt(position - mFirstPosition); if (child != null) { mContextMenuInfo = createContextMenuInfo(child, position, id); if (useOffsets) { return super.showContextMenuForChild(this, x, y); } else { return super.showContextMenuForChild(this); } } } if (useOffsets) { return super.showContextMenu(x, y); } else { return super.showContextMenu(); } } @Override public boolean showContextMenuForChild(View originalView) { if (isShowingContextMenuWithCoords()) { return false; } return showContextMenuForChildInternal(originalView, 0, 0, false); } @Override public boolean showContextMenuForChild(View originalView, float x, float y) { return showContextMenuForChildInternal(originalView,x, y, true); } private boolean showContextMenuForChildInternal(View originalView, float x, float y, boolean useOffsets) { final int longPressPosition = getPositionForView(originalView); if (longPressPosition < 0) { return false; } final long longPressId = mAdapter.getItemId(longPressPosition); boolean handled = false; if (mOnItemLongClickListener != null) { handled = mOnItemLongClickListener.onItemLongClick(this, originalView, longPressPosition, longPressId); } if (!handled) { final View child = getChildAt(longPressPosition - mFirstPosition); mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); if (useOffsets) { handled = super.showContextMenuForChild(originalView, x, y); } else { handled = super.showContextMenuForChild(originalView); } } return handled; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (KeyEvent.isConfirmKey(keyCode)) { if (!isEnabled()) { return true; } if (isClickable() && isPressed() && mSelectedPosition >= 0 && mAdapter != null && mSelectedPosition < mAdapter.getCount()) { final View view = getChildAt(mSelectedPosition - mFirstPosition); if (view != null) { performItemClick(view, mSelectedPosition, mSelectedRowId); view.setPressed(false); } setPressed(false); return true; } } return super.onKeyUp(keyCode, event); } @Override protected void dispatchSetPressed(boolean pressed) { // Don't dispatch setPressed to our children. We call setPressed on ourselves to // get the selector in the right state, but we don't want to press each child. } @Override public void dispatchDrawableHotspotChanged(float x, float y) { // Don't dispatch hotspot changes to children. We'll manually handle // calling drawableHotspotChanged on the correct child. }
Maps a point to a position in the list.
Params:
  • x – X in local coordinate
  • y – Y in local coordinate
Returns:The position of the item which contains the specified point, or AdapterView<ListAdapter>.INVALID_POSITION if the point does not intersect an item.
/** * Maps a point to a position in the list. * * @param x X in local coordinate * @param y Y in local coordinate * @return The position of the item which contains the specified point, or * {@link #INVALID_POSITION} if the point does not intersect an item. */
public int pointToPosition(int x, int y) { Rect frame = mTouchFrame; if (frame == null) { mTouchFrame = new Rect(); frame = mTouchFrame; } final int count = getChildCount(); for (int i = count - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getVisibility() == View.VISIBLE) { child.getHitRect(frame); if (frame.contains(x, y)) { return mFirstPosition + i; } } } return INVALID_POSITION; }
Maps a point to a the rowId of the item which intersects that point.
Params:
  • x – X in local coordinate
  • y – Y in local coordinate
Returns:The rowId of the item which contains the specified point, or AdapterView<ListAdapter>.INVALID_ROW_ID if the point does not intersect an item.
/** * Maps a point to a the rowId of the item which intersects that point. * * @param x X in local coordinate * @param y Y in local coordinate * @return The rowId of the item which contains the specified point, or {@link #INVALID_ROW_ID} * if the point does not intersect an item. */
public long pointToRowId(int x, int y) { int position = pointToPosition(x, y); if (position >= 0) { return mAdapter.getItemId(position); } return INVALID_ROW_ID; } private final class CheckForTap implements Runnable { float x; float y; @Override public void run() { if (mTouchMode == TOUCH_MODE_DOWN) { mTouchMode = TOUCH_MODE_TAP; final View child = getChildAt(mMotionPosition - mFirstPosition); if (child != null && !child.hasExplicitFocusable()) { mLayoutMode = LAYOUT_NORMAL; if (!mDataChanged) { final float[] point = mTmpPoint; point[0] = x; point[1] = y; transformPointToViewLocal(point, child); child.drawableHotspotChanged(point[0], point[1]); child.setPressed(true); setPressed(true); layoutChildren(); positionSelector(mMotionPosition, child); refreshDrawableState(); final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); final boolean longClickable = isLongClickable(); if (mSelector != null) { final Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { if (longClickable) { ((TransitionDrawable) d).startTransition(longPressTimeout); } else { ((TransitionDrawable) d).resetTransition(); } } mSelector.setHotspot(x, y); } if (longClickable) { if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.setCoords(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, longPressTimeout); } else { mTouchMode = TOUCH_MODE_DONE_WAITING; } } else { mTouchMode = TOUCH_MODE_DONE_WAITING; } } } } } private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) { // Check if we have moved far enough that it looks more like a // scroll than a tap final int deltaY = y - mMotionY; final int distance = Math.abs(deltaY); final boolean overscroll = mScrollY != 0; if ((overscroll || distance > mTouchSlop) && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { createScrollingCache(); if (overscroll) { mTouchMode = TOUCH_MODE_OVERSCROLL; mMotionCorrection = 0; } else { mTouchMode = TOUCH_MODE_SCROLL; mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop; } removeCallbacks(mPendingCheckForLongPress); setPressed(false); final View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); // Time to start stealing events! Once we've stolen them, don't let anyone // steal from us final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } scrollIfNeeded(x, y, vtev); return true; } return false; } private void scrollIfNeeded(int x, int y, MotionEvent vtev) { int rawDeltaY = y - mMotionY; int scrollOffsetCorrection = 0; int scrollConsumedCorrection = 0; if (mLastY == Integer.MIN_VALUE) { rawDeltaY -= mMotionCorrection; } if (dispatchNestedPreScroll(0, mLastY != Integer.MIN_VALUE ? mLastY - y : -rawDeltaY, mScrollConsumed, mScrollOffset)) { rawDeltaY += mScrollConsumed[1]; scrollOffsetCorrection = -mScrollOffset[1]; scrollConsumedCorrection = mScrollConsumed[1]; if (vtev != null) { vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } } final int deltaY = rawDeltaY; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY; int lastYCorrection = 0; if (mTouchMode == TOUCH_MODE_SCROLL) { if (PROFILE_SCROLLING) { if (!mScrollProfilingStarted) { Debug.startMethodTracing("AbsListViewScroll"); mScrollProfilingStarted = true; } } if (mScrollStrictSpan == null) { // If it's non-null, we're already in a scroll. mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll"); } if (y != mLastY) { // We may be here after stopping a fling and continuing to scroll. // If so, we haven't disallowed intercepting touch events yet. // Make sure that we do so in case we're in a parent that can intercept. if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 && Math.abs(rawDeltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } final int motionIndex; if (mMotionPosition >= 0) { motionIndex = mMotionPosition - mFirstPosition; } else { // If we don't have a motion position that we can reliably track, // pick something in the middle to make a best guess at things below. motionIndex = getChildCount() / 2; } int motionViewPrevTop = 0; View motionView = this.getChildAt(motionIndex); if (motionView != null) { motionViewPrevTop = motionView.getTop(); } // No need to do all this work if we're not going to move anyway boolean atEdge = false; if (incrementalDeltaY != 0) { atEdge = trackMotionScroll(deltaY, incrementalDeltaY); } // Check to see if we have bumped into the scroll limit motionView = this.getChildAt(motionIndex); if (motionView != null) { // Check if the top of the motion view is where it is // supposed to be final int motionViewRealTop = motionView.getTop(); if (atEdge) { // Apply overscroll int overscroll = -incrementalDeltaY - (motionViewRealTop - motionViewPrevTop); if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll, mScrollOffset)) { lastYCorrection -= mScrollOffset[1]; if (vtev != null) { vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } } else { final boolean atOverscrollEdge = overScrollBy(0, overscroll, 0, mScrollY, 0, 0, 0, mOverscrollDistance, true); if (atOverscrollEdge && mVelocityTracker != null) { // Don't allow overfling if we're at the edge mVelocityTracker.clear(); } final int overscrollMode = getOverScrollMode(); if (overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { if (!atOverscrollEdge) { mDirection = 0; // Reset when entering overscroll. mTouchMode = TOUCH_MODE_OVERSCROLL; } if (incrementalDeltaY > 0) { mEdgeGlowTop.onPull((float) -overscroll / getHeight(), (float) x / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } invalidateTopGlow(); } else if (incrementalDeltaY < 0) { mEdgeGlowBottom.onPull((float) overscroll / getHeight(), 1.f - (float) x / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } invalidateBottomGlow(); } } } } mMotionY = y + lastYCorrection + scrollOffsetCorrection; } mLastY = y + lastYCorrection + scrollOffsetCorrection; } } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) { if (y != mLastY) { final int oldScroll = mScrollY; final int newScroll = oldScroll - incrementalDeltaY; int newDirection = y > mLastY ? 1 : -1; if (mDirection == 0) { mDirection = newDirection; } int overScrollDistance = -incrementalDeltaY; if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) { overScrollDistance = -oldScroll; incrementalDeltaY += overScrollDistance; } else { incrementalDeltaY = 0; } if (overScrollDistance != 0) { overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0, 0, mOverscrollDistance, true); final int overscrollMode = getOverScrollMode(); if (overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { if (rawDeltaY > 0) { mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(), (float) x / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } invalidateTopGlow(); } else if (rawDeltaY < 0) { mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(), 1.f - (float) x / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } invalidateBottomGlow(); } } } if (incrementalDeltaY != 0) { // Coming back to 'real' list scrolling if (mScrollY != 0) { mScrollY = 0; invalidateParentIfNeeded(); } trackMotionScroll(incrementalDeltaY, incrementalDeltaY); mTouchMode = TOUCH_MODE_SCROLL; // We did not scroll the full amount. Treat this essentially like the // start of a new touch scroll final int motionPosition = findClosestMotionRow(y); mMotionCorrection = 0; View motionView = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0; mMotionY = y + scrollOffsetCorrection; mMotionPosition = motionPosition; } mLastY = y + lastYCorrection + scrollOffsetCorrection; mDirection = newDirection; } } } private void invalidateTopGlow() { if (mEdgeGlowTop == null) { return; } final boolean clipToPadding = getClipToPadding(); final int top = clipToPadding ? mPaddingTop : 0; final int left = clipToPadding ? mPaddingLeft : 0; final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight()); } private void invalidateBottomGlow() { if (mEdgeGlowBottom == null) { return; } final boolean clipToPadding = getClipToPadding(); final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight(); final int left = clipToPadding ? mPaddingLeft : 0; final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth(); invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom); } @Override public void onTouchModeChanged(boolean isInTouchMode) { if (isInTouchMode) { // Get rid of the selection when we enter touch mode hideSelector(); // Layout, but only if we already have done so previously. // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore // state.) if (getHeight() > 0 && getChildCount() > 0) { // We do not lose focus initiating a touch (since AbsListView is focusable in // touch mode). Force an initial layout to get rid of the selection. layoutChildren(); } updateSelectorState(); } else { int touchMode = mTouchMode; if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { if (mFlingRunnable != null) { mFlingRunnable.endFling(); } if (mPositionScroller != null) { mPositionScroller.stop(); } if (mScrollY != 0) { mScrollY = 0; invalidateParentCaches(); finishGlows(); invalidate(); } } } }
@hide
/** @hide */
@Override protected boolean handleScrollBarDragging(MotionEvent event) { // Doesn't support normal scroll bar dragging. Use FastScroller. return false; } @Override public boolean onTouchEvent(MotionEvent ev) { if (!isEnabled()) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return isClickable() || isLongClickable(); } if (mPositionScroller != null) { mPositionScroller.stop(); } if (mIsDetaching || !isAttachedToWindow()) { // Something isn't right. // Since we rely on being attached to get data set change notifications, // don't risk doing anything where we might try to resync and find things // in a bogus state. return false; } startNestedScroll(SCROLL_AXIS_VERTICAL); if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) { return true; } initVelocityTrackerIfNotExists(); final MotionEvent vtev = MotionEvent.obtain(ev); final int actionMasked = ev.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { mNestedYOffset = 0; } vtev.offsetLocation(0, mNestedYOffset); switch (actionMasked) { case MotionEvent.ACTION_DOWN: { onTouchDown(ev); break; } case MotionEvent.ACTION_MOVE: { onTouchMove(ev, vtev); break; } case MotionEvent.ACTION_UP: { onTouchUp(ev); break; } case MotionEvent.ACTION_CANCEL: { onTouchCancel(); break; } case MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); final int x = mMotionX; final int y = mMotionY; final int motionPosition = pointToPosition(x, y); if (motionPosition >= 0) { // Remember where the motion event started final View child = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = child.getTop(); mMotionPosition = motionPosition; } mLastY = y; break; } case MotionEvent.ACTION_POINTER_DOWN: { // New pointers take over dragging duties final int index = ev.getActionIndex(); final int id = ev.getPointerId(index); final int x = (int) ev.getX(index); final int y = (int) ev.getY(index); mMotionCorrection = 0; mActivePointerId = id; mMotionX = x; mMotionY = y; final int motionPosition = pointToPosition(x, y); if (motionPosition >= 0) { // Remember where the motion event started final View child = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = child.getTop(); mMotionPosition = motionPosition; } mLastY = y; break; } } if (mVelocityTracker != null) { mVelocityTracker.addMovement(vtev); } vtev.recycle(); return true; } private void onTouchDown(MotionEvent ev) { mHasPerformedLongPress = false; mActivePointerId = ev.getPointerId(0); hideSelector(); if (mTouchMode == TOUCH_MODE_OVERFLING) { // Stopped the fling. It is a scroll. mFlingRunnable.endFling(); if (mPositionScroller != null) { mPositionScroller.stop(); } mTouchMode = TOUCH_MODE_OVERSCROLL; mMotionX = (int) ev.getX(); mMotionY = (int) ev.getY(); mLastY = mMotionY; mMotionCorrection = 0; mDirection = 0; } else { final int x = (int) ev.getX(); final int y = (int) ev.getY(); int motionPosition = pointToPosition(x, y); if (!mDataChanged) { if (mTouchMode == TOUCH_MODE_FLING) { // Stopped a fling. It is a scroll. createScrollingCache(); mTouchMode = TOUCH_MODE_SCROLL; mMotionCorrection = 0; motionPosition = findMotionRow(y); mFlingRunnable.flywheelTouch(); } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) { // User clicked on an actual view (and was not stopping a // fling). It might be a click or a scroll. Assume it is a // click until proven otherwise. mTouchMode = TOUCH_MODE_DOWN; // FIXME Debounce if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = ev.getX(); mPendingCheckForTap.y = ev.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } } if (motionPosition >= 0) { // Remember where the motion event started final View v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); } mMotionX = x; mMotionY = y; mMotionPosition = motionPosition; mLastY = Integer.MIN_VALUE; } if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION && performButtonActionOnTouchDown(ev)) { removeCallbacks(mPendingCheckForTap); } } private void onTouchMove(MotionEvent ev, MotionEvent vtev) { if (mHasPerformedLongPress) { // Consume all move events following a successful long press. return; } int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { pointerIndex = 0; mActivePointerId = ev.getPointerId(pointerIndex); } if (mDataChanged) { // Re-sync everything if data has been changed // since the scroll operation can query the adapter. layoutChildren(); } final int y = (int) ev.getY(pointerIndex); switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: // Check if we have moved far enough that it looks more like a // scroll than a tap. If so, we'll enter scrolling mode. if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) { break; } // Otherwise, check containment within list bounds. If we're // outside bounds, cancel any active presses. final View motionView = getChildAt(mMotionPosition - mFirstPosition); final float x = ev.getX(pointerIndex); if (!pointInView(x, y, mTouchSlop)) { setPressed(false); if (motionView != null) { motionView.setPressed(false); } removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress); mTouchMode = TOUCH_MODE_DONE_WAITING; updateSelectorState(); } else if (motionView != null) { // Still within bounds, update the hotspot. final float[] point = mTmpPoint; point[0] = x; point[1] = y; transformPointToViewLocal(point, motionView); motionView.drawableHotspotChanged(point[0], point[1]); } break; case TOUCH_MODE_SCROLL: case TOUCH_MODE_OVERSCROLL: scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev); break; } } private void onTouchUp(MotionEvent ev) { switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: final int motionPosition = mMotionPosition; final View child = getChildAt(motionPosition - mFirstPosition); if (child != null) { if (mTouchMode != TOUCH_MODE_DOWN) { child.setPressed(false); } final float x = ev.getX(); final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right; if (inList && !child.hasExplicitFocusable()) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } final AbsListView.PerformClick performClick = mPerformClick; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); mResurrectToPosition = motionPosition; if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress); mLayoutMode = LAYOUT_NORMAL; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { mTouchMode = TOUCH_MODE_TAP; setSelectedPositionInt(mMotionPosition); layoutChildren(); child.setPressed(true); positionSelector(mMotionPosition, child); setPressed(true); if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } mSelector.setHotspot(x, ev.getY()); } if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); } mTouchModeReset = new Runnable() { @Override public void run() { mTouchModeReset = null; mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) { performClick.run(); } } }; postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; updateSelectorState(); } return; } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { performClick.run(); } } } mTouchMode = TOUCH_MODE_REST; updateSelectorState(); break; case TOUCH_MODE_SCROLL: final int childCount = getChildCount(); if (childCount > 0) { final int firstChildTop = getChildAt(0).getTop(); final int lastChildBottom = getChildAt(childCount - 1).getBottom(); final int contentTop = mListPadding.top; final int contentBottom = getHeight() - mListPadding.bottom; if (mFirstPosition == 0 && firstChildTop >= contentTop && mFirstPosition + childCount < mItemCount && lastChildBottom <= getHeight() - contentBottom) { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocity = (int) (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale); // Fling if we have enough velocity and we aren't at a boundary. // Since we can potentially overfling more than we can overscroll, don't // allow the weird behavior where you can scroll to a boundary then // fling further. boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity; if (flingVelocity && !((mFirstPosition == 0 && firstChildTop == contentTop - mOverscrollDistance) || (mFirstPosition + childCount == mItemCount && lastChildBottom == contentBottom + mOverscrollDistance))) { if (!dispatchNestedPreFling(0, -initialVelocity)) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); mFlingRunnable.start(-initialVelocity); dispatchNestedFling(0, -initialVelocity, true); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); if (mFlingRunnable != null) { mFlingRunnable.endFling(); } if (mPositionScroller != null) { mPositionScroller.stop(); } if (flingVelocity && !dispatchNestedPreFling(0, -initialVelocity)) { dispatchNestedFling(0, -initialVelocity, false); } } } } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } break; case TOUCH_MODE_OVERSCROLL: if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); if (Math.abs(initialVelocity) > mMinimumVelocity) { mFlingRunnable.startOverfling(-initialVelocity); } else { mFlingRunnable.startSpringback(); } break; } setPressed(false); if (mEdgeGlowTop != null) { mEdgeGlowTop.onRelease(); mEdgeGlowBottom.onRelease(); } // Need to redraw since we probably aren't drawing the selector anymore invalidate(); removeCallbacks(mPendingCheckForLongPress); recycleVelocityTracker(); mActivePointerId = INVALID_POINTER; if (PROFILE_SCROLLING) { if (mScrollProfilingStarted) { Debug.stopMethodTracing(); mScrollProfilingStarted = false; } } if (mScrollStrictSpan != null) { mScrollStrictSpan.finish(); mScrollStrictSpan = null; } } private void onTouchCancel() { switch (mTouchMode) { case TOUCH_MODE_OVERSCROLL: if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } mFlingRunnable.startSpringback(); break; case TOUCH_MODE_OVERFLING: // Do nothing - let it play out. break; default: mTouchMode = TOUCH_MODE_REST; setPressed(false); final View motionView = this.getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } clearScrollingCache(); removeCallbacks(mPendingCheckForLongPress); recycleVelocityTracker(); } if (mEdgeGlowTop != null) { mEdgeGlowTop.onRelease(); mEdgeGlowBottom.onRelease(); } mActivePointerId = INVALID_POINTER; } @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { if (mScrollY != scrollY) { onScrollChanged(mScrollX, scrollY, mScrollX, mScrollY); mScrollY = scrollY; invalidateParentIfNeeded(); awakenScrollBars(); } } @Override public boolean onGenericMotionEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: final float axisValue; if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL); } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) { axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL); } else { axisValue = 0; } final int delta = Math.round(axisValue * mVerticalScrollFactor); if (delta != 0) { if (!trackMotionScroll(delta, delta)) { return true; } } break; case MotionEvent.ACTION_BUTTON_PRESS: if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { int actionButton = event.getActionButton(); if ((actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY || actionButton == MotionEvent.BUTTON_SECONDARY) && (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP)) { if (performStylusButtonPressAction(event)) { removeCallbacks(mPendingCheckForLongPress); removeCallbacks(mPendingCheckForTap); } } } break; } return super.onGenericMotionEvent(event); }
Initiate a fling with the given velocity.

Applications can use this method to manually initiate a fling as if the user initiated it via touch interaction.

Params:
  • velocityY – Vertical velocity in pixels per second. Note that this is velocity of content, not velocity of a touch that initiated the fling.
/** * Initiate a fling with the given velocity. * * <p>Applications can use this method to manually initiate a fling as if the user * initiated it via touch interaction.</p> * * @param velocityY Vertical velocity in pixels per second. Note that this is velocity of * content, not velocity of a touch that initiated the fling. */
public void fling(int velocityY) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); mFlingRunnable.start(velocityY); } @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return ((nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0); } @Override public void onNestedScrollAccepted(View child, View target, int axes) { super.onNestedScrollAccepted(child, target, axes); startNestedScroll(SCROLL_AXIS_VERTICAL); } @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { final int motionIndex = getChildCount() / 2; final View motionView = getChildAt(motionIndex); final int oldTop = motionView != null ? motionView.getTop() : 0; if (motionView == null || trackMotionScroll(-dyUnconsumed, -dyUnconsumed)) { int myUnconsumed = dyUnconsumed; int myConsumed = 0; if (motionView != null) { myConsumed = motionView.getTop() - oldTop; myUnconsumed -= myConsumed; } dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); } } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { final int childCount = getChildCount(); if (!consumed && childCount > 0 && canScrollList((int) velocityY) && Math.abs(velocityY) > mMinimumVelocity) { reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } if (!dispatchNestedPreFling(0, velocityY)) { mFlingRunnable.start((int) velocityY); } return true; } return dispatchNestedFling(velocityX, velocityY, consumed); } @Override public void draw(Canvas canvas) { super.draw(canvas); if (mEdgeGlowTop != null) { final int scrollY = mScrollY; final boolean clipToPadding = getClipToPadding(); final int width; final int height; final int translateX; final int translateY; if (clipToPadding) { width = getWidth() - mPaddingLeft - mPaddingRight; height = getHeight() - mPaddingTop - mPaddingBottom; translateX = mPaddingLeft; translateY = mPaddingTop; } else { width = getWidth(); height = getHeight(); translateX = 0; translateY = 0; } if (!mEdgeGlowTop.isFinished()) { final int restoreCount = canvas.save(); canvas.clipRect(translateX, translateY, translateX + width ,translateY + mEdgeGlowTop.getMaxHeight()); final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY; canvas.translate(translateX, edgeY); mEdgeGlowTop.setSize(width, height); if (mEdgeGlowTop.draw(canvas)) { invalidateTopGlow(); } canvas.restoreToCount(restoreCount); } if (!mEdgeGlowBottom.isFinished()) { final int restoreCount = canvas.save(); canvas.clipRect(translateX, translateY + height - mEdgeGlowBottom.getMaxHeight(), translateX + width, translateY + height); final int edgeX = -width + translateX; final int edgeY = Math.max(getHeight(), scrollY + mLastPositionDistanceGuess) - (clipToPadding ? mPaddingBottom : 0); canvas.translate(edgeX, edgeY); canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); if (mEdgeGlowBottom.draw(canvas)) { invalidateBottomGlow(); } canvas.restoreToCount(restoreCount); } } } private void initOrResetVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } else { mVelocityTracker.clear(); } } private void initVelocityTrackerIfNotExists() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } } private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept) { recycleVelocityTracker(); } super.requestDisallowInterceptTouchEvent(disallowIntercept); } @Override public boolean onInterceptHoverEvent(MotionEvent event) { if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) { return true; } return super.onInterceptHoverEvent(event); } @Override public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { if (mFastScroll != null) { PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex); if (pointerIcon != null) { return pointerIcon; } } return super.onResolvePointerIcon(event, pointerIndex); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int actionMasked = ev.getActionMasked(); View v; if (mPositionScroller != null) { mPositionScroller.stop(); } if (mIsDetaching || !isAttachedToWindow()) { // Something isn't right. // Since we rely on being attached to get data set change notifications, // don't risk doing anything where we might try to resync and find things // in a bogus state. return false; } if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) { return true; } switch (actionMasked) { case MotionEvent.ACTION_DOWN: { int touchMode = mTouchMode; if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) { mMotionCorrection = 0; return true; } final int x = (int) ev.getX(); final int y = (int) ev.getY(); mActivePointerId = ev.getPointerId(0); int motionPosition = findMotionRow(y); if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) { // User clicked on an actual view (and was not stopping a fling). // Remember where the motion event started v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); mMotionX = x; mMotionY = y; mMotionPosition = motionPosition; mTouchMode = TOUCH_MODE_DOWN; clearScrollingCache(); } mLastY = Integer.MIN_VALUE; initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); mNestedYOffset = 0; startNestedScroll(SCROLL_AXIS_VERTICAL); if (touchMode == TOUCH_MODE_FLING) { return true; } break; } case MotionEvent.ACTION_MOVE: { switch (mTouchMode) { case TOUCH_MODE_DOWN: int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { pointerIndex = 0; mActivePointerId = ev.getPointerId(pointerIndex); } final int y = (int) ev.getY(pointerIndex); initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) { return true; } break; } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { mTouchMode = TOUCH_MODE_REST; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); stopNestedScroll(); break; } case MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); break; } } return false; } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. // TODO: Make this decision more intelligent. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mMotionX = (int) ev.getX(newPointerIndex); mMotionY = (int) ev.getY(newPointerIndex); mMotionCorrection = 0; mActivePointerId = ev.getPointerId(newPointerIndex); } }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void addTouchables(ArrayList<View> views) { final int count = getChildCount(); final int firstPosition = mFirstPosition; final ListAdapter adapter = mAdapter; if (adapter == null) { return; } for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (adapter.isEnabled(firstPosition + i)) { views.add(child); } child.addTouchables(views); } }
Fires an "on scroll state changed" event to the registered OnScrollListener, if any. The state change is fired only if the specified state is different from the previously known state.
Params:
  • newState – The new scroll state.
/** * Fires an "on scroll state changed" event to the registered * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change * is fired only if the specified state is different from the previously known state. * * @param newState The new scroll state. */
void reportScrollStateChange(int newState) { if (newState != mLastScrollState) { if (mOnScrollListener != null) { mLastScrollState = newState; mOnScrollListener.onScrollStateChanged(this, newState); } } }
Responsible for fling behavior. Use start(int) to initiate a fling. Each frame of the fling is handled in run(). A FlingRunnable will keep re-posting itself until the fling is done.
/** * Responsible for fling behavior. Use {@link #start(int)} to * initiate a fling. Each frame of the fling is handled in {@link #run()}. * A FlingRunnable will keep re-posting itself until the fling is done. * */
private class FlingRunnable implements Runnable {
Tracks the decay of a fling scroll
/** * Tracks the decay of a fling scroll */
private final OverScroller mScroller;
Y value reported by mScroller on the previous fling
/** * Y value reported by mScroller on the previous fling */
private int mLastFlingY;
If true, endFling() will not report scroll state change to OnScrollListener.SCROLL_STATE_IDLE.
/** * If true, {@link #endFling()} will not report scroll state change to * {@link OnScrollListener#SCROLL_STATE_IDLE}. */
private boolean mSuppressIdleStateChangeCall; private final Runnable mCheckFlywheel = new Runnable() { @Override public void run() { final int activeId = mActivePointerId; final VelocityTracker vt = mVelocityTracker; final OverScroller scroller = mScroller; if (vt == null || activeId == INVALID_POINTER) { return; } vt.computeCurrentVelocity(1000, mMaximumVelocity); final float yvel = -vt.getYVelocity(activeId); if (Math.abs(yvel) >= mMinimumVelocity && scroller.isScrollingInDirection(0, yvel)) { // Keep the fling alive a little longer postDelayed(this, FLYWHEEL_TIMEOUT); } else { endFling(); mTouchMode = TOUCH_MODE_SCROLL; reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } } }; private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds FlingRunnable() { mScroller = new OverScroller(getContext()); } void start(int initialVelocity) { int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0; mLastFlingY = initialY; mScroller.setInterpolator(null); mScroller.fling(0, initialY, 0, initialVelocity, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); mTouchMode = TOUCH_MODE_FLING; mSuppressIdleStateChangeCall = false; postOnAnimation(this); if (PROFILE_FLINGING) { if (!mFlingProfilingStarted) { Debug.startMethodTracing("AbsListViewFling"); mFlingProfilingStarted = true; } } if (mFlingStrictSpan == null) { mFlingStrictSpan = StrictMode.enterCriticalSpan("AbsListView-fling"); } } void startSpringback() { mSuppressIdleStateChangeCall = false; if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) { mTouchMode = TOUCH_MODE_OVERFLING; invalidate(); postOnAnimation(this); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } void startOverfling(int initialVelocity) { mScroller.setInterpolator(null); mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight()); mTouchMode = TOUCH_MODE_OVERFLING; mSuppressIdleStateChangeCall = false; invalidate(); postOnAnimation(this); } void edgeReached(int delta) { mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance); final int overscrollMode = getOverScrollMode(); if (overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { mTouchMode = TOUCH_MODE_OVERFLING; final int vel = (int) mScroller.getCurrVelocity(); if (delta > 0) { mEdgeGlowTop.onAbsorb(vel); } else { mEdgeGlowBottom.onAbsorb(vel); } } else { mTouchMode = TOUCH_MODE_REST; if (mPositionScroller != null) { mPositionScroller.stop(); } } invalidate(); postOnAnimation(this); } void startScroll(int distance, int duration, boolean linear, boolean suppressEndFlingStateChangeCall) { int initialY = distance < 0 ? Integer.MAX_VALUE : 0; mLastFlingY = initialY; mScroller.setInterpolator(linear ? sLinearInterpolator : null); mScroller.startScroll(0, initialY, 0, distance, duration); mTouchMode = TOUCH_MODE_FLING; mSuppressIdleStateChangeCall = suppressEndFlingStateChangeCall; postOnAnimation(this); } void endFling() { mTouchMode = TOUCH_MODE_REST; removeCallbacks(this); removeCallbacks(mCheckFlywheel); if (!mSuppressIdleStateChangeCall) { reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } clearScrollingCache(); mScroller.abortAnimation(); if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } } void flywheelTouch() { postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT); } @Override public void run() { switch (mTouchMode) { default: endFling(); return; case TOUCH_MODE_SCROLL: if (mScroller.isFinished()) { return; } // Fall through case TOUCH_MODE_FLING: { if (mDataChanged) { layoutChildren(); } if (mItemCount == 0 || getChildCount() == 0) { endFling(); return; } final OverScroller scroller = mScroller; boolean more = scroller.computeScrollOffset(); final int y = scroller.getCurrY(); // Flip sign to convert finger direction to list items direction // (e.g. finger moving down means list is moving towards the top) int delta = mLastFlingY - y; // Pretend that each frame of a fling scroll is a touch scroll if (delta > 0) { // List is moving towards the top. Use first view as mMotionPosition mMotionPosition = mFirstPosition; final View firstView = getChildAt(0); mMotionViewOriginalTop = firstView.getTop(); // Don't fling more than 1 screen delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta); } else { // List is moving towards the bottom. Use last view as mMotionPosition int offsetToLast = getChildCount() - 1; mMotionPosition = mFirstPosition + offsetToLast; final View lastView = getChildAt(offsetToLast); mMotionViewOriginalTop = lastView.getTop(); // Don't fling more than 1 screen delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); } // Check to see if we have bumped into the scroll limit View motionView = getChildAt(mMotionPosition - mFirstPosition); int oldTop = 0; if (motionView != null) { oldTop = motionView.getTop(); } // Don't stop just because delta is zero (it could have been rounded) final boolean atEdge = trackMotionScroll(delta, delta); final boolean atEnd = atEdge && (delta != 0); if (atEnd) { if (motionView != null) { // Tweak the scroll for how far we overshot int overshoot = -(delta - (motionView.getTop() - oldTop)); overScrollBy(0, overshoot, 0, mScrollY, 0, 0, 0, mOverflingDistance, false); } if (more) { edgeReached(delta); } break; } if (more && !atEnd) { if (atEdge) invalidate(); mLastFlingY = y; postOnAnimation(this); } else { endFling(); if (PROFILE_FLINGING) { if (mFlingProfilingStarted) { Debug.stopMethodTracing(); mFlingProfilingStarted = false; } if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } } } break; } case TOUCH_MODE_OVERFLING: { final OverScroller scroller = mScroller; if (scroller.computeScrollOffset()) { final int scrollY = mScrollY; final int currY = scroller.getCurrY(); final int deltaY = currY - scrollY; if (overScrollBy(0, deltaY, 0, scrollY, 0, 0, 0, mOverflingDistance, false)) { final boolean crossDown = scrollY <= 0 && currY > 0; final boolean crossUp = scrollY >= 0 && currY < 0; if (crossDown || crossUp) { int velocity = (int) scroller.getCurrVelocity(); if (crossUp) velocity = -velocity; // Don't flywheel from this; we're just continuing things. scroller.abortAnimation(); start(velocity); } else { startSpringback(); } } else { invalidate(); postOnAnimation(this); } } else { endFling(); } break; } } } }
The amount of friction applied to flings. The default value is ViewConfiguration.getScrollFriction.
/** * The amount of friction applied to flings. The default value * is {@link ViewConfiguration#getScrollFriction}. */
public void setFriction(float friction) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } mFlingRunnable.mScroller.setFriction(friction); }
Sets a scale factor for the fling velocity. The initial scale factor is 1.0.
Params:
  • scale – The scale factor to multiply the velocity by.
/** * Sets a scale factor for the fling velocity. The initial scale * factor is 1.0. * * @param scale The scale factor to multiply the velocity by. */
public void setVelocityScale(float scale) { mVelocityScale = scale; }
Override this for better control over position scrolling.
/** * Override this for better control over position scrolling. */
AbsPositionScroller createPositionScroller() { return new PositionScroller(); }
Smoothly scroll to the specified adapter position. The view will scroll such that the indicated position is displayed.
Params:
  • position – Scroll to this adapter position.
/** * Smoothly scroll to the specified adapter position. The view will * scroll such that the indicated position is displayed. * @param position Scroll to this adapter position. */
public void smoothScrollToPosition(int position) { if (mPositionScroller == null) { mPositionScroller = createPositionScroller(); } mPositionScroller.start(position); }
Smoothly scroll to the specified adapter position. The view will scroll such that the indicated position is displayed offset pixels below the top edge of the view. If this is impossible, (e.g. the offset would scroll the first or last item beyond the boundaries of the list) it will get as close as possible. The scroll will take duration milliseconds to complete.
Params:
  • position – Position to scroll to
  • offset – Desired distance in pixels of position from the top of the view when scrolling is finished
  • duration – Number of milliseconds to use for the scroll
/** * Smoothly scroll to the specified adapter position. The view will scroll * such that the indicated position is displayed <code>offset</code> pixels below * the top edge of the view. If this is impossible, (e.g. the offset would scroll * the first or last item beyond the boundaries of the list) it will get as close * as possible. The scroll will take <code>duration</code> milliseconds to complete. * * @param position Position to scroll to * @param offset Desired distance in pixels of <code>position</code> from the top * of the view when scrolling is finished * @param duration Number of milliseconds to use for the scroll */
public void smoothScrollToPositionFromTop(int position, int offset, int duration) { if (mPositionScroller == null) { mPositionScroller = createPositionScroller(); } mPositionScroller.startWithOffset(position, offset, duration); }
Smoothly scroll to the specified adapter position. The view will scroll such that the indicated position is displayed offset pixels below the top edge of the view. If this is impossible, (e.g. the offset would scroll the first or last item beyond the boundaries of the list) it will get as close as possible.
Params:
  • position – Position to scroll to
  • offset – Desired distance in pixels of position from the top of the view when scrolling is finished
/** * Smoothly scroll to the specified adapter position. The view will scroll * such that the indicated position is displayed <code>offset</code> pixels below * the top edge of the view. If this is impossible, (e.g. the offset would scroll * the first or last item beyond the boundaries of the list) it will get as close * as possible. * * @param position Position to scroll to * @param offset Desired distance in pixels of <code>position</code> from the top * of the view when scrolling is finished */
public void smoothScrollToPositionFromTop(int position, int offset) { if (mPositionScroller == null) { mPositionScroller = createPositionScroller(); } mPositionScroller.startWithOffset(position, offset); }
Smoothly scroll to the specified adapter position. The view will scroll such that the indicated position is displayed, but it will stop early if scrolling further would scroll boundPosition out of view.
Params:
  • position – Scroll to this adapter position.
  • boundPosition – Do not scroll if it would move this adapter position out of view.
/** * Smoothly scroll to the specified adapter position. The view will * scroll such that the indicated position is displayed, but it will * stop early if scrolling further would scroll boundPosition out of * view. * * @param position Scroll to this adapter position. * @param boundPosition Do not scroll if it would move this adapter * position out of view. */
public void smoothScrollToPosition(int position, int boundPosition) { if (mPositionScroller == null) { mPositionScroller = createPositionScroller(); } mPositionScroller.start(position, boundPosition); }
Smoothly scroll by distance pixels over duration milliseconds.
Params:
  • distance – Distance to scroll in pixels.
  • duration – Duration of the scroll animation in milliseconds.
/** * Smoothly scroll by distance pixels over duration milliseconds. * @param distance Distance to scroll in pixels. * @param duration Duration of the scroll animation in milliseconds. */
public void smoothScrollBy(int distance, int duration) { smoothScrollBy(distance, duration, false, false); } void smoothScrollBy(int distance, int duration, boolean linear, boolean suppressEndFlingStateChangeCall) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } // No sense starting to scroll if we're not going anywhere final int firstPos = mFirstPosition; final int childCount = getChildCount(); final int lastPos = firstPos + childCount; final int topLimit = getPaddingTop(); final int bottomLimit = getHeight() - getPaddingBottom(); if (distance == 0 || mItemCount == 0 || childCount == 0 || (firstPos == 0 && getChildAt(0).getTop() == topLimit && distance < 0) || (lastPos == mItemCount && getChildAt(childCount - 1).getBottom() == bottomLimit && distance > 0)) { mFlingRunnable.endFling(); if (mPositionScroller != null) { mPositionScroller.stop(); } } else { reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); mFlingRunnable.startScroll(distance, duration, linear, suppressEndFlingStateChangeCall); } }
Allows RemoteViews to scroll relatively to a position.
/** * Allows RemoteViews to scroll relatively to a position. */
void smoothScrollByOffset(int position) { int index = -1; if (position < 0) { index = getFirstVisiblePosition(); } else if (position > 0) { index = getLastVisiblePosition(); } if (index > -1) { View child = getChildAt(index - getFirstVisiblePosition()); if (child != null) { Rect visibleRect = new Rect(); if (child.getGlobalVisibleRect(visibleRect)) { // the child is partially visible int childRectArea = child.getWidth() * child.getHeight(); int visibleRectArea = visibleRect.width() * visibleRect.height(); float visibleArea = (visibleRectArea / (float) childRectArea); final float visibleThreshold = 0.75f; if ((position < 0) && (visibleArea < visibleThreshold)) { // the top index is not perceivably visible so offset // to account for showing that top index as well ++index; } else if ((position > 0) && (visibleArea < visibleThreshold)) { // the bottom index is not perceivably visible so offset // to account for showing that bottom index as well --index; } } smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position))); } } } private void createScrollingCache() { if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) { setChildrenDrawnWithCacheEnabled(true); setChildrenDrawingCacheEnabled(true); mCachingStarted = mCachingActive = true; } } private void clearScrollingCache() { if (!isHardwareAccelerated()) { if (mClearScrollingCache == null) { mClearScrollingCache = new Runnable() { @Override public void run() { if (mCachingStarted) { mCachingStarted = mCachingActive = false; setChildrenDrawnWithCacheEnabled(false); if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { setChildrenDrawingCacheEnabled(false); } if (!isAlwaysDrawnWithCacheEnabled()) { invalidate(); } } } }; } post(mClearScrollingCache); } }
Scrolls the list items within the view by a specified number of pixels.

The actual amount of scroll is capped by the list content viewport height which is the list height minus top and bottom paddings minus one pixel.

Params:
  • y – the amount of pixels to scroll by vertically
See Also:
/** * Scrolls the list items within the view by a specified number of pixels. * * <p>The actual amount of scroll is capped by the list content viewport height * which is the list height minus top and bottom paddings minus one pixel.</p> * * @param y the amount of pixels to scroll by vertically * @see #canScrollList(int) */
public void scrollListBy(int y) { trackMotionScroll(-y, -y); }
Check if the items in the list can be scrolled in a certain direction.
Params:
  • direction – Negative to check scrolling up, positive to check scrolling down.
See Also:
Returns:true if the list can be scrolled in the specified direction, false otherwise.
/** * Check if the items in the list can be scrolled in a certain direction. * * @param direction Negative to check scrolling up, positive to check * scrolling down. * @return true if the list can be scrolled in the specified direction, * false otherwise. * @see #scrollListBy(int) */
public boolean canScrollList(int direction) { final int childCount = getChildCount(); if (childCount == 0) { return false; } final int firstPosition = mFirstPosition; final Rect listPadding = mListPadding; if (direction > 0) { final int lastBottom = getChildAt(childCount - 1).getBottom(); final int lastPosition = firstPosition + childCount; return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom; } else { final int firstTop = getChildAt(0).getTop(); return firstPosition > 0 || firstTop < listPadding.top; } }
Track a motion scroll
Params:
  • deltaY – Amount to offset mMotionView. This is the accumulated delta since the motion began. Positive numbers mean the user's finger is moving down the screen.
  • incrementalDeltaY – Change in deltaY from the previous event.
Returns:true if we're already at the beginning/end of the list and have nothing to do.
/** * Track a motion scroll * * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion * began. Positive numbers mean the user's finger is moving down the screen. * @param incrementalDeltaY Change in deltaY from the previous event. * @return true if we're already at the beginning/end of the list and have nothing to do. */
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { final int childCount = getChildCount(); if (childCount == 0) { return true; } final int firstTop = getChildAt(0).getTop(); final int lastBottom = getChildAt(childCount - 1).getBottom(); final Rect listPadding = mListPadding; // "effective padding" In this case is the amount of padding that affects // how much space should not be filled by items. If we don't clip to padding // there is no effective padding. int effectivePaddingTop = 0; int effectivePaddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { effectivePaddingTop = listPadding.top; effectivePaddingBottom = listPadding.bottom; } // FIXME account for grid vertical spacing too? final int spaceAbove = effectivePaddingTop - firstTop; final int end = getHeight() - effectivePaddingBottom; final int spaceBelow = lastBottom - end; final int height = getHeight() - mPaddingBottom - mPaddingTop; if (deltaY < 0) { deltaY = Math.max(-(height - 1), deltaY); } else { deltaY = Math.min(height - 1, deltaY); } if (incrementalDeltaY < 0) { incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); } else { incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } final int firstPosition = mFirstPosition; // Update our guesses for where the first and last views are if (firstPosition == 0) { mFirstPositionDistanceGuess = firstTop - listPadding.top; } else { mFirstPositionDistanceGuess += incrementalDeltaY; } if (firstPosition + childCount == mItemCount) { mLastPositionDistanceGuess = lastBottom + listPadding.bottom; } else { mLastPositionDistanceGuess += incrementalDeltaY; } final boolean cannotScrollDown = (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0); final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0); if (cannotScrollDown || cannotScrollUp) { return incrementalDeltaY != 0; } final boolean down = incrementalDeltaY < 0; final boolean inTouchMode = isInTouchMode(); if (inTouchMode) { hideSelector(); } final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount(); int start = 0; int count = 0; if (down) { int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position); } } } } else { int bottom = getHeight() - incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; } for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position); } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count); mRecycler.removeSkippedScrap(); } // invalidate before moving the children to avoid unnecessary invalidate // calls to bubble up from the children all the way to the top if (!awakenScrollBars()) { invalidate(); } offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); } mRecycler.fullyDetachScrapViews(); boolean selectorOnScreen = false; if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectedPosition, getChildAt(childIndex)); selectorOnScreen = true; } } else if (mSelectorPosition != INVALID_POSITION) { final int childIndex = mSelectorPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectorPosition, getChildAt(childIndex)); selectorOnScreen = true; } } if (!selectorOnScreen) { mSelectorRect.setEmpty(); } mBlockLayoutRequests = false; invokeOnItemScrollListener(); return false; }
Returns the number of header views in the list. Header views are special views at the top of the list that should not be recycled during a layout.
Returns:The number of header views, 0 in the default implementation.
/** * Returns the number of header views in the list. Header views are special views * at the top of the list that should not be recycled during a layout. * * @return The number of header views, 0 in the default implementation. */
int getHeaderViewsCount() { return 0; }
Returns the number of footer views in the list. Footer views are special views at the bottom of the list that should not be recycled during a layout.
Returns:The number of footer views, 0 in the default implementation.
/** * Returns the number of footer views in the list. Footer views are special views * at the bottom of the list that should not be recycled during a layout. * * @return The number of footer views, 0 in the default implementation. */
int getFooterViewsCount() { return 0; }
Fills the gap left open by a touch-scroll. During a touch scroll, children that remain on screen are shifted and the other ones are discarded. The role of this method is to fill the gap thus created by performing a partial layout in the empty space.
Params:
  • down – true if the scroll is going down, false if it is going up
/** * Fills the gap left open by a touch-scroll. During a touch scroll, children that * remain on screen are shifted and the other ones are discarded. The role of this * method is to fill the gap thus created by performing a partial layout in the * empty space. * * @param down true if the scroll is going down, false if it is going up */
abstract void fillGap(boolean down); void hideSelector() { if (mSelectedPosition != INVALID_POSITION) { if (mLayoutMode != LAYOUT_SPECIFIC) { mResurrectToPosition = mSelectedPosition; } if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) { mResurrectToPosition = mNextSelectedPosition; } setSelectedPositionInt(INVALID_POSITION); setNextSelectedPositionInt(INVALID_POSITION); mSelectedTop = 0; } }
Returns:A position to select. First we try mSelectedPosition. If that has been clobbered by entering touch mode, we then try mResurrectToPosition. Values are pinned to the range of items available in the adapter
/** * @return A position to select. First we try mSelectedPosition. If that has been clobbered by * entering touch mode, we then try mResurrectToPosition. Values are pinned to the range * of items available in the adapter */
int reconcileSelectedPosition() { int position = mSelectedPosition; if (position < 0) { position = mResurrectToPosition; } position = Math.max(0, position); position = Math.min(position, mItemCount - 1); return position; }
Find the row closest to y. This row will be used as the motion row when scrolling
Params:
  • y – Where the user touched
Returns:The position of the first (or only) item in the row containing y
/** * Find the row closest to y. This row will be used as the motion row when scrolling * * @param y Where the user touched * @return The position of the first (or only) item in the row containing y */
abstract int findMotionRow(int y);
Find the row closest to y. This row will be used as the motion row when scrolling.
Params:
  • y – Where the user touched
Returns:The position of the first (or only) item in the row closest to y
/** * Find the row closest to y. This row will be used as the motion row when scrolling. * * @param y Where the user touched * @return The position of the first (or only) item in the row closest to y */
int findClosestMotionRow(int y) { final int childCount = getChildCount(); if (childCount == 0) { return INVALID_POSITION; } final int motionRow = findMotionRow(y); return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1; }
Causes all the views to be rebuilt and redrawn.
/** * Causes all the views to be rebuilt and redrawn. */
public void invalidateViews() { mDataChanged = true; rememberSyncState(); requestLayout(); invalidate(); }
If there is a selection returns false. Otherwise resurrects the selection and returns true if resurrected.
/** * If there is a selection returns false. * Otherwise resurrects the selection and returns true if resurrected. */
boolean resurrectSelectionIfNeeded() { if (mSelectedPosition < 0 && resurrectSelection()) { updateSelectorState(); return true; } return false; }
Makes the item at the supplied position selected.
Params:
  • position – the position of the new selection
/** * Makes the item at the supplied position selected. * * @param position the position of the new selection */
abstract void setSelectionInt(int position);
Attempt to bring the selection back if the user is switching from touch to trackball mode
Returns:Whether selection was set to something.
/** * Attempt to bring the selection back if the user is switching from touch * to trackball mode * @return Whether selection was set to something. */
boolean resurrectSelection() { final int childCount = getChildCount(); if (childCount <= 0) { return false; } int selectedTop = 0; int selectedPos; int childrenTop = mListPadding.top; int childrenBottom = mBottom - mTop - mListPadding.bottom; final int firstPosition = mFirstPosition; final int toPosition = mResurrectToPosition; boolean down = true; if (toPosition >= firstPosition && toPosition < firstPosition + childCount) { selectedPos = toPosition; final View selected = getChildAt(selectedPos - mFirstPosition); selectedTop = selected.getTop(); int selectedBottom = selected.getBottom(); // We are scrolled, don't get in the fade if (selectedTop < childrenTop) { selectedTop = childrenTop + getVerticalFadingEdgeLength(); } else if (selectedBottom > childrenBottom) { selectedTop = childrenBottom - selected.getMeasuredHeight() - getVerticalFadingEdgeLength(); } } else { if (toPosition < firstPosition) { // Default to selecting whatever is first selectedPos = firstPosition; for (int i = 0; i < childCount; i++) { final View v = getChildAt(i); final int top = v.getTop(); if (i == 0) { // Remember the position of the first item selectedTop = top; // See if we are scrolled at all if (firstPosition > 0 || top < childrenTop) { // If we are scrolled, don't select anything that is // in the fade region childrenTop += getVerticalFadingEdgeLength(); } } if (top >= childrenTop) { // Found a view whose top is fully visisble selectedPos = firstPosition + i; selectedTop = top; break; } } } else { final int itemCount = mItemCount; down = false; selectedPos = firstPosition + childCount - 1; for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); final int top = v.getTop(); final int bottom = v.getBottom(); if (i == childCount - 1) { selectedTop = top; if (firstPosition + childCount < itemCount || bottom > childrenBottom) { childrenBottom -= getVerticalFadingEdgeLength(); } } if (bottom <= childrenBottom) { selectedPos = firstPosition + i; selectedTop = top; break; } } } } mResurrectToPosition = INVALID_POSITION; removeCallbacks(mFlingRunnable); if (mPositionScroller != null) { mPositionScroller.stop(); } mTouchMode = TOUCH_MODE_REST; clearScrollingCache(); mSpecificTop = selectedTop; selectedPos = lookForSelectablePosition(selectedPos, down); if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) { mLayoutMode = LAYOUT_SPECIFIC; updateSelectorState(); setSelectionInt(selectedPos); invokeOnItemScrollListener(); } else { selectedPos = INVALID_POSITION; } reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); return selectedPos >= 0; } void confirmCheckedPositionsById() { // Clear out the positional check states, we'll rebuild it below from IDs. mCheckStates.clear(); boolean checkedCountChanged = false; for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) { final long id = mCheckedIdStates.keyAt(checkedIndex); final int lastPos = mCheckedIdStates.valueAt(checkedIndex); final long lastPosId = mAdapter.getItemId(lastPos); if (id != lastPosId) { // Look around to see if the ID is nearby. If not, uncheck it. final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE); final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount); boolean found = false; for (int searchPos = start; searchPos < end; searchPos++) { final long searchId = mAdapter.getItemId(searchPos); if (id == searchId) { found = true; mCheckStates.put(searchPos, true); mCheckedIdStates.setValueAt(checkedIndex, searchPos); break; } } if (!found) { mCheckedIdStates.delete(id); checkedIndex--; mCheckedItemCount--; checkedCountChanged = true; if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) { mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, lastPos, id, false); } } } else { mCheckStates.put(lastPos, true); } } if (checkedCountChanged && mChoiceActionMode != null) { mChoiceActionMode.invalidate(); } } @Override protected void handleDataChanged() { int count = mItemCount; int lastHandledItemCount = mLastHandledItemCount; mLastHandledItemCount = mItemCount; if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) { confirmCheckedPositionsById(); } // TODO: In the future we can recycle these views based on stable ID instead. mRecycler.clearTransientStateViews(); if (count > 0) { int newPos; int selectablePos; // Find the row we are supposed to sync to if (mNeedSync) { // Update this first, since setNextSelectedPositionInt inspects it mNeedSync = false; mPendingSync = null; if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) { mLayoutMode = LAYOUT_FORCE_BOTTOM; return; } else if (mTranscriptMode == TRANSCRIPT_MODE_NORMAL) { if (mForceTranscriptScroll) { mForceTranscriptScroll = false; mLayoutMode = LAYOUT_FORCE_BOTTOM; return; } final int childCount = getChildCount(); final int listBottom = getHeight() - getPaddingBottom(); final View lastChild = getChildAt(childCount - 1); final int lastBottom = lastChild != null ? lastChild.getBottom() : listBottom; if (mFirstPosition + childCount >= lastHandledItemCount && lastBottom <= listBottom) { mLayoutMode = LAYOUT_FORCE_BOTTOM; return; } // Something new came in and we didn't scroll; give the user a clue that // there's something new. awakenScrollBars(); } switch (mSyncMode) { case SYNC_SELECTED_POSITION: if (isInTouchMode()) { // We saved our state when not in touch mode. (We know this because // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to // restore in touch mode. Just leave mSyncPosition as it is (possibly // adjusting if the available range changed) and return. mLayoutMode = LAYOUT_SYNC; mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); return; } else { // See if we can find a position in the new data with the same // id as the old selection. This will change mSyncPosition. newPos = findSyncPosition(); if (newPos >= 0) { // Found it. Now verify that new selection is still selectable selectablePos = lookForSelectablePosition(newPos, true); if (selectablePos == newPos) { // Same row id is selected mSyncPosition = newPos; if (mSyncHeight == getHeight()) { // If we are at the same height as when we saved state, try // to restore the scroll position too. mLayoutMode = LAYOUT_SYNC; } else { // We are not the same height as when the selection was saved, so // don't try to restore the exact position mLayoutMode = LAYOUT_SET_SELECTION; } // Restore selection setNextSelectedPositionInt(newPos); return; } } } break; case SYNC_FIRST_POSITION: // Leave mSyncPosition as it is -- just pin to available range mLayoutMode = LAYOUT_SYNC; mSyncPosition = Math.min(Math.max(0, mSyncPosition), count - 1); return; } } if (!isInTouchMode()) { // We couldn't find matching data -- try to use the same position newPos = getSelectedItemPosition(); // Pin position to the available range if (newPos >= count) { newPos = count - 1; } if (newPos < 0) { newPos = 0; } // Make sure we select something selectable -- first look down selectablePos = lookForSelectablePosition(newPos, true); if (selectablePos >= 0) { setNextSelectedPositionInt(selectablePos); return; } else { // Looking down didn't work -- try looking up selectablePos = lookForSelectablePosition(newPos, false); if (selectablePos >= 0) { setNextSelectedPositionInt(selectablePos); return; } } } else { // We already know where we want to resurrect the selection if (mResurrectToPosition >= 0) { return; } } } // Nothing is selected. Give up and reset everything. mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP; mSelectedPosition = INVALID_POSITION; mSelectedRowId = INVALID_ROW_ID; mNextSelectedPosition = INVALID_POSITION; mNextSelectedRowId = INVALID_ROW_ID; mNeedSync = false; mPendingSync = null; mSelectorPosition = INVALID_POSITION; checkSelectionChanged(); } @Override protected void onDisplayHint(int hint) { super.onDisplayHint(hint); switch (hint) { case INVISIBLE: if (mPopup != null && mPopup.isShowing()) { dismissPopup(); } break; case VISIBLE: if (mFiltered && mPopup != null && !mPopup.isShowing()) { showPopup(); } break; } mPopupHidden = hint == INVISIBLE; }
Removes the filter window
/** * Removes the filter window */
private void dismissPopup() { if (mPopup != null) { mPopup.dismiss(); } }
Shows the filter window
/** * Shows the filter window */
private void showPopup() { // Make sure we have a window before showing the popup if (getWindowVisibility() == View.VISIBLE) { createTextFilter(true); positionPopup(); // Make sure we get focus if we are showing the popup checkFocus(); } } private void positionPopup() { int screenHeight = getResources().getDisplayMetrics().heightPixels; final int[] xy = new int[2]; getLocationOnScreen(xy); // TODO: The 20 below should come from the theme // TODO: And the gravity should be defined in the theme as well final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20); if (!mPopup.isShowing()) { mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, xy[0], bottomGap); } else { mPopup.update(xy[0], bottomGap, -1, -1); } }
What is the distance between the source and destination rectangles given the direction of focus navigation between them? The direction basically helps figure out more quickly what is self evident by the relationship between the rects...
Params:
  • source – the source rectangle
  • dest – the destination rectangle
  • direction – the direction
Returns:the distance between the rectangles
/** * What is the distance between the source and destination rectangles given the direction of * focus navigation between them? The direction basically helps figure out more quickly what is * self evident by the relationship between the rects... * * @param source the source rectangle * @param dest the destination rectangle * @param direction the direction * @return the distance between the rectangles */
static int getDistance(Rect source, Rect dest, int direction) { int sX, sY; // source x, y int dX, dY; // dest x, y switch (direction) { case View.FOCUS_RIGHT: sX = source.right; sY = source.top + source.height() / 2; dX = dest.left; dY = dest.top + dest.height() / 2; break; case View.FOCUS_DOWN: sX = source.left + source.width() / 2; sY = source.bottom; dX = dest.left + dest.width() / 2; dY = dest.top; break; case View.FOCUS_LEFT: sX = source.left; sY = source.top + source.height() / 2; dX = dest.right; dY = dest.top + dest.height() / 2; break; case View.FOCUS_UP: sX = source.left + source.width() / 2; sY = source.top; dX = dest.left + dest.width() / 2; dY = dest.bottom; break; case View.FOCUS_FORWARD: case View.FOCUS_BACKWARD: sX = source.right + source.width() / 2; sY = source.top + source.height() / 2; dX = dest.left + dest.width() / 2; dY = dest.top + dest.height() / 2; break; default: throw new IllegalArgumentException("direction must be one of " + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, " + "FOCUS_FORWARD, FOCUS_BACKWARD}."); } int deltaX = dX - sX; int deltaY = dY - sY; return deltaY * deltaY + deltaX * deltaX; } @Override protected boolean isInFilterMode() { return mFiltered; }
Sends a key to the text filter window
Params:
  • keyCode – The keycode for the event
  • event – The actual key event
Returns:True if the text filter handled the event, false otherwise.
/** * Sends a key to the text filter window * * @param keyCode The keycode for the event * @param event The actual key event * * @return True if the text filter handled the event, false otherwise. */
boolean sendToTextFilter(int keyCode, int count, KeyEvent event) { if (!acceptFilter()) { return false; } boolean handled = false; boolean okToSend = true; switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: okToSend = false; break; case KeyEvent.KEYCODE_BACK: if (mFiltered && mPopup != null && mPopup.isShowing()) { if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } handled = true; } else if (event.getAction() == KeyEvent.ACTION_UP && event.isTracking() && !event.isCanceled()) { handled = true; mTextFilter.setText(""); } } okToSend = false; break; case KeyEvent.KEYCODE_SPACE: // Only send spaces once we are filtered okToSend = mFiltered; break; } if (okToSend) { createTextFilter(true); KeyEvent forwardEvent = event; if (forwardEvent.getRepeatCount() > 0) { forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0); } int action = event.getAction(); switch (action) { case KeyEvent.ACTION_DOWN: handled = mTextFilter.onKeyDown(keyCode, forwardEvent); break; case KeyEvent.ACTION_UP: handled = mTextFilter.onKeyUp(keyCode, forwardEvent); break; case KeyEvent.ACTION_MULTIPLE: handled = mTextFilter.onKeyMultiple(keyCode, count, event); break; } } return handled; }
Return an InputConnection for editing of the filter text.
/** * Return an InputConnection for editing of the filter text. */
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (isTextFilterEnabled()) { if (mPublicInputConnection == null) { mDefInputConnection = new BaseInputConnection(this, false); mPublicInputConnection = new InputConnectionWrapper(outAttrs); } outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER; outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; return mPublicInputConnection; } return null; } private class InputConnectionWrapper implements InputConnection { private final EditorInfo mOutAttrs; private InputConnection mTarget; public InputConnectionWrapper(EditorInfo outAttrs) { mOutAttrs = outAttrs; } private InputConnection getTarget() { if (mTarget == null) { mTarget = getTextFilterInput().onCreateInputConnection(mOutAttrs); } return mTarget; } @Override public boolean reportFullscreenMode(boolean enabled) { // Use our own input connection, since it is // the "real" one the IME is talking with. return mDefInputConnection.reportFullscreenMode(enabled); } @Override public boolean performEditorAction(int editorAction) { // The editor is off in its own window; we need to be // the one that does this. if (editorAction == EditorInfo.IME_ACTION_DONE) { InputMethodManager imm = getContext().getSystemService(InputMethodManager.class); if (imm != null) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } return true; } return false; } @Override public boolean sendKeyEvent(KeyEvent event) { // Use our own input connection, since the filter // text view may not be shown in a window so has // no ViewAncestor to dispatch events with. return mDefInputConnection.sendKeyEvent(event); } @Override public CharSequence getTextBeforeCursor(int n, int flags) { if (mTarget == null) return ""; return mTarget.getTextBeforeCursor(n, flags); } @Override public CharSequence getTextAfterCursor(int n, int flags) { if (mTarget == null) return ""; return mTarget.getTextAfterCursor(n, flags); } @Override public CharSequence getSelectedText(int flags) { if (mTarget == null) return ""; return mTarget.getSelectedText(flags); } @Override public int getCursorCapsMode(int reqModes) { if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; return mTarget.getCursorCapsMode(reqModes); } @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return getTarget().getExtractedText(request, flags); } @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { return getTarget().deleteSurroundingText(beforeLength, afterLength); } @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength); } @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { return getTarget().setComposingText(text, newCursorPosition); } @Override public boolean setComposingRegion(int start, int end) { return getTarget().setComposingRegion(start, end); } @Override public boolean finishComposingText() { return mTarget == null || mTarget.finishComposingText(); } @Override public boolean commitText(CharSequence text, int newCursorPosition) { return getTarget().commitText(text, newCursorPosition); } @Override public boolean commitCompletion(CompletionInfo text) { return getTarget().commitCompletion(text); } @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return getTarget().commitCorrection(correctionInfo); } @Override public boolean setSelection(int start, int end) { return getTarget().setSelection(start, end); } @Override public boolean performContextMenuAction(int id) { return getTarget().performContextMenuAction(id); } @Override public boolean beginBatchEdit() { return getTarget().beginBatchEdit(); } @Override public boolean endBatchEdit() { return getTarget().endBatchEdit(); } @Override public boolean clearMetaKeyStates(int states) { return getTarget().clearMetaKeyStates(states); } @Override public boolean performPrivateCommand(String action, Bundle data) { return getTarget().performPrivateCommand(action, data); } @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return getTarget().requestCursorUpdates(cursorUpdateMode); } @Override public Handler getHandler() { return getTarget().getHandler(); } @Override public void closeConnection() { getTarget().closeConnection(); } @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return getTarget().commitContent(inputContentInfo, flags, opts); } }
For filtering we proxy an input connection to an internal text editor, and this allows the proxying to happen.
/** * For filtering we proxy an input connection to an internal text editor, * and this allows the proxying to happen. */
@Override public boolean checkInputConnectionProxy(View view) { return view == mTextFilter; }
Creates the window for the text filter and populates it with an EditText field;
Params:
  • animateEntrance – true if the window should appear with an animation
/** * Creates the window for the text filter and populates it with an EditText field; * * @param animateEntrance true if the window should appear with an animation */
private void createTextFilter(boolean animateEntrance) { if (mPopup == null) { PopupWindow p = new PopupWindow(getContext()); p.setFocusable(false); p.setTouchable(false); p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); p.setContentView(getTextFilterInput()); p.setWidth(LayoutParams.WRAP_CONTENT); p.setHeight(LayoutParams.WRAP_CONTENT); p.setBackgroundDrawable(null); mPopup = p; getViewTreeObserver().addOnGlobalLayoutListener(this); mGlobalLayoutListenerAddedFilter = true; } if (animateEntrance) { mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter); } else { mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilterRestore); } } private EditText getTextFilterInput() { if (mTextFilter == null) { final LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mTextFilter = (EditText) layoutInflater.inflate( com.android.internal.R.layout.typing_filter, null); // For some reason setting this as the "real" input type changes // the text view in some way that it doesn't work, and I don't // want to figure out why this is. mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER); mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); mTextFilter.addTextChangedListener(this); } return mTextFilter; }
Clear the text filter.
/** * Clear the text filter. */
public void clearTextFilter() { if (mFiltered) { getTextFilterInput().setText(""); mFiltered = false; if (mPopup != null && mPopup.isShowing()) { dismissPopup(); } } }
Returns if the ListView currently has a text filter.
/** * Returns if the ListView currently has a text filter. */
public boolean hasTextFilter() { return mFiltered; } @Override public void onGlobalLayout() { if (isShown()) { // Show the popup if we are filtered if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) { showPopup(); } } else { // Hide the popup when we are no longer visible if (mPopup != null && mPopup.isShowing()) { dismissPopup(); } } }
For our text watcher that is associated with the text filter. Does nothing.
/** * For our text watcher that is associated with the text filter. Does * nothing. */
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
For our text watcher that is associated with the text filter. Performs the actual filtering as the text changes, and takes care of hiding and showing the popup displaying the currently entered filter text.
/** * For our text watcher that is associated with the text filter. Performs * the actual filtering as the text changes, and takes care of hiding and * showing the popup displaying the currently entered filter text. */
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (isTextFilterEnabled()) { createTextFilter(true); int length = s.length(); boolean showing = mPopup.isShowing(); if (!showing && length > 0) { // Show the filter popup if necessary showPopup(); mFiltered = true; } else if (showing && length == 0) { // Remove the filter popup if the user has cleared all text dismissPopup(); mFiltered = false; } if (mAdapter instanceof Filterable) { Filter f = ((Filterable) mAdapter).getFilter(); // Filter should not be null when we reach this part if (f != null) { f.filter(s, this); } else { throw new IllegalStateException("You cannot call onTextChanged with a non " + "filterable adapter"); } } } }
For our text watcher that is associated with the text filter. Does nothing.
/** * For our text watcher that is associated with the text filter. Does * nothing. */
@Override public void afterTextChanged(Editable s) { } @Override public void onFilterComplete(int count) { if (mSelectedPosition < 0 && count > 0) { mResurrectToPosition = INVALID_POSITION; resurrectSelection(); } } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new AbsListView.LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof AbsListView.LayoutParams; }
Puts the list or grid into transcript mode. In this mode the list or grid will always scroll to the bottom to show new items.
Params:
  • mode – the transcript mode to set
See Also:
/** * Puts the list or grid into transcript mode. In this mode the list or grid will always scroll * to the bottom to show new items. * * @param mode the transcript mode to set * * @see #TRANSCRIPT_MODE_DISABLED * @see #TRANSCRIPT_MODE_NORMAL * @see #TRANSCRIPT_MODE_ALWAYS_SCROLL */
public void setTranscriptMode(int mode) { mTranscriptMode = mode; }
Returns the current transcript mode.
Returns:TRANSCRIPT_MODE_DISABLED, TRANSCRIPT_MODE_NORMAL or TRANSCRIPT_MODE_ALWAYS_SCROLL
/** * Returns the current transcript mode. * * @return {@link #TRANSCRIPT_MODE_DISABLED}, {@link #TRANSCRIPT_MODE_NORMAL} or * {@link #TRANSCRIPT_MODE_ALWAYS_SCROLL} */
public int getTranscriptMode() { return mTranscriptMode; } @Override public int getSolidColor() { return mCacheColorHint; }
When set to a non-zero value, the cache color hint indicates that this list is always drawn on top of a solid, single-color, opaque background. Zero means that what's behind this object is translucent (non solid) or is not made of a single color. This hint will not affect any existing background drawable set on this view ( typically set via View.setBackgroundDrawable(Drawable)).
Params:
  • color – The background color
/** * When set to a non-zero value, the cache color hint indicates that this list is always drawn * on top of a solid, single-color, opaque background. * * Zero means that what's behind this object is translucent (non solid) or is not made of a * single color. This hint will not affect any existing background drawable set on this view ( * typically set via {@link #setBackgroundDrawable(Drawable)}). * * @param color The background color */
public void setCacheColorHint(@ColorInt int color) { if (color != mCacheColorHint) { mCacheColorHint = color; int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).setDrawingCacheBackgroundColor(color); } mRecycler.setCacheColorHint(color); } }
When set to a non-zero value, the cache color hint indicates that this list is always drawn on top of a solid, single-color, opaque background
Returns:The cache color hint
/** * When set to a non-zero value, the cache color hint indicates that this list is always drawn * on top of a solid, single-color, opaque background * * @return The cache color hint */
@ViewDebug.ExportedProperty(category = "drawing") @ColorInt public int getCacheColorHint() { return mCacheColorHint; }
Move all views (excluding headers and footers) held by this AbsListView into the supplied List. This includes views displayed on the screen as well as views stored in AbsListView's internal view recycler.
Params:
  • views – A list into which to put the reclaimed views
/** * Move all views (excluding headers and footers) held by this AbsListView into the supplied * List. This includes views displayed on the screen as well as views stored in AbsListView's * internal view recycler. * * @param views A list into which to put the reclaimed views */
public void reclaimViews(List<View> views) { int childCount = getChildCount(); RecyclerListener listener = mRecycler.mRecyclerListener; // Reclaim views on screen for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't reclaim header or footer views, or views that should be ignored if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { views.add(child); child.setAccessibilityDelegate(null); if (listener != null) { // Pretend they went through the scrap heap listener.onMovedToScrapHeap(child); } } } mRecycler.reclaimScrapViews(views); removeAllViewsInLayout(); } private void finishGlows() { if (mEdgeGlowTop != null) { mEdgeGlowTop.finish(); mEdgeGlowBottom.finish(); } }
Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService through the specified intent.
Params:
  • intent – the intent used to identify the RemoteViewsService for the adapter to connect to.
/** * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService * through the specified intent. * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to. */
public void setRemoteViewsAdapter(Intent intent) { setRemoteViewsAdapter(intent, false); }
@hide
/** @hide **/
public Runnable setRemoteViewsAdapterAsync(final Intent intent) { return new RemoteViewsAdapter.AsyncRemoteAdapterAction(this, intent); }
@hide
/** @hide **/
@Override public void setRemoteViewsAdapter(Intent intent, boolean isAsync) { // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing // service handling the specified intent. if (mRemoteAdapter != null) { Intent.FilterComparison fcNew = new Intent.FilterComparison(intent); Intent.FilterComparison fcOld = new Intent.FilterComparison( mRemoteAdapter.getRemoteViewsServiceIntent()); if (fcNew.equals(fcOld)) { return; } } mDeferNotifyDataSetChanged = false; // Otherwise, create a new RemoteViewsAdapter for binding mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this, isAsync); if (mRemoteAdapter.isDataReady()) { setAdapter(mRemoteAdapter); } }
Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
Params:
  • handler – The OnClickHandler to use when inflating RemoteViews.
@hide
/** * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews * * @param handler The OnClickHandler to use when inflating RemoteViews. * * @hide */
public void setRemoteViewsOnClickHandler(OnClickHandler handler) { // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing // service handling the specified intent. if (mRemoteAdapter != null) { mRemoteAdapter.setRemoteViewsOnClickHandler(handler); } }
This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not connected yet.
/** * This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not * connected yet. */
@Override public void deferNotifyDataSetChanged() { mDeferNotifyDataSetChanged = true; }
Called back when the adapter connects to the RemoteViewsService.
/** * Called back when the adapter connects to the RemoteViewsService. */
@Override public boolean onRemoteAdapterConnected() { if (mRemoteAdapter != mAdapter) { setAdapter(mRemoteAdapter); if (mDeferNotifyDataSetChanged) { mRemoteAdapter.notifyDataSetChanged(); mDeferNotifyDataSetChanged = false; } return false; } else if (mRemoteAdapter != null) { mRemoteAdapter.superNotifyDataSetChanged(); return true; } return false; }
Called back when the adapter disconnects from the RemoteViewsService.
/** * Called back when the adapter disconnects from the RemoteViewsService. */
@Override public void onRemoteAdapterDisconnected() { // If the remote adapter disconnects, we keep it around // since the currently displayed items are still cached. // Further, we want the service to eventually reconnect // when necessary, as triggered by this view requesting // items from the Adapter. }
Hints the RemoteViewsAdapter, if it exists, about which views are currently being displayed by the AbsListView.
/** * Hints the RemoteViewsAdapter, if it exists, about which views are currently * being displayed by the AbsListView. */
void setVisibleRangeHint(int start, int end) { if (mRemoteAdapter != null) { mRemoteAdapter.setVisibleRangeHint(start, end); } }
Sets the recycler listener to be notified whenever a View is set aside in the recycler for later reuse. This listener can be used to free resources associated to the View.
Params:
  • listener – The recycler listener to be notified of views set aside in the recycler.
See Also:
/** * Sets the recycler listener to be notified whenever a View is set aside in * the recycler for later reuse. This listener can be used to free resources * associated to the View. * * @param listener The recycler listener to be notified of views set aside * in the recycler. * * @see android.widget.AbsListView.RecycleBin * @see android.widget.AbsListView.RecyclerListener */
public void setRecyclerListener(RecyclerListener listener) { mRecycler.mRecyclerListener = listener; } class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver { @Override public void onChanged() { super.onChanged(); if (mFastScroll != null) { mFastScroll.onSectionsChanged(); } } @Override public void onInvalidated() { super.onInvalidated(); if (mFastScroll != null) { mFastScroll.onSectionsChanged(); } } }
A MultiChoiceModeListener receives events for AbsListView.CHOICE_MODE_MULTIPLE_MODAL. It acts as the Callback for the selection mode and also receives onItemCheckedStateChanged(ActionMode, int, long, boolean) events when the user selects and deselects list items.
/** * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. * It acts as the {@link ActionMode.Callback} for the selection mode and also receives * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user * selects and deselects list items. */
public interface MultiChoiceModeListener extends ActionMode.Callback {
Called when an item is checked or unchecked during selection mode.
Params:
  • mode – The ActionMode providing the selection mode
  • position – Adapter position of the item that was checked or unchecked
  • id – Adapter ID of the item that was checked or unchecked
  • checked – true if the item is now checked, false if the item is now unchecked.
/** * Called when an item is checked or unchecked during selection mode. * * @param mode The {@link ActionMode} providing the selection mode * @param position Adapter position of the item that was checked or unchecked * @param id Adapter ID of the item that was checked or unchecked * @param checked <code>true</code> if the item is now checked, <code>false</code> * if the item is now unchecked. */
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked); } class MultiChoiceModeWrapper implements MultiChoiceModeListener { private MultiChoiceModeListener mWrapped; public void setWrapped(MultiChoiceModeListener wrapped) { mWrapped = wrapped; } public boolean hasWrappedCallback() { return mWrapped != null; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { if (mWrapped.onCreateActionMode(mode, menu)) { // Initialize checked graphic state? setLongClickable(false); return true; } return false; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return mWrapped.onPrepareActionMode(mode, menu); } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return mWrapped.onActionItemClicked(mode, item); } @Override public void onDestroyActionMode(ActionMode mode) { mWrapped.onDestroyActionMode(mode); mChoiceActionMode = null; // Ending selection mode means deselecting everything. clearChoices(); mDataChanged = true; rememberSyncState(); requestLayout(); setLongClickable(true); } @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { mWrapped.onItemCheckedStateChanged(mode, position, id, checked); // If there are no items selected we no longer need the selection mode. if (getCheckedItemCount() == 0) { mode.finish(); } } }
AbsListView extends LayoutParams to provide a place to hold the view type.
/** * AbsListView extends LayoutParams to provide a place to hold the view type. */
public static class LayoutParams extends ViewGroup.LayoutParams {
View type for this view, as returned by Adapter.getItemViewType(int)
/** * View type for this view, as returned by * {@link android.widget.Adapter#getItemViewType(int) } */
@ViewDebug.ExportedProperty(category = "list", mapping = { @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"), @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER") }) int viewType;
When this boolean is set, the view has been added to the AbsListView at least once. It is used to know whether headers/footers have already been added to the list view and whether they should be treated as recycled views or not.
/** * When this boolean is set, the view has been added to the AbsListView * at least once. It is used to know whether headers/footers have already * been added to the list view and whether they should be treated as * recycled views or not. */
@ViewDebug.ExportedProperty(category = "list") boolean recycledHeaderFooter;
When an AbsListView is measured with an AT_MOST measure spec, it needs to obtain children views to measure itself. When doing so, the children are not attached to the window, but put in the recycler which assumes they've been attached before. Setting this flag will force the reused view to be attached to the window rather than just attached to the parent.
/** * When an AbsListView is measured with an AT_MOST measure spec, it needs * to obtain children views to measure itself. When doing so, the children * are not attached to the window, but put in the recycler which assumes * they've been attached before. Setting this flag will force the reused * view to be attached to the window rather than just attached to the * parent. */
@ViewDebug.ExportedProperty(category = "list") boolean forceAdd;
The position the view was removed from when pulled out of the scrap heap.
@hide
/** * The position the view was removed from when pulled out of the * scrap heap. * @hide */
int scrappedFromPosition;
The ID the view represents
/** * The ID the view represents */
long itemId = -1;
Whether the adapter considers the item enabled.
/** Whether the adapter considers the item enabled. */
boolean isEnabled; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int w, int h) { super(w, h); } public LayoutParams(int w, int h, int viewType) { super(w, h); this.viewType = viewType; } public LayoutParams(ViewGroup.LayoutParams source) { super(source); }
@hide
/** @hide */
@Override protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { super.encodeProperties(encoder); encoder.addProperty("list:viewType", viewType); encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter); encoder.addProperty("list:forceAdd", forceAdd); encoder.addProperty("list:isEnabled", isEnabled); } }
A RecyclerListener is used to receive a notification whenever a View is placed inside the RecycleBin's scrap heap. This listener is used to free resources associated to Views placed in the RecycleBin.
See Also:
/** * A RecyclerListener is used to receive a notification whenever a View is placed * inside the RecycleBin's scrap heap. This listener is used to free resources * associated to Views placed in the RecycleBin. * * @see android.widget.AbsListView.RecycleBin * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) */
public static interface RecyclerListener {
Indicates that the specified View was moved into the recycler's scrap heap. The view is not displayed on screen any more and any expensive resource associated with the view should be discarded.
Params:
  • view –
/** * Indicates that the specified View was moved into the recycler's scrap heap. * The view is not displayed on screen any more and any expensive resource * associated with the view should be discarded. * * @param view */
void onMovedToScrapHeap(View view); }
The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the start of a layout. By construction, they are displaying current information. At the end of layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that could potentially be used by the adapter to avoid allocating views unnecessarily.
See Also:
/** * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the * start of a layout. By construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that * could potentially be used by the adapter to avoid allocating views unnecessarily. * * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) * @see android.widget.AbsListView.RecyclerListener */
class RecycleBin { private RecyclerListener mRecyclerListener;
The position of the first view stored in mActiveViews.
/** * The position of the first view stored in mActiveViews. */
private int mFirstActivePosition;
Views that were on screen at the start of layout. This array is populated at the start of layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. Views in mActiveViews represent a contiguous range of Views, with position of the first view store in mFirstActivePosition.
/** * Views that were on screen at the start of layout. This array is populated at the start of * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. * Views in mActiveViews represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */
private View[] mActiveViews = new View[0];
Unsorted views that can be used by the adapter as a convert view.
/** * Unsorted views that can be used by the adapter as a convert view. */
private ArrayList<View>[] mScrapViews; private int mViewTypeCount; private ArrayList<View> mCurrentScrap; private ArrayList<View> mSkippedScrap; private SparseArray<View> mTransientStateViews; private LongSparseArray<View> mTransientStateViewsById; public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } //noinspection unchecked ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList<View>(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; } public void markChildrenDirty() { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; final int scrapCount = scrap.size(); for (int i = 0; i < scrapCount; i++) { scrap.get(i).forceLayout(); } } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { scrap.get(j).forceLayout(); } } } if (mTransientStateViews != null) { final int count = mTransientStateViews.size(); for (int i = 0; i < count; i++) { mTransientStateViews.valueAt(i).forceLayout(); } } if (mTransientStateViewsById != null) { final int count = mTransientStateViewsById.size(); for (int i = 0; i < count; i++) { mTransientStateViewsById.valueAt(i).forceLayout(); } } } public boolean shouldRecycleViewType(int viewType) { return viewType >= 0; }
Clears the scrap heap.
/** * Clears the scrap heap. */
void clear() { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; clearScrap(scrap); } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; clearScrap(scrap); } } clearTransientStateViews(); }
Fill ActiveViews with all of the children of the AbsListView.
Params:
  • childCount – The minimum number of views mActiveViews should hold
  • firstActivePosition – The position of the first view that will be stored in mActiveViews
/** * Fill ActiveViews with all of the children of the AbsListView. * * @param childCount The minimum number of views mActiveViews should hold * @param firstActivePosition The position of the first view that will be stored in * mActiveViews */
void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; //noinspection MismatchedReadAndWriteOfArray final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. // However, we will NOT place them into scrap views. activeViews[i] = child; // Remember the position so that setupChild() doesn't reset state. lp.scrappedFromPosition = firstActivePosition + i; } } }
Get the view corresponding to the specified position. The view will be removed from mActiveViews if it is found.
Params:
  • position – The position to look up in mActiveViews
Returns:The view if it is found, null otherwise
/** * Get the view corresponding to the specified position. The view will be removed from * mActiveViews if it is found. * * @param position The position to look up in mActiveViews * @return The view if it is found, null otherwise */
View getActiveView(int position) { int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >=0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; return match; } return null; } View getTransientStateView(int position) { if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) { long id = mAdapter.getItemId(position); View result = mTransientStateViewsById.get(id); mTransientStateViewsById.remove(id); return result; } if (mTransientStateViews != null) { final int index = mTransientStateViews.indexOfKey(position); if (index >= 0) { View result = mTransientStateViews.valueAt(index); mTransientStateViews.removeAt(index); return result; } } return null; }
Dumps and fully detaches any currently saved views with transient state.
/** * Dumps and fully detaches any currently saved views with transient * state. */
void clearTransientStateViews() { final SparseArray<View> viewsByPos = mTransientStateViews; if (viewsByPos != null) { final int N = viewsByPos.size(); for (int i = 0; i < N; i++) { removeDetachedView(viewsByPos.valueAt(i), false); } viewsByPos.clear(); } final LongSparseArray<View> viewsById = mTransientStateViewsById; if (viewsById != null) { final int N = viewsById.size(); for (int i = 0; i < N; i++) { removeDetachedView(viewsById.valueAt(i), false); } viewsById.clear(); } }
Returns:A view from the ScrapViews collection. These are unordered.
/** * @return A view from the ScrapViews collection. These are unordered. */
View getScrapView(int position) { final int whichScrap = mAdapter.getItemViewType(position); if (whichScrap < 0) { return null; } if (mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); } else if (whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } return null; }
Puts a view into the list of scrap views.

If the list data hasn't changed or the adapter has stable IDs, views with transient state will be preserved for later retrieval.

Params:
  • scrap – The view to add
  • position – The view's position within its parent
/** * Puts a view into the list of scrap views. * <p> * If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent */
void addScrapView(View scrap, int position) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { // Can't recycle, but we don't know anything about the view. // Ignore it completely. return; } lp.scrappedFromPosition = position; // Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { // Can't recycle. If it's not a header or footer, which have // special handling and should be ignored, then skip the scrap // heap and we'll fully detach the view later. if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { getSkippedScrap().add(scrap); } return; } scrap.dispatchStartTemporaryDetach(); // The the accessibility state of the view may change while temporary // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<>(); } mTransientStateViews.put(position, scrap); } else { // Otherwise, we'll have to remove the view and start over. clearScrapForRebind(scrap); getSkippedScrap().add(scrap); } } else { clearScrapForRebind(scrap); if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } } private ArrayList<View> getSkippedScrap() { if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<>(); } return mSkippedScrap; }
Finish the removal of any views that skipped the scrap heap.
/** * Finish the removal of any views that skipped the scrap heap. */
void removeSkippedScrap() { if (mSkippedScrap == null) { return; } final int count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); }
Move all views remaining in mActiveViews to mScrapViews.
/** * Move all views remaining in mActiveViews to mScrapViews. */
void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null; final boolean multipleScraps = mViewTypeCount > 1; ArrayList<View> scrapViews = mCurrentScrap; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) victim.getLayoutParams(); final int whichScrap = lp.viewType; activeViews[i] = null; if (victim.hasTransientState()) { // Store views with transient state for later use. victim.dispatchStartTemporaryDetach(); if (mAdapter != null && mAdapterHasStableIds) { if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<View>(); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<View>(); } mTransientStateViews.put(mFirstActivePosition + i, victim); } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // The data has changed, we can't keep this view. removeDetachedView(victim, false); } } else if (!shouldRecycleViewType(whichScrap)) { // Discard non-recyclable views except headers/footers. if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(victim, false); } } else { // Store everything else on the appropriate scrap heap. if (multipleScraps) { scrapViews = mScrapViews[whichScrap]; } lp.scrappedFromPosition = mFirstActivePosition + i; removeDetachedView(victim, false); scrapViews.add(victim); if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } } } } pruneScrapViews(); }
At the end of a layout pass, all temp detached views should either be re-attached or completely detached. This method ensures that any remaining view in the scrap list is fully detached.
/** * At the end of a layout pass, all temp detached views should either be re-attached or * completely detached. This method ensures that any remaining view in the scrap list is * fully detached. */
void fullyDetachScrapViews() { final int viewTypeCount = mViewTypeCount; final ArrayList<View>[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList<View> scrapPile = scrapViews[i]; for (int j = scrapPile.size() - 1; j >= 0; j--) { final View view = scrapPile.get(j); if (view.isTemporarilyDetached()) { removeDetachedView(view, false); } } } }
Makes sure that the size of mScrapViews does not exceed the size of mActiveViews, which can happen if an adapter does not recycle its views. Removes cached transient state views that no longer have transient state.
/** * Makes sure that the size of mScrapViews does not exceed the size of * mActiveViews, which can happen if an adapter does not recycle its * views. Removes cached transient state views that no longer have * transient state. */
private void pruneScrapViews() { final int maxViews = mActiveViews.length; final int viewTypeCount = mViewTypeCount; final ArrayList<View>[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList<View> scrapPile = scrapViews[i]; int size = scrapPile.size(); while (size > maxViews) { scrapPile.remove(--size); } } final SparseArray<View> transViewsByPos = mTransientStateViews; if (transViewsByPos != null) { for (int i = 0; i < transViewsByPos.size(); i++) { final View v = transViewsByPos.valueAt(i); if (!v.hasTransientState()) { removeDetachedView(v, false); transViewsByPos.removeAt(i); i--; } } } final LongSparseArray<View> transViewsById = mTransientStateViewsById; if (transViewsById != null) { for (int i = 0; i < transViewsById.size(); i++) { final View v = transViewsById.valueAt(i); if (!v.hasTransientState()) { removeDetachedView(v, false); transViewsById.removeAt(i); i--; } } } }
Puts all views in the scrap heap into the supplied list.
/** * Puts all views in the scrap heap into the supplied list. */
void reclaimScrapViews(List<View> views) { if (mViewTypeCount == 1) { views.addAll(mCurrentScrap); } else { final int viewTypeCount = mViewTypeCount; final ArrayList<View>[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList<View> scrapPile = scrapViews[i]; views.addAll(scrapPile); } } }
Updates the cache color hint of all known views.
Params:
  • color – The new cache color hint.
/** * Updates the cache color hint of all known views. * * @param color The new cache color hint. */
void setCacheColorHint(int color) { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; final int scrapCount = scrap.size(); for (int i = 0; i < scrapCount; i++) { scrap.get(i).setDrawingCacheBackgroundColor(color); } } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { scrap.get(j).setDrawingCacheBackgroundColor(color); } } } // Just in case this is called during a layout pass final View[] activeViews = mActiveViews; final int count = activeViews.length; for (int i = 0; i < count; ++i) { final View victim = activeViews[i]; if (victim != null) { victim.setDrawingCacheBackgroundColor(color); } } } private View retrieveFromScrap(ArrayList<View> scrapViews, int position) { final int size = scrapViews.size(); if (size > 0) { // See if we still have a view for this position or ID. // Traverse backwards to find the most recently used scrap view for (int i = size - 1; i >= 0; i--) { final View view = scrapViews.get(i); final AbsListView.LayoutParams params = (AbsListView.LayoutParams) view.getLayoutParams(); if (mAdapterHasStableIds) { final long id = mAdapter.getItemId(position); if (id == params.itemId) { return scrapViews.remove(i); } } else if (params.scrappedFromPosition == position) { final View scrap = scrapViews.remove(i); clearScrapForRebind(scrap); return scrap; } } final View scrap = scrapViews.remove(size - 1); clearScrapForRebind(scrap); return scrap; } else { return null; } } private void clearScrap(final ArrayList<View> scrap) { final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { removeDetachedView(scrap.remove(scrapCount - 1 - j), false); } } private void clearScrapForRebind(View view) { view.clearAccessibilityFocus(); view.setAccessibilityDelegate(null); } private void removeDetachedView(View child, boolean animate) { child.setAccessibilityDelegate(null); AbsListView.this.removeDetachedView(child, animate); } }
Returns the height of the view for the specified position.
Params:
  • position – the item position
Returns:view height in pixels
/** * Returns the height of the view for the specified position. * * @param position the item position * @return view height in pixels */
int getHeightForPosition(int position) { final int firstVisiblePosition = getFirstVisiblePosition(); final int childCount = getChildCount(); final int index = position - firstVisiblePosition; if (index >= 0 && index < childCount) { // Position is on-screen, use existing view. final View view = getChildAt(index); return view.getHeight(); } else { // Position is off-screen, obtain & recycle view. final View view = obtainView(position, mIsScrap); view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED); final int height = view.getMeasuredHeight(); mRecycler.addScrapView(view, position); return height; } }
Sets the selected item and positions the selection y pixels from the top edge of the ListView. (If in touch mode, the item will not be selected but it will still be positioned appropriately.)
Params:
  • position – Index (starting at 0) of the data item to be selected.
  • y – The distance from the top edge of the ListView (plus padding) that the item will be positioned.
/** * Sets the selected item and positions the selection y pixels from the top edge * of the ListView. (If in touch mode, the item will not be selected but it will * still be positioned appropriately.) * * @param position Index (starting at 0) of the data item to be selected. * @param y The distance from the top edge of the ListView (plus padding) that the * item will be positioned. */
public void setSelectionFromTop(int position, int y) { if (mAdapter == null) { return; } if (!isInTouchMode()) { position = lookForSelectablePosition(position, true); if (position >= 0) { setNextSelectedPositionInt(position); } } else { mResurrectToPosition = position; } if (position >= 0) { mLayoutMode = LAYOUT_SPECIFIC; mSpecificTop = mListPadding.top + y; if (mNeedSync) { mSyncPosition = position; mSyncRowId = mAdapter.getItemId(position); } if (mPositionScroller != null) { mPositionScroller.stop(); } requestLayout(); } }
@hide
/** @hide */
@Override protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { super.encodeProperties(encoder); encoder.addProperty("drawing:cacheColorHint", getCacheColorHint()); encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled()); encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled()); encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled()); encoder.addProperty("list:stackFromBottom", isStackFromBottom()); encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled()); View selectedView = getSelectedView(); if (selectedView != null) { encoder.addPropertyKey("selectedView"); selectedView.encode(encoder); } }
Abstract positon scroller used to handle smooth scrolling.
/** * Abstract positon scroller used to handle smooth scrolling. */
static abstract class AbsPositionScroller { public abstract void start(int position); public abstract void start(int position, int boundPosition); public abstract void startWithOffset(int position, int offset); public abstract void startWithOffset(int position, int offset, int duration); public abstract void stop(); }
Default position scroller that simulates a fling.
/** * Default position scroller that simulates a fling. */
class PositionScroller extends AbsPositionScroller implements Runnable { private static final int SCROLL_DURATION = 200; private static final int MOVE_DOWN_POS = 1; private static final int MOVE_UP_POS = 2; private static final int MOVE_DOWN_BOUND = 3; private static final int MOVE_UP_BOUND = 4; private static final int MOVE_OFFSET = 5; private int mMode; private int mTargetPos; private int mBoundPos; private int mLastSeenPos; private int mScrollDuration; private final int mExtraScroll; private int mOffsetFromTop; PositionScroller() { mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); } @Override public void start(final int position) { stop(); if (mDataChanged) { // Wait until we're back in a stable state to try this. mPositionScrollAfterLayout = new Runnable() { @Override public void run() { start(position); } }; return; } final int childCount = getChildCount(); if (childCount == 0) { // Can't scroll without children. return; } final int firstPos = mFirstPosition; final int lastPos = firstPos + childCount - 1; int viewTravelCount; int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); if (clampedPosition < firstPos) { viewTravelCount = firstPos - clampedPosition + 1; mMode = MOVE_UP_POS; } else if (clampedPosition > lastPos) { viewTravelCount = clampedPosition - lastPos + 1; mMode = MOVE_DOWN_POS; } else { scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION); return; } if (viewTravelCount > 0) { mScrollDuration = SCROLL_DURATION / viewTravelCount; } else { mScrollDuration = SCROLL_DURATION; } mTargetPos = clampedPosition; mBoundPos = INVALID_POSITION; mLastSeenPos = INVALID_POSITION; postOnAnimation(this); } @Override public void start(final int position, final int boundPosition) { stop(); if (boundPosition == INVALID_POSITION) { start(position); return; } if (mDataChanged) { // Wait until we're back in a stable state to try this. mPositionScrollAfterLayout = new Runnable() { @Override public void run() { start(position, boundPosition); } }; return; } final int childCount = getChildCount(); if (childCount == 0) { // Can't scroll without children. return; } final int firstPos = mFirstPosition; final int lastPos = firstPos + childCount - 1; int viewTravelCount; int clampedPosition = Math.max(0, Math.min(getCount() - 1, position)); if (clampedPosition < firstPos) { final int boundPosFromLast = lastPos - boundPosition; if (boundPosFromLast < 1) { // Moving would shift our bound position off the screen. Abort. return; } final int posTravel = firstPos - clampedPosition + 1; final int boundTravel = boundPosFromLast - 1; if (boundTravel < posTravel) { viewTravelCount = boundTravel; mMode = MOVE_UP_BOUND; } else { viewTravelCount = posTravel; mMode = MOVE_UP_POS; } } else if (clampedPosition > lastPos) { final int boundPosFromFirst = boundPosition - firstPos; if (boundPosFromFirst < 1) { // Moving would shift our bound position off the screen. Abort. return; } final int posTravel = clampedPosition - lastPos + 1; final int boundTravel = boundPosFromFirst - 1; if (boundTravel < posTravel) { viewTravelCount = boundTravel; mMode = MOVE_DOWN_BOUND; } else { viewTravelCount = posTravel; mMode = MOVE_DOWN_POS; } } else { scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION); return; } if (viewTravelCount > 0) { mScrollDuration = SCROLL_DURATION / viewTravelCount; } else { mScrollDuration = SCROLL_DURATION; } mTargetPos = clampedPosition; mBoundPos = boundPosition; mLastSeenPos = INVALID_POSITION; postOnAnimation(this); } @Override public void startWithOffset(int position, int offset) { startWithOffset(position, offset, SCROLL_DURATION); } @Override public void startWithOffset(final int position, int offset, final int duration) { stop(); if (mDataChanged) { // Wait until we're back in a stable state to try this. final int postOffset = offset; mPositionScrollAfterLayout = new Runnable() { @Override public void run() { startWithOffset(position, postOffset, duration); } }; return; } final int childCount = getChildCount(); if (childCount == 0) { // Can't scroll without children. return; } offset += getPaddingTop(); mTargetPos = Math.max(0, Math.min(getCount() - 1, position)); mOffsetFromTop = offset; mBoundPos = INVALID_POSITION; mLastSeenPos = INVALID_POSITION; mMode = MOVE_OFFSET; final int firstPos = mFirstPosition; final int lastPos = firstPos + childCount - 1; int viewTravelCount; if (mTargetPos < firstPos) { viewTravelCount = firstPos - mTargetPos; } else if (mTargetPos > lastPos) { viewTravelCount = mTargetPos - lastPos; } else { // On-screen, just scroll. final int targetTop = getChildAt(mTargetPos - firstPos).getTop(); smoothScrollBy(targetTop - offset, duration, true, false); return; } // Estimate how many screens we should travel final float screenTravelCount = (float) viewTravelCount / childCount; mScrollDuration = screenTravelCount < 1 ? duration : (int) (duration / screenTravelCount); mLastSeenPos = INVALID_POSITION; postOnAnimation(this); }
Scroll such that targetPos is in the visible padded region without scrolling boundPos out of view. Assumes targetPos is onscreen.
/** * Scroll such that targetPos is in the visible padded region without scrolling * boundPos out of view. Assumes targetPos is onscreen. */
private void scrollToVisible(int targetPos, int boundPos, int duration) { final int firstPos = mFirstPosition; final int childCount = getChildCount(); final int lastPos = firstPos + childCount - 1; final int paddedTop = mListPadding.top; final int paddedBottom = getHeight() - mListPadding.bottom; if (targetPos < firstPos || targetPos > lastPos) { Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + " not visible [" + firstPos + ", " + lastPos + "]"); } if (boundPos < firstPos || boundPos > lastPos) { // boundPos doesn't matter, it's already offscreen. boundPos = INVALID_POSITION; } final View targetChild = getChildAt(targetPos - firstPos); final int targetTop = targetChild.getTop(); final int targetBottom = targetChild.getBottom(); int scrollBy = 0; if (targetBottom > paddedBottom) { scrollBy = targetBottom - paddedBottom; } if (targetTop < paddedTop) { scrollBy = targetTop - paddedTop; } if (scrollBy == 0) { return; } if (boundPos >= 0) { final View boundChild = getChildAt(boundPos - firstPos); final int boundTop = boundChild.getTop(); final int boundBottom = boundChild.getBottom(); final int absScroll = Math.abs(scrollBy); if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { // Don't scroll the bound view off the bottom of the screen. scrollBy = Math.max(0, boundBottom - paddedBottom); } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { // Don't scroll the bound view off the top of the screen. scrollBy = Math.min(0, boundTop - paddedTop); } } smoothScrollBy(scrollBy, duration); } @Override public void stop() { removeCallbacks(this); } @Override public void run() { final int listHeight = getHeight(); final int firstPos = mFirstPosition; switch (mMode) { case MOVE_DOWN_POS: { final int lastViewIndex = getChildCount() - 1; final int lastPos = firstPos + lastViewIndex; if (lastViewIndex < 0) { return; } if (lastPos == mLastSeenPos) { // No new views, let things keep going. postOnAnimation(this); return; } final View lastView = getChildAt(lastViewIndex); final int lastViewHeight = lastView.getHeight(); final int lastViewTop = lastView.getTop(); final int lastViewPixelsShowing = listHeight - lastViewTop; final int extraScroll = lastPos < mItemCount - 1 ? Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom; final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll; smoothScrollBy(scrollBy, mScrollDuration, true, lastPos < mTargetPos); mLastSeenPos = lastPos; if (lastPos < mTargetPos) { postOnAnimation(this); } break; } case MOVE_DOWN_BOUND: { final int nextViewIndex = 1; final int childCount = getChildCount(); if (firstPos == mBoundPos || childCount <= nextViewIndex || firstPos + childCount >= mItemCount) { reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); return; } final int nextPos = firstPos + nextViewIndex; if (nextPos == mLastSeenPos) { // No new views, let things keep going. postOnAnimation(this); return; } final View nextView = getChildAt(nextViewIndex); final int nextViewHeight = nextView.getHeight(); final int nextViewTop = nextView.getTop(); final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll); if (nextPos < mBoundPos) { smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), mScrollDuration, true, true); mLastSeenPos = nextPos; postOnAnimation(this); } else { if (nextViewTop > extraScroll) { smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true, false); } else { reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } break; } case MOVE_UP_POS: { if (firstPos == mLastSeenPos) { // No new views, let things keep going. postOnAnimation(this); return; } final View firstView = getChildAt(0); if (firstView == null) { return; } final int firstViewTop = firstView.getTop(); final int extraScroll = firstPos > 0 ? Math.max(mExtraScroll, mListPadding.top) : mListPadding.top; smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true, firstPos > mTargetPos); mLastSeenPos = firstPos; if (firstPos > mTargetPos) { postOnAnimation(this); } break; } case MOVE_UP_BOUND: { final int lastViewIndex = getChildCount() - 2; if (lastViewIndex < 0) { return; } final int lastPos = firstPos + lastViewIndex; if (lastPos == mLastSeenPos) { // No new views, let things keep going. postOnAnimation(this); return; } final View lastView = getChildAt(lastViewIndex); final int lastViewHeight = lastView.getHeight(); final int lastViewTop = lastView.getTop(); final int lastViewPixelsShowing = listHeight - lastViewTop; final int extraScroll = Math.max(mListPadding.top, mExtraScroll); mLastSeenPos = lastPos; if (lastPos > mBoundPos) { smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true, true); postOnAnimation(this); } else { final int bottom = listHeight - extraScroll; final int lastViewBottom = lastViewTop + lastViewHeight; if (bottom > lastViewBottom) { smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true, false); } else { reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } break; } case MOVE_OFFSET: { if (mLastSeenPos == firstPos) { // No new views, let things keep going. postOnAnimation(this); return; } mLastSeenPos = firstPos; final int childCount = getChildCount(); final int position = mTargetPos; final int lastPos = firstPos + childCount - 1; // Account for the visible "portion" of the first / last child when we estimate // how many screens we should travel to reach our target final View firstChild = getChildAt(0); final int firstChildHeight = firstChild.getHeight(); final View lastChild = getChildAt(childCount - 1); final int lastChildHeight = lastChild.getHeight(); final float firstPositionVisiblePart = (firstChildHeight == 0.0f) ? 1.0f : (float) (firstChildHeight + firstChild.getTop()) / firstChildHeight; final float lastPositionVisiblePart = (lastChildHeight == 0.0f) ? 1.0f : (float) (lastChildHeight + getHeight() - lastChild.getBottom()) / lastChildHeight; float viewTravelCount = 0; if (position < firstPos) { viewTravelCount = firstPos - position + (1.0f - firstPositionVisiblePart) + 1; } else if (position > lastPos) { viewTravelCount = position - lastPos + (1.0f - lastPositionVisiblePart); } // Estimate how many screens we should travel final float screenTravelCount = viewTravelCount / childCount; final float modifier = Math.min(Math.abs(screenTravelCount), 1.f); if (position < firstPos) { final int distance = (int) (-getHeight() * modifier); final int duration = (int) (mScrollDuration * modifier); smoothScrollBy(distance, duration, true, true); postOnAnimation(this); } else if (position > lastPos) { final int distance = (int) (getHeight() * modifier); final int duration = (int) (mScrollDuration * modifier); smoothScrollBy(distance, duration, true, true); postOnAnimation(this); } else { // On-screen, just scroll. final int targetTop = getChildAt(position - firstPos).getTop(); final int distance = targetTop - mOffsetFromTop; final int duration = (int) (mScrollDuration * ((float) Math.abs(distance) / getHeight())); smoothScrollBy(distance, duration, true, false); } break; } default: break; } } } }