Android View绘制2 onMeasure()流程

一 理解MeasureSpec

对于测量,我们来说几个知识点,了解这几个知识点,之后的实例分析才会更容易理解。

MeasureSpec 的理解

对于 View 的测量,肯定会和 MeasureSpec 接触,MeasureSpec 是由两个单词组成的,翻译过来就是 “测量规格” 或者 “测量参数”,很多博客包括官方文档对他的说明基本都是 “一个 MeasureSpec 封装了从父容器传递给子容器的布局要求”,这个 MeasureSpec 封装的是父容器传递给子容器的布局要求,而不是父容器对子容器的布局要求,“传递” 两个字很重要,更精确的说法应该是:这个 MeasureSpec 是由父 View 的 MeasureSpec 和子 View 的 LayoutParams 通过简单的计算得出一个针对子 View 的测量要求,这个测量要求就是 MeasureSpec。

我们知道一个 MeasureSpec 是一个大小和模式的组合值,MeasureSpec 中的值是一个整型(32位)将 size 和 mode 打包成一个 Int 型,其中高两位是 mode,后面 30 位存的是 size,是为了减少对象的分配开支。MeasureSpec 类似于下图,只不过这边用的是十进制的数,而MeasureSpec 是二进制存储的。
在这里插入图片描述

注:-1 代表的是 EXACTLY,-2 是 AT_MOST

MeasureSpec 一共有三种模式:

  • UNSPECIFIED:父容器对于子容器没有任何限制,子容器想要多大就多大
  • EXACTLY:父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间
  • AT_MOST:子容器可以是声明大小内的任意大小

很多文章都会把这三个模式说成这样,当然其实包括官方文档也是这样表达的,但是这样并不好理解。特别是如果把这三种模式又和 MATCH_PARENT 和 WRAP_CONTENT 联系到一起,很多人就懵逼了。

如果从代码上来看 view.measure(int widthMeasureSpec, int heightMeasureSpec) 的两个 MeasureSpec 是父类传递过来的,但并不是完全是父 View 的要求,而是父 View 的 MeasureSpec 和子 View 自己的 LayoutParams 共同决定的,而子 View 的 LayoutParams 其实就是我们在书写 xml 的时候设置的 layout_width 和 layout_height 转化而来的。我们先来看代码会清晰一些:

父 View 的 measure 的过程会先测量子 View,等子 View 测量结果出来后,再来测量自己,下面的 measureChildWithMargins 就是用来测量某个子 View 的,我们来分析是怎样测量的,具体看注释:

ViewGroup.java

