Android 8.0 LinearLayout 源碼解析

一. 開(kāi)篇

在最開(kāi)始接觸 Android 開(kāi)發(fā)的時(shí)候便學(xué)習(xí)了 LinearLayout 布局控件沟优,它可以在垂直/水平方向依次展開(kāi) childView,再配合 weight 屬性使用的話,可以高效命斧、方便地完成許多 UI 界面的開(kāi)發(fā)闹击。其實(shí) LinearLayout 還有一些其他用法返弹,可能用的不多脂崔,可以參考這篇文章 你對(duì)LinearLayout到底有多少了解滤淳?(一)-屬性篇
以前就知道,在 LinearLayout 布局時(shí)砌左,如果不使用 weight 屬性娇钱,LinearLayout 中每個(gè) childView 只會(huì)測(cè)量一次伤柄,如果使用 weight 屬性,每個(gè) childView 會(huì)測(cè)量?jī)纱挝穆В治隽嗽创a之后,發(fā)現(xiàn)這種說(shuō)法也不是十分準(zhǔn)確秤朗,childView 會(huì)不會(huì)被測(cè)量?jī)纱蚊翰洌艘蕾?lài)是否設(shè)置 android:layout_weight 屬性,還需要依賴(lài)其他屬性的

二. 源碼解析

在 LinearLayout 中有垂直/水平兩個(gè)方向的布局取视,任一方向的布局思想都是相同的硝皂,所以我們只需要具體分析其中一個(gè)方向即可,另一個(gè)方向可以類(lèi)比作谭,在這里我們分析垂直方向的思想

在 View 和 ViewGroup 中的布局有三大流程稽物,分別是 onMeasureonLayoutonDraw折欠,在 LinearLayout 中 onLayoutonDraw 兩個(gè)流程基本都是模板化的寫(xiě)法贝或,而且 LinearLayout 布局簡(jiǎn)單,無(wú)論是垂直方向還是水平方向都是依次排列每個(gè) childView 的锐秦,分析起來(lái)并不復(fù)雜咪奖,大家可以自行分析。
但是 onMeasure 流程就比較復(fù)雜酱床,分為兩種情況:

  • 不使用 layout_weight 屬性羊赵,每個(gè) childView 按照自身的情況計(jì)算本身的大小即可
  • 使用 layout_weight 屬性,需要根據(jù) LinearLayout 的剩余空間和 layout_weight 的比例扇谣,計(jì)算每個(gè) childView 的大小

Ok, let's fuck the source code

2.1 非 weight 的情況

2.1.1 布局文件 & 效果

首先昧捷,我們來(lái)看一個(gè)簡(jiǎn)單的布局,xml 文件如下所示

<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

    <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:text="TextView1"
            android:gravity="center"
            android:textSize="24sp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_green_light"/>

    <TextView
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:text="TextView2"
            android:gravity="center"
            android:textSize="24sp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
</LinearLayout>

其中罐寨,兩個(gè) TextView 都沒(méi)有設(shè)置 layout_weight 屬性靡挥,第一個(gè) TextView 的 layout_height 屬性是 200dp,第二個(gè) TextView 的 layout_height300dp衩茸,我想這樣簡(jiǎn)單的布局只要稍微懂 Android 開(kāi)發(fā)的人都知道是什么樣的芹血,它的效果如下圖所示,但是說(shuō)到它的源碼執(zhí)行楞慈,不知道又有多少人可以分析得清楚呢幔烛?
我們就以這個(gè)簡(jiǎn)單的示例,分析 LinearLayout 中的 onMeasure 流程

image1.png

2.1.2 onMeasure() 執(zhí)行流程

在測(cè)量階段囊蓝,也就是 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 階段饿悬,主要測(cè)量 LinearLayout 的整體大小,以及其中每個(gè) childView 的大小

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

onMeasure(int widthMeasureSpec, int heightMeasureSpec) 源碼如上所示聚霜,通過(guò) mOrientation 分別處理垂直和水平兩個(gè)方向的測(cè)量狡恬,其中的 mOrientation 變量則是我們?cè)?xml 布局文件中通過(guò) android:orientation="vertical" 或者直接通過(guò) setOrientation(@OrientationMode int orientation) 方法設(shè)置的 LinearLayout 文件方向變量

