昨晚写的第三章 控件架构与自定义控件详解读书笔记中已经对控件树已经View的测量有了一定了解今天结合ViewGroup的测量来具体分析下onMeasure()的源码。
1.ViewGroup测量子View流程
measureChildWithMargins()/measureChild()->child.measure()->child.onMeasure-> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); );
- 其中measureChildWithMargins()中调用getChildMeasureSpec()通过ViewGroup的MeasureSpec来确定子View的MeasureSpec。
- measureChild( )计算父View所占空间为mPaddingLeft + mPaddingRight,即父容器左右两侧的padding值之和
- measureChildWithMargins( )计算父View所占空间为mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed。此处,除了父容器左右两侧的padding值之和还包括了子View左右的margin值之和( lp.leftMargin + lp.rightMargin),因为这两部分也是不能用来摆放子View的应算作父View已经占用的空间。这点从方法名measureChildWithMargins也可以看出来它是考虑了子View的margin所占空间的。
- 最后看下getDefaultSize()源码:
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:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
除去第一种情况不考虑以外,可知: 在measure阶段View的宽和高由其measureSpec中的specSize决定!!
-
终于搞懂了一个问题,如果子View在XML布局文件中对于大小的设置采用wrap_content,那么不管父View的specMode是MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空间。这时wrap_content就失去了原本的意义,变成了match_parent一样了。
-
我们再回过头来看看getChildMeasureSpec()方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = View.MeasureSpec.getMode(spec);
int specSize = View.MeasureSpec.getSize(spec);int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { case View.MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = View.MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = View.MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = View.MeasureSpec.AT_MOST; } break; case View.MeasureSpec.AT_MOST: if (childDimension >= 0) { resultSize = childDimension; resultMode = View.MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = View.MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = View.MeasureSpec.AT_MOST; } break; case View.MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { resultSize = childDimension; resultMode = View.MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = View.MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = View.MeasureSpec.UNSPECIFIED; } break; } return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
最终解释可借助下图:
既然在ViewGroup中wrap_content失去了原本的意义,那图中的绿色标记部分,该如何处理呢?
-
第一种情况: 如果在xml布局中View的宽和高均用wrap_content.那么需要设置 View的宽和高为mWidth和mHeight。
-
第二种情况: 如果在xml布局中View的宽或高其中一个为wrap_content,那么就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可。
具体实现可参考:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSpceSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpceSize, mHeight);
}}
网友评论