View可以说是Android开发中,我们接触的最多的东西了。但是View内部的工作原理,我还并不是很懂。所以在此作一个总结归纳。
我们先来了解一下View相关的一些基本概念:
1.ViewRootImpl
ViewRootImpl是GUI管理系统(WindowManager)和GUI呈现系统(DecorView)之间的桥梁,它既不是View的子类,也不是View的父类。我们理解ViewRootImpl时,可以将它理解为“View树的管理者”--它有一个mView的成员变量,指向的是它所管理的View树的根。其主要完成两件事情:
(1)向DecorView分发收到的用户发起的event事件,入按键,触屏,滑动等事件。
(2)与WindowManagerService进行交互,完成Activity的GUI绘制过程。
(3)完成View的三大流程:measure、layout、draw。
在ActivityThread中,当Activity对象被创建完毕之后,会将DecorView添加到Window中,同时也会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView对象简历关联。(在之前的Activity的启动流程源码解析中我们介绍到~Activity的启动过程最终是ApplicationThread类中的scheduleLaunchActivity()发消息给H的handle来创建Activityd的,而ApplicationThread有时在ActivityThread中创建的。)
核心代码如下:WindowManagerGlobal类中的261~272行
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);
这里可以看出,ViewRootImpl类通过setView方法来来关联。
View的绘制流程是从ViewRootImpl的performTraversals()方法开始的,经过measure、layout、draw三个过程最终将一个View绘制出来。
measure:测量View的宽和高
layout:用来确定View相对于父容器中的位置
draw:负责将View绘制到屏幕上
下面展示下流程图(图是网上找的Android开发艺术中的图。。。自己懒得画)
如图所示,performTraversals()会依次调用performMeasure、performLayout和performDraw三个方法,而这三个方法则分别完成顶级View(DecorView)的measure、layout、deaw这三大流程。
首先在performMeasur中会调用View中的measure方法,在measure方法中又会调用onMeasure方法,而在onMeasure中则会对所有的子元素进行measure过程
,这时measure流程就从父容器传递到子元素中,这样就完成了一次Measure过程。接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。
接下来的layout和draw的流程,也和measure同理。
2.DecorView
DecorView是应用窗口最顶层的View,DecorView本身其实是一个FrameLayout,其内部一般情况下会包含一个竖直方向的LinearLayout,而在这个LinearLayout中有上下两个部分:标题栏(上)和内容栏(下)。而在Activity中,我们是通过serContentView设置的布局文件,骑士就是加到内容栏中,而在源码中我们可以看到内容栏的id是content,这就是我们指定布局时为什么叫setContentView的原因。View层的事件必须都先经过DecorView,然后再传递给我们内容区的View。
DecorView的结构如图:
3.MeasureSpec
MeasureSpec参与了View的measure过程,他代表一个32位的int值,高两位代表SpecMode,低30位代表SpecSize。其中SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
代码如下:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
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(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);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + 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);
}
从代码中我们可以看到,MeasureSpec是通过makeMeasureSpec(int size,int mode)方法将SpecSize和SpecMode打包成一个int值来避免过多的对象内存分配。
其中的SpecMode有三类:
UNSPECIFIED
父容器不对View有任何限制,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY
父容器已经测量出View所需要的精确大小,这时,View的最终大小就是SpecSize所指定的值,即它对应的是LayoutParams中的Match_parent和设置的具体的数值这两种模式。
AT_MOOST
父容器指定了一个可用大小,而View的大小不能大于这个值,但是距离数值要看View的具体实现,即对应LayoutParams中的wrap_content。
当我们给View设置了LayoutParams,在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。
只要提供父容器的MeasureSpec和子元素的LayoutPrarms,就可以快速地确定出子元素的MeasureSpec了,而有了这个MeasureSpec之后,就可以确定出子元素测量后的大小了。