对于RecyclerView的使用,大家可以查看将替代ListView的RecyclerView 的使用详解(一),单单从代码结构来说RecyclerView确实比ListView优化了很多,也简化了我们编写代码量,但是有一个问题会导致开发者不会去用它,更比说替换ListView了,我不知道使用过RecyclerView的人有没有进一步查看,RecyclerView没有提供Item的点击事件,我们使用列表不仅仅为了显示数据,同时也可以能会交互,所以RecyclerView这个问题导致基本没有人用它,我清楚谷歌是怎么想的,不过RecyclerView也并没有把所有的路给堵死,需要我们写代码来实现Item的点击事件,我们都知道RecyclerView里面新加了ViewHolder这个静态抽象类,这个类里面有一个方法getPosition()可以返回当前ViewHolder实例的位置,实现onItemClick就是使用它来做的,下面有两种方法来实现:
第一种:不修改源码
这种方法不修改源码,问题是只能在RecyclerView.Adapter中实现ItemClick事件
public static class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View itemView) { super(itemView); itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.e("jwzhangjie", "当前点击的位置:"+getPosition()); } }); } }
这种方式直观上看起来不太好,不过也可以实现ItemClick事件。
第二种方法:修改RecyclerView源码
1、把在RecyClerView类里面定义OnItemClickListener接口
/** * Interface definition for a callback to be invoked when an item in this * RecyclerView.Adapter has been clicked. */ public interface OnItemClickListener { /** * Callback method to be invoked when an item in this RecyclerView.Adapter has * been clicked. * <p> * Implementers can call getPosition(position) if they need * to access the data associated with the selected item. * * @param view The view within the RecyclerView.Adapter that was clicked (this * will be a view provided by the adapter) * @param position The position of the view in the adapter. */ void onItemClick(View view, int position); } public static OnItemClickListener mOnItemClickListener = null; /** * Register a callback to be invoked when an item in this AdapterView has * been clicked. * * @param listener The callback that will be invoked. */ public void setOnItemClickListener(OnItemClickListener listener) { mOnItemClickListener = listener; } /** * @return The callback to be invoked with an item in this AdapterView has * been clicked, or null id no callback has been set. */ public final OnItemClickListener getOnItemClickListener() { return mOnItemClickListener; }
2、在RecyclerView中的抽象类ViewHolder中添加View的点击事件
public static abstract class ViewHolder implements OnClickListener{ public final View itemView; int mPosition = NO_POSITION; int mOldPosition = NO_POSITION; long mItemId = NO_ID; int mItemViewType = INVALID_TYPE; /** * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType * are all valid. */ static final int FLAG_BOUND = 1 << 0; /** * The data this ViewHolder's view reflects is stale and needs to be rebound * by the adapter. mPosition and mItemId are consistent. */ static final int FLAG_UPDATE = 1 << 1; /** * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId * are not to be trusted and may no longer match the item view type. * This ViewHolder must be fully rebound to different data. */ static final int FLAG_INVALID = 1 << 2; /** * This ViewHolder points at data that represents an item previously removed from the * data set. Its view may still be used for things like outgoing animations. */ static final int FLAG_REMOVED = 1 << 3; /** * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() * and is intended to keep views around during animations. */ static final int FLAG_NOT_RECYCLABLE = 1 << 4; private int mFlags; private int mIsRecyclableCount = 0; // If non-null, view is currently considered scrap and may be reused for other data by the // scrap container. private Recycler mScrapContainer = null; @Override public void onClick(View v) { if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(itemView, getPosition()); } } public ViewHolder(View itemView) { if (itemView == null) { throw new IllegalArgumentException("itemView may not be null"); } this.itemView = itemView; this.itemView.setOnClickListener(this); } void offsetPosition(int offset) { if (mOldPosition == NO_POSITION) { mOldPosition = mPosition; } mPosition += offset; } void clearOldPosition() { mOldPosition = NO_POSITION; } public final int getPosition() { return mOldPosition == NO_POSITION ? mPosition : mOldPosition; } public final long getItemId() { return mItemId; } public final int getItemViewType() { return mItemViewType; } boolean isScrap() { return mScrapContainer != null; } void unScrap() { mScrapContainer.unscrapView(this); mScrapContainer = null; } void setScrapContainer(Recycler recycler) { mScrapContainer = recycler; } boolean isInvalid() { return (mFlags & FLAG_INVALID) != 0; } boolean needsUpdate() { return (mFlags & FLAG_UPDATE) != 0; } boolean isBound() { return (mFlags & FLAG_BOUND) != 0; } boolean isRemoved() { return (mFlags & FLAG_REMOVED) != 0; } void setFlags(int flags, int mask) { mFlags = (mFlags & ~mask) | (flags & mask); } void addFlags(int flags) { mFlags |= flags; } void clearFlagsForSharedPool() { mFlags = 0; } @Override public String toString() { final StringBuilder sb = new StringBuilder("ViewHolder{" + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId); if (isScrap()) sb.append(" scrap"); if (isInvalid()) sb.append(" invalid"); if (!isBound()) sb.append(" unbound"); if (needsUpdate()) sb.append(" update"); if (isRemoved()) sb.append(" removed"); sb.append("}"); return sb.toString(); }
3、完成上面的步骤,就可以使用RecyclerView来完成itemClick事件了
cashAccountList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(View view, int position) { AppLog.e("position: "+position); } });
下面是完整的RecyclerView源码:
/* * Copyright (C) 2013 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.support.v7.widget; import android.content.Context; import android.database.Observable; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; import android.support.v4.util.ArrayMap; import android.support.v4.util.Pools; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.VelocityTrackerCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.EdgeEffectCompat; import android.support.v4.widget.ScrollerCompat; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.FocusFinder; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Interpolator; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A flexible view for providing a limited window into a large data set. * * <h3>Glossary of terms:</h3> * * <ul> * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views * that represent items in a data set.</li> * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> * <li><em>Index:</em> The index of an attached child view as used in a call to * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> * <li><em>Binding:</em> The process of preparing a child view to display data corresponding * to a <em>position</em> within the adapter.</li> * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter * position may be placed in a cache for later reuse to display the same type of data again * later. This can drastically improve performance by skipping initial layout inflation * or construction.</li> * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached * state during layout. Scrap views may be reused without becoming fully detached * from the parent RecyclerView, either unmodified if no rebinding is required or modified * by the adapter if the view was considered <em>dirty</em>.</li> * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before * being displayed.</li> * </ul> */ public class RecyclerView extends ViewGroup { private static final String TAG = "RecyclerView"; private static final boolean DEBUG = false; private static final boolean ENABLE_PREDICTIVE_ANIMATIONS = false; private static final boolean DISPATCH_TEMP_DETACH = false; public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; public static final int NO_POSITION = -1; public static final long NO_ID = -1; public static final int INVALID_TYPE = -1; private static final int MAX_SCROLL_DURATION = 2000; private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); private final Recycler mRecycler = new Recycler(); private SavedState mPendingSavedState; /** * Note: this Runnable is only ever posted if: * 1) We've been through first layout * 2) We know we have a fixed size (mHasFixedSize) * 3) We're attached */ private final Runnable mUpdateChildViewsRunnable = new Runnable() { public void run() { if (mPendingUpdates.isEmpty()) { return; } eatRequestLayout(); updateChildViews(); resumeRequestLayout(true); } }; private final Rect mTempRect = new Rect(); private final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>(); private final ArrayList<UpdateOp> mPendingLayoutUpdates = new ArrayList<UpdateOp>(); private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE); private Adapter mAdapter; private LayoutManager mLayout; private RecyclerListener mRecyclerListener; private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>(); private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = new ArrayList<OnItemTouchListener>(); private OnItemTouchListener mActiveOnItemTouchListener; private boolean mIsAttached; private boolean mHasFixedSize; private boolean mFirstLayoutComplete; private boolean mEatRequestLayout; private boolean mLayoutRequestEaten; private boolean mAdapterUpdateDuringMeasure; private final boolean mPostUpdatesOnAnimation; private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; ItemAnimator mItemAnimator = new DefaultItemAnimator(); private static final int INVALID_POINTER = -1; /** * The RecyclerView is not currently scrolling. * @see #getScrollState() */ public static final int SCROLL_STATE_IDLE = 0; /** * The RecyclerView is currently being dragged by outside input such as user touch input. * @see #getScrollState() */ public static final int SCROLL_STATE_DRAGGING = 1; /** * The RecyclerView is currently animating to a final position while not under * outside control. * @see #getScrollState() */ public static final int SCROLL_STATE_SETTLING = 2; // Touch/scrolling handling private int mScrollState = SCROLL_STATE_IDLE; private int mScrollPointerId = INVALID_POINTER; private VelocityTracker mVelocityTracker; private int mInitialTouchX; private int mInitialTouchY; private int mLastTouchX; private int mLastTouchY; private final int mTouchSlop; private final int mMinFlingVelocity; private final int mMaxFlingVelocity; private final ViewFlinger mViewFlinger = new ViewFlinger(); private final State mState = new State(); private OnScrollListener mScrollListener; // For use in item animations boolean mItemsAddedOrRemoved = false; boolean mItemsChanged = false; int mAnimatingViewIndex = -1; int mNumAnimatingViews = 0; boolean mInPreLayout = false; private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = new ItemAnimatorRestoreListener(); private boolean mPostedAnimatorRunner = false; private Runnable mItemAnimatorRunner = new Runnable() { @Override public void run() { if (mItemAnimator != null) { mItemAnimator.runPendingAnimations(); } mPostedAnimatorRunner = false; } }; private static final Interpolator sQuinticInterpolator = new Interpolator() { public float getInterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; } }; public RecyclerView(Context context) { this(context, null); } public RecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); final int version = Build.VERSION.SDK_INT; mPostUpdatesOnAnimation = version >= 16; final ViewConfiguration vc = ViewConfiguration.get(context); mTouchSlop = vc.getScaledTouchSlop(); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); mItemAnimator.setListener(mItemAnimatorListener); } /** * RecyclerView can perform several optimizations if it can know in advance that changes in * adapter content cannot change the size of the RecyclerView itself. * If your use of RecyclerView falls into this category, set this to true. * * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. */ public void setHasFixedSize(boolean hasFixedSize) { mHasFixedSize = hasFixedSize; } /** * @return true if the app has specified that changes in adapter content cannot change * the size of the RecyclerView itself. */ public boolean hasFixedSize() { return mHasFixedSize; } /** * Set a new adapter to provide child views on demand. * * @param adapter The new adapter to set, or null to set no adapter. */ public void setAdapter(Adapter adapter) { if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); } // end all running animations if (mItemAnimator != null) { mItemAnimator.endAnimations(); } // Since animations are ended, mLayout.children should be equal to recyclerView.children. // This may not be true if item animator's end does not work as expected. (e.g. not release // children instantly). It is safer to use mLayout's child count. if (mLayout != null) { mLayout.removeAndRecycleAllViews(mRecycler); mLayout.removeAndRecycleScrapInt(mRecycler, true); } final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { adapter.registerAdapterDataObserver(mObserver); } if (mLayout != null) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } mRecycler.onAdapterChanged(oldAdapter, mAdapter); mState.mStructureChanged = true; markKnownViewsInvalid(); requestLayout(); } /** * Retrieves the previously set adapter or null if no adapter is set. * * @return The previously set adapter * @see #setAdapter(Adapter) */ public Adapter getAdapter() { return mAdapter; } /** * Register a listener that will be notified whenever a child view is recycled. * * <p>This listener will be called when a LayoutManager or the RecyclerView decides * that a child view is no longer needed. If an application associates expensive * or heavyweight data with item views, this may be a good place to release * or free those resources.</p> * * @param listener Listener to register, or null to clear */ public void setRecyclerListener(RecyclerListener listener) { mRecyclerListener = listener; } /** * Set the {@link LayoutManager} that this RecyclerView will use. * * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom * layout arrangements for child views. These arrangements are controlled by the * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> * * <p>Several default strategies are provided for common uses such as lists and grids.</p> * * @param layout LayoutManager to use */ public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } mRecycler.clear(); removeAllViews(); if (mLayout != null) { if (mIsAttached) { mLayout.onDetachedFromWindow(this); } mLayout.mRecyclerView = null; } mLayout = layout; if (layout != null) { if (layout.mRecyclerView != null) { throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView: " + layout.mRecyclerView); } layout.mRecyclerView = this; if (mIsAttached) { mLayout.onAttachedToWindow(this); } } requestLayout(); } @Override protected Parcelable onSaveInstanceState() { SavedState state = new SavedState(super.onSaveInstanceState()); if (mPendingSavedState != null) { state.copyFrom(mPendingSavedState); } else if (mLayout != null) { state.mLayoutState = mLayout.onSaveInstanceState(); } else { state.mLayoutState = null; } return state; } @Override protected void onRestoreInstanceState(Parcelable state) { mPendingSavedState = (SavedState) state; super.onRestoreInstanceState(mPendingSavedState.getSuperState()); if (mLayout != null && mPendingSavedState.mLayoutState != null) { mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); } } /** * Adds a view to the animatingViews list. * mAnimatingViews holds the child views that are currently being kept around * purely for the purpose of being animated out of view. They are drawn as a regular * part of the child list of the RecyclerView, but they are invisible to the LayoutManager * as they are managed separately from the regular child views. * @param view The view to be removed */ private void addAnimatingView(View view) { boolean alreadyAdded = false; if (mNumAnimatingViews > 0) { for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) { if (getChildAt(i) == view) { alreadyAdded = true; break; } } } if (!alreadyAdded) { if (mNumAnimatingViews == 0) { mAnimatingViewIndex = getChildCount(); } ++mNumAnimatingViews; addView(view); } mRecycler.unscrapView(getChildViewHolder(view)); } /** * Removes a view from the animatingViews list. * @param view The view to be removed * @see #addAnimatingView(View) */ private void removeAnimatingView(View view) { if (mNumAnimatingViews > 0) { for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) { if (getChildAt(i) == view) { removeViewAt(i); --mNumAnimatingViews; if (mNumAnimatingViews == 0) { mAnimatingViewIndex = -1; } mRecycler.recycleView(view); return; } } } } private View getAnimatingView(int position, int type) { if (mNumAnimatingViews > 0) { for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) { final View view = getChildAt(i); ViewHolder holder = getChildViewHolder(view); if (holder.getPosition() == position && ( type == INVALID_TYPE || holder.getItemViewType() == type)) { return view; } } } return null; } /** * Return the {@link LayoutManager} currently responsible for * layout policy for this RecyclerView. * * @return The currently bound LayoutManager */ public LayoutManager getLayoutManager() { return mLayout; } /** * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; * if no pool is set for this view a new one will be created. See * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. * * @return The pool used to store recycled item views for reuse. * @see #setRecycledViewPool(RecycledViewPool) */ public RecycledViewPool getRecycledViewPool() { return mRecycler.getRecycledViewPool(); } /** * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. * This can be useful if you have multiple RecyclerViews with adapters that use the same * view types, for example if you have several data sets with the same kinds of item views * displayed by a {@link android.support.v4.view.ViewPager ViewPager}. * * @param pool Pool to set. If this parameter is null a new pool will be created and used. */ public void setRecycledViewPool(RecycledViewPool pool) { mRecycler.setRecycledViewPool(pool); } /** * Set the number of offscreen views to retain before adding them to the potentially shared * {@link #getRecycledViewPool() recycled view pool}. * * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing * a LayoutManager to reuse those views unmodified without needing to return to the adapter * to rebind them.</p> * * @param size Number of views to cache offscreen before returning them to the general * recycled view pool */ public void setItemViewCacheSize(int size) { mRecycler.setViewCacheSize(size); } /** * Return the current scrolling state of the RecyclerView. * * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or * {@link #SCROLL_STATE_SETTLING} */ public int getScrollState() { return mScrollState; } private void setScrollState(int state) { if (state == mScrollState) { return; } mScrollState = state; if (state != SCROLL_STATE_SETTLING) { stopScroll(); } if (mScrollListener != null) { mScrollListener.onScrollStateChanged(state); } } /** * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can * affect both measurement and drawing of individual item views. * * <p>Item decorations are ordered. Decorations placed earlier in the list will * be run/queried/drawn first for their effects on item views. Padding added to views * will be nested; a padding added by an earlier decoration will mean further * item decorations in the list will be asked to draw/pad within the previous decoration's * given area.</p> * * @param decor Decoration to add * @param index Position in the decoration chain to insert this decoration at. If this value * is negative the decoration will be added at the end. */ public void addItemDecoration(ItemDecoration decor, int index) { if (mItemDecorations.isEmpty()) { setWillNotDraw(false); } if (index < 0) { mItemDecorations.add(decor); } else { mItemDecorations.add(index, decor); } markItemDecorInsetsDirty(); requestLayout(); } /** * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can * affect both measurement and drawing of individual item views. * * <p>Item decorations are ordered. Decorations placed earlier in the list will * be run/queried/drawn first for their effects on item views. Padding added to views * will be nested; a padding added by an earlier decoration will mean further * item decorations in the list will be asked to draw/pad within the previous decoration's * given area.</p> * * @param decor Decoration to add */ public void addItemDecoration(ItemDecoration decor) { addItemDecoration(decor, -1); } /** * Remove an {@link ItemDecoration} from this RecyclerView. * * <p>The given decoration will no longer impact the measurement and drawing of * item views.</p> * * @param decor Decoration to remove * @see #addItemDecoration(ItemDecoration) */ public void removeItemDecoration(ItemDecoration decor) { mItemDecorations.remove(decor); if (mItemDecorations.isEmpty()) { setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); } markItemDecorInsetsDirty(); requestLayout(); } /** * Set a listener that will be notified of any changes in scroll state or position. * * @param listener Listener to set or null to clear */ public void setOnScrollListener(OnScrollListener listener) { mScrollListener = listener; } /** * Convenience method to scroll to a certain position. * * RecyclerView does not implement scrolling logic, rather forwards the call to * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)} * @param position Scroll to this adapter position * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int) */ public void scrollToPosition(int position) { stopScroll(); mLayout.scrollToPosition(position); awakenScrollBars(); } /** * Starts a smooth scroll to an adapter position. * <p> * To support smooth scrolling, you must override * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a * {@link SmoothScroller}. * <p> * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to * provide a custom smooth scroll logic, override * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your * LayoutManager. * * @param position The adapter position to scroll to * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) */ public void smoothScrollToPosition(int position) { mLayout.smoothScrollToPosition(this, mState, position); } @Override public void scrollTo(int x, int y) { throw new UnsupportedOperationException( "RecyclerView does not support scrolling to an absolute position."); } @Override public void scrollBy(int x, int y) { if (mLayout == null) { throw new IllegalStateException("Cannot scroll without a LayoutManager set. " + "Call setLayoutManager with a non-null argument."); } final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); final boolean canScrollVertical = mLayout.canScrollVertically(); if (canScrollHorizontal || canScrollVertical) { scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0); } } /** * Helper method reflect data changes to the state. * <p> * Adapter changes during a scroll may trigger a crash because scroll assumes no data change * but data actually changed. * <p> * This method consumes all deferred changes to avoid that case. * <p> * This also ends all pending animations. It will be changed once we can support * animations during scroll. */ private void consumePendingUpdateOperations() { if (mItemAnimator != null) { mItemAnimator.endAnimations(); } if (mPendingUpdates.size() > 0) { mUpdateChildViewsRunnable.run(); } } /** * Does not perform bounds checking. Used by internal methods that have already validated input. */ void scrollByInternal(int x, int y) { int overscrollX = 0, overscrollY = 0; consumePendingUpdateOperations(); if (mAdapter != null) { eatRequestLayout(); if (x != 0) { final int hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState); overscrollX = x - hresult; } if (y != 0) { final int vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState); overscrollY = y - vresult; } resumeRequestLayout(false); } if (!mItemDecorations.isEmpty()) { invalidate(); } if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { pullGlows(overscrollX, overscrollY); } if (mScrollListener != null && (x != 0 || y != 0)) { mScrollListener.onScrolled(x, y); } if (!awakenScrollBars()) { invalidate(); } } /** * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal * range. This value is used to compute the length of the thumb within the scrollbar's track. * </p> * * <p>The range is expressed in arbitrary units that must be the same as the units used by * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> * * <p>Default implementation returns 0.</p> * * <p>If you want to support scroll bars, override * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your * LayoutManager. </p> * * @return The horizontal offset of the scrollbar's thumb * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset * (RecyclerView.Adapter) */ @Override protected int computeHorizontalScrollOffset() { return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; } /** * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the * horizontal range. This value is used to compute the length of the thumb within the * scrollbar's track.</p> * * <p>The range is expressed in arbitrary units that must be the same as the units used by * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> * * <p>Default implementation returns 0.</p> * * <p>If you want to support scroll bars, override * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your * LayoutManager.</p> * * @return The horizontal extent of the scrollbar's thumb * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) */ @Override protected int computeHorizontalScrollExtent() { return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; } /** * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> * * <p>The range is expressed in arbitrary units that must be the same as the units used by * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> * * <p>Default implementation returns 0.</p> * * <p>If you want to support scroll bars, override * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your * LayoutManager.</p> * * @return The total horizontal range represented by the vertical scrollbar * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) */ @Override protected int computeHorizontalScrollRange() { return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; } /** * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. * This value is used to compute the length of the thumb within the scrollbar's track. </p> * * <p>The range is expressed in arbitrary units that must be the same as the units used by * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> * * <p>Default implementation returns 0.</p> * * <p>If you want to support scroll bars, override * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your * LayoutManager.</p> * * @return The vertical offset of the scrollbar's thumb * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset * (RecyclerView.Adapter) */ @Override protected int computeVerticalScrollOffset() { return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; } /** * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. * This value is used to compute the length of the thumb within the scrollbar's track.</p> * * <p>The range is expressed in arbitrary units that must be the same as the units used by * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> * * <p>Default implementation returns 0.</p> * * <p>If you want to support scroll bars, override * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your * LayoutManager.</p> * * @return The vertical extent of the scrollbar's thumb * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(Recycle