美文网首页
简单聊聊Android View绘制流程

简单聊聊Android View绘制流程

作者: Jevely | 来源:发表于2019-03-31 10:19 被阅读0次

    哈喽,大家好。今天我们来简单聊聊Android中View的绘制流程。这些东西个人感觉挺枯燥的,不过又是必须要掌握的东西,只有硬着头皮学了。本篇文章我会尽量说的简单易懂,如果有不懂得,可以留言交流。

    好了,废话不多说,下面开始正文。


    说到View绘制流程,我们就从绘制流程的触发开始讲起。
    View的绘制是ActivityThread在创建Activity后调用的handleResumeActivity方法中触发的,所以我们在Avtivity的onCreate方法中获取到的View宽高都是0也是这么来的,因为还没开始绘制。下面我们来看看handleResumeActivity方法

        @Override
        public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                String reason) {
            ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ...
                WindowManager.LayoutParams l = r.window.getAttributes();
                ...
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);//这里调用的是WindowManagerImpl的addView方法
                    } else {
                        a.onWindowAttributesChanged(l);
                    }
                }
    
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
           ...
        }
    

    代码省略了很多,其中注释的地方就是关键。我们跟进去发现最后是调用WindowManagerGlobal类的addView方法。下面我们来看看这个方法的源码

        public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
            ...
            ViewRootImpl root;
    
            synchronized (mLock) {
                ...
                root = new ViewRootImpl(view.getContext(), display);
                ...
                try {
                    root.setView(view, wparams, panelParentView);//这里调用了ViewRootImpl的setView方法
                } catch (RuntimeException e) {
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                    throw e;
                }
            }
        }
    
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    ...
                    requestLayout();//继续跟进
                    if ((mWindowAttributes.inputFeatures
                            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                        mInputChannel = new InputChannel();
                    }
                   ...
                }
            }
        }
    
        void doTraversal() {
            if (mTraversalScheduled) {
                ...
                performTraversals();
                ...
            }
        }
    

    最终我们调用了ViewRootImpl的performTraversals方法,在这个方法中我们会分别调用performMeasure,performLayout和performDraw方法,这三个方法分别就是对应View中的measure,layout和draw方法。这样一来,View绘制是如何触发的就有一个大致的了解了。


    下面我们从measure方法开始分析。

    MeasureSpec

    MeasureSpec封装了控件的布局需求。measure respec由size和mode组成。其中size是大小,mode是模式。mode一共有三种:

    • UNSPECIFIED:父控件没有对子控件施加任何约束。它可以是任意大小。这个一般是系统内部使用,我们很少使用。
    • EXACTLY:父控件为子控件的高宽确定了固定的值。这个相当于是在布局中填入固定的值或是match_parent。
    • AT_MOST:子控件的高宽可以为任意值,但是必须小于父控件的高宽。这个相当于在布局中填入wrap_content。

    我们对MeasureSpec做了一个简单的介绍,下面我们来看看MeasureSpec的值是如何生成的。首先一个MeasureSpec是由父控件的MeasureSpec和子控件的LayoutParams一起生成的,所以有的说这个是父控件对子控件的约束是不完全正确的。那么有人肯定就会有一个疑问,那根布局如何生成这个MeasureSpec呢。下面我们就开看看根布局是如何生成MeasureSpec的

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    
    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
       + mWidth + " measuredWidth=" + host.getMeasuredWidth()
       + " mHeight=" + mHeight
       + " measuredHeight=" + host.getMeasuredHeight()
       + " coveredInsetsChanged=" + contentInsetsChanged);
    

    这里系统调用了getRootMeasureSpec方法来获取MeasureSpec。下面我们来看看getRootMeasureSpec的内部实现

    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
        private static int getRootMeasureSpec(int windowSize, int rootDimension) {
            int measureSpec;
            switch (rootDimension) {
    
            case ViewGroup.LayoutParams.MATCH_PARENT:
                // Window can't resize. Force root view to be windowSize.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                // Window can resize. Set max size for root view.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                break;
            default:
                // Window wants to be an exact size. Force root view to be that size.
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                break;
            }
            return measureSpec;
        }
    

    这里根据传进来的windowSize和rootDimension来生成对应的MeasureSpec。这里的windowSize一般为屏幕的高宽。

    在知道了根布局是如何获取的MeasureSpec之后,我们来看看ViewGroup在测量子View时是如何生成相应的MeasureSpec的。因为ViewGroup中并没有具体实现onMeasure方法,所以我们来用LinearLayout来看看

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
    

    LinearLayout的onMeasure方法分了垂直和水平两种情况,我们就看看垂直情况。

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            ...
            final int count = getVirtualChildCount();
    
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            ...
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }
    
                if (child.getVisibility() == View.GONE) {
                   i += getChildrenSkipCount(child, i);
                   continue;
                }
               ...
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                totalWeight += lp.weight;
    
                final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
                if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                    // Optimization: don't bother measuring children who are only
                    // laid out using excess space. These views will get measured
                    // later if we have space to distribute.
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                    skippedMeasure = true;
                } else {
                    ...
                    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    //这里是测量子View高宽的方法
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
                    ...
                }
            ...
            }
            ...
        }
    

    这里调用了measureChildBeforeLayout方法来测量子View的高宽,我们进入方法,看看是如何生成子View需要的MeasureSpec的

        void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
                int totalHeight) {
            measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
        }
    
        protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec
              , int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //这里是获取子View宽的MeasureSpec,传入的参数以此为父控件MeasureSpec,已使用宽(为0)加上Margin和Padding的值,
           //子控件的LayoutParams宽的模式。
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
          //这里是获取子View宽的MeasureSpec,参数同上。
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
          //调用子View的measure方法。
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    在代码中我加入了注释,我们可以看到最终是调用getChildMeasureSpec方法来获取子View需要的MeasureSpec。下面就来看看getChildMeasureSpec方法的内部实现

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            //根据方法getMode和getSize分别获取父控件的模式和大小。
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
           //计算出合适的大小。
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            //如果父控件的布局需求模式为EXACTLY。
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    //如果子View有指定的值,那么就直接等于这个值。
                    resultSize = childDimension;
                    //子View模式为EXACTLY。
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // 如果子控件大小设置为MATCH_PARENT(childDimension = -1),那么就让他等于父控件的大小。
                    resultSize = size;
                    //子View模式为EXACTLY。
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                     // 如果子控件大小设置为WRAP_CONTENT(childDimension = -2),那么就让他小于等于父控件的大小。
                    resultSize = size;
                    //子View模式为AT_MOST。
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // 如果父控件的模式为AT_MOST。
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    //如果子View有固定大小,那么就等于固定的大小。
                    resultSize = childDimension;
                    //子View模式为EXACTLY。
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    //子View为MATCH_PARENT,那么就应该等于父控件的大小,所以将值设置成size。
                    resultSize = size;
                    //子View模式为AT_MOST。
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    //子View的大小为小于等于父控件大小,所以将值设置成size。
                    resultSize = size;
                    //子View模式为AT_MOST。
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // UNSPECIFIED多为系统使用,这里我们就不具体分析了,代码比较简单,有兴趣的同学可以自己看看。
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    

    代码中加入了注释,大家可以仔细的看看,有问题的话可以留言相互交流下。


    measure

    介绍完MeasureSpec过后,我们从ViewGroup来开始分析是如何测量控件高宽的。因为各个布局的排列方式不一样,所以ViewGroup没有对测量方法有具体的实现,所以我们结合上面讲过的LinearLayout来详细分析下它是如何对子View进行高宽测量的。因为LinearLayout最终也是继承View的,它这里只实现了View的onMeasure方法,所以我们直接看他的onMeasure方法。

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
    

    我们可以看到方法中是分垂直排列和水平排列来分别测量的,我们就看垂直排列就好。水平排列大家有兴趣的可以自己去看看。

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            mTotalLength = 0;
            int maxWidth = 0;
            int childState = 0;
            int alternativeMaxWidth = 0;
            int weightedMaxWidth = 0;
            boolean allFillParent = true;
            float totalWeight = 0;
            //获取子View数量
            final int count = getVirtualChildCount();
            //获取父控件布局高宽的规则模式
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
            boolean matchWidth = false;
            boolean skippedMeasure = false;
    
            final int baselineChildIndex = mBaselineAlignedChildIndex;
            final boolean useLargestChild = mUseLargestChild;
    
            int largestChildHeight = Integer.MIN_VALUE;
            int consumedExcessSpace = 0;
    
            int nonSkippedChildCount = 0;
    
            //开始循环测量子View的高宽
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    //如果子View为空,总高度加0
                    mTotalLength += measureNullChild(i);
                    continue;
                }
    
                if (child.getVisibility() == View.GONE) {
                   //如果子View为不可见,跳过
                   i += getChildrenSkipCount(child, i);
                   continue;
                }
    
                nonSkippedChildCount++;
                if (hasDividerBeforeChildAt(i)) {
                    mTotalLength += mDividerHeight;
                }
                //获取子View的LayoutParams
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //记录子View是否有设置weight属性
                totalWeight += lp.weight;
    
                final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
    
                if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                    //如果父控件布局模式为EXACTLY ,并且子View有设置weight 属性。先记录子View的topMargin和bottomMargin值。
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                    skippedMeasure = true;
                } else {
                    if (useExcessSpace) {
                        //如果父控件布局不为EXACTLY ,先设置子View的height为 WRAP_CONTENT来测量子View高宽。
                        lp.height = LayoutParams.WRAP_CONTENT;
                    }
    
                    //测量子View的高宽
                    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
                    //获取子View测量后的高度
                    final int childHeight = child.getMeasuredHeight();
                    if (useExcessSpace) {
                        //如果子View的weight >0,并且height原来值为0,这里把height 改为0;
                        lp.height = 0;
                        consumedExcessSpace += childHeight;
                    }
                    //累加高度
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                           lp.bottomMargin + getNextLocationOffset(child));
    
                    if (useLargestChild) {
                        largestChildHeight = Math.max(childHeight, largestChildHeight);
                    }
                }
    
                ...
    
                boolean matchWidthLocally = false;
                if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                    //记录一下父控件布局规则不为EXACTLY 而子View的width为 MATCH_PARENT的情况
                    matchWidth = true;
                    matchWidthLocally = true;
                }
                //计算子View宽度
                final int margin = lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                //记录是否所有子View宽度都设置为MATCH_PARENT
                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
                if (lp.weight > 0) {
                    /*
                     * Widths of weighted Views are bogus if we end up
                     * remeasuring, so keep them separate.
                     */
                    weightedMaxWidth = Math.max(weightedMaxWidth,
                            matchWidthLocally ? margin : measuredWidth);
                } else {
                    alternativeMaxWidth = Math.max(alternativeMaxWidth,
                            matchWidthLocally ? margin : measuredWidth);
                }
    
                i += getChildrenSkipCount(child, i);
            }
            ...
            //总高度加入padding的值
            mTotalLength += mPaddingTop + mPaddingBottom;
    
            int heightSize = mTotalLength;
    
            //和背景高度对比
            heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
    
            //测量高度和MeasureSpec中的高度对比
            int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
            heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
            //计算剩余高度
            int remainingExcess = heightSize - mTotalLength
                    + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
            if (skippedMeasure
                    || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
                float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
                //如果前面有父控件布局模式为EXACTLY,并且子View有设置weight属性和height为0时会进入这个判断,重新测量子View高宽。
                mTotalLength = 0;
    
                for (int i = 0; i < count; ++i) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }
                    //根据剩余高度来测量有weight属性的子View高度
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    final float childWeight = lp.weight;
                    if (childWeight > 0) {
                        final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                        remainingExcess -= share;
                        remainingWeightSum -= childWeight;
    
                        final int childHeight;
                        if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                            childHeight = largestChildHeight;
                        } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                                || heightMode == MeasureSpec.EXACTLY)) {
                            // This child needs to be laid out from scratch using
                            // only its share of excess space.
                            childHeight = share;
                        } else {
                            // This child had some intrinsic height to which we
                            // need to add its share of excess space.
                            childHeight = child.getMeasuredHeight() + share;
                        }
    
                        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.max(0, childHeight), MeasureSpec.EXACTLY);
                        final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                                lp.width);
                        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                        // Child may now not fit in vertical dimension.
                        childState = combineMeasuredStates(childState, child.getMeasuredState()
                                & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                    }
                    //计算子View宽度
                    final int margin =  lp.leftMargin + lp.rightMargin;
                    final int measuredWidth = child.getMeasuredWidth() + margin;
                    maxWidth = Math.max(maxWidth, measuredWidth);
    
                    boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                            lp.width == LayoutParams.MATCH_PARENT;
    
                    alternativeMaxWidth = Math.max(alternativeMaxWidth,
                            matchWidthLocally ? margin : measuredWidth);
                    //记录是否所有子View宽度都设置为MATCH_PARENT
                    allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
                    //累加所有子View高度
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                            lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
                }
    
                //总高度加入padding值
                mTotalLength += mPaddingTop + mPaddingBottom;
                // TODO: Should we recompute the heightSpec based on the new total length?
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth);
                ...
            }
    
            if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
                maxWidth = alternativeMaxWidth;
            }
            //宽度加入padding值
            maxWidth += mPaddingLeft + mPaddingRight;
    
            //对比宽度和背景宽度
            maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
            //设置LinearLayout的高宽
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    heightSizeAndState);
    
            if (matchWidth) {
                forceUniformWidth(count, heightMeasureSpec);
            }
        }
    

    代码中加入了注释,我省略掉了不常用的代码。流程比较复杂,大家可以慢慢的看几次,加深理解。


    在看过LinearLayout的onMeasure方法过后,我们来看看View的是如何对自己进行测量的。

        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            ...
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            ...
        }
    

    View的measure方法中我们可以看到调用了onMeasure方法

        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //调用setMeasuredDimension方法来设置View的高宽
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
        protected int getSuggestedMinimumWidth() {
            //对比宽和View背景的大小
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }
        //根据传入的measureSpec来获取高宽的值
        public static int getDefaultSize(int size, int measureSpec) {
            int result = size;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            }
            return result;
        }
    

    View的测量代码中加入了简单的注释,有问题的话可以留言讨论。


    layout

    measure分析完成过后,我们来分析一下layout的流程。
    Layout的作用是ViewGroup用来确定子View的位置,当ViewGroup的位置确定过后,会在Onlayout方法中遍历所有子View并调用其layout方法来确定子View的位置。layout方法是确定View本身的位置,下面我们来看看View的layout方法

        public void layout(int l, int t, int r, int b) {
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
    
            //先调用setFrame方法,将l,t,r,b四个值传入,确定View在父控件中的位置。
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                //调用OnLayout方法来确定子View的位置
                onLayout(changed, l, t, r, b);
    
                if (shouldDrawRoundScrollbar()) {
                    if(mRoundScrollbarRenderer == null) {
                        mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                    }
                } else {
                    mRoundScrollbarRenderer = null;
                }
    
                mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnLayoutChangeListeners != null) {
                    ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                    int numListeners = listenersCopy.size();
                    for (int i = 0; i < numListeners; ++i) {
                        listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                    }
                }
            }
        ...
        }
    

    在代码中加入了关键方法的注释,其他代码我们先忽略,在确定自己位置之后,会去确定子View的位置,那么我们来看看OnLayout的实现

        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    

    我们看到OnLayout是一个空实现,它和onMeasure方法一样,不同的布局会有不同的实现,所以我们还是在LinearLayout中来分析下是如何实现OnLayout方法的

        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (mOrientation == VERTICAL) {
                layoutVertical(l, t, r, b);
            } else {
                layoutHorizontal(l, t, r, b);
            }
        }
    

    因为LinearLayout的特性,这里分为水平和垂直两种,我们来看看垂直的layoutVertical

        void layoutVertical(int left, int top, int right, int bottom) {
            ...
            final int count = getVirtualChildCount();
            ...
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    //如果child为null,那么childTop加0
                    childTop += measureNullChild(i);
                } else if (child.getVisibility() != GONE) {
                    //获取child的宽和高
                    final int childWidth = child.getMeasuredWidth();
                    final int childHeight = child.getMeasuredHeight();
                   ...
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
    
                    childTop += lp.topMargin;
                    //调用setChildFrame方法,传入子View距离父控件左边和顶部的距离,再传入子View的高宽
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    //childTop 累加子View高度,使下一个子View排列在改View下方刚好符合LinearLayout垂直排列的特性
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    

    代码中主要是调用setChildFrame方法来确定子View的位置,我们进入setChildFrame方法看看

        private void setChildFrame(View child, int left, int top, int width, int height) {
            child.layout(left, top, left + width, top + height);
        }
    

    我们看到在方法中调用了子View自己的layout方法来确定自己的位置,这下layout方法也就明了了。


    draw

    最后我们来看看View的draw方法,调用这个方法过后View的绘制也将完成。
    draw的流程相对来说比较简单,主要是分了如下几个步骤:
    1.绘制背景
    2.绘制自己
    3.绘制children
    3.绘制装饰

    下面我们来看看draw的代码

        public void draw(Canvas canvas) {
            final int privateFlags = mPrivateFlags;
            final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
            mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    
            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
    
            // 1.绘制背景
            int saveCount;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
            // skip step 2 & 5 if possible (common case)
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
            if (!verticalEdges && !horizontalEdges) {
                // 2.绘制自己
                if (!dirtyOpaque) onDraw(canvas);
    
                //3.绘制children
                dispatchDraw(canvas);
    
                drawAutofilledHighlight(canvas);
    
                // Overlay is part of the content and draws beneath Foreground
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().dispatchDraw(canvas);
                }
    
                // 4.绘制装饰
                onDrawForeground(canvas);
    
                // Step 7, draw the default focus highlight
                drawDefaultFocusHighlight(canvas);
    
                if (debugDraw()) {
                    debugDrawFocus(canvas);
                }
    
                // we're done...
                return;
            }
            ...
        }
    

    代码中有简单的解释,在绘制子View是调用的是dispatchDraw方法,在ViewGroup中遍历所有子View,并最终调用子View的draw方法,这样绘制就一层层的传递下去。


    到此为止整个View的绘制流程就分析完成了,其中没有很细节的去分析每一句代码,个人认为分析源码主要是为了了解Android机制的一些流程。

    文中有错误的地方欢迎大家提出,我会及时修改。

    谢谢观看!!!

    相关文章

      网友评论

          本文标题:简单聊聊Android View绘制流程

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