简介
我们知道,在 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
的启动过程进行分析。
我们知道,在 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
启动完成后,最终就会渲染出上述层次结构的视图。
因此,如果我们要求取得到子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 为屏幕大小。
经过上述步骤求取得到 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
,此时:
LayoutParams
为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension
,测量模式为EXACTLY
。LayoutParams
为MATCH_PARENT
:表示 子View 的大小撑满父容器,由于父容器是EXACTLY
,即大小已知,因此,子View 也是大小已知,故其测量模式为EXACTLY
,且其测量大小就是父容器剩余空间大小,具体为size
。LayoutParams
为WRAP_CONTENT
:表示 子View 自适应内容大小,但是其尺寸最大不能超过父容器剩余空间,因此其测量模式为AT_MOST
,测量大小为父容器剩余空间size
。如果父容器的测量模式为AT_MOST
:即父容器自适应其内容大小,也即父容器大小不确定,此时:
LayoutParams
为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension
,测量模式为EXACTLY
。LayoutParams
为MATCH_PARENT
:表示 子View 的大小撑满父容器,由于父容器是AT_MOST
,即大小未知,因此,子View 也是大小未知,即其测量模式为AT_MOST
,且其测量大小不超过父容器剩余空间大小size
。LayoutParams
为WRAP_CONTENT
:表示 子View 自适应内容大小,但是其尺寸最大不能超过父容器剩余空间,因此其测量模式为AT_MOST
,测量大小为父容器剩余空间size
。如果父容器的测量模式为UNSPECIFIED
:即父容器大小无限制,此时:
LayoutParams
为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension
,测量模式为EXACTLY
。LayoutParams
为MATCH_PARENT
:表示 子View 的大小撑满父容器,由于父容器大小无限制,因此,子View 的大小也是无限制的,所以,子View 的测量模式为UNSPECIFIED
,测量大小未知,通常设置为0
,表示无限。LayoutParams
为WRAP_CONTENT
:表示 子View 自适应内容大小,由于父容器大小无限制,因此,子View 的测量大小也是无限制的,所以其模式为UNSPECIFIED
,测量大小无限,通常使用0
进行表示。上述的逻辑总结如下图所示:(注:图片来源于互联网,侵删)
注:前面我们一直强调:子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 大小,最终才能确定自己的测量大小。
当确定了 View 的测量大小后,接下来就可以来确定 View 的布局位置了,也即将 View 放置到屏幕具体哪个位置。
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 的布局流程由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 的位置。
当 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(...)内部通过
<meta charset="utf-8">
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 绘制流程,总结一下上述流程,如下图所示:
书接前文,我们知道,真正开始 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 中占比最大的测量宽/高,作为自己的最终测量大小。
<meta charset="utf-8">
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 的布局。
完成了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 绘制主要包含如下三大流程:
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(...)
。
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(...)
) 和 视图装饰(滚动条、前景等) 的绘制。
作者:Whyn
链接:https://www.jianshu.com/p/ee5d3bb5ab90
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。