我們僅分析垂直方向的測(cè)量方法珠叔,也就是 measureVertical(int widthMeasureSpec, int heightMeasureSpec)(水平方向的測(cè)量方法 measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) 是類(lèi)似的原理,有興趣的朋友可以自己分析)弟劲。measureVertical 方法還是很長(zhǎng)的祷安,不過(guò)整個(gè)過(guò)程可以分為三個(gè)階段,為了分析的比較清楚兔乞,我們也分階段循序漸進(jìn)的分析

1. 聲明變量

measureVertical 開(kāi)始之前汇鞭,需要初始化一些類(lèi)變量 & 聲明一些重要的局部變量,重要的變量我都有注釋
其中庸追,最重要的就是有三類(lèi):

  1. mTotalLength:所有 childView 的高度和 + 本身的 padding霍骄,注意:它和 LinearLayout 本身的高度是不同的
  2. 三個(gè)寬度相關(guān)的變量
    • maxWidth:所有 childView 中寬度的最大值
    • alternativeMaxWidth:所有 layout_weight <= 0 的 childView 中寬度的最大值
    • weightedMaxWidth:所有 layout_weight >0 的 childView 中寬度的最大值
  3. totalWeight:所有 childView 的 weight 之和
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // 一些重要的變量
        mTotalLength = 0;            // 所有 childView 的高度和 + 本身的 padding,注意:它和 LinearLayout 本身的高度是不同的
        int maxWidth = 0;            // 所有 childView 中寬度的最大值
        int childState = 0;
        int alternativeMaxWidth = 0;    // 所有 layout_weight <= 0 的 childView 中寬度的最大值
        int weightedMaxWidth = 0;       // 所有 layout_weight >0 的 childView 中寬度的最大值
        boolean allFillParent = true;
        float totalWeight = 0;          // 所有 childView 的 weight 之和

        final int count = getChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;    

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

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;

        int nonSkippedChildCount = 0;
    }
2. 測(cè)量第一階段

在測(cè)量第一階段會(huì)計(jì)算那些沒(méi)有設(shè)置 weight 的 childView 的高度淡溯、計(jì)算 mTotleLength读整,并且計(jì)算三個(gè)寬度相關(guān)的變量的值

在看下面代碼之前,請(qǐng)想想我們上面提到的 xml 布局是什么樣的咱娶,我們就按照上面的 xml 布局文件的樣式進(jìn)行分析米间。其中一些重要的英文注釋?zhuān)也](méi)有去掉,大家可以仔細(xì)思考這些英文注釋?zhuān)兄诶斫?/p>

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // 接上面的代碼

        // See how tall everyone is. Also remember max width.
        // 第一次循環(huán)遍歷豺总,正如上面的英文注釋所說(shuō)明的意圖所在
        for (int i = 0; i < count; ++i) {
            // 依次得到每一個(gè) childView
            // { 在此 xml 布局中车伞,會(huì)依次得到 TextView1 & TextView2 }
            final View child = getChildAt(index);
            // { 在此 xml 布局中的 TextView1 & TextView2 都不滿足下面的兩個(gè)條件 }
            if (child == null) {
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               continue;
            }

            // 沒(méi)有跳過(guò)的 childView 個(gè)數(shù)
            // { 在此 xml 布局中,nonSkippedChildCount 最終為 2 }
            nonSkippedChildCount++;
            // 在總高度中加上每一個(gè) Divider 的 height
            // { 在此 xml 布局中喻喳,沒(méi)有設(shè)置 `android:divider` 相關(guān)屬性另玖,跳過(guò)此 if 判斷 }
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            
            // 計(jì)算總權(quán)重 totalWeight
            // { 在此 xml 布局中,兩個(gè) childView 都沒(méi)有設(shè)置 `android:layout_weight` 屬性表伦,
            // 所以 totalWeight 一直為 0} 
            totalWeight += lp.weight;

            // { 在此 xml 布局中谦去,useExcessSpace 為 false }
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // 符合這種條件的 childView 先跳過(guò)測(cè)量,在這里不做測(cè)量計(jì)算
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    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).
                // 這是非常重要的一個(gè)方法蹦哼,將會(huì)決定每個(gè) childView 的大小
                // 如果此 childView 及在此 childView 之前的 childView 中使用了 weight 屬性鳄哭,
                // 我們?cè)试S此 childView 使用所有的空間(后續(xù)如果需要,再做調(diào)整)
                // { 在此 xml 布局中纲熏,在調(diào)用時(shí) usedHeight 都是 mTotalLength }
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
                
                // 得到測(cè)量之后的 childView 的 childHeight
                // { 在此 xml 中妆丘,TextView1 的 childHeight 是 200 dp;
                // TextView2 的 childHeight 是 300 dp }
                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                // 將此 childView 的 childHeight 加入到 mTotalLength 中
                // 并加上 childView 的 topMargin 和 bottomMargin 
                // getNextLocationOffset 方法返回 0局劲,方便以后擴(kuò)展使用
                // { 在此 xml 中勺拣,mTotalLength 最后的結(jié)果將是 500 dp }
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

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

            // 下面兩個(gè) if 判斷都和 `android:baselineAlignedChildIndex` 屬性有關(guān)
            // 在這里不做分析
            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;
            // { 在此 xml 中,`android: layout_width` 是 `match_parent`鱼填,
            // 所以 widthMode 是 `MeasureSpec.EXACTLY`药有,不會(huì)進(jìn)入此 if 判斷  }
            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;
            }

            // 計(jì)算三個(gè)和寬度相關(guān)的變量值
            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) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                // { 在此 xml 布局中,最終都會(huì)走到此 代碼塊 中,matchWidthLocally == false }
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }
        
        // 如果存在沒(méi)有跳過(guò)的 childView 并且需要繪制 end divider 則需要加上 end 位置的 divider 的高度
        // { 在此 xml 中愤惰,沒(méi)有設(shè)置 android:showDividers="end"苇经,跳過(guò)此 if 代碼塊 }
        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        ......
    }

