View的绘制流程经过measure、layout、draw三个过程。
measure:测量View的高宽,layout用来确定View在父容器存放的位置,draw负责将View绘制在屏幕上。
// 源码中 最先从ViewRoot的开始绘制,从ViewGroup开始,遍历里面的子View,完成绘制
View的大小:dx、wrap_content、match_parent ,跟模式有关:EXACTLY、AT_MOST、UNSPECIFIED。android将其集成为一体,即MeasureSpec,通过对MeasureSpec的解析,可以得到View的大小与其对应的宽高模式。
class MeasureSpec {
public static final int EXACTLY // match_parent、dp
public static final int AT_MOST // wrap_content
public static final int UNSPECIFIED // don't care
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
// size 是view的大小,宽高。 mode是模式 返回一个int值,避免过多的内存分配
系统将View的size和mode封装到MeasureSpec中,通过对MeasureSpec解析可以得到size和mode。
MeasureSpec怎么来的?
对于顶级View(DecorView)来说,它没有父亲。所以它的MeasureSpec是由窗口的尺寸和其自身的LayoutParams共同决定的。
源码如下:
在measureHierarchy方法中
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
再来看看getRootMeasureSpec:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.精确模式,大小即为窗口大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.最大模式,大小最大为窗口
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.精确模式,大小为LayoutParams指定的大小
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
非顶级的View的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams决定的。
所有的View其measure过程都是由ViewGroup传递过来,因此先看ViewGroup是如何measure的。
ViewGroup本身没有onMeasure的方法,它也不需要,它包含子View,目的是View的绘制。所以它提供了measureChildren --> measureChild方法。
它会遍历所有的子View,执行子View的measure方法。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
很显然,measureChild的思想就是取出子元素的LayoutParams,然后通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View去绘制。
所以具体看看getChildMeasureSpec是个什么样的逻辑。
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 = 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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
int size =Math.max(0, specSize - padding);
普通View -MeasureSpec的创建规则
pSpecMode / cLayoutParams | EXACTLY | AT_MOST | UNSPECIFIED |
dp/px | EXACTLY/childSize | EXACTLY/childSize | EXACTLY/childSize |
match_parent | EXACTLY/parentSize | AT_MOST/parentSize | UNSPECIFIED/0 |
wrap_content | AT_MOST/parentSize | AT_MOST/parentSize | UNSPECIFIED/0 |
parentSize指父容器可以用的大小
注:这里容易出现问题,当父为EXACtLY时,即match_parent,子View的wrap_content会不生效,因为根据表,它的size是parentSize,所以这个改法是将高/宽进行指定dp
直接继承View和ViewGroup的控件,padding是默认无法生效的,需要自己处理。需要在onDraw方法中自己处理下:
final int paddingLeft = getPaddingLeft();
.....
int width = getWidth() - paddingLeft - paddingRight;
.....
最后再来看View的measure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure里就进行了setMeasureDimension,这个方法会去设置View的宽/高的测量值,即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;
}
specSize即为View测量后的大小。