onMeasure->dispatchLayoutStep2
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
...
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
...
} else {
...
}
}
private void dispatchLayoutStep2() {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
}
mLayout.onLayoutChildren,mLayout即RecyclerView.LayoutManager,这里以LinearLayoutManager为例,mAnchorInfo为布局锚点信息,包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向(mLayoutFromEnd)——这里是指start、end方向。这部分代码的功能就是:确定布局锚点,以此为起点向开始和结束方向填充ItemView
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.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
...
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
...
if (mAnchorInfo.mLayoutFromEnd) {
...
} 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;
...
}
...
}
fill->layoutChunk
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
while (...&&layoutState.hasMore(state)) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
if (...) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
...
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
...
measureChildWithMargins(view, 0, 0);
...
// 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);
...
}
measureChildWithMargins
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = ...
final int heightSpec = ...
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
getItemDecorInsetsForChild
Rect getItemDecorInsetsForChild(View child) {
...
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);
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即是ItemDecoration中重写的方法,到此测量出每个子ItemView的位置偏移量
layoutChunk->layoutDecorated->child.layout
public void layoutDecorated(...) {
...
child.layout(...);
}
在RecyclerView的measure及layout阶段,填充ItemView的算法为:向父容器增加子控件,测量子控件大小,布局子控件,布局锚点向当前布局方向平移子控件大小,重复上诉步骤至RecyclerView可绘制空间消耗完毕或子控件已全部填充。这里的可绘制空间,可以简单地理解为父容器的大小
RecyclerView的onMeasure方法,执行mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec)这行代码的作用就是根据子控件的大小,设置RecyclerView的大小。
再看RecyclerView的onLayout方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
dispatchLayout();
...
}
void dispatchLayout() {
...
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
...
dispatchLayoutStep2();
}
dispatchLayoutStep3();
...
}
这里又调用了onMeasure中调用的dispatchLayoutStep2,结合onMeasure方法对skipMeasure的判断可以看出,如果要支持WRAP_CONTENT,那么子控件的measure及layout就会提前在RecyclerView的测量方法中执行完成,也就是说,先确定了子控件的大小及位置后,再由此设置RecyclerView的大小;如果是其它情况(测量模式为EXACTLY),子控件的measure及layout过程就会延迟至RecyclerView的layout过程(RecyclerView.onLayout())中执行。再看onMeasure方法中的mLayout.mAutoMeasure,它表示,RecyclerView的measure及layout过程是否要委托给RecyclerView.LayoutManager,在兼容包中提供的3种RecyclerView.LayoutManager的这个属性默认都是为true的。
onTouchEvent
public boolean onTouchEvent(MotionEvent e) {
...
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
...
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
...
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
...
if (mScrollState != SCROLL_STATE_DRAGGING) {
...
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
} break;
...
case MotionEvent.ACTION_UP: {
...
final float yvel = canScrollVertically ?
-VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
...
}
...
}
case MotionEvent.ACTION_MOVE->scrollByInternal最终是调用LinearLayoutManager.scrollBy
case MotionEvent.ACTION_UP->fling
public boolean fling(int velocityX, int velocityY) {
...
mViewFlinger.fling(velocityX, velocityY);
...
}
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
void postOnAnimation() {
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true;
} else {
removeCallbacks(this);
ViewCompat.postOnAnimation(RecyclerView.this, this);
}
}
postOnAnimation()方法的作用就是在将来的某个时刻会执行我们给定的一个Runnable对象,在这里就是这个mViewFlinger对象
ViewFlinger.run()方法,其中有个调用mLayout.scrollVerticallyBy(),跟踪进入你会发现它最终也会走到LinearLayoutManager.scrollBy
public void run() {
...
if (scroller.computeScrollOffset()) {
final int x = scroller.getCurrX();
final int y = scroller.getCurrY();
final int dx = x - mLastFlingX;
final int dy = y - mLastFlingY;
...
if (mAdapter != null) {
...
if (dy != 0) {
vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
overscrollY = dy - vresult;
}
...
}
...
if (!awakenScrollBars()) {
invalidate();//刷新界面
}
...
if (scroller.isFinished() || !fullyConsumedAny) {
setScrollState(SCROLL_STATE_IDLE);
} else {
postOnAnimation();
}
}
...
}
再看LinearLayoutManager的scrollBy
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
...
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
...
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
...
}
如上文所讲到的fill()方法,作用就是向可绘制区间填充ItemView,那么在这里,可绘制区间就是滑动偏移量!再看方法mOrientationHelper.offsetChildren()作用就是平移ItemView。
layoutChunk方法中,有行代码layoutState.next(recycler),它的作用自然就是获取ItemView,我们进入这个方法查看,最终它会调用到RecyclerView.Recycler.getViewForPosition()方法
View getViewForPosition(int position, boolean dryRun) {
...
// 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) {
...
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
...
}
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
if (holder == null) {
...
holder = getRecycledViewPool().getRecycledView(type);
...
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
...
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()) {
...
mAdapter.bindViewHolder(holder, offsetPosition);
...
}
...
}
在fill()方法的循环体中有行代码recycleByLayoutState(recycler, layoutState),最终这个方法会执行到RecyclerView.Recycler.recycleViewHolderInternal()方法
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
}
...
}
逻辑是如果cached满了,就把cached的第一个移到recycled集合中,然后把新的ItemView的holder添加到cached中,不满就直接添加。
exCached集合是我们自己创建的,所以添加删除元素也要我们自己实现。
RecyclerView定义了4种针对数据集的操作,分别是ADD、REMOVE、UPDATE、MOVE,封装在了AdapterHelper.UpdateOp类中,并且所有操作由一个大小为30的对象池管理着。当我们要对数据集作任何操作时,都会从这个对象池中取出一个UpdateOp对象,放入一个等待队列中,最后调用RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根据这个等待队列中的信息,对所有子控件重新测量、布局并绘制且执行动画。以上就是我们调用Adapter.notifyItemXXX()系列方法后发生的事。
以remove为例,调用Adapter.notifyItemRemove(),追溯到方法RecyclerView.RecyclerViewDataObserver.onItemRangeRemoved()
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
而动画在哪执行呢?之前onLayout()方法发现dispatchLayoutStepX方法共有3个,其中dispatchLayoutStep3
private void dispatchLayoutStep3() {
...
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
...
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
...
}
process()方法最终会执行到RecyclerView.animateDisappearance()方法
private void animateDisappearance(...) {
addAnimatingView(holder);
holder.setIsRecyclable(false);
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
animateDisappearance()会把一个动画与ItemView绑定,并添加到待执行队列中, postAnimationRunner()调用后就会执行这个队列中的动画
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
...
mChildHelper.addView(view, true);
...
}
这里最终会向ChildHelper中的一个名为mHiddenViews的集合添加给定的ItemView,
上面getViewForPosition()方法中有个getScrapViewForPosition(),作用是从scrapped集合中获取ItemView
ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) {
...
View view = mChildHelper.findHiddenNonRemovedView(position, type);
...
}
View findHiddenNonRemovedView(int position, int type) {
final int count = mHiddenViews.size();
for (int i = 0; i < count; i++) {
final View view = mHiddenViews.get(i);
RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
if (holder.getLayoutPosition() == position && !holder.isInvalid() && !holder.isRemoved()
&& (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) {
return view;
}
}
return null;
}
所以mHiddenViews就是Recycler缓存里的scrapped