Android中mesure过程详解
2019獨角獸企業重金招聘Python工程師標準>>>
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肯定是有原 因的,它們會使得布局更加靈活。
? ? ? 首先我們來看幾個關鍵的函數和參數:
? ? ? 1、public final void measue(int widthMeasureSpec, int heightMeasureSpec);
? ? ? 2、protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
? ? ? 3、protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)
? ? ? 4、protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
? ? ? 5、protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,?int parentHeightMeasureSpec, int heightUsed)
? ? ?接著我們來看View類中measure和onMeasure函數的源碼:
public?final?void?measure(int?widthMeasureSpec,?int?heightMeasureSpec)?{????????if?((mPrivateFlags?&?FORCE_LAYOUT)?==?FORCE_LAYOUT?||widthMeasureSpec?!=?mOldWidthMeasureSpec?||heightMeasureSpec?!=?mOldHeightMeasureSpec)?{????????????//?first?clears?the?measured?dimension?flagmPrivateFlags?&=?~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?developerif?((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的默認實現很簡單,源碼如下:
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分三種:
? ? ? 1、MeasureSpec.UNSPECIFIED,父視圖不對子視圖施加任何限制,子視圖可以得到任意想要的大小;
? ? ? 2、MeasureSpec.EXACTLY,父視圖希望子視圖的大小是specSize中指定的大小;
? ? ? 3、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?uscase?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?uscase?MeasureSpec.AT_MOST:????????????if?(childDimension?>=?0)?{????????????????//?Child?wants?a?specific?size...?so?be?itresultSize?=?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?becase?MeasureSpec.UNSPECIFIED:????????????if?(childDimension?>=?0)?{????????????????//?Child?wants?a?specific?size...?let?him?have?itresultSize?=?childDimension;resultMode?=?MeasureSpec.EXACTLY;}?else?if?(childDimension?==?LayoutParams.MATCH_PARENT)?{????????????????//?Child?wants?to?be?our?size...?find?out?how?big?it?should????????????????//?beresultSize?=?0;resultMode?=?MeasureSpec.UNSPECIFIED;}?else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{????????????????//?Child?wants?to?determine?its?own?size....?find?out?how????????????????//?big?it?should?beresultSize?=?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的大小,已尊重程序員的意圖。
轉載于:https://my.oschina.net/u/2370693/blog/543119
總結
以上是生活随笔為你收集整理的Android中mesure过程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ***CSS魔法堂:选择器及其优先级
- 下一篇: js文件引用方式及其同步执行与异步执行