Android角落 不妨再看LinearLayout

羽翼君發表於2018-01-09

本文首發於github,是我所在的組LittleFriendsGroup的一個原始碼分析專案哦,如果您感興趣,也可以去認領一篇文章寫寫你的觀點。

宣告.本專案原始碼基於Api 23

1.談談LinearLayout

Android的常用佈局裡,LinearLayout屬於使用頻率很高的佈局。RelativeLayout也是,但相比於RelativeLayout每個子控制元件都需要給上ID以供另一個相關控制元件擺放位置來說,LinearLayout兩個方向上的排列規則在明顯垂直/水平排列情況下使用更加方便。

同時,出於效能上來說,一般而言功能越複雜的佈局,效能也是越低的(不考慮巢狀的情況下)。

相比於RelativeLayout無論如何都是兩次測量的情況下,LinearLayout只有子控制元件設定了weight屬性時,才會有二次測量,其餘情況都是一次。

另外,LinearLayout的高階用法除了weight,還有divider,baselineAligned等用法,雖然用的不常見就是了。

以下是LinearLayout相比於其他佈局所擁有的特性:

屬性 值型別 描述 備註
orientation int 作為LinearLayout必須使用的屬性之一,支援縱向排布或者水平排布子控制元件
weightSum float 指定權重總和 預設值為1.0
baselineAligned boolean 基線對齊
baselineAlignedChildIndex int 該LinearLayout下的view以某個繼承TextView的View的基線對齊
measureWithLargestChild boolean 當值為true,所有帶權重屬性的View都會使用最大View的最小尺寸
divider(需要配合showDividers使用) drawable in java/reference in xml 如同您常在ListView使用一樣,為LinearLayout新增分割線 [api>11] 同時如果是自己建立的drawable,請指定size

【注意】divider附加屬性為showDividers(middle|end|beginning|none):

  • middle 在每兩項之間新增分割線
  • end 在整體的最後一項新增分割線
  • beginning 在整體的首項新增分割線
  • none 無

本篇主要針對LinearLayout垂直方向的測量、weight和divider進行分析,其餘屬性因為比較冷門,因此不會詳說


2.使用方法

對於LinearLayout的使用,相信您閉著眼睛都能寫出來,因此這裡就略過了。


3.原始碼分析

原始碼分析階段主要針對這幾個地方:

  • measure流程
  • weight的計算

後兩者的主要工作其實都是被包含在measure裡面的,因此對於LinearLayout來說,最重要的,依然是measure.

3.1 measure

在LinearLayout的onMeasure()裡面,所有的測量都根據mOrientation這個int值來進行水平或者垂直的測量計算。

我們都知道,java中int在初始化不分配值的時候,都是預設的0,因此如果我們不指定orientation,measure則會按照水平方向來測量【水平orientation=0/垂直orientation=1】

接下來我們主要看看measureVertical方法,瞭解了垂直方向的測量之後,水平方向的也就不難理解了,為了篇幅,我們主要分析垂直方向的測量。

measureVertical方法除去註釋,大概200多行,因此我們分段分析。

方法主要分為三大塊:

  • 一大堆變數
  • 一個主要的for迴圈來不斷測量子控制元件
  • 其餘引數影響以及根據是否有weight再次測量
3.1.1

一大堆變數

為何這裡要說說變數,因為這些變數都會極大的影響到後面的測量,同時也是十分容易混淆的,所以這裡需要貼一下。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

        // mTotalLength作為LinearLayout成員變數,其主要目的是在測量的時候通過累加得到所有子控制元件的高度和(Vertical)或者寬度和(Horizontal)
        mTotalLength = 0;
        // maxWidth用來記錄所有子控制元件中控制元件寬度最大的值。
        int maxWidth = 0;
        // 子控制元件的測量狀態,會在遍歷子控制元件測量的時候通過combineMeasuredStates來合併上一個子控制元件測量狀態與當前遍歷到的子控制元件的測量狀態,採取的是按位相或
        int childState = 0;
        
        /**
         * 以下兩個最大寬度跟上面的maxWidth最大的區別在於matchWidthLocally這個引數
         * 當matchWidthLocally為真,那麼以下兩個變數只會跟當前子控制元件的左右margin和相比較取大值
         * 否則,則跟maxWidth的計算方法一樣
         */
        // 子控制元件中layout_weight<=0的View的最大寬度
        int alternativeMaxWidth = 0;
        // 子控制元件中layout_weight>0的View的最大寬度
        int weightedMaxWidth = 0;
        // 是否子控制元件全是match_parent的標誌位,用於判斷是否需要重新測量
        boolean allFillParent = true;
        // 所有子控制元件的weight之和
        float totalWeight = 0;

        // 如您所見,得到所有子控制元件的數量,準確的說,它得到的是所有同級子控制元件的數量
        // 在官方的註釋中也有著對應的例子
        // 比如TableRow,假如TableRow裡面有N個控制元件,而LinearLayout(TableLayout也是繼承LinearLayout哦)下有M個TableRow,那麼這裡返回的是M,而非M*N
        // 但實際上,官方似乎也只是直接返回getChildCount(),起這個方法名的原因估計是為了讓人更加的明白,畢竟如果是getChildCount()可能會讓人誤認為為什麼沒有返回所有(包括不同級)的子控制元件數量
        final int count = getVirtualChildCount();
        
        // 得到測量模式
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // 當子控制元件為match_parent的時候,該值為ture,同時判定的還有上面所說的matchWidthLocally,這個變數決定了子控制元件的測量是父控制元件干預還是填充父控制元件(剩餘的空白位置)。
        boolean matchWidth = false;
        
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;        
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
    }
複製程式碼

這裡有很多變數和值,事實上,直到現在,我依然沒有完全弄明白這些值的意義。

在這一大堆變數裡面,我們主要留意的是三個方面:

  • mTotalLength:這個就是最終得到的整個LinearLayout的高度(子控制元件高度累加及自身padding)
  • 三個跟width相關的變數
  • weight相關的變數

3.1.2

測量

通過for迴圈不斷的得到子控制元件然後根據自己的定義進行賦值,這就是LinearLayout測量裡面最重要的一步。

這裡的程式碼比較長,去掉註釋後有100行左右,因此這裡採取重要地方註釋結合文字描述來分析。

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // ...接上面的一大堆變數
        for (int i = 0; i < count; ++i) {

            final View child = getVirtualChildAt(i);

            if (child == null) {
                // 目前而言,measureNullChild()方法返回的永遠是0,估計是設計者留下來以後或許有補充的。
                mTotalLength += measureNullChild(i);
                continue;
            }
           
            if (child.getVisibility() == GONE) {
               // 同上,返回的都是0。
               // 事實上這裡的意思應該是當前遍歷到的View為Gone的時候,就跳過這個View,下一句的continue關鍵字也正是這個意思。
               // 忽略當前的View,這也就是為什麼Gone的控制元件不佔用佈局資源的原因。(畢竟根本沒有分配空間)
                i += getChildrenSkipCount(child, i);
                continue;
            }

            // 根據showDivider的值(before/middle/end)來決定遍歷到當前子控制元件時,高度是否需要加上divider的高度
            // 比如showDivider為before,那麼只會在第0個子控制元件測量時加上divider高度,其餘情況下都不加
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerWidth;
            }

            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                    child.getLayoutParams();
            // 得到每個子控制元件的LayoutParams後,累加權重和,後面用於跟weightSum相比較
            totalWeight += lp.weight;
            
            // 我們都知道,測量模式有三種:
            // * UNSPECIFIED:父控制元件對子控制元件無約束
            // * Exactly:父控制元件對子控制元件強約束,子控制元件永遠在父控制元件邊界內,越界則裁剪。如果要記憶的話,可以記憶為有對應的具體數值或者是Match_parent
            // * AT_Most:子控制元件為wrap_content的時候,測量值為AT_MOST。
            
            // 下面的if/else分支都是跟weight相關
            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // 這個if裡面需要滿足三個條件:
                // * LinearLayout的高度為match_parent(或者有具體值)
                // * 子控制元件的高度為0
                // * 子控制元件的weight>0
                // 這其實就是我們通常情況下用weight時的寫法
                // 測量到這裡的時候,會給個標誌位,稍後再處理。此時會計算總高度
                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) {
                    // 滿足這兩個條件,意味著父類即LinearLayout是wrap_content,或者mode為UNSPECIFIED
                    // 那麼此時將當前子控制元件的高度置為wrap_content
                    // 為何需要這麼做,主要是因為當父類為wrap_content時,其大小實際上由子控制元件控制
                    // 我們都知道,自定義控制元件的時候,通常我們會指定測量模式為wrap_content時的預設大小
                    // 這裡強制給定為wrap_content為的就是防止子控制元件高度為0.
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                
                /**【1】*/
                // 下面這句雖然最終呼叫的是ViewGroup通用的同名方法,但傳入的height值是跟平時不一樣的
                // 這裡可以看到,傳入的height是跟weight有關,關於這裡,稍後的文字描述會著重闡述
                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;
                // getNextLocationOffset返回的永遠是0,因此這裡實際上是比較child測量前後的總高度,取大值。
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            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;
            
            // 還記得我們變數裡又說到過matchWidthLocally這個東東嗎
            // 當父類(LinearLayout)不是match_parent或者精確值的時候,但子控制元件卻是一個match_parent
            // 那麼matchWidthLocally和matchWidth置為true
            // 意味著這個控制元件將會佔據父類(水平方向)的所有空間
            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);
        }
    }
複製程式碼

在程式碼中我註釋了一部分,其中最值得注意的是measureChildBeforeLayout()方法。這個方法將會決定子控制元件可用的剩餘分配空間。

measureChildBeforeLayout()最終呼叫的實際上是ViewGroup的measureChildWithMargins(),不同的是,在傳入高度值的時候(垂直測量情況下),會對weight進行一下判定

假如當前子控制元件的weight加起來還是為0,則說明在當前子控制元件之前還沒有遇到有weight的子控制元件,那麼LinearLayout將會進行正常的測量,若之前遇到過有weight的子控制元件,那麼LinearLayout傳入0。

那麼measureChildWithMargins()的最後一個引數,也就是LinearLayout在這裡傳入的這個高度值是用來幹嘛的呢?

如果我們追溯下去,就會發現,這個函式最終其實是為了結合父類的MeasureSpec以及child自身的LayoutParams來對子控制元件測量。而最後傳入的值,在子控制元件測量的時候被新增進去。

    
     protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
複製程式碼

在官方的註釋中,我們可以看到這麼一句:

  • @param heightUsed Extra space that has been used up by the parent vertically (possibly by other children of the parent)

事實上,我們在程式碼中也可以很清晰的看到,在getChildMeasureSpec()中,子控制元件需要把父控制元件的padding,自身的margin以及一個可調節的量三者一起測量出自身的大小。

那麼假如在測量某個子控制元件之前,weight一直都是0,那麼該控制元件在測量時,需要考慮在本控制元件之前的總高度,來根據剩餘控制元件分配自身大小。而如果有weight,那麼就不考慮已經被佔用的控制元件,因為有了weight,子控制元件的高度將會在後面重新賦值。


3.2 weight

3.2.1

weight的再次測量

在上面的程式碼中,LinearLayout做了針對沒有weight的工作,在這裡主要是確定自身的大小,然後再針對weight進行第二次測量來確定子控制元件的大小。

我們接著看下面的程式碼:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        //...接上面
        // 下面的這一段程式碼主要是為useLargestChild屬性服務的,不在本文主要分析範圍,略過
        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        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());
        
        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

}    

複製程式碼

上面這裡是為weight情況做的預處理。

我們略過useLargestChild 的情況,主要看看if處理外的程式碼。在這裡,我沒有去掉官方的註釋,而是保留了下來。

從中我們不難看出heightSize做了兩次賦值,為何需要做兩次賦值。

因為我們的佈局除了子控制元件,還有自己本身的background,因此這裡需要比較當前的子控制元件的總高度和背景的高度取大值。

接下來就是判定大小,我們都知道測量的MeasureSpec實際上是一個32位的int,高兩位是測量模式,剩下的就是大小,因此heightSize = heightSizeAndState & MEASURED_SIZE_MASK;作用就是用來得到大小的精確值(不含測量模式)

接下來我們看這個方法裡面第二佔比最大的程式碼:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
		//...接上面

		//算出剩餘空間,假如之前是skipp的話,那麼幾乎可以肯定是有剩餘空間(同時有weight)的
        int delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
			// 限定weight總和範圍,假如我們給過weighSum範圍,那麼子控制元件的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) {
                    // 全篇最精華的一個地方。。。。擁有weight的時候計算方式,ps:執行到這裡時,child依然還沒進行自身的measure
					
					// 公式 = 剩餘高度*(子控制元件的weight/weightSum),也就是子控制元件的weight佔比*剩餘高度
                    int share = (int) (childExtra * delta / weightSum);
					// weightSum計餘
                    weightSum -= childExtra;
					// 剩餘高度
                    delta -= share;
					
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);
                   
                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }
                        
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
   
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }


                    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));
            }


            mTotalLength += mPaddingTop + mPaddingBottom;

        } 
		
		// 沒有weight的情況下,只看useLargestChild引數,如果都無相關,那就走layout流程了,因此這裡忽略
		else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);

            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));
                    }
                }
            }
        }
}
複製程式碼

3.2.2

weight的兩種情況

這次我的註釋比較少,主要是因為需要有一大段的文字來描述。

在weight計算方面,我們可以清晰的看到,weight為何是針對剩餘空間進行分配的原理了。 我們打個比方,假如現在我們的LinearLayout的weightSum=10,總高度100,有兩個子控制元件(他們的height=0dp),他們的weight分別為2:8。

