016.View的測繪基礎MeasureSpec

在學習onMeasure方法前,首先要掌握MeasureSpec這個類。MeasureSpec 主要是通過父容器的LayoutParams上的規則生成的,它影響到了測量的時候View的寬高。

1.閱讀MeasureSpec源碼

下面開始從源碼開始閱讀,源碼上有一些註釋,我就先自己翻譯成中文:

/**
     *一個MesasureSpec代表着從父容器到子控件的佈局需求。
     *每一個MesureSpec都代表着寬度和高度的需求
     * 一個MeasureSpec對象都由大小(size)和模式(mode)組成,模式包含了一下幾種:
     * UNSPECIFIED
     * 父控件對子控件沒有加任何的限制,子控件想要多大就給子控件多大。
     * EXACTLY
     * 子控件的大小就是父控件指定的大小。
     *
     * <dt>AT_MOST</dt>
     *父控件給了一個最大的大小,View的大小不能超出這個大小。
     *MeasureSpec 是通過int數值來實現的,這樣避免了對象的分配。
      * 這個類提供了使用int來對大小(size) ,模式(mode),tuple(數組)進行封裝和解析
     */
    public static class MeasureSpec {
        //代表後面30位放size
        private static final int MODE_SHIFT = 30;
        //0x3 也就是11 右移30位,成11000000000000000000000000000000 ,後面30位放size相關的內容
        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;

        /**
         * 
         *
         * mode值必須爲下面中的某一個
         * 
         *  ●  android.view.View.MeasureSpec#UNSPECIFIED 
         *  ●  android.view.View.MeasureSpec#EXACTLY 
         *  ● android.view.View.MeasureSpec#AT_MOST 
         * 
         * 記住: 在Android版本17以及以下的版本中, makeMeasureSpec這個方法的實現和參數的順序無關
         * 並且還會造成值溢出影響到MeasurSpec的值,android.widget.RelativeLayout就受到了這個BUG的影響。
         *在API17開始,這個BUG就被修復了,並且加之以更嚴格的規則。
         *
         * @param size 指定的測量的大小
         * @param mode 指定的測量模式
         * @return 根據大小和模式返回測量規格
         */
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * 從指定的測量規格中獲取到模式(非運算清除掉後30位的內容)
         */
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

        /**
         *從指定的測量規格中獲取到大小(非運算清除掉前2位的內容保留後30位的內容)
 
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        /**根據闕值調整MeasureSpec的大小*/
        static int adjust(int measureSpec, int delta) {
            return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }
到這裏,我們這邊只有一個疑問,就是後面30位的內容是怎麼放的,什麼規則了,用法。關於MeasureSpec的用法,那和LayoutParams有比較大的關係。下面就開始介紹MeasureSpec和LayoutParams的關係.
我們對一個View進行佈局,往往是這三個參數:match_parent 、wrap_content、 以及指定的大小。我們在前面說過ViewRootImpl參與到了DecorView的測量、繪製等過程,在ViewRootImpl的measureHierarchy方法中可以看到MesureSpec生成的過程:

   childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
接着進入getRootMeasureSpec 這個方法:

  private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
可以看到DecorView的MeasureSpec是和WindowSize以及自身的LayoutParams有關的。上面也讓我們知道了match_parent 、wrap_content、 以及指定的大小和簽名說的SpecMode的關係了。
那麼,現在問題來了,MeasureSpec這個類,用在哪裏。我們知道,decorView是FrameLayout 他本身也是ViewGroup的派生類,其他的容器,也都是繼承與ViewGroup。之前我們知道,繪製流程是在ViewRoot開始傳遞的,我們現在看到ViewRootImpl的#performTraversals方法中,有調用performMeasure方法,performMeasure會調用View的measure->onMeasure方法,我們可以看到在LinearLayout的onMeasure方法中,
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

LinearLayout中的measure過程比較複雜,還受到了父容器傳入的MeasureSpec參數的影響,這點我們從onMeasure方法的聲明就可以看到了,我們直接拉到最後一行,看
 if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }

 private void forceUniformWidth(int count, int heightMeasureSpec) {
        // Pretend that the linear layout has an exact size.
        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
                MeasureSpec.EXACTLY);
        for (int i = 0; i< count; ++i) {
           final View child = getVirtualChildAt(i);
           if (child.getVisibility() != GONE) { 
               LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());

               if (lp.width == LayoutParams.MATCH_PARENT) {
                   // Temporarily force children to reuse their old measured height
                   // FIXME: this may not be right for something like wrapping text?
                   int oldHeight = lp.height;
                   lp.height = child.getMeasuredHeight();

                   // Remeasue with new dimensions
                   measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
                   lp.height = oldHeight;
               }
           }
        }
    }

這裏面調用了measureChildWithMargins 來測量子控件的大小,measureChildWithMargins是在ViewGroup方法中聲明的,我們可以看到如下:


 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方法中生成的childWidthMeasureSpec  會傳遞給child用於測量,而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);
    }
 
從上面就可以看到child的MeasureSpec的創建流程,它不僅僅是受到了我們給的佈局參數的影響(比如layout_width),還受到了父容器的MeasureSpec的影響。


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