美文网首页
RecyclerView绘制

RecyclerView绘制

作者: leap_ | 来源:发表于2020-06-16 18:05 被阅读0次

    measure

    在分析onMeasure()前,需要介绍三个基本的layout方法和mState.mLayoutStep状态:

    • State.STEP_START : 默认状态,表示RV此时还没有经过dispatchLayoutStep1()
    • State.STEP_LAYOUT : dispatchLayoutStep1()后会将状态转变为此状态,表示处于layout阶段,会调用dispatchLayoutStep2();
    • State.STEP_ANIMATIONS: dispatchLayoutStep2()会将状态转变为次状态,处于执行动画阶段,会调用dispatchLayoutStep3(), dispatchLayoutStep3()执行完毕会将状态重新转为初始默认状态STEP_START
    dispatchLayoutStep1():
        /**
         * The first step of a layout where we;
         * - process adapter updates
         * - decide which animation should run
         * - save information about current views
         * - If necessary, run predictive layout and save its information
         */
        private void dispatchLayoutStep1() {
            processAdapterUpdatesAndSetAnimationFlags();
            if (mState.mRunSimpleAnimations) {
                // Step 0: Find out where all non-removed items are, pre-layout
                    final ItemHolderInfo animationInfo = mItemAnimator
                            .recordPreLayoutInformation(mState, holder,
                                    ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                    holder.getUnmodifiedPayloads());
                    mViewInfoStore.addToPreLayout(holder, animationInfo);
                }
            if (mState.mRunPredictiveAnimations) {
                // Step 1: run prelayout: This will use the old positions of items. 
                // Save old positions so that LayoutManager can run its mapping logic.
                saveOldPositions();
                // temporarily disable flag because we are asking for previous layout
                mLayout.onLayoutChildren(mRecycler, mState);
                for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                    final View child = mChildHelper.getChildAt(i);
                    final ViewHolder viewHolder = getChildViewHolderInt(child);
                        final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                                mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                        if (wasHidden) {
                            recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                        } else {
                            mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                        }
                    }
                }
            } 
            mState.mLayoutStep = State.STEP_LAYOUT;
        }
    
    1. 调用processAdapterUpdatesAndSetAnimationFlags()处理adapter更新和设置动画标记
    2. 保存animationInforun pre-layout:保存animationInfo到ViewInfoStore类中;
    3. 改变state为Step_Layout
    dispatchLayoutStep2():
        private void dispatchLayoutStep2() {
     
            // Step 2: Run layout
            mState.mInPreLayout = false;
            mLayout.onLayoutChildren(mRecycler, mState);
     
            mState.mLayoutStep = State.STEP_ANIMATIONS;
    
        }
    
    1. 调用LayoutManager的onLayoutChildren()真正进行child的测量和布局
    2. 将state转为Step_Animations

    看看LinearLayoutManager的onLayoutChildren()实现

        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
    
                updateAnchorInfoForLayout(recycler, state, mAnchorInfo);   //  计算锚点
    
            detachAndScrapAttachedViews(recycler);  //  一级缓存scrap缓存
    
                fill(recycler, mLayoutState, state, false);   // 
        }
    
    1. 调用updateAnchorInfoForLayout()计算锚点

      锚点
    2. detachAndScrapAttachedViews()一级缓存,Scrap缓存

    3. fill()计算可填充空间,在fill()中调用layoutChunk()真正的去填充child

    锚点布局的三种
    
        void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                LayoutState layoutState, LayoutChunkResult result) {
    
            //  如果是第一次,addView()
            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);
                }
            }
    
            //   measure child
            measureChildWithMargins(view, 0, 0);
    
            //   计算child的layout参数
            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;
                }
            }
    
            //  layout child
            layoutDecoratedWithMargins(view, left, top, right, bottom);
    
        }
    
    1. 调用layoutState.next()获取View,如果是第一次布局(Scrap缓存为null),addView()
    2. 调用measureChildWithMargins()measure child
    3. 根据LayoutManager的Orientation计算child 坐标
    4. 调用layoutDecoratedWithMargins()layout child
    dispatchLayoutStep3():
        /**
         * The final step of the layout where we save the information about views for animations,
         * trigger animations and do any necessary cleanup.
         */
        private void dispatchLayoutStep3() {
    
            mState.mLayoutStep = State.STEP_START;
    
                // Step 4: Process view info lists and trigger animations
                mViewInfoStore.process(mViewInfoProcessCallback);
        }
    
    1. 将state设置为STEP_START,下次再判断state时肯定会重新经历Step1(),Step2(),Step3()
    2. trigger animations,开启动画

    measure

    
        @Override
        protected void onMeasure(int widthSpec, int heightSpec) {
            if (mLayout == null) {    //  第一种情况,没有LayoutManager
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
            if (mLayout.mAutoMeasure) {           //  第二种情况,有layoutmanager,并且开启自动测量
    
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    
                if (mState.mLayoutStep == State.STEP_START) {
                    dispatchLayoutStep1();
                }
    
                dispatchLayoutStep2();
    
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    
            } else {   //  第三种情况,有layoutmanager,没有开启自动测量
                      mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            }
        }
    

    1. 如果没有layoutManger:

        void defaultOnMeasure(int widthSpec, int heightSpec) {
            // calling LayoutManager here is not pretty but that API is already public and it is better
            // than creating another method since this is internal.
            final int width = LayoutManager.chooseSize(widthSpec,
                    getPaddingLeft() + getPaddingRight(),
                    ViewCompat.getMinimumWidth(this));
            final int height = LayoutManager.chooseSize(heightSpec,
                    getPaddingTop() + getPaddingBottom(),
                    ViewCompat.getMinimumHeight(this));
    
            setMeasuredDimension(width, height);
        }
    
            public static int chooseSize(int spec, int desired, int min) {
                final int mode = View.MeasureSpec.getMode(spec);
                final int size = View.MeasureSpec.getSize(spec);
                switch (mode) {
                    case View.MeasureSpec.EXACTLY:
                        return size;
                    case View.MeasureSpec.AT_MOST:
                        return Math.min(size, Math.max(desired, min));
                    case View.MeasureSpec.UNSPECIFIED:
                    default:
                        return Math.max(desired, min);
                }
            }
    

    当前RV如果没有LayoutManager,就调用defaultOnMeasure()根据MeasureSpec默认测量

    2. 如果有layoutManger,并且开启了自动测量默认场景


    上面已经介绍了step1()和step2()的作用,在第二种情况下,onMeasure()首先对child进行measurelayout,然后再结合childsetMeasuredDimension()

    3. 关闭自动测量模式:

    和第一种情况一样,也是调用了defaultOnMeasure(),不过这是通过LayoutManger来完成调用的;

    至此RV的measure过程分析完毕

    layout

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
            dispatchLayout();
            TraceCompat.endSection();
            mFirstLayoutComplete = true;
        }
    
        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();
        }
    
    

    layout中的工作就变得非常简单了,首先判断了adapter和layoutManager不为null,然后再调用step3,因为step3会将state设置为start,所以下次再layout的时候就会执行step1,step2,step3

    如果开启了自动测量,就会在measure中测量,将state设置为animationlayout过程就只会执行step3;
    如果没有开启自动测量,就会在layout过程执行step1,step2,step3;

    draw

        @Override
        public void draw(Canvas c) {
            super.draw(c);
     
            for (int i = 0; i < count; i++) {
                mItemDecorations.get(i).onDrawOver(c, this, mState);
            }
        }
    
        @Override
        public void onDraw(Canvas c) {
            super.onDraw(c);
    
            final int count = mItemDecorations.size();
            for (int i = 0; i < count; i++) {
                mItemDecorations.get(i).onDraw(c, this, mState);
            }
        }
    
    

    在RV的draw过程中,调用了ItemDecorationonDrawOveronDraw为item添加装饰

    相关文章

      网友评论

          本文标题:RecyclerView绘制

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