RecyclerView源码解析

关于在项目中需不需要将ListView替换成RecyclerView,这个问题前段时间碰到过,今天就来分析下RecyclerView的实现机制。

1.ListView的实现机制

这块我就不多说,网上关于这块的剖析还是很多的,还是直接贴出郭霖大神的那篇博文吧Android ListView工作原理完全解析,带你从源码的角度彻底理解,如果对于ListView的工作原理感兴趣的,可以搭乘时光机去瞅瞅,这里就不多介绍。

2.RecyclerView工作原理

关于RecyclerView,官方的说法它是ListView的增强版本,它的新特性如ViewHolder、ItemDecorator、LayoutManager、SmothScroller以及增加或删除item时item动画等,而且可以对于listView和GridView进行替代。当在处理复杂布局的情况下,利用RecyclerView会比ListView更加的方便跟灵活。 接下来,我们来分析RecycleView大体的实现机制。在Android中,一个View的绘制大体会经过Measure、Layout、Draw这几个方法,RecyclerView当然也会经历这几个过程。我们首先看看RecyclerView的onMeasure()方法:

 @Override
    protected void onMeasure(int widthSpec, int heightSpec) {

        ...
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
            // consistency
            mLayout.setMeasureSpecs(widthSpec, heightSpec);
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();

            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(...);
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
           ...
    }

我们截取onMeasure()方法中重要的代码进行分析,不难发现,RecyclerView的绘制过程其实是交给了mLayout这个对象,这个对象其实就是我们LayoutManager的实例对象。我们看看主要代码

 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

上面onMeasure()方法其实就是做了初始化RecyclerView的属性操作;接着我们看看dispatchLayoutStep1():

private void dispatchLayoutStep1() {
    ...
    if (mState.mRunSimpleAnimations) {
        // Step 0: Find out where all non-removed items are, pre-layout
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
            final ViewHolder holder = ...
            ...
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(...);
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            ...
        }
    }
    ...
}

截取了部分代码内容,这个方法做的操作其实就是Pre-Layout,我们关注三个地方,第一,ItemHolderInfo:这个对象保存了RecycleView中的一些边界信息。

    public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,
                    @AdapterChanges int flags) {
                final View view = holder.itemView;
                this.left = view.getLeft();
                this.top = view.getTop();
                this.right = view.getRight();
                this.bottom = view.getBottom();
                return this;
            }

第二,mViewInfoStore,这个对象保存的是RecyclerView中item的动画信息。

 /**
     * Keeps data about views to be used for animations
     */
    final ViewInfoStore mViewInfoStore = new ViewInfoStore();
 第三,addToPreLayout(),这个方法主要就是将pre-layout阶段ItemView的信息保存在mViewInfoStore中的mLayoutHolderMap中,做的是一些保存信息操作。
 /**
     * Adds the item information to the prelayout tracking
     * @param holder The ViewHolder whose information is being saved
     * @param info The information to save
     */
    void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
        InfoRecord record = mLayoutHolderMap.get(holder);
        if (record == null) {
            record = InfoRecord.obtain();
            mLayoutHolderMap.put(holder, record);
        }
        record.preInfo = info;
        record.flags |= FLAG_PRE;
    }
   接着,回到onMeasure()方法,继续跟进dispatchLayoutStep2()。首先还是看看源码:
/**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
        ...

        // Step 2: Run layout
       ...
        mLayout.onLayoutChildren(mRecycler, mState);

       ...
    }

从源码的注释我们能够看出,这一步的操作就是布局子控件。 mLayout.onLayoutChildren(mRecycler, mState); 进入该方法我们发现,这个方法是个空方法,必须要被重写。也就是说,在实现RecyclerView.LayoutManager这个抽象类的时候,需要重写onLayoutChildren()这个方法,由于在RecyclerView中,提供了3个RecyclerView.LayoutManager的实现类,我们就以LinearLayoutManager类中的onLayoutChildren()方法来进行说明(similar functionality to {@link android.widget.ListView})。

 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.
        // create layout state
        ...
        ensureLayoutState();
      ...
        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
                mPendingSavedState != null) {
            mAnchorInfo.reset();
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // calculate anchor position and coordinate
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        }
     ...
        if (mAnchorInfo.mLayoutFromEnd) {
            ...
        } else {
           ...
        }

        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
        detachAndScrapAttachedViews(recycler);
       ...
        if (mAnchorInfo.mLayoutFromEnd) {
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
           ...
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            endOffset = 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;
            }
        }

      ...
    }

我们可以从注释中了解到,onLayoutChidren()这个方法主要是通过获取一个anchor (锚点)的信息来以此为起始方向或者结束方向来填充itemView。在上述方法中,真正执行填充的方法不难发现是fill()这个方法。我们跟进fill()这个方法:

/**
The magic functions :). Fills the given layout, defined by the layoutState. This is fairly independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager} and with little change, can be made publicly available as a helper class.
  */
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
       ...
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
          ...
            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) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
         ...
        return start - layoutState.mAvailable;
    }

