LayoutInflater 只負責實例化各個 View 類,不負責調用 measure/layout/draw 等方法
經過 inflate() 后,各個 View 的實例都已實例化完成,并且層級關系也已確定。但其 measure/layout/draw 并沒有調用。
因此,onMeasure() 在 onFinishInflate() 后調用。
屬性
sConstructorMap:各個 View 的 Constructor 集合。key 值為各個 View 的 name(自定義的為全名,系統的為 xml 中寫的名,如 TextView等)
mFilter:過濾器。根據 Class 判斷當前 View 是否要過濾。如果在 xml 中使用了要過濾的 View, 則會拋出異常
mFilterMap:存儲過濾結果。key 值為各個 View 的 name (自定義的為全名,系統的為 xml 中寫的名,如 TextView等)
方法
inflate
負責解析出根結點,并根據根結點不同調用下面方法
當根結點為 merget 時,調用
rInflate()
-
當根結點為普通 View 時:
- 首先調用
createViewFromTag()
創建根節點自身實例,該方法中并不創建其子 view。 - 調用
rInflateChildren()
創建所有子 View,并在創建過程中將子 View 添加到他們各自的父 View 中。
- 首先調用
rInflateChildren
其主要用于遞歸創建子 view,同時將子 view 添加到其父布局中。
該方法直接調用 rInflate()。
createViewFromTag
創建 view 自身實例,不涉及其子 view。
該方法中會按 mFactory2,mFactory,mPrivateFactory 優先級創建 View 實例。
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
如果工廠類中有返回值,則本方法結束,將將工廠類的返回值直接返回。
-
如果三個工廠類都沒有返回值,就會調用 onCreateView 或 createView —— 系統 view 調用前者,自定義 view 調用后者。
但 onCreateView 對 name 添加
android.view.
包前綴后,又調用到 createView。因此,系統 view 走到 createView 方法時,prefix 不為空,其值為 android.view.;但自定義 view 走到 createView() 時,prefix 為空。
createView
該方法中,經過 mFilter 過濾后,得到對應 view 的 constructor ,然后通過反射得到 view 的實例。
也就是說該方法會得到 view 的實例或者拋出異常(mFilter 沒有過濾通過)
rInflate
遍歷 xml 文件中所有的節點,并創建這些節點對應的 view 實例,同時將每一個實例添加到其父 view 中。
-
該方法有兩個入口:
根結點為 merge 標簽時,直接該方法。此時 finishInflate 為 false,且此時 parent 為外界傳入的 root。
通過 rInflateChildren 調用該方法,此時 finishInflate 為 true,且參數 parent 的值為 createViewFromTag() 的返回值。
-
該方法內部會通過 while 循環遍歷所有的 view 結點,并通過 parser.getName() 獲取當前結點的名字。拿到名字后,會根據名字的不同進行不同的操作,如:
普通 view,首先調用 createViewFromTag() 生成該 view 實例,再調用 rInflateChildren 創建子 view。最后通過 ViewGroup#addView 將子 View 添加到當前 View 中。
merge 標簽:直接拋異常。因為布局的外層 view 已經解析 ( 在 inflate 方法中完成),所以此時出現 merge 標簽說明 merge 并不是最外層。但 merge 只能做為根標簽。
-
tag 標簽:解析 tag 標簽中配置的的 id 與 value,并設置給 parent 。
<Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="選擇圖片"> <tag android:id="@id/id_tag" android:value="test" /> </Button>
requestFocus:將當前 parent 設置為焦點 view。
include 標簽:直接調用 parseInclude 方法
- 該方法最后會調用 view.onFinishInflate()。因此,如果 view 是一個 ViewGroup,該方法回調時表示其子 view 已經添加完畢;如果是一個普通的 View,表示該 View 的實例已創建結束,并且已經 add 到其父 View 中。
parseInclude 方法:
首先讀取 layout 屬性的值,并嘗試轉成 layoutId(即讀取通過 @layout/layoutId 形式配置的 layout)。如果失敗,會嘗試讀取 theme 中配置的 layout 屬性的值(即通過 ?attr/layout 配置的值)。
如果上述兩步結束后依舊沒有 layout 的值,則拋異常。
-
如果有 layout 的值,則跟普通的通過 inflater() 解析一個 layout 邏輯一樣:首先調用 createViewFromTag 得到布局的根結點實例,再調用 rInflateChildren() 填充所有子 view。
但在創建根結點實例后,會讀取布局 id 與 visibility 屬性:
visibility 控制根結點的顯示與隱藏
會調用 View#setId() 方法,將 id 的值設置為根結點的 id。
if (id != View.NO_ID) { view.setId(id); }
blink
在解析過程中,有一個 <blink>
標簽,其主要作用是讓其子 View 不斷閃動:出現與隱藏交替進行。
其對應的實例是 LayoutInflater 中的 BlinkLayout 內部類。
<blink
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="this is test" />
</blink>