首页
登录 | 注册

RecyclerView 初探——绘制流程

https://www.jianshu.com/p/c52b947fe064

总结:

  • RecyclerView它的绘制其实是交给了LayoutManager处理,如果没有设置,则不会测量子View。如果RecyclerView是固定的长宽,在OnMeasure中是不会测量子View的,而是会在onLayout中测量
  • 绘制其实会区分正向绘制和倒置绘制
  • 绘制的过程是先确定一个瞄点,然后分别向上,向下绘制,如果对与RecyclerView来说还有剩余空间,则会再执行一次fill方法
  • LayoutManager获得View是从RecyclerView中的Recycler.next()方法获得,涉及到RecyclerView的缓存策略,如果缓存没有拿到,则走我们自己重写的onCreateView方法
  • onLayout主要的作用就是重置一些参数,但当RecyclerView的长宽是精确值得时候,则还有这测量子View的功能
基本用法
  rv.layoutManager= layoutManager
  rv.adapter=myAdapter

这个跟ListView有点不同的是增加了个layoutManager,我们都知道,如果在RecyclerView中没有设置layoutManager,数据是显示不出来的,具体的实现就不说了

源码分析

这两个方法都会执行requestlayout方法,这个方法我们都知道其实就是执行View的onMeasure,onLayout,onDraw方法,那先来看看onMeasure方法:

    protected void onMeasure(int widthSpec, int heightSpec) {
    //这里就说明了,如果没有设置layoutManager,就不会绘制子Children了
    //我们后面也会看到,如果没设置mLayout,onLayout其实也不会实现什么
        if (mLayout == null) {
            //这里直接绘制本身,如果本身不是确定的大小,不会绘制children
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        //官方定义的LayoutManager这个值都是true
        if (mLayout.mAutoMeasure) {
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
            //如果长宽都是精确值,则执行defaultOnMeasure方法,可能有疑问了,那不就跟Layout==null的时候一样了?后面再说
            final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                    && heightMode == MeasureSpec.EXACTLY;
            //最后还是会执行上面的defaultOnMeasure的方法
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            if (skipMeasure || mAdapter == null) {
                return;
            }
            //mLayoutStep默认值是 State.STEP_START
            if (mState.mLayoutStep == State.STEP_START) {
            
            /**
            * 1.处理Adapter的更新
            * 2.决定那些动画需要执行
            * 3.保存当前View的信息
            * 4.如果必要的话,执行上一个Layout的操作并且保存他的信息
            * 这里跟绘制并没有什么关系
            */
                dispatchLayoutStep1();
                //执行完dispatchLayoutStep1()后是State.STEP_LAYOUT
            }
            // 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.
            //如果Child没有填充满RecyclerView,继续
            if (mLayout.shouldMeasureTwice()) {
                mLayout.setMeasureSpecs(
                        MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
                mState.mIsMeasuring = true;
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } 
        .....
    }
    
    private void dispatchLayoutStep2() {
        ....
        //这里我们就比较熟悉,State..mItemCount也就是数据个数目
        mState.mItemCount = mAdapter.getItemCount();
        .....
        //为了分析,我们就来看看LinearLayoutManager的onLayoutChildren方法吧
        mLayout.onLayoutChildren(mRecycler, mState);

        .....
    }
    
LinearLayoutManager.java:

    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
       .....
       
        // resolve layout direction
        //确定layout的方向,顺序呢,还是倒序
        resolveShouldLayoutReverse();

        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
                mPendingSavedState != null) {
            mAnchorInfo.reset();
            //mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // calculate anchor position and coordinate
            //找到瞄点的位置
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        }
        
        .....
        
        if (mAnchorInfo.mLayoutFromEnd) {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
                    LayoutState.ITEM_DIRECTION_HEAD;
        } else {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
                    LayoutState.ITEM_DIRECTION_TAIL;
        }
        //上面这部分是用来判断RecyclerView绘制是顺序还是倒序
        
        ......
        //这部分主要就是绘制了,关键的就是fill方法
        if (mAnchorInfo.mLayoutFromEnd) {
        
            // fill towards start
            //其实就是确定当前方向上锚点的相关的状态信息。
            updateLayoutStateToFillStart(mAnchorInfo);
           .....
            fill(recycler, mLayoutState, state, false);
           ......
        }else {
        ...
        }



    //这个方法关键的是layoutChunk
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
       ......
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            
           .....
        }
        return start - layoutState.mAvailable;
    }
       
       
       
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
            //获取到子View,稍后我们具体分析分析
        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);
            }
        }
        //测量ChildView
        measureChildWithMargins(view, 0, 0);
       ......
    }


上述过程大致就是取出子View,然后加进去,紧接着绘制子View。现在我们来看看上面说的layoutState.next方法:

LinearLayoutManager&LayoutState.java:

        View next(RecyclerView.Recycler recycler) {
          //默认mScrapList=null,
          //但是执行layoutForPredictiveAnimations方法的时候不会为空,执行完后又变为空,所以这里暂时不考虑
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            //这里是关键
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }

RecyclerView&Recycler.java:

        public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }

        View getViewForPosition(int position, boolean dryRun) {
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }
        
        //这里其实存在缓存的,有关缓存的以后再讲
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
           .....
           //这里就比较熟悉了
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
             
            return holder;
        }

到此,onMeasure方法就结束了,接下来我们来看看onLayout方法吧:


RecyclerView.java:

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       
        dispatchLayout();
        ....
        
    }
    void dispatchLayout() {
    
    //这里注意了,adapter和mLayout没设置的话,相当于跳过onLayout方法,
        if (mAdapter == null) {
            return;
        }
        if (mLayout == null) {
          
            return;
        }
        mState.mIsMeasuring = false;
        
        //这个点是:当RecyclerView长宽都是精确值的话,直接回跳过onMeasure
        //这里就是为了绘制子View
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
                mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        //其实不严谨的来讲onLayout的作用就仅仅是dispatchLayoutStep3()
        //而 dispatchLayoutStep3()方法的作用除了重置一些参数外还和执行动画有关。
        dispatchLayoutStep3();
    }

扩展:

假如现在有个需求,RecyclerView最大的高度为4,即当RecyclerView的Item数量超过4个的时候,RecyclerView的高度是固定的,假如就显示4个Item

我们来回顾下,在上述的源码分析中,我们知道测绘子View其实是在layoutChunk中
RecyclerView 初探——绘制流程

layoutChunk其实是在一个循环中的

 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state))

前两个比较复杂去实现,但最后一个:

layoutState.hasMore(state)

boolean hasMore(RecyclerView.State state) {

    return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}

根据上述的绘制流程我们知道,State的mItemCount属性的值其实是mAdapter.getItemCount(),这个方法就比较熟悉了,所以我们只需这样写就行了:

    override fun getItemCount(): Int {
        return if (data.size >= 4) 4
        else
            super.getItemCount()
    }

其实很多网上的做法是不可行的,他们其实就是重写了LinearLayoutManager的onMeasure方法。但通过我们这的分析,子View的测量根本不在LinearLayoutManager的onMeasure方法,而是在LinearLayoutManager的onLayoutChildren方法。



2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.009 s.
京ICP备10005923号