一,疑问?
相信很多人有过这样的经历,直接在(setContentView)之后就直接使用view.getMeasuredHeight/Width,
相信很多人有过这样的经历,直接在(setContentView)之后就直接使用view.getMeasuredHeight/Width,
很尴尬, 结果是0。 那么大家有没有想想为什么是0?
前面我们提到view测绘流程实际源头是activity.onResume回调之前创建的ViewRootImpl处理。而setContentView也仅仅只是构建视图层级结构而已,还远远未到需要测绘的地步。
二,measure准备
前面我们提到view测绘流程实际源头是activity.onResume回调之前创建的ViewRootImpl处理。而setContentView也仅仅只是构建视图层级结构而已,还远远未到需要测绘的地步。
二,measure准备
1,MeasureSpec: 这个是测量主要的参数,共32位, 由两部分组成 Mode(前两位), Size(后三十位) 。
Mode:
EXACTLY (精确) 字面意义很清晰,大小固定。
AT_MOST: (最多) ,这个“最多”有点,大家想想wrap_content ,子视图要测量自己实际大小,方可告知父视图。
UNSPECIFIED: (不明确), 就是根本不明确任何大小
EXACTLY (精确) 字面意义很清晰,大小固定。
AT_MOST: (最多) ,这个“最多”有点,大家想想wrap_content ,子视图要测量自己实际大小,方可告知父视图。
UNSPECIFIED: (不明确), 就是根本不明确任何大小
Size: 大小
2, Mode 如何明确
EXACTLY:
2, Mode 如何明确
EXACTLY:
(1) 如果父视图明确大小, 如果子视图Match_Parent ,那么子视图也是EXACTLY
(2) 不管父视图是否明确,如果子视图固定大小,那么子视图也是EXACTLY
(2) 不管父视图是否明确,如果子视图固定大小,那么子视图也是EXACTLY
AT_MOST:
(1) 如果父视图大小明确, 如果子视图wrap_content, 那么子视图也是AT_MOST
(2) 如果父视图大小不明确,子视图wrap_content,或者match_parent, 子视图也是AT_MOST
(2) 如果父视图大小不明确,子视图wrap_content,或者match_parent, 子视图也是AT_MOST
UNSPECIFIED:
(1) 父视图 UNSPECIFIED, 子视图未明确大小,那么子视图就UNSPECIFIED
(2) 子视图本身UNSPECIFIED
3, measure思路
3, measure思路
view树在遍历过程, 根部视图是知道自身大小的(如屏幕大小),在遍历过程,子视图如何确定自己范围呢?必须依赖父视图给子视图,因此,父视图会将建议子视图大小传过去,依次类推。
如何界定子视图需要大小? 父视图本身的padding,以及子视图的margin属性其实都是不属于子视图区域的,因此得剔除。
三,measure流程
1, ViewRootImpl.performMeasure
如何界定子视图需要大小? 父视图本身的padding,以及子视图的margin属性其实都是不属于子视图区域的,因此得剔除。
三,measure流程
1, ViewRootImpl.performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
2, DecorView.OnMeasure(实际FrameLayout)
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); 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) { 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); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear();
主要处理了:让子视图去measure本身(当然传给子视图建议大小measurespec)
setMeasuredDimension设置此次measure后当前视图的measure大小。
setMeasuredDimension设置此次measure后当前视图的measure大小。
附:我们前面说到getMeasuredHeight获取结果为0,实际就是通过setMeasuredDimension设置measure后的结果。这样我们才能获取正确数据,因此必须要先measure。
注:我们在自定义view的onMeasure后要么super.onMeasure,要么自己重写后调用setMeasuredDimension。务必调用setMeasuredDimension,该方法调用反映测量过程完结。
3,measureChildWithMargin
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); }
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 us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.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 resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
计算传递给子视图的建议大小,排除父视图本身padding, 子视图margin。以及确定子视图的mode。子视图进一步measure本身。
四:小结
(1)我们务必在measure之后方能拿到getMeasuredHeight/Width。
(1)我们务必在measure之后方能拿到getMeasuredHeight/Width。
(2)务必在onMeasure中调用setMeasuredDimension返回measure结果。
(3)measure由根视图开始分发,(根视图是包含固定大小(由window或屏幕决定)),根视图在会让子视图measure,传递给子视图建议空间(当然去除父视图padding,和子视图margin后大小)
(3)measure由根视图开始分发,(根视图是包含固定大小(由window或屏幕决定)),根视图在会让子视图measure,传递给子视图建议空间(当然去除父视图padding,和子视图margin后大小)