遍历View树performTraversals()执行过程
view树遍历概述
还是回到ViewRoot.java,我们直接看performTraversals(),该函数就是android系统View树遍历工作的核心。一眼看去,发现这个函数挺长的,但是逻辑是非常清晰的,其执行过程可简单概括为根据之前所有设置好的状态,判断是否需要计算视图大小(measure)、是否需要重新安置视图的位置(layout),以及是否需要重绘(draw)视图,可以用以下图来表示该流程。
private void performTraversals() {
// cache mView since it is used so much below...
//1 处理mAttachInfo的初始化,并根据resize、visibility改变的情况,给相应的变量赋值。
final View host = mView;
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);
}
下面我们就来会会view那三部曲吧。
计算视图大小(measure)的过程
整个view视图的Measure过程就是一个量体裁衣,按需分配的过程。看一下以下的递归过程:
从上图可以看出,measure过程始于ViewRoot的host.measure(),调的就是view类的measure()函数,该函数然后回调onMeasure。如果host对象是一个ViewGroup实例,一般会重载onMeasure,如果没有的话,则会执行view类中默认的onMeasure。合理的情况是编程人员重载onMeasure并逐一对里面的子view进行measure。我们可以看一下view的measure方法:
/**
* 该方法在需要确定view所需尺寸大小时调用,父视图会提供宽和高的属性约束。
* 具体视图完全可以在onMeasure中改变这些。
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags