1. 程式人生 > >View的工作原理之Measure過程原始碼學習(三)

View的工作原理之Measure過程原始碼學習(三)

        上一篇文章講解了整個Android應用程式的View檢視的頂級節點DecorView的Measure過程,文章最後就講到了DecorView的onMeasure方法中呼叫super.onMeasure(widthMeasureSpec, heightMeasureSpec);之後,在FrameLayout的onMeasure方法中通過迴圈遍歷子元素,從而往下進行每一級View的Measure過程。

        在開始本文內容講解之前,我們需要先理解一些概念。在Android原始碼中,我們可以發現,所有的View控制元件都是直接或間接繼承自View這個類,包括ViewGroup也是。所以這裡我們有必要進行一下區分,我們知道ViewGroup是可以有子元素的View,那麼對於不能有子元素的View,我們稱之為普通View

,例如TextView等。區分好了這兩者,就繼續來講解ViewGroup和普通View的Measure過程。

        回顧第一篇文章中,最後部分說到,DecorView的Measure過程是在performMeasure方法中呼叫DecorView的measure方法開始,而子元素的Measure過程是在DecorView的父類FrameLayout中的onMeasure方法中呼叫子元素的measure方法開始的。這就可以知道,不管是哪個ViewMeasure過程,一定會呼叫到它的measure方法。檢視原始碼發現,這個measure方法是在View類中定義的,並且它是final型別

,任何子類都不能重寫這個方法,那我們就來看一下這個方法的原始碼。

//View.java 21961行
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
        //光學邊界處理
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        //...

        if (forceLayout || needsLayout) {

          //...

            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

         //...
        }
}

        在View的measure方法程式碼中,可以看到,前面有部分是光學邊界處理的。至於這光學邊界是什麼來的,我還不是很瞭解,但是這裡還是提一下,防止以後再看程式碼糾結。程式碼省略部分大多是判斷條件的處理,我認為不需要過多關注,所以註釋了。這裡其實重點是onMeasuresetMeasuredDimensionRaw這兩個方法。整個measure方法中重點就是,根據判斷條件的不同,分別呼叫這兩個方法。根據註釋及相關程式碼,我猜測應該是,為了避免重複測量,所以會在測量的時候靜得到的測量值快取下來,然後下一次再需要測量的時候,檢查是不是有快取的測量值。有得話就拿出來呼叫setMeasuredDimensionRaw方法就行,沒有就呼叫onMeasure方法進行測量,值的一提的是onMeasure方法的引數就是父容器的MeasureSpec值,這個是可以在onMeasure方法註釋中瞭解到。下面來看View的這兩個方法。

//View.java 22115行
 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

//View.java 22089行
 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.java 22072行
 * @param widthMeasureSpec Horizontal space requirements as imposed by the parent
 * @param heightMeasureSpec Vertical space requirements as imposed by the parent

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

       可以看到,setMeasuredDimensionRaw方法很簡單,將拿到的測量值,賦給全域性變數就完成了。onMeasure方法最終也是呼叫setMeasuredDimension方法,這裡又是光學邊界處理,然後呼叫setMeasuredDimensionRaw方法,也是把處理得到的值給到全域性變數。

      到這,可以很直接的說,所有View的measure過程的最終目的就是把測量得到的值賦值給mMeasuredHeight和mMeasuredWidth。很顯然View的測量結果,就是為了View的佈局(layout)過程和繪畫(draw)過程服務的。在這兩個過程,都有使用到getMeasuredWidthgetMeasuredHeight方法來獲取他們終的SpecSize。程式碼如下,之所以需要“與運算”,是因為mMeasuredWidth和mMeasuredHeight包含測量模式和測量大小,這裡只取大小。

//View.java  13626行
 public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
//View.java  13654行
public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

       View的measure過程到這,其實就可以看出一些東西了。接下來的步驟就是具體的實現View重寫onMeasure方法,然後進行相應的處理了。那麼當我們自定義View的時候重寫的onMeasure方法,到底該做些什麼呢?我們在這裡是不得而知的。關於這個問題,我們可以參看目前Android已經寫好的控制元件,看看這些控制元件是如何實現的,就可以知道,當我們自定義View的時候該怎麼做了。

           先來看一下,預設情況下View的onMeasure方法是怎麼做的,看原始碼:

//View.java 22072行
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

//View.java 22232行
 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
 }
//Drawable.java  1052行
 public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
 }

//Drawable.java  1026行
 public int getIntrinsicWidth() {
        return -1;
 }

        在onMeasure方法中,setMeasuredDimension方法傳遞的引數是getDefaultSize方法返回來的,getDefaultSize方法的引數又是getSuggestedMinimumWidth和父容器的widthMeasureSpec(這裡只分析width的情況,height的情況類似)。在getSuggestedMinimumWidth方法中,檢視背景是否設定,如果沒有設定背景,那麼View的寬度是mMinWidth,而mMinWidth對應android:minWidth這個屬性的值,如果這個屬性沒有設定,那麼預設0。如果View設定了背景,那麼getSuggestedMinimumWidth方法返回的值是max(mMinWidth, mBackground.getMinimumWidth())。通過程式碼得知,mBackground.getMinimumWidth()返回值為intrinsicWidth,intrinsicWidth的值是Drawable的原始寬度,有些Drawable由原始寬度,有些沒有,如果沒有則直接返回0。

//View.java 22188行
 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;
 }

           getDefaultSize方法也很簡單, View的寬高就是specSize(不考慮MeasureSpec.UNSPECIFIED模式的情況)。所以在預設的onMeasure方法中,View的寬高就是父容器剩餘的大小,也就是父容器的MeasureSpec值的SpecSize。由此我們得出一個結論:不管父容器的測量模式是什麼,預設情況下,子元素的測量大小,都是父容器剩餘的大小。到這瞭解了預設情況下View的onMeasure方法的工作。下一篇文章,將會學習ViewGroup和普通View的onMeasure方法的工作。