【参考链接】

 

我们在写布局文件的时候,是在设置LayoutParams,共有3种模式:

match_parentwrap_contentdimens

3种模式表达了我们希望给自己的View设置的宽度高度

 

但是,系统在进行测量measure()的时候,传进来的数据类型并不是LayoutParams,而是MeasureSpec

1.      这个MeasureSpec是什么

MeasureSpec是一个32位的int值,其中高2位代表Mode,低30位代表SizeMode主要有3类:EXACTLYAT_MOSTUNSPECIFIED。可以通过如下方式解析一个MeasureSpec

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
       int
widthSize =MeasureSpec.getSize(widthMeasureSpec);

       int
heightMode=MeasureSpec.getMode(heightMeasureSpec);
       int
heightSize=MeasureSpec.getSize(heightMeasureSpec);

//        //可以通过如下方式来创建一个MeasureSpec
//       int spec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);

 

2.      MeasureSpec是怎么来的

对于DecorView

MeasureSpecmodeEXACTLYsize是窗口宽高

\frameworks\base\core\java\android\view\ViewRoot.java

private int getRootMeasureSpec(int windowSize, int rootDimension) {
   
int measureSpec;
    switch
(rootDimension) {

   
case ViewGroup.LayoutParams.MATCH_PARENT:
       
// Window can'tresize. Force root view to be windowSize.
       
measureSpec =MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
       break;
    case
ViewGroup.LayoutParams.WRAP_CONTENT:
       
// Window canresize. Set max size for root view.
       
measureSpec =MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
       break;
    default
:
       
// Window wants tobe an exact size. Force root view to be that size.
       
measureSpec =MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
       break;
   
}
   
return measureSpec;
}

 

对于子View

系统把我们在布局文件中设置的LayoutParams和父ViewMeasureSpec进行计算,生成新的MeasureSpec,传递给自己的measure()方法

\frameworks\base\core\java\android\view\ViewGroup.java

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
   
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) {
   
// Parent has imposed an exact size onus
   
case MeasureSpec.EXACTLY:
       
if (childDimension >= 0) {
           resultSize = childDimension
;
           
resultMode =MeasureSpec.EXACTLY;
       
} else if (childDimension ==LayoutParams.MATCH_PARENT) {
           
// Child wants tobe our size. So be it.
           
resultSize = size;
           
resultMode =MeasureSpec.EXACTLY;
       
} else if (childDimension ==LayoutParams.WRAP_CONTENT) {
           
// Child wants todetermine its own size. It can't be
           // bigger than us.
           
resultSize = size;
           
resultMode =MeasureSpec.AT_MOST;
       
}
       
break;

   
// Parent has imposed a maximum size onus
   
case MeasureSpec.AT_MOST:
        
if (childDimension >= 0) {
           
// Child wants aspecific size... so be it
           
resultSize =childDimension;
           
resultMode =MeasureSpec.EXACTLY;
       
} else if (childDimension ==LayoutParams.MATCH_PARENT) {
           
// Child wants tobe 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 ownsize. It can't be
           // bigger than us.
           
resultSize = size;
           
resultMode =MeasureSpec.AT_MOST;
       
}
       
break;

   
// Parent asked to see how big we wantto be
   
case MeasureSpec.UNSPECIFIED:
       
if (childDimension >= 0) {
           
// Child wants aspecific size... let him have it
           
resultSize =childDimension;
           
resultMode =MeasureSpec.EXACTLY;
       
} else if (childDimension == LayoutParams.MATCH_PARENT){
           
// Child wants tobe our size... find out how big it should
           // be
           
resultSize = 0;
           
resultMode =MeasureSpec.UNSPECIFIED;
       
} else if (childDimension ==LayoutParams.WRAP_CONTENT) {
           
// Child wants todetermine its own size.... find out how
           // big it should be
           
resultSize = 0;
           
resultMode =MeasureSpec.UNSPECIFIED;
       
}
       
break;
   
}
   
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

 

转换关系可以用下表表示

 View[0] measure


 

match_parent

wrap_content

childDimen

AT_MOST

parentSize

AT_MOST

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

EXACTLY

parentSize

EXACTLY

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

 

3.      所以系统并没有直接采用我们的LayoutParams,而是转换成了MeasureSpec,这个转换造成了什么影响呢?

从上面DecorView可以知道,最上面的MeasureSpecmodeEXACTLY,所以,在MeasureSpec向下传递的过程中

如果下面的ViewLayoutParamsmatch_parent,那么其MeasureSpecmode就会保持为EXACTLY,并且sizeparentSize

 

match_parent

wrap_content

childDimen

AT_MOST

parentSize

AT_MOST

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

EXACTLY

parentSize

EXACTLY

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

 

但是,如果某个ViewLayoutParamswrap_content,则其MeasureSpecmode就会变成AT_MOST。并且即使下下面的ViewLayoutParamsmatch_parent

MeasureSpec也只能是AT_MOST了。

 

match_parent

wrap_content

childDimen

AT_MOST

parentSize

AT_MOST

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

EXACTLY

parentSize

EXACTLY

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

 

 

match_parent

wrap_content

childDimen

AT_MOST

parentSize

AT_MOST

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

EXACTLY

parentSize

EXACTLY

parentSize

AT_MOST

parentSize

EXACTLY

childDimen

 

所以,虽然LayoutParams表达了我们的期望,但是系统并没有完全满足我们的期望,还是使用转换成的MeasureSpec来测量宽高。

 

4.      那么我们要如何使用这个MeasureSpec呢?

从上表可知,

如果LayoutParamsmatch_parent,则MeasureSpecsize就是parentSize。如果LayoutParamschildDimen,则MeasureSpecsize就是ChildDimen。这两个值是符合跟父View一样大小/指定大小的语义的。

并且此时MeasureSpecmode都是EXACTLY,所以当MeasureSpecmodeEXACTLY时,我们可以直接使用MeasureSpecsize作为measuredDimension

 

而当LayoutParamswrap_content时,MeasureSpecmodeAT_MOST,并且valueparentSize。这个时候我们可以根据自己的测量/布局想法,计算出长宽,设置成measuredDimension(实际是取跟parentSize比较的较小值)。

 

总结一下就是,在onMeasure()

如果MeasureSpecmodeEXACTLY,则可以直接使用MeasureSpecsize作为measuredDimension,否则再根据自己的需要计算长宽。

 

resolveSize()

按照上面的思路,在自定义View的时候,我们只需要计算AT_MOST时的长宽

所以系统为我们提供了resolveSize()方法,将AT_MOST时的长宽传进去就可以得到所有情况时的长宽,这可以方便我们少些很多代码。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

       //
假设AT_MOST时的长宽想要为300300//实际需要根据自己的测量/布局规则来计算
       
setMeasuredDimension(
               resolveSize(
300, widthMeasureSpec),
               
resolveSize(300, heightMeasureSpec)
        )
;

   
}

 

\frameworks\base\core\java\android\view\View.java

public static int resolveSize(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://将计算大小和parentSize进行比较,取较小值
       
result =Math.min(size, specSize);
       break;
    case
MeasureSpec.EXACTLY:
       result = specSize
;
       break;
   
}
   
return result;
}

 

measureChildren()

在自定义ViewGroup的时候,重写onMeasure()时,还需要先对子View进行measure。系统也为ViewGroup提供了measureChildren()等方法。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

 

此外,并不是在performTraversal()measure()就只执行一次

都是看各自的实现的,比如说LinearLayoutmeasure()就会进行两次。

View[0] measure


相关文章: