view经过measure、layout、draw三个过程才将一个view绘制出来,其中measure负责测量view,layout负责确定view在父容器中的位置,draw负责将view绘制在屏幕上。在measure、layout方法中又会调用onMeasure、onLayout方法,完成对子view的测量和定位,在draw方法中会调用dispatchDraw方法,对子view的绘制。
1、MeasureSpace
MeasureSpace是一个32位的int值,高两位代表SpecMode,低30位代表SpecSize。对于普通的view,其MeasureSpace由父容器的MeasureSpace和其自身的LayoutParams共同决定。
SpecMode有三类:
- EXACITY:
如果在当前view的LayoutParams中,指定了具体的宽高或者宽高是MATCH_PARENT(父容器是精确模式),那么当前view的SpecMode是精确模式,SpecSize是在LayoutParams中指定的具体值或者是父容器的剩余空间。 - AT_MOST:
如果在当前view的LayoutParams中,设置的宽高是WARAP_CONTENT,或者父容器的SpecMode是最大模式(但是当前view的LayoutParams不是具体值),那么当前view的SpecMode是最大模式,SpecSize不能超过父容器的剩余空间。 - UNSPECIFIED:父容器不对view的大小有任何限制,当前view想要多大就给多大,listview,scrollview。
如果view采用固定宽高,其MeasureSpace是精确模式,大小就是在LayoutParams中指定的大小;当父容器的MeasureSpace是精确模式时,如果当前view的LayoutParams是MATCH_PARENT,该view的MeasureSpace是精确模式,大小是父容器的剩余空间;如果当前view的LayoutParams是WARAP_CONTENT,或者当父容器的MeasureSpace是最大模式时,该view的MeasureSpace是最大模式,大不能超过父容器的剩余空间。
2、measure过程
- measure方法用来测量view的大小,但是实际的测量过程是在onMeasure方法中进行的。
- measure方法是final,子view不能重写。
- 主要是通过对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值来完成测量的。
1、view的measure过程
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
//回调onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
通过setMeasuredDimension方法对view的成员变量mMeasuredWidth、mMeasuredHeight进行赋值
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
在onMeasure方法中,可以根据父容器的MeasureSpace和view的LayoutParams,计算view的大小
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;
}
但是在系统的默认实现中,最大模式下view的大小等于父容器的剩余空间,可以重写它
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight);
}else if (widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}else if ( heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
2、viewgroup的measure过程
viewgroup没有实现onMeasure方法,不同的viewgroup子类具有不同的布局特征,这也导致他们的测量细节各不相同。可以在其子类中实现onMeasure方法。
例如, LinearLayout 的measure过程
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout会遍历每个子view,并最终调用每个子view的measure方法。当子元素测量完毕后,LinearLayout会测量自己的大小。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
...
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,heightMeasureSpec, 0);
...
...
}
...
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
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);
}
3、layout过程
layout方法确定当前view四个顶点相对与父容器的位置,即为mLeft、mTop、mBottom、mRight赋值;在onLayout方法中会循环调用子view的layout方法,确定子view的位置。
1、view的layout过程
view在layout方法中已经得到了四个顶点位置,所以onLayout()是一个空实现
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 确定View的位置
// 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
protected boolean setFrame(int left, int top, int right, int bottom) {
...
// 确定View的四个顶点
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
2、viewgroup的layout过程
在layout方法中,可以确定viewgroup自身的位置。但是如果要确定子view在viewgroup的位置,需要实现onLayout方法。由于确定位置与具体布局有关,所以onLayout方法在ViewGroup中是一个抽象的方法:需要具体的子类去实现。
例如,LinearLayout的onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
在layoutVertical中遍历view,并根据layoutDirection和gravity计算每个子view的childTop、childLeft,调用setChildFrame,完成对子view的定位。
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
...
//根据layoutDirection和gravity计算childTop、childLeft
...
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
@Override
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
3、draw过程
view的绘制过程如下:
- 1、绘制背景
- 2、绘制自己
- 3、绘制children
- 4、绘制装饰
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
// Step 2, save the canvas' layers
// Step 3, draw the content 通常在自定义view的时候,需要子类实现
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children 遍历子View并绘制包含的所有子View
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// Step 6, draw decorations (foreground, scrollbars) (滚动指示器、滚动条、和前景)
onDrawForeground(canvas);
}
注意:
viewgroup默认情况下是不需要绘制内容的,如果需要绘制内容,可以在构造函数中设置setWillNotDraw(false)
参考: Android开发艺术探讨、 Android应用层View绘制流程与源码分析、自定义View Measure过程 - 最易懂的自定义View原理系列、自定义View Layout过程 - 最易懂的自定义View原理系列、自定义View Draw过程- 最易懂的自定义View原理系列