? ? ? ??? 本文原創(chuàng), 轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/qinjuning
? ? ? ? 上篇文章<<Android中measure過程、WRAP_CONTENT詳解以及xml布局文件解析流程淺析(上)>>中,我們
? 了解了View樹的轉(zhuǎn)換過程以及如何設(shè)置View的LayoutParams的。本文繼續(xù)沿著既定軌跡繼續(xù)未完成的job。
? ? ? ? 主要知識(shí)點(diǎn)如下:
? ? ? ? ? ? ? ? ?1、MeasureSpc類說明
? ? ? ? ? ? ? ? ?2、measure過程詳解(揭秘其細(xì)節(jié));
? ? ? ? ? ? ? ? ?3、root View被添加至窗口時(shí),UI框架是如何設(shè)置其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)要求。每個(gè)MeasureSpc實(shí)例代表寬度或者高度
? ?(只能是其一)要求。?它有三種模式:
? ? ? ? ? ? ①、UNSPECIFIED(未指定),父元素部隊(duì)自元素施加任何束縛,子元素可以得到任意想要的大小;
? ? ? ? ? ? ②、EXACTLY(完全),父元素決定自元素的確切大小,子元素將被限定在給定的邊界里而忽略它本身大小;
? ? ? ? ? ? ③、AT_MOST(至多),子元素至多達(dá)到指定大小的值。
? ?常用的三個(gè)函數(shù):
static int getMode(int measureSpec) ?: ?根據(jù)提供的測(cè)量值(格式)提取模式(上述三個(gè)模式之一)
? ? ?static int getSize(int measureSpec) ?: 根據(jù)提供的測(cè)量值(格式)提取大小值(這個(gè)大小也就是我們通常所說的大小)
? ? ?static int makeMeasureSpec(int size,int mode) ?: ?根據(jù)提供的大小值和模式創(chuàng)建一個(gè)測(cè)量值(格式)
? ? ? ? ? ? ?以上摘取自: ?<<MeasureSpec介紹及使用詳解>>
? ?1.2 ? MeasureSpc類源碼分析???其為View.java類的內(nèi)部類,路徑:\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類的處理思路是:
? ? ? ①、右移運(yùn)算,使int 類型的高兩位表示模式的實(shí)際值,其余30位表示其余30位代表長(zhǎng)或?qū)挼膶?shí)際值----可以是
? ? ? ? ?WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。
? ? ? ②、通過掩碼MODE_MASK進(jìn)行與運(yùn)算 “&”,取得模式(mode)以及長(zhǎng)或?qū)?value)的實(shí)際值。
?2、measure過程詳解
?
? ?2.1 ?measure過程深入分析
? ? ? ?之前的一篇博文<<?Android中View繪制流程以及invalidate()等相關(guān)方法分析>>,我們從”二B程序員”的角度簡(jiǎn)單 ? ?解了measure過程的調(diào)用過程。過了這么多,我們也該升級(jí)了,- - 。現(xiàn)在請(qǐng)開始從”普通程序員”角度去理解這個(gè)
?過程。我們重點(diǎn)查看measure過程中地相關(guān)方法。
? ? ?我們說過,當(dāng)UI框架開始繪制時(shí),皆是從ViewRoot.java類開始繪制的。
? ? ? ViewRoot類簡(jiǎn)要說明: 任何顯示在設(shè)備中的窗口,例如:Activity、Dialog等,都包含一個(gè)ViewRoot實(shí)例,該
? 類主要用來與遠(yuǎn)端 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類的來由(為了避免額外地開銷,等到
第三部分時(shí)我們?cè)趤砉タ怂?#xff0c;現(xiàn)在只需要記住其值MeasureSpec.makeMeasureSpec()構(gòu)建的。
? ? Step 2 、調(diào)用measure()方法去做一些前期準(zhǔn)備
? ? ? ?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;???????}??????...??}??
? ? ??參數(shù)widthMeasureSpec和heightMeasureSpec?由父View構(gòu)建,表示父View給子View的測(cè)量要求。其值地構(gòu)建
?會(huì)在下面步驟中詳解。 ?
? ?measure()方法顯示判斷是否需要重新調(diào)用設(shè)置改View大小,即調(diào)用onMeasure()方法,然后操作兩個(gè)標(biāo)識(shí)符:
? ? ? ? ? ??①、重置MEASURED_DIMENSION_SET? ?: onMeasure()方法中,需要添加該標(biāo)識(shí)符,否則,會(huì)報(bào)異常; ? ?
? ? ? ?②、添加LAYOUT_REQUIRED : 表示需要進(jìn)行l(wèi)ayout操作。
? ? 最后,保存當(dāng)前的widthMeasureSpec和heightMeasureSpec值。
?? Step 3 、調(diào)用onMeasure()方法去真正設(shè)置View的長(zhǎng)寬值,其默認(rèn)實(shí)現(xiàn)為:
[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;??????}??
? ? ? ?主要功能就是根據(jù)該View屬性(android:minWidth和背景圖片大小)和父View對(duì)該子View的"測(cè)量要求",設(shè)置該 ? ? ?View的?mMeasuredWidth 和?mMeasuredHeight 值。
? ? ? ?這兒只是一般的View類型地實(shí)現(xiàn)方法。一般來說,父View,也就是ViewGroup類型,都需要在重寫onMeasure() ? 方法,遍歷所有子View,設(shè)置每個(gè)子View的大小。基本思想如下:遍歷所有子View,設(shè)置每個(gè)子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是如何設(shè)定其值的?
??
? ? ? 要想回答這個(gè)問題,我們看是去源代碼里找找答案吧。在ViewGroup.java類中,為我們提供了三個(gè)方法,去設(shè)置
每個(gè)子View的大小,基本思想也如同我們之前描述的思想:遍歷所有子View,設(shè)置每個(gè)子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,調(diào)用measureChild()方法去設(shè)置該子View的屬性值。
? ? ?measureChild() ?方法 ? : 獲取特定子View的widthMeasureSpec、heightMeasureSpec,調(diào)用measure()方法
?設(shè)置子View的實(shí)際寬高值。
? ??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);??}??
? ? ? ?為了便于分析,我將上面的邏輯判斷語句使用列表項(xiàng)進(jìn)行了說明.
? getChildMeasureSpec()方法的主要功能如下:
? ? ? ??根據(jù)父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View內(nèi)部
??LayoutParams屬性值,共同決定子View的measureSpec值的大小。主要判斷條件主要為MeasureSpec的mode
?類型以及LayoutParams的寬高實(shí)際值(lp.width,lp.height),見于以上所貼代碼中的列表項(xiàng): 1、 1.1 ; 1.2 ; 1.3 ;?
? 2、2.1等。
? ? ? ? 例如,分析列表3:假設(shè)當(dāng)父View為MeasureSpec.UNSPECIFIED類型,即未定義時(shí),只有當(dāng)子View的width
?或height指定時(shí),其mode才為MeasureSpec.EXACTLY,否者該View size為 0 ,mode為MeasureSpec.UNSPECIFIED時(shí)
?,即處于未指定狀態(tài)。
? ? ? 由此可以得出, 每個(gè)View大小的設(shè)定都事由其父View以及該View共同決定的。但這只是一個(gè)期望的大小,每個(gè)
?View在測(cè)量時(shí)最終大小的設(shè)定是由setMeasuredDimension()最終決定的。因此,最終確定一個(gè)View的“測(cè)量長(zhǎng)寬“是
?由以下幾個(gè)方面影響:
? ? ? ? 1、父View的MeasureSpec屬性;
? ? ? ? 2、子View的LayoutParams屬性 ;
? ? ? ? 3、setMeasuredDimension()或者其它類似設(shè)定?mMeasuredWidth 和?mMeasuredHeight 值的方法。
? ? ? ? ? ? ? ??setMeasuredDimension()原型:
[java] view plaincopyprint?
??protected?final?void?setMeasuredDimension(int?measuredWidth,?int?measuredHeight)?{??????mMeasuredWidth?=?measuredWidth;??????mMeasuredHeight?=?measuredHeight;????????mPrivateFlags?|=?MEASURED_DIMENSION_SET;????}??
? 將上面列表項(xiàng)轉(zhuǎn)換為表格為:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ?這張表格更能幫助我們分析View的MeasureSpec的確定條件關(guān)系。
? ?為了幫助大家理解,下面我們分析某個(gè)窗口使用地xml布局文件,我們弄清楚該xml布局文件中每個(gè)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>?????
? ? ?該布局文件共有兩個(gè)View: ?①、id為llayout的LinearLayout布局控件 ;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?②、id為tv的TextView控件。
? ? ? 假設(shè)LinearLayout的父View對(duì)應(yīng)地widthSpec和heightSpec值皆為MeasureSpec.EXACTLY類型(Activity窗口
? 的父View為DecorView,具體原因見第三部分說明)。
? ? ? ?對(duì)LinearLayout而言比較簡(jiǎn)單,由于 android:layout_width="match_parent",因此其width對(duì)應(yīng)地widthSpec?
? mode值為MeasureSpec.EXACTLY ,?size由父視圖大小指定 ;??由于android:layout_height = "match_parent",
? 因此其height對(duì)應(yīng)地heightSpec mode值為MeasureSpec.EXACTLY,size由父視圖大小指定 ;
? ? ? ?對(duì)TextView而言 ,其父View為L(zhǎng)inearLayout的widthSpec和heightSpec值皆為MeasureSpec.EXACTLY類型,
?由于android:layout_width="match_parent" , 因此其width對(duì)應(yīng)地widthSpec mode值為MeasureSpec.EXACTLY,
?size由父視圖大小指定 ; ?由于android:layout_width="wrap_content" , 因此其height對(duì)應(yīng)地widthSpec mode值為
?MeasureSpec.AT_MOST,size由父視圖大小指定 。
? ? 我們繼續(xù)窺測(cè)下LinearLayout類是如何進(jìn)行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);?????}??...??
? ? ? ? ??
? ? ? ? 繼續(xù)看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內(nèi),基本流程同于measureChild()方法,但添加了對(duì)子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()過程時(shí),LinearLayout類做了如下事情 :
? ? ? ? ? ? 1、遍歷每個(gè)子View,對(duì)其調(diào)用measure()方法;
? ? ? ? ? ? 2、子View?measure()完成后,需要取得該子View地寬高實(shí)際值,繼而做處理(例如:LinearLayout屬性為
? ? ? ?android:widht="wrap_content"時(shí),LinearLayout的實(shí)際width值則是每個(gè)子View的width值的累加值)。
? ? ?
??2.2 WRAP_CONTENT、MATCH_PARENT以及measure動(dòng)機(jī)揭秘
? ? ? ? 子View地寬高實(shí)際值 ,即child.getMeasuredWidth()值得返回最終會(huì)是一個(gè)確定值?? 難道WRAP_CONTENT(
其值為-2)?、MATCH_PARENT(值為-1)或者說一個(gè)具體值(an exactly size > 0)。前面我們說過,View最終“測(cè)量”值的
確定是有三個(gè)部分組成地:
? ? ? ? ?①、父View的MeasureSpec屬性;
? ? ? ? ?②、子View的LayoutParams屬性 ;
? ? ? ? ?③、setMeasuredDimension()或者其它類似設(shè)定?mMeasuredWidth 和?mMeasuredHeight 值的方法。
? ?因此,一個(gè)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()方法,根據(jù)傳遞過來的widthMeasureSpec和heightMeasureSpec簡(jiǎn)單設(shè)置了
?該View的mMeasuredWidth 和?mMeasuredHeight值。
? ? ? 對(duì)于TextView而言,如果它地mode不是Exactly類型 , 它會(huì)根據(jù)一些屬性,例如:android:textStyle
? 、android:textSizeandroid:typeface等去確定TextView類地需要占用地長(zhǎng)和寬。
? ?
? ? ?因此,如果你地自定義View必須手動(dòng)對(duì)不同mode做出處理。否則,則是mode對(duì)你而言是無效的。
? ?
? ? ? Android框架中提供地一系列View/ViewGroup都需要去進(jìn)行這個(gè)measure()過程地 ,因?yàn)樵趌ayout()過程中,父
? View需要調(diào)用getMeasuredWidth()或getMeasuredHeight()去為每個(gè)子View設(shè)置他們地布局坐標(biāo),只有確定布局
? 坐標(biāo)后,才能真正地將該View 繪制(draw)出來,否則該View的layout大小為0,得不到期望效果。我們繼續(xù)看看
? 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);??????}??????...??}?????
? ? ? 對(duì)一個(gè)View進(jìn)行measure操作地主要目的就是為了確定該View地布局大小,見上面所示代碼。但measure操作
?通常是耗時(shí)的,因此對(duì)自定義ViewGroup而言,我們可以自由控制measure、layout過程,如果我們知道如何layout
?一個(gè)View,我們可以跳過該ViewGroup地measure操作(onMeasure()方法中measure所有子View地),直接去layout
? ? ? 在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明>>中,我們自定義了一個(gè) ? ? ViewGroup, ?并且重寫了onMeasure()和onLayout()方法去分別操作每個(gè)View。就該ViewGroup而言,我們只需要
??重寫onLayout()操作即可,因?yàn)槲覀冎廊绾蝜ayout每個(gè)子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?;?????????????????????????}??????}??}????
? ? ?更多關(guān)于自定義ViewGroup無須重寫measure動(dòng)作的,可以參考 Android API :
? ? ? ? ? ? ? ?<<Optimizing the View?>>
? ? ?中文翻譯見于:<<?Android中View繪制優(yōu)化之三---- 優(yōu)化View>>
?3、root View被添加至窗口時(shí),UI框架是如何設(shè)置其LayoutParams值
? ? ? ? ?老子道德經(jīng)有言:“道生一,一生二,二生三,三生萬物。” ?UI繪制也就是個(gè)遞歸過程。理解其基本架構(gòu)后,
?也就“掌握了一個(gè)中心點(diǎn)”了。在第一節(jié)中,我們沒有說明開始UI繪制時(shí) ,沒有說明mView.measure()參數(shù)地由來,
?參數(shù)也就是我們本節(jié)需要弄懂的“道” --- root View的 widthMeasureSpec和heightMeasureSpec?是如何確定的。
? ?對(duì)于如下布局文件: 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>?? ??
? ? 當(dāng)使用LayoutInflater類解析成View時(shí) ,LinearLayout對(duì)象的LayoutParams參數(shù)為null 。具體原因請(qǐng)參考上篇博文
? ? 任何一個(gè)View被添加至窗口時(shí),都需要利用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?
??
? ? ???關(guān)于WindowManager的使用請(qǐng)看如下博客 :
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?<<android學(xué)習(xí)---- WindowManager 接口?>>
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <<在Android中使用WindowManager實(shí)現(xiàn)懸浮窗口>>
? ? ??關(guān)于WindowManager.LayoutParams類說明請(qǐng)看如下博客:?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <<?android學(xué)習(xí)---- WindowManager.LayoutParams>>
? ? ? ?下面,我們從獲得WindowManager對(duì)象引用開始,一步步觀察addView()做了一些什么事情。
? ?Step 1 、獲得WindowManager對(duì)象服務(wù) ,具體實(shí)現(xiàn)類在ContextImpl.java內(nèi)中
? ? ? ? ? ?路徑:?/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是個(gè)接口,具體返回對(duì)象則是WindowManagerImpl的單例對(duì)象。
?Step 2 、?獲得WindowManagerImpl的單例對(duì)象,以及部分源碼分析
? ? ? ? ? ?路徑:?/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類的三個(gè)數(shù)組集合保存了每個(gè)窗口相關(guān)屬性,這樣我們可以通過這些屬性去操作特定的
?窗口(例如,可以根據(jù)View去更新/銷毀該窗口)。當(dāng)參數(shù)檢查成功時(shí),構(gòu)建一個(gè)ViewRoot對(duì)象,并且設(shè)置設(shè)置root
?View 的LayoutParams為wparams,即WindowManager.LayoutParams類型。最后調(diào)用root.setView()方法去通知
?系統(tǒng)需要?jiǎng)?chuàng)建該窗口。我們接下來往下看看ViewRoot類相關(guān)操作。
??
? ??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,實(shí)現(xiàn)了ViewParent接口
? setView()方法地主要功能如下:
? ? ? ? 1、保存相關(guān)屬性值,例如:mView、mWindowAttributes等;
? ? ? ? 2、調(diào)用requestLayout()方法請(qǐng)求UI繪制,由于ViewRoot是個(gè)Handler對(duì)象,異步請(qǐng)求;
? ? ? ? 3、通知WindowManagerService添加一個(gè)窗口;
? ? ? ? 4、注冊(cè)一個(gè)事件監(jiān)聽管道,用來監(jiān)聽:按鍵(KeyEvent)和觸摸(MotionEvent)事件。
??我們這兒重點(diǎn)關(guān)注 requestLayout()方法請(qǐng)求UI繪制地流程。
??Step 4、異步調(diào)用請(qǐng)求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()方法比較復(fù)雜,我們側(cè)重于第一次設(shè)置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;???}?????????
? ? ? 調(diào)用root View的measure()方法時(shí),其參數(shù)是由getRootMeasureSpec()設(shè)置的,基本思路同我們前面描述的
? 差不多。貼出來的代碼只是簡(jiǎn)簡(jiǎn)單單列出了measure 、layout 、 draw 過程的調(diào)用點(diǎn),里面有很多邏輯處理,
? 閱讀起來比較費(fèi)勁,我也只能算是個(gè)囫圇吞棗水平。大家有興趣地可以看看源碼,加深理解。
? ??
? ? 最后,由于小子理解水平有限,可能很多地方讓大家“丈二和尚--摸不著頭腦”,給大家兩個(gè)小建議吧:
? ? ? ? ? ? 1、仔細(xì)鉆研源碼 ?;
? ? ? ? ? ? 2、想認(rèn)真系統(tǒng)性研讀UI繪制原理的話,建議詳細(xì)閱讀<<Android內(nèi)核剖析>>第十三章 <UI繪制原理>
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。