1.ViewGroup的绘制流程
众所周知,ViewGroup的绘制流程分为onMeasure->onLayout->onDraw三个环节。这篇文章我们先来介绍一下onMeasure测量流程。
2.OnMeasure测量套路
- 遍历ChildView
- 对每一个ChildView进行测量
- 获取每一个childVIew测量结果,从而根据开发者自身需求,计算ViewGroup整体宽高
- 调用setMeasuredDimension(int measuredWidth, int measuredHeight)设置VIewGroup的尺寸
1. 遍历childView,相信应该不用过多解释了,请参考以下代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取直接childView的个数
int count = getChildCount();
for(int i = 0; i < count; i++){
//对每一个ChildView进行测量
...
}
}
2.对每一个ChildView进行测量,这里我们需要知道childView的大小是由哪些条件决定的(划重点),这里先卖个关子,等看完源码后再来总结。
大家在测量childView的时候,多数会用到以下2个方法:
//遍历后对单个childView进行测量
1. measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
//直接对所有的childView进行测量,无须先遍历
2. measureChildren(int widthMeasureSpec, int heightMeasureSpec)
这两个方法最终都会通过传入的widthMeasureSpec和heightMeasureSpec来测量childView。那么这2个值从何而来呢?其实是当前ViewGroup的父View在自己的onMeasure方法中调用measureChild或measureChildren的时候传过来的,同理当前的ViewGroup也会在自己的onMeasure方法中调用measureChild或measureChildren将计算好的widthMeasureSpec和heightMeasureSpec传入childView的onMeasure中。
这里插入一个关于MeasureSpec的解释:
MeasureSpec是View的一个内部类。由32位的int数组成,高2位SpecMode,分为EXACTLY,AT_MOST和UNSPECIFIED,低30位SpecSize。下面是一张关于MeasureSpec创建规则的图片(重要)
继续回到childView的测量流程上面来,调用measureChild和measureChildren方法,最终都会通过getChildMeasureSpec方法获取childView对应的widthMeasureSpec和heightMeasureSpec 。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
//获取childView的LayoutParams
final LayoutParams lp = child.getLayoutParams();
//根据ViewGroup传入的parentWidthMeasureSpec,ViewGroup水平方向的padding, child的LayoutParams的width,来获取childView的widthMeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
//同上
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//根据getChildMeasureSpec获取的widthMeasureSpec和heightMeasureSpec进行测量childView
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
进入getChildMeasureSpec方法一探究竟。
/**
* 根据父View(ViewGroup)传入的MeasureSpec,父View(ViewGroup)的padding,以及childView自
* 身的size来获取childView自身的测量值。
* @param 父View(ViewGroup)传入的MeasureSpec
* @param 父View(ViewGroup)的padding
* @param childView的LayoutParams得到的width或height,也有可能会在宽高基础上加上margin的
* 值
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取父View(ViewGroup)传入的MeasureSpec的SpecMode和SpecSize,即父View可以提供的最大
//尺寸,规格
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//表示childView可用的最大size,即父View最大尺寸-父View的padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据父View传入的SpecMode,分为以下 3 * 3 种情况
switch (specMode) {
// 父View的大小是确定的
case MeasureSpec.EXACTLY:
if (childDimension >= 0) { //childView大小固定
//childMeasureSpecSize = childView的LayoutParams中得到的尺寸
resultSize = childDimension;
//childMeasureSpecMode = MeasureSpec.EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) { //MATCH_PARENT
//childMeasureSpecSize = size,即父View可以提供的最大size
resultSize = size;
//childMeasureSpecMode = MeasureSpec.EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) { //WRAP_CONTENT
//childMeasureSpecSize = size,表示父View最大只能提供这么大的size了
//注:最终childView的size会在自己的onMeasure中自行计算出实际值,这里是父View
//给的最大参考值
resultSize = size;
//childMeasureSpecMode = MeasureSpec.AT_MOST
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父View也不知道自己有多大
case MeasureSpec.AT_MOST:
if (childDimension >= 0) { //childView大小固定
//childMeasureSpecSize = childView的LayoutParams中得到的尺寸
resultSize = childDimension;
//childMeasureSpecMode = MeasureSpec.EXACTLY 表示大小确切
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) { //MATCH_PARENT
// childView与父View的尺寸一致,但是由于父View自己也不知道有多大,所以只能将
// 自己最大的size提供给childView的MeasureSpecSize
resultSize = size;
//因为父View的大小不固定,所以childView的大小同样也不固定,所
//以childMeasureSpecMode = MeasureSpec.EXACTLY
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) { //WRAP_CONTENT
//childView自适应大小,父View最大只能提供size的大小了
resultSize = size;
//因为父View和childView自身均不确定大小,所以childMeasureSpec =
//MeasureSpec.AT_MOST
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//将resultSize和resultMode组装成MeasureSpec对象,也就是childView的MeasureSpec了
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
看到这里,不难理解,其实childView的大小由自身的LayoutParams和父View的MeasureSpec共同决定的,在childView获取到childMeasureSpecWidth和childMeasureSpecHeight之后,会调用child.measure(int widthMeasureSpec, int heightMeasureSpec)进行childView真正的测量,这时候会调用childView自身的onMeasure方法。在childView的onMeasure方法的最后都会调用setMeasuredDimension方法设置真正的size。来看一下setMeasuredDimension源码:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
//最终会调用这个方法
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
进入setMeasuredDemensionRaw方法:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//将childView的onMeasure中最终计算得出的measureWidth值赋值给mMeasuredWidth保存
mMeasuredWidth = measuredWidth;
//将childView的onMeasure中最终计算得出的measureHeight值赋值给mMeasuredHeight保存
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
这里会把childView最终测量的值赋值给mMeasureWidth和mMeasureHeight保存,其实childView.getMeasuredWidth返回的值就是mMeasureWidth,childView.getMeasureHeight返回的值就是mMeasureHeight。4.
3. 此时所有childView均已测量过了,调用getMeasureWidth和getMeasureHeight获取到各个childView的实际size后,根据业务需求将父view(ViewGroup)的size计算出来。
4. 在ViewGroup的onMeasure的最后,调用setMeasuredDimension将计算后的measureWidth和measureHeight传入,设置VIewGroup自身的Size。
3.总结
- 在onMeasure中,view的大小与自身的LayoutParams和父View的MeasureSpec共同决定
- onMeasure最后要调用setMeasuredDimension方法
至此,ViewGroup的onMeasure就结束啦。