MeasureSpc类说明
MeasureSpec类的职责是把view的with,height和SpecMode封装成两个32位的整数,同时它也提供了把32位整数解包成相应的with和with对应的SpecMode或者是height和height对应的SpecMode。SpecMode有三种模式:
- UNSPECIFIED(未指定):父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;
- EXACTLY(完全):父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身的大小;它对应于LayoutParams中的match_parent和具体的数值这两种模式。
- AT_MOST(最多):子元素至最多达到指定大小的值。它对应于LayoutParams中的Wrap_content
android如何绘制view
当Activity获取到焦点的时候,它就会去请求绘制整个android界面,framework会处理绘制过程,但是Activity必须要提供树结点,当调用者在setConentView的时候,android系统会根据xml生成相应的view,然后把view添加到Activity的树结点,如下图
从Activity的ViewRoot开始绘制整个view树的,绘制的过程就是遍历整个view树和绘制每个view的有效区域。ViewGroup的职责是负责去帮助它的子view去请求ondraw方法,而每个view的职责就是渲自身的内容。例如TextView(1)的职责是负责绘制自身的内容,而RelativeLayout(2)的职责是帮助ImageView和TextView(2)的渲染工作。从图中可以看出view的渲染顺序是自上而下的,就是说ViewRoot先渲染,然后再到它的子view.
在这里需要说明的是框架不会渲染不在显示区或的view,并且它也为你提供后台绘制视图的功能。通过调用invalidate(),你可以强制绘制视图
绘制布局分成两个过程:onMeasure和onLayout,onMeasure负责测量view的尺寸,如果有下一层的view,当前的view会把自己的测量值传到下一层的view中,这样不断的往下传下去测量出整个view树的大小;而onLayout也是自上而下的一个流程,通过onLayout来设置每个子view的大小和位置
当视图的onMeasure方法执行后,它的getMeasuredWidth()和getMeasuredHeight()必定是己经设置了的,视图测量后它的宽度和高度必须是受到父视图的约束,这就保证了测量结束时,所有的父视图可以接收子视图的测量值。举个例子,当父类使用了unspecified方式去布局时父类为了确定子类的大小就需要调用一次onMeasuere方法,然后父类会再次调用onMeasure方法来避免子类太大或者是太小(也就是说,如果子视图不满意它们获得的区域大小,那么父视图将会干涉并设置第二次测量规则)。
测量过程中使用了两个类来传递尺寸。ViewGroup.LayoutParams类是子view告诉父view子view应该测量子子view和怎样放置子view,ViewGroud的LayoutParams类只描述了视图的尺寸,它可以有以下的值:
- 具体的数值
- WRAP_CONTENT-适配显示的内容的大小
- MATCH_PARENT-和父view的大小一样大
如果想自定义view可以继承ViewGroup,并重写generateLayoutParams方法,提供自己的LayoutParams来砍认view放置的规则。为了说明View的测量过程,我们以RelativeLayout作为例子来分析RelativeLayout的布局过程。
RelativeLayout的布局过程分析
由于onMeasure代码比较多,我们把onMeasure分成几个部份一起分析,首先我们先看下水平方向的测量。applyHorizontalSizeRules方法负责测量水child的水平方向的位置,measureChildHorizontal则负责测量view的宽度,具体看以下代码引用的注释
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int myWidth = -1;
int myHeight = -1;
int width = 0;
int height = 0;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 如果不是UNSPECIFIED模式 则将widthSize赋值于myWidth
if (widthMode != MeasureSpec.UNSPECIFIED) {
myWidth = widthSize;
}
// 如果不是UNSPECIFIED模式 则将heightSize赋值于myHeight
if (heightMode != MeasureSpec.UNSPECIFIED) {
myHeight = heightSize;
}
//如果是EXACTLY模式 则将myWidth和myHeight记录
if (widthMode == MeasureSpec.EXACTLY) {
width = myWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = myHeight;
}
....................................初始化代码....................................
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
// 测量view的水平方向的位置(view的左右位置)
applyHorizontalSizeRules(params, myWidth, rules);
// 测量view的宽度
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
....................................待分析代码....................................
}
/**
* 最终计算出child view距离左边和右边的边距
*/
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams;
// 获取到childview的toLeftOf所对应的view的LayoutParams
anchorParams = getRelatedViewParams(rules, LEFT_OF);
if (anchorParams != null) {
// 计算child view的右边的位置
childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
childParams.rightMargin);
// child view如果有layout_alignWithParentIfMissing和layout_toLeftOf
// 相对参考值为空时,就以当前的Relative的边界为参考,即child的
// toLeftof对应的view为visible为gone时,就以当前类的右边界为参考点,
// 即child view会居右显示
} else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
if (myWidth >= 0) {
// 计算child view的右边的位置
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
// 获取toRightOf所对应的view的LayoutParams
anchorParams = getRelatedViewParams(rules, RIGHT_OF);
if (anchorParams != null) {
// 计算child view的左边的位置
childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
childParams.leftMargin);
// child view如果有layout_alignWithParentIfMissing和layout_toRightOf
// 相对参考值为空时,就以当前的Relative的边界为参考,即child的
// toRightof对应的view为visible为gone时,就以当前类的左边界为参考点,
// 即child view会居左显示
} else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
// 居左显示
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// child view的layout_alignLeft左边和参照的view的左边对齐
anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
if (anchorParams != null) {
childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
} else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// child view的右边和参照view的右边对齐
anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
if (anchorParams != null) {
childParams.mRight = anchorParams.mRight - childParams.rightMargin;
} else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
// layout_alignParentLeft优先级最高,如果有值就直接居左
if (0 != rules[ALIGN_PARENT_LEFT]) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// layout_alignParentRight优先级最高,如果有值就直接居右
if (0 != rules[ALIGN_PARENT_RIGHT]) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
}
/**
* 计算出view的水平宽度
*/
private void measureChildHorizontal(
View child, LayoutParams params, int myWidth, int myHeight) {
// 计算出child view的宽度
final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
myWidth);
final int childHeightMeasureSpec;
// mAllowBrokenMeasureSpecs是对4.2版本以下的系统的兼容,在这里不介绍
// 如果小于0,证明是WRAP_CONTENT或者是MATCH_PARENT
if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
if (params.height >= 0) {
// child view有具体的高度值,把值包装成32位的整数
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
params.height, MeasureSpec.EXACTLY);
} else {
// Negative values in a mySize/myWidth/myWidth value in
// RelativeLayout measurement is code for, "we got an
// unspecified mode in the RelativeLayout's measure spec."
// Carry it forward.
// child view没有指定高度,子类可以得到想要的高度值
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
} else {
final int maxHeight;
// mMeasureVerticalWithPaddingMargin是大于等于4.3时的支持
// 这里计算出最大的高度值
if (mMeasureVerticalWithPaddingMargin) {
maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
- params.topMargin - params.bottomMargin);
} else {
maxHeight = Math.max(0, myHeight);
}
// 计算出height mode
final int heightMode;
if (params.height == LayoutParams.MATCH_PARENT) {
heightMode = MeasureSpec.EXACTLY;
} else {
heightMode = MeasureSpec.AT_MOST;
}
// 把高度和mode合起来生成一个32位的整数
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
由于垂直方向的测量过程和水平方向的测量过程是一样的,在这里不会做详细的分析,有兴趣的童鞋可以自行查阅相应的代码。
下面我们测量出所有child view的上下左右四个位置后, 我们就可以测出当前当前RelativeLayout的真实宽度,知道真实宽度后就可以对依懒RelativeLayout的child view的布局进行重新修正,详细请看以下的代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 计算出了当前view的真实宽度后,需要重新去设置那些基于父类布局的view的水平方向的真实位置
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
width += mPaddingRight;
if (mLayoutParams != null && mLayoutParams.width >= 0) {
width = Math.max(width, mLayoutParams.width);
}
width = Math.max(width, getSuggestedMinimumWidth());
width = resolveSize(width, widthMeasureSpec);
if (offsetHorizontalAxis) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
// 如果child view是否有用到水平布局方式,就重新计算它的左边水边的值
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
} else if (rules[ALIGN_PARENT_RIGHT] != 0) {
// 居右显示时,重新计算child view的左右边缘
final int childWidth = child.getMeasuredWidth();
params.mLeft = width - mPaddingRight - childWidth;
params.mRight = params.mLeft + childWidth;
}
}
}
}
}
// 计算出了当前view的真实高度后,需要重新去设置那些基于父类布局的view的垂直方向的真实位置
if (isWrapContentHeight) {
// Height already has top padding in it since it was calculated by looking at
// the bottom of each child view
height += mPaddingBottom;
if (mLayoutParams != null && mLayoutParams.height >= 0) {
height = Math.max(height, mLayoutParams.height);
}
height = Math.max(height, getSuggestedMinimumHeight());
height = resolveSize(height, heightMeasureSpec);
if (offsetVerticalAxis) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
// 判断child view是否有垂直方向的布局方式,如果有就重新修正布局方式
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
判断child view是否是在RelativeLayout的底部,如果是重新计算child的上下边缘
} else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
final int childHeight = child.getMeasuredHeight();
params.mTop = height - mPaddingBottom - childHeight;
params.mBottom = params.mTop + childHeight;
}
}
}
}
}
// 根据当前RelativeLayout的gravity来修正child view的上下左右的位置
if (horizontalGravity || verticalGravity) {
final Rect selfBounds = mSelfBounds;
selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
height - mPaddingBottom);
final Rect contentBounds = mContentBounds;
Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
layoutDirection);
// 根据gravity计算上下左右的水平修正值
final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
if (horizontalOffset != 0 || verticalOffset != 0) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE && child != ignore) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (horizontalGravity) {
params.mLeft += horizontalOffset;
params.mRight += horizontalOffset;
}
if (verticalGravity) {
params.mTop += verticalOffset;
params.mBottom += verticalOffset;
}
}
}
}
}
// 最后调用setMeasuredDimension设置当前RelativeLayout的宽度和高度,最后可以通过getMeasureHeight和getMeasureWidth可以拿到
// RelativeLayout的真实高度
setMeasuredDimension(width, height);
}
复制代码
最后我们看下onLayout方法,onLayout方法很简单,只是把LayoutParams的上下左右的位置传到字类中来确定子view的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
复制代码
RelativeLayout的布局总结
从源码我们可以看到,Relative在布局的过程中至少是调用了所有child的onMeasure方法两次,而在布局的过程中也会涉及到view的排序,相对位置的计算,这样就导致relativeLayout在布局过程中耗时比较长的原因,所以如果非必机的情况下不要用RelativeLayout。
综上所述View的测量过程是先通过onMeasure测量出所有view的位置和大不小,然后通过onLayout来把view放到相应的位置。