美文网首页ViewPager转场动画
Android RecyclerView 解析之绘制流程篇

Android RecyclerView 解析之绘制流程篇

作者: Amter | 来源:发表于2020-01-11 00:11 被阅读0次

    前言: 当前市场上有很多成熟的RecyclerView分析文章,但那始终是其他人总结出来的,还得自己动手分析,才知道自己理解了有多少,当然这个也算是加深对RecyclerView对理解吧;

    官方简介:A flexible view for providing a limited window into a large data set.
    一种灵活的视图,在有限的窗口,展示大量的数据集;

    在开始之前,为了加深理解,我们需要带着疑问进行阅读;
    (1),RecyclerView是怎么加载数据的?
    (2),RecyclerView是怎么将View绘制到页面上的?
    (3),RecyclerView是怎么复用item的?

    1.1 总体结构

    RecyclerView主体架构.png

    由上图可知,RecyclerView主要由这几部分组成;那他们的关系是啥呢? 具体是如何关联的呢?且听完细细道来!

    数据层面:首页RecyclerView需要将数据和view绑定起来,是通过Adapter加载ViewHolder来实现绑定数据的;
    布局层面:RecyclerView的Item的布局是通过LayoutManager来进行布局的;
    复用层面:LayoutManger从Recycler获取item来进行复用;

    总结:

    1,Adapter:将数据转化为RecyclerView可以识别的数据;
    2,ViewHolder:将数据和item绑定起来;
    3,LayoutManager:通过计算将Item布局到页面中;
    4,Recycler:复用机制,统一管理Item,用于复用;
    5,ItemDecoration:绘制item的样式;

    1.2 具体流程:

    1.2.1 RecyclerView 初始化流程

    首先,先来看看RecyclerView 的初始化流程,先举个简单的例子;

    //获取RecyclerView 控件
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
    //创建adapter
    MyAdapter adapter = new MyAdapter(list);
    //创建LayoutManager
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
    //设置LayoutManager
    recyclerView.setLayoutManager(linearLayoutManager);
    //设置Adapter
    recyclerView.setAdapter(adapter);

    1,我们先来看看RecyclerView 的构造方法做了啥?

    public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            //创建观察者
            this.mObserver = new RecyclerView.RecyclerViewDataObserver();
            //创建回收器
            this.mRecycler = new RecyclerView.Recycler();
            //创建布局信息保存类
            this.mViewInfoStore = new ViewInfoStore();
            this.mUpdateChildViewsRunnable = new Runnable() {
                public void run() {
                    if (RecyclerView.this.mFirstLayoutComplete && !RecyclerView.this.isLayoutRequested()) {
                        if (!RecyclerView.this.mIsAttached) {
                            RecyclerView.this.requestLayout();
                        } else if (RecyclerView.this.mLayoutFrozen) {
                            RecyclerView.this.mLayoutWasDefered = true;
                        } else {
                            RecyclerView.this.consumePendingUpdateOperations();
                        }
                    }
                }
            };
            ...
            if (attrs != null) {
                TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
                this.mClipToPadding = a.getBoolean(0, true);
                a.recycle();
            } else {
                this.mClipToPadding = true;
            }
            ...
            this.mAccessibilityManager = (AccessibilityManager)this.getContext().getSystemService("accessibility");
            this.setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
            boolean nestedScrollingEnabled = true;
            if (attrs != null) {
                int defStyleRes = 0;
                TypedArray a = context.obtainStyledAttributes(attrs, styleable.RecyclerView, defStyle, defStyleRes);
                //从布局文件获取Layoutmanger的名称
                String layoutManagerName = a.getString(styleable.RecyclerView_layoutManager);
                int descendantFocusability = a.getInt(styleable.RecyclerView_android_descendantFocusability, -1);
                if (descendantFocusability == -1) {
                    this.setDescendantFocusability(262144);
                }
    
                this.mEnableFastScroller = a.getBoolean(styleable.RecyclerView_fastScrollEnabled, false);
                //通过layoutManger的名称进行反射创建layoutManager,并设置给RecycleView
                this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
              ...
            } else {
                this.setDescendantFocusability(262144);
            }
            //设置是否支持嵌套滚动,默认为true
            this.setNestedScrollingEnabled(nestedScrollingEnabled);
        }
    

    从构造方法可以看出,里面做了一大堆初始化的操作,最主要看一下这个创建layoutManager的方法createLayoutManager();

    根据布局属性进行反射来创建layoutManager;

    private void createLayoutManager(Context context, String className, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            if (className != null) {
                className = className.trim();
                if (!className.isEmpty()) {
                    className = this.getFullClassName(context, className);
    
                    try {
                        ClassLoader classLoader;
                        if (this.isInEditMode()) {
                            classLoader = this.getClass().getClassLoader();
                        } else {
                            classLoader = context.getClassLoader();
                        }
    
                        Class<? extends RecyclerView.LayoutManager> layoutManagerClass = classLoader.loadClass(className).asSubclass(RecyclerView.LayoutManager.class);
                        Object[] constructorArgs = null;
    
                        Constructor constructor;
                        try {
                            //通过反射创建布局构造器
                            constructor = layoutManagerClass.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
                            constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
                        } catch (NoSuchMethodException var13) {
                            try {
                                constructor = layoutManagerClass.getConstructor();
                            } catch (NoSuchMethodException var12) {
                                var12.initCause(var13);
                                throw new IllegalStateException(attrs.getPositionDescription() + ": Error creating LayoutManager " + className, var12);
                            }
                        }
    
                        constructor.setAccessible(true);
    
                         //将创建出来的LayoutManger设置给RecycleView       
                         this.setLayoutManager((RecyclerView.LayoutManager)constructor.newInstance(constructorArgs));
                    } catch (ClassNotFoundException var14) {
                        throw new IllegalStateException(attrs.getPositionDescription() + ": Unable to find LayoutManager " + className, var14);
                    } catch (InvocationTargetException var15) {
                        throw new IllegalStateException(attrs.getPositionDescription() + ": Could not instantiate the LayoutManager: " + className, var15);
                    } catch (InstantiationException var16) {
                        throw new IllegalStateException(attrs.getPositionDescription() + ": Could not instantiate the LayoutManager: " + className, var16);
                    } catch (IllegalAccessException var17) {
                        throw new IllegalStateException(attrs.getPositionDescription() + ": Cannot access non-public constructor " + className, var17);
                    } catch (ClassCastException var18) {
                        throw new IllegalStateException(attrs.getPositionDescription() + ": Class is not a LayoutManager " + className, var18);
                    }
                }
            }
    
        }
    

    再看一下setLayoutManager()这个方法里面做了啥操作?

    public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) {
            if (layout != this.mLayout) {
               //停止当前的滚动操作
                this.stopScroll();
                if (this.mLayout != null) {
                    //判断当前的layoutManager如果为空,则将该layoutManager的状态进行初始化;
                    if (this.mItemAnimator != null) {
                        this.mItemAnimator.endAnimations();
                    }
    
                    this.mLayout.removeAndRecycleAllViews(this.mRecycler);
                    this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
                    this.mRecycler.clear();
                    if (this.mIsAttached) {
                        this.mLayout.dispatchDetachedFromWindow(this, this.mRecycler);
                    }
    
                    this.mLayout.setRecyclerView((RecyclerView)null);
                    this.mLayout = null;
                } else {
                    this.mRecycler.clear();
                }
    
                this.mChildHelper.removeAllViewsUnfiltered();
                //将当前的layoutManager赋值给成员变量
                this.mLayout = layout;
                if (layout != null) {
                    if (layout.mRecyclerView != null) {
                        throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView:" + layout.mRecyclerView.exceptionLabel());
                    }
                    //将当前的RecyclerView赋值给layoutManager
                    this.mLayout.setRecyclerView(this);
                    if (this.mIsAttached) {
                        this.mLayout.dispatchAttachedToWindow(this);
                    }
                }
                //更新一下RecyclerView的缓存
                this.mRecycler.updateViewCacheSize();
                //触发重新布局
                this.requestLayout();
            }
        }
    

    总结:看完RecyclerView的构造方法,里面主要是做了一些初始化的操作,并创建了layoutManager设置给RecyclerView(如果布局属性有设置的话);

    2,看完了RecyclerView的setLayoutManager()的流程,我们继续接着分析,看一下setAdapter()具体做了啥?

    public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
            this.setLayoutFrozen(false);
            //主要模块
            this.setAdapterInternal(adapter, false, true);
            this.processDataSetCompletelyChanged(false);
            this.requestLayout();
        }
    

    跟进源码,我们主要分析setAdapterInternal()这个方法,让我们看看这个源码里面做了什么操作;

    private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
            if (this.mAdapter != null) {
                //解注册之前的数据观察者
                this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
                this.mAdapter.onDetachedFromRecyclerView(this);
            }
    
            if (!compatibleWithPrevious || removeAndRecycleViews) { 
                //进行初始化操作,初始化layoutManger,初始化mRecycler
                this.removeAndRecycleViews();
            }
            
            this.mAdapterHelper.reset();
            RecyclerView.Adapter oldAdapter = this.mAdapter;
            //将adapter赋值给当前成员变量
            this.mAdapter = adapter;
            if (adapter != null) {
                //adapter注册数据观察者,用于监听数据的增删改查
                adapter.registerAdapterDataObserver(this.mObserver);
                adapter.onAttachedToRecyclerView(this);
            }
    
            if (this.mLayout != null) {
                this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
            }
    
            this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious);
            this.mState.mStructureChanged = true;
        }
    

    这个方法里面主要是给adapter注册数据监听,用于数据的增删改查的刷新,并做一些初始化的操作;

    我们再看一下这个观察者里面主要做了什么操作,具体的实现是在RecyclerViewDataObserver 这个类里面;

    private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
            RecyclerViewDataObserver() {
            }
    
            public void onChanged() {
                RecyclerView.this.assertNotInLayoutOrScroll((String)null);
                RecyclerView.this.mState.mStructureChanged = true;
                RecyclerView.this.processDataSetCompletelyChanged(true);
                if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
                    RecyclerView.this.requestLayout();
                }
    
            }
    
            public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                RecyclerView.this.assertNotInLayoutOrScroll((String)null);
                if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                    this.triggerUpdateProcessor();
                }
    
            }
    
            public void onItemRangeInserted(int positionStart, int itemCount) {
                RecyclerView.this.assertNotInLayoutOrScroll((String)null);
                if (RecyclerView.this.mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                    this.triggerUpdateProcessor();
                }
    
            }
    
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                RecyclerView.this.assertNotInLayoutOrScroll((String)null);
                if (RecyclerView.this.mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                    this.triggerUpdateProcessor();
                }
    
            }
    
            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                RecyclerView.this.assertNotInLayoutOrScroll((String)null);
                if (RecyclerView.this.mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
                    this.triggerUpdateProcessor();
                }
    
            }
    
            void triggerUpdateProcessor() {
                if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
                    ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
                } else {
                    RecyclerView.this.mAdapterUpdateDuringMeasure = true;
                    RecyclerView.this.requestLayout();
                }
    
            }
        }
    

    看到了我们很熟悉的方法,即adapter刷新数据所调用的方法;我们主要分析其中一个方法即可,让我们来看一下onItemRangeChanged()这个方法;
    这里面主要分为两步:

    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                RecyclerView.this.assertNotInLayoutOrScroll((String)null);
                //这里通过AdapterHelper将传进来的信息保存起来
                if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                    //重新布局
                    this.triggerUpdateProcessor();
                }
    
            }
    

    (1)通过AdapterHelper将传进来的信息保存起来;

    boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            if (itemCount < 1) {
                return false;
            } else {
                this.mPendingUpdates.add(this.obtainUpdateOp(4, positionStart, itemCount, payload));
                this.mExistingUpdateTypes |= 4;
                return this.mPendingUpdates.size() == 1;
            }
        }
    

    (2)通过triggerUpdateProcessor()方法触发RecyclerView重新布局;

    void triggerUpdateProcessor() {
           if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
                    //当前有动画正在执行的时候会走这里
                    ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
                } else { 
                    //触发重新布局
                    RecyclerView.this.mAdapterUpdateDuringMeasure = true;
                    RecyclerView.this.requestLayout();
                }
    
            }
    

    1,RecyclerView的主要绘制流程;
    2,复用机制;

    2. 工作流程

    2.1 主体关系

    首先我们来看一下各个模块的关系;


    关系图.png

    通过上图大体可以看出这几个模块的关系:

    (1)RecyclerView通过LayoutManager来进行布局操作;
    (2)LayoutManager从Recycler里面获取复用的item来进行布局;
    (3)Recycler管理着ViewHolder的创建与复用;
    (4)Adapter将数据和ViewHolder绑定起来,并和RecyclerView注册观察者;
    (5)RecyclerView通过ItemDecoration进行item样式的绘制;

    接下来通过源码来细细剖析,看看具体是怎么实现的;
    那么我们接着上面分析的setAdapter()方法继续分析,在setAdapter()方法里,最后调用来requestLayout(),来触发RecyclerView 的绘制流程;
    这个requestLayout()这个方法最终会调用到ViewRootImp里面的requestLayout()方法;

    public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                //触发绘制流程
                scheduleTraversals();
            }
        }
    

    在ViewRootImp里调用requestLayout()方法进行绘制,我们主要看scheduleTraversals()方法,里面最终会调用到performTraversals()方法,源码如下;

    void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    ...
    final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    ...
    void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
                if (mProfile) {
                    Debug.startMethodTracing("ViewAncestor");
                }
    
                performTraversals();
    
                if (mProfile) {
                    Debug.stopMethodTracing();
                    mProfile = false;
                }
            }
        }
    ...
    
    

    performTraversals()这个方法里面执行了三大步骤,测量(measure),布局(layout),绘制(draw),完成的view的工作流程,将页面绘制出来;

    {
            // cache mView since it is used so much below...
            final View host = mView;
    
            ...
            if (!mStopped || mReportNextDraw) {
               
                 //执行view的测量流程
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            } else {
                ...
            }
           ...
            if (didLayout) {
                //执行view的布局流程
                performLayout(lp, mWidth, mHeight);
    
                ...
            }
            ...
            if (!cancelDraw && !newSurface) {
                ...
                //执行view的绘制流程
                performDraw();
            } else {
               ...
            }
        }
    

    从上面整理的方法来看,绘制流程主要是这performMeasure(),performLayout(),performDraw();最终会触发RecyclerView的onMeasure(),onLayout(),onDraw()方法,具体源码这里就不过多分析了,感兴趣的可以看一下View的绘制流程;

    让我们一个个来进行分析,先看看RecyclerView的onMeasure()方法里面做了什么?

    onMeasure()分析:

    protected void onMeasure(int widthSpec, int heightSpec) {
            if (mLayout == null) {
                //1.判断当前的LayoutManger是否为空,为空则走RecyclerView默认测量的方法 ;
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
             //2.LayoutManger开启自动测量时走这里处理逻辑;
            if (mLayout.mAutoMeasure) {
                final int widthMode = MeasureSpec.getMode(widthSpec);
                final int heightMode = MeasureSpec.getMode(heightSpec);
                final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                        && heightMode == MeasureSpec.EXACTLY;
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                if (skipMeasure || mAdapter == null) {
                    return;
                }
                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(
                            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);
                }
            } else {
              //3.LayoutManger没有开启自动测量时走这里处理逻辑;
                if (mHasFixedSize) {
                    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                    return;
                }
                // custom onMeasure
                if (mAdapterUpdateDuringMeasure) {
                    eatRequestLayout();
                    onEnterLayoutOrScroll();
                    processAdapterUpdatesAndSetAnimationFlags();
                    onExitLayoutOrScroll();
    
                    if (mState.mRunPredictiveAnimations) {
                        mState.mInPreLayout = true;
                    } else {
                        // consume remaining updates to provide a consistent state with the layout pass.
                        mAdapterHelper.consumeUpdatesInOnePass();
                        mState.mInPreLayout = false;
                    }
                    mAdapterUpdateDuringMeasure = false;
                    resumeRequestLayout(false);
                }
    
                if (mAdapter != null) {
                    mState.mItemCount = mAdapter.getItemCount();
                } else {
                    mState.mItemCount = 0;
                }
                eatRequestLayout();
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                resumeRequestLayout(false);
                mState.mInPreLayout = false; // clear
            }
        }
    

    这里面主要分三种情况,而我们大部分情况都是走第三步,通过查看官方的LayoutManger的源码得知,LinearLayoutManager和StaggeredGridLayoutManager都开启了自动测试,而GridLayoutManager继承自LinearLayoutManager;所以,官方的LayoutManager都开启了自动测量,这里我们只需要关注第二步的逻辑;

    从上面源码可以看出,RecyclerView通过LayoutManger里的onMeasure()来进行测量操作;
    通过State这个类来进行布局和测试状态的记录,这里的mLayoutStep 包括STEP_START、STEP_LAYOUT 、 STEP_ANIMATIONS三个状态;

    从源码分析,此时测量完毕之后,判断当前状态为开始的时候(STEP_START),调用了dispatchLayoutStep1()进行了一系列的操作,这个方法执行完了之后,会将mLayoutStep 赋值为STEP_LAYOUT;后面就执行了dispatchLayoutStep2(),在这个方法里将mLayoutStep 赋值为STEP_ANIMATIONS;

    这里我们可以理解为,RecyclerView在测量完毕之后,就开始进行布局了,分别执行了dispatchLayoutStep1()和dispatchLayoutStep2()方法;到此onMeasure()分析完了;

    让我们继续接着往下看,此时RecyclerView的onMeasure()已经执行完了,接下来会执行onLayout()方法,让我们看看这个方法里面做了啥?

    onLayout()分析:

    先看一下源码

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
            TraceCompat.beginSection("RV OnLayout");
            //执行布局操作
            this.dispatchLayout();
            TraceCompat.endSection();
            this.mFirstLayoutComplete = true;
        }
    

    主要看dispatchLayout()这个方法

    void dispatchLayout() {
            if (mAdapter == null) {
                Log.e(TAG, "No adapter attached; skipping layout");
                // leave the state in START
                return;
            }
            if (mLayout == null) {
                Log.e(TAG, "No layout manager attached; skipping layout");
                // leave the state in START
                return;
            }
            mState.mIsMeasuring = false;
            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);
            }
            dispatchLayoutStep3();
        }
    

    通过上面源码可以看出,之前在onMeasure()里的这个dispatchLayoutStep2()方法里面已经把mLayoutStep 赋值为STEP_ANIMATIONS,那么这里就会走最后一个方法dispatchLayoutStep3();如果没有执行STEP_START方法,那么就会依次执行dispatchLayoutStep1(),dispatchLayoutStep2(),dispatchLayoutStep3()这几个布局方法;让我们来一个个分析;

    dispatchLayoutStep1():
    private void dispatchLayoutStep1() {
            mState.assertLayoutStep(State.STEP_START);
            mState.mIsMeasuring = false;
            eatRequestLayout();
            mViewInfoStore.clear();
            onEnterLayoutOrScroll();
            processAdapterUpdatesAndSetAnimationFlags();
            saveFocusInfo();
            mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
            mItemsAddedOrRemoved = mItemsChanged = false;
            mState.mInPreLayout = mState.mRunPredictiveAnimations;
            mState.mItemCount = mAdapter.getItemCount();
            findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
    
            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 = getChildViewHolderInt(mChildHelper.getChildAt(i));
                    if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                        continue;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator
                            .recordPreLayoutInformation(mState, holder,
                                    ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                    holder.getUnmodifiedPayloads());
                    mViewInfoStore.addToPreLayout(holder, animationInfo);
                    if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                            && !holder.shouldIgnore() && !holder.isInvalid()) {
                        long key = getChangedHolderKey(holder);
                        // This is NOT the only place where a ViewHolder is added to old change holders
                        // list. There is another case where:
                        //    * A VH is currently hidden but not deleted
                        //    * The hidden item is changed in the adapter
                        //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                        // When this case is detected, RV will un-hide that view and add to the old
                        // change holders list.
                        mViewInfoStore.addToOldChangeHolders(key, holder);
                    }
                }
            }
            if (mState.mRunPredictiveAnimations) {
                // Step 1: run prelayout: This will use the old positions of items. The layout manager
                // is expected to layout everything, even removed items (though not to add removed
                // items back to the container). This gives the pre-layout position of APPEARING views
                // which come into existence as part of the real layout.
    
                // Save old positions so that LayoutManager can run its mapping logic.
                saveOldPositions();
                final boolean didStructureChange = mState.mStructureChanged;
                mState.mStructureChanged = false;
                // temporarily disable flag because we are asking for previous layout
                mLayout.onLayoutChildren(mRecycler, mState);
                mState.mStructureChanged = didStructureChange;
    
                for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                    final View child = mChildHelper.getChildAt(i);
                    final ViewHolder viewHolder = getChildViewHolderInt(child);
                    if (viewHolder.shouldIgnore()) {
                        continue;
                    }
                    if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                        int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                        boolean wasHidden = viewHolder
                                .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                        if (!wasHidden) {
                            flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                        }
                        final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                                mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                        if (wasHidden) {
                            recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                        } else {
                            mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                        }
                    }
                }
                // we don't process disappearing list because they may re-appear in post layout pass.
                clearOldPositions();
            } else {
                clearOldPositions();
            }
            onExitLayoutOrScroll();
            resumeRequestLayout(false);
            mState.mLayoutStep = State.STEP_LAYOUT;
        }
    

    这个方法主要做了ViewHolder信息的保存,里面通过遍历当前的子View,根据子view的位置信息创建ItemHolderInfo,并添加到 ViewInfoStore这个类里面进行保存;
    看一下ItemHolderInfo这个类;

    public static class ItemHolderInfo {
                public int left;
                public int top;
                public int right;
                public int bottom;
                public ItemHolderInfo() {
                }
              ...
                public ItemHolderInfo setFrom(@NonNull 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;
                }
            }
    
    class ViewInfoStore {
    
        private static final boolean DEBUG = false;
    
        /**
         * View data records for pre-layout
         */
        @VisibleForTesting
        final ArrayMap<RecyclerView.ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
    
        @VisibleForTesting
        final LongSparseArray<RecyclerView.ViewHolder> mOldChangedHolders = new LongSparseArray<>();
    
        /**
         * Clears the state and all existing tracking data
         */
        void clear() {
            mLayoutHolderMap.clear();
            mOldChangedHolders.clear();
        }
    
        /**
         * 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(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
            InfoRecord record = mLayoutHolderMap.get(holder);
            if (record == null) {
                record = InfoRecord.obtain();
                mLayoutHolderMap.put(holder, record);
            }
            record.preInfo = info;
            record.flags |= FLAG_PRE;
        }
    }
    

    通过源码可以看出,在dispatchLayoutStep1()方法里会先遍历子view,并创建ItemHolderInfo,然后再通过ViewInfoStore的addToPreLayout()的这个方法将ItemHolderInfo赋值给InfoRecord,再保存到mLayoutHolderMap这个集合里面;

    下面我们再来分析一下dispatchLayoutStep2()这个方法里面做来啥?

    dispatchLayoutStep2():
    private void dispatchLayoutStep2() {
           private void dispatchLayoutStep2() {
            startInterceptRequestLayout();
            onEnterLayoutOrScroll();
            mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
            mAdapterHelper.consumeUpdatesInOnePass();
            mState.mItemCount = mAdapter.getItemCount();
            mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    
            // Step 2: Run layout
            mState.mInPreLayout = false;
            //  开始真正的去布局
            mLayout.onLayoutChildren(mRecycler, mState);
    
            mState.mStructureChanged = false;
            mPendingSavedState = null;
    
            // onLayoutChildren may have caused client code to disable item animations; re-check
            mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
            mState.mLayoutStep = State.STEP_ANIMATIONS;
            onExitLayoutOrScroll();
            stopInterceptRequestLayout(false);
        }
        }
    

    通过上面的源码可以看出,dispatchLayoutStep2()里面就开始真正的去布局了,通过onLayoutChildre()方法进行布局,具体的实现都在LayoutManager的子类里面;我们常用的LayoutManager基本上是LinearLayoutManager,那么这里我们具体来分析一下这个类里面是怎么实现的;

    先看一下源码:

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
          
           ...
            final View focused = getFocusedChild();
            if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                    || mPendingSavedState != null) {
                mAnchorInfo.reset();
                // 获取布局的锚点
                updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
                mAnchorInfo.mValid = true;
            } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                            >= mOrientationHelper.getEndAfterPadding()
                    || mOrientationHelper.getDecoratedEnd(focused)
                    <= mOrientationHelper.getStartAfterPadding())) {
                 ...
                // 更新锚点信息
                mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
            }
            
            //判断是否是从后往前开始布局
            if (mAnchorInfo.mLayoutFromEnd) {
                ...
                //布局操作
                fill(recycler, mLayoutState, state, false);
               ...
            } else {
                 ...
                // fill towards end
                fill(recycler, mLayoutState, state, false);
    
              // fill towards start
              fill(recycler, mLayoutState, state, false);
              ...
            }
    
            ...
        }
    

    这里把代码简化了,我们只需要关注几个重点的方法;这里的布局操作是,通过寻找布局的锚点(mAnchorInfo),判断是从后往前布局还是从前往后布局,然后调用fill()方法进行布局;

    寻找布局的锚点是通过updateAnchorInfoForLayout(recycler, state, mAnchorInfo)这个方法

    private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
                AnchorInfo anchorInfo) {
            ...
    
            if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
                if (DEBUG) {
                    Log.d(TAG, "updated anchor info from existing children");
                }
                return;
            }
            ...
        }
    

    这里我们只需要关注updateAnchorFromChildren这个方法,跟进去看一下具体做了什么;

    private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
                RecyclerView.State state, AnchorInfo anchorInfo) {
            if (getChildCount() == 0) {
                return false;
            }
            final View focused = getFocusedChild();
            if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
                anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
                return true;
            }
            if (mLastStackFromEnd != mStackFromEnd) {
                return false;
            }
            View referenceChild = anchorInfo.mLayoutFromEnd
                    ? findReferenceChildClosestToEnd(recycler, state)
                    : findReferenceChildClosestToStart(recycler, state);
            if (referenceChild != null) {
                anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
                ...
                }
                return true;
            }
            return false;
        }
    

    从这里的源码可以看出,先通过getFocusedChild()去获取focused 这个view,当获取到了的时候将其标记为锚点,如果获取不到那么就通过findReferenceChildClosestToEnd和findReferenceChildClosestToStart去寻找合适的view,并将其标记为锚点;

    让我们回到onLayoutChildren这个方法,当获取到锚点的时候,调用fill方法开始填充页面,根据fill方法看看具体做了什么?

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                RecyclerView.State state, boolean stopOnFocusable) {
            ...
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                //回收没有用到的view
                recycleByLayoutState(recycler, layoutState);
            }
            ...
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                  ...
            }
        }
    

    这里通过recycleByLayoutState方法先将没有用到view进行回收,然后再通过while循环调用layoutChunk方法进行布局;

    看一下layoutChunk方法具体做了什么操作?

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                LayoutState layoutState, LayoutChunkResult result) {
            View view = layoutState.next(recycler);
            ...
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
            if (layoutState.mScrapList == null) {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
               ...
            }
            ...
            layoutDecoratedWithMargins(view, left, top, right, bottom);
            ...
        }
    

    到这里就是最终布局的地方了,先通过recycler获取要布局的view,再通过addView方法将view添加到RecyclerView里去,然后根据参数调用layoutDecoratedWithMargins方法进行布局;

    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
                    int bottom) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final Rect insets = lp.mDecorInsets;
                child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                        right - insets.right - lp.rightMargin,
                        bottom - insets.bottom - lp.bottomMargin);
            }
    

    这里最终调用了view的layout方法进行布局;到这里dispatchLayoutStep2()就分析完了,让我们继续接着看dispatchLayoutStep3()第三步里面做了啥;

    dispatchLayoutStep3():
    private void dispatchLayoutStep3() {
            mState.assertLayoutStep(State.STEP_ANIMATIONS);
            ...
            mState.mLayoutStep = State.STEP_START;
            if (mState.mRunSimpleAnimations) {
                ...
                for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
                    ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                    if (holder.shouldIgnore()) {
                        continue;
                    }
                    long key = getChangedHolderKey(holder);
                    final ItemHolderInfo animationInfo = mItemAnimator
                            .recordPostLayoutInformation(mState, holder);
                    ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
                    ...
                        if (oldDisappearing && oldChangeViewHolder == holder) {
                            // run disappear animation instead of change
                            mViewInfoStore.addToPostLayout(holder, animationInfo);
                        } else {
                            ...
                            mViewInfoStore.addToPostLayout(holder, animationInfo);
                            ...
                        }
                    } else {
                        mViewInfoStore.addToPostLayout(holder, animationInfo);
                    }
                }
    
                // Step 4: Process view info lists and trigger animations
              //触发动画
                mViewInfoStore.process(mViewInfoProcessCallback);
            }
    
            ...
        }
    

    这个方法里面只需要关注addToPostLayout这个方法就行,这里和第一步类似,也是通过遍历viewholder信息来创建ItemHolderInfo,并保存到mViewInfoStore里去;

    看一下addToPostLayout这个方法做了啥?

    void addToPostLayout(RecyclerView.ViewHolder holder, RecyclerView.ItemAnimator.ItemHolderInfo info) {
            InfoRecord record = mLayoutHolderMap.get(holder);
            if (record == null) {
                record = InfoRecord.obtain();
                mLayoutHolderMap.put(holder, record);
            }
            record.postInfo = info;
            
    

    也是通过将ItemHolderInfo信息转化为InfoRecord类,然后保存到集合里去(mLayoutHolderMap);

    到此,RecyclerView的onLayout流程就已经走完了;那么接下来就要开始分析onDraw的流程了;

    onDraw()分析

    先看一下源码;

    public void draw(Canvas c) {
            super.draw(c);
    
            ...
            for (int i = 0; i < count; i++) {
                mItemDecorations.get(i).onDrawOver(c, this, mState);
            }
            ...
        }
    
    
    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);
            }
        }
    

    很简单,就几行,mItemDecorations这个集合里面存的是ItemDecoration,也就是说,RecyclerView的onDraw是用来绘制ItemDecoration的;而itemView的绘制是在ViewGroup里面;

    至此,RecyclerView的onMeasure,onLayout,onDraw,流程就已经分析完毕了;

    总结:

    RecyclerView的布局流程比较复杂,但是还是遵循viewGroup的绘制原理,即onMeasure,onLayout,onDraw这几步流程;

    绘制流程.png

    那么到这里,布局到流程就已经讲完了,希望能对你有所帮助,后面会继续分析RecyclerView的复用机制,敬请期待!

    相关文章

      网友评论

        本文标题:Android RecyclerView 解析之绘制流程篇

        本文链接:https://www.haomeiwen.com/subject/cvzyoctx.html