UI绘制流程(测量,布局,绘制)

  • 作者:鱼八(网易1902期)
  • 公众号:鱼八说
  • 日期:2019-06-23
  • 类型:Android
  • 说明:本文源于 鱼八 原创,如需转载请带上链接或注明出处!

一、测量performMeasure

在这个方法中我们我们需要清楚2件事。

  • 1 . 测量 view的宽高流程是什么?
  • 2 . MeasureSpec 是什么?
    最后我们再去看看DecorView测量的整个流程是什么样的。

1.测量 view的宽高流程

最开始我们先去了解测量一个View的过程,我们进入到 ViewRootlmpl类中,找到performMeasure()方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

从源码中可以看到,mView 直接就调用了measure()方法,我们点进去可以看到我们进入到了View类中的Measure()方法,是final类型不可以被重写。继续阅读源码,在measure()方法中,调用了onMeasure()方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}

进入到onMeasure() 方法中

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

发现调用了setMeasuredDimension()方法,进入该方法中我们发现又调用了setMeasuredDimensionRaw()方法,在进入到该方法中,发现这个方法里只是对mMeasuredWidthmMeasuredHeight 进行了赋值,并且进行了一个标志位的设置,表示对这2个值已经做了赋值。

  private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

所以一个 view 从performMeasuer—> measure() —> onMeasure —> setMeasuredDimension( ) — > setMeasuredDimensionRaw 的过程实际上最后获得是View的宽高。

2.了解 MeasureSpec (View类的静态内部类)

我们知道了测量的流程后,我们再去了解一下MeasureSpec 。MeasureSpec封装了父布局传递给子布局的布局要求,有两个部分,一个是View的模式还有一个是View的尺寸,这两个部分被封装到了 MeasureSpec 中,MeasureSpec 是一个 32位的int值。
32位值 :00000000 00000000 00000000 00000000 ,他的前2位就是view的模式 SpecMode。后30位就是代表着尺寸SpecSize。
在 进入到 MeasureSpec 中,我们可以看到 定义了三个值:UNSPECIFIED ,EXACTLY(精确模式) ,AT_MOST (最大模式)

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;

阅读源码,我们发现:

  • 1 . public static final int UNSPECIFIED = 0 << MODE_SHIFT; 00000000 00000000 00000000 00000000
    表示 父容器 不对 View做任何限制,系统内部使用
  • 2 . public static final int EXACTLY = 1 << MODE_SHIFT; 01000000 00000000 00000000 00000000 精确模式
    表示父容器检测出View的大小,View的最终大小就是 SpecSize , 这个模式对应的布局属性是:LayoutPamras.match_parent 或者是 固定大小
  • 3 . public static final int AT_MOST = 2 << MODE_SHIFT; 10000000 00000000 00000000 00000000 最大模式
    表示父容器会指定一个可用大小,View的大小不能超过这个值,对应的布局属性是:LayoutPamras.wrap_content

接着继续往下看MeasureSpec这个类,我们主要关心以下几个方法:

  • 1 . makeMeasureSpec
    我们会看到makeMeasureSpec这个方法,这个方法主要是将一个SpecMode值和SpecSize值进行一个打包的操作,打包成MeasureSpec。因为sUseBrokenMakeMeasureSpec 为false,所以我们只要关心else里的代码return (size & ~MODE_MASK) | (mode & MODE_MASK);

makeMeasureSpec方法 这个return表达意思是取MODE_MASK后30位做size,前2位做mode,然后打包返回得到一个MeasureSpec 这样的一个32位int值

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);
}
}
  • 2 . getMode()getSize()
    接着往下看 可以看到两个方法 getMode()和 getSize(),这2个方法表示传入一个MeasureSpec,将MeasureSpec进行一个解包操作,得到它的SpecMode和SpecSize.
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

我们现在知道了view的测量过程和测量规格MeasureSpec
接着我们回到源码去看看DecorView(顶层view)测量的过程。

3.DecorView的测量获取 MeasureSpec 的规则

首先我们知道DecorView开始绘制UI是从performTraversals()方法开始的,它先调用了 performMeasure()方法:

// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

我们看到了这个方法的childWidthMeasureSpec ,childHeightMeasureSpec这两个参数
现在我们需要知道这两个参数是如何得到的,这这个方法的上面我们发现是由以下两句话获得的:

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

我们看到的getRootMeasureSpec()这个方法,它的第一个参数表示窗口的宽,第二参数表示顶层view布局属性的宽。我们进入到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.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

阅读源码发现根据顶层View的不同布局属性,会进入不同的条件中。通过这个方法可以知道顶层View测量的方式经过一定的规则。也就是View的测量过程中 DecorView(顶层View) 的MeasureSpec 是由窗口的大小和自身的LayoutParams所决定的,遵守如下规则

  • 1 .如果DecorView的LayoutParams是:LayoutParams.MATCH_PARENT。那么我们给DecorView设置的MeasureSpec的值,他的模式是精确模式,它的尺寸是窗口大小。
  • 2 . 如果DecorView的LayoutParams是LayoutParams.WARP_CONTENT。那么我们给DecorView设置的MeasureSpec的值,他的模式是最大模式,它的最大尺寸是窗口大小
  • 3 . 如果DecorView的LayoutParams给的是固定大小,那么我们给DecorView设置的MeasureSpec的值,他的模式是精确模式它的尺寸是LayoutParams的大小。

4.DecorView 测量过程

1.获取DecorView 的 MeasureSpec

我们之前已经知道了窗口宽度和layoutParams,再通过getRootMeasureSpec()方法得到了 DecorView 的实际的 MeasureSpec。也就是说从顶层View开始绘制,会先通过一定的规则获取到它的MeasureSpec,再去往下走,
我们接着往下看,会发现performMeasure()中调用的mView.Measure()方法(这里的mView实际上就是顶层的DecorView)。因为measure()是被final修饰的无法被重写,进入到Measure()方法中,在这个方法里,我们主要去看它实现的 onMeasure(widthMeasureSpec, heightMeasureSpec);方法。值得注意的是,根据源码注释我们要知道这个onMeasure方法是可以且必须被他的子View进行重写的。我们都知道DecorView是继承自FrameLayout的,

* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}

2.进入onMeasure()方法遍历测量子控件

所以在这里我们现在要去看看FrameLayout的onMeasure()方法。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
//循环遍历measure子控件
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
....
//这个方法是决定自身的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}

一开始我们会看到一个遍历子View的一个for循环,这个for循环遍历了每一个子View ,调用了measureChildWithMargins(),进入到这个方法中:

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);
}

measureChildWithMargins()实际上是在测量子view。
在这个方法中我们需要关注getChildMeasureSpec()这个方法,它是获取到子View的MeasureSpec的一个方法。
这个方法的三个参数: spec 是指父控件的MeasureSpec ,padding 是指 当前父容器已经使用的空间,childDimension 是指子控件的布局参数对应的尺寸。
现在让我们进入到这个方法中去看看:

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 获取父控件的 specMode和specSize
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

//根据父控件的 specMode 进行判断
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
// 固定大小
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
// MATCH_PARENT
} 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);
}

这段源码是获取view的MeasureSpec的规则:
首先我们要知道获取view的MeasureSpec和顶级View的MeasureSpec是不一样的。
View的MeasureSpec的获取是由父容器MeasureSpec和自身的LayoutParams决定的。
而我们前面也提到了顶层的View也就是DecorView 的MeasureSpec 是由窗口的大小和自身的LayoutParams所决定的。
规则

现在通过阅读源码我们知道这个方法实际上是子控件获取MeasureSpec 的一个规则,最后一行return MeasureSpec.makeMeasureSpec(resultSize, resultMode);表明了这个方法 返回值是MeasureSpec 。
通过这个规则子View获取到它的MeasureSpec ,然后让我们在回到measureChildWithMargins()方法中,继续往下看。
我们会看到执行了child.measure()measure()方法中又会去到view类中的onMeasure()方法,紧接着在这个方法中又会去调用setMeasuredDimension()然后通过setMeasuredDimensionRaw()去得到子view的宽高。注:在子view中不需要再去测量直接就通过onMeasure中的一系列方法获得自身的宽高。 在我们测量子View的过程中,onMeasure() 方法里有这样一个方法:

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

我们需要注意这个方法的参数中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;
}

