View MeasureSpec 和LayoutParams关系
系统内部是通过MeasureSpec来给View 进行测量工作的,但是我们实际却是只用LayoutParams来设置的.这里我们就是分析2者直接的联系.
其实View在测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后根据这个MeasureSpec来确定View测量之后的高和宽.也就是说MeasureSpec不是由LayoutParams唯一确定的,而是要和父容器一起来确定的.而且普通的View和页面的顶级View(DecorView)的MeasureSpec的转换过程是有些不同的.
DecorView的MeasureSpec是由窗口的尺寸和DecorView其自身的LayoutParams来共同决定的.
普通View的MeasureSpec是由父容器的MeasureSpec和普通View自身的LayoutParams来共同决定的.
在DecorView的measureHierarchy方法就是它的MeasureSpec创建的过程.
ViewRootImpl.java(这里删除了部分log和暂时不用关注的代码)
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {int childWidthMeasureSpec;int childHeightMeasureSpec;boolean windowSizeMayChange = false;boolean goodMeasure = false;if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//这个情况是弹窗,普通的页面窗口不是wrap content//..........}if (!goodMeasure) {//childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {windowSizeMayChange = true;}}return windowSizeMayChange;}
上面的代码中childHeightMeasureSpec和childWidthMeasureSpec就是屏幕的尺寸lp就是DecorView的参数.所以我们需要现在看看getRootMeasureSpec方法
ViewRootImpl.java
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;}
根据上面的getRootMeasureSpec()可以知道:
如果DecorView的LayoutParams是match parent模式,窗口的大小就是DecorView的大小.
如果DecorView的LayoutParams是wrap content模式,DecorView就是最大状态,但是具体大小没有确定(不能超过窗口大小)
如果DecorView的LayoutParams是具体的大小,比如多少像素之类的,那么DecorView 就是它LayoutParams中的实际大小.
同时, DecorView的LayoutParams是match parent或者具体数值(dp,px)的时候,其直接的子View会是精确模式(EXACTLY),
DecorView的LayoutParams是warp
content的时候,其直接的子View会是最大模式(AT_MOST)
在measureHierarchy获取DecorView的MeasureSpec之后就是通过performMeasure()来执行
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec) ,这样就是调用DecorView的自己的measure()方法.关于measure()方法这里暂时不分析.
对普通的View来说,它的measure过程是由它的ViewGroup在measureChildWithMargins()来调用的
ViewGroup.java
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);}
对ViewGroup的每个child 都会被执行上面的measureChildWithMargins方法,先使用父容器ViewGroup的MeasureSpec和child
view 自己的LayoutParams来获取child view自己的MeasureSpec,在调用child view自己本身的measure().
measure()方法后续分析.
上面的是使用getChildMeasureSpec来获取child view 的MeasureSpec的,下面我们分析一下这个方法.
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 on uscase MeasureSpec.EXACTLY:if (childDimension >= 0) {//view 是固定宽高resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {//view 是match parent// Child wants to be our size. So be it.resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {//view 是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 uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {//view 是固定宽高// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {//view 是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) {//view 是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 becase MeasureSpec.UNSPECIFIED://主要用于系统内部多次Measure,暂时不关注if (childDimension >= 0) {//view 是固定宽高// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {//view 是match parent// Child wants to be our size... find out how big it should// beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {//view 是wrap content// Child wants to determine its own size.... find out how// big it should beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
所以我们可以知道,对于普通的View,它的MeasureSpec有父容器的MeasureSpec和View自身的LayoutParams来一起决定的,那么不然的父容器的MeasureSpec和不同View 的LayoutParams,View 就可以有很多种不同的MeasureSpec.当是根据以上代码可以总结如下:
1.当View 是固定宽高的时候,不管父容器是什么模式,View都是精确模式(EXACTLY),并遵循使用LayoutParams里面的大小.
2.当View 的宽高是match parent,父容器是精确模式(EXACTLY),View也是精确模式(EXACTLY);父容器是最大模式(AT_MOST),View也是最大模式(AT_MOST).而且这2种情况View
的大小都是父容器剩余大小空间
3.当View 的宽高是wrap content,不管父容器是精确模式(EXACTLY)还是最大模式(AT_MOST),View始终都是最大模式(AT_MOST).并且大小都是父容器剩余的大小空间
注意:上面提到的精确模式和最大模式已经大小都在MeasureSpec里面有体现,可以参考其他笔记.
本文解析了View的测量机制,详细阐述了MeasureSpec与LayoutParams的关系。包括DecorView如何根据窗口尺寸及LayoutParams生成MeasureSpec,以及普通View如何根据父容器的MeasureSpec和自身LayoutParams进行测量。
829

被折叠的 条评论
为什么被折叠?



