在 Android 的知识体系中,View
扮演者很重要的角色。View
是 Android 在视觉上的呈现。本文结合 android-28
的源码来分析 View
的绘制过程。
ViewRootImpl
ViewRootImpl
类是连接 WindowManager
和 DecorView
的纽带,View
的绘制流程均是通过 ViewRootImpl
来完成的。
// ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 获取 WindowManager 及 DecorView
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 将 DecorView 添加到当前 Window 中
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
}
复制代码
上面的代码说明,在 ActivityThread
中,当 Activity
对象被创建完毕后,会将 DecorView
通过 WindowManager
添加到 Window
中。
// WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
复制代码
可以知道最终是通过 WindowManagerGlobal
的 addView
方法来将 DecorView
添加到 Window
中
// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// 初始化 ViewRootImpl 并将 ViewRootImpl 对象和 DecorView 建立关联
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
复制代码
上述代码创建了 ViewRootImpl
对象,并将 ViewRootImpl
对象和 DecorView
建立关联。最终在 setView
方法中,会执行 ViewRootImpl
的 requestLayout
方法来执行 View
的绘制流程
// ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
复制代码
scheduleTraversals
方法最终会调用 performTraversals
方法,经过 measure
、layout
和 draw
三个过程才能最终将一个 View
绘制出来
- meausre: 用来测量
View
的宽和高 - layout: 用来确定
View
在父容器中的放置位置 - draw: 负责将
View
绘制在屏幕上
如图所示,
performTraversals
会依次调用performMeasure
、performLayout
和performDraw
三个方法,这三个方法分别完成顶级View
的measure
、layout
和draw
这三大流程。其中在performMeasure
中会调用measure
方法,在measure
方法中又会去调用onMeasure
方法,在onMeasure
方法中又会对所有的子元素进行measure
过程,这个时候measure
流程就从父容器传递到了子元素中了,这样就完成了一次measure
过程。接着子元素会重复父容器的measure
过程,如此反复就完成了整个View
树的遍历。通过performLayout
和performDraw
的传递流程跟performMeasure
类似
MeasureSpec
为了更好地理解 View
的测量过程,我们还需要理解 MeasureSpec
。MeasureSpec
参与了 View
的 measure
过程,在很大程度上决定了一个 View
的尺寸规格,但父容器也会影响 View
的 MeasureSpec
的创建过程。在测量过程中,系统会将 View
的 LayoutParams
根据父容器所试驾的规则转换成对应的 MeasureSpec
,然后再根据这个 MeasureSpec
来测量出 View
的宽和高。
// View.java
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 {}
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;
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);
}
}
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
复制代码
MeasureSpec
代表一个 32 位的 int
值,高 2 位代表 SpecMode
, 低 30 位代表 SpecSize
- SpecMode: 测量模式
- SpecSize: 在某种测量模式下的规格大小
MeasureSpec
通过将 SpecMode
和 SpecSize
打包成一个 int
值来避免过多的对象内存分配。makeMeasureSpec
是打包方法,getMode
和 getSize
则为解包方法。
SpecMode
有三类,每一类都标识特殊的含义
UNSPECIFIED
父容器不对 View
有任何限制,要多大给多大,这种情况一般用于系统内部,标识一种测量的状态
EXACTLY
父容器已经检测出 View
所需要的精确大小,这个时候 View
的最终大小就是 SpecSize
所指定的值。它对应于 LayoutParams
中的 match_parent
和具体的数值这两种模式
AT_MOST
父容器指定了一个可用大小即 SpecSize
,View
的大小不能大于这个值,具体是什么值要看不同 View
的具体实现。它对应于 LayoutParams
中的 wrap_content
MeasupreSpec 和 LayoutParams
MeasureSpec
不是唯一由 LayoutParams
决定的,LayoutParams
需要和父容器一起才能决定 View
的 MeasureSpec
,从而进一步决定 View
的宽和高。
对于 DecorView
,其 MeasureSpec
由窗口的尺寸和其自身的 LayoutParams
来共同确定;对于普通的 View
,其 MeasureSpec
由父容器的 MeasureSpec
和自身的 LayoutParams
来共同局诶的那个,MeasureSpec
一旦确定后, onMeasure
中就可以确定 View
的测量宽和高
DecorView 创建 MeasureSpec
对于 DecorView
来说,它的 MeasureSpec
创建过程是由 getRootMeasureSpec
方法来完成的
// 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: // 精确模式,大小为 LayoutParams 中指定的大小
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码
通过上述代码,DecorView
的 MeasureSpec
的产生过程就很明确了,具体来说其遵守如下规则,根据它的 LayoutParams
中的宽和高参数来划分
- LayoutParams.MATCH_PARENT: 精确模式,大小就是窗口大小
- LayoutParams.WRAP_CONTENT: 最大模式,大小不定,但是不能超过窗口的大小
- 固定大小(如 100dp): 精确模式,大小为 LayoutParams 中指定的大小
ViewRootImpl
在 performTraversals
方法中调用 getRootMeasureSpec
获取到 childWidthMeasureSpec
和 childHeightMeasureSpec
后,会传给 performMeasure
方法,最终调用 DecorView
的 measure
方法
普通 View 创建 MeasureSpec
对于普通 View
来说,即布局中的 View
,View
的 measure
过程由 ViewGroup
传递而来,在 ViewGroup
的 measureChildWithMargins
方法
// ViewGroup.java
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
方法一般会在自定义 Layout
组件的 onMeasure
方法中调用(如 FrameLayout, LinearLayout),来测量子元素的规格。在调用子元素的 measure
方法之前会先通过 getChildMeasureSpec
方法来得到子元素的 MeasureSpec
。通过上面代码可知,子元素的 MeasureSpec
的创建和父容器的 MeasureSpec
和子元素本身的 LayoutParams
有关,此外还和 View
的 margin
及 padding
有关
// ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父容器的 mode 和 size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 子元素可用大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (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);
}
复制代码
getChildMeasureSpec
函数主要的作用是根据父容器的 MeasureSpec
同时结合 View
本身的 LayoutParams
来确定子元素的 MeasureSpec
。
当
View
采用固定宽和高的时候,不管父容器的MeasureSpec
是什么,View
的MeasureSpec
都是精确模式并且其大小遵循LayoutParamas
中的大小。当View
的宽和高是match_parent
时,如果父容器的模式是精确模式,那么View
也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View
也是最大模式并且其大小不会超过父容器的剩余空间。如果父容器是最大模式,那么View
也是最大模式并且其大小不会超过父容器的剩余空间。当View
的宽和高是wrap_content
时,不管父容器的模式是精准还是最大化,View
的模式总是最大化并且大小不能超过父容器的剩余空间。