深色模式
Android 绘制流程
绘制流程的起点
从 ViewRootImpl 的performTraversals()
方法开始:
java
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//执行测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制流程
performDraw();
}
理解 MeasureSpec
MeasureSpec 源码
MeasureSpec 是 View 类的一个静态内部类,用来说明应该如何测量这个 View。
MeasureSpec 表示的是一个32位的整形值,它的高2位表示测量模式 mode
,低30位表示某种测量模式下的规格大小 size
。
java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0X3 << MODE_SHIFT;
// 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要
// 的任何尺寸,通常用于系统内部,应用开发中很少用到。
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 精确测量模式,视图宽高指定为match_parent或具体数值时生效,
// 表示父视图已经决定了子视图的精确大小,这种模式下View的测量
// 值就是SpecSize的值。
public static final int EXACTLY = 1 << MODE_SHIFT;
// 最大值测量模式,当视图的宽高指定为wrap_content时生效,此时
// 子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
public static final int AT_MOST = 2 << MODE_SHIFT;
// 根据指定的大小和模式创建一个MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 微调某个MeasureSpec的大小
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return make MeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + delta;
if (size < 0) {
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
DecorView 的 MeasureSpec 创建
对于 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;
java
//desiredWindowWidth和desiredWindowHeight是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
private static int getRootMeaureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATRCH_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.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
普通 View 的 MeasureSpec 创建
对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和其自身的 LayoutParams 共同决定。
java
// ViewGroup.java
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身
// 的LayoutParams有关,此外还和View的margin及padding有关
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);
}
下面源码对应的计算规则:
parent | child | specMode |
---|---|---|
MeasureSpec.EXACTLY | match_parent | MeasureSpec.EXACTLY |
MeasureSpec.EXACTLY | wrap_content | MeasureSpec.AT_MOST |
MeasureSpec.AT_MOST | match_parent | MeasureSpec.AT_MOST |
MeasureSpec.AT_MOST | wrap_content | MeasureSpec.AT_MOST |
java
// ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// padding是指父容器中已占用的空间大小,因此子元素可用的
// 大小为父容器的尺寸减去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (sepcMode) {
// 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 (childDimesion == 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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size....
// find out how big it should be
resultSize = 0;
resultMode == MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
measure 流程
从 ViewRootImpl 开始:
java
// ViewRootImpl.java
private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
// 具体的测量操作分发给ViewGroup
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
遍历测量 ViewGroup 中所有的 child view:
java
// ViewGroup.java
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];
// 当View的可见性处于GONE状态时,不对其进行测量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
测量单个 child:
java
// ViewGroup.java
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根据父容器的MeasureSpec和子View的LayoutParams等信息计算
// 子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在 measure()
中,具体测量行为交由onMeasure()
来定义:
java
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// ViewGroup没有定义测量的具体过程,因为ViewGroup是一个
// 抽象类,其测量过程的onMeasure方法需要各个子类去实现
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
ViewGroup 没有重写onMeasure()
方法,View 的 onMeasure()
:
java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasureDimension方法用于设置View的测量宽高
// getSuggestedMinimumWidth() 的作用是:
// 如果View没有设置背景,那么返回android:minWidth这个属性所指定的值,这个值可以为0;
// 如果View设置了背景,则返回android:minWidth和背景的最小宽度这两者中的最大值。
setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
View 的默认测量行为是使用getDefaultSize()
来测量宽高,从这里可以看出,View 在默认情况下,wrap_content
和 match_parent
的行为是一样的:
java
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 = sepcSize;
break;
}
return result;
}
layout 流程
开始布局:
java
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
具休 layout 行为,交给 onLayout()
:
java
// View.java
public void layout(int l, int t, int r, int b) {
...
// 通过setFrame方法来设定View的四个顶点的位置,即View在父容器中的位置
boolean changed = isLayoutModeOptical(mParent) ?
set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
java
// View.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup 重写了onLayout()
方法,并将它改为抽象方法,子类必须实现此方法:
java
// ViewGroup.java
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
draw 流程
开始绘制:
java
// ViewRootImpl.java
private void performDraw() {
...
draw(fullRefrawNeeded);
...
}
java
// ViewRootImpl.java
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset,
scalingRequired, dirty)) {
return;
}
...
}
java
// ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
具体绘制行为,为6个步骤,其中onDraw()
用于绘制内容:
java
// 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(canvas);
...
// 步骤四:绘制View的子View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步骤六:绘制View的装饰(例如滚动条等等)
onDrawForeground(canvas)
}