在上面的代碼中,我都做了詳細(xì)的注釋?zhuān)渲杏幸粋€(gè)方法調(diào)用非常重要宦言,即 measureChildBeforeLayout() 方法扇单,在此方法中將會(huì)計(jì)算每個(gè) childView 的大小

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

measureChildBeforeLayout() 方法中,又調(diào)用 ViewGroupmeasureChildWithMargins() 方法計(jì)算每個(gè) childView 的大小蜡励,在測(cè)量垂直方向的 childView 時(shí)令花,有一個(gè)非常重要的參數(shù)需要注意,即:heightUsed凉倚,根據(jù)英文注釋?zhuān)?code>heightUsed 是指在垂直方向,已經(jīng)被 parentView 或者 parentView 的其他 childView 使用了的空間

    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    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);
    }

那么在上面示例的 xml 布局測(cè)量過(guò)程中 heightUsed 的值是多少呢嫂沉?

  • 在測(cè)量 TextView1 時(shí) heightUsed0稽寒,因?yàn)槭堑谝粋€(gè)測(cè)量的 childView,在垂直方向的空間還沒(méi)有被使用
  • 在測(cè)量 TextView2 時(shí) heightUsed200 dp 趟章,因?yàn)?TextView1 已經(jīng)使用了 200 dp
3. 測(cè)量第二階段

如果進(jìn)入這個(gè) if 條件杏糙,會(huì)進(jìn)行第二次的 for 循環(huán)遍歷 childView,重新計(jì)算 mTotalLength蚓土。不過(guò)這個(gè) if 條件需要 useLargestChildtrue宏侍,useLargestChild 可以通過(guò) xml 屬性 android:measureWithLargestChild 設(shè)置的,不在本文的討論范圍內(nèi)

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // 接上面的代碼

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

        ......

    }
4. 測(cè)量第三階段

