從源碼層剖析View的Measure過程

View展示出來一共有三個過程秩命,大致為:測量每個View大邪暗痢(measure)-->把每個View放置到相應(yīng)的位置(layout)-->繪制每個View(draw
今天本文主要詳解的是測量每個View大小(measure)這個過程
主要先從如下流程圖開始,從上往下一路詳解其主要的方法原理

viewroot.jpeg

主要方法簡介

handleLaunchActivity

該方法會執(zhí)行很多方法,這個是入口,簡單來說會創(chuàng)建Activity對象

  • 調(diào)用其啟動生命周期吃型,attachonCreate僚楞、onStart勤晚、onResume枉层,以及添加到WindowManager中
  • 我們在Activity的attach函數(shù)中新建了PhoneWindow對象,在PhoneWindowsetContentView函數(shù)中會調(diào)用installDector來創(chuàng)建DecorView對象

handleResumeActivity

進入到繪制界面后赐写,會走到handleResumeActivity方法鸟蜡,通過performResumeActivity調(diào)用activity的onResume方法
如下是進入繪制界面的主要步驟

  • 第一步通過ViewManager wm = a.getWindowManager(),綁定WindowManager獲取其對象挺邀。
  • 第二步wm.addView(decor, l)將decorView傳入揉忘,而WindowManager的實現(xiàn)類是WindowManagerImpl,而它則是通過WindowManagerGlobal代理實現(xiàn)addView的

WindowManagerGlobal

WindowManagerImpl這種工作模式是典型的橋接模式端铛,將所有的操作委托給WindowManagerGlobal來實現(xiàn),如下是其實現(xiàn)的addView方法

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        ...
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        //ViewRootImpl開始繪制view
        root.setView(view, wparams, panelParentView);
        ...
    }

ViewRootImpl.setView

  • 先調(diào)用requestLayout()泣矛,完成第一次layout布局過程,以確保在收到任何系統(tǒng)事件后面重新布局禾蚕。
  • 接著會通過WindowSession最終來完成Window的添加過程您朽。在下面的代碼中mWindowSession類型是IWindowSession,它是一個Binder對象夕膀,真正的實現(xiàn)類是Session,也就是說這其實是一次IPC過程美侦,遠程調(diào)用了Session中的addToDisPlay方法产舞。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                //requestLayout最終會調(diào)用performTraversals方法來完成View的繪制
                requestLayout();
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
    }

performTraversals

ViewRootImpl調(diào)用performTraversals方法開始對view的測量布局繪制
關(guān)鍵方法有三個

  • performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)
  • performLayout(lp, desiredWindowWidth, desiredWindowHeight)
  • performDraw()

pic.jpeg

其中當窗口的最新尺寸與ViewRootImpl中的現(xiàn)有尺寸不同時
layoutRequested會設(shè)置會true,這個時候會要求進行預測量即measureHierarchy

measureHierarchy方法

該方法用于測量整個控件樹菠剩,通常針對懸浮彈框布局易猫,通過預測量來調(diào)整彈框的顯示大小,達到最好的視覺顯示效果(如下圖右邊)具壮。
一共有兩次協(xié)商測量機會(兩次協(xié)商測量僅在其width等于wrap_content下進行准颓,因為match_parent及設(shè)置大小均沒必要進行協(xié)商測量,因為一個是填充滿父view棺妓,另一個是已設(shè)定好的大小)攘已,第一次協(xié)商使用系統(tǒng)資源預設(shè)好的大小配置,若設(shè)置后仍不滿意則進入到第二次協(xié)商即 (baseSize+desiredWindowWidth)/2怜跑,若仍不滿意样勃,則放棄所有限制進入最終測量

dialog.jpeg

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;// 合成后的用于描述寬度的MeasureSpec
        int childHeightMeasureSpec;// 合成后的用于描述高度的MeasureSpec
        boolean windowSizeMayChange = false;// 表示測量結(jié)果是否可能導致窗口的尺寸發(fā)生變化
        boolean goodMeasure = false;// 表示了測量是否能滿足控件樹充分顯示內(nèi)容的要求
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            //預測量只在wrap_content中進行
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
           //第一次協(xié)商是通過使用它最期望的寬度限制進行測量。這一寬度限制定義為一個系統(tǒng)資源
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
               //第一次測量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
               //通過獲取view的測量結(jié)果來判斷性芬,如果滿足條件則認為是測量滿意否則進入第二次協(xié)商
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    // 第二次協(xié)商
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    // 第二次測量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }

        if (!goodMeasure) {
            // 最終測量峡眶。當控件樹對上述兩次協(xié)商的結(jié)果都不滿意時,measureHierarchy()放棄所有限制
            // 做最終測量植锉。這一次將不再檢查控件樹是否滿意了辫樱,因為即便其不滿意,measurehierarchy()也沒有更多的空間供其使用了
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
               //如果測量結(jié)果與viewrootimpl的高寬不一致則需要進行調(diào)整
                windowSizeMayChange = true;
            }
        }
        return windowSizeMayChange;
    }

onMeasure()

從上面所說的當我們進入到performTraversals后俊庇,會執(zhí)行performMeasure來進入view的測量即onMeasure
下圖是view測量的流程圖

measure_pic1.jpeg

注意:FrameLayout(id/content)往下走的view為自定義的layout布局

從上圖可知view的根view是DecorView狮暑,DecorView由TitleViewContentView構(gòu)成鸡挠,而ContentView就是我們啟動activity時setContentView進去的xml布局,也就是說當我們xml中的textview組件需要繪制的時候心例,必須先從DecorView開始宵凌,由外層view一路到最內(nèi)層view的繪制過程。

MeasureSpec

MeasureSpec類封裝了一個View的規(guī)格尺寸止后,包括View的寬和高的信息瞎惫,但是要注意,MeasureSpec并不是指View的測量寬高译株,這是不同的瓜喇,是根據(jù)MeasueSpec而測出測量寬高。
在系統(tǒng)中組件的大小模式有三種:

  • 精確模式(MeasureSpec.EXACTLY
    在這種模式下歉糜,尺寸的值是多少乘寒,那么這個組件的長或?qū)捑褪嵌嗌佟?/p>

  • 最大模式(MeasureSpec.AT_MOST
    特指當前組件的寬或高大小只能在父組件給出的最大空間里定義,不得超出

  • 未指定模式(MeasureSpec.UNSPECIFIED
    父控件對子控件不加任何束縛匪补,子元素可以得到任意想要的大小伞辛,這種MeasureSpec一般是由父控件自身的特性決定的。比如ScrollView夯缺,它的子View可以隨意設(shè)置大小蚤氏,無論多高,都能滾動顯示踊兜,這個時候竿滨,size一般就沒什么意義。

一個int型整數(shù)表示兩個東西(大小模式和大小的值)捏境,一個int類型我們知道有32位于游。而模式有三種,要表示三種狀態(tài)垫言,至少得2位二進制位贰剥。于是系統(tǒng)采用了最高的2位表示模式。如圖:

pic3.png

最高兩位是00的時候表示"未指定模式"筷频。即MeasureSpec.UNSPECIFIED
最高兩位是01的時候表示"'精確模式"鸠澈。即MeasureSpec.EXACTLY
最高兩位是11的時候表示"最大模式"。即MeasureSpec.AT_MOST
當然我們并不需要刻意的去記住截驮,因為MeasureSpec類已提供getSize笑陈、getMode等方法給我們,但我們應(yīng)該大概了解即可葵袭。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
      //判斷當前布局的寬高是否是match_parent模式涵妥,如果是則置measureMatchParent為false.
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
               // 該方法主要把margin 及  padding 也作為子視圖大小的一部分并返回MeasureSpec給到子view進行測繪
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    //子view為寬或高為LayoutParams.MATCH_PARENT模式則加入mMatchParentChildren
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
               //根據(jù)當前布局的寬高來測量模式為LayoutParams.MATCH_PARENT的子view
               //子view可以覆蓋的范圍是FrameLayout的測量寬度,減去padding和margin后剩下的空間坡锡。
                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                 //對于這部分的子View需要重新進行measure過程
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

measureChildWithMargins方法

該方法主要把margin 及 padding 也作為子視圖大小的一部分并返回MeasureSpec給到子view進行測繪

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

getChildMeasureSpec方法

根據(jù)獲取的specMode蓬网、specSize來重新計算resultSize窒所、resultMode

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        int size = Math.max(0, specSize - padding);
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        case MeasureSpec.EXACTLY:
           //在這種模式下,尺寸的值是多少帆锋,那么這個組件的長或?qū)捑褪嵌嗌?            if (childDimension >= 0) {
               //如果子view設(shè)置具體值吵取, 則取子view大小,mode設(shè)置為   MeasureSpec.EXACTLY(即match_parent) 
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
               //子view的大小是match_parent,則填充父view空間锯厢,size為父的空間大小減去子view的margin邊距(如果是計算子view的高度空間皮官,則減去頂部和底部margin,反之則左右margin)
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
               //與match_parent一致实辑,但是mode則為AT_MOST捺氢,說明希望子View的大小不要超過父View的大小
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST:
           //這個也就是父組件,能夠給出的最大的空間剪撬,當前組件的長或?qū)捴荒茉谄涓附M件給出的范圍內(nèi)
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
             //子View的大小為父View的size摄乒,但是mode則為AT_MOST,說明希望子View的大小不要超過父View的大小
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
             //與上面一致
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.UNSPECIFIED:
         //當前組件残黑,可以隨便用空間馍佑,不受限制。
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //大小自己設(shè)置
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

measureVertical方法

setContentView中的布局文件是一個以LinearLayout為根布局 其子view是TextView為例
從上面DecorView的onMeasure到其繼承的FrameLayout onMeasure一路遍歷其子view測量梨水,最后進入setContentView
LinearLayout的onMeasure一共有兩個方法拭荤,根據(jù)其布局屬性來執(zhí)行,分別為

  • measureVertical(widthMeasureSpec, heightMeasureSpec)
  • measureHorizontal(widthMeasureSpec, heightMeasureSpec)

以measureVertical為例

先獲取子view數(shù)量然后進行遍歷冰木,如果heightMode為Match_Parent且高度為0權(quán)重大于0 則統(tǒng)計當前Linearlayout總大小穷劈,并設(shè)置skippedMeasure為true笼恰,進入到權(quán)重測量(即根據(jù)weight來進行二次measure)踊沸,否則則進入measureChildBeforeLayout(關(guān)鍵方法)該方法點進去其實就是measureChildWithMargins用于把 margin 及 padding 也作為子視圖大小的一部分返回,最后進入child.measure計算測量社证,當子view測量完成后逼龟,再由父view設(shè)置setMeasuredDimension(widthSizeAndState,heightSizeAndState)決定當前容器大小

widthSizeAndState:

主要由兩種情況得出maxWidth

  • maxWidth由所有子view的寬度+margin疊加
  • 當不填充滿父view及父的widthMode != MeasureSpec.EXACTLY都滿足下
    則取alternativeMaxWidth,alternativeMaxWidth及getSuggestedMinimumWidth通過max得出的值maxWidth追葡,再由resolveSizeAndState(maxWidth, widthMeasureSpec, childState)返回一個合適的大小即widthSizeAndState
    其中weightedMaxWidth:權(quán)重 的最大寬
    其中alternativeMaxWidth:改變本地最大寬度
    關(guān)于alternativeMaxWidth的獲取如下:
  if (lp.weight > 0) {
      //如果子view設(shè)置了weight
      weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
  } else {
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
  }

  if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
       ····
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
  }else{
       ····
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);
  }

heightSizeAndState:

