Android View體系(七)從源碼解析View的measure流程

相關(guān)文章
Android View體系(一)視圖坐標(biāo)系
Android View體系(二)實(shí)現(xiàn)View滑動(dòng)的六種方法
Android View體系(三)屬性動(dòng)畫
Android View體系(四)從源碼解析Scroller
Android View體系(五)從源碼解析View的事件分發(fā)機(jī)制
Android View體系(六)從源碼解析Activity的構(gòu)成

前言

在上一篇我們了解了Activity的構(gòu)成后,開(kāi)始了解一下View的工作流程,就是measure、layout和draw。measure用來(lái)測(cè)量View的寬高,layout用來(lái)確定View的位置,draw則用來(lái)繪制View。這一講我們來(lái)看看measure流程,measure流程分為View的measure流程和ViewGroup的measure流程,只不過(guò)ViewGroup的measure流程除了要完成自己的測(cè)量還要遍歷去調(diào)用子元素的measure()方法。

1.View的measure流程

先來(lái)看看onMeasure()方法(View.java):

   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

看看setMeasuredDimension()方法:

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

很顯然是用來(lái)設(shè)置View的寬高的,先來(lái)看看getDefaultSize()方法處理了什么:

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

specMode是View的測(cè)量模式,而specSize是View的測(cè)量大小,看到這里我們有必要先看看MeasureSpec類:

 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

...省略

 public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
  public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
...省略        
}

MeasureSpec類幫助我們來(lái)測(cè)量View,它是一個(gè)32位的int值,高兩位為specMode (測(cè)量的模式),低30位為specSize (測(cè)量的大小),測(cè)量模式分為三種:

  • UNSPECIFIED:未指定模式,View想多大就多大,父容器不做限制,一般用于系統(tǒng)內(nèi)部的測(cè)量。
  • AT_MOST:最大模式,對(duì)應(yīng)于wrap_comtent屬性,只要尺寸不超過(guò)父控件允許的最大尺寸就行。
  • EXACTLY:精確模式,對(duì)應(yīng)于match_parent屬性和具體的數(shù)值,父容器測(cè)量出View所需要的大小,也就是specSize的值。

讓我們回頭看看getDefaultSize()方法,很顯然在AT_MOST和EXACTLY模式下,都返回specSize這個(gè)值,也就是View測(cè)量后的大小,而在UNSPECIFIED模式返回的是getDefaultSize()方法的第一次個(gè)參數(shù)的值,這第一個(gè)參數(shù)從onMeasure()方法來(lái)看是getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()得到的,那我們來(lái)看看getSuggestedMinimumWidth()方法做了什么,我們只需要弄懂getSuggestedMinimumWidth()方法,因?yàn)檫@兩個(gè)方法原理是一樣的:

  protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

如果View沒(méi)有設(shè)置背景則取值為mMinWidth,mMinWidth是可以設(shè)置的,它對(duì)應(yīng)于android:minWidth這個(gè)屬性設(shè)置的值或者View的setMinimumWidth的值,如果不指定的話則默認(rèn)為0:

    public void setMinimumWidth(int minWidth) {
        mMinWidth = minWidth;
        requestLayout();

    }

如果View設(shè)置了背景在取值為max(mMinWidth, mBackground.getMinimumWidth()),取值mMinWidth和mBackground.getMinimumWidth()的最大值,上面我們說(shuō)過(guò)了mMinWidth,那來(lái)看看mBackground.getMinimumWidth(),這個(gè)mBackground是Drawable類型的,看一下Drawable類的getMinimumWidth()方法(Drawable.java):

 public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

intrinsicWidth得到的是這個(gè)Drawable的固有的寬度,如果固有寬度大于0則返回固有寬度,否則返回0。
總結(jié)一下getSuggestedMinimumWidth()方法就是:如果View沒(méi)有設(shè)置背景則返回mMinWidth ,如果設(shè)置了背景就返回mMinWidth 和Drawable最小寬度兩個(gè)值的最大值。

2.ViewGroup的measure流程

ViewGroup的measure原理

講完了View的measure流程,接下來(lái)看看ViewGroup的measure流程,對(duì)于ViewGroup,它不只要measure自己本身,還要遍歷的調(diào)用子元素的measure()方法,ViewGroup中沒(méi)有定義onMeasure()方法,但他定義了measureChildren()方法(ViewGroup.java):

 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

就是遍歷子元素并調(diào)用measureChild()方法:

 protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

調(diào)用child.getLayoutParams()方法來(lái)獲得子元素的LayoutParams屬性,并獲取到子元素的MeasureSpec并調(diào)用子元素的measure()方法進(jìn)行測(cè)量。getChildMeasureSpec()方法里寫了什么呢?

    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) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

很顯然這是根據(jù)父容器的MeasureSpec的模式再結(jié)合子元素的LayoutParams屬性來(lái)得出子元素的MeasureSpec屬性,有一點(diǎn)需要注意的是如果父容器的MeasureSpec屬性為AT_MOST,子元素的LayoutParams屬性為WRAP_CONTENT,那根據(jù)代碼我們會(huì)發(fā)現(xiàn)子元素的MeasureSpec屬性也為AT_MOST,它的specSize值為父容器的specSize減去padding的值,也就是說(shuō)跟這個(gè)子元素設(shè)置LayoutParams屬性為MATCH_PARENT效果是一樣的,為了解決這個(gè)問(wèn)題需要在LayoutParams屬性為WRAP_CONTENT時(shí)指定一下默認(rèn)的寬和高。

LinearLayout的measure流程

ViewGroup并沒(méi)有提供onMeasure()方法,而是讓其子類來(lái)各自實(shí)現(xiàn)測(cè)量的方法,究其原因就是ViewGroup有不同的布局的需要很難統(tǒng)一,接下來(lái)我們來(lái)簡(jiǎn)單分析一下ViewGroup的子類LinearLayout的measure流程,先來(lái)看看它的onMeasure()方法(LinearLayout.java):

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

來(lái)看看垂直measureVertical()方法的部分源碼:

 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
     mTotalLength = 0;       
 ...省略
  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)) {
                mTotalLength += mDividerHeight;
            }

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

            totalWeight += lp.weight;
            
            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // Optimization: don't bother measuring children who are going to use
                // leftover space. These views will get measured again down below if
                // there is any leftover space.
                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) {
                    // heightMode is either UNSPECIFIED or AT_MOST, and this
                    // child wanted to stretch to fill available space.
                    // Translate that to WRAP_CONTENT so that it does not end up
                    // with a height of 0
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                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 &&
                (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

定義了mTotalLength用來(lái)存儲(chǔ)LinearLayout在垂直方向的高度,然后遍歷子元素,根據(jù)子元素的MeasureSpec模式分別計(jì)算每個(gè)子元素的高度,如果是wrap_content則將每個(gè)子元素的高度和margin垂直高度等值相加并賦值給mTotalLength得出整個(gè)LinearLayout的高度。如果布局高度設(shè)置為match_parent者具體數(shù)值則和View的測(cè)量方法一樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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