在上述方法中,有一个while()循环,其中有一个layoutChunk()内部方法,我们继续看一下:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
       ...
        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);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        ...
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.    
        layoutDecoratedWithMargins(view, left, top, right, bottom);
       ...
    }

在这个方法中,所做的事情就是添加子view(addView())与测量子view的大小(measureChildWithMargins())。我们看看最后调用的一个方法,layoutDecoratedWithMargins():

/**
 Lay out the given child view within the RecyclerView using coordinates that include any current {@link ItemDecoration ItemDecorations} and margins.
  */
 public void layoutDecoratedWithMargins(View child, int left, int top, int right,int bottom) {
           ...
            child.layout(...);
        }

这个方法就是布局子view的方法。接下来我们梳理下RecyclerView填充ItemView的算法:1.向父容器中添加子控件;2.测量子控件的大小;3.将测量后的子控件进行布局,将锚点向布局的方向进行平移,直到RecyclerView的子控件全部填充或者其绘制空间已绘制完毕。RecyclerView的绘制空间就是RecyclerView的父容器规定RecyclerView所占的空间大小。
接下来就是来看看RecyclerView的draw()方法部分,同所有的view的绘制一样,RecyclerView在这个方法将子控件一一的绘制出来。

 @Override
    public void draw(Canvas c) {
        super.draw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
       ...
    }

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

我们发现,draw()方法十分的简单,就是确定子View距离边界的offset,然后将其画出来。

RecyclerView的滑动机制

了解RecyclerView的滑动机制首先我们来看看其onTouchEvent()这个方法:

 @Override
    public boolean onTouchEvent(MotionEvent e) {
       ...

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        boolean eventAddedToVelocityTracker = false;

        final MotionEvent vtev = MotionEvent.obtain(e);
        final int action = MotionEventCompat.getActionMasked(e);
        final int actionIndex = MotionEventCompat.getActionIndex(e);

      ...

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                startNestedScroll(nestedScrollAxis);
            } break;

            case MotionEventCompat.ACTION_POINTER_DOWN: {
                ...

            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                ...

                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) {
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        if (dx > 0) {
                            dx -= mTouchSlop;
                        } else {
                            dx += mTouchSlop;
                        }
                        startScroll = true;
                    }
                    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: {
                mVelocityTracker.addMovement(vtev);
                eventAddedToVelocityTracker = true;
                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
                final float xvel = canScrollHorizontally ?
                        -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
                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;

           ...
        }

       ...
    }

RecyclerView的滑动机制主要的思想为:首先接触到ACTION_MOVE事件之后,会计算出手指在屏幕上的移动距离(dx、dy),然后根据该移动的距离与mTouchSlop(阈值)进行对比,从而设置RecyclerView的滑动状态(setScrollState(SCROLL_STATE_DRAGGING);),最终,通过调用scrollByInternal()这个方法实现滑动。这个时候的滑动并没有结束,继续接受ACTION_UP事件之后,RecyclerView会通过VelocityTracker来计算其滑动的一个初始速度(xvel、yvel)当拿到这个速度值之后,调用fling()方法实现后续的滑动,最后初始化RecyclerView的touch事件。总结下说,就是RecyclerView的滑动主要涉及到了两个核心的方法,scrollByInternal()与fling()。

1.scrollByInternal()

看看源码:

boolean scrollByInternal(int x, int y, MotionEvent ev) {
       ...
        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 (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);
        }
        ...
    }

