记录自己的理解,如果有错误 请大家指正 谢谢
人们说估计使用RelativeLayout而不是LinearLayout,因为LinearLayout比relativeLayout多测量一次,事实上是这样吗?我们来详细看看
1、LinearLayout 有两种模式,VERTICAL和HORIZONTAL所以也有两种测量方式
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
2、现在我们就对使用多一点的VERTICAL学习一下onMeasure过程
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;//linearlayout 自己所占的高度比如DividerHeight
int maxWidth = 0;//
int childState = 0;//child状态 是否有觉得父类给的大小太小
int alternativeMaxWidth = 0;//暂时未知
int weightedMaxWidth = 0;//权重状态下的最大宽度
boolean allFillParent = true;
float totalWeight = 0;//总的权重值
final int count = getVirtualChildCount();//获取child数量 有个虚拟的修饰,跟踪进去也没理解
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex; //基线,你学英文的时候三条横线就是基线,大家可以试下设置这个属性然后看下几个child有什么不同
final boolean useLargestChild = mUseLargestChild;//从名字感觉没懂
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);//方法返回0,不知道为什么这样设计
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);//方法返回0
continue;
}
if (hasDividerBeforeChildAt(i)) {//确定child前面是否有分割线
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
//这里就可以看出为什么要鼓励大家设置weight的时候最好将宽或者高设置为0
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);// LinearLayout第一次测量
if (oldHeight != Integer.MIN_VALUE) {//如果linearLayout本身没设置match,并且child的初始lp.height == 0 && lp.weight > 0
lp.height = oldHeight;//那么就把高度设置为0
}
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);//对比找出最高的child大小
}
}
从代码可以看出 linearLayout设置match 然后child设置height=0,weight>0 就可以少测量一次,那么你就优化了一把你的APP了,其中测量child暂时不看,关于width的暂时不看,与我们研究的无关。我们继续往下看
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;//如果你设置了baselineChildIndex属性,并且这里匹配到了
}
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;//如果是上面没有测量child,那么这里getMeasureWidth应该返回0
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());//如果是上面没有测量child 状态设置也无意义了
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {//有权重和没有 连宽度都能受到影响
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);//不知道有什么意义 i += 0 ?
}
至此第一个for循环取child完了,主要完成mTotalLength和alternativeMaxWidth的读取;
还有第二次循环 ,主要是对权重的处理,如果child没有weight属性则不会进入,大家可以简单看下,忽略关于width的代码,因为我们这里主要分析高度方面的测量
3、总结一下
总的来说 linearout有两个循环测量child大小,第一个循环测量大小值然后确定linearout的高度;第二个循环是调整child的大小。由于LinearLayout含有weight,那么总是先测量不含weight的child的大小,剩下的大小由weight比例均分。
总结一下下面几种情况:
a、LinearLayout是match,child全是height=0,weight>0: 在这种情况下会跳过第一次for循环measure child, 经历第一次循环,mTotalLength只是含有divider值和child设置的margin值;接下来加上LinearLayout设置的paddingTop和paddingBottom;然后取出parent给的高度减去这些margin和padding消耗的值得到剩余的高度;将mTotalLength设置为0,根据child的weight与weightTotal的比值来给child设置高度,child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(XXXX,MeasureSpec.EXACTLY));将每次child测量的值加入到mTotalLength中,然后设置linearLayout的测量大小setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
这种状态下 Linearout的高度就是parent传过来的heightMeasureSpec里面的值
b、LinearLayout是match,除了a中child的情况:将height不等于0的child都会测量一下大小,并且将测量的大小和Linearlayout总的大小 相减后 ,如果结果>0 则将这部分高度按weight均分再加上 上个循环测量的大小,重新给child测量;
这种情况大家可以实现一下,最后发现LinearLayout的child如果height设置为wrap,并且有权重值,那么你会发现他的大小和权重值分配到的大小不一致,会大许多。
c、LinearLayout是wrap_content: 如果child 是height=0,weight>0,那么在第一次循环测量的时候会把你的height设置为wrap_content,然后就没有然后了 你的weight失效了,
失效了
总的大家说linearLayout 会测量两次是有问题的,LinearLayout为wrap则,child只测量一次,如果LinearLayout为match,则child只有weight>0才有可能会测量两次,如果height=0 会测量一次,height不为0会测量两次。如果weight没有值则只测量一次
文章比较粗糙,请大家见谅 如果有说的不对的欢迎大家斧正 谢谢