这个方法中我们看到通过getModegetSize获得measureSpec的mode和size值,在通过specMode的值进入到不同的case中。通过源码我们可以知道,我们在view的测量过程中,无论它是什么模式,他的测量尺寸specSize都是测量规格中的specSize 。
如果我们在自定义View的过程里,不去重写我们的onMeasure()方法,那么在布局文件中写wrap_content和match_content的效果是一样的。所以我们在定义view的时候,一定要去重写onMeasure()方法。

3.梳理decorView的测量流程

  • 1 . 测量结束

    最后我们在回到for循环。当循环遍历执行结束后,至此我们对子View的全部测量就结束了。往下走,我们会看到setMeasuredDimension()这个方法,这个方法是用来决定父容器的自身的宽高,而它自身的宽高是由我们的父容器的子控件所决定的。 当他内部的所有方法执行完毕后,到此我们DecorView的 measure就全部结束了。

  • 2 . 梳理测量流程

    现在我们来回顾一下整个流程,
    首先有一点我们要明白:DecorView是继承自FrameLayout,而FrameLayout是一个ViewGroup。也就是说对DecorView的测量实际上就是对ViewGroup的测量。我们要知道ViewGroup的测量和View的测量是有区别的。

    然后我们再来看流程 首先我们要知道测量一个view之前我们需要知道它的测量规格,而对于顶层View也就是DecorView来说,他的测量规格是通过getRootMeasureSpec(mWidth, lp.width);所获得的,它是获得MeasureSpec的规则。第一个参数代表着窗口的宽,第二个参数是顶层view布局属性的宽(高同理)。

    进入到performMeasure()中, 我们看到mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);这里的mView实际上就是我们的DecorView,进入到measure()方法中因为它被final修饰所以无法被重写,

    接着往下看可以看到onMeasure(widthMeasureSpec, heightMeasureSpec);方法的调用。值得注意的是,这个onMeasure方法是可以且必须被子View重写的,所以我们去到DecorView的onMeasure()方法中,然后发现它最终调用了父类也就是FrameLayout的onMeasure()方法。在这个onMeasure()方法中我们看到for循环对子view进行了遍历测量,而子view测量的流程是:先通过measureChildWithMargins()中的getChildMeasureSpec()获取到子view的MeasureSpec,再通过child.measure(childWidthMeasureSpec, childHeightMeasureSpec);方法传入获取到的宽高的MeasureSpec。

    最后经过以下方法:
    childe.measure()---> onMeasure--->setMeasuredDimension()---->setMeasuredDimensionRaw()后方法直接获取到子view自身的宽和高。当子view遍历循环结束后。在后面会调用setMeasuredDimension()---->setMeasuredDimensionRaw()方法去获取DecorView自身的宽高,而它自身的宽高是由它内部的子View所决定的。至此我们整个DecorView的测量也就全部完成了。
    从以上我们也可以得出顶层view对子view的测量实际上做的是一个递归操作。

5.总结

对于ViewGroup的测量会先调用measure(),然后去调用onMeasure()方法,在重写的onMeasure()方法中,我们需要去通过measureChildWithMargins()方法测量子控件宽高,在测量子view宽高之前我们又要先通过getChildMeasureSpec()方法获得子控件的测量规格MeasureSpec,这个测量规格遵循一定的规则。在调用子控件的measure()方法,最后完成了子控件的测量。接着ViewGroup 会调用setMeasuredDimension()然后通过setMeasuredDimensionRaw()方法保存自己的宽高。

对于view的测量 当我们在对子控件进行测量的时候,它会有个child.measure()方法被调用,他会来到view的measure()方法,在measure()方法中又会去到onMeasure()方法,在这个方法中又会去调用setMeasuredDimension()然后通过setMeasuredDimensionRaw()去得到子控件的宽高。注:在子控件中不需要再去测量直接就通过onMeasure中的一系列方法获得自身的宽高。

所以我们得到结论,如果在自定义ViewGroup 时,我们先要调用onMeasure方法去测量子控件的宽高,再去调用setMeasuredDimension()去确定自身的宽高。而在自定义view的时候。我们需要去重写onMeasure()方法去重新确定view的宽高,否则布局文件中写wrap_content和match_content的效果是一样的。

view的测量流程

二、布局performLayout

1.laout布局

