日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下)

發布時間:2025/6/15 Android 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(下) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
? ? ? ??? 本文原創, 轉載請注明出處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;?//移位位數為30??
  • ????????//int類型占32位,向右移位30位,該屬性表示掩碼值,用來與size和mode進行"&"運算,獲取對應值。??
  • ????????private?static?final?int?MODE_MASK??=?0x3?<<?MODE_SHIFT;??
  • ??
  • ????????//向右移位30位,其值為00?+?(30位0)??,?即?0x0000(16進制表示)??
  • ????????public?static?final?int?UNSPECIFIED?=?0?<<?MODE_SHIFT;??
  • ????????//向右移位30位,其值為01?+?(30位0)??,?即0x1000(16進制表示)??
  • ????????public?static?final?int?EXACTLY?????=?1?<<?MODE_SHIFT;??
  • ????????//向右移位30位,其值為02?+?(30位0)??,?即0x2000(16進制表示)??
  • ????????public?static?final?int?AT_MOST?????=?2?<<?MODE_SHIFT;??
  • ??
  • ????????//創建一個整形值,其高兩位代表mode類型,其余30位代表長或寬的實際值。可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly?size??
  • ????????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?{??
  • ????...??
  • ????//mView對象指添加至窗口的root?View?,對Activity窗口而言,則是DecorView對象。??
  • ????View?mView;??????
  • ??????
  • ????//開始View繪制流程??
  • ????private?void?performTraversals(){??
  • ????????...??
  • ????????//這兩個值我們在后面討論時,在回過頭來看看是怎么賦值的。現在只需要記住其值MeasureSpec.makeMeasureSpec()構建的。??
  • ????????int?childWidthMeasureSpec;?//其值由MeasureSpec類構建?,?makeMeasureSpec??
  • ????????int?childHeightMeasureSpec;//其值由MeasureSpec類構建?,?makeMeasureSpec??
  • ??????????
  • ??
  • ????????//?Ask?host?how?big?it?wants?to?be??
  • ????????host.measure(childWidthMeasureSpec,?childHeightMeasureSpec);??
  • ????????...??
  • ????}??
  • ????...??
  • }??

  • 這兒,我并沒有說出childWidthMeasureSpec和childHeightMeasureSpec類的來由(為了避免額外地開銷,等到 第三部分時我們在來攻克它,現在只需要記住其值MeasureSpec.makeMeasureSpec()構建的。

    ? ? Step 2 、調用measure()方法去做一些前期準備

    ? ? ? ?measure()方法原型定義在View.java類中,final修飾符修飾,其不能被重載:

    ? ??

    [java] view plaincopyprint?
  • public?class?View?implements?...?{??
  • ????...??
  • ????/**?
  • ?????*?This?is?called?to?find?out?how?big?a?view?should?be.?The?parent?
  • ?????*?supplies?constraint?information?in?the?width?and?height?parameters.?
  • ?????*?
  • ?????*?@param?widthMeasureSpec?Horizontal?space?requirements?as?imposed?by?the?
  • ?????*????????parent?
  • ?????*?@param?heightMeasureSpec?Vertical?space?requirements?as?imposed?by?the?
  • ?????*????????parent?
  • ?????*?@see?#onMeasure(int,?int)?
  • ?????*/??
  • ????public?final?void?measure(int?widthMeasureSpec,?int?heightMeasureSpec)?{??
  • ????????//判斷是否為強制布局,即帶有“FORCE_LAYOUT”標記?以及?widthMeasureSpec或heightMeasureSpec發生了改變??
  • ????????if?((mPrivateFlags?&?FORCE_LAYOUT)?==?FORCE_LAYOUT?||??
  • ????????????????widthMeasureSpec?!=?mOldWidthMeasureSpec?||??
  • ????????????????heightMeasureSpec?!=?mOldHeightMeasureSpec)?{??
  • ??
  • ????????????//?first?clears?the?measured?dimension?flag??
  • ????????????//清除MEASURED_DIMENSION_SET標記???,該標記會在onMeasure()方法后被設置??
  • ????????????mPrivateFlags?&=?~MEASURED_DIMENSION_SET;???
  • ??
  • ????????????//?measure?ourselves,?this?should?set?the?measured?dimension?flag?back??
  • ????????????//?1、?測量該View本身的大小?;?2?、?設置MEASURED_DIMENSION_SET標記,否則接寫來會報異常。??
  • ????????????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;??//下一步是layout了,添加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?
  • /**?
  • ???*?Measure?the?view?and?its?content?to?determine?the?measured?width?and?the?
  • ???*?measured?height.?This?method?is?invoked?by?{@link?#measure(int,?int)}?and?
  • ???*?should?be?overriden?by?subclasses?to?provide?accurate?and?efficient?
  • ???*?measurement?of?their?contents.?
  • ???*??
  • ???*?@param?widthMeasureSpec?horizontal?space?requirements?as?imposed?by?the?parent.?
  • ???*?????????????????????????The?requirements?are?encoded?with?
  • ???*?@param?heightMeasureSpec?vertical?space?requirements?as?imposed?by?the?parent.?
  • ???*?????????????????????????The?requirements?are?encoded?with?
  • ???*/??
  • ??//設置該View本身地大小??
  • ??protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{??
  • ??????setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),?widthMeasureSpec),??
  • ??????????????getDefaultSize(getSuggestedMinimumHeight(),?heightMeasureSpec));??
  • ??}??
  • ????
  • ??/**?
  • ???*?Utility?to?return?a?default?size.?Uses?the?supplied?size?if?the?
  • ???*?MeasureSpec?imposed?no?contraints.?Will?get?larger?if?allowed?
  • ???*?by?the?MeasureSpec.?
  • ???*?
  • ???*?@param?size?Default?size?for?this?view?
  • ???*?@param?measureSpec?Constraints?imposed?by?the?parent?
  • ???*?@return?The?size?this?view?should?be.?
  • ???*/??
  • ??//@param?size參數一般表示設置了android:minHeight屬性或者該View背景圖片的大小值??
  • ??public?static?int?getDefaultSize(int?size,?int?measureSpec)?{??
  • ??????int?result?=?size;????
  • ??????int?specMode?=?MeasureSpec.getMode(measureSpec);??
  • ??????int?specSize?=??MeasureSpec.getSize(measureSpec);??
  • ??
  • ??????//根據不同的mode值,取得寬和高的實際值。??
  • ??????switch?(specMode)?{??
  • ??????case?MeasureSpec.UNSPECIFIED:??//表示該View的大小父視圖未定,設置為默認值??
  • ??????????result?=?size;??
  • ??????????break;??
  • ??????case?MeasureSpec.AT_MOST:??????//表示該View的大小由父視圖指定了??
  • ??????case?MeasureSpec.EXACTLY:??
  • ??????????result?=?specSize;??
  • ??????????break;??
  • ??????}??
  • ??????return?result;??
  • ??}??
  • ??//獲得設置了android:minHeight屬性或者該View背景圖片的大小值,?最為該View的參考值??
  • ??protected?int?getSuggestedMinimumWidth()?{??
  • ??????int?suggestedMinWidth?=?mMinWidth;??//??android:minHeight??
  • ??
  • ??????if?(mBGDrawable?!=?null)?{?//?背景圖片對應地Width。??
  • ??????????final?int?bgMinWidth?=?mBGDrawable.getMinimumWidth();??
  • ??????????if?(suggestedMinWidth?<?bgMinWidth)?{??
  • ??????????????suggestedMinWidth?=?bgMinWidth;??
  • ??????????}??
  • ??????}??
  • ??
  • ??????return?suggestedMinWidth;??
  • ??}??
  • ??//設置View在measure過程中寬和高??
  • ??protected?final?void?setMeasuredDimension(int?measuredWidth,?int?measuredHeight)?{??
  • ??????mMeasuredWidth?=?measuredWidth;??
  • ??????mMeasuredHeight?=?measuredHeight;??
  • ??
  • ??????mPrivateFlags?|=?MEASURED_DIMENSION_SET;??//設置了MEASURED_DIMENSION_SET標記??
  • ??}??

  • ? ? ? ?主要功能就是根據該View屬性(android:minWidth和背景圖片大小)和父View對該子View的"測量要求",設置該 ? ? ?View的?mMeasuredWidth 和?mMeasuredHeight 值。


    ? ? ? ?這兒只是一般的View類型地實現方法。一般來說,父View,也就是ViewGroup類型,都需要在重寫onMeasure() ? 方法,遍歷所有子View,設置每個子View的大小。基本思想如下:遍歷所有子View,設置每個子View的大小。偽

    ? 代碼表示為:

    [java] view plaincopyprint?
  • //某個ViewGroup類型的視圖??
  • protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{??
  • ??//必須調用super.ononMeasure()或者直接調用setMeasuredDimension()方法設置該View大小,否則會報異常。??
  • ??super.onMeasure(widthMeasureSpec?,?heightMeasureSpec)??
  • ?????//setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),?widthMeasureSpec),??
  • ?????//????????getDefaultSize(getSuggestedMinimumHeight(),?heightMeasureSpec));??
  • ???????
  • ??//遍歷每個子View??
  • ??for(int?i?=?0?;?i?<?getChildCount()?;?i++){??
  • ????View?child?=?getChildAt(i);??
  • ????//調用子View的onMeasure,設置他們的大小。childWidthMeasureSpec?,?childHeightMeasureSpec????
  • ????child.onMeasure(childWidthMeasureSpec,?childHeightMeasureSpec);??
  • ??}??
  • }??


  • ? ? ??Step 2、Step 3?代碼也比較好理解,但問題是我們示例代碼中widthMeasureSpec、heightMeasureSpec是如何

    ?確定的呢?父View是如何設定其值的?

    ??

    ? ? ? 要想回答這個問題,我們看是去源代碼里找找答案吧。在ViewGroup.java類中,為我們提供了三個方法,去設置

    個子View的大小,基本思想也如同我們之前描述的思想:遍歷所有子View,設置每個子View的大小。

    ? ? ?主要有如下方法:

    [java] view plaincopyprint?
  • /**?
  • ?*?Ask?all?of?the?children?of?this?view?to?measure?themselves,?taking?into?
  • ?*?account?both?the?MeasureSpec?requirements?for?this?view?and?its?padding.?
  • ?*?We?skip?children?that?are?in?the?GONE?state?The?heavy?lifting?is?done?in?
  • ?*?getChildMeasureSpec.?
  • ?*/??
  • //widthMeasureSpec?和??heightMeasureSpec?表示該父View的布局要求??
  • //遍歷每個子View,然后調用measureChild()方法去實現每個子View大小??
  • 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)?{?//?不處于?“GONE”?狀態??
  • ????????????measureChild(child,?widthMeasureSpec,?heightMeasureSpec);??
  • ????????}??
  • ????}??
  • }??
  • ?????
  • /**?
  • ?*?Ask?one?of?the?children?of?this?view?to?measure?itself,?taking?into?
  • ?*?account?both?the?MeasureSpec?requirements?for?this?view?and?its?padding.?
  • ?*?The?heavy?lifting?is?done?in?getChildMeasureSpec.?
  • ?*?
  • ?*?@param?child?The?child?to?measure?
  • ?*?@param?parentWidthMeasureSpec?The?width?requirements?for?this?view?
  • ?*?@param?parentHeightMeasureSpec?The?height?requirements?for?this?view?
  • ?*/??
  • //測量每個子View高寬時,清楚了該View本身的邊距大小,即android:padding屬性?或android:paddingLeft等屬性標記??
  • protected?void?measureChild(View?child,?int?parentWidthMeasureSpec,??
  • ????????int?parentHeightMeasureSpec)?{??
  • ????final?LayoutParams?lp?=?child.getLayoutParams();?//?LayoutParams屬性??
  • ????//設置子View的childWidthMeasureSpec屬性,去除了該父View的邊距值??mPaddingLeft?+?mPaddingRight??
  • ????final?int?childWidthMeasureSpec?=?getChildMeasureSpec(parentWidthMeasureSpec,??
  • ????????????mPaddingLeft?+?mPaddingRight,?lp.width);??
  • ????//設置子View的childHeightMeasureSpec屬性,去除了該父View的邊距值??mPaddingTop?+?mPaddingBottom??
  • ????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?
  • /**?
  • ?*?Does?the?hard?part?of?measureChildren:?figuring?out?the?MeasureSpec?to?
  • ?*?pass?to?a?particular?child.?This?method?figures?out?the?right?MeasureSpec?
  • ?*?for?one?dimension?(height?or?width)?of?one?child?view.?
  • ?*?
  • ?*?The?goal?is?to?combine?information?from?our?MeasureSpec?with?the?
  • ?*?LayoutParams?of?the?child?to?get?the?best?possible?results.?
  • ?*/??
  • //?spec參數????????????????????????????????????表示該父View本身所占的widthMeasureSpec?或??heightMeasureSpec值??
  • //?padding參數??????????????????????????表示該父View的邊距大小,見于android:padding屬性?或android:paddingLeft等屬性標記??
  • //?childDimension參數??表示該子View內部LayoutParams屬性的值,可以是wrap_content、match_parent、一個精確指(an?exactly?size),??
  • //???????????例如:由android:width指定等。??
  • public?static?int?getChildMeasureSpec(int?spec,?int?padding,?int?childDimension)?{??
  • ????int?specMode?=?MeasureSpec.getMode(spec);??//獲得父View的mode??
  • ????int?specSize?=?MeasureSpec.getSize(spec);??//獲得父View的實際值??
  • ??
  • ????int?size?=?Math.max(0,?specSize?-?padding);?//父View為子View設定的大小,減去邊距值,??
  • ??
  • ????int?resultSize?=?0;????//子View對應地?size?實際值?,由下面的邏輯條件賦值??
  • ????int?resultMode?=?0;????//子View對應地?mode?值?,?由下面的邏輯條件賦值??
  • ??
  • ????switch?(specMode)?{??
  • ????//?Parent?has?imposed?an?exact?size?on?us??
  • ????//1、父View是EXACTLY的?!??
  • ????case?MeasureSpec.EXACTLY:???
  • ????????//1.1、子View的width或height是個精確值?(an?exactly?size)??
  • ????????if?(childDimension?>=?0)?{????????????
  • ????????????resultSize?=?childDimension;?????????//size為精確值??
  • ????????????resultMode?=?MeasureSpec.EXACTLY;????//mode為?EXACTLY?。??
  • ????????}???
  • ????????//1.2、子View的width或height為?MATCH_PARENT/FILL_PARENT???
  • ????????else?if?(childDimension?==?LayoutParams.MATCH_PARENT)?{??
  • ????????????//?Child?wants?to?be?our?size.?So?be?it.??
  • ????????????resultSize?=?size;???????????????????//size為父視圖大小??
  • ????????????resultMode?=?MeasureSpec.EXACTLY;????//mode為?EXACTLY?。??
  • ????????}???
  • ????????//1.3、子View的width或height為?WRAP_CONTENT??
  • ????????else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{??
  • ????????????//?Child?wants?to?determine?its?own?size.?It?can't?be??
  • ????????????//?bigger?than?us.??
  • ????????????resultSize?=?size;???????????????????//size為父視圖大小??
  • ????????????resultMode?=?MeasureSpec.AT_MOST;????//mode為AT_MOST?。??
  • ????????}??
  • ????????break;??
  • ??
  • ????//?Parent?has?imposed?a?maximum?size?on?us??
  • ????//2、父View是AT_MOST的?!??????
  • ????case?MeasureSpec.AT_MOST:??
  • ????????//2.1、子View的width或height是個精確值?(an?exactly?size)??
  • ????????if?(childDimension?>=?0)?{??
  • ????????????//?Child?wants?a?specific?size...?so?be?it??
  • ????????????resultSize?=?childDimension;????????//size為精確值??
  • ????????????resultMode?=?MeasureSpec.EXACTLY;???//mode為?EXACTLY?。??
  • ????????}??
  • ????????//2.2、子View的width或height為?MATCH_PARENT/FILL_PARENT??
  • ????????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;??????????????????//size為父視圖大小??
  • ????????????resultMode?=?MeasureSpec.AT_MOST;???//mode為AT_MOST??
  • ????????}??
  • ????????//2.3、子View的width或height為?WRAP_CONTENT??
  • ????????else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{??
  • ????????????//?Child?wants?to?determine?its?own?size.?It?can't?be??
  • ????????????//?bigger?than?us.??
  • ????????????resultSize?=?size;??????????????????//size為父視圖大小??
  • ????????????resultMode?=?MeasureSpec.AT_MOST;???//mode為AT_MOST??
  • ????????}??
  • ????????break;??
  • ??
  • ????//?Parent?asked?to?see?how?big?we?want?to?be??
  • ????//3、父View是UNSPECIFIED的?!??
  • ????case?MeasureSpec.UNSPECIFIED:??
  • ????????//3.1、子View的width或height是個精確值?(an?exactly?size)??
  • ????????if?(childDimension?>=?0)?{??
  • ????????????//?Child?wants?a?specific?size...?let?him?have?it??
  • ????????????resultSize?=?childDimension;????????//size為精確值??
  • ????????????resultMode?=?MeasureSpec.EXACTLY;???//mode為?EXACTLY??
  • ????????}??
  • ????????//3.2、子View的width或height為?MATCH_PARENT/FILL_PARENT??
  • ????????else?if?(childDimension?==?LayoutParams.MATCH_PARENT)?{??
  • ????????????//?Child?wants?to?be?our?size...?find?out?how?big?it?should??
  • ????????????//?be??
  • ????????????resultSize?=?0;????????????????????????//size為0!?,其值未定??
  • ????????????resultMode?=?MeasureSpec.UNSPECIFIED;??//mode為?UNSPECIFIED??
  • ????????}???
  • ????????//3.3、子View的width或height為?WRAP_CONTENT??
  • ????????else?if?(childDimension?==?LayoutParams.WRAP_CONTENT)?{??
  • ????????????//?Child?wants?to?determine?its?own?size....?find?out?how??
  • ????????????//?big?it?should?be??
  • ????????????resultSize?=?0;????????????????????????//size為0!?,其值未定??
  • ????????????resultMode?=?MeasureSpec.UNSPECIFIED;??//mode為?UNSPECIFIED??
  • ????????}??
  • ????????break;??
  • ????}??
  • ????//根據上面邏輯條件獲取的mode和size構建MeasureSpec對象。??
  • ????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?
  • //設置View在measure過程中寬和高??
  • protected?final?void?setMeasuredDimension(int?measuredWidth,?int?measuredHeight)?{??
  • ????mMeasuredWidth?=?measuredWidth;??
  • ????mMeasuredHeight?=?measuredHeight;??
  • ??
  • ????mPrivateFlags?|=?MEASURED_DIMENSION_SET;??//設置了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 modeMeasureSpec.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??//onMeasure方法。??
  • protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{??
  • ????//判斷是垂直方向還是水平方向,這兒我們假設是VERTICAL垂直方向,??
  • ????if?(mOrientation?==?VERTICAL)?{??
  • ????????measureVertical(widthMeasureSpec,?heightMeasureSpec);??
  • ????}?else?{??
  • ????????measureHorizontal(widthMeasureSpec,?heightMeasureSpec);??
  • ????}??
  • }??
  • //垂直方向布局??
  • ???void?measureVertical(int?widthMeasureSpec,?int?heightMeasureSpec)?{??
  • ???????mTotalLength?=?0;?????????//該LinearLayout測量子View時的總高度。??
  • ????float?totalWeight?=?0;????//所有子View的權重和?,?android:layout_weight??
  • ????int?maxWidth?=?0;?????????//保存子View中最大width值??
  • ???????...??
  • ???????final?int?count?=?getVirtualChildCount();??//子View的個數??
  • ?????????
  • ???????final?int?widthMode?=?MeasureSpec.getMode(widthMeasureSpec);??
  • ???????final?int?heightMode?=?MeasureSpec.getMode(heightMeasureSpec);??
  • ??????????...??
  • ???????//?See?how?tall?everyone?is.?Also?remember?max?width.??
  • ???????for?(int?i?=?0;?i?<?count;?++i)?{??
  • ???????????final?View?child?=?getVirtualChildAt(i);??
  • ??????????????...??
  • ???????????LinearLayout.LayoutParams?lp?=?(LinearLayout.LayoutParams)?child.getLayoutParams();??
  • ??
  • ???????????totalWeight?+=?lp.weight;????
  • ???????????//滿足該條件地View會在該LinearLayout有剩余高度時,才真正調用measure()??
  • ???????????if?(heightMode?==?MeasureSpec.EXACTLY?&&?lp.height?==?0?&&?lp.weight?>?0)?{??
  • ???????????????...??
  • ???????????}?else?{??
  • ???????????????int?oldHeight?=?Integer.MIN_VALUE;??
  • ???????????????//如果View的hight值為0,并且設置了android:layout_weight屬性,重新糾正其height值為WRAP_CONTENT??
  • ???????????????if?(lp.height?==?0?&&?lp.weight?>?0)?{??
  • ???????????????????oldHeight?=?0;??
  • ???????????????????lp.height?=?LayoutParams.WRAP_CONTENT;??
  • ???????????????}??
  • ???????????????//?Determine?how?big?this?child?would?like?to?be.?If?this?or??
  • ???????????????//?previous?children?have?given?a?weight,?then?we?allow?it?to??
  • ???????????????//?use?all?available?space?(and?we?will?shrink?things?later??
  • ???????????????//?if?needed).??
  • ???????????????//對每個子View調用measure()方法??
  • ???????????????measureChildBeforeLayout(??
  • ??????????????????????child,?i,?widthMeasureSpec,?0,?heightMeasureSpec,??
  • ??????????????????????totalWeight?==?0???mTotalLength?:?0);??
  • ?????????????????
  • ???????????????//這三行代碼做了如下兩件事情:??
  • ???????????????//1、獲得該View的measuredHeight值,每個View都會根據他們地屬性正確設置值??>?0?;??
  • ???????????????//2、更新mTotalLength值:取當前高度mTotalLength值與mTotalLength?+?childHeight?的最大值??
  • ???????????????//?于是對于android:layout_height="wrap_height"屬性地LinearLayout控件也就知道了它的確切高度值了。??
  • ???????????????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);??
  • ???????????...??
  • ???????}??
  • ??????????//后續還有很多處理,包括繼續measure()某些符合條件地子View??
  • ???????...??
  • ???}??
  • ???void?measureChildBeforeLayout(View?child,?int?childIndex,??
  • ???????????int?widthMeasureSpec,?int?totalWidth,?int?heightMeasureSpec,??
  • ???????????int?totalHeight)?{??
  • ????//調用measureChildWithMargins()方法去設置子View大小??
  • ???????measureChildWithMargins(child,?widthMeasureSpec,?totalWidth,??
  • ???????????????heightMeasureSpec,?totalHeight);??
  • ???}??
  • ...??

  • ? ? ? ? ??

    ? ? ? ? 繼續看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內,基本流程同于measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理。

    ? ? ??measureChildWithMargins@ViewGroup.java?

    [java] view plaincopyprint?
  • /**?
  • ?*?Ask?one?of?the?children?of?this?view?to?measure?itself,?taking?into?
  • ?*?account?both?the?MeasureSpec?requirements?for?this?view?and?its?padding?
  • ?*?and?margins.?The?child?must?have?MarginLayoutParams?The?heavy?lifting?is?
  • ?*?done?in?getChildMeasureSpec.?
  • ?*/??
  • //基本流程同于measureChild()方法,但添加了對子View?Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理??
  • //widthUsed參數??表示該父View已經使用的寬度??
  • //heightUsed參數??表示該父View已經使用的高度??
  • protected?void?measureChildWithMargins(View?child,??
  • ????????int?parentWidthMeasureSpec,?int?widthUsed,??
  • ????????int?parentHeightMeasureSpec,?int?heightUsed)?{??
  • ????final?MarginLayoutParams?lp?=?(MarginLayoutParams)?child.getLayoutParams();??
  • ??
  • ????//獲得子View的childWidthMeasureSpec和childHeightMeasureSpec值??
  • ????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?
  • //自定義View?????
  • public?Class?MyView?extends?View?{??
  • ??????
  • ?????//針對不同地mode值,設置本View地大小??
  • ?????protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec){??
  • ?????????//獲得父View傳遞給我們地測量需求??
  • ?????????int?widthMode?=?MeasureSpec.getMode(widthMeasureSpec);??
  • ?????????int?heightMode?=?MeasureSpec.getMode(heightMeasureSpec);??
  • ???????????
  • ?????????int?width?=?0?;??
  • ?????????int?height?=?0?;??
  • ?????????//對UNSPECIFIED?則拋出異常??
  • ?????????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??//layout?過程??
  • ????protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{??
  • ????????//假定是垂直方向布局??
  • ????????if?(mOrientation?==?VERTICAL)?{??
  • ????????????layoutVertical();??
  • ????????}?else?{??
  • ????????????layoutHorizontal();??
  • ????????}??
  • ????}??
  • ????//對每個子View調用layout過程??
  • ????void?layoutVertical()?{??
  • ????????...??
  • ????????final?int?count?=?getVirtualChildCount();??
  • ????????...??
  • ????????for?(int?i?=?0;?i?<?count;?i++)?{??
  • ????????????final?View?child?=?getVirtualChildAt(i);??
  • ????????????if?(child?==?null)?{??//一般為非null??
  • ????????????????childTop?+=?measureNullChild(i);??
  • ????????????}?else?if?(child.getVisibility()?!=?GONE)?{??
  • ????????????????//獲得子View測量時的實際寬高值,??
  • ????????????????final?int?childWidth?=?child.getMeasuredWidth();??
  • ????????????????final?int?childHeight?=?child.getMeasuredHeight();??
  • ??????????????????
  • ????????????????...??
  • ????????????????//??封裝了child.layout()方法,見如下??
  • ????????????????setChildFrame(child,?childLeft,?childTop?+?getLocationOffset(child),??
  • ????????????????????????childWidth,?childHeight);???
  • ????????????????childTop?+=?childHeight?+?lp.bottomMargin?+?getNextLocationOffset(child);??
  • ??
  • ????????????????i?+=?getChildrenSkipCount(child,?i);??
  • ????????????}??
  • ????????}??
  • ????}??
  • ????//width?=?getMeasuredWidth()?;?height?=?childHeight();?View的大小就是測量大小??
  • ????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?
  • //自定義ViewGroup?,?包含了三個LinearLayout控件,存放在不同的布局位置??
  • public?class?MultiViewGroup?extends?ViewGroup?{??
  • ????private?void?init()?{??
  • ????????//?初始化3個?LinearLayout控件??
  • ????????LinearLayout?oneLL?=?new?LinearLayout(mContext);??
  • ????????oneLL.setBackgroundColor(Color.RED);??
  • ????????addView(oneLL);??
  • ????????...??
  • ????}??
  • ????@Override??
  • ????//?我們知曉每個子View的layout布局大小,因此我們不需要為每個子View進行measure()操作了。??
  • //??protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{??
  • //??????setMeasuredDimension(width,?height);??
  • //??????//?設置該ViewGroup的大小??
  • //??????int?width?=?MeasureSpec.getSize(widthMeasureSpec);??
  • //??????int?height?=?MeasureSpec.getSize(heightMeasureSpec);??
  • //??????int?childCount?=?getChildCount();??
  • //??????for?(int?i?=?0;?i?<?childCount;?i++)?{??
  • //??????????View?child?=?getChildAt(i);??
  • //??????????//?設置每個子視圖的大小?,?即全屏??
  • //??????????child.measure(MultiScreenActivity.screenWidth,?MultiScreenActivity.scrrenHeight);??
  • //??????}??
  • ????}??
  • ??
  • ????//?layout過程??
  • ????@Override??
  • ????protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{??
  • ????????//?TODO?Auto-generated?method?stub??
  • ????????Log.i(TAG,?"---?start?onLayout?--");??
  • ????????int?startLeft?=?0;?//?每個子視圖的起始布局坐標??
  • ????????int?startTop?=?10;?//?間距設置為10px?相當于?android:marginTop=?"10px"??
  • ????????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?;?//校準每個子View的起始布局位置??
  • ????????????//三個子視圖的在屏幕中的分布如下?[0?,?320]?/?[320,640]?/?[640,960]??
  • ????????}??
  • ????}??
  • }????

  • ? ? ?更多關于自定義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?
  • //顯示一個懸浮窗吧?,?just?so?so???
  • public?void?showView()??
  • {??
  • ????//解析布局文件??
  • ????LayoutInflater?layoutInflater?=?(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);??
  • ????//rootView對應地LayoutParams屬性值為null,將會在UI繪制時設定其值??
  • ????View?rootView?=?layoutInflater.inflate(R.layout.main,?null);??
  • ??????
  • ????WindowManager?windowManager?=?(WindowManager)getSystemService(Context.WINDOW_SERVICE);??
  • ????//設置WindowManager.LayoutParams參數值,作為該窗口的各種屬性??
  • ????WindowManager.LayoutParams?winparams?=?WindowManager.LayoutParams();??
  • ?????//?以屏幕左上角為原點,設置x、y初始值??
  • ????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;??
  • ???}??
  • ???//以特定Window屬性添加一個窗口??
  • ???public?void?addView(View?view,?ViewGroup.LayoutParams?params)??
  • ???{??
  • ???????addView(view,?params,?false);??
  • ???}??
  • ???//參數nest表示該窗口是不是一個字窗口??
  • ???private?void?addView(View?view,?ViewGroup.LayoutParams?params,?boolean?nest)??
  • ???{???...??
  • ???????final?WindowManager.LayoutParams?wparams?=?(WindowManager.LayoutParams)params;??
  • ?????????
  • ???????ViewRoot?root;??
  • ???????View?panelParentView?=?null;????//該子窗口對應地父窗口View??
  • ?????????
  • ???????synchronized?(this)?{??
  • ????????????
  • ???????????...//需要對傳遞過來地參數進行檢測...??
  • ?????????????
  • ???????????//對每個窗口皆構建一個ViewRoot對象??
  • ???????????root?=?new?ViewRoot(view.getContext());??
  • ???????????root.mAddNesting?=?1;??
  • ???????????//設置root?View?的LayoutParams為wparams,即WindowManager.LayoutParams類型??
  • ???????????view.setLayoutParams(wparams);??
  • ???????????...//對參數檢測,以及拷貝原有數組...??
  • ?????????????
  • ???????????//將窗口對應地view、root、wparams保存在屬性集合中??
  • ???????????mViews[index]?=?view;??
  • ???????????mRoots[index]?=?root;??
  • ???????????mParams[index]?=?wparams;??
  • ???????}??
  • ???????//?do?this?last?because?it?fires?off?messages?to?start?doing?things??
  • ???????//?調用ViewRoot對象去通知系統添加一個窗口??
  • ???????root.setView(view,?wparams,?panelParentView);??
  • ???}??
  • ???...??
  • ???//這三個數組分別保存了一個窗口對應地屬性??
  • ???private?View[]?mViews;?????????//root?View對象?,?View類型??
  • ???private?ViewRoot[]?mRoots;?????//ViewRoot類型?,?與WMS通信??
  • ???private?WindowManager.LayoutParams[]?mParams;??//窗口屬性??
  • ?????
  • ???//WindowManagerImpl實現了單例模式??
  • ???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;???//所有窗口地root?View?????
  • ????final?WindowManager.LayoutParams?mWindowAttributes?=?new?WindowManager.LayoutParams();????
  • ??
  • ????...??
  • ?????/**?
  • ?????*?We?have?one?child?
  • ?????*/??
  • ????public?void?setView(View?view,?WindowManager.LayoutParams?attrs,??
  • ????????????View?panelParentView)?{??
  • ????????synchronized?(this)?{??
  • ????????????if?(mView?==?null)?{??
  • ????????????????mView?=?view;??
  • ????????????????mWindowAttributes.copyFrom(attrs);?//保存WindowManager.LayoutParams屬性值??
  • ????????????????attrs?=?mWindowAttributes;??
  • ????????????????...??
  • ??????????????????
  • ????????????????mAdded?=?true;??
  • ????????????????int?res;?/*?=?WindowManagerImpl.ADD_OKAY;?*/??
  • ??
  • ????????????????//?Schedule?the?first?layout?-before-?adding?to?the?window??
  • ????????????????//?manager,?to?make?sure?we?do?the?relayout?before?receiving??
  • ????????????????//?any?other?events?from?the?system.??
  • ????????????????requestLayout();???//請求UI開始繪制。??
  • ????????????????mInputChannel?=?new?InputChannel();??//創建一個InputChannel對象,接受消息??
  • ????????????????try?{??
  • ????????????????????//通知WindowManagerService添加一個窗口??
  • ????????????????????res?=?sWindowSession.add(mWindow,?mWindowAttributes,??
  • ????????????????????????????getHostVisibility(),?mAttachInfo.mContentInsets,??
  • ????????????????????????????mInputChannel);??
  • ????????????????}???
  • ????????????????...??
  • ????????????????view.assignParent(this);??//將root?View的父View設置為該ViewRoot對象(實現了ViewParent接口)??
  • ????????????????...??
  • ????????????}??
  • ????????}??
  • ????}??
  • }??
  • ? ? ?? ? ? 說明: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?
  • /**?
  • ?*?{@inheritDoc}?
  • ?*/??
  • public?void?requestLayout()?{??
  • ????checkThread();????????//檢查是不是UI線程調用,如果不是UI線程,會報異常??
  • ????mLayoutRequested?=?true;???//置為真,表示需要進行measure和layout過程??
  • ????scheduleTraversals();????
  • }??
  • //開始UI繪制流程??
  • public?void?scheduleTraversals()?{??
  • ????if?(!mTraversalScheduled)?{??
  • ????????mTraversalScheduled?=?true;???????//防止多次調用??
  • ????????sendEmptyMessage(DO_TRAVERSAL);???//異步請求UI繪制??
  • ????}??
  • }??
  • @Override??
  • public?void?handleMessage(Message?msg)?{??
  • ?switch?(msg.what)?{??
  • ????????case?DO_TRAVERSAL:??
  • ?????????????performTraversals();??//開始UI繪制??
  • ?????????????break;??
  • ?}??
  • }??
  • ? ? ? ? ? ? ? 由于performTraversals()方法比較復雜,我們側重于第一次設置root View的widhtSpecSize以及 ? ? ? heightSpecSize值。 [java] view plaincopyprint?
  • private?void?performTraversals()?{??
  • ????//?cache?mView?since?it?is?used?so?much?below...??
  • ????final?View?host?=?mView;??
  • ??
  • ????mTraversalScheduled?=?false;???????????
  • ????boolean?surfaceChanged?=?false;??
  • ????WindowManager.LayoutParams?lp?=?mWindowAttributes;????
  • ??
  • ????int?desiredWindowWidth;??????????????//表示該窗口期望width值??
  • ????int?desiredWindowHeight;?????????????//表示該窗口期望width值??
  • ????int?childWidthMeasureSpec;???????????//保存root?View的widthMeasureSpec??
  • ????int?childHeightMeasureSpec;??????????//保存root?View的heightMeasureSpec??
  • ??
  • ????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)?{???//mFirst表示是否是第一次繪制該Window??
  • ????????fullRedrawNeeded?=?true;??
  • ????????mLayoutRequested?=?true;??
  • ??
  • ????????DisplayMetrics?packageMetrics?=??
  • ????????????mView.getContext().getResources().getDisplayMetrics();??
  • ????????//第一次繪制時desiredWindowWidth,desiredWindowHeight?值大小為屏幕大小??
  • ????????desiredWindowWidth?=?packageMetrics.widthPixels;??
  • ????????desiredWindowHeight?=?packageMetrics.heightPixels;??
  • ????????...??
  • ????}?else?{???//不是第一次繪制,則desiredWindowWidth值為frame保存大小,frame值會由WMS填充??
  • ????????desiredWindowWidth?=?frame.width();??
  • ????????desiredWindowHeight?=?frame.height();??
  • ????????...??
  • ????}??
  • ????...??
  • ????boolean?insetsChanged?=?false;??
  • ??
  • ????if?(mLayoutRequested)?{??
  • ????????...//獲得root?View的widthMeasureSpec?和?heightMeasureSpec值??
  • ????????childWidthMeasureSpec?=?getRootMeasureSpec(desiredWindowWidth,?lp.width);??
  • ????????childHeightMeasureSpec?=?getRootMeasureSpec(desiredWindowHeight,?lp.height);??
  • ????????//開始measure過程??
  • ????????host.measure(childWidthMeasureSpec,?childHeightMeasureSpec);??
  • ????}??
  • ????...??
  • ????final?boolean?didLayout?=?mLayoutRequested;??
  • ??????
  • ????boolean?triggerGlobalLayoutListener?=?didLayout??
  • ????????????||?attachInfo.mRecomputeGlobalAttributes;??
  • ????if?(didLayout)?{??
  • ????????...?//layout過程??
  • ???????host.layout(0,?0,?host.mMeasuredWidth,?host.mMeasuredHeight);??
  • ????????...??
  • ????}??
  • ????...??
  • ????if?(!cancelDraw?&&?!newSurface)?{??
  • ????????mFullRedrawNeeded?=?false;??
  • ????????draw(fullRedrawNeeded);??
  • ????????...??
  • }??
  • [java] view plaincopyprint?
  • /**?
  • ??*?@param?windowSize??The?available?width?or?height?of?the?window?
  • ??*?
  • ??*?@param?rootDimension?The?layout?params?for?one?dimension?(width?or?height)?of?the?window.?
  • ?*/??
  • ?private?int?getRootMeasureSpec(int?windowSize,?int?rootDimension)?{??
  • ?????int?measureSpec;??
  • ?????switch?(rootDimension)?{??
  • ?????case?ViewGroup.LayoutParams.MATCH_PARENT:??
  • ?????????//?Window?can't?resize.?Force?root?view?to?be?windowSize.??
  • ?????????measureSpec?=?MeasureSpec.makeMeasureSpec(windowSize,?MeasureSpec.EXACTLY);??
  • ?????????break;??
  • ?????case?ViewGroup.LayoutParams.WRAP_CONTENT:??
  • ?????????//?Window?can?resize.?Set?max?size?for?root?view.??
  • ?????????measureSpec?=?MeasureSpec.makeMeasureSpec(windowSize,?MeasureSpec.AT_MOST);??
  • ?????????break;??
  • ?????default:??
  • ?????????//?Window?wants?to?be?an?exact?size.?Force?root?view?to?be?that?size.??
  • ?????????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布局文件解析流程浅析(下)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。