从上述方法中,我们可以看到该方法兜兜转转,最后还是会调用到LinearLayoutManager中的ScrollBy()方法。这里就不在赘述。

2.fling()

老样子,还是先看源码:

public boolean fling(int velocityX, int velocityY) {
       ...
         if (canScroll) {
         velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
         velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
         mViewFlinger.fling(velocityX, velocityY);
             return true;
            }
        }
        return false;
    }

fling()方法中核心的代码就是mViewFlinger.fling(velocityX, velocityY);这句。mViewFlinger是一个runnable对象,而RecyclerView中的fling()还是通过Scroller来实现的。继续深入:

public ViewFlinger() {
            mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator);
        }

//mViewFlinger.fling(...)
 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();
        }

继续看看mViewFlinger中的run()方法,看看里面到底做了什么事情:

 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) {
                    eatRequestLayout();
                    onEnterLayoutOrScroll();
                    TraceCompat.beginSection(TRACE_SCROLL_TAG);
                    if (dx != 0) {
                        hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
                        overscrollX = dx - hresult;
                    }
                    if (dy != 0) {
                        vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
                        overscrollY = dy - vresult;
                    }
                    TraceCompat.endSection();
                    repositionShadowingViews();

                    onExitLayoutOrScroll();
                    resumeRequestLayout(false);

                   ...

                if (!awakenScrollBars()) {
                    invalidate();
                }

               ...

                if (scroller.isFinished() || !fullyConsumedAny) {
                    setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
                } else {
                    postOnAnimation();
                }
            }
           ...
        }

这段代码中需要注意一个方法mLayout.scrollHorizontallyBy(dx, mRecycler, mState);这个方法最终会走到LinearLayoutManager中的ScrollBy()方法。上段代码的思想就是根据Scroller事件偏移量来计算itemView的平移距离或者是在此创建itemView。
最后我们来看看LinearLayoutManager中的ScrollBy()方法:

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
      ...
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        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);
        ....
    }

在这个方法中,我们可以看到RecyclerView填充itemView的绘制区域就是滑动事情的偏移量,而 mOrientationHelper.offsetChildren(-scrolled);这个方法就是平移itemView的方法。所以RecyclerView的滑动最终都是通过ScrollBy()这个方法进行操作的。

3.RecyclerView的复用机制

首先,我们回顾一下在前面的onLayoutChildren()这个方法中,使用的 fill(recycler, mLayoutState, state, false)方法来填充itemView,跟进之后我们发现,其实最终是调用RecyclerView.Recycler.getViewForPosition()方法。

 View getViewForPosition(int position, boolean dryRun) {
           ...
            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) {
               ...
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                   ...
                }
                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) { // fallback to recycler
                    // try recycler.
                    // Head to the shared pool.
                  ...
     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);
               ...
            }
           ...
        }

getViewForPosition()这个方法比较长,但是不难理解,如果您对listView的复用机制有过了解,那么这里理解起来就相对的容易一些。梳理下这个方法的思路:这个方法是根据列表位置获取ItemView,先后从scrapped、cached、exCached、recycled集合中查找相对应的ItemView,最后没有找到的话,就新建一个新的holder( holder = mAdapter.createViewHolder(RecyclerView.this, type))最后与数据集进行绑定显示。scrapped、cached、exCached集合是定义在RecyclerView.Recycler中,scrapped表示废弃的ItemView,这里可以理解为删除掉的ItemView,cached与exCached分别表示一级和二级缓存的ItemView。recycled集合就是一个map,它是定义在RecyclerView.RecyclerViewPool中,它的作用就是将ItemView以ItemType分类保存起来。
好了,讲述了RecyclerView复用的ItmeView是从哪里来的,那么什么时候它们被添加进了复用的集合中呢?scrapped集合中的ItemView是当我们执行remove操作释放的ItemView,而caced集合中的ItemView呢?答案当然是在前面布局的时候,还记得在布局onLayoutChildren()方法中的核心方法fill()时,当我们循环的向RecyclerView的可绘制区域添加ItmeView的时候,其实最后执行了 recycleByLayoutState(recycler, layoutState)这个方法,这个方法我们最终可以追溯到RccyclerView.Recyler.recycleViewHolderInternal()方法:

 /**
   * internal implementation checks if view is scrapped or        attached and throws an exception
   */
        void recycleViewHolderInternal(ViewHolder holder) {

        ...
            if (forceRecycle || holder.isRecyclable()) {
    if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize --;
                    }
                    if (cachedViewSize < mViewCacheMax) {
                        mCachedViews.add(holder);
                        cached = true;
                    }
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder);
                    recycled = true;
                }
            } 
           ...
        }