了解完了view的测量,我们在来看看view的布局方式,首先我们需要在viewRootImpl类中的performTraversals中找到 performLayout(lp, mWidth, mHeight);方法,这个方法的第一个参数是指顶层布局的布局属性,后面两个参数是顶层布局的宽和高。然后我们进入到()方法中,我们看到 mView赋值给了host,那么这个host就是我们的顶层view。我们往下看可以看到host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());传入的是测量的宽和高。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
mInLayout = false;
}

之后我们在进入到layout()方法中

public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

...
onLayout(changed, l, t, r, b);
....

这个方法实际上是通过setFrame()方法确定我们上下左右的值。接着我们往下看可以看到 这里也调用了一个 onLayout(changed, l, t, r, b); 进入到这个方法中,我们发现它一个空方法,供子类实现。如果我们是ViewGroup,我们需要在onLayout里对子view进行摆放。如果是view,那么就不必重写这个方法。
我们这里以FrameLayout为例,我们进入到FrameLayout的onLayout()方法中:它会先调用一个layoutChildren()这个方法,这个方法就是去遍历我们的子view,对子view的控件摆放,然后又会去执行child.layout()方法 形成了递归的操作。

2.总结

view的布局实际上是调用view.layout确定自身的位置,即用在layout()方法中的setFrame()方法确定mLeft,mTop,mRight和mBottom的值。确定位置之后,会执行onLayout()方法,如果此时是ViewGroup那么需要调用onLayout来确定子view的位置(通过递归的方式确定子view的位置),如果是view就不需要。

三、绘制performDraw

1.draw布局

最后我们在去看view的绘制,同样的我们需要在viewRootImpl类中的performTraversals中找到 performDraw();,我们进入到中,

private void performDraw() {
...
draw(fullRedrawNeeded);
...
}

我们看到了一个draw()方法,

private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}

在进入到其中我们可以看到drawSoftWare()方法, 然后我们在这个drawSoftWare()可以看到一个关键方法 mView.draw(canvas);,

 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
...
mView.draw(canvas)
...
}

这里就会开始我们DecorView的绘制,通过它的注释我们可以看到绘制的步骤: 1. 绘制背景 2. 绘制图层保存 3. 绘制内容 4. 绘制子View 5.绘制边缘恢复图层 6.绘制装饰

public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background //绘制背景
* 2. If necessary, save the canvas' layers to prepare for fading //如果需要 进行图层保存
* 3. Draw view's content //绘制view的内容
* 4. Draw children // 绘制子View 如果是容器则去绘制
* 5. If necessary, draw the fading edges and restore layers //如果有需要绘制边缘恢复图层
* 6. Draw decorations (scrollbars for instance) // 绘制装饰
*/

// Step 1, draw the background, if needed
int saveCount;

if (!dirtyOpaque) {
drawBackground(canvas);
}

// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

...

如果是绘制view,我们需要重写view的onDraw方法,如果自定义的是viewGroup ,那么我们还需要去绘制子view,这个时候我们进入到viewGroup的dispatchDraw()方法中,我们可以看到viewGroup里面实现了dispatchDraw()方法。 在这个方法中同样是有一个for循环去遍历我们的子view,执行了drawChild()方法去绘制我们的子view。实际上drawChild()方法内部就是child.draw()方法,这里也是一个绘制的递归操作。

2.总结

ViewGroup的绘制流程
可以总结以下几点:
绘制流程的六个步骤:
1、对View的背景进行绘制 drawBackground(canvas)
2、保存当前的图层信息(可跳过)
3、绘制View的内容 onDraw(canvas)
4、对View的子View进行绘制(如果有子View) dispatchDraw(canvas)
5、绘制View的褪色的边缘,类似于阴影效果(可跳过)
6、绘制View的装饰(例如:滚动条)
其中第2步和第5步是可以跳过的。

那么view 的绘制流程 就是

  • 1 . 绘制背景 drawBackground(canvas)
  • 2 . 绘制自己 onDraw(canvas)
  • 3 . 绘制前景,滚动条等装饰 onDrawForeground(canvas)

四、View的绘制流程总结

在自定义开发中,我们需要去实现onMeasure()—> onLayout() — onDraw()
如果我们自定义的是ViewGroup方法onMeasure()—> onLayout () — onDraw()
如果是自定义view的方法 onLayout方法不用 去实现,而onDraw()方法可选。