經(jīng)過(guò)上面的分析之后蜀漆,終于來(lái)到了最后的一個(gè)階段谅河,在這里會(huì)針對(duì)設(shè)置了 android:layout_weight 屬性的布局,重新計(jì)算 mTotalLength

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // 接上面的代碼

        // 加上 LinearLayout 自己的 paddingTop 和 paddingBottom
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // 通過(guò) getSuggestedMinimumHeight() 得到建議最小高度确丢,并和計(jì)算得到的
        // mTotalLength 比較取最大值
        // { 在此 xml 布局中绷耍,并沒(méi)有設(shè)置 minHeight 和 background,所以還是取 mTotalHeight 值}
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // 通過(guò) heightMeasureSpec鲜侥,調(diào)整 heightSize 的大小褂始,具體的過(guò)程需要
        // 看一下 resolveSizeAndState() 方法的實(shí)現(xiàn)
        // {在此 xml 布局中,heightSize 經(jīng)過(guò)調(diào)整之后就是 LinearLayout 的大小了描函,
        // 也就是整個(gè)屏幕的高度了 }
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        
        // 重新計(jì)算有 weight 屬性的 childView 大小崎苗,
        // 如果還有可用的空間,則擴(kuò)展 childView舀寓,計(jì)算其大小
        // 如果 childView 超出了 LinearLayout 的邊界胆数,則收縮 childView
        // { 在此 xml 布局中,不會(huì)進(jìn)入此 if 語(yǔ)句基公,直接走 else 代碼塊了幅慌,
        // 因?yàn)椴环蠗l件,skippedMeasure == false轰豆,totalWeight == 0 }
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            ......
        } else {
            // 重新計(jì)算 alternativeMaxWidth
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            // useLargestChild 為 false胰伍,不在本文討論范圍內(nèi)
            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;

        // 調(diào)整 width 大小
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        // 調(diào)用 setMeasuredDimension() 設(shè)置 LinearLayout 的大小
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

經(jīng)過(guò)上面四步的源碼分析齿诞,非 weight 情況下的垂直布局 onMeasure() 代碼就分析的差不多了。在不使用 android:layout_weight 屬性時(shí)骂租,LinearLayout 的 onMeasure 流程還是比較簡(jiǎn)單的祷杈,只會(huì)進(jìn)入第一個(gè) for 循環(huán)遍歷所有的 childView 并計(jì)算他們的大小,如果使用了 android:layout_weight 屬性則會(huì)進(jìn)入第三個(gè) for 循環(huán)并再次遍歷所有的 childView渗饮,再次重新執(zhí)行 childView 的 measure() 方法

2.2 使用 weight 的情況

2.2.1 布局文件 & 效果

上面分析了不使用 android:layout_weight 的情況但汞,現(xiàn)在來(lái)分析下使用 android:layout_weight 的情況,還是通過(guò)一個(gè)例子入手互站,xml 布局如下所示

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:showDividers="end"
        android:orientation="vertical">

    <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:text="TextView1"
            android:gravity="center"
            android:textSize="24sp"
            android:layout_weight="2"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_green_light"/>

    <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:text="TextView2"
            android:gravity="center"
            android:textSize="24sp"
            android:layout_weight="3"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_blue_light"/>
</LinearLayout>

這也是一個(gè)我們最常見(jiàn)的 LinearLayout 的用法私蕾,TextView1 的 android:layout_height="0dp"android:layout_weight="2",TextView2 的 android:layout_height="0dp"android:layout_weight="3"胡桃,如下圖所示踩叭,TextView1 和 TextView2 在垂直方向上,會(huì)以 2 : 3 的比例分配整個(gè)屏幕的高度

image2.png
1. 聲明變量

還是和上面同樣的思路分析 onMeasure 的流程翠胰,由于聲明的變量沒(méi)有區(qū)別容贝,我們直接跳過(guò)聲明變量,從測(cè)量第一階段開(kāi)始

2. 測(cè)量第一階段

如果是上面這種布局的 xml 代碼之景,在第一次 for 循環(huán)遍歷 childView 時(shí)斤富,會(huì)標(biāo)記 skippedMeasure = true,并計(jì)算所有的 totalWeight锻狗,在第二次 for 循環(huán)遍歷時(shí)满力,重新計(jì)算每個(gè)有 weight 屬性的 childView 的大小

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // 接上面的變量聲明

        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            // 遍歷每個(gè) childView,如果滿足下面兩個(gè) if 條件之一屋谭,則跳過(guò)
            // { 在此 xml 布局中脚囊,兩個(gè) TextView 都不會(huì)跳過(guò) }
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            // 沒(méi)有跳過(guò)的 childView 個(gè)數(shù)
            // { 在此 xml 布局中,nonSkippedChildCount 最終為 2 }
            nonSkippedChildCount++;
            // 在總高度中加上每一個(gè) Divider 的 height
            // { 在此 xml 布局中桐磁,沒(méi)有設(shè)置 `android:divider` 相關(guān)屬性悔耘,跳過(guò)此 if 判斷 }
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        
            // 不同的地方開(kāi)始了
            // 計(jì)算總權(quán)重 totalWeight
            // { 在此 xml 布局中,TextView1 的 weight == 2我擂,TextView2 的 weight == 3 
            // 所以最終 totalWeight == 5 }  
            totalWeight += lp.weight;

            // {在此 xml 布局中衬以,遍歷 TextView1 和 TextView2 時(shí),useExcessSpace 均為 true校摩,
            // 并且滿足下面的 if 條件判斷看峻,skippedMeasure 賦值為 true }
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // 符合這種條件的 childView 先跳過(guò)這個(gè)循環(huán)測(cè)量,將 skippedMeasure 賦值為 true衙吩,
                // 在后面第三個(gè) for 循環(huán)重新計(jì)算此 childView 大小
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                ......
            }

            // 下面兩個(gè) if 判斷都和 `android:baselineAlignedChildIndex` 屬性有關(guān)
            // 在這里不做分析
            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;
            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;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            // { 在此 xml 布局中互妓,直到這里都還沒(méi)有測(cè)量 childView,所以
            // child.getMeasuredWidth() == 0}
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            // { 在此 xml 布局中,weightedMaxWidth 一直為 0 }
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        // { 和上面的作用一樣冯勉,在計(jì)算高度時(shí)澈蚌,計(jì)算 endDivider 的高度 }
        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        ......

    }