/**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
    int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

    // 子View的LayoutParams,你在xml的layout_width和layout_height,
    // layout_xxx的值最后都会封装到这个LayoutParams。
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

    //根据父View的测量规格和父View自己的Padding,还有子View的Margin和已经用掉的空间大小(widthUsed)
    //就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。 
    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);  

    //通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,
    //然后父容器传递给子容器的,然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)
    //去测量自己,如果子View是ViewGroup 那还会递归往下测量。
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

// spec: 表示父View的MeasureSpec 
// padding: 父View的Padding+子View的Margin,父View的大小减去这些边距,才能精确算出
//          子View的MeasureSpec的size
// childDimension: 表示该子View内部LayoutParams属性的值(lp.width或者lp.height)
//                 可以是wrap_content、match_parent、一个精确指(an exactly size)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //获得父View的大小  

    //父View的大小-(自己的Padding+子View的Margin),得到值是父View的可用大小。
    int size = Math.max(0, specSize - padding);   
  
    int resultSize = 0;    //初始化值,最后通过这个两个值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值,最后通过这个两个值生成子View的MeasureSpec
  
    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1、父View是EXACTLY
    case MeasureSpec.EXACTLY:   
        //1.1、子View的width或height是个精确值 (an exactly size)  
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size为精确值  
            resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
        }   
        //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT   
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size. So be it.  
            resultSize = size;                   //size为父视图大小  
            resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
        }   
        //1.3、子View的width或height为 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                   //size为父视图大小  
            resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。  
        }  
        break;  
  
    // Parent has imposed a maximum size on us  
    //2、父View是AT_MOST    
    case MeasureSpec.AT_MOST:  
        //2.1、子View的width或height是个精确值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... so be it  
            resultSize = childDimension;        //size为精确值  
            resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。  
        }  
        //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
        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;                  //size为父视图大小  
            resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
        }  
        //2.3、子View的width或height为 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                  //size为父视图大小  
            resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
        }  
        break;  
  
    // Parent asked to see how big we want to be  
    //3、父View是UNSPECIFIED的
    case MeasureSpec.UNSPECIFIED:  
        //3.1、子View的width或height是个精确值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... let him have it  
            resultSize = childDimension;        //size为精确值  
            resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY  
        }  
        //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
        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;  //mode为 UNSPECIFIED  
        }   
        //3.3、子View的width或height为 WRAP_CONTENT  
        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;  //mode为 UNSPECIFIED  
        }  
        break;  
    }  
    //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}    

上面的代码有点多,但是仔细看一些注释,其实计算原理很简单:

1.如果我们把 layout_width 或者 layout_height 的值都写死,那么上述的测量完全就不需要了,之所以要上面的测量,是因为 match_parent 就是充满父容器,wrap_content 就是自己多大就多大, 我们写代码的时候特别爽,我们编码方便的时候,google 就需要帮我们计算你 match_parent 的时候是多大,wrap_content 的是多大,这个计算过程,就是计算出来的父 View 的 MeasureSpec 不断往子 View 传递,结合子 View 的 LayoutParams 一起再计算出子 View 的 MeasureSpec,然后继续传给子 View,不断计算出每个 View 的 MeasureSpec,子 View 有了 MeasureSpec 才能更测量自己和自己的子 View。

2.上述代码如果这么来理解就简单了

父View的MeasureSpec是EXACTLY

如果父 View 的 MeasureSpec 是 EXACTLY,说明父 View 的大小是确切的,(确切的意思很好理解,如果一个 View 的 MeasureSpec 是EXACTLY,那么它的 size 是多大,最后展示到屏幕就一定是那么大)。

  • 如果子 View 的 layout_xxxx 是 MATCH_PARENT,父 View 的大小是确切,子 View 的大小又 MATCH_PARENT,那么子 View 的大小肯定是确切的,而且大小值就是父 View 的 size。所以子 View 的 size = 父 View 的 size,mode = EXACTLY
  • 如果子 View 的 layout_xxxx 是 WRAP_CONTENT,也就是子 View 的大小是根据自己的 content 来决定的,但是子 View 毕竟是子 View,大小不能超过父 View 的大小,但是子 View 是 WRAP_CONTENT,我们还不知道具体子 View 的大小是多少,要等到 child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子 View 自己 content 的大小。通过上述描述,子 View MeasureSpec mode 应该是 AT_MOST,而 size 暂定父 View 的 size,表示的意思就是子 View 的大小还没有确切的值,子 View 的大小最大为父 View 的大小,不能超过父 View 的大小(这就是 AT_MOST 的意思),然后这个 MeasureSpec 做为子 View measure 方法的参数,做为子 View 大小的约束或者说是要求,有了这个 MeasureSpec 子 View 再实现自己的测量。
  • 如果如果子 View 的 layout_xxxx 是确定的值,那么就更简单了,不管你父 View 的 mode 和 size 是什么,我都写死了,那么控件最后展示就是就是固定的值,不管我的父 View 有多大,也不管我自己的 content 有多大,反正我就是这么大,所以这种情况 MeasureSpec 的 mode = EXACTLY 大小 size = 你在 layout_xxxx 填的那个值。

父View的MeasureSpec是AT_MOST

如果父 View 的 MeasureSpec 是 AT_MOST,说明父 View 的大小是不确定,最大的大小是 MeasureSpec 的 size 值,不能超过这个值。

  • 如果子 View 的 layout_xxxx 是 MATCH_PARENT,父 View 的大小是不确定(只知道最大只能多大),子 View 的大小 MATCH_PARENT,那么子 View 即使充满父容器,它的大小也是不确定的,父 View 自己都确定不了自己的大小,你 MATCH_PARENT 的大小肯定也不能确定,所以子 View 的 mode = AT_MOST,size = 父 View 的 size,也就是你在布局虽然写的是 MATCH_PARENT,但是由于你的父容器自己的大小不确定,导致子 View 的大小也不确定,只知道最大就是父 View 的大小。

  • 如果子 View 的 layout_xxxx 是 WRAP_CONTENT,父 View 的大小是不确定,子 View 又是 WRAP_CONTENT,那么在子 View 的 Content 没算出大小之前,子 View 的大小最大就是父 View 的大小,所以子 View MeasureSpec mode 的就是AT_MOST,而 size 暂定父 View 的 size。

  • 如果如果子 View 的 layout_xxxx 是确定的值,同上,写多少就是多少,改变不了的。

父View的MeasureSpec是UNSPECIFIED

如果父 View 的 MeasureSpec 是 UNSPECIFIED,表示没有任何束缚和约束,不像 AT_MOST 表示最大只能多大,不也像 EXACTLY 表示父 View 确定的大小,子 View 可以得到任意想要的大小,不受约束

  • 如果子 View 的 layout_xxxx 是 MATCH_PARENT,因为父 View 的 MeasureSpec 是 UNSPECIFIED,父 View 自己的大小并没有任何约束和要求,那么对于子 View 来说无论是 WRAP_CONTENT 还是 MATCH_PARENT,子 View 也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size 的值就没有任何意义了,所以一般都直接设置成 0

  • 如果子 View 的 layout_xxxx 是 WRAP_CONTENT,同上分析

  • 如果子 View 的 layout_xxxx 是确定的值,写多少就是多少,改变不了的(记住,只有设置的确切的值,那么无论怎么测量,大小都是不变的,都是你写的那个值)

到此为止,你是不是已经对 MeasureSpec 和其中的三种模式、还有 WRAP_CONTENT 和 MATCH_PARENT 有一定的了解了?

二 onMeasure()方法

打开 View 的源码,找到 measure 方法,这个方法代码不少,但是测量工作都是在 onMeasure() 做的,measure 方法是 final 的所以这个方法也不可重写,如果想自定义 View 的测量,你应该去重写 onMeasure() 方法

View.java

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

2.1 View.onMeasure

View.java

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

View 的 onMeasure 方法默认实现很简单,就是调用 setMeasuredDimension(),setMeasuredDimension() 可以简单理解为就是给 mMeasuredWidth 和 mMeasuredHeight 设值,如果这两个值确定了,那么就意味着对这个 View 的测量结束了,这个 View 的宽高已经有测量的结果出来了。如果我们想设定某个 View 的宽高,完全可以直接通过 setMeasuredDimension(100,200)来设置死它的宽高(不建议),但是 setMeasuredDimension 方法必须在 onMeasure 方法中调用,不然会抛异常。我们来看下对于 View 来说它的默认高宽是怎么获取的。

//获取的是android:minWidth属性的值或者View背景图片的大小值
protected int getSuggestedMinimumWidth() { 
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); 
} 

//@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值  
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: //表示该View的大小父视图未定,设置为默认值 
            result = size;  
            break;
        case MeasureSpec.AT_MOST:    
        case MeasureSpec.EXACTLY:        
            result = specSize;  
            break;   
    }    
    return result;
}

getDefaultSize 的第一个参数 size 等于 getSuggestedMinimumXXXX 返回的的值(建议的最小宽度和高度),而建议的最小宽度和高度都是由 View 的 Background 尺寸与通过设置 View 的 minXXX 属性共同决定的,这个 size 可以理解为 View 的默认长度,而第二个参数 measureSpec,是父 View 传给自己的 MeasureSpec,这个 MeasureSpec 是通过测量计算出来的,具体的计算测量过程前面在讲解 MeasureSpec 的时候已经讲得比较清楚了(是由父 View 的 MeasureSpec 和子 View 自己的 LayoutParams 共同决定的)只要这个测量的 mode 不是 UNSPECIFIED,那么默认的就会用这个测量的数值当做 View 的宽高。

对于 View 的默认测量很简单,大部分情况就是拿计算出来的 MeasureSpec 的 size 当做最终测量的大小。而对于其他的一些 View 的派生类,如 TextView、Button、ImageView 等,它们的 onMeasure 方法系统了都做了重写,不会这么简单直接拿 MeasureSpec 的 size 来用的,而会先去测量字符或者图片的宽高等,然后拿到 View 本身 content 这个宽高(字符宽高等),如果 MeasureSpec 是 AT_MOST,而且 View 本身 content 的宽高不超出 MeasureSpec 的 size,那么可以直接用 View 本身 content 的宽高(字符宽高等),而不是像 View.java 直接用 MeasureSpec 的 size 做为 View 的大小。

2.2 ViewGroup.Measure

ViewGroup 类并没有实现 onMeasure,我们知道测量过程其实都是在 onMeasure 方法里面做的,我们来看下 FrameLayout 的 onMeasure 方法,具体分析看注释。

FrameLayout.jaa

//FrameLayout 的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    ......
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    for (int i = 0; i < count; i++) {    
        final View child = getChildAt(i);    
        if (mMeasureAllChildren || child.getVisibility() != GONE) {   
    // 遍历自己的子View,只要不是GONE的都会参与测量,measureChildWithMargins方法在最上面
    // 的源码已经讲过了,如果忘了回头去看看,基本思想就是父View把自己的MeasureSpec 
    // 传给子View结合子View自己的LayoutParams 算出子View 的MeasureSpec,然后继续往下传,
    // 传递叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
             measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
             final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
             maxWidth = Math.max(maxWidth,
                 child.getMeasuredWidth() +  lp.leftMargin + lp.rightMargin);        
             maxHeight = Math.max(maxHeight,
                 child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);  
             ......
        }
    }
    ......
//所有的孩子测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高,
//对于FrameLayout 可能用最大的子View的大小,对于LinearLayout,可能是高度的累加,
//具体测量的原理去看看源码。总的来说,父View是等所有的子View测量结束之后,再来测量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
......
}  

三 实例分析

到目前为止,基本把 Measure 主要原理都过了一遍,接下来我们来结合实例来讲解整个 match 的过程,首先看下面的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"    
   android:id="@+id/linear"
   android:layout_width="match_parent"    
   android:layout_height="wrap_content"    
   android:layout_marginTop="50dp"    
   android:background="@android:color/holo_blue_dark"    
   android:paddingBottom="70dp"    
   android:orientation="vertical">    
   <TextView        
    android:id="@+id/text"       
    android:layout_width="match_parent"     
    android:layout_height="wrap_content"  
    android:background="@color/material_blue_grey_800"       
    android:text="TextView"        
    android:textColor="@android:color/white"        
    android:textSize="20sp" />    
   <View       
      android:id="@+id/view"       
     android:layout_width="match_parent" 
     android:layout_height="150dp"    
     android:background="@android:color/holo_green_dark" />
</LinearLayout>

上面的代码对应出来的布局是下面的一张图
在这里插入图片描述
说明:

整个图是一个 DecorView,DecorView 可以理解成整个窗口的根 View,DecorView 是一个 FrameLayout,它里边有 LinearLayout 这个子View 这个 LinearLayout 比较重要,它包含一个 title 和一个 content,title 很好理解其实就是 TitleBar 或者 ActionBar,content 就更简单了,setContentView() 方法你应该用过吧,android.R.id.content 你应该听过吧,没错就是它,content 是一个 FrameLayout,你写的页面布局通过 setContentView 加进来就成了 content 的直接子 View。

整个 View 的布局图如下:

在这里插入图片描述
这张图在下面分析 measure 方法的时候,会经常用到,主要用于了解递归的时候 view 的 measure 顺序

说明:

  • header 是个 ViewStub,用来惰性加载 ActionBar,为了便于分析整个测量过程,我们把 Theme 设成 NoActionBar,避免 ActionBar 相关的 measure 干扰整个过程,这样可以忽略掉 ActionBar 的测量。
  • 包含 Header(ActionBar)和 id/content 的那个父 View,我不知道叫什么名字好,我们姑且叫它 ViewRoot,它是垂直的 LinearLayout,放着整个页面除 statusBar 的之外所有的东西,叫它 ViewRoot 应该还 ok,一个代号而已。

既然我们知道整个 View 的 Root 是 DecorView,那么 View 的绘制是从哪里开始的呢,我们知道每个 Activity 均会创建一个 PhoneWindow 对象,是 Activity 和整个 View 系统交互的接口,每个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,对于 Activity 来说,ViewRootImpl 是连接 WindowManager 和 DecorView 的纽带,绘制的入口是由 ViewRootImpl 的 performTraversals 方法来发起 Measure,Layout,Draw 等流程的。

我们来看 ViewRootImpl 的 performTraversals 方法:

private void performTraversals() { 
    ...... 
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
    ...... 
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, mWidth, mHeight);
    ...... 
    performDraw(); 
    ......
}


private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
     if (mView == null) {
         return;
     }
     Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
     try {
         mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     } finally {
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
}


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; 
    ...... 
    } 
    return measureSpec; 
}

performMeasure 中我们看到的 mView 其实就是 DecorView,View 的绘制从 DecorView 开始, 在 mView.measure() 的时候调用 getRootMeasureSpec 获得两个 MeasureSpec 做为参数,getRootMeasureSpec 的两个参数 mWith 和 mHeight 是屏幕的宽度和高度, lp 是 WindowManager.LayoutParams,它的 lp.width 和 lp.height 的默认值是 MATCH_PARENT,所以通过 getRootMeasureSpec 生成的测量规格 MeasureSpec 的 mode 是 EXACTLY ,size 是屏幕的高宽。

因为 DecorView 是一个 FrameLayout 那么接下来会进入 FrameLayout 的 measure 方法,measure 的两个参数就是刚才调用 getRootMeasureSpec 生成的两个 MeasureSpec,DecorView 的测量开始了。

首先是 DecorView 的 MeasureSpec ,根据上面的分析 DecorView 的 MeasureSpec 是 Windows 传过来的,我们画出 DecorView 的 MeasureSpec 图:

在这里插入图片描述
注:
1、-1 代表的是 EXACTLY,-2 是 AT_MOST
2、由于屏幕的像素是 1440x2560,所以 DecorView 的 MeasureSpec 的 size 对应于这两个值

那么接下来在 FrameLayout 的 onMeasure() 方法 DecorView 开始 for 循环测量自己的子 View,测量完所有的子 View 再来测量自己,由下图可知,接下来要测量 ViewRoot 的大小

在这里插入图片描述

//FrameLayout 的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    ......
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    for (int i = 0; i < count; i++) {    
        final View child = getChildAt(i);    
        if (mMeasureAllChildren || child.getVisibility() != GONE) {   
    // 遍历自己的子View,只要不是GONE的都会参与测量,measureChildWithMargins方法在最上面
    // 的源码已经讲过了,如果忘了回头去看看,基本思想就是父View把自己的MeasureSpec 
    // 传给子View结合子View自己的LayoutParams 算出子View 的MeasureSpec,然后继续往下传,
    // 传递叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
             measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
             final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
             maxWidth = Math.max(maxWidth,
                 child.getMeasuredWidth() +  lp.leftMargin + lp.rightMargin);        
             maxHeight = Math.max(maxHeight,
                 child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);  
             ......
        }
    }
    ......
//所有的孩子测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高,
//对于FrameLayout 可能用最大的子View的大小,对于LinearLayout,可能是高度的累加,
//具体测量的原理去看看源码。总的来说,父View是等所有的子View测量结束之后,再来测量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
......
} 

DecorView 测量 ViewRoot 的时候把自己的 widthMeasureSpec 和 heightMeasureSpec 传进去了,接下来就要去看measureChildWithMargins 的源码了

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
    int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

    // 子View的LayoutParams,你在xml的layout_width和layout_height,
    // layout_xxx的值最后都会封装到这个LayoutParams。
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

    //根据父View的测量规格和父View自己的Padding,还有子View的Margin和已经用掉的空间大小(widthUsed)
    //就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。 
    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);  

    //通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,
    //然后父容器传递给子容器的,然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)
    //去测量自己,如果子View是ViewGroup 那还会递归往下测量。
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

ViewRoot 是系统的 View,它的 LayoutParams 默认都是 match_parent,根据我们文章最开始介绍的 MeasureSpec 的计算规则,ViewRoot 的 MeasureSpec mode 应该等于 EXACTLY( DecorView MeasureSpec 的 mode 是 EXACTLY,ViewRoot 的 layoutparams 是 match_parent),size 也等于 DecorView 的 size,所以 ViewRoot 的 MeasureSpec 图如下:
在这里插入图片描述
算出 ViewRoot 的 MeasureSpec 之后,开始调用 ViewRoot.measure 方法去测量 ViewRoot 的大小,然而 ViewRoot 是一个 LinearLayout ,ViewRoot.measure 最终会执行 LinearLayout 的 onMeasure 方法,LinearLayout 的onMeasure 方法又开始逐个测量它的子 View,上面的 measureChildWithMargins 方法又会被调用,那么根据 View 的层级图,接下来测量的是 header(ViewStub),由于 header 的 Gone,所以直接跳过不做测量工作,所以接下来轮到 ViewRoot 的第二个 child content(android.R.id.content),我们要算出这个 content 的 MeasureSpec,所以又要拿 ViewRoot 的 MeasureSpec 和 android.R.id.content 的 LayoutParams 做计算了,计算过程就是调用 getChildMeasureSpec 的方法。

在这里插入图片描述

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
    int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

    // 子View的LayoutParams,你在xml的layout_width和layout_height,
    // layout_xxx的值最后都会封装到这个LayoutParams。
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

    //根据父View的测量规格和父View自己的Padding,还有子View的Margin和已经用掉的空间大小(widthUsed)
    //就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。 
    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);  

    //通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,
    //然后父容器传递给子容器的,然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)
    //去测量自己,如果子View是ViewGroup 那还会递归往下测量。
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}


public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //获得父View的大小  
//父View的大小-自己的Padding+子View的Margin,得到值才是子View可能的最大值
    int size = Math.max(0, specSize - padding);
    .....
}

由上面的代码可以算出 android.R.id.content 的 MeasureSpec 的 size,由于 ViewRoot 的 mPaddingBottom = 100px (这个可能和状态栏的高度有关,我们测量最后会发现 id/statusBarBackground 的 View 的高度刚好等于 100px,ViewRoot 是系统 View,它的 Padding 我们没法改变,所以计算出来 Content(android.R.id.content)的 MeasureSpec 的高度少了100px ,它的宽高的 mode 根据算出来也是 EXACTLY(ViewRoot 是 EXACTLY 和 android.R.id.content 是 match_parent)。所以 Content(android.R.id.content)的M easureSpec 如下(高度少了 100px):

在这里插入图片描述
Content(android.R.id.content)是 FrameLayout,递归调用开始准备计算 id/linear 的 MeasureSpec,我们先给出结果:
在这里插入图片描述
图中有两个要注意的地方:

  • id/linear 的 heightMeasureSpec 的 mode = AT_MOST,因为 id/linear 的 LayoutParams 的l ayout_height=“wrap_content”
  • id/linear 的 heightMeasureSpec 的 size 少了 200px

由上面的代码

padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;
int size = Math.max(0, specSize - padding);

由于 id/linear 的 android:layout_marginTop=“50dp” 使得 lp.topMargin = 200px (本设备的 density=4,px=4*pd),在计算后 id/linear 的heightMeasureSpec 的 size 少了200px。(布局代码前面已给出,可自行查看 id/linear 控件 xml 中设置的属性)

linear.measure 接着往下算 linear 的子 View 的 MeasureSpec,看下 View 层级图,往下走应该是 id/text,接下来是计算 id/text 的 MeasureSpec,直接看图,mode = AT_MOST ,size 少了 280,大家可以自己理解原因。
在这里插入图片描述

算出 id/text 的 MeasureSpec 后,接下来 text.measure(childWidthMeasureSpec, childHeightMeasureSpec);准备测量 id/text 的高宽,这时候已经到底了,id/text 是 TextView,已经没有子类了,这时候跳到 TextView 的 onMeasure 方法了。TextView 拿着刚才计算出来的heightMeasureSpec(mode = AT_MOST,size = 1980),这个就是对 TextView 的高度和宽度的约束,进到 TextView 的 onMeasure(widthMeasureSpec,heightMeasureSpec) 方法,在 onMeasure 方法执行调试过程中,我们发现下面的代码:

在这里插入图片描述
TextView 字符的高度(也就是 TextView 的 content 高度)测出来 = 107px,107px 并没有超过 1980px (允许的最大高度),所以实际测量出来 TextView 的高度是 107px。

最终算出 id/text 的 mMeasureWidth = 1440px,mMeasureHeight = 107px。

贴一下布局代码,免得忘了具体布局。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"    
   android:id="@+id/linear"
   android:layout_width="match_parent"    
   android:layout_height="wrap_content"    
   android:layout_marginTop="50dp"    
   android:background="@android:color/holo_blue_dark"    
   android:paddingBottom="70dp"    
   android:orientation="vertical">    
   <TextView        
    android:id="@+id/text"       
    android:layout_width="match_parent"     
    android:layout_height="wrap_content"  
    android:background="@color/material_blue_grey_800"       
    android:text="TextView"        
    android:textColor="@android:color/white"        
    android:textSize="20sp" />    
   <View       
      android:id="@+id/view"       
     android:layout_width="match_parent" 
     android:layout_height="150dp"    
     android:background="@android:color/holo_green_dark" />
</LinearLayout>

TextView 的高度已经测量出来了,接下来测量 id/linear 的第二个 child(id/view),同样的原理测出 id/view 的 MeasureSpec。
在这里插入图片描述
id/view 的 MeasureSpec 计算出来后,调用 view.measure(childWidthMeasureSpec, childHeightMeasureSpec) 的测量 id/view 的高宽,之前已经说过 View measure 的默认实现是

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

最终算出 id/view 的 mMeasureWidth=1440px,mMeasureHeight=600px。

id/linear 的子 View 的高度都计算完毕了,接下来 id/linear 就通过所有子 View 的测量结果计算自己的高宽,id/linear 是 LinearLayout,所以它的高度计算简单理解就是子 View 的高度的累积+自己的 Padding。

在这里插入图片描述
最终算出 id/linear 的 mMeasureWidth=1440px,mMeasureHeight=987px。

最终算出 id/linear 出来后,id/content 就要根据它唯一的子 View id/linear 的测量结果和自己的之前算出的 MeasureSpec 一起来测量自己的结果,具体计算的逻辑去看 FrameLayout onMeasure 函数的计算过程。以此类推,接下来测量 ViewRoot,然后再测量 id/statusBarBackground,虽然不知道 id/statusBarBackground 是什么,但是调试的过程中,测出的它的高度 =100px,和 id/content 的paddingTop 刚好相等。最后测量 DecorView 的高宽,最终整个测量过程结束。所有的 View 的大小测量完毕。所有的 getMeasureWidth 和 getMeasureWidth 都已经有值了。Measure 分析到此为止,如有疑问,欢迎留言。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值