那麼在測量第一個子控制元件的時候,可用的剩餘高度為100,第一個子控制元件的高度則是100*(2/10)=20,接下來可用的剩餘高度為80

我們繼續第二個控制元件的測量,此時它的高度實質上是80*(8/8)=80

到目前為止,看起來似乎都是正確的,但關於weight我們一直有一個疑問:**就是我們為子控制元件給定height=0dp和height=match_parent時我們就會發現我們的子控制元件的高度比是不同的,前者是2:8而後者是調轉過來變成8:2 **

對於這個問題,我們不妨繼續看看程式碼。

接下來我們會看到這麼一個分支:

if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { } else {}

首先我們不管heightMode,也就是父類的測量模式,剩下一個判定條件就是lp.height,也就是子類的高度。

既然有針對這個進行判定,那就是意味著肯定在此之前對child進行過measure,事實上,在這裡我們一早就對這個地方進行過描述,這個方法正是measureChildBeforeLayout()

還記得我們的measureChildBeforeLayout()執行的先行條件嗎

YA,just u see,正是不滿足(LinearLayout的測量模式非EXACTLY/child.height==0/child.weight/child.weight>0)之中的child.height==0

因為除非我們指定height=0,否則match_parent是等於-1,wrap_content是等於-2.

在執行measureChildBeforeLayout(),由於我們的child的height=match_parent,因此此時可用空間實質上是整個LinearLayout,執行了measureChildBeforeLayout()後,此時的mTotalLength是整個LinearLayout的大小

回到我們的例子,假設我們的LinearLayout高度為100,兩個child的高度都是match_parent,那麼執行了measureChildBeforeLayout()後,我們兩個子控制元件的高度都將會是這樣:

child_1.height=100

child_2.height=100

mTotalLength=100+100=200

在一系列的for之後,執行到我們剩餘空間:

int delta = heightSize - mTotalLength;

(delta=100[linearlayout的實際高度]-200=-100)

沒錯,你看到的的確是一個負數。

接下來就是套用weight的計算公式:

share=(int) (childExtra * delta / weightSum)

即:share=-100(2/10)=-20;*

然後走到我們所說的if/else裡面

 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));
                    } 
複製程式碼

我們知道**child.getMeasuredHeight()=100**

接著這裡有一條int childHeight = child.getMeasuredHeight() + share;

這意味著我們的**childHeight=100+(-20)=80;**

接下來就是走child.measure,並把childHeight傳進去,因此最終反饋到介面上,我們就會發現,在兩個match_parent的子控制元件中,weight的比是反轉的。

接下來沒什麼分析的,剩下的就是走layout流程了,對於layout方面,要講的其實沒什麼東西,畢竟基本都是模板化的寫法了。


4.小結

在這裡,我們花費了大篇幅講解measureVertical()的流程,事實上對於LinearLayout來說,其最大的特性也正是兩個方向的排布以及weight的計算方式。

在這裡我們不妨回過頭看一下,其實我們會發現在測量過程中,設計者總是有意分開含有weight和不含有weight的測量方式,同時利用height跟0比較來更加的細分每一種情況。

可能初看的時候覺得程式碼太多,事實上一輪分析下來,方向還是很清晰的。畢竟有weight的地方前期都給個標誌跳過,在測量完需要的資料(比如父控制元件的總高度什麼的)後,再根據父控制元件的資料和weight再針對進行二次測量。

在文章的最後,我們小結一下對於測量這裡的演算法的不同情況下的區別以及原理:

  • 父控制元件是match_parent(或者精確值),子控制元件擁有weight,並且高度給定為0:

    • 子控制元件的高度比例將會跟我們分配的layout_weight一致,原因在於weight二次測量時走了else分支,傳入的是計算出來的share值
  • 父控制元件是match_parent(或者精確值),子控制元件擁有weight,但高度給定為match_parent(或者精確值):

    • 子控制元件高度比例將會跟我們分配的layout_weight相反,原因在於在此之前子控制元件測量過一次,同時子控制元件的測量高度為父控制元件的高度,在計算剩餘空間的時候得出一個負值,加上自身的測量高度的時候反而更小
  • 父控制元件是wrap_content,子控制元件擁有weight:

    • 子控制元件的高度將會強行置為其wrap_content給的值並以wrap_content模式進行測量
  • 父控制元件是wrap_content,子控制元件沒有weight:

    • 子控制元件的高度跟其他的viewgroup一致

至此,LinearLayout針對measure的解析到此結束

感謝您的觀閱讀。

因為本人能力經驗有限,有些地方可能分析錯誤,如果您發現了,在下非常歡迎督促指正喲。

簡書:羽翼君

相關文章