/* * 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 java.util.ArrayList; import java.util.List; import android.content.Context; 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.text.Editable; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ContextMenu.ContextMenuInfo; import com.android.internal.R; import com.intel.mpt.annotation.MayloonStubAnnotation; /** * Base class that can be used to implement virtualized lists of items. A list does * not have a spatial definition here. For instance, subclases of this class can * display the content of the list in a grid, in a carousel, as stack, etc. * */ public abstract class AbsListView extends AdapterView<ListAdapter> { /** * 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. */ 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 */ 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 */ static final int TOUCH_MODE_DONE_WAITING = 2; /** * Indicates the touch gesture is a scroll */ static final int TOUCH_MODE_SCROLL = 3; /** * 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. */ static final int TOUCH_MODE_OVERSCROLL = 5; /** * 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 */ static final int LAYOUT_NORMAL = 0; /** * Show the first item */ static final int LAYOUT_FORCE_TOP = 1; /** * Force the selected item to be on somewhere on the screen */ static final int LAYOUT_SET_SELECTION = 2; /** * 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. */ static final int LAYOUT_SPECIFIC = 4; /** * 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 */ static final int LAYOUT_MOVE_SELECTION = 6; /** * Controls how the next layout will happen */ int mLayoutMode = LAYOUT_NORMAL; /** * 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 */ ListAdapter mAdapter; /** * 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 */ Drawable mSelector; /** * Defines the selector's location and dimension at drawing time */ Rect mSelectorRect = new Rect(); /** * The selection's left padding */ int mSelectionLeftPadding = 0; /** * The selection's top padding */ int mSelectionTopPadding = 0; /** * The selection's right padding */ int mSelectionRightPadding = 0; /** * The selection's bottom padding */ int mSelectionBottomPadding = 0; /** * This view's padding */ Rect mListPadding = new Rect(); /** * Subclasses must retain their measure spec from onMeasure() into this member */ int mWidthMeasureSpec = 0; /** * 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; /** * Y value from on the previous motion event (if any) */ int mLastY; /** * How far the finger moved before we started scrolling */ int mMotionCorrection; /** * 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. */ boolean mStackFromBottom; /** * 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 */ boolean mFastScrollEnabled; /** * Optional callback to notify client when scroll position has changed */ private OnScrollListener mOnScrollListener; /** * Used with type filter window */ EditText mTextFilter; /** * Indicates whether to use pixels-based or position-based scrollbar * properties. */ private boolean mSmoothScrollbarEnabled = true; /** * The position to resurrect the selected position to. */ int mResurrectToPosition = INVALID_POSITION; /** * Maximum distance to record overscroll */ int mOverscrollMax; /** * Content height divided by this is the overscroll limit. */ static final int OVERSCROLL_LIMIT_DIVISOR = 3; /** * The last scroll state reported to clients through {@link OnScrollListener}. */ private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; private int mTouchSlop; /** * Maximum distance to overscroll by during edge effects */ int mOverscrollDistance; /** * Maximum distance to overfling during edge effects */ int mOverflingDistance; /** * 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 */ int mMotionViewOriginalTop; /** * 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 */ int mMotionX; /** * 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 */ int mTouchMode = TOUCH_MODE_REST; /** * Indicates that this list is always drawn on top of a solid, single-color, opaque * background */ private int mCacheColorHint; /** * Sentinel value for no current active pointer. * Used by {@link #mActivePointerId}. */ private static final int INVALID_POINTER = -1; /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */ private int mActivePointerId = INVALID_POINTER; /** * Acts upon click */ private AbsListView.PerformClick mPerformClick; /** * The select child's view (from the adapter's getView) is enabled. */ private boolean mIsChildViewEnabled; private ContextMenuInfo mContextMenuInfo = null; /** * 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. */ public static int SCROLL_STATE_IDLE = 0; /** * 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 */ 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 * {@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_IDLE}, * {@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 * @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 adaptor */ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount); } public AbsListView(Context context) { super(context); initAbsListView(); setVerticalScrollBarEnabled(true); } public AbsListView(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.absListViewStyle); } public AbsListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initAbsListView(); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AbsListView, defStyle, 0); mDrawSelectorOnTop = a.getBoolean( com.android.internal.R.styleable.AbsListView_drawSelectorOnTop, false); boolean stackFromBottom = a.getBoolean( R.styleable.AbsListView_stackFromBottom, false); setStackFromBottom(stackFromBottom); boolean smoothScrollbar = a.getBoolean( R.styleable.AbsListView_smoothScrollbar, true); setSmoothScrollbarEnabled(smoothScrollbar); a.recycle(); } private void initAbsListView() { // Setting focusable in touch mode will set the focusable property to true setClickable(true); setFocusableInTouchMode(true); setWillNotDraw(false); final ViewConfiguration configuration = ViewConfiguration.get(mContext); mTouchSlop = configuration.getScaledTouchSlop(); mOverscrollDistance = configuration.getScaledOverscrollDistance(); mOverflingDistance = configuration.getScaledOverflingDistance(); } /** * {@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); } } /** * @return true if all list content currently fits within the view boundaries */ private boolean contentFits() { final int childCount = getChildCount(); if (childCount != mItemCount) { return false; } return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom; } /** * Returns the current state of the fast scroll feature. * @see #setFastScrollEnabled(boolean) * @return true if fast scroll is enabled, false otherwise */ public boolean isFastScrollEnabled() { return mFastScrollEnabled; } /** * 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. * * @return True if smooth scrollbar is enabled is enabled, false otherwise. * * @see #setSmoothScrollbarEnabled(boolean) */ public boolean isSmoothScrollbarEnabled() { return mSmoothScrollbarEnabled; } /** * 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 */ void invokeOnItemScrollListener() { if (mOnScrollListener != null) { mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount); } } @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); } } /** * 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 */ 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. * * @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(); } } @Override public void requestLayout() { if (!mBlockLayoutRequests && !mInLayout) { super.requestLayout(); } } /** * The list is empty. Clear everything out. */ void resetList() { removeAllViewsInLayout(); mFirstPosition = 0; mDataChanged = false; mNeedSync = false; mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; setSelectedPositionInt(INVALID_POSITION); setNextSelectedPositionInt(INVALID_POSITION); mSelectedTop = 0; 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; } private void useDefaultSelector() { setSelector(getResources().getDrawable( com.android.internal.R.drawable.list_selector_background)); } @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; } /** * 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); // System.out.println("AbsListView::onLayout"); mInLayout = true; if (changed) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } } layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; } /** * Subclasses must override this method to layout their children. */ protected void layoutChildren() { } @Override 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 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 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 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 android.view.View#getPaddingRight() * @see #getSelector() * * @return The right list padding. */ public int getListPaddingRight() { return mListPadding.right; } /** * Get 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 * * @return A view displaying the data associated with the specified position */ View obtainView(int position) { return mAdapter.getView(position, null, this); } void positionSelector(View sel) { final Rect selectorRect = mSelectorRect; selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); positionSelector(selectorRect.left, selectorRect.top, selectorRect.right, selectorRect.bottom); final boolean isChildViewEnabled = mIsChildViewEnabled; if (sel.isEnabled() != isChildViewEnabled) { mIsChildViewEnabled = !isChildViewEnabled; refreshDrawableState(); } } private void positionSelector(int l, int t, int r, int b) { mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r + mSelectionRightPadding, b + mSelectionBottomPadding); } @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 void onSizeChanged(int w, int h, int oldw, int oldh) { if (getChildCount() > 0) { mDataChanged = true; rememberSyncState(); } } /** * @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_DOWN: // MayLoon Added 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. * * @return True if the selector should be shown */ boolean shouldShowSelector() { return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState(); } private void drawSelector(Canvas canvas) { if (shouldShowSelector() && mSelectorRect != null && !mSelectorRect.isEmpty()) { final Drawable selector = mSelector; selector.setBounds(mSelectorRect); selector.draw(canvas); } } /** * 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. * * @param resID A Drawable resource to use as the selection highlight. * * @attr ref android.R.styleable#AbsListView_listSelector */ public void setSelector(int resID) { setSelector(getResources().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); sel.setState(getDrawableState()); } /** * 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; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: 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; } break; } return super.onKeyUp(keyCode, event); } /** * 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) { mOnScrollListener.onScrollStateChanged(this, newState); mLastScrollState = newState; } } } /** * 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. * * @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. * * @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; } } /** * @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 * * @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. * * @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. */ public void invalidateViews() { mDataChanged = true; rememberSyncState(); requestLayout(); invalidate(); } /** * Makes the item at the supplied position selected. * * @param position the position of the new selection */ abstract void setSelectionInt(int position); @Override protected void handleDataChanged() { int count = mItemCount; 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; 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; checkSelectionChanged(); } /** * 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; default: throw new IllegalArgumentException("direction must be one of " + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); } int deltaX = dX - sX; int deltaY = dY - sY; return deltaY * deltaY + deltaX * deltaX; } /** * 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; } @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; } /** * 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 * * @param color The background color */ public void setCacheColorHint(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 * * @return The cache color hint */ 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. * * @param views A list into which to put the reclaimed views */ public void reclaimViews(List<View> views) { int childCount = getChildCount(); // 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) { views.add(child); } } removeAllViewsInLayout(); } /** * 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 * {@link android.widget.Adapter#getItemViewType(int) } */ 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. */ 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. */ boolean forceAdd; 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); } } /** * 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 = new Rect(); 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; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (mSelector != null) { mSelector.setState(getDrawableState()); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); View v; // if (mFastScroller != null) { // boolean intercepted = mFastScroller.onInterceptTouchEvent(ev); // if (intercepted) { // return true; // } // } switch (action & MotionEvent.ACTION_MASK) { 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; if (touchMode == TOUCH_MODE_FLING) { return true; } break; } case MotionEvent.ACTION_MOVE: { switch (mTouchMode) { case TOUCH_MODE_DOWN: final int pointerIndex = ev.findPointerIndex(mActivePointerId); final int y = (int) ev.getY(pointerIndex); if (startScrollIfNeeded(y - mMotionY)) { return true; } break; } break; } case MotionEvent.ACTION_UP: { mTouchMode = TOUCH_MODE_REST; mActivePointerId = INVALID_POINTER; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); break; } } return false; } private boolean startScrollIfNeeded(int deltaY) { // Check if we have moved far enough that it looks more like a // scroll than a tap final int distance = Math.abs(deltaY); final boolean overscroll = mScrollY != 0; if (/*overscroll || */distance > mTouchSlop) { //createScrollingCache(); mTouchMode = /*overscroll ? TOUCH_MODE_OVERSCROLL : */TOUCH_MODE_SCROLL; mMotionCorrection = deltaY; // final Handler handler = getHandler(); // // Handler should not be null unless the AbsListView is not attached to a // // window, which would make it very hard to scroll it... but the monkeys // // say it's possible. // if (handler != null) { // handler.removeCallbacks(mPendingCheckForLongPress); // } setPressed(false); 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 requestDisallowInterceptTouchEvent(true); return true; } return false; } 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(); } final int action = ev.getAction(); View v; int deltaY; switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final int x = (int) ev.getX(); final int y = (int) ev.getY(); mMotionPosition = pointToPosition(x, y); /* switch (mTouchMode) { case TOUCH_MODE_OVERFLING: { mFlingRunnable.endFling(); mTouchMode = TOUCH_MODE_OVERSCROLL; mMotionY = mLastY = (int) ev.getY(); mMotionCorrection = 0; mActivePointerId = ev.getPointerId(0); break; } default: { mActivePointerId = ev.getPointerId(0); final int x = (int) ev.getX(); final int y = (int) ev.getY(); int motionPosition = pointToPosition(x, y); if (!mDataChanged) { if ((mTouchMode != TOUCH_MODE_FLING) && (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(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { if (ev.getEdgeFlags() != 0 && motionPosition < 0) { // If we couldn't find a view to click on, but the down event was touching // the edge, we will bail out and try again. This allows the edge correcting // code in ViewRoot to try to find a nearby view to select return false; } if (mTouchMode == TOUCH_MODE_FLING) { // Stopped a fling. It is a scroll. createScrollingCache(); mTouchMode = TOUCH_MODE_SCROLL; mMotionCorrection = 0; motionPosition = findMotionRow(y); reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } } }*/ if (mMotionPosition >= 0) { // Remember where the motion event started v = getChildAt(mMotionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); if (v != null && !v.hasFocusable()) { if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { if (!mDataChanged && mAdapter.isEnabled(mMotionPosition)) { setSelectedPositionInt(mMotionPosition); v.setPressed(true); positionSelector(v); if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } } invalidate(); } else { mTouchMode = TOUCH_MODE_REST; } return true; } } } mMotionX = x; mMotionY = y; //mMotionPosition = motionPosition; mLastY = Integer.MIN_VALUE; mTouchMode = TOUCH_MODE_DOWN; break; /*} }*/ //break; } case MotionEvent.ACTION_MOVE: { final int pointerIndex = ev.findPointerIndex(mActivePointerId); final int y = (int) ev.getY(pointerIndex); deltaY = y - mMotionY; 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 startScrollIfNeeded(deltaY); break; case TOUCH_MODE_SCROLL: // if (PROFILE_SCROLLING) { // if (!mScrollProfilingStarted) { // Debug.startMethodTracing("AbsListViewScroll"); // mScrollProfilingStarted = true; // } // } 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(deltaY) > mTouchSlop) { requestDisallowInterceptTouchEvent(true); } final int rawDeltaY = deltaY; deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; 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); // overScrollBy(0, overscroll, 0, mScrollY, 0, 0, // 0, mOverscrollDistance, true); // if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { // // 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())) { // mDirection = 0; // Reset when entering overscroll. // mTouchMode = TOUCH_MODE_OVERSCROLL; // } // } mMotionY = y; invalidate(); } mLastY = y; } break; // case TOUCH_MODE_OVERSCROLL: // if (y != mLastY) { // final int rawDeltaY = deltaY; // deltaY -= mMotionCorrection; // int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; // // final int oldScroll = mScrollY; // final int newScroll = oldScroll - incrementalDeltaY; // int newDirection = y > mLastY ? 1 : -1; // // if (mDirection == 0) { // mDirection = newDirection; // } // // if (mDirection != newDirection) { // // Coming back to 'real' list scrolling // incrementalDeltaY = -newScroll; // mScrollY = 0; // // // No need to do all this work if we're not going to move anyway // if (incrementalDeltaY != 0) { // trackMotionScroll(incrementalDeltaY, incrementalDeltaY); // } // // // Check to see if we are back in // View motionView = this.getChildAt(mMotionPosition - mFirstPosition); // if (motionView != null) { // 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; // motionView = getChildAt(motionPosition - mFirstPosition); // mMotionViewOriginalTop = motionView.getTop(); // mMotionY = y; // mMotionPosition = motionPosition; // } // } else { // overScrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0, // 0, mOverscrollDistance, true); // final int overscrollMode = getOverScrollMode(); // if (overscrollMode == OVER_SCROLL_ALWAYS || // (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && // !contentFits())) { // invalidate(); // } // if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { // // Don't allow overfling if we're at the edge. // mVelocityTracker.clear(); // } // } // mLastY = y; // mDirection = newDirection; // } // break; } break; } case MotionEvent.ACTION_UP: { final int x = (int) ev.getX(); final int y = (int) ev.getY(); mMotionPosition = pointToPosition(x, y); 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 && !child.hasFocusable()) { if (mTouchMode != TOUCH_MODE_DOWN) { child.setPressed(false); } if (mPerformClick == null) { mPerformClick = new PerformClick(); } final AbsListView.PerformClick performClick = mPerformClick; performClick.mChild = child; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); mResurrectToPosition = motionPosition; if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { // final Handler handler = getHandler(); // if (handler != null) { // handler.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(child); //avoid all list items turn orange //setPressed(true); if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } } invalidate(); postDelayed(new Runnable() { public void run() { child.setPressed(false); setPressed(false); setSelectedPositionInt(INVALID_POSITION); if (!mDataChanged) { post(performClick); child.clearFocus(); invalidate(); } mTouchMode = TOUCH_MODE_REST; } }, ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; } return true; } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { post(performClick); child.clearFocus(); invalidate(); } } mTouchMode = TOUCH_MODE_REST; 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); // 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. if (Math.abs(initialVelocity) > mMinimumVelocity && !((mFirstPosition == 0 && firstChildTop == contentTop - mOverscrollDistance) || (mFirstPosition + childCount == mItemCount && lastChildBottom == contentBottom + mOverscrollDistance))) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); mFlingRunnable.start(-initialVelocity); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } } 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(); // final Handler handler = getHandler(); // if (handler != null) { // handler.removeCallbacks(mPendingCheckForLongPress); // } // // if (mVelocityTracker != null) { // mVelocityTracker.recycle(); // mVelocityTracker = null; // } // // mActivePointerId = INVALID_POINTER; // // if (PROFILE_SCROLLING) { // if (mScrollProfilingStarted) { // Debug.stopMethodTracing(); // mScrollProfilingStarted = false; // } // } break; } case MotionEvent.ACTION_CANCEL: { // 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); View motionView = this.getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } // clearScrollingCache(); // // final Handler handler = getHandler(); // if (handler != null) { // handler.removeCallbacks(mPendingCheckForLongPress); // } // // if (mVelocityTracker != null) { // mVelocityTracker.recycle(); // mVelocityTracker = null; // } // } // // if (mEdgeGlowTop != null) { // mEdgeGlowTop.onRelease(); // mEdgeGlowBottom.onRelease(); // } mActivePointerId = INVALID_POINTER; 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 v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); mMotionPosition = motionPosition; } mLastY = y; break; }*/ } return super.onTouchEvent(ev); } /** * 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; // FIXME account for grid vertical spacing too? final int spaceAbove = listPadding.top - firstTop; final int end = getHeight() - listPadding.bottom; 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 - mListPadding.top; // } else { // mFirstPositionDistanceGuess += incrementalDeltaY; // } // if (firstPosition + childCount == mItemCount) { // mLastPositionDistanceGuess = lastBottom + mListPadding.bottom; // } else { // mLastPositionDistanceGuess += incrementalDeltaY; // } if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) { // Don't need to move views down if the top of the first position // is already visible return incrementalDeltaY != 0; } if (firstPosition + childCount == mItemCount && lastBottom <= end && incrementalDeltaY <= 0) { // Don't need to move views up if the bottom of the last position // is already visible 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) { final int top = listPadding.top - incrementalDeltaY; 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) { // mRecycler.addScrapView(child); // if (ViewDebug.TRACE_RECYCLER) { // ViewDebug.trace(child, // ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, // firstPosition + i, -1); // } } } } } else { final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY; 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) { // mRecycler.addScrapView(child); // // if (ViewDebug.TRACE_RECYCLER) { // ViewDebug.trace(child, // ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, // firstPosition + i, -1); // } } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count); } offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } invalidate(); final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); } if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { //positionSelector(getChildAt(childIndex)); } } mBlockLayoutRequests = false; invokeOnItemScrollListener(); awakenScrollBars(); return false; } @Override public boolean showContextMenuForChild(View originalView) { final int longPressPosition = getPositionForView(originalView); if (longPressPosition >= 0) { final long longPressId = mAdapter.getItemId(longPressPosition); boolean handled = false; if (mOnItemLongClickListener != null) { handled = mOnItemLongClickListener.onItemLongClick(AbsListView.this, originalView, longPressPosition, longPressId); } if (!handled) { mContextMenuInfo = createContextMenuInfo( getChildAt(longPressPosition - mFirstPosition), longPressPosition, longPressId); handled = super.showContextMenuForChild(originalView); } return handled; } return false; } /** * 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); } /** * @j2sNative * console.log("Missing method: afterTextChanged"); */ @MayloonStubAnnotation() public void afterTextChanged(Editable s) { System.out.println("Stub" + " Function : afterTextChanged"); return; } /** * @j2sNative * console.log("Missing method: setRecyclerListener"); */ @MayloonStubAnnotation() public void setRecyclerListener(Object listener) { System.out.println("Stub" + " Function : setRecyclerListener"); return; } /** * @j2sNative * console.log("Missing method: setFilterText"); */ @MayloonStubAnnotation() public void setFilterText(String filterText) { System.out.println("Stub" + " Function : setFilterText"); return; } /** * @j2sNative * console.log("Missing method: getTranscriptMode"); */ @MayloonStubAnnotation() public int getTranscriptMode() { System.out.println("Stub" + " Function : getTranscriptMode"); return 0; } /** * @j2sNative * console.log("Missing method: smoothScrollToPosition"); */ @MayloonStubAnnotation() public void smoothScrollToPosition(int position, int boundPosition) { System.out.println("Stub" + " Function : smoothScrollToPosition"); return; } /** * @j2sNative * console.log("Missing method: setScrollingCacheEnabled"); */ @MayloonStubAnnotation() public void setScrollingCacheEnabled(boolean enabled) { System.out.println("Stub" + " Function : setScrollingCacheEnabled"); return; } /** * @j2sNative * console.log("Missing method: setFastScrollEnabled"); */ @MayloonStubAnnotation() public void setFastScrollEnabled(boolean enabled) { System.out.println("Stub" + " Function : setFastScrollEnabled"); return; } /** * @j2sNative * console.log("Missing method: onFilterComplete"); */ @MayloonStubAnnotation() public void onFilterComplete(int count) { System.out.println("Stub" + " Function : onFilterComplete"); return; } /** * @j2sNative * console.log("Missing method: clearTextFilter"); */ @MayloonStubAnnotation() public void clearTextFilter() { System.out.println("Stub" + " Function : clearTextFilter"); return; } /** * @j2sNative * console.log("Missing method: onTouchModeChanged"); */ @MayloonStubAnnotation() public void onTouchModeChanged(boolean isInTouchMode) { System.out.println("Stub" + " Function : onTouchModeChanged"); return; } /** * @j2sNative * console.log("Missing method: isTextFilterEnabled"); */ @MayloonStubAnnotation() public boolean isTextFilterEnabled() { System.out.println("Stub" + " Function : isTextFilterEnabled"); return true; } /** * @j2sNative * console.log("Missing method: hasTextFilter"); */ @MayloonStubAnnotation() public boolean hasTextFilter() { System.out.println("Stub" + " Function : hasTextFilter"); return true; } /** * @j2sNative * console.log("Missing method: isScrollingCacheEnabled"); */ @MayloonStubAnnotation() public boolean isScrollingCacheEnabled() { System.out.println("Stub" + " Function : isScrollingCacheEnabled"); return true; } /** * @j2sNative * console.log("Missing method: clear"); */ @MayloonStubAnnotation() void clear() { System.out.println("Stub" + " Function : clear"); return; } /** * @j2sNative * console.log("Missing method: setTranscriptMode"); */ @MayloonStubAnnotation() public void setTranscriptMode(int mode) { System.out.println("Stub" + " Function : setTranscriptMode"); return; } /** * @j2sNative * console.log("Missing method: setTextFilterEnabled"); */ @MayloonStubAnnotation() public void setTextFilterEnabled(boolean textFilterEnabled) { System.out.println("Stub" + " Function : setTextFilterEnabled"); return; } /** * @j2sNative * console.log("Missing method: smoothScrollToPosition"); */ @MayloonStubAnnotation() public void smoothScrollToPosition(int position) { System.out.println("Stub" + " Function : smoothScrollToPosition"); return; } /** * @j2sNative * console.log("Missing method: getTextFilter"); */ @MayloonStubAnnotation() public CharSequence getTextFilter() { System.out.println("Stub" + " Function : getTextFilter"); return null; } /** * @j2sNative * console.log("Missing method: onGlobalLayout"); */ @MayloonStubAnnotation() public void onGlobalLayout() { System.out.println("Stub" + " Function : onGlobalLayout"); return; } /** * 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 hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount; } } private class PerformClick extends WindowRunnnable implements Runnable { View mChild; int mClickMotionPosition; 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 && mChild != null) /* if (adapter != null && mItemCount > 0 && motionPosition != INVALID_POSITION && motionPosition < adapter.getCount() && sameWindow())*/ { performItemClick(mChild, motionPosition, adapter.getItemId(motionPosition)); } } } }