View在测量时的MeasureSpec由什么决定?
我们都知道系统要确定View的大小,首先得先获得MeasureSpec,再通过MeasureSpec来决定View的大小。
MeasureSpec(32为int值)由两部分组成:
SpecMode(高2位):测量模式。
SpecSize(低30位):某种测量模式下的规格大小。
SpecMode有3类:
UNSPECIFIED: 父容器不对view做大小限制,一般用于系统内部,表示一种测量状态。
EXACTLY:精确模式。对应于:LayoutPrams中的match_parent和具体数值。
AT_MOST:最大值模式。对应于LayoutParam中的wrap_content模式。
那么问题来了,这个MeasureSpec又是由什么决定的呢?我们从源代码里面入手。
ViewGroup里面有一个方法,叫做measureChildWithMargins,用来测量子view的大小的。
/** * 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) { 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); }
可以看出,在代码里,会先获取childWidthMeasureSpec和childHeightMeasureSpec,然后再测量子元素 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
我们关键是要看这两个MeasureSpec是怎么获取到的,通过方法getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);我们可以看出MeasureSpec的获取不但与子元素本身的LayoutParam有关,还与父容器的MeasureSpec有关,当然,也与它的padding,margin有关。
我们进去看看,这部分代码有点长,不过里面的逻辑很简单,我们直接在源代码里面通过加注释来分析。
/** * Does the hard part of measureChildren: figuring out the MeasureSpec to * pass to a particular child. This method figures out the right MeasureSpec * for one dimension (height or width) of one child view. * * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and * margins, if applicable * @param childDimension How big the child wants to be in the current * dimension * @return a MeasureSpec integer for the child */ 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; //子元素的specSize int resultMode = 0; //子元素的specMode switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: //如果父容器的测量模式为EXACTLY if (childDimension >= 0) { //如果子元素的LayoutParam为具体数值>=0 resultSize = childDimension; //那么子元素的specSize就是childDimension resultMode = MeasureSpec.EXACTLY; //那么子元素的specMode是EXACTLY
//以下的分析都是一样的,也就不写了 } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. 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: 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: 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
//这里View.sUseZeroUnspecifiedMeasureSpec一直都是false,因此resultSize==0; 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; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
由以上的分析可以看出子元素的MeasureSpec的获取不但与子元素本身的LayoutParam有关,还与父容器的MeasureSpec有关,当然,也与它的padding,margin有关。
其实我们可以得出下面的结论:
parentMeasureSpec
childLayoutParam |
EXACTLY |
AT_MOST |
UNSPECIFIED |
具体数值 |
EXACTLLY childSize |
EXACTLY childSize |
EXACTLY childSize |
Match_parent |
EXACTLY parentSize |
AT_MOST parentSize |
UNSPECIFIED 0 |
Wrap_content |
AT_MOST parentSize |
AT_MOST parentSize |
UNSPECIFIED 0 |
parentSize是父容器的剩余空间。
从上面的表格也可以看出,当我们直接继承view来实现自定义控件的时候,需要重写onMeasure方法并设置wrap_content时候的自身大小,不然使用wrap_content得时候就相当于使用match_parent。我们可以用下面的模板:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST) { setMeasuredDimension(100,100); } else if (widthSpecMode==MeasureSpec.AT_MOST) { setMeasuredDimension(100,heightSpecSize); } else if (heightSpecMode==MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize,100); } }
上面代码中的100是我们自己设置的数值,你也可以用其他数值,根据自己需要。
以上便是对MeasureSpec由何决定的分析。