? ?
?
在之前一篇博文中<<?Android中View繪制流程以及invalidate()等相關方法分析>>
,簡單的闡述 了
Android View?
? 繪制流程的三個步驟,即:
? ? ? ? ? ? ? ? ? ? ? 1、 ?measure過程 --- 測量過程
? ? ? ? ? ? ? ? ? ? ? 2、 layout 過程 ? ? --- 布局過程
? ? ? ? ? ? ? ? ? ? ? 3、 draw 過程 ? ? ?--- 繪制過程
? ? ? 要想對Android 中View這塊深入理解,對這三個步驟地學習是必不可少的 。
? ? ? 今天,我著重講解下如下三個內容:
? ? ? ? ? ? 1、?measure過程
? ? ? ? ? ? 2、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT屬性的原理說明
? ? ? ? ? ? 3、xml布局文件解析成View樹的流程分析。
? ? ?希望對大家能有幫助。- -??分析版本基于Android 2.3 。
?1、WRAP_CONTENT、MATCH_PARENT/FILL_PARENT?
? ? ? ?初入Android殿堂的同學們,對這三個屬性一定又愛又恨。愛的是使用起來挺爽地---照葫蘆畫瓢即可,恨的
? 卻是時常混淆這幾個屬性地意義,需要三思而后行。在帶著大家重溫下這幾個屬性的用法吧(希望我沒有啰嗦)。
? ? ? 這三個屬性都用來適應視圖的水平或垂直大小,一個以視圖的內容或尺寸為基礎的布局比精確地指定視圖范圍
? 更加方便。
? ? ? ? ① ?fill_parent
? ? ? ? ? ? ? ? 設置一個視圖的布局為fill_parent將強制性地使視圖擴展至父元素大小。
? ? ? ? ② match_parent
? ? ? ? ? ? ? ?Android 中match_parent和fill_parent意思一樣,但match_parent更貼切,于是從2.2開始兩個詞都可以
? ? ? ? ? 用,但2.3版本后建議使用match_parent。
? ? ? ?③ wrap_content
? ? ? ? ? ? ? 自適應大小,強制性地使視圖擴展以便顯示其全部內容。以TextView和ImageView控件為例,設置為
? ? ? ? ?wrap_content將完整顯示其內部的文本和圖像。布局元素將根據內容更改大小。
? ? ? ?
? ? ? 可不要重復造輪子,以上摘自<<Android?fill_parent、wrap_content和match_parent的區別>>。
? ? ? 當然,我們可以設置View的確切寬高,而不是由以上屬性指定。
[java] view plaincopyprint?
android:layout_weight="wrap_content"?????android:layout_weight="match_parent"?????android:layout_weight="fill_parent"??????android:layout_weight="100dip"???????????
? ? ? 接下來,我們需要轉換下視角,看看ViewGroup.LayoutParams類及其派生類。
?2、ViewGroup.LayoutParams類及其派生類
? ? 2.1、 ?ViewGroup.LayoutParams類說明
? ? ? ? ? ? Android API中如下介紹:
? ? ? ? ? ? ??? LayoutParams are used by views to tell their parents how they want to be laid out.
? ? ?意思大概是說:?View通過LayoutParams類告訴其父視圖它想要地大小(即,長度和寬度)。
? ? 因此,每個View都包含一個ViewGroup.LayoutParams類或者其派生類,View類依賴于ViewGroup.LayoutParams。
? ? ? ? 路徑:frameworks\base\core\java\android\view\View.java
[java] view plaincopyprint?
public?class?View?implements?Drawable.Callback,?KeyEvent.Callback,?AccessibilityEventSource?{????...?????????????????protected?ViewGroup.LayoutParams?mLayoutParams;??????...??}??
? ? ?2.2、 ?ViewGroup.LayoutParams源碼分析
? ? ? 路徑位于:frameworks\base\core\java\android\view\ViewGroup.java
[java] view plaincopyprint?
public?abstract?class?ViewGroup?extends?View?implements?ViewParent,?ViewManager?{??????...???????public?static?class?LayoutParams?{?????????????????????????@Deprecated??????????public?static?final?int?FILL_PARENT?=?-1;??????????????????????????public?static?final?int?MATCH_PARENT?=?-1;?????????????????????????public?static?final?int?WRAP_CONTENT?=?-2;?????????????????????????public?int?width;??????????????????????????public?int?height;???????????????????????public?LayoutAnimationController.AnimationParameters?layoutAnimationParameters;????????????????????????public?LayoutParams(Context?c,?AttributeSet?attrs)?{??????????????TypedArray?a?=?c.obtainStyledAttributes(attrs,?R.styleable.ViewGroup_Layout);??????????????setBaseAttributes(a,??????????????????????R.styleable.ViewGroup_Layout_layout_width,??????????????????????R.styleable.ViewGroup_Layout_layout_height);??????????????a.recycle();??????????}?????????????????????????public?LayoutParams(int?width,?int?height)?{??????????????this.width?=?width;??????????????this.height?=?height;??????????}????????????????????????public?LayoutParams(LayoutParams?source)?{??????????????this.width?=?source.width;??????????????this.height?=?source.height;??????????}???????????????????????LayoutParams()?{??????????}??????????????????????????protected?void?setBaseAttributes(TypedArray?a,?int?widthAttr,?int?heightAttr)?{??????????????width?=?a.getLayoutDimension(widthAttr,?"layout_width");??????????????height?=?a.getLayoutDimension(heightAttr,?"layout_height");??????????}??}??
? ? ? ?我們發現FILL_PARENT/MATCH_PARENT值為 -1 ,WRAP_CONETENT值為-2,是不是有點詫異? 將值
? 設置為負值的目的是為了區別View的具體值(an exact size) 總是大于0的。
? ? ? ?ViewGroup子類可以實現自定義LayoutParams,自定義LayoutParams提供了更好地擴展性,例如LinearLayout
?就有LinearLayout.?LayoutParams自定義類(見下文)。整個LayoutParams類家族還是挺復雜的。
? ? ? ViewGroup.LayoutParams及其常用派生類的類圖(部分類圖)如下:
? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 該類圖是在太龐大了,大家有興趣的去看看Android API吧。
? ? ? ? ? ?
? ? ? 前面我們說過,每個View都包含一個ViewGroup.LayoutParams類或者其派生類,下面我們的疑問是Android框架
?中時如何為View設置其LayoutParams屬性的。
? ? ?有兩種方法會設置View的LayoutParams屬性:
? ? ? ?1、 直接添加子View時,常見于如下幾種方法:ViewGroup.java
[java] view plaincopyprint?
??void?addView(View?child,?int?index)??????void?addView(View?child,?int?width,?int?height)????void?addView(View?child,?ViewGroup.LayoutParams?params)??
? ? ? ? ?三個重載方法的區別只是添加View時構造LayoutParams對象的方式不同而已,稍后我們探尋一下它們的源碼。
? ? ? 2、 通過xml布局文件指定某個View的屬性為:android:layout_heigth=””以及android:layout_weight=”” 時。
? ? 總的來說,這兩種方式都會設定View的LayoutParams屬性值----指定的或者Default值。
? 方式1流程分析:
? ? ?直接添加子View時,比較容易理解,我們先來看看這種方式設置LayoutParams的過程:
? ? ? ? ?路徑:\frameworks\base\core\java\android\view\ViewGroup.java
[java] view plaincopyprint?
public?abstract?class?ViewGroup?extends?View?implements?ViewParent,?ViewManager?{??????...???????????????????public?void?addView(View?child)?{??????????addView(child,?-1);??????}????????????????????public?void?addView(View?child,?int?index)?{??????????LayoutParams?params?=?child.getLayoutParams();??????????if?(params?==?null)?{??????????????params?=?generateDefaultLayoutParams();???????????????if?(params?==?null)?{??????????????????throw?new?IllegalArgumentException("generateDefaultLayoutParams()?cannot?return?null");??????????????}??????????}??????????addView(child,?index,?params);??????}?????????????????public?void?addView(View?child,?int?width,?int?height)?{????????????????????final?LayoutParams?params?=?generateDefaultLayoutParams();???????????params.width?=?width;?????????????params.height?=?height;???????????addView(child,?-1,?params);???????}?????????????????public?void?addView(View?child,?LayoutParams?params)?{??????????addView(child,?-1,?params);??????}??????????????????public?void?addView(View?child,?int?index,?LayoutParams?params)?{??????????...????????????????????????????????????????requestLayout();??????????invalidate();??????????addViewInner(child,?index,?params,?false);??????}??????????????????protected?LayoutParams?generateDefaultLayoutParams()?{??????????????????????????????return?new?LayoutParams(LayoutParams.WRAP_CONTENT,?LayoutParams.WRAP_CONTENT);??????}??????private?void?addViewInner(View?child,?int?index,?LayoutParams?params,??????????????boolean?preventRequestLayout)?{????????????if?(!checkLayoutParams(params))?{???????????????params?=?generateLayoutParams(params);???????????}????????????????????if?(preventRequestLayout)?{????????????????child.mLayoutParams?=?params;???????????}?else?{??????????????child.setLayoutParams(params);??????????}????????????????????...??????}??????...??}??
? ? ? 主要功能就是在添加子View時為其構建了一個LayoutParams對象。但更重要的是,ViewGroup的子類可以重載
?上面的幾個方法,返回特定的LayoutParams對象,例如:對于LinearLayout而言,則是LinearLayout.LayoutParams
?對象。這么做地目的是,能在其他需要它的地方,可以將其強制轉換成LinearLayout.LayoutParams對象。
? ? ??LinearLayout重寫函數地實現為:
[java] view plaincopyprint?
public?class?LinearLayout?extends?ViewGroup?{??????...??????@Override??????public?LayoutParams?generateLayoutParams(AttributeSet?attrs)?{??????????return?new?LinearLayout.LayoutParams(getContext(),?attrs);??????}??????@Override??????protected?LayoutParams?generateDefaultLayoutParams()?{????????????????????if?(mOrientation?==?HORIZONTAL)?{???????????????return?new?LayoutParams(LayoutParams.WRAP_CONTENT,?LayoutParams.WRAP_CONTENT);??????????}?else?if?(mOrientation?==?VERTICAL)?{??????????????return?new?LayoutParams(LayoutParams.MATCH_PARENT,?LayoutParams.WRAP_CONTENT);??????????}??????????return?null;??????}??????@Override??????protected?LayoutParams?generateLayoutParams(ViewGroup.LayoutParams?p)?{??????????return?new?LayoutParams(p);??????}??????????????????public?static?class?LayoutParams?extends?ViewGroup.MarginLayoutParams?{?????????????????????????@ViewDebug.ExportedProperty(category?=?"layout")??????????public?float?weight;??????????????????????????????public?int?gravity?=?-1;????????????????????????public?LayoutParams(Context?c,?AttributeSet?attrs)?{??????????????super(c,?attrs);??????????????TypedArray?a?=c.obtainStyledAttributes(attrs,?com.android.internal.R.styleable.LinearLayout_Layout);??????????????weight?=?a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight,?0);??????????????gravity?=?a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity,?-1);????????????????a.recycle();??????????}??????????????????????public?LayoutParams(int?width,?int?height)?{??????????????super(width,?height);??????????????weight?=?0;??????????}?????????????????????????????public?LayoutParams(int?width,?int?height,?float?weight)?{??????????????super(width,?height);??????????????this.weight?=?weight;??????????}??????????public?LayoutParams(ViewGroup.LayoutParams?p)?{??????????????super(p);??????????}??????????public?LayoutParams(MarginLayoutParams?source)?{??????????????super(source);??????????}??????}??????...??}??
? ? ? ?LinearLayout.LayoutParams類繼承至ViewGroup.MarginLayoutParams類,添加了對android:layout_weight以及
? ?android:layout_gravity這兩個屬性的獲取和保存。而且它的重寫函數返回的都是LinearLayout.LayoutParams
? ?類型。這樣,我們可以再對子View進行其他操作時,可以將將其強制轉換成LinearLayout.LayoutParams對象進行
? ?使用。
? ? ? ? ?例如,LinearLayout進行measure過程,使用了LinearLayout.LayoutParam對象,有如下代碼:
[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;??????????????...????????????????????????????for?(int?i?=?0;?i?<?count;?++i)?{??????????????????final?View?child?=?getVirtualChildAt(i);???????????????????...????????????????????????????????????????????????????????????????????????LinearLayout.LayoutParams?lp?=?(LinearLayout.LayoutParams)?child.getLayoutParams();??????????????????...??????????}??????...??}??
? ? ? ? 超類ViewGroup.LayoutParams強制轉換為了子類LinearLayout.LayoutParams,因為LinearLayout的每個
? ”直接“子View的LayoutParams屬性都是LinearLayout.LayoutParams類型,因此可以安全轉換。
? ? ? ?PS : Android 2.3源碼Launcher2中也實現了自定義的LayoutParams類,在IDLE界面的每個View至少包含如下
? 信息:所在X方向的單元格索引和高度、所在Y方向的單元格索引和高度等。
? ? ? ? ? ??路徑:?packages\apps\Launcher2\src\com\android\launcher2\CellLayout.java
[java] view plaincopyprint?
public?class?CellLayout?extends?ViewGroup?{??????...???????public?static?class?LayoutParams?extends?ViewGroup.MarginLayoutParams?{??????????????????????????????public?int?cellX;?????????????????????????????????public?int?cellY;?????????????????????????????????public?int?cellHSpan;????????????????????????????????public?int?cellVSpan;????????????????...??????????????public?LayoutParams(Context?c,?AttributeSet?attrs)?{??????????????????super(c,?attrs);??????????????????cellHSpan?=?1;????????????????????cellVSpan?=?1;??????????????}????????????????public?LayoutParams(ViewGroup.LayoutParams?source)?{??????????????????super(source);???????????????????cellHSpan?=?1;??????????????????cellVSpan?=?1;??????????????}????????????????????????????public?LayoutParams(int?cellX,?int?cellY,?int?cellHSpan,?int?cellVSpan)?{??????????????????super(LayoutParams.MATCH_PARENT,?LayoutParams.MATCH_PARENT);??????????????????this.cellX?=?cellX;??????????????????this.cellY?=?cellY;??????????????????this.cellHSpan?=?cellHSpan;??????????????????this.cellVSpan?=?cellVSpan;??????????????}??????????????...??????????}??????...??}??
? ? ?對該自定義CellLayout.LayoutParams類的使用可以參考LinearLayout.LayoutParams類,我也不再贅述了。
?方法2流程分析:
? ? ? ? 使用屬性android:layout_heigth=””以及android:layout_weight=”” 時,為某個View設置LayoutParams值。
?
? ? ? ?其實這種賦值方法其實也如同前面那種,只不過它需要一個前期孵化過程---需要利用XML解析將布局文件
? 解析成一個完整的View樹,可別小看它了,所有Xxx.xml的布局文件都需要解析成一個完整的View樹。下面,
? 我們就來仔細走這個過程,重點關注如下兩個方面
? ? ? ? ?①、xml布局是如何解析成View樹的 ;
? ? ? ? ?②、android:layout_heigth=””和android:layout_weight=””的解析。
? ? ? ? PS: 一直以來,我都想當然android:layout_heigth以及android:layout_weight這兩個屬性的解析過程是在
? ?View.java內部完成的,但當我真正去找尋時,卻一直沒有在View.java類或者ViewGroup.java類找到。直到一位
? ?網友的一次提問,才發現它們的藏身之地。
3、布局文件解析流程分析
? ? ? ?解析布局文件時,使用的類為LayoutInflater。 關于該類的使用請參考如下博客:
? ? ? ? ? ? ? ? ? ? ? ? ??? <android中LayoutInflater的使用?>>
? ? ? 主要有如下API方法:
? ? ? ?
?public?View?inflate?(XmlPullParser?parser,?ViewGroup?root, boolean attachToRoot)
? ? ? ? ??public?View?inflate?(int resource,?ViewGroup?root)
? ? ? ? ??public?View?inflate?(int resource,?ViewGroup?root, boolean attachToRoot)
? ? ?這三個類主要迷惑之處在于地三個參數attachToRoot,即是否將該View樹添加到root中去。具體可看這篇博客:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?<<關于inflate的第3個參數>>
? ? 當然還有LayoutInflater的inflate()的其他重載方法,大家可以自行了解下。
? ? ?
? ? 我利用下面的例子給大家走走這個流程 :
[java] view plaincopyprint?
public?class?MainActivity?extends?Activity?{????????????@Override??????public?void?onCreate(Bundle?savedInstanceState)?{??????????super.onCreate(savedInstanceState);????????????????????setContentView(R.layout.main);??????????????????????????????LayoutInflater?layoutInflater?=?(LayoutInflater)getSystemService();??????????View?root?=?layoutInflater.inflate(R.layout.main,?null);??????}??}??
? ?Step 1、獲得LayoutInflater的引用。
? ? ? ? ?路徑:\frameworks\base\core\java\android\app\ContextImpl.java
[java] view plaincopyprint?
?????class?ContextImpl?extends?Context?{??????if?(WINDOW_SERVICE.equals(name))?{??????????return?WindowManagerImpl.getDefault();??????}?else?if?(LAYOUT_INFLATER_SERVICE.equals(name))?{??????????synchronized?(mSync)?{??????????????LayoutInflater?inflater?=?mLayoutInflater;????????????????????????????if?(inflater?!=?null)?{??????????????????return?inflater;??????????????}????????????????????????????mLayoutInflater?=?inflater?=?PolicyManager.makeNewLayoutInflater(getOuterContext());??????????????return?inflater;??????????}??????}?else?if?(ACTIVITY_SERVICE.equals(name))?{??????????return?getActivityManager();??????}...??}??
? ? ? ? ?繼續去PolicyManager查詢對應函數,看看內部實現。 ? ?
? ? ? ? ? ?路徑:frameworks\base\core\java\com\android\internal\policy\PolicyManager.java
[java] view plaincopyprint?
public?final?class?PolicyManager?{??????private?static?final?String?POLICY_IMPL_CLASS_NAME?=?"com.android.internal.policy.impl.Policy";??????private?static?final?IPolicy?sPolicy;?????????static?{????????????????????try?{??????????????Class?policyClass?=?Class.forName(POLICY_IMPL_CLASS_NAME);??????????????sPolicy?=?(IPolicy)policyClass.newInstance();??????????}??????????...??????}??????...??????public?static?LayoutInflater?makeNewLayoutInflater(Context?context)?{??????????return?sPolicy.makeNewLayoutInflater(context);???????}??}??
? ? IPolicy接口的實現對為Policy類。路徑:/frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java
[java] view plaincopyprint?
????public?class?Policy?implements?IPolicy{??????...??????public?PhoneLayoutInflater?makeNewLayoutInflater(Context?context)?{????????????????????return?new?PhoneLayoutInflater(context);??????}??}????public?class?PhoneLayoutInflater?extends?LayoutInflater?{??????...????????????????????public?PhoneLayoutInflater(Context?context)?{??????????super(context);??????}??????...??}??
? ? ? ?LayoutInflater是個抽象類,實際上我們返回的是PhoneLayoutInflater類,但解析過程的操作基本上是在
? LayoutInflater中完成地。
? ?Step 2、調用inflate()方法去解析布局文件。
[java] view plaincopyprint?
public?abstract?class?LayoutInflater?{??????...??????public?View?inflate(int?resource,?ViewGroup?root)?{????????????????????return?inflate(resource,?root,?root?!=?null);???????}????????????public?View?inflate(int?resource,?ViewGroup?root,?boolean?attachToRoot)?{??????????????????????????????XmlResourceParser?parser?=?getContext().getResources().getLayout(resource);??????????try?{??????????????return?inflate(parser,?root,?attachToRoot);??????????}?finally?{??????????????parser.close();??????????}??????}??}?????????public?interface?XmlResourceParser?extends?XmlPullParser,?AttributeSet?{???????????????public?void?close();??}??
? ? ? 我們獲得了一個當前應用程序環境的XmlResourceParser對象,該對象的主要作用就是來解析xml布局文件的。
??XmlResourceParser類是個接口類,更多關于XML解析的,大家可以參考下面博客:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <<android之XmlResourceParser類使用實例>>
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? <<android解析xml文件的方式(其一)>>
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??<<android解析xml文件的方式(其二)>>
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?<<android解析xml文件的方式(其三)>>
? ?Step 3 、真正地開始解析工作 。
[java] view plaincopyprint?
public?abstract?class?LayoutInflater?{??????...?????????????????????public?View?inflate(XmlPullParser?parser,?ViewGroup?root,?boolean?attachToRoot)?{??????????synchronized?(mConstructorArgs)?{??????????????final?AttributeSet?attrs?=?Xml.asAttributeSet(parser);??????????????Context?lastContext?=?(Context)mConstructorArgs[0];??????????????mConstructorArgs[0]?=?mContext;????????????????View?result?=?root;??????????????????try?{????????????????????????????????????int?type;??????????????????while?((type?=?parser.next())?!=?XmlPullParser.START_TAG?&&??????????????????????????type?!=?XmlPullParser.END_DOCUMENT)?{????????????????????????????????????????}??????????????????...??????????????????final?String?name?=?parser.getName();????????????????????if?(TAG_MERGE.equals(name))?{???????????????????????if?(root?==?null?||?!attachToRoot)?{??????????????????????????throw?new?InflateException("<merge?/>?can?be?used?only?with?a?valid?"??????????????????????????????????+?"ViewGroup?root?and?attachToRoot=true");??????????????????????}????????????????????????????????????????????rInflate(parser,?root,?attrs);??????????????????}?else?{??????????????????????????????????????????????????????????????????View?temp?=?createViewFromTag(name,?attrs);?????????????????????????ViewGroup.LayoutParams?params?=?null;????????????????????????if?(root?!=?null)?{??????????????????????????????????????????????????????????????????????????????params?=?root.generateLayoutParams(attrs);???????????????????????????if?(!attachToRoot)?{???????????????????????????????????????????????????????????????????????????????????????????temp.setLayoutParams(params);??????????????????????????}??????????????????????}??????????????????????????????????????????????????????????????????rInflate(parser,?temp,?attrs);????????????????????????????????????????????????????????????????????????????????????????if?(root?!=?null?&&?attachToRoot)?{??????????????????????????root.addView(temp,?params);??????????????????????}??????????????????????????????????????????????????????????????????if?(root?==?null?||?!attachToRoot)?{??????????????????????????result?=?temp;??????????????????????}??????????????????}??????????????}???????????????...??????????????return?result;??????????}??????}????????????????????View?createViewFromTag(String?name,?AttributeSet?attrs)?{????????????????????if?(name.equals("view"))?{????????????????name?=?attrs.getAttributeValue(null,?"class");??????????}??????????try?{??????????????View?view?=?(mFactory?==?null)???null?:?mFactory.onCreateView(name,??????????????????????mContext,?attrs);??????????????????if?(view?==?null)?{????????????????????????????????????if?(-1?==?name.indexOf('.'))?{??????????????????????view?=?onCreateView(name,?attrs);???????????????????}?else?{??????????????????????view?=?createView(name,?null,?attrs);??????????????????}??????????????}??????????????return?view;??????????}???????????...??????}????????????public?final?View?createView(String?name,?String?prefix,?AttributeSet?attrs)?{??????????Constructor?constructor?=?sConstructorMap.get(name);??????????Class?clazz?=?null;??????????????????????????????????????????????????try?{??????????????if?(constructor?==?null)?{????????????????????????????clazz?=?mContext.getClassLoader().loadClass(prefix?!=?null???(prefix?+?name)?:?name);??????????????...??????????????constructor?=?clazz.getConstructor(mConstructorSignature);??????????????sConstructorMap.put(name,?constructor);??????????}?else?{????????????????????????????if?(mFilter?!=?null)?{??????????????????...?????????????????}??????????}????????????????????????????Object[]?args?=?mConstructorArgs;??????????????args[1]?=?attrs;??????????????return?(View)?constructor.newInstance(args);??????????}???????????...??????}????}??
? ?? 這段代碼的作用是獲取xml布局文件的root View,做了如下兩件事情
? ? ? ? ? 1、獲取xml布局的View實例,通過createViewFromTag()方法獲取,該方法會判斷節點名是API 控件
? ? ? ? ? ? 還是自定義控件,繼而調用合適的方法去實例化View。
? ? ? ? ? 2、判斷root以及attachToRoot參數,重新設置root View值以及temp變量的LayoutParams值。
? ? ?? ?如果仔細看著段代碼,不知大家心里有沒有疑惑:當root為null時,我們的temp變量的LayoutParams值是為
? null的,即它不會被賦值?有個View的LayoutParams值為空,那么,在系統中不會報異常嗎?見下面部分
? 代碼:
[java] view plaincopyprint?
??public?View?inflate(XmlPullParser?parser,?ViewGroup?root,?boolean?attachToRoot)?{??????synchronized?(mConstructorArgs)?{??????????...??????????try?{????????????????????????????...??????????????if?(TAG_MERGE.equals(name))?{???????????????????...??????????????}?else?{??????????????????????????????????????????????????????View?temp?=?createViewFromTag(name,?attrs);???????????????????ViewGroup.LayoutParams?params?=?null;??????????????????????????????????????if?(root?!=?null)?{??????????????????????????????????????????????????????????????????params?=?root.generateLayoutParams(attrs);???????????????????????if?(!attachToRoot)?{???????????????????????????????????????????????????????????????????????????????temp.setLayoutParams(params);??????????????????????}??????????????????}??????????????????...??????????????}??????????}???????????...??????}??}??
? ? ? ? 關于這個問題的詳細答案,我會在后面講到。這兒我簡單說下,任何View樹的頂層View被添加至窗口時,
? 一般調用WindowManager.addView()添加至窗口時,在這個方法中去做進一步處理。即使,LayoutParams
? 值為空,UI框架每次measure()時都忽略該View的LayoutParams值,而是直接傳遞MeasureSpec值至View樹。
??? ? ?接下來,我們關注另外一個函數,rInflate(),該方法會遞歸調用每個View下的子節點,以當前View作為根View
?形成一個View樹。
[java] view plaincopyprint?
???????private?void?rInflate(XmlPullParser?parser,?View?parent,?final?AttributeSet?attrs)??????????throws?XmlPullParserException,?IOException?{????????final?int?depth?=?parser.getDepth();??????int?type;????????while?(((type?=?parser.next())?!=?XmlPullParser.END_TAG?||??????????????parser.getDepth()?>?depth)?&&?type?!=?XmlPullParser.END_DOCUMENT)?{????????????if?(type?!=?XmlPullParser.START_TAG)?{??????????????continue;??????????}??????????final?String?name?=?parser.getName();????????????????????if?(TAG_REQUEST_FOCUS.equals(name))?{???????????????parseRequestFocus(parser,?parent);??????????}?else?if?(TAG_INCLUDE.equals(name))?{???????????????if?(parser.getDepth()?==?0)?{??????????????????throw?new?InflateException("<include?/>?cannot?be?the?root?element");??????????????}??????????????parseInclude(parser,?parent,?attrs);??????????}?else?if?(TAG_MERGE.equals(name))?{???????????????throw?new?InflateException("<merge?/>?must?be?the?root?element");??????????}?else?{????????????????????????????final?View?view?=?createViewFromTag(name,?attrs);???????????????final?ViewGroup?viewGroup?=?(ViewGroup)?parent;????????????????????????????final?ViewGroup.LayoutParams?params?=?viewGroup.generateLayoutParams(attrs);??????????????rInflate(parser,?view,?attrs);???????????????viewGroup.addView(view,?params);???????????}??????}??????parent.onFinishInflate();????}??
? ? ? ? ? 值得注意的是,每次addView前都調用了viewGroup.generateLayoutParams(attrs)去構建一個LayoutParams
? 實例,然后在addView()方法中為其賦值。參見如下代碼:ViewGroup.java
??
[java] view plaincopyprint?
public?abstract?class?ViewGroup?extends?View?implements?ViewParent,?ViewManager?{??????...????????????public?LayoutParams?generateLayoutParams(AttributeSet?attrs)?{??????????return?new?LayoutParams(getContext(),?attrs);??????}??????public?static?class?LayoutParams?{??????????...???????????public?LayoutParams(Context?c,?AttributeSet?attrs)?{??????????????TypedArray?a?=?c.obtainStyledAttributes(attrs,?R.styleable.ViewGroup_Layout);??????????????setBaseAttributes(a,??????????????????????R.styleable.ViewGroup_Layout_layout_width,??????????????????????R.styleable.ViewGroup_Layout_layout_height);??????????????a.recycle();??????????}??????????protected?void?setBaseAttributes(TypedArray?a,?int?widthAttr,?int?heightAttr)?{??????????????width?=?a.getLayoutDimension(widthAttr,?"layout_width");??????????????height?=?a.getLayoutDimension(heightAttr,?"layout_height");??????????}????????}??
? ??好吧 ~~ 我們還是探尋根底,去TypeArray類的getLayoutDimension()看看。
? ? ? ? ?路徑:/frameworks/base/core/java/android/content/res/TypedArray.java
[java] view plaincopyprint?
public?class?TypedArray?{??????...???????????????????????public?int?getLayoutDimension(int?index,?String?name)?{??????????index?*=?AssetManager.STYLE_NUM_ENTRIES;??????????final?int[]?data?=?mData;????????????????????final?int?type?=?data[index+AssetManager.STYLE_TYPE];??????????if?(type?>=?TypedValue.TYPE_FIRST_INT??????????????????&&?type?<=?TypedValue.TYPE_LAST_INT)?{??????????????return?data[index+AssetManager.STYLE_DATA];??????????}?else?if?(type?==?TypedValue.TYPE_DIMENSION)?{???????????????return?TypedValue.complexToDimensionPixelSize(??????????????????data[index+AssetManager.STYLE_DATA],?mResources.mMetrics);??????????}??????????????????????????????throw?new?RuntimeException(getPositionDescription()??????????????????+?":?You?must?supply?a?"?+?name?+?"?attribute.");??????}??????...??}??
? ? ? ? ?從上面得知, ?
我們將View的AttributeSet屬性傳遞給generateLayoutParams()方法,讓其構建合適地
? ?LayoutParams對象,并且初始化屬性值weight和height。同時我們也得知?布局文件中的View包括自定義View
? ?必須加上屬性layout_weight和layout_height,否則會報異常。
? ? Step 3 主要做了如下事情:
? ? ? ?首先,獲得了了布局文件地root View,即布局文件中最頂層的View。
? ? ? ?其次,通過遞歸調用,我們形成了整個View樹以及設置了每個View的LayoutParams對象。
? ??
? ??總結:通過對布局文件的解析流程的學習,也就是轉換為View樹的過程,我們明白了解析過程的個中奧妙,以及
設置ViewLayoutParams對象的過程。但是,我們這兒只是簡單的浮光掠影,更深層次的內容希望大家能深入學習。
? ? ? 本來是準備接下去往下寫的,但無奈貼出來的代碼太多,文章有點長而且自己也有點凌亂了,因此決定做兩篇
? 博客發表吧。下篇內容包括如下方面:
? ? ? ? 1、MeasureSpec類說明 ;
? ? ? ? 2、measure過程中如何正確設置每個View的長寬 ;
? ? ? ? 3、UI框架正確設置頂層View的LayoutParams對象,對Activity而言,頂層View則是DecorView,
? ?其他的皆是普通View了。
總結
以上是生活随笔為你收集整理的Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。