前言
在第一篇View绘制源码浅析(一)布局的加载我们知道了setContentView()
完成了DecorView
的创建,并且将xml中的布局标签转换成了对应的View、属性转换成了对应的LayoutParams
然后添加到了id为content的布局上,也就是说完成了布局对象的创建并且和DecorView
关联上了。
那么第二篇将介绍View是如何显示出来的,主体流程可分为测量、布局、绘制这三步。
本文源码基于API27,接下来我们开始吧。
概述
绘制的开始是在Activity收到AMS的Resume事件,然后给DecorView
设置上ViewRootImpl
这个视图结构的顶部对象作为DecorView
的parent
,然后通过调用ViewRootImpl
的requestLayout()
触发测量、布局、绘制的流程。
对于ViewRootImpl
来说是一个包含了父布局功能的视图顶层对象(因为每个View都有parent属性指向父布局,而DecorView
已经是最外层的布局了是没有父布局的,所以指向的是ViewRootImpl
),不过需要注意它不是一个View。
Activity收到Resume事件后最终会走道ViewRootImpl
的setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();//触发测量、布局、绘制
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);//在window上显示
...
}
而requestLayout()
最终会走到performTraversals()
这个方法贼鸡儿长,我们只看重点
private void performTraversals() {
...
measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);//会调到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)进行测量
performLayout(lp, mWidth, mHeight);//布局
performDraw();//绘制
...
}
这里也就引出了我们的重点方法
performMeasure()
测量performLayout()
布局performDraw()
绘制
接下来我们分别介绍这个过程。
测量
看测量前,我们得先了解一个概念measureSpec,它是一个32位的int值,是父布局的宽高约束和要测量的View的LayoutParams
宽高共同作用生成的,作为测量方法的形参传入指导View的测量。其中高2位用于存储测量的模式,低30位用于存储具体的数值。然后为了方便生成和取出这个32位的int值,提供了一个工具类MeasureSpec
。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;//高两位的掩码
//下面是3种模式
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//传入size和mode生成MeasureSpec
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);
}
}
//拿到mode
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//拿到size
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
模式一共是有三种,这里先简单介绍下
-
UNSPECIFIED
未指定的,大小根据size来决定 -
EXACTLY
大小有明确的值,比如width为32或者macth_parent都适用该模式 -
AT_MOST
对应上wrap_content这种情况,size为View可用的最大值
通过makeMeasureSpec()
可生成32位的measureSpec
,getMode()
和getSize()
可拿到mode和size。
准备工作做完了,接下来回到主线看测量方法measureHierarchy()
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
...
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);//获取根布局的MeasureSpec
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);//获取根布局的MeasureSpec
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//执行测量方法
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
...
return windowSizeMayChange;
}
前面不是说measureSpec是根据父布局的宽高约束和要测量的View的LayoutParams
宽高共同作用生成的。而这里稍有不同因为DecorView是最外层的布局了,没有父布局给他生成measureSpec参数所以用window去生成一个。
desiredWindowWidth和desiredWindowHeight是屏幕的宽高,lp.width和lp.height默认是match_parent
,接下来看下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;
}
可以看到生成的measureSpec的size为屏幕的尺寸,mode为MeasureSpec.EXACTLY,。然后将measureSpec作为performMeasure()
的形参传入。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//这里mView为DecorView,即调用DecorView.measure
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
这里mView为DecorView
为了简便我们直接看FrameLayout
的measure()
方法,而FrameLayout
并未重写也没法重写因为是final修饰的,所以最终走到View的measure()
。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {