Layout inflate方法解析:由Xml文件生成View Hierarchy的一些細(xì)節(jié)

太長(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的生成有四步:

  1. 由標(biāo)簽生成一個(gè)View
  2. 根據(jù)View的父布局的類型生成對(duì)應(yīng)的LayoutParams眼滤,并將LayoutParams設(shè)置給View
  3. 生成View的所有Children
  4. 將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í)行的流程:

  1. 將最外層的標(biāo)簽轉(zhuǎn)換為一個(gè)View,記為temp鼓蜒。
  2. 當(dāng)root不為空時(shí)痹换,利用root生成的temp的LayoutParams。
  3. 解析Xml并生成temp的所有子View都弹。
  4. 當(dāng)root不為空且attachToRoot為true時(shí)娇豫,將temp添加為root的一個(gè)child。
  5. 當(dāng)root不為空且attachToRoot為true時(shí)畅厢,返回root冯痢,否則返回temp。

這個(gè)流程包含了幾個(gè)細(xì)節(jié):

  1. 由Xml標(biāo)簽生成View對(duì)象
  2. 根據(jù)Xml嵌套結(jié)構(gòu)生成View父子結(jié)構(gòu)
  3. 應(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):

  1. LayoutParams保存了Xml中的layout_開(kāi)頭的布局屬性
  2. ViewGroup子類通常會(huì)實(shí)現(xiàn)一個(gè)LayoutParams類碗旅,用于讀取他們需要的布局屬性
  3. 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的部分

  1. 由Factory2生成
  2. 由Factory生成
  3. 由mPrivateFactory生成
  4. 由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中正確使用搭幻。
`

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咧擂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子檀蹋,更是在濱河造成了極大的恐慌松申,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贸桶,居然都是意外死亡舅逸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門皇筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)琉历,“玉大人,你說(shuō)我怎么就攤上這事水醋∑毂剩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵拄踪,是天一觀的道長(zhǎng)蝇恶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)惶桐,這世上最難降的妖魔是什么撮弧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮耀盗,結(jié)果婚禮上想虎,老公的妹妹穿的比我還像新娘卦尊。我一直安慰自己叛拷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布岂却。 她就那樣靜靜地躺著忿薇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躏哩。 梳的紋絲不亂的頭發(fā)上署浩,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音扫尺,去河邊找鬼筋栋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛正驻,可吹牛的內(nèi)容都是我干的弊攘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼姑曙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼襟交!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起伤靠,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捣域,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體焕梅,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迹鹅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丘侠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徒欣。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜗字,靈堂內(nèi)的尸體忽然破棺而出打肝,到底是詐尸還是另有隱情,我是刑警寧澤挪捕,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布粗梭,位于F島的核電站,受9級(jí)特大地震影響断医,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奏纪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望序调。 院中可真熱鬧,春花似錦发绢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)墩朦。三九已至,卻和暖如春氓涣,著一層夾襖步出監(jiān)牢的瞬間牛哺,已是汗流浹背春哨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赴背,地道東北人椰拒。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓晶渠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親燃观。 傳聞我的和親對(duì)象是個(gè)殘疾皇子褒脯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345