MeasureSpec

MeasureSpec——View測量過程中的建橋


一.什麼是MeasureSpec

在View工作的流程中,measure過程決定view的寬高,在view的measure過程中,MeasureSpec起到了至關重要的作用,它參與了Measure的測量過程。
我們知道,一個view的寬高有時受到父容器的影響,在測量工程中,系統會將view的LayoutParams根據父容器所施加的規則轉換成對應的MeasureSpec,再根據這個MeasureSpec測量出View的寬高,所以View很大程度上決定了view的尺寸規格。所以這個很抽象的MeasureSpec我們可以把它理解爲“策略規格書”。

二.探索MeasureSpec

先看下View的內部類MeasureSpec的內容:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY     = 1 << MODE_SHIFT;
public static final int AT_MOST     = 2 << MODE_SHIFT;

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

 public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

MeasureSpec代表一個32位的int值,通過SpecMode和SpecSize組合成一個int來節約內存,其中高2位代表SpecMode,指測量模式,低30位代表SpecSize,指該測量模式下的規格大小。通過getMode和getSize可以得到其中的值。

通過讓0,1,2向左移位30定義了三個常量作爲SpecMode的三種類型:

EXACTLY

檢查出view的精確大小,此時view的寬高就等於SpecSize的寬高。

AT_MOST

父容器指定了一個可用大小的SpecSize,view寬高不能大於這個值。
用於wrapcontent時,內容包裹大小,但view大小不會超過父容器大小。

UNSPECIFIED

對view不做任何限制,不常出現,多用於scrollview這種要顯示內容遠大於可顯示區域的情況。

二.MeasureSpec與LayoutParams

舉個例子,這是自定義view的時候重寫onMeasure中的一小段:

int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height;
if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = getPaddingTop() + mBound.height() + getPaddingBottom();
            if(heightMode==MeasureSpec.AT_MOST){
                height = Math.min(height,heightSize);
            }
        }

可以看出,這裏通過條件判斷SpecMode對view的高進行了約束,一般情況下,我們可以給view設置LayoutParams,比如寬高或者wrapcontent,細心的同學會發現重寫onMeasure的時候我們會發現傳了兩個參數,寬高的MeasureSpec,這兩個參數是伴隨父容器傳下來的,它會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然後根據這個MS來確定View的寬高。

對於一般的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定。

那對於頂級View(DecorView)來說沒有上層父容器了,所以其MeasureSpec由窗口的尺寸和自身的LayoutParams來決定。

三.MeasureSpec的創建過程

頂級view的MeasureSpec的創建過程與普通view有所不同,MeasureSpec由窗口的尺寸和自身的LayoutParams來決定,很好理解,父容器=屏幕。這裏代碼就不貼了。
總來就說就是跟屏幕尺寸有關:

  • 如果設置matchparent:大小就是屏幕大小。
  • wrapcontent:內容包裹,大小不能超過屏幕大小。
  • 固定大小(android:layout_height=”100dp”):指定多大就是多大。

那麼對於普通View來說,measure過程由viewGroup傳遞過來,
viewGroup中的measureChildWithMargin方法:

    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);//調用子元素的measure方法
    }

注意child.measure傳的參數就是我們之前所說的重寫onMeasure方法得到的那兩個參數,通過getChildMeasureSpec方法(注意傳的參數)來得到子元素的MeasureSpec,所以解釋了爲什麼子元素MeasureSpec的創建與父容器的的MeasureSpec和子元素和子元素本身的LayoutParams有關,還與View的margin和padding有關:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        //size爲子容器可用大小,爲父容器減去padding的大小
        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);
    }
......//省略部分
    }

上述measureChildWithMargin方法傳遞了參數爲父容器的MeasureSpec和子容器的padding=mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin+ heightUsed(還有一組寬度),通過父容器的MeasureSpec結合view本身的LayoutParams來確定子元素的MeasureSpec。
那麼總結來說:

  • view採用固定模式:
    • 不管父容器的SpecMode是什麼,view的MeasureSpec都是EXACTLY,大小就是開發者設置的大小。
  • View是matchparent:
    • 父容器爲EXACTLY,那麼view也是EXACTLY,大小是父容器的剩餘空間。
    • 父容器是AT_MOST,view也是AT_MOST,大小不能超過父容器的剩餘大小。
  • view是wrapcontent:
    • 不管父容器爲什麼,view的模式總是AT_MOST,大小不超過父容器的剩餘大小。
  • view爲UNSPECIFIED:
    • 理論沒有限制,可以無限大。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章