Android View 原始碼解析(四) - LinearLayout原始碼分析
LinearLayout 原始碼分析
measure過程
主要過程
- 根據佈局方向選擇measure過程分支
- 初始化相關變數
- 對View進行第一次測量
- mTotalLength的再次測量
- 二次測量部分View和對為測量的子View進行測量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //判斷佈局方向 if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }
measureVertical和measureHorizontal只是佈局方向上的區別 以下主要分析measureVertical方法
初始化相關變數
//mTotalLength是記錄內部使用的高度也就是子View的高度和 而不是LinearLayout的高度 mTotalLength = 0; //子檢視的最大寬度(不包括layout_weight>0的子View) int maxWidth = 0; int childState = 0; int alternativeMaxWidth = 0; //子檢視的最大寬度(僅包含layout_weight>0的子View) int weightedMaxWidth = 0; //子檢視是否均為fillParent 用於判斷是否需要重新計算 boolean allFillParent = true; //權重值的總和 float totalWeight = 0; //子View的數量(統一級別下) final int count = getVirtualChildCount(); //高度寬度模式 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //子View的寬度是否需要由父View決定 boolean matchWidth = false; boolean skippedMeasure = false; //第幾個子View的baseLine作為LinearLayout的基準線 final int baselineChildIndex = mBaselineAlignedChildIndex; //mUseLargestChild為是否使用最大子元素的尺寸作為標準再次測量 final boolean useLargestChild = mUseLargestChild; //子View中最高高度 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); // 測量為null的子檢視的高度 // measureNullChild() 暫時返回 0 便於擴充套件 if (child == null) { mTotalLength += measureNullChild(i); continue; } //Visibility為Gone的時候跳過該View // getChildrenSkipCount()方法同樣返回0 便於擴充套件 if (child.getVisibility() == View.GONE) { i += getChildrenSkipCount(child, i); continue; } //根據showDivider的值(通過hasDividerBeforeChildAt()) 來決定當前子View是否需要新增分割線的高度 if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerHeight; } //會將子view的LayoutParams強轉為父View的LayoutParams型別 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)child.getLayoutParams(); totalWeight += lp.weight; if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { // 滿足該條件的話 不需要現在計算該子檢視的高度 測量工作會在之後進行 // 若子View的height=0 且weight> 0 則說明該View希望使用的是LinearLayout的剩餘空間 // LinearLayout是EXACTLY模式的說明LinearLayout高度已經確定 不需要依賴子View的測量結果來計算自己 就無需測量該子View final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { //測量子View int oldHeight = Integer.MIN_VALUE; //當前View的height=0 且weight> 0 則說明該LinearLayout的高度需要靠子View測量(不需要的在上面分支處理了) //將子View的高度設為-1 防止子View高度為0 if (lp.height == 0 && lp.weight > 0) { oldHeight = 0; lp.height = LayoutParams.WRAP_CONTENT; } //呼叫子View的measureChildWithMargins() 對子View進行測量 //第四個引數表示當前已使用的寬度因為是豎直模式 所以為0 //最後一個引數表示已使用的高度 如果之前的子View或者當前的View有weight屬性 則當前子檢視使用 LinearLayout 的所有高度 已使用的高度為0 measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { //測量完成後 重置子View高度 lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; // 比較child測量前後總高度 取較大值 ///getNextLocationOffset() 返回0 便於擴充套件 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); // 設定最高子檢視大小 if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } } // mBaselineChildTop 表示指定的 baseline 的子檢視的頂部高度 if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) { mBaselineChildTop = mTotalLength; } // 設定為 baseline 的子檢視的前面不允許設定 weiget 屬性 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; //當LinearLayout非EXACTLY模式 並且自View為MATCH_PARENT時 //設定matchWidth和matchWidthLocally為true //該子View佔據LinearLayout水平方向上所有空間 if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) { matchWidth = true; matchWidthLocally = true; } final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; //對一堆變數賦值 maxWidth = Math.max(maxWidth, measuredWidth); childState = combineMeasuredStates(childState, child.getMeasuredState()); allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; if (lp.weight > 0) { weightedMaxWidth = Math.max(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); } i += getChildrenSkipCount(child, i); }
二次測量mTotalLength
//根據hasDividerBeforeChildAt得到showDivider的值是否為end 來判斷是否需要加上divider的高度 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) mTotalLength += mDividerHeight; } //如果高度測量模式為AT_MOST或者UNSPECIFIED 則進行二次測量 且設定了measureWithLargestChild 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(); // 計算所有子View的高度之和 final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } }
就是需要useLargestChild
而 mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);
就是說僅在LinearLayout的measureWithLargestChild屬性設定為True時(預設為false)才可能出現某個child被二次測量
例項如下

mTotalLength的二次測量
二次測量部分View和對為測量的子View進行測量
//加上padding的值 mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; //minHeight和當前使用的高度比較取較大值 heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); //根據heightMeasureSpec協助計算heightSizeAndState的大小 //resolveSizeAndState方法之後會分析 int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); 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. //delta為額外的空間 及LinearLayout中未被分配的空間(可以為負) int delta = heightSize - mTotalLength; if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { //skippedMeasure為第一次測量下對跳過測量的子View設定的 //weightSum為權重和 如果設定了總權重則使用我們所設定的如果沒有則使用子View的weight和 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 //計算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)) { //子檢視已經被測量過 //非EXACTLY view需要加上share int childHeight = child.getMeasuredHeight() + share; if (childHeight < 0) { childHeight = 0; } //重新測量View child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); } else { //如果當前是EXACTLY模式 說明沒有被測量 需要進行測量 //子檢視首次被測量 //EXACTLY模式下 將weight佔比的高度分配給子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 { 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)); } } } } if (!allFillParent && widthMode != MeasureSpec.EXACTLY) { maxWidth = alternativeMaxWidth; } maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); if (matchWidth) { forceUniformWidth(count, heightMeasureSpec); }
resolveSizeAndState方法 定義在View中
/** * Utility to reconcile a desired size and state, with constraints imposed * by a MeasureSpec. Will take the desired size, unless a different size * is imposed by the constraints. The returned value is a compound integer, * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the * resulting size is smaller than the size the view wants to be. * * @param size How big the view wants to be. * @param measureSpec Constraints imposed by the parent. * @param childMeasuredState Size information bit mask for the view's *children. * @return Size information bit mask as defined by *{@link #MEASURED_SIZE_MASK} and *{@link #MEASURED_STATE_TOO_SMALL}. */ public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
delta為負的相關解析
相關程式碼及效果如下

負delta相關解析
根據之前的measure流程分析一下
- 相關變數初始化
- 第一次測量 兩個子TextView都會被測量 TextView1.height = TextView1.height = 500dp 則mToatalLength為1000dp
- mToatalLength再次測量跳過
- 計算delta delta = heightSize - mTotalLength 根據resolveSizeAndState方法 父LinearLayout是EXACTLY模式 所以最終heightSize為500dp delta = -500dp
- 根據weight分配剩餘空間 TextView1.height = 500 + 1 / 5 * (- 500) = 400 dp
TextView2.height = 500 + 4 / 5 * (- 500) = 100 dp
layout過程
protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }
我們可以看出 同樣是分成水平和豎直兩個方向的 同樣分析豎直 方向下的layout過程
/** * Position the children during a layout pass if the orientation of this * LinearLayout is set to {@link #VERTICAL}. * * @see #getOrientation() * @see #setOrientation(int) * @see #onLayout(boolean, int, int, int, int) * @param left * @param top * @param right * @param bottom */ void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; //父View預設子View的寬度 final int width = right - left; //子View的右側預設位置 int childRight = width - mPaddingRight; // 子View的可用空間大小 int childSpace = width - paddingLeft - mPaddingRight; //子View的個數 final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //根據LinearLayout設定的對其方式 設定第一個子View的Top值 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; } //遍歷各個子View for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { //LinearLayout中子View的寬和高有measure過程決定 final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); //獲取子View的LayoutParams 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); //根據子View的對其方式設定Left值 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; } //子View的top修改 childTop += lp.topMargin; //用setChildFrame()方法設定子控制元件控制元件的在父控制元件上的座標軸 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }
draw 原始碼分析
protected void onDraw(Canvas canvas) { if (mDivider == null) { return; } if (mOrientation == VERTICAL) { drawDividersVertical(canvas); } else { drawDividersHorizontal(canvas); } }
同樣主要分析垂直方向的處理
void drawDividersVertical(Canvas canvas) { final int count = getVirtualChildCount(); //根據計算好的座標繪製對應的子View for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { if (hasDividerBeforeChildAt(i)) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int top = child.getTop() - lp.topMargin - mDividerHeight; drawHorizontalDivider(canvas, top); } } } //繪製分割線 if (hasDividerBeforeChildAt(count)) { final View child = getLastNonGoneChild(); int bottom = 0; if (child == null) { bottom = getHeight() - getPaddingBottom() - mDividerHeight; } else { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); bottom = child.getBottom() + lp.bottomMargin; } drawHorizontalDivider(canvas, bottom); } } void drawHorizontalDivider(Canvas canvas, int top) { mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight); mDivider.draw(canvas); }
以上
【附錄】

資料圖