上文从源码角度分析了view和viewGroup的measure机制,如果还没有阅读的同志们,可以前往从源码角度分析Android View的绘制机制(一)阅读。下面我再结合linearLayout的measure过程解释以下两个问题的缘由。
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
"@+id/tv_1"
android:layout_width="match_parent"
android:layout_height="0dip"
android:background="#aaffdd"
android:layout_weight="2" />
"@+id/tv_2"
android:layout_width="match_parent"
android:layout_height="0dip"
android:background="#ddaabb"
android:layout_weight="4" />
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
"@+id/tv_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#aaffdd"
android:layout_weight="2" />
"@+id/tv_2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ddaabb"
android:layout_weight="4" />
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/ll"
android:orientation="vertical" >
"@+id/tv_1"
android:layout_width="match_parent"
android:layout_height="500dip"
android:background="#aaffdd"
android:layout_weight="2" />
"@+id/tv_2"
android:layout_width="match_parent"
android:layout_height="100dip"
android:background="#ddaabb"
android:layout_weight="4" />
对于以上问题我们逐一分析。
问题一:
parent的layout_height是match_parent,而两个TextView的layout_height都是0并且layout_weight分别是2和4,但是显示出来的高度比例是1:2。
for (int i = 0; i < count; ++i) {
........
//拿到child的LayoutParams
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
//将weight的值加到totalWeight,weight的值就是xml文件中的layout_weight属性的值
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
/**
如果父View的mode是EXACTLY,并且height==0 并且lp.weight>0(就是我们上面的例子中的第一张图的情况)
那么就先不measure这个child,直接把topMargin和bottoMargin等属性加到totaoLength中
*/
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
} else {
int oldHeight = Integer.MIN_VALUE;
//如果父View不是EXACLTY,那么将子View的height变为WRAP_CONTENT
if (lp.height == 0 && lp.weight > 0) {
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
.............
// 给总高度加上自身的padding
mTotalLength += mPaddingTop + mPaddingBottom;
//将所有View的高度赋值给heightSize
int heightSize = mTotalLength;
// Check against our minimum height
// 最小的高度就是背景的高度mBGDrawable.getMinimumHeight()
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//这里对heightSize再次赋值,不过如果LinearLayout是xml文件的根标签,并且设置到Activity的话
//此时heightSize的大小就是屏幕的高度,我们暂时就考虑等于屏幕高度的情况,其他情况类似
// Reconcile our calculated size with the heightMeasureSpec
// 如果heightMeasureSpec的size是精确的,那么这个heightsize值==heightMeasureSpec的size,也就是屏幕的高度
heightSize = resolveSize(heightSize, heightMeasureSpec);
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds
//屏幕的高度还剩下delta,如果对于我们上面第一张图,delta>0,对于第二张图则<0
int delta = heightSize - mTotalLength;
if (delta != 0 && totalWeight > 0.0f) {
//如果设置了weightsum属性,这weightSum等于weightsum的属性,否则等于totalWeight
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
// 计算方式:子view的weight属性值 * 剩余高度 / weight总和
int share = (int) (childExtra * delta / weightSum);
weightSum -= childExtra;
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
// 重新设置子view的高度
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
// child was skipped in the loop above.
// Measure for this first time here
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
}
}
由于问题一满足if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)的条件,所以两个TextView都不会测量大小只会先记录weight,然后看一下下面梁行代码:
int heightSize = mTotalLength;
......
heightSize = resolveSize(heightSize, heightMeasureSpec);
int delta = heightSize - mTotalLength;
这里mTotalLength其实是等于0的,因为两个TextView的高度都没有测量。通过调用resolveSize方法,参数heightSize为0,heightMeasureSpec的mode为MeasureSpec.EXACTLY ,所以这个方法返回的heightMeasureSpec的size,在这个例子中也就是linearLayout的height,也就是matchParent窗体的大小。剩余高度delta也就为窗体的大小。到目前为止两个TextView的高度都是0.
int share = (int) (childExtra * delta / weightSum);
weightSum -= childExtra;
delta -= share;
........
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// 重新设置子view的高度
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
child.measure(childWidthMeasureSpec,MeasureSpec.makeMeasureSpec(share > 0 ? share : MeasureSpec.EXACTLY));
}
假设现在窗体的高度为900:
第一个textView:share = 权重 2 * 900 / 总权重6 = 300。那么childHeight = 0 + 300 = 300,然后调用child的measure方法测量
第二个textview:share = 权重 4 * (900 - 300) / 总权重(6-2) = 600。那么childHeight = 0 + 600 = 600,然后调用child的measure方法测量.
由于两个TextView的高度都是精确值,所以测量出来的分别是300、600,高度也就是1:2。
问题二:
linearlayout和两个TextView的高度都是match_Parent。
总权重还是2+4 = 6不变,但是过程中明显不满足if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)这个条件,所以直接测量这个子view,走的是下面代码:
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0);
..........
heightSize = resolveSize(heightSize, heightMeasureSpec);
int delta = heightSize - mTotalLength;
..........
这个方法里面,如果parentHeightMeasureSpec和mode是MeasureSpec.EXACTLY并且child的高度是大于0的话,测量出来的子view的高度就是子view本身layout_height,本例也就是match_parent,窗体的高度。
假设窗体的高度为900,两个textview测量出来的高度总和为两个窗体的高度 2 *900 = 1800。上面也讲到resolveSize出来的heightSize 为900。所以剩余高度delta = 900 - 1800 = -900。
第一个textView:share = 权重 2 * (-900) / 总权重6 = -300。那么childHeight = 900 + (-300) = 600,然后调用child的measure方法测量
第二个textview:share = 权重 4 * (- 900 - (-300)) / 总权重(6-2) = -600。那么childHeight = 900 + (-600) = 300,然后调用child的measure方法测量.
由于两个TextView的高度都是精确值,所以测量出来的分别是600、300,高度也就是2:1。
问题三:
linearlayout高度match_Parent,两个TextView的高度分别是500dip和200dip。总权重还是2+4 = 6不变,
过程中明显不满足if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0)这个条件,所以直接测量这个子view,走的是下面代码:
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0);
..........
heightSize = resolveSize(heightSize, heightMeasureSpec);
int delta = heightSize - mTotalLength;
..........
这个方法里面,如果parentHeightMeasureSpec和mode是MeasureSpec.EXACTLY并且child的高度是大于0的话,测量出来的子view的高度就是子view本身layout_height,对于第一个textView来讲就是500dip,第二个也就是200dip。
我的手机上窗体的高度为1557px,密度为3.0,所以把dip换算成px后,第一个textView的测量高度为500 * 3.0 + 0.5f = 1500.5px。第二个textview的测量高度为200 * 3.0 + 0.5f = 600.5px。所以测量出来的总高度 = 1500.5 + 600.5 = 2101
上面也讲到如果heightMeasureSpec的mode为MeasureSpec.EXACTLY ,所以这个方法返回的heightMeasureSpec的size,也就是1557px。所以剩余高度delta = 1557- 2101= -544。
第一个textView:share = 权重 2 * (-544) / 总权重6 = -181。那么childHeight = 1500.5 + (-181) = 1319,然后调用child的measure方法测量。
第二个textview:share = 权重 4 * (- 544 - (-181)) / 总权重(6-2) = -363。那么childHeight = 600.5 + (-363) = 238,然后调用child的measure方法测量.。
由于两个TextView的高度都是精确值,所以测量出来的分别是1319、238,高度也就不详之前一样的比值了。
measure过程已经讲述完毕了。请继续关注本文下半部分view的绘制机制之layout和draw。