注释里的第一部分说了,测量大小的工作应该主要放在这个方法里,并且所有继承的子类都应该重写这个方法。还有两个比较重要的点:一是重写的方法在onMeasure最后一定要将测量结果通过setMeasuredDimension(int, int)来存储起来,这样这个view的measuredWidth和measuredHeight就是有效值了;二是需要保证测量得出的高和宽不能小于getSuggestedMinimumHeight()和getSuggestedMinimumWidth()的返回值。
View类中的onMeasure方法很简单,它就是直接获得建议的宽和高作为测量结果,为了能明确onMeasure方法而又不至于陷入源码无法自拔,我们选取一个比较简单的FrameLayout来看看它的onMeasure源码,相应的注释我已经写在里面了。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); /** * 来确定是否需要重复测量那些宽和高参数为match_parent的子view,如果FrameLayout的宽高都不是确定的(Exactly), * 那么只有在确定了FrameLayout的宽高之后,才能去测量那些宽或高参数为match_parent的子view。 * */ final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; /** * 测量所有可见的子view * */ for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { /** * 这个是测量子view的主要方法 * */ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); /** * 子view测量完之后,获取子view的测量的宽和高,然后用FrameLayout已有的长和宽相比较,取其大者,这样能保证 * 完整显示所有的子view。 * */ 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 == FrameLayout.LayoutParams.MATCH_PARENT || lp.height == FrameLayout.LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } /** * 将padding也加入到测量结果中 * */ // 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()); } /** * 设置该FrameLayout的测量大小 * */ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); /** * 查看需要测量宽或高为match_parent的子view,如果需要测量,就重新构造子view的MeasureSpec。 * */ count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); int childWidthMeasureSpec; int childHeightMeasureSpec; if (lp.width == FrameLayout.LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } if (lp.height == FrameLayout.LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }