Measure
当我们启动一个应用时,会启动一个主Activity,Android会根据我们的Activity布局进行绘制,绘制从ViewRootImpl类中看到performTraversals方法开始
private void performTraversals(){
int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);
....
//执行测量流程
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
...
//执行布局流程
performLayout(childWidthMeasureSpec,childHeightMeasureSpec);
...
//执行绘制流程
performDraw(childWidthMeasureSpec,childHeightMeasureSpec);
}
自定义view我们都知道重写onMeasure
,onLayout
,onDraw
,但我们总是在写onMeasure
时分类讨论,其实都是一些固定的格式,但身为程序员,我们总是想要知道为什么这么写。
首先我们先介绍一下MeasureSpec
,它表示的是一个32位的整形值,最高两位表示测量的模式SpecMode
,低三十位表示某种测量模式下的规格大小SpecSize
.
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
.........
MeasureSpec
是View的一个静态内部类。
我们需要重点关注代码中的以下三种测量模式:
- UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
- EXACTLY:精确测量模式,当视图的layout_width或者layout_height指定为具体数值,或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种情况下View的测量值就是Specsize的值。
- AT_MOST:最大值模式,当该视图的layout_width或者layout_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸
对于DecorView而言,他的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,他的MeasureSpec由父类的MeasureSpec和其自身的LayoutParams共同决定,我们自定义view在重写onMeasure方法时传入的两个参数就是由父类的MeasureSpec和我们自定义view的LayoutParams合成的。
下面我们可以看ViewGroup的一段代码,看完我们就能理解onMeasure的两个参数如何使用了。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
//循环遍历测量子view的大小
for (int i = 0; i < size; ++i) {
final View child = children[i];
//当view的状态为GONE时不执行测量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//下面就是根据父类的MeasureSpec组成子view的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
下面我们就看看getChildMeasureSpec是如何组成子类的MeasureSpec
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);
}
父类MeasSpec | 父类MeasureSpec中的size | 子类LayoutParams | 子类MeasureSpec | 子类MeasureSpec中的size |
---|---|---|---|---|
EXACTLY | parent_size | 固定尺寸 | EXACTLY | 固定尺寸 |
EXACTLY | parent_size | match_parent | EXACTLY | parent_size |
EXACTLY | parent_size | wrap_content | AT_MOST | parent_size |
AT_MOST | parentsize | 固定尺寸 | EXACTLY | 固定尺寸 |
AT_MOST | parent_size | match_parent | AT_MOST | parent_size |
AT_MOST | parent_size | wrap_content | AT_MOST | parent_size |
UNSPECIFIED | parentsize | 固定尺寸 | EXACTLY | 固定尺寸 |
UNSPECIFIED | parent_size | match_parent | UNSPECIFIED | parent_size |
UNSPECIFIED | parent_size | wrap_content | UNSPECIFIED | parent_size |
从上表可以得出当我们在重写子view的onMeasure时,当传过来的Mode为EXACTLY时,此时传过来的size就是我们想要的size,其他两种情况我们需要自己手动测量大小,此时传过来的size并不是我们想要的大小
而我们在写onMeasure时需要保存测量的值
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我们可以在重写的onMeasure方法中调用setMeasuredDimension方法,传入我们想要设置的size或者直接执行super.onMeasure传入我们设置的size大小
Layout
Layout过程用来确定View在父容器的布局位置,它是由父容器获取子view的位置参数后,调用View的layout方法并将位置参数传入实现。ViewRootImpl的performLayout代码如下:
//ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
//View.java
public void layout(int l, int t, int r, int b) {
...
onLayout(changed,l,t,r,b);
...
//View.java
//这是个空方法,子类如果是ViewGroup类型,则重写这个方法,实现子View的布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
Draw
Draw操作用来将控件绘制出来,绘制的流程从performDraw方法开始
//ViewRootImpl.java
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
........
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
.....
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
可以看到最终调用到每个View的draw方法绘制每个具体的View,绘制基本可以分为六个步骤
//View.java
public void draw(Canvas canvas) {
...
//步骤一:绘制View背景
drawBackground(canvas);
...
//步骤二:如果需要的话,保存canvas的图层,为fading做准备
saveCount = canvas.getSaveCount();
....
canvas.saveLayer(left,top,right,top+length,null,flags);
....
//步骤三:绘制View的内容
onDraw();
...
//步骤四:绘制View的子View
dispatchDraw();
...
//步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left,top,right,top+length,p);
...
canvas.restoreToCount();
..
//步骤六:绘制view的装饰(例如滚动条)
onDrawScrollBars(canvas);