读RecyclerView源码

RecyclerView的measure和layout

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。


Recycler缓存

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);
        ...
    }
    ...
}


根据列表位置获取ItemView,先后从scrapped、cached、exCached、recycled集合中查找相应的ItemView,如果没有找到,就创建(Adapter.createViewHolder()),最后与数据集绑定。


在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();
    }
}


这里的mAdapterHelper.onItemRangeRemoved()就是向之前提及的等待队列添加一个类型为REMOVE的UpdateOp对象, triggerUpdateProcessor()方法就是调用View.requestLayout()方法,这会导致界面重新布局,也就是说方法RecyclerView.onLayout()会随后调用,这之后的流程就和在绘制流程一节中所描述的一致了。


而动画在哪执行呢?之前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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值