RecyclerView源码解析

来源:互联网 发布:windows日志 u盘 编辑:程序博客网 时间:2024/05/17 04:36

关于在项目中需不需要将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

0 0
原创粉丝点击