先看看美女吧
- 核心成員標記
- 內部類
- 核心方法分解
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兩次都測量了,那么效率自然是受到影響的啦。哪些情況會測量兩次呢:
如果有幫助到你認識系統控件,點個贊再走唄......