先看看美女吧
- 核心成员标记
- 内部类
- 核心方法分解
1. 核心成员,标记
orientation: 横向或者竖向排列,默认是0, 0是横向的, 1是竖向的。
gravity属性:控制子view整体的摆放,如left-左顶边, right-右顶边, bottom-下顶边,center-horizontal-水平居中。
weightSum:横向或者纵向的权重和,默认是-1哦也就是没有权重。一般是不计算该数值,而是计算子view集设置的权重和,与该值不同呢。
divider, showDividers, dividerPadding:内置分割线,以及他的位置和padding距离。注意padding距离是不会拉开子元素的间距的,而只是设定分割线本身的宽度呢。
2. 内部类
- LinearLayout.LayoutParams: 提供给LinearLayout的子元素布局定位使用。
- weight: 宽或高的权重,不设置默认是0。
- gravity: 其实这个和linearlayout本身的gravity属性是很相似的,只不过前者是应用到所有的item,这个只是给当前的子child使用, 不设置默认是-1,代表啥都不是。
3. 重要方法分解
onDraw: 该方法是来绘制view内容的,但LinearLayout的内容只有divider-分割线, 所以该方法只是根据横向还是纵向规则来绘制divider哦,别无其他的内容。
-
drawDividersVertical:绘制纵向排列的分割线,分割线其实就是横着的一条线啦。
1. 遍历所有的child, 然后根据showDividers是middle, start, end来给child前面绘制分割线。比如:如果是middle,那么则在每个子child的前面都绘制一个分割线(除了第一个chld外), 如是start,则在第一个child前绘制分割线;如是end, 那么在最后一个child后面绘制分割线。 2. drawHorizontalDivider,这个是绘制纵排列的分割线divider的核心方法, 这里很简单就是先设置drawable的上,下,左右边界,然后将该drawable绘制到view的canvas上。即drawable.draw(canvas)。
-
drawDividersHorizontal: 绘制横向排列的分割,就是一条垂线啦。
1. 逻辑和上面的基本上一样的。有一个地方不同是,横向绘制,需要考虑是从左到右还是从右边到左边。
onMeasure: 测量---横向测量与纵向测量。
measureVertical: 纵向测量
1. 第一次测量,测出大部分的子view, 并得出LinearLayout的高度。要知道,这里的子view如果是wrap的或者带有权重的测量的都不是最终的。
2. 第二次测量,补充测量前面没有测过的子view, 如果确定了LinearLayout的高度后,前面测量的子view并未有填充满linearlayout的高(因为带权重的view第一次测量的高度不是最终的),这里会通过他们的权重比去计算出最终的各个带wrap, 或者带有权重的view的高度。
3. 设置LinearLayout的高度,并纠正那些子view的宽度是match的,测量出这些子view的宽度。-
第一次测量:("废话一堆,show me the code",好的, 仔细地看注释哦~)
for (int i = 0; i < count; ++i) {// ....... totalWeight += lp.weight; //情景一, 如果LinearLayout是精确模式, child的height=0, weight>0,那么该child第一次测量的时候不测量。 if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { final int totalLength = mTotalLength; //但是margin空间累计起来,算是LinearLayout占用的空间 mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { //除了情况一之外, 所有的子view都是要测量的呢! 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 // 这里的情景二,是LinearLayout不是精确模式, 要先测量该子view, 要用wrap_conet,去计算我们的view占据的空间。就是说如果view(0dp+weight),测量的实际结果是由view本身的wrap给出的限度值。 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). // 测量我们的子view,如果没有weight那就是除开已经占据的mTotalLength空间,去进行测量啦;如果有weight这里测量的view就不考虑已经用过的空间。 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; // 将所有测量过的view都累计起来,计算已经占据了多少的空间,为后面计算LinearLayout的高度而用。 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); if (useLargestChild) {//如果xml设置了measureWithLargestChild,这里会记录最大高度的view, 以备后面使用。 largestChildHeight = Math.max(childHeight, largestChildHeight); } } ...... //如果LinearLayout的width是wrap,并且子view是match的情景,后面需要将该子view的宽度来一次校准。充满linearlayout的宽度。 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; } ...... //累计divider的高度 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerHeight; } //这是一种特殊情况,如果xml使用了measureWithLargestChild,并且LinearLayout不是Exactly,一般也就是wrap啦, 那么我们LinearLayout的高度就是n*largestChildHeight啦,当然要加上对应的margin,不信你试试。 if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); if (child == null) { mTotalLength += measureNullChild(i); continue; } if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); // Account for negative margins final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } } ....... // Add in our padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // 通过LinearLayout使用的高度heightSize,和LinearLayout的测量规格来一次最终测量, //从而得出我们的LinearLayout的最终高度。如果测量规格是Exactyly <Linearlayt是match或者dimense>,那么我们的高度就是测量规格里给的高度,如果是At_most(linearlayout是wrap), 那么我们的高度就是上面的heightSize啦。 int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); //linearlayout最终的高度 heightSize = heightSizeAndState & MEASURED_SIZE_MASK; // Either expand children with weight to take up available space or // shrink them if they extend beyond our current bounds. If we skipped // measurement on any children, we need to measure them now. } }
-
第二次测量:show me the code
...... //看看还有没有剩余空间。哪些情况下有剩余空间呢?比如我们的LinearLayout是match的时候呀,且有子view有weight,高度是0或者是wrap 这个时候我们第一次测量后可能有剩余的空间,我们要把这个剩余的空间平分给带weight的兄弟们。 int delta = heightSize - mTotalLength; if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { 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) { //这里就是weight平分的逻辑啦。 // 公式 = 剩余高度*(子控件的weight/weightSum),也就是子控件的weight占比*剩余高度 // Child said it could absorb extra space -- give him his share 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 //走这里的时候,view之前有高度,这里再加上平分一份就是view的最终高度啦。好好想想这里,view之前有高度就是测量过了啦,这里又测量,这就是linearlayout子view耗性能的地方。所以linearLayout如果设置了weight不要轻易设置wrap或者dimense,这是不好滴操作的。 if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { // child was measured once already above... // base new measurement on stored values 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 //如果子view第一次跳过的在这里会必然测量的哦。 child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, MeasureSpec.EXACTLY)); } // Child may now not fit in vertical dimension. childState = combineMeasuredStates(childState, child.getMeasuredState() & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT)); } 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 {//这里其实是对xml里配置了measureWithLargestChild的情景,且上面的条件不满足,主要是没了剩余空间的,将所有的子view带weight都设为高largestChildHeight。比如我们的LinearLayout是wrap, 然后子view都是wrap又有weight, 第一次测量完之后其实是没有剩余空间的,就走到这里来了,会用最大view的高度给其他的带weight的view设高,不管weight是多少都是一样的。不信你试试呢。。。。 alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth); // We have no limit, so make all weighted views as tall as the largest child. // Children will have already been measured once. if (useLargestChild && heightMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null || child.getVisibility() == View.GONE) { continue; } final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { child.measure( MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(largestChildHeight, MeasureSpec.EXACTLY)); } } } }
-
设置LinearLayout的高度,并纠正那些子view的宽度是match的,测量出这些子view的宽度。
maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); //哈哈,这里就设置了linearLayout的宽,高啦。差不多结束啦。 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); //纠正那些子view的宽度是match的,测量出这些子view的宽度 if (matchWidth) { //这个方法很简单,看看就好了。 forceUniformWidth(count, heightMeasureSpec); } //结束啦。
measureHorizontal: 逻辑和纵向测量差不多,就不说啦。
-
onLayout:纵向横向布局子view呀, layoutVertical,layoutHorizontal。
-
layoutVertical:
void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // 计算出LinearLayout的宽度 final int width = right - left; int childRight = width - mPaddingRight; // Space available for child int childSpace = width - paddingLeft - mPaddingRight; final int count = getVirtualChildCount(); //垂直向的重心规则计算,计算出第一个child的top位置 final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; switch (majorGravity) { case Gravity.BOTTOM: //如果是重心在底部 // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already //如果重心在中间, case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; //重心默认在上面 case Gravity.TOP: default: childTop = mPaddingTop; break; } //计算完child的top位置之后, 现在要计算每个child的left和top位置呢。 for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //读取水平方向的重心 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { //重心在中间 case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; //如果是右对齐, case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; //如果是左对齐,默认左对齐计算 case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } // childTop += lp.topMargin; //计算完child的left, top,就可以在这里设置他的位置啦。 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); //计算下个child的top啦。 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }
-
4. 疑问解惑
-
当你在布局时候,LinearLayout的子控件中写上了weight, ide有时候会提醒你注意效率问题, linearLayout哪些布局情况会导致效率低下?
- 从上面可以看出,linearlayout的测量有两次。如果子view两次都测量了,那么效率自然是受到影响的啦。哪些情况会测量两次呢:
1. view的布局是带weight的,而且高度不为0, LInearLayout是Exactly, 这种第一次会测量,第二次一般也会测量,所以就有两次测量啦。(之所以说一般是要求第二次测量之前是有剩余空间去分配权重的哦)
2. view带权重,LinearLayout不是Exactly模式,第一次会测量,第二次一般也会测量,也是要求有剩余空间的。
3. view带权重,linearayout不是Exactly模式,设定了useLargestChild = true(这里一般是没有剩余空间的), 依然也会再测试一次。 - 总结:所以呀安全策略, 就是view如果带权重呢,LinearLayout设定为Exactly模式, 子view的待测宽或者高设置为0就好了,那么就不会有测量效率问题啦。
- 从上面可以看出,linearlayout的测量有两次。如果子view两次都测量了,那么效率自然是受到影响的啦。哪些情况会测量两次呢:
如果有帮助到你认识系统控件,点个赞再走呗......