
简介
我们知道,在 Android 中,View 绘制主要包含 3 大流程:
measure(测量):主要用于确定 View 的测量宽/高。
layout(布局):主要用于确定 View 在父容器中的放置位置。
draw(绘制):结合前面两步结果,将 View 真正绘制到屏幕上。
Android 中,主要有两种视图:View和ViewGroup,其中:
-
View:就是一个独立的视图 -
ViewGroup:一个容器组件,该容器可容纳多个子视图,即ViewGroup可容纳多个View或ViewGroup,且支持嵌套。
虽然ViewGroup继承于View,但是在 View 绘制三大流程中,某些流程需要区分View和ViewGroup,它们之间的操作并不完全相同,比如:
-
View和ViewGroup都需要进行 measure,确定各自的测量宽/高。View只需直接测量自身即可,而ViewGroup通常都必须先测量所有子View,最后才能测量自己 - 通常
ViewGroup先定位自己的位置(layout),然后再定位其子View 位置(onLayout) -
View需要进行 draw 过程,而ViewGroup通常不需要(当然也可以进行绘制),因为ViewGroup更多作为容器存在,起存储放置功能
measure 流程
对 View 进行测量,主要包含两个步骤:
- 求取 View 的测量规格
MeasureSpec。 - 依据上一步求得的
MeasureSpec,对 View 进行测量,求取得到 View 的最终测量宽/高。
MeasureSpec
对于第一个步骤,即求取 View 的MeasureSpec,首先我们来看下MeasureSpec的源码定义:
// frameworks/base/core/java/android/view/View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 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(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
// 获取测量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// 获取测量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
}
MeasureSpec是View的一个公有静态内部类,它是一个 32 位的int值,高 2 位表示 SpecMode(测量模式),低 30 位表示 SpecSize(测量尺寸/测量大小)。
MeasureSpec将两个数据打包到一个int值上,可以减少对象内存分配,并且其提供了相应的工具方法可以很方便地让我们从一个int值中抽取出 View 的 SpecMode 和 SpecSize。
一个MeasureSpec表达的是:该 View 在该种测量模式(SpecMode)下对应的测量尺寸(SpecSize)。其中,SpecMode 有三种类型:
UNSPECIFIED:表示父容器对子View 未施加任何限制,子View 尺寸想多大就多大。EXACTLY:如果子View 的模式为EXACTLY,则表示子View 已设置了确切的测量尺寸,或者父容器已检测出子View 所需要的确切大小。
这种模式对应于LayoutParams.MATCH_PARENT和子View 设置具体数值两种情况。AT_MOST:表示自适应内容,在该种模式下,View 的最大尺寸不能超过父容器的 SpecSize,因此也称这种模式为 最大值模式。
这种模式对应于LayoutParams.WRAP_CONTENT。
LayoutParams
对 View 进行测量,最关键的一步就是计算得到 View 的MeasureSpec,子View 在创建时,可以指定不同的LayoutParams(布局参数),LayoutParams的源码主要内容如下所示:
// frameworks/base/core/java/android/view/ViewGroup.java
public static class LayoutParams {
...
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;
/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
/**
* Information about how wide the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
public int width;
/**
* Information about how tall the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT
* in API Level 8) or WRAP_CONTENT, or an exact size.
*/
public int height;
...
}
其中:
-
LayoutParams.MATCH_PARENT:表示子View 的尺寸与父容器一样大(注:需要减去父容器padding部分空间,让父容器padding生效) -
LayoutParams.WRAP_CONTENT:表示子View 的尺寸自适应其内容大小(注:需要包含子View 本身的padding空间) -
width/height:表示 View 的设置宽/高,即layout_width和layout_height设置的值,其值有三种选择:LayoutParams.MATCH_PARENT、LayoutParams.WRAP_CONTENT和 具体数值。
LayoutParams会受到父容器的MeasureSpec的影响,测量过程会依据两者之间的相互约束最终生成子View 的MeasureSpec,完成 View 的测量规格。
简而言之,View 的MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同决定(DecorView的MeasureSpec是由自身的LayoutParams和屏幕尺寸共同决定,参考后文)。也因此,如果要求取子View 的MeasureSpec,那么首先就需要知道父容器的MeasureSpec,层层逆推而上,即最终就是需要知道顶层View(即DecorView)的MeasureSpec,这样才能一层层传递下来,这整个过程需要结合Activity的启动过程进行分析。
Activity 视图基本结构
我们知道,在 Android 中,Activity是作为视图组件存在,主要就是在手机上显示视图界面,可以供用户操作,Activity就是 Andorid 中与用户直接交互最多的系统组件。
Activity的基本视图层次结构如下所示:
Activity中,实际承载视图的组件是Window(更具体来说为PhoneWindow),顶层View 是DecorView,它是一个FrameLayout,DecorView内部是一个LinearLayout,该LinearLayout由两部分组成(不同 Android 版本或主题稍有差异):TitleView和ContentView,其中,TitleView就是标题栏,也就是我们常说的TitleBar或ActionBar,ContentView就是内容栏,它也是一个FrameLayout,主要用于承载我们的自定义根布局,即当我们调用setContentView(...)时,其实就是把我们自定义的布局设置到该ContentView中。
当Activity启动完成后,最终就会渲染出上述层次结构的视图。
DecorView 测量规格
因此,如果我们要求取得到子View 的MeasureSpec,那么第一步就是求取得到顶层View(即DecorView)的MeasureSpec。大致过程如下所示:
-
在
Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:// frameworks/base/core/java/android/app/ActivityThread.java final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... ActivityClientRecord r = performResumeActivity(token, clearHide); ... // 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow r.window = r.activity.getWindow(); // PhoneWindow 绑定的顶层视图:DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); ... // 添加 DecorView 到 PhoneWindow 上(相当于设置 Activity 根视图) wm.addView(decor, l); ... }其中,
r.window.getDecorView()实际调用的是PhoneWindow.getDecorView(),其会返回顶层DecorView(不存在时会自动实例化):// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java public class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the top-level view of the window, containing the window decor. private DecorView mDecor; ... @Override public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; } private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ... } ... } protected DecorView generateDecor() { // 实例化 DecorView return new DecorView(getContext(), -1); } ... }然后,
r.window.getAttributes()实际调用的是Window.getAttributes():// frameworks/base/core/java/android/view/Window.java public abstract class Window { private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); ... public final WindowManager.LayoutParams getAttributes() { return mWindowAttributes; } } // frameworks/base/core/java/android/view/WindowManager.java public interface WindowManager extends ViewManager { ... public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { public LayoutParams() { // DecorView 的布局参数为 MATCH_PARENT super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); ... } } }这里可以看到,此处
r.window.getAttributes()返回的是一个WindowManager.LayoutParams实例,对应的最终宽/高布局参数为LayoutParams.MATCH_PARENT,最后通过wm.addView(decor,l)将DecorView添加到WindowManager上(最终其实是设置到ViewRootImpl上),所以DecorView的布局参数为MATCH_PARENT。 -
View 的绘制流程真正开始的地方为
ViewRootImpl.performTraversals(),在其中,有如下代码片段:// frameworks/base/core/java/android/view/ViewRootImpl.java private void performTraversals() { ... int desiredWindowWidth; int desiredWindowHeight; ... // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); ... } private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { ... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... }此处的
desiredWindowWidth和desiredWindowHeight是屏幕的尺寸,内部最终会调用到ViewRootImpl.getRootMeasureSpec(...),其源码如下所示:// frameworks/base/core/java/android/view/ViewRootImpl.java 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; }ViewRootImpl.getRootMeasureSpec(...)见名知意,其实就是用来获取顶层View(即DecorView)的MeasureSpec,其逻辑如下:- 当
DecorView的LayoutParams为MATCH_PARENT时,说明DecorView的大小与屏幕一样大,而又由于屏幕大小是确定的,因此,其 SpecMode 为EXACTLY,SpecSize 为windowSize,; - 当
DecorView的LayoutParams为WRAP_CONTENT时,说明DecorView自适应内容大小,因此它的大小不确定,但是最大不能超过屏幕大小,故其 SpecMode 为AT_MOST,SpecSize 为windowSize; - 其余情况为
DecorView设置了具体数值大小或UNSPECIFIED,故以DecorView为主,其 SpecMode 为EXACTLY,SpecSize 就是自己设置的值,即rootDimension;
结合我们上面的分析,由于
DecorView的LayoutParams为MATCH_PARENT,因此,DecorView的MeasureSpec最终为:MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY),即DecorView的 SpecMode 为EXACTLY,SpecSize 为屏幕大小。 - 当
默认测量(measure)
经过上述步骤求取得到 View 的MeasureSpec后,接下来就可以真正对 View 进行测量,求取 View 的最终测量宽/高:
Android 内部对视图进行测量的过程是由View#measure(int, int)方法负责的,但是对于View和ViewGroup,其具体测量过程有所差异。
因此,对于测量过程,我们分别对View和ViewGroup进行分析:
-
View测量:View的测量过程由View.measure(...)方法负责,其源码如下所示:// frameworks/base/core/java/android/view/View.java public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); ... }View#measure(int, int)中参数widthMeasureSpec和heightMeasureSpec是由父容器传递进来的,具体的测量过程请参考后文内容。需要注意的是,
View#measure(int, int)是一个final方法,因此其不可被覆写,实际真正测量 View 自身使用的是View#onMeasure(int, int)方法,如下所示:// frameworks/base/core/java/android/view/View.java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }onMeasure(...)主要做了三件事:-
首先通过
getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法获取得到 View 的推荐最小测量宽/高:// frameworks/base/core/java/android/view/View.java protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }这两个方法的实现原理是一致的,这里就只分析
getSuggestedMinimumWidth()方法实现,该方法内部是一个三目运算符,可以很清晰看出,当 View 没有设置背景时,它的宽度就为mMinWidth,mMinWidth就是android:minWidth这个属性对应设置的值(未设置android:minWidth时,其值默认为0),当 View 设置了背景时,它的宽度就是mMinWidth和mBackground.getMinimumWidth()之中的较大值,其中,mBackground.getMinimumWidth()源码如下:// frameworks/base/graphics/java/android/graphics/drawable/Drawable.java /* * @return The minimum width suggested by this Drawable. If this Drawable * doesn't have a suggested minimum width, 0 is returned. */ public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; } // 不同子类可实现具体大小 public int getIntrinsicWidth() { return -1; }Drawable.getMinimumWidth()就是返回 Drawable 的原始宽度,如果该 Drawable 未设置宽度,则返回0。综上,
getSuggestedMinimumWidth()/getSuggestedMinimumHeight()其实就是用于获取 View 的最小测量宽/高,其具体逻辑为:当 View 没有设置背景时,其最小宽/高为android:minWidth/android:mMinHeight所指定的值,当 View 设置了背景时,其最小测量宽/高为android:minWidth/android:minHeight与其背景图片宽/高的较大值。简而言之,View 的最小测量宽/高为
android:minWidth/android:minHeight和其背景宽/高之间的较大值。 -
通过
getDefaultSize(...)获取到 View 的默认测量宽/高,具体获取过程如下所示:// frameworks/base/core/java/android/view/View.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 = specSize; break; } return result; }此处的
size是通过getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法获取得到系统建议 View 的最小测量宽/高。参数
measureSpec是经由View.measure(...)->View.onMeasure(...)->View.getDefaultSize(...)调用链传递进来的,表示的是当前 View 的MeasureSpec。getDefaultSize(...)内部首先会获取 View 的测量模式和测量大小,然后当 View 的测量模式为UNSPECIFIED时,也即未限制 View 的大小,因此此时 View 的大小就是其原生大小(也即android:minWidth或背景图片大小),当 View 的测量模式为AT_MOST或EXACTLY时,此时不对这两种模式进行区分,一律将 View 的大小设置为测量大小(即 SpecSize)。
注:实际上,这里可以看到,默认情况下,View 不区分AT_MOST和EXACTLY,也即,当自定义 View 时,LayoutParams.WRAP_CONTENT和LayoutParams.MATCH_PARENT效果是一样的,均为MATCH_PARENT的效果,原因是 子View 的MeasureSpec是由父容器传递进来的,父容器是通过ViewGroup#getChildMeasureSpec(...)方法获取得到 子View 的MeasureSpec,在该方法内部,子View 的测量模式无论是AT_MOST或是EXACTLY,其测量大小都为父容器大小(确定的说,是父容器剩余空间大小),因此其效果就等同于MATCH_PARENT,具体源码详情分析请参考后文。总之,一般自定义 View 时,都需要覆写
onMeasure(...),并为其LayoutParams.WRAP_CONTENT设置一个默认大小,如下所示:@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 先进行默认测量 super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 默认大小依据自己灵活配置,这里为 400px int defaultSize = 400; // 获取默认测量宽/高 int width = this.getMeasuredWidth(); int height = this.getMeasuredHeight(); // 获取 View 的布局参数 ViewGroup.LayoutParams lp = this.getLayoutParams(); // 宽度为自适应,则设置一个默认大小 if(lp.width == ViewGroup.LayoutParams.WARP_CONTENT) { width = defaultSize; } // 高度为自适应,则设置一个默认大小 if(lp.height == ViewGroup.LayoutParams.WARP_CONTENT) { height = defaultSize; } this.setMeasuredDimension(width, height); } -
获取到 View 的测量宽/高后,通过
setMeasuredDimension(...)记录 View 的测量宽/高:// frameworks/base/core/java/android/view/View.java 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; }setMeasuredDimension(...)其实就是将 View 的最终测量宽/高设置到View.mMeasuredWidth/View.mMeasuredHeight属性中,完成测量过程。
-
-
ViewGroup测量:ViewGroup是一个抽象类,其继承于View:public abstract class ViewGroup extends View implements ViewParent, ViewManager {...}ViewGroup的测量过程也是由View.measure(...)负责,因此实际负责测量的是ViewGroup.onMeasure(...)方法,但是由于ViewGroup的作用是用于容纳子View,如果想测量ViewGroup,则必须先测量其子View,而又由于不同的ViewGroup有不同的布局特性,因此无法抽象出一套标准的测量流程,所以ViewGroup本身没有覆写onMeasure(...)方法(交由具体自定义ViewGroup覆写),但是它提供了一些测量子View 的辅助方法,比如:measureChildren(...)、measureChildrenWithMargins(...)、measureChild(...)、getChildMeasureSpec(...)等等,自定义ViewGroup可借助这些辅助方法,在onMeasure(...)中完成子View 的测量,然后最终才能完成自己的测量。我们随便选择一个辅助方法,比如
ViewGroup#measureChildWithMargins(...),查看其源码:// android/view/ViewGroup.java protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { // 获取 子View 的 LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 获取 子View 的 MeasureSpec // 父容器已使用的空间为:自身已使用空间 + 自身的 padding + 子View的 margin 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); // 测量子View child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }代码非常简洁易懂,其核心就是先获取得到 子View 的
MeasureSpec(getChildMeasureSpec(...)),然后就可以对 子View 进行测量(child.measure(...))。View#measure(...)的测量详情上述我们已经介绍过了,这里我们主要来看下ViewGroup#getChildMeasureSpec(...)获取 子View 测量规格的具体过程:// android/view/ViewGroup.java /** * * @param spec 父容器的 MeasureSpec * @param padding 父容器已使用的空间(比如:父View自身的 padding + 子View的 margin) * @param childDimension 子View的 LayoutParams * @return 子View 的 MeasureSpec */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // 当前View(即父容器)的测量模式 int specMode = MeasureSpec.getMode(spec); // 父容器的测量大小 int specSize = MeasureSpec.getSize(spec); // 父容器剩余可用空间 int size = Math.max(0, specSize - padding); // 子View 最终测量大小 int resultSize = 0; // 子View 最终测量模式 int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us // 父容器大小已确定 case MeasureSpec.EXACTLY: if (childDimension >= 0) { // 子View 设置了具体大小(精确数值) resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 子View 大小撑满父容器 // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 子View 自适应内容大小 // 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; } // 子View 的最终测量规格 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }getChildMeasureSpec(...)其实就是ViewGroup对其内部 子View 的默认测量过程,其核心逻辑为:-
如果父容器的测量模式为
EXACTLY:即父容器测量大小是确切的,且其剩余空间精确为size,此时:-
如果 子View 的
LayoutParams为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension,测量模式为EXACTLY。 -
如果 子View 的
LayoutParams为MATCH_PARENT:表示 子View 的大小撑满父容器,由于父容器是EXACTLY,即大小已知,因此,子View 也是大小已知,故其测量模式为EXACTLY,且其测量大小就是父容器剩余空间大小,具体为size。 -
如果 子View 的
LayoutParams为WRAP_CONTENT:表示 子View 自适应内容大小,但是其尺寸最大不能超过父容器剩余空间,因此其测量模式为AT_MOST,测量大小为父容器剩余空间size。
-
如果 子View 的
-
如果父容器的测量模式为
AT_MOST:即父容器自适应其内容大小,也即父容器大小不确定,此时:-
如果 子View 的
LayoutParams为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension,测量模式为EXACTLY。 -
如果 子View 的
LayoutParams为MATCH_PARENT:表示 子View 的大小撑满父容器,由于父容器是AT_MOST,即大小未知,因此,子View 也是大小未知,即其测量模式为AT_MOST,且其测量大小不超过父容器剩余空间大小size。 -
如果 子View 的
LayoutParams为WRAP_CONTENT:表示 子View 自适应内容大小,但是其尺寸最大不能超过父容器剩余空间,因此其测量模式为AT_MOST,测量大小为父容器剩余空间size。
-
如果 子View 的
-
如果父容器的测量模式为
UNSPECIFIED:即父容器大小无限制,此时:-
如果 子View 的
LayoutParams为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension,测量模式为EXACTLY。 -
如果 子View 的
LayoutParams为MATCH_PARENT:表示 子View 的大小撑满父容器,由于父容器大小无限制,因此,子View 的大小也是无限制的,所以,子View 的测量模式为UNSPECIFIED,测量大小未知,通常设置为0,表示无限。 -
如果 子View 的
LayoutParams为WRAP_CONTENT:表示 子View 自适应内容大小,由于父容器大小无限制,因此,子View 的测量大小也是无限制的,所以其模式为UNSPECIFIED,测量大小无限,通常使用0进行表示。
-
如果 子View 的
上述的逻辑总结如下图所示:(注:图片来源于互联网,侵删)
ViewGroup#getChildMeasureSpec注:前面我们一直强调:子View 的
MeasureSpec是由其LayoutParams和父容器的MeasureSpec共同约束构造而成,其实这部分逻辑就是ViewGroup#getChildMeasureSpec(...)方法负责的,可以很清晰看到,子View 的MeasureSpec就是在父容器MeasureSpec约束下,与其自身LayoutParams共同协商决定的。 -
综上,无论是对View的测量还是ViewGroup的测量,都是由View#measure(int widthMeasureSpec, int heightMeasureSpec)方法负责,然后真正执行 View 测量的是 View 的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。
具体来说,View直接在onMeasure(...)中测量并设置自己的最终测量宽/高。在默认测量情况下,View的测量宽/高由其父容器的MeasureSpec和自身的LayoutParams共同决定,当View自身的测量模式为LayoutParams.UNSPECIFIED时,其测量宽/高为android:minWidth/android:minHeight和其背景宽/高之间的较大值,其余情况皆为自身MeasureSpec指定的测量尺寸。
而对于ViewGroup来说,由于布局特性的丰富性,只能自己手动覆写onMeasure(...)方法,实现自定义测量过程,但是总的思想都是先测量 子View 大小,最终才能确定自己的测量大小。
layout 流程
当确定了 View 的测量大小后,接下来就可以来确定 View 的布局位置了,也即将 View 放置到屏幕具体哪个位置。
View layout
View 的布局过程由View#layout(...)负责,其源码如下:
// android/view/View.java
/**
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
...
setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
View#layout(...)主要就做了两件事:
-
setFrame(...):首先通过View#setFrame(...)来确定自己的布局位置,其源码如下:// android/view/View.java protected boolean setFrame(int left, int top, int right, int bottom) { ... // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; }setFrame(...)其实就是更新记录 View 的四个顶点位置,这样 View 在父容器中的坐标位置就确定了。 -
onLayout(...):setFrame(...)是用于确定 View 自身的布局位置,而onLayout(...)主要用于确定 子View 的布局位置:protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }由于 View 不包含子组件,因此其
onLayout是一个空实现。
ViewGroup layout
ViewGroup 的布局流程由ViewGroup#layout(...)负责,其源码如下:
// android/view/ViewGroup.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
...
@Override
public final void layout(int l, int t, int r, int b) {
...
super.layout(l, t, r, b);
...
}
可以看到,ViewGroup#layout(...)最终也是通过View#layout(...)完成自身的布局过程,一个注意的点是,ViewGroup#layout(...)是一个final方法,因此子类无法覆写该方法,主要是ViewGroup#layout(...)方法内部对子视图动画效果进行了相关设置。
由于ViewGroup#layout(...)内部最终调用的还是View#layout(...),因此,ViewGroup#onLayout(...)就会得到回调,用于处理 子View 的布局放置,其源码如下:
// android/view/ViewGroup.java
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
由于不同的ViewGroup,其布局特性不同,因此ViewGroup#onLayout(...)是一个抽象方法,交由ViewGroup子类依据自己的布局特性,摆放其 子View 的位置。
draw 流程
当 View 的测量大小,布局位置都确定后,就可以最终将该 View 绘制到屏幕上了。
View 的绘制过程由View#draw(...)方法负责,其源码如下:
// android/view/View.java
public void draw(Canvas canvas) {
...
/*
* 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
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
...
// Step 2, save the canvas' layers
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
...
if (drawTop) {
...
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
...
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
...
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
...
canvas.drawRect(right - length, top, right, bottom, p);
}
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
其实注释已经写的很清楚了,View#draw(...)主要做了以下 6 件事:
绘制背景:
drawBackground(...)如果有必要的话,保存画布图层:
Canvas.saveLayer(...)-
绘制自己:
onDraw(...),其源码如下:// android/view/View.java protected void onDraw(Canvas canvas) { }View#onDraw(...)是一个空方法,因为每个 View 的绘制都是不同的,自定义 View 时,通常会覆写该方法,手动绘制该 View 内容。 -
绘制子View:
dispatchDraw(...),其源码如下:// android/view/View.java protected void dispatchDraw(Canvas canvas) { }由于 View 没有子元素,因此其
dispatchDraw是一个空实现。查看下
ViewGroup#dispatchDraw(...),其源码如下:// android/view/ViewGroup.java @Override protected void dispatchDraw(Canvas canvas) { ... final int childrenCount = mChildrenCount; final View[] children = mChildren; ... for (int i = 0; i < childrenCount; i++) { ... more |= drawChild(canvas, child, drawingTime); ... } ... } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }可以看到,其内部主要就是遍历子View,最后通过
child.draw(...)让子View自己进行绘制。 如果有必要的话,绘制淡化效果并恢复图层:
Canvas.drawRect(...)-
绘制装饰:
onDrawForeground(...),其源码如下:// android/view/View.java public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; ... foreground.draw(canvas); } }其实主要就是绘制滚动条,前景图片等视图相关的装饰。
绘制起始流程
我们知道,在Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:
// frameworks/base/core/java/android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
...
// 回调 Activity.onResume() 方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
...
// 获取当前 Activity 实例
final Activity a = r.activity;
...
// 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow
r.window = r.activity.getWindow();
// PhoneWindow 绑定的顶层视图:DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
// 添加 DecorView 到 PhoneWindow 上(相当于设置 Activity 根视图)
wm.addView(decor, l);
...
}
可以看到,ActivityThread.handleResumeActivity(...)主要就是获取到当前Activity绑定的ViewManager,最后调用ViewManager.addView(...)方法将DecorView设置到PhoneWindow上,也即设置到当前Activity上。ViewManager是一个接口,WindowManager继承ViewManager,而WindowManagerImpl实现了接口WindowManager,此处的ViewManager.addView(...)实际上调用的是WindowManagerImpl.addView(...),源码如下所示:
// frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
...
}
WindowManagerImpl.addView(...)内部转发到WindowManagerGlobal.addView(...):
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
...
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
...
// 实例化一个 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
// 将 ViewRootImpl 与 DecorView 关联到一起
root.setView(view, wparams, panelParentView);
...
}
...
}
在WindowManagerGlobal.addView(...)内部,会创建一个ViewRootImpl实例,然后调用ViewRootImpl.setView(...)将ViewRootImpl与DecorView关联到一起:
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// 将 DecorView 绑定到 ViewRootImpl.mView 属性上
mView = view;
...
mWindowAttributes.copyFrom(attrs);
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
...
}
...
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查是否处于主线程
checkThread();
...
scheduleTraversals();
}
}
...
}
ViewRootImpl.setView(...)内部首先关联了传递过来的DecorView(通过属性mView指向DecorView即可建立关联),然后最终调用requestLayout(),而requestLayout()内部又会调用方法scheduleTraversals():
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
Choreographer mChoreographer;
...
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 开始执行绘制
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...
void scheduleTraversals() {
if (!mTraversalScheduled) { // 同一帧内不会多次调用遍历
mTraversalScheduled = true;
// 发送一个同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 将 UI 绘制任务发送到 Choreographer,回调触发 mTraversalRunnable,执行绘制操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
...
void doTraversal() {
...
performTraversals();
...
}
...
}
ViewRootImpl.scheduleTraversals()内部主要做了两件事:
- 调用
MessageQueue.postSyncBarrier()方法发送一个同步屏障,同步屏障可以拦截Looper对同步消息的获取与分发,即加入同步屏障后,此时Looper只会获取和处理异步消息,如果没有异步消息,则进入阻塞状态。 - 通过
Choreographer.postCallback(...)发送一个Choreographer.CALLBACK_TRAVERSAL的异步视图渲染消息。因为前面已经发送了一个同步屏障,因此此处的视图绘制渲染消息会优先被处理。
Choreographer.postCallback(...)会申请一次 VSYNC 中断信号,当 VSYNC 信号到达时,便会回调Choreographer.doFrame(...)方法,内部会触发已经添加的回调任务,Choreographer的回调任务有以下四种类型:
// 回调 INPUT 任务
doCallbacks(Choreographer.CALLBACK_INPUT, mframeTimeNanos);
// 回调 ANIMATION
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 回调 View 绘制任务 TRAVERSAL
doCallbacks(Choreographer,CALLBACK_TRAVERSAL, frameTimeNanos);
// API Level 23 新增,COMMIT
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
因此,ViewRootImpl.scheduleTraversals(...)内部通过mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)发送的异步视图渲染消息就会得到回调,即回调mTraversalRunnable.run()方法,最终会执行doTraversal()方法,而doTraversal()内部又会调用performTraversals()方法,该方法才是真正开始执行 View 绘制流程的地方,其源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
...
private void performTraversals() {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
...
}
...
}
综上,performTraversals()会依次调用performMeasure(...)、performLayout(...)和performDraw()三个方法,这三个方法会依次完成顶层View(即DecorView)的测量(measure)、布局(layout)和绘制(draw)流程,具体详情请参考后文。
到此,我们才真正进入 View 绘制流程,总结一下上述流程,如下图所示:

performMeasure
书接前文,我们知道,真正开始 View 绘制流程是ViewRootImpl.performTraversals(),该方法内部首先进行的是performMeasure(...)流程:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 调用 DecorView.measure(...)
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
此处的mView其实就是DecorView,其赋值指向在ViewRootImpl.setView(...)中进行,可以看到,performMeasure(...)实际调用的是DecorView.measure(...),所以最终会回调DecorView#onMeasure(...)方法,其源码如下:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
...
}
可以看到,DecorView#onMeasure(...)内部将测量过程交由其父类,即FrameLayout进行处理,那我们看下FrameLayout#onMeasure(...)源码:
// frameworks/base/core/java/android/widget/FrameLayout.java
public class FrameLayout extends ViewGroup {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取 子View 数量
int count = getChildCount();
...
// 最大高度
int maxHeight = 0;
// 最大宽度
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
// 获取 子View
final View child = getChildAt(i);
// 只对可见的 子View 进行测量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 测量子View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// 获取 子View 的布局参数
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 获取当前子View的宽度,包含其外边距,记录子View的最大宽度
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
// 记录子View的最大高度
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
...
}
}
// Account for padding too
// 最大宽度包含前景偏移量:padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
// 最大高度包含前景偏移量:padding
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
// 比较子View 和 系统建议的 子View 最小高度,获取两者中的较大值
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
// 比较子View 和 系统建议的 子View 最小宽度,获取两者中的较大值
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
// 子View 高度和 前景图片高度比较,记录其中较大值
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
// 子View 高度和 前景图片宽度比较,记录其中较大值
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// 记录测量结果
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
...
}
...
}
FrameLayout的布局特性为:所有 子View 层叠在一起,所以FrameLayout的测量宽/高就是其所有 子View 中最大的宽和高,因此FrameLayout#onMeasure(...)的核心逻辑就是遍历其所有子View,然后通过measureChildWithMargins(...)(该方法前面内容已详细介绍)测量子View,然后就可以获取 子View 的宽/高,记录其中最大的宽/高值,作为自己的测量宽/高。
经过以上步骤,DecorView的测量就已经完成了。
综上,ViewRootImpl#performMeasure(...)其实就是对DecorView的测量过程(DecorView#measure(...)),DecorView是一个FrameLayout,其测量过程主要由FrameLayout#onMeasure(...)负责,内部主要测量逻辑是先遍历所有子View,让 子View 先自己进行测量(child.measure(...)),然后就可以获取 子View 的测量大小,记录所有 子View 中占比最大的测量宽/高,作为自己的最终测量大小。
performLayout
ViewRootImpl#performMeasure(...)完成对DecorView的测量后,接下来执行的是ViewRootImpl#performLayout(...),其源码如下:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
// cache mView since it is used so much below...
final View host = mView;
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
其中,参数lp的width和height均为MATCH_PARENT,desiredWindowWidth和desiredWindowHeight为屏幕宽/高,mView为DecorView。
所以,performLayout(...)内部其实就是调用DecorView#layout(...),前面 layout 流程中介绍过,ViewGroup#layout(...)内部最终会通过View#layout(...)进行布局,而View#layout(...)内部最终通过View#setFrame(...)方法记录四个顶点位置,这样DecorView自己的布局位置就已确定了,即host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())。
确定了DecorView自身的布局位置后,接下来就是要布局其 子View 了,因此,这里最终回调的是DecorView#onLayout(...)方法,其源码如下所示:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
}
...
}
DecorView#onLayout(...)内部转交给FrameLayout#onLayout(...)进行 子View 布局操作,其源码如下:
// frameworks/base/core/java/android/widget/FrameLayout.java
public class FrameLayout extends ViewGroup {
...
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 布局子View
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
// 获取 子View 数量
final int count = getChildCount();
// 左边可放置起始点坐标
final int parentLeft = getPaddingLeftWithForeground();
// 右边可放置终点坐标
final int parentRight = right - left - getPaddingRightWithForeground();
// 顶部可放置起始点坐标
final int parentTop = getPaddingTopWithForeground();
// 底部可放置终点坐标
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
// 遍历 子View
for (int i = 0; i < count; i++) {
// 获取 子View
final View child = getChildAt(i);
// 不放置状态为 GONE 的子View
if (child.getVisibility() != GONE) {
// 获取 子View 布局参数
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 获取 子View 测量宽/高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
// 当前 子View 的布局左边界
int childLeft;
// 当前 子View 的布局右边界
int childTop;
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
...
}
FrameLayout#onLayout(...)内部是通过FrameLayout#layoutChildren(...)进行 子View 的布局操作,其主要逻辑就是遍历所有 子View,计算得到 子View 的四个顶点位置坐标,最后将结果传递给child.layout(...),让 子View 记录自己在父容器中的布局位置,完成 子View 的布局过程。
综上,ViewRootImpl#performLayout(...)就是对DecorView的布局过程,此过程会递归计算各个 子View 的布局位置,调用 子View 的布局方法,完成各个 子View 的布局。
performDraw
完成了performMeasure(...)和performLayout(...)后,最后一步就是performDraw(...)过程,其源码如下:
// frameworks/base/core/java/android/view/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);
...
}
可以看到,ViewRootImpl#performDraw()内部会经由ViewRootImpl#draw(...)、ViewRootImpl#drawSoftware(...),最终执行的还是DecorView#draw(...)过程,其源码如下:
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
...
}
由于FrameLayout没有覆写draw(...)方法,因此,super.draw(...)最终调用的是View#draw(...)方法,所以DecorView默认采用的就是 View 的绘制方法,具体绘制详情上文已介绍过了,主要就是对DecorView的背景、内容、子View、滚动条等装饰视图进行绘制。
至此,View 绘制的整个流程已基本介绍完毕。
总结
View 的绘制主要有以下一些核心内容:
-
三大流程:View 绘制主要包含如下三大流程:
-
measure:测量流程,主要负责对 View 进行测量,其核心逻辑位于
View#measure(...),真正的测量处理由View#onMeasure(...)负责。默认的测量规则为:如果 View 的布局参数为LayoutParams.WRAP_CONTENT或LayoutParams.MATCH_PARENT,那么其测量大小为 SpecSize;如果其布局参数为LayoutParams.UNSPECIFIED,那么其测量大小为android:minWidth/android:minHeight和其背景之间的较大值。
自定义View 通常覆写
onMeasure(...)方法,在其内一般会对WRAP_CONTENT预设一个默认值,区分WARP_CONTENT和MATCH_PARENT效果,最终完成自己的测量宽/高。而ViewGroup在onMeasure(...)方法中,通常都是先测量子View,收集到相应数据后,才能最终测量自己。-
layout:布局流程,主要完成对 View 的位置放置,其核心逻辑位于
View#layout(...),该方法内部主要通过View#setFrame(...)记录自己的四个顶点坐标(记录与对应成员变量中即可),完成自己的位置放置,最后会回调View#onLayout(...)方法,在其内完成对 子View 的布局放置。注:不同于 measure 流程首先对 子View 进行测量,最后才测量自己,layout 流程首先是先定位自己的布局位置,然后才处理放置 子View 的布局位置。
-
draw:绘制流程,就是将 View 绘制到屏幕上,其核心逻辑位于
View#draw(...),主要就是对 背景、自身内容(onDraw(...))、子View(dispatchDraw(...))、装饰(滚动条、前景等) 进行绘制。注:通常自定义View 覆写
onDraw(...)方法,完成自己的绘制即可,ViewGroup 一般充当容器使用,因此通常无需覆写onDraw(...)。
-
measure:测量流程,主要负责对 View 进行测量,其核心逻辑位于
Activity 的根视图(即
DecorView)最终是绑定到ViewRootImpl,具体是由ViewRootImpl#setView(...)进行绑定关联的,后续 View 绘制的三大流程都是均有ViewRootImpl负责执行的。对 View 的测量流程中,最关键的一步是求取 View 的
MeasureSpec,View 的MeasureSpec是在其父容器MeasureSpec的约束下,结合自己的LayoutParams共同测量得到的,具体的测量逻辑由ViewGroup#getChildMeasureSpec(...)负责。
DecorView的MeasureSpec取决于自己的LayoutParams和屏幕尺寸,具体的测量逻辑位于ViewRootImpl#getRootMeasureSpec(...)。
最后,稍微总结一下 View 绘制的整个流程:
-
首先,当 Activity 启动时,会触发调用到
ActivityThread#handleResumeActivity(..),其内部会经历一系列过程,生成DecorView和ViewRootImpl等实例,最后通过ViewRootImpl#setView(decor,MATCH_PARENT)设置 Activity 根View。注:
ViewRootImpl#setView(...)内容通过将其成员属性ViewRootImpl#mView指向DecorView,完成两者之间的关联。 ViewRootImpl成功关联DecorView后,其内部会设置同步屏障并发送一个CALLBACK_TRAVERSAL异步渲染消息,在下一次 VSYNC 信号到来时,CALLBACK_TRAVERSAL就会得到响应,从而最终触发执行ViewRootImpl#performTraversals(...),真正开始执行 View 绘制流程。-
ViewRootImpl#performTraversals(...)内部会依次调用ViewRootImpl#performMeasure(...)、ViewRootImpl#performLayout(...)和ViewRootImpl#performDraw(...)三大绘制流程,其中:performMeasure(..):内部主要就是对DecorView执行测量流程:DecorView#measure(...)。DecorView是一个FrameLayout,其布局特性是层叠布局,所占的空间就是其 子View 占比最大的宽/高,因此其测量逻辑(onMeasure(...))是先对所有 子View 进行测量,具体是通过ViewGroup#measureChildWithMargins(...)方法对 子View 进行测量,子View 测量完成后,记录最大的宽/高,设置为自己的测量大小(通过View#setMeasuredDimension(...)),如此便完成了DecorView的测量流程。performLayout(...):内部其实就是调用DecorView#layout(...),如此便完成了DecorView的布局位置,最后会回调DecorView#onLayout(...),负责 子View 的布局放置,核心逻辑就是计算出各个 子View 的坐标位置,最后通过child.layout(...)完成 子View 布局。performDraw():内部最终调用到的是DecorView#draw(...),该方法内部并未对绘制流程做任何修改,因此最终执行的是View#draw(...),所以主要就是依次完成对DecorView的 背景、子View(dispatchDraw(...)) 和 视图装饰(滚动条、前景等) 的绘制。
