您的位置:首页 > 编程语言

关于ViewRootImpl中measure相关代码的分析

2014-12-25 14:55 337 查看
首先先看下在一个Activity创建到DecorView的measure函数被调用过程的时序图:



可以看到这些主要在ViewRootImpl的performTraversals()函数中完成。如下是preformTraversals()函数,我们关注一下标红的这几行代码

private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (DEBUG_LIFECYCLE || DEBUG_DEFAULT) {
Xlog.v(TAG, "ViewRoot performTraversals+ : mFirst = " + mFirst + ", this = " + this);
}

if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}

if (host == null || !mAdded)
return;

mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
<span style="background-color: rgb(255, 0, 0);">        WindowManager.LayoutParams lp = mWindowAttributes;</span>

<span style="white-space:pre">	</span>.............

// Ask host how big it wants to be
<span style="background-color: rgb(255, 0, 0);">            windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);</span>
......................
}


可以看到View的measure工作最后主要由measureHierarchy()完成,并将Window的LayoutParameter传入到函数内:

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 (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Xlog.v(TAG, "ViewRoot measure+ " + host + " in display " + desiredWindowWidth + "x"
+ desiredWindowHeight + ", lp = " + lp + ", this = " + this);
}

boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text.  First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) {
Xlog.v(TAG, "Window " + mView + ": baseSize=" + baseSize + ", this = " + this);
}
if (baseSize != 0 && desiredWindowWidth > baseSize) {
<span style="background-color: rgb(255, 0, 0);">                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);</span>
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) {
Xlog.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth() + ","
+ host.getMeasuredHeight() + ") " + this);
}
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) {
Xlog.v(TAG, "Window " + mView + ": next baseSize=" + baseSize + " " + this);
}
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
<span style="background-color: rgb(255, 0, 0);">                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);</span>
if (DEBUG_DIALOG) {
Xlog.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth()
+ "," + host.getMeasuredHeight() + ") " + this);
}
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) {
Xlog.v(TAG, "Good!");
}
goodMeasure = true;
}
}
}
}

if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}

if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}

if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Xlog.v(TAG, "ViewRoot measure-: host measured size = (" + host.getMeasuredWidth()
+ "x" + host.getMeasuredHeight() + "), windowSizeMayChange = "
+ windowSizeMayChange + ", this = " + this);
}
return windowSizeMayChange;
}


这里,我们分析一下传进去给DecorView的measure函数的widthMeasureSpec和heightMeasureSpec到底是怎么样的?特别是他的mode到底是怎么确定的:

<span style="white-space:pre">	</span>childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
显然childWidthMeasureSpec由如下函数确定getRootMeasureSpec(),而这里的lp就是刚刚说的windown的LayoutParam。现在看一下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:

4000
// 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;
}

从这个函数可以看出如果Window的lp是ViewGroup.LayoutParams.MATCH_PARENT那么传入DecorView的measure函数的MeasureSpec的mode就是EXACTLY。而如果如果Window的lp是ViewGroup.LayoutParams.WRAP_CONTENT那么传入DecorView的measure函数的MeasureSpec的mode就是AT_MOST。

通过分析Framework的源代码,特别是FrameLayout的onMeasure()函数,还可以得出如果下结论:

ViewGroup的measure()和onMeasure()的确可能被多次调用。以FrameLayout的onMeasure为例,首先遍历所有的child,调用measureChildWithMargins(child,
widthMeasureSpec, 0, heightMeasureSpec, 0);测量子View的尺寸。这里的measureSpec采用父View给他的measureSpec。.之后如果child的LayoutParameter的高或者宽有一个为match_parent会调整measureSpec再次重新对对应的子View测量一遍,即调用子View的measure函数重新计算一遍: child.measure(childWidthMeasureSpec, childHeightMeasureSpec);。这个是完全可以理解的,因为child为match_parent,而上次测量的时候还不知道父View的尺寸,自然需要重新测量一遍了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息