3. 測(cè)量第二階段

第二階段的測(cè)量和上面提到的一樣,都是和 android:measureWithLargestChild 屬性設(shè)置相關(guān)的灼狰,不在本文的討論范圍之內(nèi)

4. 測(cè)量第三階段

在第三階段的測(cè)量之中宛瞄,針對(duì)設(shè)置了 android:layout_weight 屬性的布局,重新計(jì)算 mTotalLength

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // 接上面的代碼

        // 加上 LinearLayout 自己的 paddingTop 和 paddingBottom
        mTotalLength += mPaddingTop + mPaddingBottom;
        
        // { 在此 xml 布局中交胚,經(jīng)過(guò)上面的兩次 for 循環(huán)之后 mTotalLength == 0 }
        int heightSize = mTotalLength;

        // 通過(guò) getSuggestedMinimumHeight() 得到建議最小高度份汗,并和計(jì)算得到的
        // mTotalLength 比較取最大值
        // { 在此 xml 布局中,并沒(méi)有設(shè)置 minHeight 和 background蝴簇,所以還是取 mTotalHeight 值杯活,
        // 所以 heightSize == 0 }
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // 通過(guò) heightMeasureSpec,調(diào)整 heightSize 的大小熬词,具體的過(guò)程需要
        // 看一下 resolveSizeAndState() 方法的實(shí)現(xiàn)
        // { 在此 xml 布局中轩猩,heightSize 經(jīng)過(guò)調(diào)整之后就是 LinearLayout 的大小了,
        // 也就是整個(gè)屏幕的高度了 }
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        
        // 重新計(jì)算有 weight 屬性的 childView 大小荡澎,
        // 如果還有可用的空間,則擴(kuò)展 childView晤锹,計(jì)算其大小
        // 如果 childView 超出了 LinearLayout 的邊界摩幔,則收縮 childView
        // { 在此 xml 布局中,經(jīng)過(guò)上面的第一次  for 循環(huán)之后 skippedMeasure == true鞭铆,
        // remainingExcess == 整個(gè)屏幕的高度或衡,totalWeight == 5 }
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            // 根據(jù) mWeightSum 計(jì)算得到 remainingWeightSum,mWeightSum 是通過(guò) 
            // `android:weightSum` 屬性設(shè)置的车遂,totalWeight 是通過(guò)第一次 for 循環(huán)計(jì)算得到的
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
            // 將 mTotalLength 復(fù)位為 0
            mTotalLength = 0;
            // 開(kāi)始真正的第二次 for 循環(huán)遍歷每一個(gè) childView封断,重新測(cè)量每一個(gè) childView
            for (int i = 0; i < count; ++i) {
                // 得到每一個(gè) childView,如果符合下面的 if 判斷則跳過(guò)
                final View child = getVirtualChildAt(i);
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight;
                // 如果該 childView 設(shè)置了 `weight` 值舶担,則進(jìn)入 if 語(yǔ)句塊
                // { 在此 xml 布局中坡疼,TextView1 的 layout_weight == 2,
                // TextView2 的 layout_weight == 3衣陶,都會(huì)進(jìn)入下面的 if 條件判斷 }
                if (childWeight > 0) {
                    // 這是設(shè)置了 weight 的情況下柄瑰,最重要的一行代碼
                    // remainingExcess 剩余高度 * ( childView 的 weight / remainingWeightSum)
                    // share 便是此 childView 通過(guò)這個(gè)公式計(jì)算得到的高度,                               
                    // 并重新計(jì)算剩余高度 remainingExcess 和剩余權(quán)重總和 remainingWeightSum
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    // 通過(guò)下面的 if 條件重新計(jì)算剪况,childHeight 是最終 childView 的真正高度            
                    // { 在此 xml 布局中教沾,TextView1 和 TextView2 都會(huì)走到第二個(gè)條件中去,
                    // childHeight == share }
                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    // 計(jì)算 childHeightMeasureSpec & childWidthMeasureSpec译断,并調(diào)用 child.measure() 方法
                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                // 重新計(jì)算 maxWidth & alternativeMaxWidth
                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;

                // 考慮 childView.topMargin & childView.bottomMargin授翻,重新計(jì)算 mTotalLength
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            // 完成 for 循環(huán)之后,加入 LinearLayout 本身的 mPaddingTop & mPaddingBottom
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            ......
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        maxWidth += mPaddingLeft + mPaddingRight;

        // 調(diào)整 width 大小
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        // 調(diào)用 setMeasuredDimension() 設(shè)置 LinearLayout 的大小
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

三. 小結(jié)

經(jīng)過(guò)上面對(duì)兩種情況的分析,其實(shí) onMeasure 流程已經(jīng)比較清晰了堪唐,簡(jiǎn)單總結(jié)一下巡语,我們可以學(xué)習(xí)到以下幾點(diǎn)

  1. LinearLayout 的設(shè)計(jì)者有意的對(duì)設(shè)置了 weight 和不設(shè)置 weight 的情況分別處理,通過(guò) skippedMeasure 變量 & childView.height & childView.weight 區(qū)分羔杨,從上面我舉的兩個(gè)例子中就可以明顯的感受到捌臊,兩種測(cè)量流程分的還是比較詳細(xì)清楚的
  2. 在 LinearLayout 中總共有 3 個(gè) for 循環(huán),分別處理不同的流程
    • 第一個(gè) for 循環(huán)兜材,只會(huì)在不使用 weight 屬性時(shí)進(jìn)入理澎,并有可能會(huì)測(cè)量每個(gè) childView 的大小
    • 第二個(gè) for 循環(huán),在使用 android:measureWithLargestChild 時(shí)才會(huì)進(jìn)入曙寡,并且即使進(jìn)入也不會(huì)調(diào)用 childView 的測(cè)量方法糠爬,只會(huì)更新 mTotalLength 變量
    • 第三個(gè) for 循環(huán),只會(huì)在使用 weight 屬性時(shí)進(jìn)入举庶,并測(cè)量每個(gè) childView 的大小
  3. 通過(guò)上面的分析执隧,即使是使用了 android:layout_weight 屬性,childView 也不會(huì)一定就測(cè)量?jī)纱位Ы模€需要看 android:layout_height 和 LinearLayout 的 heightMode 屬性
  4. 通過(guò)上面的源碼分析镀琉,熟悉鞏固了 measureChildWithMargins(...)resolveSizeAndState(...)蕊唐、getChildMeasureSpec(...)屋摔、setMeasuredDimension(...)等 Api,這些 Api 對(duì)于我們自定義控件還是非常重要的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末替梨,一起剝皮案震驚了整個(gè)濱河市钓试,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌副瀑,老刑警劉巖弓熏,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異糠睡,居然都是意外死亡挽鞠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)铜幽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滞谢,“玉大人,你說(shuō)我怎么就攤上這事除抛∈ㄑ睿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵到忽,是天一觀的道長(zhǎng)橄教。 經(jīng)常有香客問(wèn)我清寇,道長(zhǎng),這世上最難降的妖魔是什么护蝶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任华烟,我火速辦了婚禮,結(jié)果婚禮上持灰,老公的妹妹穿的比我還像新娘盔夜。我一直安慰自己,他們只是感情好堤魁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布喂链。 她就那樣靜靜地躺著,像睡著了一般妥泉。 火紅的嫁衣襯著肌膚如雪椭微。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天盲链,我揣著相機(jī)與錄音蝇率,去河邊找鬼。 笑死刽沾,一個(gè)胖子當(dāng)著我的面吹牛本慕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侧漓,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼间狂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了火架?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤忙菠,失蹤者是張志新(化名)和其女友劉穎何鸡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體牛欢,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骡男,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了傍睹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隔盛。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拾稳,靈堂內(nèi)的尸體忽然破棺而出吮炕,到底是詐尸還是另有隱情,我是刑警寧澤访得,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布龙亲,位于F島的核電站陕凹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鳄炉。R本人自食惡果不足惜杜耙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拂盯。 院中可真熱鬧佑女,春花似錦、人聲如沸谈竿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)榕订。三九已至店茶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劫恒,已是汗流浹背贩幻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留两嘴,地道東北人丛楚。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像憔辫,于是被迫代替她去往敵國(guó)和親趣些。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容