上两篇已经谈到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方法
@Override
protected 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方法了
@Override
protected 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为true
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
mState.mItemCount = mAdapter.getItemCount();
//省略若干行….
// Step 2: Run layout
mState.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) {
//移除所有的子View
removeAndRecycleAllViews(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进scrapView
detachAndScrapAttachedViews(recycler);
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
//填充子View
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(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 start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutStateToFillEnd(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 start
updateLayoutStateToFillStart(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
// end
updateLayoutStateToFillEnd(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 + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//回收超过边界的View
recycleByLayoutState(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 recycling
remainingSpace -= 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 + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//回收超过边界的View
recycleByLayoutState(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 recycling
remainingSpace -= 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 changed
if (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 there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle this scrap
if (!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 exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(
mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.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:在滑动的时候缓存view
mViewCacheExtension:用户扩展缓存
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
// account
mLastTouchX -= 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
怎么实现的
@Override
public 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的值从而达到移动的效果。