您的位置:首页 > 其它

LinearLayout measure过程中的几次遍历分析

2016-02-03 16:50 274 查看
measure过程中的5次遍历childView:

GONE/Null的ChildView基本都被直接忽略.

第一次遍历:

weight能够在第一次遍历中生效的前提是: parent传递的heightMode是EXACTLY, 这样,设置了weight的childView在这次遍历中就不会被measure,而只是将其topMargin/bottomMargin累加到mTotalLength中去, 因为childView的margin是不受weight影响的.

否则heightMode不是EXACTLY, 那么设置了weight的childView的height会被强制设置为WRAP_CONTENT来进行measure,(但是measure完以后会回复其原来的height值), 这个过程中也会不断的通过比较与赋值来保证mTotalLength的最大性, 如果设置了useLargestChild,那么largestChildHeight也会不断的被比较和赋值来确保其最大性.

在第一次遍历的过程中,也会不断的搜集allFillParent/measure的child的measuredState, maxWidth/weightedMaxWidth/alternativeMaxWidth

如果设置了有效的divider,dividerHeight也会被累加到mTotalLength.

第二次遍历:

发生的前提是useLargestChild并且(heightMode是AT_MOST/UNSPECIFIED), mTotalLength会被设置为0, 因为下面会重新计算和累加.

这次遍历的主要目的是将largestChildHeight作为每个Child的height, 然后遍历childView进行mTotalLength的累加.

第三次遍历:

发生的前提是经过上面计算的mTotalLength和parent传递的HeightMeasureSpec之间有额外多出来的空间(这样才能给设置了weight的childView分配空间), 或者totalWeight > 0,即有childView设置了有效的weight.

这一次遍历只对weight>0的childView进行measure, 并且还区分此childView在上面是否已经被measure过了(如果measure过,那么是累加为其分配的空间到其measuredHeight, 否则之前没有measure过就以多出来的空间作为child的HeightMeasureSpec的size)

在这个过程中还会继续更新allFillParent/mTotalLength/alternativeMaxWidth/maxWidth等全局值.

这次遍历主要是为了weight机制能够生效.

第4次遍历:

其实和第3次遍历是并列互斥, 不满足3就会进入到4:

第4次遍历需要开启了useLargestChild以及heightMode != MeasureSpec.EXACTLY, 可见还是跟useLargestChild有关.

只对weight>0的View进行measure(可见weight机制和useLargestChild的优先级: 如果能基于weight分配空间的话,优先weight): 不同的就是这次measure使用的HeightMeasureSpec是largestChildHeight+ EXACTLY, 即基于largestChildHeight对child进行measure,而width则使用child.getMeasuredWidth() + EXACTLY 跟原来基本是一样的

第5次遍历:

前提条件是上面有机会将matchWidth设置为true: 设置为true的时机是第一次遍历中,如果widthMode不是EXACTLY并且有childView的width是MATCH_PARENT, 那么matchWidth=true, 这情况说明childView的width依赖于parent(即LinearLayout)的width, 但是parent的widthMode并不是EXACTLY,那么在这个时候,是决定不了childView的width的

因此这次遍历并为childView**设置width的时机就发生在setMeasuredDimension(…)之后,因为这个时候LinearLayout的width已经确定了**

在便利前,基于LinearLayout自己的measuredWidth + EXACTLY构造一个WidthMeasureSpec, 为后面的childView进行measure作为width参数.

遍历ChildView,只处理width是MATCH_PARENT的ChildView(也和其前面设置matchWidth的场景相映照), 对childView进行measure,基于上面构造的WidthMeasureSpec, childView的height会被暂存和回复,这次measure并不会影响childView的height,只会影响其width

上面遍历过程的详细跟踪:

onMeasure(…)->measureVertical(…)/measureHorizontal(…):

measureVertical(…):

会遍历VirtualChilds, count = getVirtualChildCount():

调用getVirtualChildAt(i)获取对应位置的childView(有可能是VirtualChild)

如果child为null, 那么会调用measureNullChild(i)对该位置的NullView进行measure, 并且将measure的返回值累加到mTotalLength. 然后continue来处理下一个virtualChild.

如果child的visiblity是gone, 那么会调用i += getChildrenSkipCount(child, i)(跳过对某些childView的处理, 该函数默认返回的是0), 然后continue来处理下一个childView.

如果hasDividerBeforeChildAt(i), 即是有分割符的, 那么会将mDividerHeight累加到mTotalLength上.

获取childView的lp, 先将lp.weight累加到totalWeight上, 这样才最后才能为每个不同的weight分配不同的高度

如果parent传递给LinearLayout measure用的heightMode是MeasureSpec.EXACTLY并且lp.height == 0 以及 lp.weight > 0: 那么会从 (mTotalLength, mTotalLength + lp.topMargin + lp.bottomMargin)中取一个最大者保存在mTotalLength中.

否则, 如果lp.height == 0并且lp.weight > 0(但是这个时候,parent给LinearLayout的HeightMeasureSpec的mode是UNSPECIFIED或者AT_MOST, 这个时候,weight机制其实已经不能正常工作了, 因为LinearLayout的总高度已经不是固定的了),那么会将lp的height设置为LayoutParams.WRAP_CONTENT, 其实其weight参数已经失去作用了,设置成WRAP_CONTENT是为了能使其勉强工作.

用measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); 其内部直接调用的是measureChildWithMargins(…)对childView进行measure.

测量过childView以后获取child.getMeasuredHeight()保存在childHeight. 如果使用了useLargestChild,那么会将当前的childHeight和largestChildHeight进行对比,将大者保存在largestChildHeight中.

如果widthMode不是EXACTLY并且childView的lp的width是MATCH_PARENT, 那么会设置matchWidth/matchWidthLocally = true.

会将此childView这次经过measure以后的所有MeasuredState合并在一个childState中,这个childState会汇总所有childView的measuredState

allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT, 即只要有一个lp的width不是MATCH_PARENT, 那么allFillParent就是false.

至此对childView的第一次遍历结束, 这一遍不一定对所有的childView都进行了measure, 对于heightMode是EXACTLY并且lp.height == 0 && lp.weight > 0的childVIew不会进行处理, 只是先简单的将其lp指定的top/bottomMargin考虑到mTotalLength中

如果LargestChild并且heightMode是AT_MOST/UNSPECIFIED, 那么还会遍历一遍virtualChild, 第二次遍历开始, 并且在这次遍历处理中,会调用Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)), 这一轮的前提是useLargestChild, 并且主要目的是为了在这种情况下得到一个合适的mTotalLength.

mTotalLength += mPaddingTop + mPaddingBottom; 加上上下padding.

然后还要将得到的mTotalLength和getSuggestedMinimumHeight()进行对比,取其中大者保存在heightSize中.

heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0): Reconcile our calculated size with the heightMeasureSpec, 该函数在基类View中实现,其思路就是基于childView想要的尺寸(size)以及parentView对其附加的约束(就是measureSpec)和一些附加的childMeasuredState(比如MEASURED_STATE_TOO_SMALL), 得到一个复合的int值,其内部包含了具体尺寸,以及measureState(比如MEASURED_STATE_TOO_SMALL), 然后 heightSize = heightSizeAndState & MEASURED_SIZE_MASK.

delta = heightSize - mTotalLength. 这个值很关键,这表示了当前LinearLayout中还留有的空白区域(这个差值基本就是mTotalLength(之前两轮遍历非weightView所需要的height的总值)和heightMeasureSpec(parent传递给LinearLayout的height上的约束)),这个区域就可以分配给weight>0的那些View去按比例分配.

如果delat != 0即有差距或者totalWeight >0, 那么还会开始第三轮的childView遍历,这一次主要针对的是weight>0的childView, 对其进行measure:

首先会得到weightSum, 如果mWeightSum>0(即实现指定了一个weight sum)那么使用mWeightSum,否则使用totalWeight.

再次进行virtual child的遍历:

对于GONE的childView, 直接略过.

获取childView的lp, 并将lp的weight保存在childExtra中,

如果childExtra > 0, 即该View指定了一个weight值. 这时候要为其将delta多余的值按照占weightSum的百分比分给该ChildView. 同时weightSum -= childExtra, delta -= share为下一个ChildView的weight计算做准备. 这里可以看出,对于这个之前已经被measure过的childView,会在其原来measure的height的基础上将delta进行叠加,然后构造一个MeasureSpec重新传递给childView进行measure.

否则, 留给这个ChildView的空间大小也就是这次从delta中根据weight占比分出的来的一部分空间, 将这个值构造一个MeasureSpec然后对ChildView进行measure

同样,childState会收集整合在这个过程中所有childView的measuredState. 在遍历过程中,也会不断的比较来取得maxWidth 以及 mTotalLength和allFillParent(width方向上的)

如果没有什么差值(delta == 0) 或者totalWeight = 0(没有View使用weight), 这种情况下只会对useLargestChild=true并且heightMode != MeasureSpec.EXACTLY(可见useLargestChild只有在heightMode不是EXACTLY并且weight机制不生效时才会生效)时才会进行一次对childView的遍历, 用largestChildHeight + EXACTLY构造一个MeasureSpec对childView进行measure.

如果当前不是allFillParent并且widthMode不是EXACTLY, 那么maxWidth = alternativeMaxWidth.

maxWidth会累加上LinearLayout的PaddingLeft/Right, 并且要考虑getSuggestedMinimumWidth(), 取大者.

基于maxWidth和parent传入的widthMeasureSpec以及之前历次遍历中收集到childView的state的集合体, 调用resolveSizeAndState(…)得到一个合适的withSize.

最后调用setMeasuredDimension(…)将上面通过resolveSizeAndState(…)得到的widthSize和heightSize通过setMeasuredDimension(…)设置上去.

最后,如果需要matchWidth(这个值会在widthMode不是EXACTLY并且某个childView的width是MATCH_PARENT时才会设置为true), 那么还需要调用forceUniformWidth(count, heightMeasureSpec):

先基于LinearLayout的measured width + EXACTLY 构造一个uniformMeasureSpec.

然后开始遍历childView: 对与指定了width是MATCH_PARENT的childView, 调用measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0)对childView进行一次width基于uniformMeasureSpec的measure, 注意的是child的height其实在measure前后会被保存和恢复, 即这次measure只会影响width是MATCH_PARENT的childView的width
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: