RecyclerView源码详解(第三篇LayoutManager源码详解)

来源:互联网 发布:淘宝售后人员工作流程 编辑:程序博客网 时间:2024/06/08 09:50

上两篇已经谈到RecyclerView布局子View的位置完全是交给LayoutManager的子类来实现,它不像ListView和GridView那样什么事情都自己处理,而把一些功能完全抽离出来交给客户端自己扩展,当然它也提供了类似ListView和GridView的布局管理器,如LinearLayoutManager线性布局,GridLayoutManager网格布局,瀑布流布局StaggeredGridLayoutManager。首先我们来看一下LayoutManager这个抽象类到底有几个抽象方法,再重写这个类的时候,我们必须实现抽象方法


generateDefaultLayoutParams() :这个方法获得子控件LayoutParams属性的时候默认调用,我们可以在这个方法里扩展一下我们自己新加的控件属性

在源码中只找到这一个抽象方法,麻痹,难道重写它的时候就只实现这个方法吗?尝试了一下果然不行,子View都不知道死哪去了
public void setLayoutManager(LayoutManager layout) {        if (layout == mLayout) {            return;        }        stopScroll();        // TODO We should do this switch a dispatchLayout pass and animate children. There is a good        // chance that LayoutManagers will re-use views.        if (mLayout != null) {            // end all running animations            if (mItemAnimator != null) {                mItemAnimator.endAnimations();            }            mLayout.removeAndRecycleAllViews(mRecycler);            mLayout.removeAndRecycleScrapInt(mRecycler);            mRecycler.clear();            if (mIsAttached) {                mLayout.dispatchDetachedFromWindow(this, mRecycler);            }            mLayout.setRecyclerView(null);            mLayout = null;        } else {            mRecycler.clear();        }        // this is just a defensive measure for faulty item animators.        mChildHelper.removeAllViewsUnfiltered();        mLayout = layout;        if (layout != null) {            if (layout.mRecyclerView != null) {                throw new IllegalArgumentException("LayoutManager " + layout +                        " is already attached to a RecyclerView: " + layout.mRecyclerView);            }            mLayout.setRecyclerView(this);            if (mIsAttached) {                mLayout.dispatchAttachedToWindow(this);            }        }        mRecycler.updateViewCacheSize();        requestLayout();    }
这个方法是为RecyclerView设置LayoutManager的,在最后一行惊喜的发现 requestLayout(),这是什么,每次设置了LayoutManager之后就会执行View树的重绘,想到重绘的三部曲不就是onMeasure、onLayout、onDraw吗,ok先看一下RecycleView的onMeasure方法
@Overrideprotected void onMeasure(int widthSpec, int heightSpec) {// 正在测量的时候数据通知改变if (mAdapterUpdateDuringMeasure) {eatRequestLayout();processAdapterUpdatesAndSetAnimationFlags();/** * 如果设置了动画的话mState.mInPreLayout为true */if (mState.mRunPredictiveAnimations) {// TODO: try to provide a better approach.// When RV decides to run predictive animations, we need to// measure in pre-layout// state so that pre-layout pass results in correct layout.// On the other hand, this will prevent the layout manager from// resizing properly.mState.mInPreLayout = true;} else {// consume remaining updates to provide a consistent state with// the layout pass.mAdapterHelper.consumeUpdatesInOnePass();mState.mInPreLayout = false;}mAdapterUpdateDuringMeasure = false;resumeRequestLayout(false);}if (mAdapter != null) {// 为状态添加子View的数量mState.mItemCount = mAdapter.getItemCount();} else {mState.mItemCount = 0;}// 假如布局文件为空,那么采用默认测量if (mLayout == null) {defaultOnMeasure(widthSpec, heightSpec);} else {// 否则用布局文件测量mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);}mState.mInPreLayout = false; // clear}

在方法的最后可以看到,如果mLayout 不为null,就会用mLayout.onMeasure 方法进行测量,如果没有,调用默认的方法测量,也就是我们可以重写LayoutManager的
onMeasure方法进行测量子view(不过这时还没有调用CreateHodler所以没有子View,所以一般此方法不用重写),那么接下来就是RecyclerView的onLayout方法了
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {eatRequestLayout();// TraceView的速度测量TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);dispatchLayout();TraceCompat.endSection();resumeRequestLayout(false);// 第一次布局完成时mFirstLayoutComplete = true;}

这个方法较为简洁,都是调用的其他方法,布局的调用方法是在dispatchLayout方法中
void dispatchLayout() {// 没有adapter和mLayout直接返回if (mAdapter == null) {Log.e(TAG, "No adapter attached; skipping layout");return;}if (mLayout == null) {Log.e(TAG, "No layout manager attached; skipping layout");return;}// 储存动画信息的类mViewInfoStore.clear();eatRequestLayout();// 布局和滚动的累加器onEnterLayoutOrScroll();// 设置一些必要的参数processAdapterUpdatesAndSetAnimationFlags();mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations&& mItemsChanged;mItemsAddedOrRemoved = mItemsChanged = false;// 如果有动画的话mInPreLayout为truemState.mInPreLayout = mState.mRunPredictiveAnimations;mState.mItemCount = mAdapter.getItemCount();findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);mState.mItemCount = mAdapter.getItemCount();//省略若干行….// Step 2: Run layoutmState.mInPreLayout = false;mLayout.onLayoutChildren(mRecycler, mState);//省略若干行…..}
dispatchLayout方法代码量相当的多,所以省略了一些itemView动画参数的判断和处理,也省略了滚动到那个位置的参数清空处理等,很明显,最后的代码调用了LayoutManager的onLayoutChildren方法进行子View的位置排版,由于onMeasure的时候子View还不存在,所以测量和布局都会交给onLayoutChildren来处理,下面以LinearLayoutManager方法为例
public void onLayoutChildren(RecyclerView.Recycler recycler,RecyclerView.State state) {省略若干行代码….if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {if (state.getItemCount() == 0) {//移除所有的子ViewremoveAndRecycleAllViews(recycler);return;}}省略若干行代码….mAnchorInfo.reset();mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;// calculate anchor position and coordinate// 得到锚点updateAnchorInfoForLayout(recycler, state, mAnchorInfo);int startOffset;int endOffset;onAnchorReady(recycler, state, mAnchorInfo);//回收所有的子View进scrapViewdetachAndScrapAttachedViews(recycler);mLayoutState.mIsPreLayout = state.isPreLayout();if (mAnchorInfo.mLayoutFromEnd) {// fill towards startupdateLayoutStateToFillStart(mAnchorInfo);mLayoutState.mExtra = extraForStart;//填充子Viewfill(recycler, mLayoutState, state, false);startOffset = mLayoutState.mOffset;final int firstElement = mLayoutState.mCurrentPosition;if (mLayoutState.mAvailable > 0) {extraForEnd += mLayoutState.mAvailable;}// fill towards endupdateLayoutStateToFillEnd(mAnchorInfo);mLayoutState.mExtra = extraForEnd;mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;fill(recycler, mLayoutState, state, false);endOffset = mLayoutState.mOffset;if (mLayoutState.mAvailable > 0) {// end could not consume all. add more items towards startextraForStart = mLayoutState.mAvailable;updateLayoutStateToFillStart(firstElement, startOffset);mLayoutState.mExtra = extraForStart;fill(recycler, mLayoutState, state, false);startOffset = mLayoutState.mOffset;}} else {// fill towards endupdateLayoutStateToFillEnd(mAnchorInfo);mLayoutState.mExtra = extraForEnd;fill(recycler, mLayoutState, state, false);endOffset = mLayoutState.mOffset;final int lastElement = mLayoutState.mCurrentPosition;if (mLayoutState.mAvailable > 0) {extraForStart += mLayoutState.mAvailable;}// fill towards startupdateLayoutStateToFillStart(mAnchorInfo);mLayoutState.mExtra = extraForStart;mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;fill(recycler, mLayoutState, state, false);startOffset = mLayoutState.mOffset;if (mLayoutState.mAvailable > 0) {extraForEnd = mLayoutState.mAvailable;// start could not consume all it should. add more items towards// endupdateLayoutStateToFillEnd(lastElement, endOffset);mLayoutState.mExtra = extraForEnd;fill(recycler, mLayoutState, state, false);endOffset = mLayoutState.mOffset;}}省略若干行…}


这个方法做的最主要工作就是调用detachAndScrapAttachedViews回收以前的itemView,updateAnchorInfoForLayout方法寻找锚点,也就是参考坐标,假如以屏幕上方为参考坐
标的话,那么子View就从屏幕上方开始填入RecyclerView,如果在下方则从底部开始填充,当然RecycleView刚开始显示的时候,默认是从屏幕上面开始填充的,锚点也有可能是在屏幕任何一个位置,比如我调用scrollToPosition方法滚动哪一个位置,那么这个子View将在下一次的Onlayout充当锚点,以它为基础向上布局,向下布局,找到锚点后就是向上或向下填充子view了,填充调用fill方法。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {// max offset we should set is mFastScroll + availablefinal int start = layoutState.mAvailable;if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {// TODO ugly bug fix. should not happenif (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}//回收超过边界的ViewrecycleByLayoutState(recycler, layoutState);}int remainingSpace = layoutState.mAvailable + layoutState.mExtra;LayoutChunkResult layoutChunkResult = new LayoutChunkResult();while (remainingSpace > 0 && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();layoutChunk(recycler, state, layoutState, layoutChunkResult);if (layoutChunkResult.mFinished) {break;}//间距累加上个子控件所占用的高度layoutState.mOffset += layoutChunkResult.mConsumed* layoutState.mLayoutDirection;/** * Consume the available space if: * layoutChunk did not request to * be ignored * OR we are laying out scrap children * OR we are not * doing pre-layout */if (!layoutChunkResult.mIgnoreConsumed|| mLayoutState.mScrapList != null || !state.isPreLayout()) {layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is// important for recyclingremainingSpace -= layoutChunkResult.mConsumed;}if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {//最后一个view离开屏幕的偏移量layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}}if (DEBUG) {validateChildOrder();}//返回实际占用了多少距离,只显示一部分的return start - layoutState.mAvailable;}


int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {// max offset we should set is mFastScroll + availablefinal int start = layoutState.mAvailable;if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {// TODO ugly bug fix. should not happenif (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}//回收超过边界的ViewrecycleByLayoutState(recycler, layoutState);}int remainingSpace = layoutState.mAvailable + layoutState.mExtra;LayoutChunkResult layoutChunkResult = new LayoutChunkResult();while (remainingSpace > 0 && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();layoutChunk(recycler, state, layoutState, layoutChunkResult);if (layoutChunkResult.mFinished) {break;}//间距累加上个子控件所占用的高度layoutState.mOffset += layoutChunkResult.mConsumed* layoutState.mLayoutDirection;/** * Consume the available space if: * layoutChunk did not request to * be ignored * OR we are laying out scrap children * OR we are not * doing pre-layout */if (!layoutChunkResult.mIgnoreConsumed|| mLayoutState.mScrapList != null || !state.isPreLayout()) {layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is// important for recyclingremainingSpace -= layoutChunkResult.mConsumed;}if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {//最后一个view离开屏幕的偏移量layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}}if (DEBUG) {validateChildOrder();}//返回实际占用了多少距离,只显示一部分的return start - layoutState.mAvailable;}

首先获得屏幕还有多少剩余空间remainingSpace可用,根据这个值不断的减去子View所占用的空间大小,当这个值小于0的时候那么,布局子View结束,如果当前所有子View满足还没有超过remainingSpace时调用layoutChunk安排下一个view的位置
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {View view = layoutState.next(recycler);if (view == null) {if (DEBUG && layoutState.mScrapList == null) {throw new RuntimeException("received null view when unexpected");}// if we are laying out views in scrap, this may return null which// means there is// no more items to layout.result.mFinished = true;return;}LayoutParams params = (LayoutParams) view.getLayoutParams();if (layoutState.mScrapList == null) {if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {addView(view);} else {addView(view, 0);}} else {if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {addDisappearingView(view);} else {addDisappearingView(view, 0);}}//测量子View、measureChildWithMargins(view, 0, 0);//result.mConsumed=子View总够需要的空间result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);int left, top, right, bottom;if (mOrientation == VERTICAL) {if (isLayoutRTL()) {right = getWidth() - getPaddingRight();left = right- mOrientationHelper.getDecoratedMeasurementInOther(view);} else {left = getPaddingLeft();right = left+ mOrientationHelper.getDecoratedMeasurementInOther(view);}if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {bottom = layoutState.mOffset;top = layoutState.mOffset - result.mConsumed;} else {top = layoutState.mOffset;bottom = layoutState.mOffset + result.mConsumed;}} else {top = getPaddingTop();//需加上分割线的距离定义top的位置bottom = top+ mOrientationHelper.getDecoratedMeasurementInOther(view);if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {right = layoutState.mOffset;left = layoutState.mOffset - result.mConsumed;} else {left = layoutState.mOffset;right = layoutState.mOffset + result.mConsumed;}}// We calculate everything with View's bounding box (which includes// decor and margins)// To calculate correct layout position, we subtract margins.layoutDecorated(view, left + params.leftMargin, top + params.topMargin,right - params.rightMargin, bottom - params.bottomMargin);if (DEBUG) {Log.d(TAG, "laid out child at position " + getPosition(view)+ ", with l:" + (left + params.leftMargin) + ", t:"+ (top + params.topMargin) + ", r:"+ (right - params.rightMargin) + ", b:"+ (bottom - params.bottomMargin));}// Consume the available space if the view is not removed OR changedif (params.isItemRemoved() || params.isItemChanged()) {result.mIgnoreConsumed = true;}result.mFocusable = view.isFocusable();}

这个方法首先通过layoutState.next获取到下一个子View,然后通过addView将子view添加到RecyclerView中,然后通过measureChildWithMargins测量子view的大小,然后计算子
view的left、top、right、bottom,最后layoutDecorated实现子view的位置排列。那么先看一下怎么通过适配器获得子View的
View next(RecyclerView.Recycler recycler) {if (mScrapList != null) {return nextViewFromScrapList();}final View view = recycler.getViewForPosition(mCurrentPosition);mCurrentPosition += mItemDirection;return view;}
首先通过判断mScrapList 集合中是否有缓存有的话就用mScrapList里面的数据,这个缓存主要用于onLayout被调用多次时用的,假如第一次没有子view,那么创建的子view会
被添加到mScrapList中,第二次的时候不会创建子view,而是直接取缓存获得速度。也就是说这个缓存只在onlayout中能用到,如果没有数据,那么调用getViewForPosition
获得子view
View getViewForPosition(int position, boolean dryRun) {if (position < 0 || position >= mState.getItemCount()) {throw new IndexOutOfBoundsException("Invalid item position "+ position + "(" + position + "). Item count:"+ mState.getItemCount());}boolean fromScrap = false;ViewHolder holder = null;// 0) If there is a changed scrap, try to find from thereif (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrap = holder != null;}// 1) Find from scrap by positionif (holder == null) {holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {// recycle this scrapif (!dryRun) {// we would like to recycle this but need to make// sure it is not used by// animation logic etc.holder.addFlags(ViewHolder.FLAG_INVALID);if (holder.isScrap()) {removeDetachedView(holder.itemView, false);holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}recycleViewHolderInternal(holder);}holder = null;} else {fromScrap = true;}}}if (holder == null) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);if (offsetPosition < 0|| offsetPosition >= mAdapter.getItemCount()) {throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "+ "position " + position + "(offset:"+ offsetPosition + ")." + "state:"+ mState.getItemCount());}final int type = mAdapter.getItemViewType(offsetPosition);// 2) Find from scrap via stable ids, if existsif (mAdapter.hasStableIds()) {holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrap = true;}}if (holder == null && mViewCacheExtension != null) {// We are NOT sending the offsetPosition because// LayoutManager does not// know it.final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);if (holder == null) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view which does not have a ViewHolder");} else if (holder.shouldIgnore()) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view that is ignored. You must call stopIgnoring before"+ " returning this view.");}}}if (holder == null) { // fallback to recycler// try recycler.// Head to the shared pool.if (DEBUG) {Log.d(TAG, "getViewForPosition(" + position+ ") fetching from shared " + "pool");}holder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();if (FORCE_INVALIDATE_DISPLAY_LIST) {invalidateDisplayListInt(holder);}}}if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);if (DEBUG) {Log.d(TAG, "getViewForPosition created new ViewHolder");}}}// This is very ugly but the only place we can grab this information// before the View is rebound and returned to the LayoutManager for// post layout ops.// We don't need this in pre-layout since the VH is not updated by// the LM.if (fromScrap&& !mState.isPreLayout()&& holder.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);if (mState.mRunSimpleAnimations) {int changeFlags = ItemAnimator.buildAdapterChangeFlagsForAnimations(holder);changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, holder,changeFlags, holder.getUnmodifiedPayloads());recordAnimationInfoIfBouncedHiddenView(holder, info);}}boolean bound = false;if (mState.isPreLayout() && holder.isBound()) {// do not update unless we absolutely have to.holder.mPreLayoutPosition = position;} else if (!holder.isBound() || holder.needsUpdate()|| holder.isInvalid()) {if (DEBUG && holder.isRemoved()) {throw new IllegalStateException("Removed holder should be bound and it should"+ " come here only in pre-layout. Holder: "+ holder);}final int offsetPosition = mAdapterHelper.findPositionOffset(position);holder.mOwnerRecyclerView = RecyclerView.this;mAdapter.bindViewHolder(holder, offsetPosition);attachAccessibilityDelegate(holder.itemView);bound = true;if (mState.isPreLayout()) {holder.mPreLayoutPosition = position;}}final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();final LayoutParams rvLayoutParams;if (lp == null) {rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();holder.itemView.setLayoutParams(rvLayoutParams);} else if (!checkLayoutParams(lp)) {rvLayoutParams = (LayoutParams) generateLayoutParams(lp);holder.itemView.setLayoutParams(rvLayoutParams);} else {rvLayoutParams = (LayoutParams) lp;}rvLayoutParams.mViewHolder = holder;rvLayoutParams.mPendingInvalidate = fromScrap && bound;return holder.itemView;}
这个方法的代码量很大,但主题意思不难看懂,首先从mAttachedScrap缓存集合中获取,如果有的话,如果没有的话从mViewCacheExtension集合中获取,如果还没有的话,从RecycledViewPool缓存中获取,如果还是没有的话,那么只能通过mAdapter.createViewHolder创建了。从中可以看出RecyclerView总共用了四级缓存
mScrapList缓存:在onLayout中使用mAttachedScrap:在滑动的时候缓存viewmViewCacheExtension:用户扩展缓存RecycledViewPool:在滑动的时候使用,配合mAttachedScrap看到这可以看出,如果想自定义LayoutManager的话,那么必须重写onLayoutChildren方法布局子view,还不要忘了将子view在合适的时机加入的缓存中。最后看一下在手指滑动的时候LayoutManager需要做一些什么操作,那么进入RecyclerView的onTouchEvent里看一下
public boolean onTouchEvent(MotionEvent e) {final boolean canScrollHorizontally = mLayout.canScrollHorizontally();final boolean canScrollVertically = mLayout.canScrollVertically();省略若干行…..switch (action) {case MotionEvent.ACTION_MOVE: {final int index = MotionEventCompat.findPointerIndex(e,mScrollPointerId);省略若干行…..if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0, vtev)) {getParent().requestDisallowInterceptTouchEvent(true);} {省略若干行…..}省略若干行…..break;case MotionEvent.ACTION_UP: {省略若干行…..if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {setScrollState(SCROLL_STATE_IDLE);}resetTouch();}break;}省略若干行…..return true;}

这个方法代码将多,这里只留最主要的代码,可以看出这里回调了mLayout.canScrollHorizontally()和mLayout.canScrollVertically(),也就是说在重写LayoutManager的方法
时,如果我们想左右滑动就将canScrollHorizontally的返回值设为true,同理想垂直滑动的话canScrollVertically必须也得返回true。如果满足一些列条件的话滑动最后将交给scrollByInternal方法处理,手指抬起时如果认为是快速滑动的话,那么会调用fling方法进行移动,那么先看一下scrollByInternal方法
boolean scrollByInternal(int x, int y, MotionEvent ev) {int unconsumedX = 0, unconsumedY = 0;int consumedX = 0, consumedY = 0;consumePendingUpdateOperations();if (mAdapter != null) {eatRequestLayout();onEnterLayoutOrScroll();TraceCompat.beginSection(TRACE_SCROLL_TAG);if (x != 0) {consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);unconsumedX = x - consumedX;}if (y != 0) {consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);unconsumedY = y - consumedY;}TraceCompat.endSection();repositionShadowingViews();onExitLayoutOrScroll();resumeRequestLayout(false);}if (!mItemDecorations.isEmpty()) {invalidate();}if (dispatchNestedScroll(consumedX, consumedY, unconsumedX,unconsumedY, mScrollOffset)) {// Update the last touch co-ords, taking any scroll offset into// accountmLastTouchX -= mScrollOffset[0];mLastTouchY -= mScrollOffset[1];if (ev != null) {ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);}mNestedOffsets[0] += mScrollOffset[0];mNestedOffsets[1] += mScrollOffset[1];} else if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {if (ev != null) {pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);}considerReleasingGlowsOnScroll(x, y);}if (consumedX != 0 || consumedY != 0) {dispatchOnScrolled(consumedX, consumedY);}if (!awakenScrollBars()) {invalidate();}return consumedX != 0 || consumedY != 0;}

这个方法最终调用了如果是水平滑动的话调用mLayout.scrollHorizontallyBy(x, mRecycler, mState),如果是垂直滑动的话调用了mLayout.scrollVerticallyBy,所以最终
滑动多少距离由LayoutManager实现,那么你想实现左右滑动就重写scrollHorizontallyBy方法,如果实现垂直滑动就重写scrollVerticallyBy方法实现看下LinearLayoutManager
怎么实现的
@Overridepublic int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state) {if (mOrientation == HORIZONTAL) {return 0;}return scrollBy(dy, recycler, state);}


int scrollBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state) {if (getChildCount() == 0 || dy == 0) {return 0;}mLayoutState.mRecycle = true;ensureLayoutState();final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END: LayoutState.LAYOUT_START;final int absDy = Math.abs(dy);updateLayoutState(layoutDirection, absDy, true, state);final int freeScroll = mLayoutState.mScrollingOffset;final int consumed = freeScroll+ fill(recycler, mLayoutState, state, false);if (consumed < 0) {if (DEBUG) {Log.d(TAG, "Don't have any more elements to scroll");}return 0;}final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;    mOrientationHelper.offsetChildren(-scrolled);if (DEBUG) {Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);}mLayoutState.mLastScrollDelta = scrolled;return scrolled;}

这两个方法主要做了三件事,通过updateLayoutState修正一些状态,比如锚点在哪,是否有itemView的动画等,通过fill先检查哪些子view超出边界进行回收,然后重新填充
新的子view,通过offsetChildren改变所有子view的top or bottom的值从而达到移动的效果。


阅读全文
0 0
原创粉丝点击