主要由mTotalLength及getSuggestedMinimumHeight()通過max得出的值heightSize腺律,再由resolveSizeAndState(heightSize, heightMeasureSpec, 0)返回一個合適的大小即heightSizeAndState
其中mTotalLength:包含所有子view大小、mDividerHeight

 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;

        final int count = getVirtualChildCount();
        
        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;

        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

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

            if (hasDividerBeforeChildAt(i)) {//如果設(shè)置了divider宜肉,則加上divider高度
                mTotalLength += mDividerHeight;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;
            //如果heightMode為Match_Parent且高度為0權(quán)重大于0 則統(tǒng)計當前Linearlayout總大小匀钧,并設(shè)置skippedMeasure為true,進入到權(quán)重測量
            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                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) {
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                //該方法主要是將LinearLayout大小傳入進去讓子view根據(jù)傳入的mTotalLength計算padding 因為是列表形式延伸子view谬返,所以widthUsed傳0 只需傳heightUsed之斯,若是縱向延伸子view,則傳widthUsed遣铝,而heightUsed為0佑刷,進入measureChildWithMargins方法(如上有詳解)
                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;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    //該屬性為true的時候, 所有帶權(quán)重的子元素都會具有最大子元素的最小尺寸莉擒。
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

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

            boolean matchWidthLocally = false;
            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);
            // 合并子元素的測量狀態(tài)
            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);
        }

        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;

        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) {
                    // 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);

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


            //我們沒有限制,所以把所有的加權(quán)視圖和最大的子view一樣高瘫絮。
            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的大小
最終返回一個合適的大小

 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) {
                //當specMode為AT_MOST麦萤,并且父控件指定的尺寸specSize小于View自己想要的尺寸時鹿鳖,
                //我們就會用掩碼MEASURED_STATE_TOO_SMALL向量算結(jié)果加入尺寸太小的標記
                //這樣其父ViewGroup就可以通過該標記其給子View的尺寸太小了,
                //然后可能分配更大一點的尺寸給子View
                    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);//使用了位運行 返回一個帶大小和狀態(tài)的值
    }

getDefaultSize方法

    /**
     * 作用是返回一個默認的值频鉴,如果MeasureSpec沒有強制限制的話則使.     用提供的大小.否則在允許范圍內(nèi)可任意指定大小
     * 第一個參數(shù)size為提供的默認大小栓辜,第二個參數(shù)為測量的大小
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (specMode) {
            // Mode = UNSPECIFIED時使用提供的默認大小
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            // Mode = AT_MOST,EXACTLY時使用測量的大小
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

getSuggestedMinimumHeight方法

獲取最小的推薦高度

    protected int getSuggestedMinimumHeight() {
     //如果沒有給View設(shè)置背景,那么就返回View本身的最小寬度mMinWidth
    //如果給View設(shè)置了背景垛孔,那么就取View本身最小寬度mMinWidth和背景的最小寬度的最大值
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末藕甩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子周荐,更是在濱河造成了極大的恐慌狭莱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件概作,死亡現(xiàn)場離奇詭異腋妙,居然都是意外死亡,警方通過查閱死者的電腦和手機讯榕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門骤素,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愚屁,你說我怎么就攤上這事济竹。” “怎么了霎槐?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵送浊,是天一觀的道長。 經(jīng)常有香客問我丘跌,道長袭景,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任闭树,我火速辦了婚禮耸棒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘报辱。我一直安慰自己与殃,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奈籽,像睡著了一般饥侵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衣屏,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天躏升,我揣著相機與錄音,去河邊找鬼狼忱。 笑死膨疏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的钻弄。 我是一名探鬼主播佃却,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窘俺!你這毒婦竟也來了饲帅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘤泪,失蹤者是張志新(化名)和其女友劉穎灶泵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體对途,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡赦邻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了实檀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惶洲。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膳犹,靈堂內(nèi)的尸體忽然破棺而出恬吕,到底是詐尸還是另有隱情,我是刑警寧澤镣奋,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布币呵,位于F島的核電站怀愧,受9級特大地震影響侨颈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芯义,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一哈垢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扛拨,春花似錦耘分、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽央渣。三九已至,卻和暖如春渴频,著一層夾襖步出監(jiān)牢的瞬間芽丹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工卜朗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拔第,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓场钉,卻偏偏與公主長得像蚊俺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逛万,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

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