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

来源:互联网 发布:手机使用统计软件 编辑:程序博客网 时间:2024/04/29 18:39

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

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

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

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

  • onMeasure
  • onLayout
  • item测量
  • item布局
  • ItemDecoraction
  • RecyclerView的滑动以及ViewHolder复用机制
  • 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有更深入的理解.

4 0
原创粉丝点击