MeasureSpec

文章大部分內容引自Android開發藝術探索

MeasureSpec

  1. 系統內部是通過MeasureSpec來進行view的測量,但是正常情況下我們無法使用它,但是我們可以使用LayoutParams,在view測量的時候,系統會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然後再根據這個MeasureSpec確定view測量後的寬高。(也就是說子元素的MeasureSpec是又父容器傳遞的,是有自身的LayoutParams有父容器的MeasureSpec共同決定的)

  2. 對於頂級View(DecorView)其MeasureSpec由自身的LayoutParams和窗口的尺寸共同決定;但是對於普通View(我們佈局中的view)來說,其measure過程由viewGroup傳遞而來(measureChildWithMargins()),viewGroup會在其onMeasure()方法中調用measureChildWithMargins()

    /**
     * sdk版本29
     * 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. 大部分工作在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);
    }
    

    可以看出上述方法會對子元素進行measure,在調用子元素的measure之前會先通過getChildMeasureSpec方法來得到子元素的MeasureSpec。從代碼上驗證又可以得出子元素的MeasureSpec的創建與父容器的MeasureSpec和子元素本身的LayoutParams有關,此外還和view的padding和margin有關

    /**
     * 測量孩子最重要的部分,計算出子元素的MeasureSpec並傳遞給它,此方法表明了一個子view的寬或高的合適的尺寸
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * 目標是從父元素自身的MeasureSpec和子元素的LayoutParams來得出最合適的結果(子元素的MeasureSpec)
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    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,父元素是MATCH_PARENT或者確定的尺寸
        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.  子元素是WRAP_CONTENT是,子元素的尺寸就是父元素剩餘的所有尺寸,也就是說會  			充滿父佈局,這就是爲什麼我們自定義view時需要處理WRAP_CONTENT來達到自適應尺寸
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // Parent has imposed a maximum size on us,父元素是WRAP_CONTENT
        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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    

總結:前面已經提到,對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定。那麼針對不同的父容器和View本身不同的LayoutParams,View就可以有多種MeasureSpec。The last:當View採用固定寬高時,不管父容器的MeasureSpec是什麼,View的MeasureSpec都是精確模式(EXACTLY)並且其大小遵循LayoutParams的大小;當View的寬/高是MATCH_PARENT時,如果父容器是精確模式EXACTLY,那麼View也是精確模式並且其大小是父容器的剩餘空間,如果父容器是最大模式AT_MOST,那麼View也是最大模式並且大小不會超過父容器的剩餘空間;當View的寬/高是WRAP_CONTENT,不管父容器的模式是精準模式還是最大模式,View的模式總是最大模式AT_MOST並且其大小不能超過---------------------------------------------------------------------------------------------------------------------精確模式EXACTLY,父容器已經檢測出View的精確大小,這個時候View的最終大小就是SpecSize(int specSize = MeasureSpec.getSize(spec);)所指定的值,它對應LayoutParams中的MATCH_PARENT和具體數值這兩種模式;最大模式AT_MOST,父容器指定了一個可用大小及SpecSize,View的大小不能大於這個值,具體是什麼要看不同View的具體實現,它對應LayoutParams中的WRAP_CONTENT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章