Android View的绘制之 从源码了解measure的过程。

Android View的绘制之measure过程初解。


前言

每个Android开发者,开发到一定程度后,都不可避免的涉及到各种自定义控件,各种性能优化的问题。
而学习和了解View的绘制过程,会对你的控件开发,性能优化等东西有很大的帮助。今天写这边博客,就是希望带大家从View和ViewGroup的基本的源码中,了解View的绘制过程。整个View的绘制涉及测量(measure),布局(layout),绘制(draw)等一系列过程。layout和draw相对比较简单,我后面的文章会继续跟进,今天这里我们来交流一下测量(measure)的过程。下面附上view绘制的流程图:

Android View的绘制之 从源码了解measure的过程。_第1张图片


measure的目的

首先你应该要知道measure是用来做神马的。简单的来讲measure是为了对view的mMeasuredWidth和mMeasuredHeight这两个变量进行赋值,因此只要这两个变量赋值完成了,view的measure就完成了。目的就是那么简单。

measure的流程

要知道流程,我们必须知道源码中涉及的关键方法。对应的view以及viewGroup中关键方法如下。按调用顺序排列,为了篇幅清洁暂时没写入参。

  • View中:

    • public final void measue();
    • protected void onMeasure()
    • protected final void setMeasuredDimension()
  • ViewGroup中:

    • protected void measureChildren()
    • protected void measureChild()
    • protected void measureChildWithMargins()
    • public static int getChildMeasureSpec()

我们来依次的研究这些方法。先从关键的view开始。因为赋值是是在view中进行。viewGroup中其实是遍历ViewTree中的所有子view,再调用view中的measure方法实现赋值。

预备知识

在讲解之前给大家复习一下MeasureSpec(测量细则).
请各位还不是特别了解的看官移步:MeasureSpec介绍

View

进入正题,知道这些,让我们一起开始读源码。

第一个方法measure():

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //若widthMeasureSpec或heightMeasureSpec就调用onMeasure去赋值。
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            // 关键的调用方法。
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }

Tips:mPrivateFlags相关的方法是一套固有逻辑不影响用户measure的过程,这里不展开讨论。大家略过这些方法就好。(其实是自己也不太清楚哈哈~~T.T)
看方法,measure其实非常简单,在发现widthMeasureSpec或 heightMeasureSpec发生变化时就去调用onMeasure()方法。大家可以发现measure方法定义成了final类型,设计者在设计之初就不希望这个方法被开发者修改。保证了android初始化view的原理是一致的。

第二个方法 onMeasure();

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

Tips:这是view的三个measure方法中唯一一个不是final的方法。因此我们在自定义空间中最多控制的也是这个方法。将你需要的widthMeasureSpec和heightMeasureSpec直接传过来就能调用setMeasuredDimension()方法进行赋值了。

第三种方法 setMeasuredDimension()

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

Tips:不难看出,mMeasuredWidth和mMeasuredHeight已经赋值成功,证明view的measure完成。是不是非常的简单。下面来了解一下ViewGroup的分发方法。


ViewGroup

方法一measureChildren()

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

遍历所有的viewTree下所有个子view.若子View未GONE就去measureChild(),测量对应的view.

方法二measureChild()

        protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        //获取对应的子view测量出来的宽高。
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        //调用子view的measure方法对mMeasureWidth和mMeasureHeight进行赋值。完成测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

方法三measureChildWithMargins()

 //和方法二measureChild()完全一样的逻辑。只是宽高计算的时候加入了margin.
 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

和方法而逻辑雷同。关键是调用方法四的时候,传入的第二个参数int padding的数值加入了margin.

方法4getChildMeasureSpec()

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //viewGroup的spec
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        //父控件的大小减去padding得到子View的大小。
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        //根据Mode以及childDimension获取子view对应的MeasureSpec。
        switch (specMode) {
        case MeasureSpec.EXACTLY:
        //若childDimension有设置具体值,布局具体到具体值。
        //若childDimension = match_parent。size赋予测出的大小,mode设置为EXACTLY。
        //若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                // LayoutParams.MATCH_PARENT的大小
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
        //若childDimension有设置具体值,布局具体到具体值。
        //若childDimension = match_parent。size赋予测出的大小,mode设置为AT_MOST。
        //若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
        //若childDimension有设置具体值,布局具体到具体值。
        //若childDimension = match_parent。size设置为0,mode设置为UNSPECIFIED。
        //若childDimension = wrap_content。size设置为0,mode设置为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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }

        //返回一个测量好的 MeasureSpec,包含resultSize以及resultMode。
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

Tips: getChildMeasureSpec是测量的关键步奏。他根据childDimension的情况,把测量出的大小和定好Mode。拼接好,并返回给子view去measure。我在代码中进行了必要的注解,大家可以打开源码,多读几遍加深影响。


后记

这里就是view以及viewGroup中measure最主要的方法介绍。了解了这些,当你再读LinearLayout等布局的measure时候就能水到渠成了。希望这篇文章能对大家能有所帮助。

我的下篇博客会针对view绘制的另两个过程 layout以及draw进行分享,
至于LinearLayout具体的计算绘制过程当我有一定心得后也会写博文和大家分享。

楼猪是工作一年的小虾。各位大神们发现问题希望能及时的拍砖。我会学马上改正,虚心学习的~

你可能感兴趣的:(view的绘制,android开发,androidSDK,android,view的绘制,measure,layout,源码)