? ? ? ??? 本文原創, 轉載請注明出處:http://blog.csdn.net/qinjuning
? ? ? ? 上篇文章<<Android中measure過程、WRAP_CONTENT詳解以及xml布局文件解析流程淺析(上)>>中,我們
? 了解了View樹的轉換過程以及如何設置View的LayoutParams的。本文繼續沿著既定軌跡繼續未完成的job。
? ? ? ? 主要知識點如下:
? ? ? ? ? ? ? ? ?1、MeasureSpc類說明
? ? ? ? ? ? ? ? ?2、measure過程詳解(揭秘其細節);
? ? ? ? ? ? ? ? ?3、root View被添加至窗口時,UI框架是如何設置其LayoutParams值得。
? ? ? ?在講解measure過程前,我們非常有必要理解MeasureSpc類的使用,否則理解起來也只能算是囫圇吞棗。
?1、MeasureSpc類說明
? ?1.1 ?SDK 說明如下
? ? ? ? ? ? ? A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec
? ? ? ? ?represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and
? ? ? ? ?a mode.
? ? ? ? 即:
? ? ? ? ? ? ?MeasureSpc類封裝了父View傳遞給子View的布局(layout)要求。每個MeasureSpc實例代表寬度或者高度
? ?(只能是其一)要求。?它有三種模式:
? ? ? ? ? ? ①、UNSPECIFIED(未指定),父元素部隊自元素施加任何束縛,子元素可以得到任意想要的大小;
? ? ? ? ? ? ②、EXACTLY(完全),父元素決定自元素的確切大小,子元素將被限定在給定的邊界里而忽略它本身大小;
? ? ? ? ? ? ③、AT_MOST(至多),子元素至多達到指定大小的值。
? ?常用的三個函數:
static int getMode(int measureSpec) ?: ?根據提供的測量值(格式)提取模式(上述三個模式之一)
? ? ?static int getSize(int measureSpec) ?: 根據提供的測量值(格式)提取大小值(這個大小也就是我們通常所說的大小)
? ? ?static int makeMeasureSpec(int size,int mode) ?: ?根據提供的大小值和模式創建一個測量值(格式)
? ? ? ? ? ? ?以上摘取自: ?<<MeasureSpec介紹及使用詳解>>
? ?1.2 ? MeasureSpc類源碼分析???其為View.java類的內部類,路徑:\frameworks\base\core\java\android\view\View.java
[java] view plaincopyprint?
public?class?View?implements?...?{???????...???????public?static?class?MeasureSpec?{??????????private?static?final?int?MODE_SHIFT?=?30;?????????????????????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;??????????????????????public?static?int?makeMeasureSpec(int?size,?int?mode)?{??????????????return?size?+?mode;??????????}????????????????????public?static?int?getMode(int?measureSpec)?{??????????????return?(measureSpec?&?MODE_MASK);??????????}????????????????????public?static?int?getSize(int?measureSpec)?{??????????????return?(measureSpec?&?~MODE_MASK);??????????}????????}??????...??}??
? ??
MeasureSpec類的處理思路是:
? ? ? ①、右移運算,使int 類型的高兩位表示模式的實際值,其余30位表示其余30位代表長或寬的實際值----可以是
? ? ? ? ?WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。
? ? ? ②、通過掩碼MODE_MASK進行與運算 “&”,取得模式(mode)以及長或寬(value)的實際值。
?2、measure過程詳解
?
? ?2.1 ?measure過程深入分析
? ? ? ?之前的一篇博文<<?Android中View繪制流程以及invalidate()等相關方法分析>>,我們從”二B程序員”的角度簡單 ? ?解了measure過程的調用過程。過了這么多,我們也該升級了,- - 。現在請開始從”普通程序員”角度去理解這個
?過程。我們重點查看measure過程中地相關方法。
? ? ?我們說過,當UI框架開始繪制時,皆是從ViewRoot.java類開始繪制的。
? ? ? ViewRoot類簡要說明: 任何顯示在設備中的窗口,例如:Activity、Dialog等,都包含一個ViewRoot實例,該
? 類主要用來與遠端 WindowManagerService交互以及控制(開始/銷毀)繪制。
? ? ?Step 1、 開始UI繪制 , 具體繪制方法則是:
[java] view plaincopyprint?
路徑:\frameworks\base\core\java\android\view\ViewRoot.java??public?final?class?ViewRoot?extends?Handler?implements?ViewParent,View.AttachInfo.Callbacks?{??????...????????????View?mView;??????????????????????private?void?performTraversals(){??????????...????????????????????int?childWidthMeasureSpec;???????????int?childHeightMeasureSpec;????????????????????????????????host.measure(childWidthMeasureSpec,?childHeightMeasureSpec);??????????...??????}??????...??}??
這兒,我并沒有說出childWidthMeasureSpec和childHeightMeasureSpec類的來由(為了避免額外地開銷,等到
第三部分時我們在來攻克它,現在只需要記住其值MeasureSpec.makeMeasureSpec()構建的。
? ? Step 2 、調用measure()方法去做一些前期準備
? ? ? ?measure()方法原型定義在View.java類中,final修飾符修飾,其不能被重載:
? ??
[java] view plaincopyprint?
public?class?View?implements?...?{??????...?????????????????????public?final?void?measure(int?widthMeasureSpec,?int?heightMeasureSpec)?{????????????????????if?((mPrivateFlags?&?FORCE_LAYOUT)?==?FORCE_LAYOUT?||??????????????????widthMeasureSpec?!=?mOldWidthMeasureSpec?||??????????????????heightMeasureSpec?!=?mOldHeightMeasureSpec)?{????????????????????????????????????????????mPrivateFlags?&=?~MEASURED_DIMENSION_SET;?????????????????????????????????????????????onMeasure(widthMeasureSpec,?heightMeasureSpec);????????????????????????????????????????????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;???????}??????...??}??
? ? ??參數widthMeasureSpec和heightMeasureSpec?由父View構建,表示父View給子View的測量要求。其值地構建
?會在下面步驟中詳解。 ?
? ?measure()方法顯示判斷是否需要重新調用設置改View大小,即調用onMeasure()方法,然后操作兩個標識符:
? ? ? ? ? ??①、重置MEASURED_DIMENSION_SET? ?: onMeasure()方法中,需要添加該標識符,否則,會報異常; ? ?
? ? ? ?②、添加LAYOUT_REQUIRED : 表示需要進行layout操作。
? ? 最后,保存當前的widthMeasureSpec和heightMeasureSpec值。
?? Step 3 、調用onMeasure()方法去真正設置View的長寬值,其默認實現為:
[java] view plaincopyprint?
??????????????????protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{????????setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),?widthMeasureSpec),????????????????getDefaultSize(getSuggestedMinimumHeight(),?heightMeasureSpec));????}????????????????????????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;????}????????protected?int?getSuggestedMinimumWidth()?{????????int?suggestedMinWidth?=?mMinWidth;????????????if?(mBGDrawable?!=?null)?{?????????????final?int?bgMinWidth?=?mBGDrawable.getMinimumWidth();????????????if?(suggestedMinWidth?<?bgMinWidth)?{????????????????suggestedMinWidth?=?bgMinWidth;????????????}????????}??????????return?suggestedMinWidth;????}????????protected?final?void?setMeasuredDimension(int?measuredWidth,?int?measuredHeight)?{????????mMeasuredWidth?=?measuredWidth;????????mMeasuredHeight?=?measuredHeight;??????????mPrivateFlags?|=?MEASURED_DIMENSION_SET;??????}??
? ? ? ?主要功能就是根據該View屬性(android:minWidth和背景圖片大小)和父View對該子View的"測量要求",設置該 ? ? ?View的?mMeasuredWidth 和?mMeasuredHeight 值。
? ? ? ?這兒只是一般的View類型地實現方法。一般來說,父View,也就是ViewGroup類型,都需要在重寫onMeasure() ? 方法,遍歷所有子View,設置每個子View的大小。基本思想如下:遍歷所有子View,設置每個子View的大小。偽
? 代碼表示為:
[java] view plaincopyprint?
??protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{????????super.onMeasure(widthMeasureSpec?,?heightMeasureSpec)?????????????????????????????for(int?i?=?0?;?i?<?getChildCount()?;?i++){??????View?child?=?getChildAt(i);????????????child.onMeasure(childWidthMeasureSpec,?childHeightMeasureSpec);????}??}??
? ? ??Step 2、Step 3?代碼也比較好理解,但問題是我們示例代碼中widthMeasureSpec、heightMeasureSpec是如何
?確定的呢?父View是如何設定其值的?
??
? ? ? 要想回答這個問題,我們看是去源代碼里找找答案吧。在ViewGroup.java類中,為我們提供了三個方法,去設置
每個子View的大小,基本思想也如同我們之前描述的思想:遍歷所有子View,設置每個子View的大小。
? ? ?主要有如下方法:
[java] view plaincopyprint?
???????????protected?void?measureChildren(int?widthMeasureSpec,?int?heightMeasureSpec)?{??????final?int?size?=?mChildrenCount;??????final?View[]?children?=?mChildren;??????for?(int?i?=?0;?i?<?size;?++i)?{??????????final?View?child?=?children[i];??????????if?((child.mViewFlags?&?VISIBILITY_MASK)?!=?GONE)?{???????????????measureChild(child,?widthMeasureSpec,?heightMeasureSpec);??????????}??????}??}???????????????????protected?void?measureChild(View?child,?int?parentWidthMeasureSpec,??????????int?parentHeightMeasureSpec)?{??????final?LayoutParams?lp?=?child.getLayoutParams();?????????????final?int?childWidthMeasureSpec?=?getChildMeasureSpec(parentWidthMeasureSpec,??????????????mPaddingLeft?+?mPaddingRight,?lp.width);????????????final?int?childHeightMeasureSpec?=?getChildMeasureSpec(parentHeightMeasureSpec,??????????????mPaddingTop?+?mPaddingBottom,?lp.height);????????child.measure(childWidthMeasureSpec,?childHeightMeasureSpec);??}??
??
? ? ?measureChildren()方法:遍歷所有子View,調用measureChild()方法去設置該子View的屬性值。
? ? ?measureChild() ?方法 ? : 獲取特定子View的widthMeasureSpec、heightMeasureSpec,調用measure()方法
?設置子View的實際寬高值。
? ??getChildMeasureSpec()就是獲取子View的widthMeasureSpec、heightMeasureSpec值。
??
[java] view plaincopyprint?
?????????????????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)?{??????????????????case?MeasureSpec.EXACTLY:?????????????????????if?(childDimension?>=?0)?{????????????????????????resultSize?=?childDimension;???????????????????????resultMode?=?MeasureSpec.EXACTLY;??????????????}?????????????????????else?if?(childDimension?==?LayoutParams.MATCH_PARENT)?{????????????????????????????resultSize?=?size;?????????????????????????????????resultMode?=?MeasureSpec.EXACTLY;??????????????}?????????????????????else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{??????????????????????????????????????????resultSize?=?size;?????????????????????????????????resultMode?=?MeasureSpec.AT_MOST;??????????????}??????????break;????????????????????case?MeasureSpec.AT_MOST:????????????????????if?(childDimension?>=?0)?{????????????????????????????resultSize?=?childDimension;??????????????????????resultMode?=?MeasureSpec.EXACTLY;?????????????}????????????????????else?if?(childDimension?==?LayoutParams.MATCH_PARENT)?{??????????????????????????????????????????resultSize?=?size;????????????????????????????????resultMode?=?MeasureSpec.AT_MOST;?????????????}????????????????????else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{??????????????????????????????????????????resultSize?=?size;????????????????????????????????resultMode?=?MeasureSpec.AT_MOST;?????????????}??????????break;????????????????????case?MeasureSpec.UNSPECIFIED:????????????????????if?(childDimension?>=?0)?{????????????????????????????resultSize?=?childDimension;??????????????????????resultMode?=?MeasureSpec.EXACTLY;?????????????}????????????????????else?if?(childDimension?==?LayoutParams.MATCH_PARENT)?{??????????????????????????????????????????resultSize?=?0;??????????????????????????????????????resultMode?=?MeasureSpec.UNSPECIFIED;????????????}?????????????????????else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{??????????????????????????????????????????resultSize?=?0;??????????????????????????????????????resultMode?=?MeasureSpec.UNSPECIFIED;????????????}??????????break;??????}????????????return?MeasureSpec.makeMeasureSpec(resultSize,?resultMode);??}??
? ? ? ?為了便于分析,我將上面的邏輯判斷語句使用列表項進行了說明.
? getChildMeasureSpec()方法的主要功能如下:
? ? ? ??根據父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View內部
??LayoutParams屬性值,共同決定子View的measureSpec值的大小。主要判斷條件主要為MeasureSpec的mode
?類型以及LayoutParams的寬高實際值(lp.width,lp.height),見于以上所貼代碼中的列表項: 1、 1.1 ; 1.2 ; 1.3 ;?
? 2、2.1等。
? ? ? ? 例如,分析列表3:假設當父View為MeasureSpec.UNSPECIFIED類型,即未定義時,只有當子View的width
?或height指定時,其mode才為MeasureSpec.EXACTLY,否者該View size為 0 ,mode為MeasureSpec.UNSPECIFIED時
?,即處于未指定狀態。
? ? ? 由此可以得出, 每個View大小的設定都事由其父View以及該View共同決定的。但這只是一個期望的大小,每個
?View在測量時最終大小的設定是由setMeasuredDimension()最終決定的。因此,最終確定一個View的“測量長寬“是
?由以下幾個方面影響:
? ? ? ? 1、父View的MeasureSpec屬性;
? ? ? ? 2、子View的LayoutParams屬性 ;
? ? ? ? 3、setMeasuredDimension()或者其它類似設定?mMeasuredWidth 和?mMeasuredHeight 值的方法。
? ? ? ? ? ? ? ??setMeasuredDimension()原型:
[java] view plaincopyprint?
??protected?final?void?setMeasuredDimension(int?measuredWidth,?int?measuredHeight)?{??????mMeasuredWidth?=?measuredWidth;??????mMeasuredHeight?=?measuredHeight;????????mPrivateFlags?|=?MEASURED_DIMENSION_SET;????}??
? 將上面列表項轉換為表格為:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ?這張表格更能幫助我們分析View的MeasureSpec的確定條件關系。
? ?為了幫助大家理解,下面我們分析某個窗口使用地xml布局文件,我們弄清楚該xml布局文件中每個View的
MeasureSpec值的組成。
? ??
[java] view plaincopyprint?
<?xml?version="1.0"?encoding="utf-8"?>??<LinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??????android:id="@+id/llayout"?????????android:orientation="vertical"???????android:layout_width="match_parent"?????????android:layout_height="match_parent">??????????????????<TextView?android:id="@+id/tv"???????????android:layout_width="match_parent"??????????android:layout_height="wrap_content"??????????android:text="@string/hello"?/>????</LinearLayout>?????
? ? ?該布局文件共有兩個View: ?①、id為llayout的LinearLayout布局控件 ;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?②、id為tv的TextView控件。
? ? ? 假設LinearLayout的父View對應地widthSpec和heightSpec值皆為MeasureSpec.EXACTLY類型(Activity窗口
? 的父View為DecorView,具體原因見第三部分說明)。
? ? ? ?對LinearLayout而言比較簡單,由于 android:layout_width="match_parent",因此其width對應地widthSpec?
? mode值為MeasureSpec.EXACTLY ,?size由父視圖大小指定 ;??由于android:layout_height = "match_parent",
? 因此其height對應地heightSpec mode值為MeasureSpec.EXACTLY,size由父視圖大小指定 ;
? ? ? ?對TextView而言 ,其父View為LinearLayout的widthSpec和heightSpec值皆為MeasureSpec.EXACTLY類型,
?由于android:layout_width="match_parent" , 因此其width對應地widthSpec mode值為MeasureSpec.EXACTLY,
?size由父視圖大小指定 ; ?由于android:layout_width="wrap_content" , 因此其height對應地widthSpec mode值為
?MeasureSpec.AT_MOST,size由父視圖大小指定 。
? ? 我們繼續窺測下LinearLayout類是如何進行measure過程的:
[java] view plaincopyprint?
?public?class?LinearLayout?extends?ViewGroup?{??...??@Override????protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{????????????if?(mOrientation?==?VERTICAL)?{??????????measureVertical(widthMeasureSpec,?heightMeasureSpec);??????}?else?{??????????measureHorizontal(widthMeasureSpec,?heightMeasureSpec);??????}??}???????void?measureVertical(int?widthMeasureSpec,?int?heightMeasureSpec)?{?????????mTotalLength?=?0;???????????????float?totalWeight?=?0;??????????int?maxWidth?=?0;??????????????????...?????????final?int?count?=?getVirtualChildCount();????????????????????final?int?widthMode?=?MeasureSpec.getMode(widthMeasureSpec);?????????final?int?heightMode?=?MeasureSpec.getMode(heightMeasureSpec);????????????...??????????????????for?(int?i?=?0;?i?<?count;?++i)?{?????????????final?View?child?=?getVirtualChildAt(i);????????????????...?????????????LinearLayout.LayoutParams?lp?=?(LinearLayout.LayoutParams)?child.getLayoutParams();???????????????totalWeight?+=?lp.weight;????????????????????????????if?(heightMode?==?MeasureSpec.EXACTLY?&&?lp.height?==?0?&&?lp.weight?>?0)?{?????????????????...?????????????}?else?{?????????????????int?oldHeight?=?Integer.MIN_VALUE;??????????????????????????????????if?(lp.height?==?0?&&?lp.weight?>?0)?{?????????????????????oldHeight?=?0;?????????????????????lp.height?=?LayoutParams.WRAP_CONTENT;?????????????????}??????????????????????????????????????????????????????????????????????????????????????????????????????measureChildBeforeLayout(????????????????????????child,?i,?widthMeasureSpec,?0,?heightMeasureSpec,????????????????????????totalWeight?==?0???mTotalLength?:?0);??????????????????????????????????????????????????????????????????????????????????????????????????????final?int?childHeight?=?child.getMeasuredHeight();?????????????????final?int?totalLength?=?mTotalLength;?????????????????mTotalLength?=?Math.max(totalLength,?totalLength?+?childHeight?+?lp.topMargin?+????????????????????????lp.bottomMargin?+?getNextLocationOffset(child));?????????????????...?????????????}?????????????final?int?margin?=?lp.leftMargin?+?lp.rightMargin;?????????????final?int?measuredWidth?=?child.getMeasuredWidth()?+?margin;?????????????maxWidth?=?Math.max(maxWidth,?measuredWidth);?????????????...?????????}?????????????????????...?????}?????void?measureChildBeforeLayout(View?child,?int?childIndex,?????????????int?widthMeasureSpec,?int?totalWidth,?int?heightMeasureSpec,?????????????int?totalHeight)?{???????????????measureChildWithMargins(child,?widthMeasureSpec,?totalWidth,?????????????????heightMeasureSpec,?totalHeight);?????}??...??
? ? ? ? ??
? ? ? ? 繼續看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內,基本流程同于measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理。
? ? ??measureChildWithMargins@ViewGroup.java?
[java] view plaincopyprint?
?????????????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()過程時,LinearLayout類做了如下事情 :
? ? ? ? ? ? 1、遍歷每個子View,對其調用measure()方法;
? ? ? ? ? ? 2、子View?measure()完成后,需要取得該子View地寬高實際值,繼而做處理(例如:LinearLayout屬性為
? ? ? ?android:widht="wrap_content"時,LinearLayout的實際width值則是每個子View的width值的累加值)。
? ? ?
??2.2 WRAP_CONTENT、MATCH_PARENT以及measure動機揭秘
? ? ? ? 子View地寬高實際值 ,即child.getMeasuredWidth()值得返回最終會是一個確定值?? 難道WRAP_CONTENT(
其值為-2)?、MATCH_PARENT(值為-1)或者說一個具體值(an exactly size > 0)。前面我們說過,View最終“測量”值的
確定是有三個部分組成地:
? ? ? ? ?①、父View的MeasureSpec屬性;
? ? ? ? ?②、子View的LayoutParams屬性 ;
? ? ? ? ?③、setMeasuredDimension()或者其它類似設定?mMeasuredWidth 和?mMeasuredHeight 值的方法。
? ?因此,一個View必須以某種合適地方法確定它地最終大小。例如,如下自定義View:
[java] view plaincopyprint?
??public?Class?MyView?extends?View?{????????????????????protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec){??????????????????????int?widthMode?=?MeasureSpec.getMode(widthMeasureSpec);???????????int?heightMode?=?MeasureSpec.getMode(heightMeasureSpec);??????????????????????int?width?=?0?;???????????int?height?=?0?;??????????????????????if(widthMode?==?MeasureSpec.UNSPECIFIED?||?heightMode?==?MeasureSpec.UNSPECIFIED)???????????????throw?new?RuntimeException("widthMode?or?heightMode?cannot?be?UNSPECIFIED");????????????????????????????????if(widthMode?==?MeasureSpec.EXACTLY){???????????????width?=?100?;???????????}??????????????????????else?if(widthMode?==?MeasureSpec.AT_MOST?)???????????????width?=?50?;???????????????????????????????????if(heightMode?==?MeasureSpec.EXACTLY){???????????????height?=?100?;???????????}??????????????????????else?if(heightMode?==?MeasureSpec.AT_MOST?)???????????????height?=?50?;??????????????????????setMeasuredDimension(width?,?height)?;???????}??}??
? ? ? ? ?
該自定義View重寫了onMeasure()方法,根據傳遞過來的widthMeasureSpec和heightMeasureSpec簡單設置了
?該View的mMeasuredWidth 和?mMeasuredHeight值。
? ? ? 對于TextView而言,如果它地mode不是Exactly類型 , 它會根據一些屬性,例如:android:textStyle
? 、android:textSizeandroid:typeface等去確定TextView類地需要占用地長和寬。
? ?
? ? ?因此,如果你地自定義View必須手動對不同mode做出處理。否則,則是mode對你而言是無效的。
? ?
? ? ? Android框架中提供地一系列View/ViewGroup都需要去進行這個measure()過程地 ,因為在layout()過程中,父
? View需要調用getMeasuredWidth()或getMeasuredHeight()去為每個子View設置他們地布局坐標,只有確定布局
? 坐標后,才能真正地將該View 繪制(draw)出來,否則該View的layout大小為0,得不到期望效果。我們繼續看看
? LinearLayout的layout布局過程:
[java] view plaincopyprint?
public?class?LinearLayout?extends?ViewGroup?{??????...??????@Override????????protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{????????????????????if?(mOrientation?==?VERTICAL)?{??????????????layoutVertical();??????????}?else?{??????????????layoutHorizontal();??????????}??????}????????????void?layoutVertical()?{??????????...??????????final?int?count?=?getVirtualChildCount();??????????...??????????for?(int?i?=?0;?i?<?count;?i++)?{??????????????final?View?child?=?getVirtualChildAt(i);??????????????if?(child?==?null)?{????????????????????childTop?+=?measureNullChild(i);??????????????}?else?if?(child.getVisibility()?!=?GONE)?{????????????????????????????????????final?int?childWidth?=?child.getMeasuredWidth();??????????????????final?int?childHeight?=?child.getMeasuredHeight();????????????????????????????????????...????????????????????????????????????setChildFrame(child,?childLeft,?childTop?+?getLocationOffset(child),??????????????????????????childWidth,?childHeight);???????????????????childTop?+=?childHeight?+?lp.bottomMargin?+?getNextLocationOffset(child);????????????????????i?+=?getChildrenSkipCount(child,?i);??????????????}??????????}??????}????????????private?void?setChildFrame(View?child,?int?left,?int?top,?int?width,?int?height)?{????????????????????child.layout(left,?top,?left?+?width,?top?+?height);??????}??????...??}?????
? ? ? 對一個View進行measure操作地主要目的就是為了確定該View地布局大小,見上面所示代碼。但measure操作
?通常是耗時的,因此對自定義ViewGroup而言,我們可以自由控制measure、layout過程,如果我們知道如何layout
?一個View,我們可以跳過該ViewGroup地measure操作(onMeasure()方法中measure所有子View地),直接去layout
? ? ? 在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明>>中,我們自定義了一個 ? ? ViewGroup, ?并且重寫了onMeasure()和onLayout()方法去分別操作每個View。就該ViewGroup而言,我們只需要
??重寫onLayout()操作即可,因為我們知道如何layout每個子View。如下代碼所示:
[java] view plaincopyprint?
??public?class?MultiViewGroup?extends?ViewGroup?{??????private?void?init()?{????????????????????LinearLayout?oneLL?=?new?LinearLayout(mContext);??????????oneLL.setBackgroundColor(Color.RED);??????????addView(oneLL);??????????...??????}??????@Override??????????????????????????????????}??????????????@Override??????protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{????????????????????Log.i(TAG,?"---?start?onLayout?--");??????????int?startLeft?=?0;???????????int?startTop?=?10;???????????int?childCount?=?getChildCount();??????????Log.i(TAG,?"---?onLayout?childCount?is?-->"?+?childCount);??????????for?(int?i?=?0;?i?<?childCount;?i++)?{??????????????View?child?=?getChildAt(i);??????????????child.layout(startLeft,?startTop,???????????????????????startLeft?+?MultiScreenActivity.screenWidth,???????????????????????startTop?+?MultiScreenActivity.scrrenHeight);??????????????startLeft?=?startLeft?+?MultiScreenActivity.screenWidth?;?????????????????????????}??????}??}????
? ? ?更多關于自定義ViewGroup無須重寫measure動作的,可以參考 Android API :
? ? ? ? ? ? ? ?<<Optimizing the View?>>
? ? ?中文翻譯見于:<<?Android中View繪制優化之三---- 優化View>>
?3、root View被添加至窗口時,UI框架是如何設置其LayoutParams值
? ? ? ? ?老子道德經有言:“道生一,一生二,二生三,三生萬物。” ?UI繪制也就是個遞歸過程。理解其基本架構后,
?也就“掌握了一個中心點”了。在第一節中,我們沒有說明開始UI繪制時 ,沒有說明mView.measure()參數地由來,
?參數也就是我們本節需要弄懂的“道” --- root View的 widthMeasureSpec和heightMeasureSpec?是如何確定的。
? ?對于如下布局文件: main.xml
[java] view plaincopyprint?
<?xml?version="1.0"?encoding="utf-8"?>??<LinearLayout?xmlns:android="http://schemas.android.com/apk/res/android"??????android:orientation="vertical"??????android:layout_width="fill_parent"??????android:layout_height="fill_parent"??????>??<TextView????????android:layout_width="fill_parent"???????android:layout_height="wrap_content"???????android:text="@string/hello"??????/>??</LinearLayout>?? ??
? ? 當使用LayoutInflater類解析成View時 ,LinearLayout對象的LayoutParams參數為null 。具體原因請參考上篇博文
? ? 任何一個View被添加至窗口時,都需要利用WindowManager類去操作。例如,如下代碼:
? [java] view plaincopyprint?
??public?void?showView()??{????????????LayoutInflater?layoutInflater?=?(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);????????????View?rootView?=?layoutInflater.inflate(R.layout.main,?null);????????????WindowManager?windowManager?=?(WindowManager)getSystemService(Context.WINDOW_SERVICE);????????????WindowManager.LayoutParams?winparams?=?WindowManager.LayoutParams();?????????????winparams.x?=?0;??????winparams.y?=?0;??????????????winparams.width?=?WindowManager.LayoutParams.WRAP_CONTENT;;??????winparams.height?=?WindowManager.LayoutParams.WRAP_CONTENT;;?????????????windowManager.addView(rootView,?winparams);??}??
[java] view plaincopyprint?
??
? ? ???關于WindowManager的使用請看如下博客 :
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?<<android學習---- WindowManager 接口?>>
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <<在Android中使用WindowManager實現懸浮窗口>>
? ? ??關于WindowManager.LayoutParams類說明請看如下博客:?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <<?android學習---- WindowManager.LayoutParams>>
? ? ? ?下面,我們從獲得WindowManager對象引用開始,一步步觀察addView()做了一些什么事情。
? ?Step 1 、獲得WindowManager對象服務 ,具體實現類在ContextImpl.java內中
? ? ? ? ? ?路徑:?/frameworks/base/core/java/android/app/ContextImpl.java ? ? ? ??
[java] view plaincopyprint?
@Override??public?Object?getSystemService(String?name)?{??????if?(WINDOW_SERVICE.equals(name))?{??????????return?WindowManagerImpl.getDefault();??????}??????...??}?? ? ? ? ??
WindowManager是個接口,具體返回對象則是WindowManagerImpl的單例對象。
?Step 2 、?獲得WindowManagerImpl的單例對象,以及部分源碼分析
? ? ? ? ? ?路徑:?/frameworks/base/core/java/android/view/WindowManagerImpl.java?
[java] view plaincopyprint?
public?class?WindowManagerImpl?implements?WindowManager{??????????????public?static?WindowManagerImpl?getDefault()?????{?????????return?mWindowManager;?????}??????????public?void?addView(View?view,?ViewGroup.LayoutParams?params)?????{?????????addView(view,?params,?false);?????}??????????private?void?addView(View?view,?ViewGroup.LayoutParams?params,?boolean?nest)?????{???...?????????final?WindowManager.LayoutParams?wparams?=?(WindowManager.LayoutParams)params;??????????????????ViewRoot?root;?????????View?panelParentView?=?null;??????????????????????synchronized?(this)?{?????????????????????????...???????????????????????????????????????root?=?new?ViewRoot(view.getContext());?????????????root.mAddNesting?=?1;??????????????????????????view.setLayoutParams(wparams);?????????????...???????????????????????????????????????mViews[index]?=?view;?????????????mRoots[index]?=?root;?????????????mParams[index]?=?wparams;?????????}???????????????????????????root.setView(view,?wparams,?panelParentView);?????}?????...??????????private?View[]?mViews;??????????????private?ViewRoot[]?mRoots;??????????private?WindowManager.LayoutParams[]?mParams;?????????????????private?static?WindowManagerImpl?mWindowManager?=?new?WindowManagerImpl();??}?? ? ?
??WindowManagerImpl類的三個數組集合保存了每個窗口相關屬性,這樣我們可以通過這些屬性去操作特定的
?窗口(例如,可以根據View去更新/銷毀該窗口)。當參數檢查成功時,構建一個ViewRoot對象,并且設置設置root
?View 的LayoutParams為wparams,即WindowManager.LayoutParams類型。最后調用root.setView()方法去通知
?系統需要創建該窗口。我們接下來往下看看ViewRoot類相關操作。
??
? ??Step 3、
? ? [java] view plaincopyprint?
public?final?class?ViewRoot?extends?Handler?implements?ViewParent,View.AttachInfo.Callbacks?{???????????????View?mView;?????????final?WindowManager.LayoutParams?mWindowAttributes?=?new?WindowManager.LayoutParams();??????????...???????????????public?void?setView(View?view,?WindowManager.LayoutParams?attrs,??????????????View?panelParentView)?{??????????synchronized?(this)?{??????????????if?(mView?==?null)?{??????????????????mView?=?view;??????????????????mWindowAttributes.copyFrom(attrs);???????????????????attrs?=?mWindowAttributes;??????????????????...????????????????????????????????????mAdded?=?true;??????????????????int?res;???????????????????????????????????????????????????????????????????????????requestLayout();?????????????????????mInputChannel?=?new?InputChannel();????????????????????try?{????????????????????????????????????????????res?=?sWindowSession.add(mWindow,?mWindowAttributes,??????????????????????????????getHostVisibility(),?mAttachInfo.mContentInsets,??????????????????????????????mInputChannel);??????????????????}???????????????????...??????????????????view.assignParent(this);????????????????????...??????????????}??????????}??????}??}?? ? ? ?
? ? ? 說明:ViewRoot類繼承了Handler,實現了ViewParent接口
? setView()方法地主要功能如下:
? ? ? ? 1、保存相關屬性值,例如:mView、mWindowAttributes等;
? ? ? ? 2、調用requestLayout()方法請求UI繪制,由于ViewRoot是個Handler對象,異步請求;
? ? ? ? 3、通知WindowManagerService添加一個窗口;
? ? ? ? 4、注冊一個事件監聽管道,用來監聽:按鍵(KeyEvent)和觸摸(MotionEvent)事件。
??我們這兒重點關注 requestLayout()方法請求UI繪制地流程。
??Step 4、異步調用請求UI繪制
? ? [java] view plaincopyprint?
????public?void?requestLayout()?{??????checkThread();??????????????mLayoutRequested?=?true;?????????scheduleTraversals();????}????public?void?scheduleTraversals()?{??????if?(!mTraversalScheduled)?{??????????mTraversalScheduled?=?true;?????????????????sendEmptyMessage(DO_TRAVERSAL);?????????}??}??@Override??public?void?handleMessage(Message?msg)?{???switch?(msg.what)?{??????????case?DO_TRAVERSAL:???????????????performTraversals();?????????????????break;???}??}?? ? ?
? ? ? ? ? 由于performTraversals()方法比較復雜,我們側重于第一次設置root View的widhtSpecSize以及 ? ?
? heightSpecSize值。
[java] view plaincopyprint?
private?void?performTraversals()?{????????????final?View?host?=?mView;????????mTraversalScheduled?=?false;???????????????boolean?surfaceChanged?=?false;??????WindowManager.LayoutParams?lp?=?mWindowAttributes;??????????int?desiredWindowWidth;????????????????????int?desiredWindowHeight;???????????????????int?childWidthMeasureSpec;?????????????????int?childHeightMeasureSpec;??????????????????final?View.AttachInfo?attachInfo?=?mAttachInfo;????????final?int?viewVisibility?=?getHostVisibility();??????boolean?viewVisibilityChanged?=?mViewVisibility?!=?viewVisibility??????????????||?mNewSurfaceNeeded;????????float?appScale?=?mAttachInfo.mApplicationScale;????????WindowManager.LayoutParams?params?=?null;??????if?(mWindowAttributesChanged)?{??????????mWindowAttributesChanged?=?false;??????????surfaceChanged?=?true;??????????params?=?lp;??????}??????Rect?frame?=?mWinFrame;??????if?(mFirst)?{?????????????fullRedrawNeeded?=?true;??????????mLayoutRequested?=?true;????????????DisplayMetrics?packageMetrics?=??????????????mView.getContext().getResources().getDisplayMetrics();????????????????????desiredWindowWidth?=?packageMetrics.widthPixels;??????????desiredWindowHeight?=?packageMetrics.heightPixels;??????????...??????}?else?{?????????????desiredWindowWidth?=?frame.width();??????????desiredWindowHeight?=?frame.height();??????????...??????}??????...??????boolean?insetsChanged?=?false;????????if?(mLayoutRequested)?{??????????...??????????childWidthMeasureSpec?=?getRootMeasureSpec(desiredWindowWidth,?lp.width);??????????childHeightMeasureSpec?=?getRootMeasureSpec(desiredWindowHeight,?lp.height);????????????????????host.measure(childWidthMeasureSpec,?childHeightMeasureSpec);??????}??????...??????final?boolean?didLayout?=?mLayoutRequested;????????????boolean?triggerGlobalLayoutListener?=?didLayout??????????????||?attachInfo.mRecomputeGlobalAttributes;??????if?(didLayout)?{??????????...??????????host.layout(0,?0,?host.mMeasuredWidth,?host.mMeasuredHeight);??????????...??????}??????...??????if?(!cancelDraw?&&?!newSurface)?{??????????mFullRedrawNeeded?=?false;??????????draw(fullRedrawNeeded);??????????...??}?? [java] view plaincopyprint?
???????private?int?getRootMeasureSpec(int?windowSize,?int?rootDimension)?{???????int?measureSpec;???????switch?(rootDimension)?{???????case?ViewGroup.LayoutParams.MATCH_PARENT:??????????????????????measureSpec?=?MeasureSpec.makeMeasureSpec(windowSize,?MeasureSpec.EXACTLY);???????????break;???????case?ViewGroup.LayoutParams.WRAP_CONTENT:??????????????????????measureSpec?=?MeasureSpec.makeMeasureSpec(windowSize,?MeasureSpec.AT_MOST);???????????break;???????default:??????????????????????measureSpec?=?MeasureSpec.makeMeasureSpec(rootDimension,?MeasureSpec.EXACTLY);???????????break;???????}???????return?measureSpec;???}?????????
? ? ? 調用root View的measure()方法時,其參數是由getRootMeasureSpec()設置的,基本思路同我們前面描述的
? 差不多。貼出來的代碼只是簡簡單單列出了measure 、layout 、 draw 過程的調用點,里面有很多邏輯處理,
? 閱讀起來比較費勁,我也只能算是個囫圇吞棗水平。大家有興趣地可以看看源碼,加深理解。
? ??
? ? 最后,由于小子理解水平有限,可能很多地方讓大家“丈二和尚--摸不著頭腦”,給大家兩個小建議吧:
? ? ? ? ? ? 1、仔細鉆研源碼 ?;
? ? ? ? ? ? 2、想認真系統性研讀UI繪制原理的話,建議詳細閱讀<<Android內核剖析>>第十三章 <UI繪制原理>
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。