最近一直在研究安卓中几个常用控件的源码,希望能通过学习源码学习到google大牛在封装一些复杂view的思想,为以后自己造轮子提供更好的思路.
RecyclerView
是一个用户可以全面定制的组件,本文将全面分析RecyclerView的各种机制,包括viewholder
复用机制,LayoutManager
布局机制,ItemAnimator
item动画等RecyclerView
暴露给使用者的所有可以自定义的部分在源码中的体现.RecylerView
完全区别于ListView
,尤其在Item的复用方面,RecyclerView
不在让用户关注于Item的复用,让用户可以更专注去处理UI上的逻辑.
关于ListView大家可以看我上一篇博客去了解一下他的Item回收机制.
注 : 其中会穿插着对LayoutManager
,ItemAnimator
等用户自定义组件的分析.
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
//调用LayoutManager中的方法测量view
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
mState.mInPreLayout = false; // clear
}
可以看到ReyelcerView
的onMeasure
这里有个判断 如果mLayout
不为空的时候,会调用mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
进行测量,这个mLayout
其实就是LayoutManager
,负责了RecyelcerView的测量.一般来说如果我们想自定义ReyclerView
的onMeasure
方法,只要在setLayoutManager方法中放入自己的自定义LayoutManger就可以了,系统为我们实现了LinearLayoutManager
用来摆放,而这个类里面并没有重写LayoutManager的onMeasure
方法,所以我们直接查看LayoutManaer
默认的测量方法看看.
下面我们来通过分析LayoutManager
看一下它是怎么进行对onMeasure的处理
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
它这里又调用了recyclerView的defaultOnMeasure(widthSpec, heightSpec);
默认的measure方法
private void defaultOnMeasure(int widthSpec, int heightSpec) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final int widthSize = MeasureSpec.getSize(widthSpec);
final int heightSize = MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
width = ViewCompat.getMinimumWidth(this);
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
height = ViewCompat.getMinimumHeight(this);
break;
}
setMeasuredDimension(width, height);
}
很熟悉的代码,主要就是对RecyclerView
根据测量模式进行测量,最后通过setMeasuredDimension(width, height);
给成员变量measuredWidth
和measuredHeight
赋值.可是这时候就会有疑惑,这里并没有看到对子view的测量,ListView
在这里就会对子view进行测量了,为什么RecyclerView没有,难道是我们分析错了吗?我们接着往下看…..
我们看下ReyclerView
的onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
eatRequestLayout();
//分发Layout事件
dispatchLayout();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
这里有个dispatchLayout方法,根据方法名我们可以猜到应该是给子控件分发layout事件的方法,我们点进去看看
void dispatchLayout() {
//第一次onLayout的时候mAdapter 和 mLayout肯定为空,所以不会有下面的逻辑,只有当我们调用setAdapter,或者其他第二次
//重绘的方法,才会继续下面的逻辑
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;
}
//这里通过我们传入的Adapter的getItemCount方法拿到了Item的个数
mState.mItemCount = mAdapter.getItemCount();
....进行些布局前的初始化操作...
// Step 2: Run layout
//开始layout
mState.mInPreLayout = false;
//具体怎么布局,会调用LayoutManager里面的方法进行布局
mLayout.onLayoutChildren(mRecycler, mState);
....后面是对ItemAnimator动画的执行,我们后面再讲解...
}
可以看出dispatchLayout()
最后还是会调用mLayout.onLayoutChildren(mRecycler, mState);
,我们上面说过mLayout
就是我们传递入的LayoutManager
,调用LayoutManager
的onLayoutChildren
进行布局,我们去看看LinearLayoutManager
的onLayoutChildren是如何进行布局的.
关于布局锚点: onLayoutChildren中会先确认布局锚点mAnchor,然后从布局锚点为开始位置,以此为起点向开始和结束方向填充ItemView.
mAnchor包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm: 布局算法
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
//通过检查孩子和其他变量,找到锚坐标和锚点项目位置 mAnchor为布局锚点
//mAnchor包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向(mLayoutFromEnd)
// 2) fill towards start, stacking from bottom , 开始填充, 从底部堆叠 从开始位置开始,向结束为位置堆叠填充itemView
// 3) fill towards end, stacking from top 结束填充,从顶部堆叠
// 4) scroll to fulfill requirements like stack from bottom. //滚动以满足堆栈从底部的要求
// create layout state
...
//这个方法会根据LinearLayoutManger构造中传入的布局方向给mShouldReverseLayout赋值
//如果是竖直方向(VERTICAL),mShouldReverseLayout为false
resolveShouldLayoutReverse();
//重置mAnchorInfo
mAnchorInfo.reset();
//得到堆叠方向 mShouldReverseLayout为false mStackFromEnd默认为false
//我们假定传入的方向是垂直方向,所以mAnchorInfo.mLayoutFromEnd = false
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
...
//重要的堆叠Item的方法,根据堆叠方向进行堆叠
//如果是end方向 从底部开始堆叠
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start // 开始填充
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
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;
} else {
//如果排列方向是VERTICAL,走这里
// fill towards end //结束填充
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
//重要的填充方法
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
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;
}
上面的代码,会计算mAnchorInfo.mLayoutFromEnd
的值,这个值是通过mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
进行计算的, mShouldReverseLayout
的值在resolveShouldLayoutReverse();
中获取,其中会根据布局朝向去给mShouldReverseLayout
赋值,如果布局朝向是VERTICAL,就为false,反之true.mStackFromEnd
是通过public void setStackFromEnd(boolean stackFromEnd)
方法进行赋值,这个方法需要调用者调用,我们一般不调用,所以为初始值false.所以根据或运算,如果是竖直方向mAnchorInfo.mLayoutFromEnd为false.
得到了布局方向,就会调用相应的逻辑进行布局,最后填充的方法为fill
.
//具体的填充方法
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//填充核心方法,从复用集合中取ViewHolder
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
//向复用集合中存ViewHolder
recycleByLayoutState(recycler, layoutState);
}
}
fill
方法中核心的填充方法layoutChunk
,他会先从缓存中取ViewHolder,如果没有,就回去创建,之后会将创建好的ViewHolder放入复用集合中.我们先看layoutChunk
如何填充的
这个方法就是核心的布局方法,layoutState.next(recycler);
是从缓存机制从取Item的具体方法,这个我们下面会说到.
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//获取ItemView,会先从Scrap中取,如果没有会从一级缓存,二级缓存取,最后检查ReyclerViewPool 如果都没有 就创建ViewHolder
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;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
//如果mScrapList为空,就将view填充进去,这个mScrapList就是Item被移除屏幕被缓存起来的集合,如果没有在mScrapList中
//说明需要添加到RecyerView显示界面中
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//addView 就是 viewGroup的addView方法 将子view填充到RecylerView中
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//测量子控件的大小
measureChildWithMargins(view, 0, 0);
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();
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.
//布局子view,这个方法里面调用了child.layout方法,参数就是计算出来的child位置.
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();
}
这个方法会先从RecyelrVIew缓存中View,然后判断layoutState.mScrapList
是否为null,如果为空,就表示这个view不在移除屏幕的位置,就要进行填充,调用addView
方法,将view填充进来,这个方法内部就式调用viewGroup
的addView方法.
之后调用measureChildWithMargins(view, 0, 0);
对view进行测量,这就是为什么在RecyerlView的构造方法中没有看到对子view的测量,原来在这里测量.
之后调用layoutDecorated
对view进行layout布局, 这个方法里面就是调用了child.layout
方法对控件进行布局.
到了这里,recycleView的填充就此结束了,所有应该在recycleView可见区域的view就被填充到屏幕上了.
我们一般通过addItemDecoration
对分割线进行绘制,谷歌为我们实现的DividerItemDecoration
,其实内部就算调用了系统ListView的分割线样式进行绘制,在ItemDecoration
的onDraw方法中绘制分割线,我们就来研究下这个ItemDecoration
在源码中的体现.
我们都知道一个view的绘制是通过draw方法开始的,所以我们从draw方法查找他的痕迹.
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
//ItemDecoration 的数量
for (int i = 0; i < count; i++) {
//调用ItemDecoration 的onDrawOver 方法绘制ReyclervView的背景
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
...
}
darw方法中调用ItemDecoration
的onDrawOver
,可以看到这个方法是在super.draw(c);
执行完毕后调用的,根据View的绘制逻辑,在draw
方法调用过后,表示系统的绘制流程已经结束了,也就是说这个onDrawOver
是在view的绘制流程全部结束以后调用的方法.
如果看过View的绘制流程,我们知道在super.draw(c)
方法中,会调用onDraw方法进行绘制,这个一般才是绘制内容的方法,我们找一下有没有重写这个方法.
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
//绘制内容
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
看到了吗,这里才是真正调用ItemDecoration
的onDraw
方法的地方,在这个地方,我们就可以调用ItemDecoration
的onDraw方法绘制分割线了.
但是还有个方法需要我们注意,如果我们想要自定义Item的间距怎么办,我们知道ItemDecoration
中有个getItemOffSets
方法可以自定义间距,那么这个方法是怎么发生作用的呢?
根据View的绘制流程,我们猜想,如果要给view添加边距,那么一定会在测量view的时候对padding,margin进行赋值,我们回到刚才的核心填充方法layoutChunk
中对Item测量的方法measureChildWithMargins(view, 0, 0);
.
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//用来修改Item的边距 ,也就是说在child.measure之前会先设置好边距
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
....
child.measure(widthSpec, heightSpec);
}
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//这里可以通过ItemDecoration 修改边距
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
这样是不是感觉豁然开朗了,原来是这里会调用getItemOffsets
拿到间距,这样就实现了用户自定义布局边距.
如果看过我上一篇ListView
解析,一定会记得ListView
就是在滑动过程中完成的对Item的回收,这里我们将通过对RecyclerView的滑动讲解,进一步的分析ReyclerView中重要的ViewHolder
机制.
在研究源码之前,我们先了解下RecyclerView的滑动状态 : RecyclerView的滑动过程可以分为2个阶段,手指在屏幕上移动,使RecyclerView滑动的过程,可以称为scroll;手指离开屏幕,RecyclerView继续滑动一段距离的过程,可以称为fling。
先看scroll过程,手指没有离开界面,还在滑动过程中
既然还没有离开界面,那一定在ACITON_MOVE中
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id " +
mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
if (mScrollState != SCROLL_STATE_DRAGGING) {
//计算出手指移动距离
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
//mTouchSlop 滑动阀值
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
startScroll = true;
}
if (startScroll) {
//如果滑动距离大于 阀值得话, scrollState 就为SCROLL_STATE_DRAGGING
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
//滑动 第一阶段scroll完成
if (scrollByInternal(
canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
mLastTouchX = x;
mLastTouchY = y;
} break;
这个方法中会进行一些对滑动的判断,只要滑动有效,就会调用scrollByInternal
方法.
//滑动
boolean scrollByInternal(int x, int y) {
int overscrollX = 0, overscrollY = 0;
int hresult = 0, vresult = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
mRunningLayoutOrScroll = true;
if (x != 0) {
//调用LayoutManager的scrollHorzontallBy方法
hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
overscrollX = x - hresult;
}
if (y != 0) {
//调用LayoutManager的scrollVerticallyBy方法
vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
overscrollY = y - vresult;
}
...
}
....
return hresult != 0 || vresult != 0;
}
可以看到这里通过对滑动方向的判断调用相对应的滑动方法,如果是我们是垂直滑动的话,会调用mLayout.scrollVerticallyBy(y, mRecycler, mState);
方法,也就说具体的滑动逻辑也是由LayoutManager
处理的.
我们来看看LinearLayoutManager
的scrollVerticallyBy
方法
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
调用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;
//如上文所讲到的fill()方法,作用就是向可绘制区间填充ItemView
//,那么在这里,可绘制区间就是滑动偏移量!再看方法mOrientationHelper.offsetChildren()作用就是平移ItemView。
//平移ItemView。 这里就完成了移动
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
}
return scrolled;
}
看到了吗,这个方法中,会调用fill方法对向前界面的Item进行填充,最后再对Item进行平移,这里我们回到fill方法
,我们回一下其中的核心填充方法layoutChunk
,其中有这么一行View view = layoutState.next(recycler);
,这个方法会实现从RecyclerView的复用机制中获取View,我们这里研究下这个方法是怎么实现的,这个方法内部会调用getViewForPosition
方法进行具体的获取操作.
这里有这么几个需要注意的复用对象 :
<1>scrapped : 从RecyclerView中删除的view
<2>cached : 是ItemView的一级缓存,cached集合的大小默认为2
<3>exCached : 是ItemView的二级缓存,exCached是需要我们通过RecyclerView.ViewCacheExtension自己实现的,默认没有
<4>recycled : 集合其实是一个Map,定义在RecyclerView.RecycledViewPool中将ItemView以ItemType分类保存了下来,这里算是RecyclerView设计上的亮点,通过RecyclerView.RecycledViewPool可以实现在不同的RecyclerView之间共享ItemView,只要为这些不同RecyclerView设置同一个RecyclerView.RecycledViewPool就可以了。
//获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为:
//step1 检查mChangedScrap,若匹配到则返回相应holder
//step2 检查AttachedScrap,若匹配到且holder有效则返回相应holder
//step3 查mViewCacheExtension,若匹配到则返回相应holder
//step4 检查mRecyclerPool,若匹配到则返回相应holder
//step5 否则执行Adapter.createViewHolder(),新建holder实例
//step6 返回holder.itemView
//step7 注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
//根据列表位置获取ItemView,先后从scrapped、cached、exCached、recycled
//集合中查找相应的ItemView,如果没有找到,就创建(Adapter.createViewHolder()),最后与数据集绑定。
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;
//先后从scrapped、cached、exCached、recycled集合中查找相应的ItemView
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
//检查mChangedScrap,若匹配到则返回相应holder
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
//检查AttachedScrap,若匹配到且holder有效则返回相应holder
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()) {
//一级缓存,先检查一级缓存, 如果有就返回viewHolder
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.
//二级缓存, 是开发者自定义的缓存, 从二级缓存中拿到ItemView
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");
}
//检查mRecyclerPool,若匹配到则返回相应holder
holder = getRecycledViewPool()
.getRecycledView(mAdapter.getItemViewType(offsetPosition));
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
//否则执行Adapter.createViewHolder(),新建holder实例
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
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);
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;
//返回holder.itemView
return holder.itemView;
}
上面注释的已经十分清楚,就是从RecyclerView的几个缓存中去获取,一级一级向下取,最后如果没有,这时候会调用Adapter
的createViewHolder
来创建ViewHolder,这个就是自己创建的ViewHolder,最后这个方法返回ViewHolder中存放的ItemView
就拿到了每个Item的View对象.
如果有向缓存中取,那么一定有存,我们来看看这个复用机制是怎么存的,在fill
中,有这么一个方法在layoutChunk
执行之后,recycleByLayoutState
,这个方法向里面一直调用,最终会有个recycleView
方法,看名字真有那么点意思,里面最后调用了recycleViewHolderInternal
方法,这个就是RecyclerView最终调用的添加方法,我们一起来分析看看.
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
boolean cached = false;
if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) &&
!holder.isChanged()) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
//首先判断cachedView 是否满了 最大mViewCacheMax = 2
////如果已満就从cached集合中移出一个到recycled集合中去,再把新的ItemView添加到cached集合
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
//如果满了 就从cachedViews中移除一个,
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
//再把新的ItemView添加到cached集合
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
//如果没有被缓存 缓存到recycleViewPool中
addViewHolderToRecycledViewPool(holder);
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are stil removing it from animation lists");
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
mState.onViewRecycled(holder);
}
这个注释分析的很清楚,就是对ViewHolder的一个存储过程.到这里,就将RecyclerView
的复用机制分析完成.
RecyclerView定义了4种针对数据集的操作,分别是ADD、REMOVE、UPDATE、MOVE,封装在了AdapterHelper.UpdateOp类中,并且所有操作由一个大小为30的对象池管理着。当我们要对数据集作任何操作时,都会从这个对象池中取出一个UpdateOp对象,放入一个等待队列中,最后调用
RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根据这个等待队列中的信息,对所有子控件重新测量、布局并绘制且执行动画。以上就是我们调用Adapter.notifyItemXXX()系列方法后发生的事。
我们以remove操作为例 :
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
调用被观察者的notifyItemRangeRemoved方法,我们到RecyclerView的被观察者AdapterDataObservable
中看看.
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
// since onItemRangeRemoved() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
这里调用了观察者RecyclerViewDataObserver
的onItemRangeRemoved
的方法
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
//第一步 将 removed信息放到集合中,对象为UpdateOp
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
//界面重新布局,也就式调用 RecyclerView的onLayout方法
requestLayout();
}
}
这里我们又回到了onLayout方法中,还记得我们之前省略过一部分吗,就是在那里实现的
void dispatchLayout() {
... 上面为填充方法,已经分析过....
if (mState.mRunSimpleAnimations) {
//removed动画
int preLayoutCount = mState.mPreLayoutHolderMap.size();
for (int i = preLayoutCount - 1; i >= 0; i--) {
ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i);
if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
mState.mPreLayoutHolderMap.removeAt(i);
View disappearingItemView = disappearingItem.holder.itemView;
mRecycler.unscrapView(disappearingItem.holder);
//执行动画 remove动画
animateDisappearance(disappearingItem);
}
}
...其他动画操作...
}
}
这里调用了animateDisappearance
执行removed动画
private void animateDisappearance(ItemHolderInfo disappearingItem) {
View disappearingItemView = disappearingItem.holder.itemView;
addAnimatingView(disappearingItem.holder);
int oldLeft = disappearingItem.left;
int oldTop = disappearingItem.top;
int newLeft = disappearingItemView.getLeft();
int newTop = disappearingItemView.getTop();
if (oldLeft != newLeft || oldTop != newTop) {
disappearingItem.holder.setIsRecyclable(false);
disappearingItemView.layout(newLeft, newTop,
newLeft + disappearingItemView.getWidth(),
newTop + disappearingItemView.getHeight());
if (DEBUG) {
Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder +
" with view " + disappearingItemView);
}
if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop,
newLeft, newTop)) {
postAnimationRunner();
}
} else {
if (DEBUG) {
Log.d(TAG, "REMOVED: " + disappearingItem.holder +
" with view " + disappearingItemView);
}
disappearingItem.holder.setIsRecyclable(false);
//remove动画,这就进行了动画执行,这里的动画就是用户自定义的实现方式
if (mItemAnimator.animateRemove(disappearingItem.holder)) {
postAnimationRunner();
}
}
}
这里最后调用了mItemAnimator.animateRemove(disappearingItem.holder)
方法,如果自定义过动画的朋友一定会知道,我们通过集成ItemAnimator
,重写它的animateXXX(ViewHolder holder) 中拿到holder.itemView 就能通过这个View进行动画操作了.
到这里,所有关于RecyclerView的源码机械就到这里结束了,我们可以看出,RecyclerView相对于ListView有更好的模块化,更加低耦合,让用户可以通过它提供的各种接口游刃有余的自定义RecyclerView的各种样式,并且考虑到使用者根本没有必要对View的复用有所关注所以加入了ViewHolder机制,这种思想真是值得我们深入学习.
后面我还会对ViewPager,Behavor 进行源码解析,希望通过这种方式能够对自定义View有更深入的理解.