invalidate()最後會發起一個View樹遍歷的請求,並經過執行performTraersal()來響應該請 求,performTraersal()正是對View樹進行遍歷和繪製的核心函數,內部的主體邏輯是判斷是否須要從新測量視圖大小(measure), 是否須要從新佈局(layout),是否從新須要繪製(draw)。measure過程是遍歷的前提,只有measure後才能進行佈局(layout) 和繪製(draw),由於在layout的過程當中須要用到measure過程當中計算獲得的每一個View的測量大小,而draw過程須要layout肯定每 個view的位置才能進行繪製。下面咱們主要來探討一下measure的主要過程,相對與layout和draw,measure過程理解起來比較困難。程序員
咱們在編寫layout的xml文件時會碰到layout_width和layout_height兩個屬性,對於這兩個屬性咱們有三種選擇:賦值成具體 的數值,match_parent或者wrap_content,而measure過程就是用來處理match_parent或者 wrap_content,假如layout中規定全部View的layout_width和layout_height必須賦值成具體的數值,那麼 measure實際上是沒有必要的,可是google在設計Android的時候考慮加入match_parent或者wrap_content確定是有原 因的,它們會使得佈局更加靈活。函數
首先咱們來看幾個關鍵的函數和參數:佈局
一、public final void measue(int widthMeasureSpec, int heightMeasureSpec);this
二、protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);google
三、protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)spa
四、protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)設計
五、protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)code
接着咱們來看View類中measure和onMeasure函數的源碼:orm
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~MEASURED_DIMENSION_SET; if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); } // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
因爲函數原型中有final字段,那麼measure根本沒打算被子類繼承,也就是說measure的過程是固定的,而measure中調用了onMeasure函數,所以真正有變數的是onMeasure函數,onMeasure的默認實現很簡單,源碼以下:xml
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
onMeasure默認的實現僅僅調用了setMeasuredDimension,setMeasuredDimension函數是一個很關鍵的函數,它對View的成員變量mMeasuredWidth和mMeasuredHeight變量賦值,而measure的主要目的就是對View樹中的每一個View的mMeasuredWidth和mMeasuredHeight進行賦值,一旦這兩個變量被賦值,則意味着該View的測量工做結束。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= MEASURED_DIMENSION_SET; }
對於非ViewGroup的View而言,經過調用上面默認的measure——>onMeasure,便可完成View的測量,固然你也可 以重載onMeasure,並調用setMeasuredDimension來設置任意大小的佈局,但通常不這麼作,由於這種作法太「專政」,至於爲什麼「專政」,讀完本文就會明白。
對於ViewGroup的子類而言,每每會重載onMeasure函數負責其children的measure工做,重載時不要忘記調用 setMeasuredDimension來設置自身的mMeasuredWidth和mMeasuredHeight。若是咱們在layout的時候不 須要依賴子視圖的大小,那麼不重載onMeasure也能夠,可是必須重載onLayout來安排子視圖的位置,這在下一篇博客中會介紹。
再來看下measue(int widthMeasureSpec, int heightMeasureSpec)中的兩個參數, 這兩個參數分別是父視圖提供的測量規格,當父視圖調用子視圖的measure函數對子視圖進行測量時,會傳入這兩個參數,經過這兩個參數以及子視圖自己的 LayoutParams來共同決定子視圖的測量規格,在ViewGroup的measureChildWithMargins函數中體現了這個過程,稍後會介紹。
MeasureSpec參數的值爲int型,分爲高32位和低16爲,高32位保存的是specMode,低16位表示specSize,specMode分三種:
一、MeasureSpec.UNSPECIFIED,父視圖不對子視圖施加任何限制,子視圖能夠獲得任意想要的大小;
二、MeasureSpec.EXACTLY,父視圖但願子視圖的大小是specSize中指定的大小;
三、MeasureSpec.AT_MOST,子視圖的大小最可能是specSize中的大小。
以上施加的限制只是父視圖「但願」子視圖的大小按MeasureSpec中描述的那樣,可是子視圖的具體大小取決於多方面的。
ViewGroup中定義了measureChildren, measureChild, measureChildWithMargins來對子視圖進行測量,measureChildren內部只是循環調用 measureChild,measureChild和measureChildWithMargins的區別就是是否把margin和padding也 做爲子視圖的大小,咱們主要分析measureChildWithMargins的執行過程:
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); }
總的來看該函數就是對父視圖提供的measureSpec參數進行了調整(結合自身的LayoutParams參數),而後再來調用child.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); }
getChildMeasureSpec的整體思路就是經過其父視圖提供的MeasureSpec參數獲得specMode和specSize,並 根據計算出來的specMode以及子視圖的childDimension(layout_width和layout_height中定義的)來計算自身 的measureSpec,若是其自己包含子視圖,則計算出來的measureSpec將做爲調用其子視圖measure函數的參數,同時也做爲自身調用 setMeasuredDimension的參數,若是其不包含子視圖則默認狀況下最終會調用onMeasure的默認實現,並最終調用到 setMeasuredDimension,而該函數的參數正是這裏計算出來的。
總結:從上面的描述看出,決定權最大的就是View的設計者,由於設計者能夠經過調用setMeasuredDimension決定視圖的最終大小,例如 調用setMeasuredDimension(100, 100)將視圖的mMeasuredWidth和mMeasuredHeight設置爲100,100,那麼父視圖提供的大小以及程序員在xml中設置的 layout_width和layout_height將徹底不起做用,固然良好的設計通常會根據子視圖的measureSpec來設置 mMeasuredWidth和mMeasuredHeight的大小,已尊重程序員的意圖。