从源码中我们可以发现,其实在RecyclerView的layout阶段就将ItmeView存入了cached集合,上述代码的逻辑思路为,判断cached集合是否存满,若存满了,则将ItmeView移除到recycled集合中,若没有存满,则添加ItemView到cached集合中。

RecyclerView动画机制

RecyclerView的动画其实也是跟数据集与ItemView绑定在一起的,在RecyclerView中定义了4种对数据集的操作,分别为Add、Remove、Update、Move。这4个数据集的操作封装在AdapterHelper.UpdateOp类中,而且所有的操作由一个大小为30的对象池进行管理,当我们执行任何一个操纵的时候,都会从对象池中取出一个UpdateOp对象,最后调用RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,这个方法会跟据等待队列中的信息,对所有的子控件进行重新测量、布局、重绘且执行动画。
我们举个栗子,当我们删除某个ItemView的时候,我们会调用Adapter.notifyItemRemove()这个方法。最终会追溯到RecyclerView.RecyclerViewDataObserver.onItemRangeRemove():

 @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
          ...
            if(mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

通过上面源码可以发现,在执行onItemRangeRemoved()这个方法时,会调用triggerUpdateProcessor()这个方法,我们再进入triggerUpdateProcessor()这个方法看看:

void triggerUpdateProcessor() {
            if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }

在这个方法里面会调用requestLayout()这个方法,那么接下来的操作就跟RecyclerView的绘制流程一致了,还有个问题,动画是在什么时候执行的?
回想下我们在布局的时候,关注过dispatchLayoutStep2这个方法,其实动画的执行是在dispatchLayoutStep3中执行的。看源码:

/**
     * The final step of the layout where we save the information about views for animations,
     * trigger animations and do any necessary cleanup.
     */
    private void dispatchLayoutStep3() {
       ...
        if (mState.mRunSimpleAnimations) {
            // Step 3: Find out where things are now, and process change animations.
            // traverse list in reverse because we may call animateChange in the loop which may
            // remove the target view holder.
            for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
               ...

            // Step 4: Process view info lists and trigger animations
            mViewInfoStore.process(mViewInfoProcessCallback);
        }

        ...
    }

我们关注下step4,这里注释上明确说明触发动画的地方。我们看看process()这个方法的源码:

void process(ProcessCallback callback) {
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) {
            final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
           ...
        }
    }

我们注意下传入的ProcessCallback 这个对象:

/**
  * The callback to convert view info diffs into animations.
  */
    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
        @Override
        public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                @Nullable ItemHolderInfo postInfo) {
            mRecycler.unscrapView(viewHolder);
            animateDisappearance(viewHolder, info, postInfo);
        }
        @Override
        public void processAppeared(ViewHolder viewHolder,
                ItemHolderInfo preInfo, ItemHolderInfo info) {
            animateAppearance(viewHolder, preInfo, info);
        }

      ...
    };

注意animateDisappearance(),这里就是我们执行动画的方法所在,我们再跟进查看下:

 private void animateDisappearance(@NonNull ViewHolder holder,
            @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
        **addAnimatingView(holder);**
        holder.setIsRecyclable(false);
        if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
            *postAnimationRunner();*
        }
    }

首先,说明下addAnimatingView()这个方法,这个方法最终会向mHiddenViews这个集合中添加ItemView,而所添加的ItemView,最终会以getViewForPosition()方法中的getScrapViewForPosition()来获取。接着我们说下animateDisappearance()这个方法,它其实是将动画与ItmeView进行绑定,并且添加到一个等待的执行的队列中,而当postAnimationRunner()这个方法被调用的时候,就会执行这个队列中的动画,这样就实现了RecyclerView的操作数据集的动画效果。

参考

http://blog.csdn.net/qq_23012315/article/details/50807224

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值