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

來源:互聯網
上載者:User

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

相關文章
Android View體系(一)視圖座標系
Android View體系(二)實現View滑動的六種方法
Android View體系(三)屬性動畫
Android View體系(四)從源碼解析Scroller
Android View體系(五)從源碼解析View的事件分發機制
Android View體系(六)從源碼解析Activity的構成

前言

在上一篇我們瞭解了Activity的構成後,開始瞭解一下View的工作流程,就是measure、layout和draw。measure用來測量View的寬高,layout用來確定View的位置,draw則用來繪製View。這一講我們來看看measure流程,measure流程分為View的measure流程和ViewGroup的measure流程,只不過ViewGroup的measure流程除了要完成自己的測量還要遍曆去調用子項目的measure()方法。

1.View的measure流程

先來看看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);    }

很顯然是用來設定View的寬高的,先來看看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的測量模式,而specSize是View的測量大小,看到這裡我們有必要先看看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類協助我們來測量View,它是一個32位的int值,高兩位為specMode (測量的模式),低30位為specSize (測量的大小),測量模式分為三種:

UNSPECIFIED:未指定模式,View想多大就多大,父容器不做限制,一般用於系統內部的測量。 AT_MOST:最大模式,對應於wrap_comtent屬性,只要尺寸不超過父控制項允許的最大尺寸就行。 EXACTLY:精確模式,對應於match_parent屬性和具體的數值,父容器測量出View所需要的大小,也就是specSize的值。

讓我們回頭看看getDefaultSize()方法,很顯然在AT_MOST和EXACTLY模式下,都返回specSize這個值,也就是View測量後的大小,而在UNSPECIFIED模式返回的是getDefaultSize()方法的第一次個參數的值,這第一個參數從onMeasure()方法來看是getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()得到的,那我們來看看getSuggestedMinimumWidth()方法做了什麼,我們只需要弄懂getSuggestedMinimumWidth()方法,因為這兩個方法原理是一樣的:

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

如果View沒有設定背景則取值為mMinWidth,mMinWidth是可以設定的,它對應於android:minWidth這個屬性設定的值或者View的setMinimumWidth的值,如果不指定的話則預設為0:

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

如果View設定了背景在取值為max(mMinWidth, mBackground.getMinimumWidth()),取值mMinWidth和mBackground.getMinimumWidth()的最大值,上面我們說過了mMinWidth,那來看看mBackground.getMinimumWidth(),這個mBackground是Drawable類型的,看一下Drawable類的getMinimumWidth()方法(Drawable.java):

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

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

2.ViewGroup的measure流程 ViewGroup的measure原理

講完了View的measure流程,接下來看看ViewGroup的measure流程,對於ViewGroup,它不只要measure自己本身,還要遍曆的調用子項目的measure()方法,ViewGroup中沒有定義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);            }        }    }

就是遍曆子項目並調用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);    }

調用child.getLayoutParams()方法來獲得子項目的LayoutParams屬性,並擷取到子項目的MeasureSpec並調用子項目的measure()方法進行測量。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);    }

很顯然這是根據父容器的MeasureSpec的模式再結合子項目的LayoutParams屬性來得出子項目的MeasureSpec屬性,有一點需要注意的是如果父容器的MeasureSpec屬性為AT_MOST,子項目的LayoutParams屬性為WRAP_CONTENT,那根據代碼我們會發現子項目的MeasureSpec屬性也為AT_MOST,它的specSize值為父容器的specSize減去padding的值,也就是說跟這個子項目設定LayoutParams屬性為MATCH_PARENT效果是一樣的,為瞭解決這個問題需要在LayoutParams屬性為WRAP_CONTENT時指定一下預設的寬和高。

LinearLayout的measure流程

ViewGroup並沒有提供onMeasure()方法,而是讓其子類來各自實現測量的方法,究其原因就是ViewGroup有不同的布局的需要很難統一,接下來我們來簡單分析一下ViewGroup的子類LinearLayout的measure流程,先來看看它的onMeasure()方法(LinearLayout.java):

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

來看看垂直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用來儲存LinearLayout在垂直方向的高度,然後遍曆子項目,根據子項目的MeasureSpec模式分別計算每個子項目的高度,如果是wrap_content則將每個子項目的高度和margin垂直高度等值相加並賦值給mTotalLength得出整個LinearLayout的高度。如果布局高度設定為match_parent者具體數值則和View的測量方法一樣。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.