太長(zhǎng)了,不想看?
LayoutInflater.inflate()
方法能將Xml格式的布局文件轉(zhuǎn)換成以父子關(guān)系組合的一系列View,轉(zhuǎn)換后的結(jié)構(gòu)也稱為View Hierarchy。
我們通常向inflate()方法傳入三個(gè)參數(shù):布局資源的resId整葡,可為空的ViewGroup:root橘券,以及布爾值attachToRoot榨崩。許多初學(xué)者對(duì)于后兩個(gè)參數(shù)的使用比較生硬七问,缺乏理解浦译。
我理解的引入root的原因是為了讀取Xml文件中最外層標(biāo)簽的布局屬性棒假。布局屬性就是我們常看到的以<layout->
開(kāi)頭的屬性精盅,他表示一個(gè)View希望在父布局中獲得的尺寸以及位置帽哑,因此布局屬性是提供給父布局讀取以便計(jì)算尺寸、位置的叹俏。布局屬性體現(xiàn)在View中就是mLayoutParams
變量妻枕,這個(gè)變量的類型是ViewGroup.LayoutParams
。不同的ViewGroup的子類實(shí)現(xiàn)了不同的LayoutParams
類粘驰,用以從Xml中讀取自己關(guān)心的布局屬性屡谐,一個(gè)View的mLayoutParams
的類型必須與其父布局實(shí)現(xiàn)的LayoutParams相一致。
對(duì)于Xml文件的最外層標(biāo)簽蝌数,他所轉(zhuǎn)換成的View并不知道自己的父布局會(huì)是什么類型的愕掏,因此他不會(huì)生成mLayoutParams
變量,此時(shí)該標(biāo)簽的所有<layout-XXX>
屬性都沒(méi)有應(yīng)用到View中顶伞。為了避免這種情況饵撑,我們需要告知最外層的View他的父布局是什么類型的剑梳,生成對(duì)應(yīng)的LayoutParams儲(chǔ)存布局屬性。root就能幫助我們生成LayoutParams滑潘,而如果root正是我們希望的父布局垢乙,那么我們就將attachToRoot設(shè)為true,這樣我們通過(guò)Xml生成的View Hierarchy可以直接加入到root中语卤,我們不需要手動(dòng)做這步操作了追逮。
如果我們將Xml文件的嵌套結(jié)構(gòu)看作是樹(shù)狀結(jié)構(gòu)的話,逐個(gè)標(biāo)簽的解析其實(shí)就是樹(shù)的深度優(yōu)先遍歷粹舵,我們?cè)诒闅v的同時(shí)生成了一棵以View為節(jié)點(diǎn)钮孵,使用父子關(guān)系關(guān)聯(lián)的樹(shù)。
View Hierarchy中每個(gè)View的生成有四步:
- 由標(biāo)簽生成一個(gè)View
- 根據(jù)View的父布局的類型生成對(duì)應(yīng)的LayoutParams眼滤,并將LayoutParams設(shè)置給View
- 生成View的所有Children
- 將View加入他的父布局中
由一個(gè)標(biāo)簽轉(zhuǎn)換成一個(gè)View的過(guò)程其實(shí)就是通過(guò)ClassLoader加載出標(biāo)簽對(duì)應(yīng)的View.Class文件并獲得構(gòu)造器油猫,相當(dāng)于調(diào)用View(Context context, @Nullable AttributeSet attrs)
構(gòu)造一個(gè)實(shí)例。因此我們的自定義控件需要實(shí)現(xiàn)該構(gòu)造方法才能在Xml中使用柠偶,隨后從attrs變量中獲得Xml中的屬性情妖。
inflate方法介紹
Android開(kāi)發(fā)中,我們使用LayoutInflater.inflate()
方法將layout目錄下Xml格式的資源文件轉(zhuǎn)換為一個(gè)View Hierarchy诱担,并返回一個(gè)View對(duì)象毡证。
View Hierarchy直譯大概就是視圖層次,指的是以父子關(guān)系關(guān)聯(lián)的一系列View蔫仙,我們通過(guò)View Hierarchy的根節(jié)點(diǎn)(root view)可以獲得該結(jié)構(gòu)中所有的View對(duì)象料睛。
如果讓我們自己去設(shè)計(jì)一個(gè)方法將布局文件轉(zhuǎn)換為View Hierarchy,我們可能會(huì)想到逐行讀取Xml中的標(biāo)簽摇邦,利用標(biāo)簽的信息生成一個(gè)新的View/ViewGroup恤煞,當(dāng)Xml中出現(xiàn)嵌套關(guān)系就意味我們需要使用父子關(guān)系關(guān)聯(lián)兩個(gè)View。而inflate()
方法做的就是這么一件事施籍。
我們可以使用Activity.getLayoutInflater()
等方法獲得一個(gè)LayoutInflater的實(shí)例居扒,這些方法本質(zhì)上都是通過(guò)getSystemService(Context.LAYOUT_INFLATER_SERVICE)
獲得一個(gè)系統(tǒng)服務(wù),這個(gè)方法最終會(huì)返回一個(gè)PhoneLayoutInflater
的實(shí)例丑慎。
inflate有幾種重載方法喜喂,但最終都會(huì)走到
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
第一個(gè)參數(shù)我們看著比較陌生,但是大部分情況下我們只需要傳入一個(gè)layout資源文件竿裂,F(xiàn)ramework會(huì)幫我們根據(jù)資源獲得一個(gè)Parser玉吁。需要注意的是為了提升使用時(shí)的效率,Android會(huì)在編譯時(shí)就去解析layout資源文件并生成Parser腻异,因此我們想通過(guò)inflate()方法在使用過(guò)程中使用一個(gè)單純的Xml文件(非布局資源)去生成View是不可行的进副。
后面兩個(gè)參數(shù)會(huì)在源碼解析過(guò)程中介紹。
我們看下官方對(duì)這幾個(gè)參數(shù)的定義:
參數(shù) | 意義 |
---|---|
parser | XmlPullParser:以Pull的方式解析Xml文件悔常,通過(guò)Parser對(duì)象方便我們操作 |
root | ViewGroup:可選項(xiàng)影斑。當(dāng)attachToRoot為true時(shí)曾沈,他將作為生成出來(lái)的View層級(jí)的parent。如果attachToRoot為false鸥昏,那root僅僅為View層級(jí)樹(shù)的根節(jié)點(diǎn)提供LayoutParams的值 |
attachToRoot | 配合root使用 |
inflate()方法的返回值是一個(gè)View,如果root不會(huì)為空且attachToRoot為true姐帚,返回root吏垮。否則返回Xml生成的View Hierarchy的根View。
Inflate 方法源碼解析
看下inflate階段的核心代碼
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
// 解析根節(jié)點(diǎn)
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, inflaterContext, attrs, false);
} else {
// 將Xml最外層的標(biāo)簽解析為View對(duì)象罐旗,記為temp
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
if (root != null) {
// 當(dāng)root不為空時(shí)膳汪,創(chuàng)建與root相匹配的LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 僅將params提供給temp
temp.setLayoutParams(params);
}
}
// 通過(guò)inflate生成temp的所有chiildre
rInflateChildren(parser, temp, attrs, true);
// root不為空且attachToRoot為true,將temp加入到root中九秀,且用到params
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root != null && attachToRoot)
return root;
}else {
return temp;
}
}
}
先不考慮<merge>
標(biāo)簽遗嗽,inflate()
方法執(zhí)行的流程:
- 將最外層的標(biāo)簽轉(zhuǎn)換為一個(gè)View,記為temp鼓蜒。
- 當(dāng)root不為空時(shí)痹换,利用root生成的temp的LayoutParams。
- 解析Xml并生成temp的所有子View都弹。
- 當(dāng)root不為空且attachToRoot為true時(shí)娇豫,將temp添加為root的一個(gè)child。
- 當(dāng)root不為空且attachToRoot為true時(shí)畅厢,返回root冯痢,否則返回temp。
這個(gè)流程包含了幾個(gè)細(xì)節(jié):
- 由Xml標(biāo)簽生成View對(duì)象
- 根據(jù)Xml嵌套結(jié)構(gòu)生成View父子結(jié)構(gòu)
- 應(yīng)用root及attachToRoot
下面框杜,我們由表及里的解析這幾個(gè)細(xì)節(jié)浦楣。首先來(lái)看一下傳參中root與attachToRoot兩個(gè)參數(shù)的作用。
root與attachToRoot
root在inflate()方法中的第一個(gè)作用就是生成一個(gè)LayoutParams咪辱。
if (root != null) {
// 當(dāng)root不為空時(shí)振劳,創(chuàng)建與root相匹配的LayoutParams
params = root.generateLayoutParams(attrs);
}
LayoutParams保存的是一個(gè)控件的布局屬性。那我們來(lái)看下為什么需要利用root生成LayoutParams油狂。
布局屬性與LayoutParams
在Xml文件中澎迎,有許多前綴為layout_
的屬性,比如我們最熟悉的layout_width/layout_height
选调,我們稱其為布局屬性夹供。View使用布局屬性來(lái)告知父布局它所希望的尺寸與位置。不同類型的父布局讀取的布局屬性不同仁堪,比如layout_centerInParent
屬性哮洽,父布局為RelativeLayout時(shí)會(huì)起作用,而父布局為L(zhǎng)inearLayout時(shí)則無(wú)法使用弦聂。
Xml中的布局屬性保存到View中就是mLayoutParams
變量鸟辅,它的類型是ViewGroup.LayoutParams
氛什。實(shí)際上ViewGroup的子類都會(huì)實(shí)現(xiàn)一個(gè)擴(kuò)展自ViewGroup.LayoutParams
的嵌套類,這個(gè)LayoutParams類決定了他會(huì)讀取哪些布局屬性匪凉。我們看下RelativeLayout.LayoutParams
源碼的一部分:
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.RelativeLayout_Layout);
...
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
...
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
rules[LEFT_OF] = a.getResourceId(attr, 0);
break;
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
rules[RIGHT_OF] = a.getResourceId(attr, 0);
break;
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
rules[ABOVE] = a.getResourceId(attr, 0);
break;
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
break;
...
}
}
...
a.recycle();
}
這段代碼跟我們自定義控件時(shí)讀取Xml中的自定義屬性是一樣的做法枪眉,我們看到RelativeLayout.LayoutParams
在創(chuàng)建時(shí)讀取了一系列布局屬性并存儲(chǔ),比如layout_centerInParent
再层,其他的LayoutParams不會(huì)讀取該屬性贸铜。
如果對(duì)AttributeSet、TypedArray不熟悉可以參考這里:https://blog.csdn.net/lmj623565791/article/details/45022631
我們能在RelativeLayout.onMeasure()
方法中找到對(duì)LayoutParams的使用聂受。
// RelativeLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
int count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
...
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
...
}
}
...
}
當(dāng)計(jì)算每個(gè)子View的位置時(shí)蒿秦,需要讀取他們的布局屬性,此時(shí)會(huì)將View的LayoutParams強(qiáng)制類型轉(zhuǎn)換為RelativeLayout.LayoutParams蛋济,這里可能會(huì)報(bào)出無(wú)法轉(zhuǎn)換類型的錯(cuò)誤棍鳖,所以我們需要保證加入到RelativeLayout的View的LayoutParams類型都是RelativeLayout.LayoutParams
。
我們總結(jié)一下LayoutParams的核心知識(shí)點(diǎn):
- LayoutParams保存了Xml中的
layout_
開(kāi)頭的布局屬性 - ViewGroup子類通常會(huì)實(shí)現(xiàn)一個(gè)LayoutParams類碗旅,用于讀取他們需要的布局屬性
- View的LayoutParams類型必須與其父布局的類型相匹配渡处,否則會(huì)在
onMeasure
過(guò)程中報(bào)錯(cuò)
回到root與attachToRoot
當(dāng)我們解析Xml中最外層的標(biāo)簽,也就是View Hierarchy的根View時(shí)祟辟,程序并不知道它的父布局會(huì)是什么類型的骂蓖,因此不會(huì)生成LayoutParams。這時(shí)最外層標(biāo)簽中的所有布局屬性川尖,包括layout_width/layout_height
都不會(huì)被記錄到View對(duì)象中登下,也就是俗稱的“屬性失效了”。
但在實(shí)際使用中叮喳,我們通常能知道Xml生成的View Hierarchy所要加入的父布局或是要加入的父布局的類型被芳。這時(shí)候我們傳入一個(gè)root
參數(shù),根據(jù)root的類型去讀取根View的布局屬性并生成對(duì)應(yīng)的LayoutParams
馍悟。這段代碼如下:
if (root != null) {
// root不為空時(shí)畔濒,生成與root對(duì)應(yīng)的LayoutParams
params = root.generateLayoutParams(attrs);
}
public LayoutParams generateLayoutParams(AttributeSet attrs) {
// 不同的ViewGroup生成LayoutParams的細(xì)節(jié)不同
return new LayoutParams(getContext(), attrs);
}
而attachToRoot
則用于判斷root是直接作為parent使用還是僅需要他的類型信息。
if (root != null) {
if (attachToRoot) {
root.addView(temp, params);
}else{
// Set the layout params for temp if we are not attaching.
temp.setLayoutParams(params);
}
}
遞歸處理Xml中的所有標(biāo)簽
在inflate
方法中锣咒,除了根標(biāo)簽以外所有剩余標(biāo)簽的解析只使用了一個(gè)方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
...
}
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
這個(gè)方法僅僅是調(diào)用rInflate
方法侵状,沒(méi)有其他額外的行為。rInflate方法的中r
的含義是Recursive
毅整,即遞歸趣兄,我們可以猜測(cè)這個(gè)方法是使用遞歸的方式去處理Xml中的所有嵌套關(guān)系。我們看下rInflate
核心部分:
這段方法涉及到XmlPullParser的知識(shí)悼嫉,他將Xml文件轉(zhuǎn)換為一個(gè)對(duì)象艇潭。通過(guò)
next()
方法獲得下一個(gè)事件,一共有五個(gè)事件START_DOCUMENT、START_TAG蹋凝、TEXT鲁纠、END_TAG、END_DOCUMENT鳍寂。并且通過(guò)depth取得當(dāng)前元素嵌套的深度改含,未讀取到START_TAG時(shí)depth為0,每次讀取到START_TAG時(shí)depth加1迄汛。細(xì)節(jié)參考 http://www.xmlpull.org/ 捍壤。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) {
final int depth = parser.getDepth();
// while的結(jié)束條件:找到該depth的END_TAG(或者文件結(jié)束)
while (((type = parser.next()) != XmlPullParser.END_TAG
|| parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
// 只有遇到START_TAG時(shí)進(jìn)行解析
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
...
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
// 將當(dāng)前Tag轉(zhuǎn)換為View
final View view = createViewFromTag(parent, name, context, attrs);
// 根據(jù)父布局類型讀取布局屬性
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 嵌套處理,解析當(dāng)前View的所有children
rInflateChildren(parser, view, attrs, true);
// 將View加入到parent中
viewGroup.addView(view, params);
}
}
}
我們配合例子分析下rInflate
方法的流程:
<!-- depth -->
<LayoutA > <!-- 1 -->
<ViewB /> <!-- 2 -->
<LayoutC > <!-- 2 -->
<ViewC1 /> <!-- 3 -->
</LayoutC > <!-- 2 -->
</LayoutA > <!-- 1 -->
1.首先記下當(dāng)前的depth
2.取Xml中的下一個(gè)事件隔心,直到到達(dá)文件的結(jié)尾,或者到達(dá)了當(dāng)前depth的END_TAG
參照例子尚胞,如果rInflate開(kāi)始時(shí)解析到了<LayoutC>硬霍,那么當(dāng)解析到</LayoutC>時(shí)就會(huì)從while跳出,本次執(zhí)行結(jié)束笼裳。
3. while循環(huán)中唯卖,遇到START_TAG時(shí)解析
只解析STAST_TAG是保證每個(gè)Tag都只生成一個(gè)View,比如在解析到<LayoutC>時(shí)生成一個(gè)View躬柬,解析到</LayoutC>時(shí)則不需要拜轨。
解析普通的TAG的邏輯如下:
// 將當(dāng)前Tag轉(zhuǎn)換為View
final View view = createViewFromTag(parent, name, context, attrs);
// 根據(jù)父布局類型讀取布局屬性
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 嵌套處理,解析當(dāng)前View的所有children
rInflateChildren(parser, view, attrs, true);
// 將View加入到parent中
viewGroup.addView(view, params);
這里需要關(guān)注的就是那個(gè)嵌套的rInflateChildren()
方法允青,他也會(huì)走到rInflate橄碾,以當(dāng)前解析出的View作為Parent,解析下一層的內(nèi)容颠锉。
inflate過(guò)程推演
我們還是以上面的例子將inflate方法的執(zhí)行流程推演一遍法牲。
<!-- depth -->
<LayoutA > <!-- 1 -->
<ViewB /> <!-- 2 -->
<LayoutC > <!-- 2 -->
<ViewC /> <!-- 3 -->
</LayoutC > <!-- 2 -->
</LayoutA > <!-- 1 -->
- inflate方法,取到<LayoutA>琼掠,解析出LayoutA對(duì)象
- 通過(guò)rInflate方法解析LayoutA的所有children拒垃,開(kāi)始時(shí)depth為1
- 取到<ViewB />,解析出ViewB對(duì)象瓷蛙,ViewB無(wú)children悼瓮,它的rInflateChildren會(huì)讀到ViewB的END_TAG并結(jié)束,將ViewB加入到LayoutA中
- 取到<LayoutC>艰猬,解析出LayoutC對(duì)象横堡,調(diào)用rInflate方法解析Children
- 取到<ViewC />,解析出ViewC對(duì)象冠桃,無(wú)Children翅萤,將ViewC加入到LayoutC中
- 取到<LayoutC />,LayoutC的rInflateChildren過(guò)程結(jié)束
- 將LayoutC對(duì)象加入到LayoutA中
- 取到</LayoutA>,為END_TAG且depth為1套么,rInflate方法結(jié)束
- View Hierarchy解析完成培己,配合root及attachToRoot做些處理便可返回
整個(gè)流程自上而下的解析了Xml文件中的所有Tag,并生成了對(duì)應(yīng)的View Hierarchy胚泌,嵌套關(guān)系順利轉(zhuǎn)換成了父子關(guān)系省咨。生成的View Hierarchy是一個(gè)樹(shù)狀結(jié)構(gòu),生成過(guò)程跟樹(shù)的深度優(yōu)先遍歷有相似的感覺(jué)玷室。
merge標(biāo)簽解析
解析完rInflate方法后零蓉,我們?cè)賮?lái)看下<merge>標(biāo)簽的解析:
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, inflaterContext, attrs, false);
}
其實(shí)就是忽略<merge>這一層,將<merge>的所有內(nèi)容都直接加入到root中穷缤。
由Tag生成對(duì)應(yīng)的View
前面我們介紹了layout Xml如何轉(zhuǎn)換為View Hierarchy敌蜂,這個(gè)過(guò)程中的最后一個(gè)細(xì)節(jié)就是單個(gè)Tag如何轉(zhuǎn)化成View對(duì)象
無(wú)論是inflate方法對(duì)根View的解析還是rInflate中的嵌套解析,都是調(diào)用createViewFromTag()
方法津肛,我們看下這個(gè)方法的核心部分章喉。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// <view>標(biāo)簽中可以使用class來(lái)標(biāo)注類
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// 除了<include>以外,ignoreThemeAttr總為ture身坐,讀取Xml中的theme并通過(guò)Context作用到View上
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
// 就當(dāng)是彩蛋吧秸脱,let's party
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
// 生成View
try {
View 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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
// 用于構(gòu)建View的參數(shù)
// View(Context context, @Nullable AttributeSet attrs)
// 第一個(gè)傳參是context
attrs, int defStyleAttr)
mConstructorArgs[0] = context;
try {
// 使用系統(tǒng)控件時(shí)我們可以不帶著命名空間,此時(shí)name中不包含"."
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
...
}
直接看到生成View的部分
- 由Factory2生成
- 由Factory生成
- 由mPrivateFactory生成
- 由onCreateView/createView方法生成
這里的生成方法有先后順序部蛇,View一旦生成就不用走后面的方法摊唇。Factory2及Factory可以看做是給我們hook代碼的,允許我們按自己的期望去將Tag轉(zhuǎn)換為View涯鲁,二者的區(qū)別是工廠方法的傳參不同巷查。
如果沒(méi)有設(shè)置工廠
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
Xml中使用系統(tǒng)控件可以不加上命名空間,因此name中沒(méi)有“.”抹腿,在onCreateView方法中會(huì)為系統(tǒng)控件加上前綴“"android.view."”并調(diào)用createView方法吮便。而我們前面提到了我們實(shí)際使用的LayoutInflater通常是PhoneLayoutInflater
,他重寫(xiě)了onCreateView方法:
/// PhoneLayoutInflater.java
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
/// LayoutInflater.java
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
這里的操作都是為系統(tǒng)控件補(bǔ)全命名空間幢踏,具體的生成View的工作由createView完成髓需,核心代碼如下:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
...
// 使用ClassLoader獲得構(gòu)造器
clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// View的構(gòu)造器的傳參
Object[] args = mConstructorArgs;
args[1] = attrs;
// 獲得View實(shí)例
final View view = constructor.newInstance(args);
return view;
...
}
先通過(guò)ClassLoader加載類,獲得構(gòu)造器房蝉,然后實(shí)例化View的子類僚匆。具體的構(gòu)造方法是View(Context context, @Nullable AttributeSet attrs)
,因此我們的自定義控件也需要實(shí)現(xiàn)這個(gè)構(gòu)造方法才能在Xml中正確使用搭幻。
`