安卓5.1源码解析 : RecyclerView解析 从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解

最近一直在研究安卓中几个常用控件的源码,希望能通过学习源码学习到google大牛在封装一些复杂view的思想,为以后自己造轮子提供更好的思路.

RecyclerView是一个用户可以全面定制的组件,本文将全面分析RecyclerView的各种机制,包括viewholder复用机制,LayoutManager布局机制,ItemAnimatoritem动画等RecyclerView暴露给使用者的所有可以自定义的部分在源码中的体现.RecylerView完全区别于ListView,尤其在Item的复用方面,RecyclerView不在让用户关注于Item的复用,让用户可以更专注去处理UI上的逻辑.

关于ListView大家可以看我上一篇博客去了解一下他的Item回收机制.

本文将从以下几个方面对RecyclerView进行讲解

注 : 其中会穿插着对LayoutManager,ItemAnimator等用户自定义组件的分析.

onMeasure

  • RecyclerView的onMeasure方法
    @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
    }

可以看到ReyelcerViewonMeasure这里有个判断 如果mLayout不为空的时候,会调用mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);进行测量,这个mLayout其实就是LayoutManager,负责了RecyelcerView的测量.一般来说如果我们想自定义ReyclerViewonMeasure方法,只要在setLayoutManager方法中放入自己的自定义LayoutManger就可以了,系统为我们实现了LinearLayoutManager用来摆放,而这个类里面并没有重写LayoutManager的onMeasure方法,所以我们直接查看LayoutManaer默认的测量方法看看.

下面我们来通过分析LayoutManager看一下它是怎么进行对onMeasure的处理

  • LinearLayoutManager的onMeasure
     public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
        mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
    }

它这里又调用了recyclerView的defaultOnMeasure(widthSpec, heightSpec);默认的measure方法

  • mRecyclerView.defaultOnMeasure(widthSpec, heightSpec)
    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);给成员变量measuredWidthmeasuredHeight赋值.可是这时候就会有疑惑,这里并没有看到对子view的测量,ListView在这里就会对子view进行测量了,为什么RecyclerView没有,难道是我们分析错了吗?我们接着往下看…..

onLayout

我们看下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事件的方法,我们点进去看看

  • dispatchLayout(); 进行布局的方法
    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,调用LayoutManageronLayoutChildren进行布局,我们去看看LinearLayoutManager的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.


Item测量,Item布局

  • 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如何填充的

  • 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就被填充到屏幕上了.

ItemDecoraction

我们一般通过addItemDecoration对分割线进行绘制,谷歌为我们实现的DividerItemDecoration,其实内部就算调用了系统ListView的分割线样式进行绘制,在ItemDecoration的onDraw方法中绘制分割线,我们就来研究下这个ItemDecoration在源码中的体现.

我们都知道一个view的绘制是通过draw方法开始的,所以我们从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方法中调用ItemDecorationonDrawOver,可以看到这个方法是在super.draw(c);执行完毕后调用的,根据View的绘制逻辑,在draw方法调用过后,表示系统的绘制流程已经结束了,也就是说这个onDrawOver是在view的绘制流程全部结束以后调用的方法.

如果看过View的绘制流程,我们知道在super.draw(c)方法中,会调用onDraw方法进行绘制,这个一般才是绘制内容的方法,我们找一下有没有重写这个方法.

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

看到了吗,这里才是真正调用ItemDecorationonDraw方法的地方,在这个地方,我们就可以调用ItemDecoration的onDraw方法绘制分割线了.

但是还有个方法需要我们注意,如果我们想要自定义Item的间距怎么办,我们知道ItemDecoration中有个getItemOffSets方法可以自定义间距,那么这个方法是怎么发生作用的呢?

根据View的绘制流程,我们猜想,如果要给view添加边距,那么一定会在测量view的时候对padding,margin进行赋值,我们回到刚才的核心填充方法layoutChunk中对Item测量的方法measureChildWithMargins(view, 0, 0);.

  • measureChildWithMargins
    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);
   }
  • getItemDecorInsetsForChild
    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拿到间距,这样就实现了用户自定义布局边距.

ReyclerView的滑动以及ViewHolder机制

如果看过我上一篇ListView解析,一定会记得ListView就是在滑动过程中完成的对Item的回收,这里我们将通过对RecyclerView的滑动讲解,进一步的分析ReyclerView中重要的ViewHolder机制.

在研究源码之前,我们先了解下RecyclerView的滑动状态 : RecyclerView的滑动过程可以分为2个阶段,手指在屏幕上移动,使RecyclerView滑动的过程,可以称为scroll;手指离开屏幕,RecyclerView继续滑动一段距离的过程,可以称为fling。

先看scroll过程,手指没有离开界面,还在滑动过程中

  • onTouchEvent

既然还没有离开界面,那一定在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方法.

  • 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处理的.

我们来看看LinearLayoutManagerscrollVerticallyBy方法

  • 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方法进行具体的获取操作.

  • 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的几个缓存中去获取,一级一级向下取,最后如果没有,这时候会调用AdaptercreateViewHolder来创建ViewHolder,这个就是自己创建的ViewHolder,最后这个方法返回ViewHolder中存放的ItemView就拿到了每个Item的View对象.

如果有向缓存中取,那么一定有存,我们来看看这个复用机制是怎么存的,在fill中,有这么一个方法在layoutChunk执行之后,recycleByLayoutState,这个方法向里面一直调用,最终会有个recycleView方法,看名字真有那么点意思,里面最后调用了recycleViewHolderInternal方法,这个就是RecyclerView最终调用的添加方法,我们一起来分析看看.

  • recycleViewHolderInternal
    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动画

RecyclerView定义了4种针对数据集的操作,分别是ADD、REMOVE、UPDATE、MOVE,封装在了AdapterHelper.UpdateOp类中,并且所有操作由一个大小为30的对象池管理着。当我们要对数据集作任何操作时,都会从这个对象池中取出一个UpdateOp对象,放入一个等待队列中,最后调用
RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根据这个等待队列中的信息,对所有子控件重新测量、布局并绘制且执行动画。以上就是我们调用Adapter.notifyItemXXX()系列方法后发生的事。

我们以remove操作为例 :

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

这里调用了观察者RecyclerViewDataObserveronItemRangeRemoved的方法

    @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方法中,还记得我们之前省略过一部分吗,就是在那里实现的

  • dispatchLayout
    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有更深入的理解.

猜你喜欢

转载自blog.csdn.net/hfyd_/article/details/53910631
今日推荐