View的绘制是一个递归的过程,父View绘制自己和子View,然后子view绘制自己和自己的子View。
我们知道递归的一般数学表达是A1=T,An=f(An-1),那么与之对应,View绘制的A1和函数f()又是什么呢?
答案 :View绘制的A1是DecorView,它是View绘制的起始节点,View绘制的f()函数是measure,layout,draw三大过程,通过递归调用这三个方法完成View的绘制。
在介绍View的绘制过程之前,先介绍几点关于View的相关概念,方便大家理解。
一、View相关概念
PhoneWindow:继承自Window类,负责管理界面显示以及事件响应,每个Activity 界面都包含一个PhoneWindow对象,它是Activity和整个View系统交互的接口。
DecorView:是PhoneWindow中的起始节点View,继承自View类,是作为整个视图容器来使用的,主要负责设置窗口属性。
ViewRoot:在系统启动一个Activty组件的同时将其创建,类似于MVC模型中的Controller,负责管理、布局和渲染窗口UI等事务。
二、View绘制开始
系统启动一个Activity的同时创建一个ViewRoot实例。
Activity 在attach阶段生成一个PhoneWindow对象,它包含一个DecorView对象。
在Activity执行onCreate中的setContentView之后,将读入的view加载进入第一张图的ContentViews区域。
加载完毕后触发ViewRoot中的scheduleTraversals异步函数,从而进入ViewRoot的performTraversals函数,View的绘制从这里开始。
三、performTraversals
ViewRoot中的performTraversals方法以DecorView为父容器(ViewGroup)开始自上而下的View工作流程。
View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽和高,layout确定View的最终宽/高和四个顶点位置,而draw则将View绘制到屏幕上。
关于measure,layout,draw的具体内容可以看后面的文章,这里不用多在意。这个函数的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个。
private void performTraversals() {
//1 处理mAttachInfo的初始化,并根据resize、visibility改变的情况,给相应的变量赋值。
final View host = mView;//这里的mView即DecorView
final View.AttachInfo attachInfo = mAttachInfo;
final int viewVisibility = getHostVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility
|| mNewSurfaceNeeded;
float appScale = mAttachInfo.mApplicationScale;
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
}
Rect frame = mWinFrame;
if (mFirst) {
// For the very first time, tell the view hierarchy that it
// is attached to the window. Note that at this point the surface
// object is not initialized to its backing store, but soon it
// will be (assuming the window is visible).
attachInfo.mSurface = mSurface;
attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||
lp.format == PixelFormat.RGBX_8888;
attachInfo.mHasWindowFocus = false;
attachInfo.mWindowVisibility = viewVisibility;
......
}
//2 如果mLayoutRequested判断为true,那么说明需要重新layout,不过在此之前那必须重新measure。
if (mLayoutRequested) {
// Execute enqueued actions on every layout in case a view that was detached
// enqueued an action after being detached
getRunQueue().executeActions(attachInfo.mHandler);
if (mFirst) {
......
}
}
//3 判断是否有子视图的属性发生变化,ViewRoot需要获取这些变化。
if (attachInfo.mRecomputeGlobalAttributes) {
......
}
if (mFirst || attachInfo.mViewVisibilityChanged) {
......
}
//4 根据上面得到的变量数值,确定我们的view需要多大尺寸才能装下。于是就得measure了,有viewgroup的weight属性还得再做些处理
// Ask host how big it wants to be
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mLayoutRequested = true;
}
}
//5 measure完毕,接下来可以layout了。
final boolean didLayout = mLayoutRequested;
boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
}
//6 如果mFirst为true,那么会进行view获取焦点的动作。
if (mFirst) {
mRealFocusedView = mView.findFocus();
}
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
//7 终于,来到最后一步,前面的工作可以说都是铺垫,都是为了draw而准备的。
if (!cancelDraw && !newSurface) {
mFullRedrawNeeded = false;
draw(fullRedrawNeeded)
}