1. 程式人生 > >Android 中LayoutInflater(佈局載入器)原始碼篇之rInflate方法

Android 中LayoutInflater(佈局載入器)原始碼篇之rInflate方法

前言

如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。

導航

概述

本篇部落格,是屬於Android 中LayoutInflater(佈局載入器)原始碼篇其中一個部分,專門介紹rInflate方法的流程,具體有以下幾部分:

  1. 一些不常見的標籤的解析方法以及使用,例如:requestFocus、tag

  2. 一個XML節點,變成一個View到底是怎麼做到的?

  3. XML深度是什麼,有什麼作用?

rInflate()的原始碼分析

    void rInflate(XmlPullParser parser, View parent
, Context context, AttributeSet attrs, boolean finishInflate) 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(); //如果該節點為requestFocus if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent
); //如果該節點為tag } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); //如果該節點為include標籤 } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } //解析include標籤 parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { //如果該節點為Merge throw new InflateException("<merge /> must be the root element"); } else { //否則屬於正常的View final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //接下來解析子View rInflateChildren(parser, view, attrs, true); //注意這裡直接進行addView操作 viewGroup.addView(view, params); } } //如果解析完成,需要通知父View,解析完成。 if (finishInflate) { parent.onFinishInflate(); } }

在rInflate這裡做的操作,就是識別這些節點,然後對應解析形成響應的元素,下面我們會根據程式碼,一段一段分析rInflate都做了什麼.

(1)如果發現requestFocus標籤,則呼叫父View的requestFocus()方法。

requestFocus標籤使用:

    <EditText  
        android:id="@+id/text"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content" >  
        <!-- 當前控制元件處於焦點狀態 -->  
        <requestFocus />  

parseRequestFocus方法:

    private void parseRequestFocus(XmlPullParser parser, View view)
            throws XmlPullParserException, IOException {
         //呼叫其父View的requestFocus()方法
        view.requestFocus();
        consumeChildElements(parser);
    }

(2)如果發現tag標籤,為其設定(key,value)模式的tag。

tag標籤使用:

    <Button
        android:id="@+id/tag_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="openClickNotification"
        android:text="自定義帶監聽事件的通知">

        <tag
            android:id="@+id/tag_id"
            android:value="@string/app_name" />

    </Button>

parseViewTag方法 :

    private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        final Context context = view.getContext();
        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
        //這裡設定tag的key
        final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
        //這裡設定tag的value
        final CharSequence value = ta.getText(R.styleable.ViewTag_value);
        view.setTag(key, value);
        ta.recycle();
        consumeChildElements(parser);
    }

在parseViewTag()方法中,會把(key,value)形式的tag賦予View。

Key指的是R.id.tag_id對應的int型別資料;

Value指的是R.string.app_name的String型別資料;

(3)如果是Include標籤,這裡開始先獲取了Include的深度

        final int depth = parser.getDepth();

所謂深度就是XML的層級關係,例如這樣:

 <!-- outside -->     0
 <root>                     1
    sometext                1
    <foobar>                    2
    </foobar>                   2
 </root>                    1
 <!-- outside -->     0

判斷該Include標籤的深度是否是0,如果為0,則丟擲異常,因為include不能為根元素。

解析include標籤時,主要方法為parseInclude()方法,具體分析請參考這篇部落格

(4)如果是Merge標籤,那麼會直接丟擲異常,因為Merge必須為根元素,也就是深度為0的節點。

(5)最後是其他標籤,例如View,一起其他的一些標籤

      final View view = createViewFromTag(parent, name, context, attrs);
      final ViewGroup viewGroup = (ViewGroup) parent;
      final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
      rInflateChildren(parser, view, attrs, true);
      viewGroup.addView(view, params);

在載入View的過程,大致分為三個階段:

  1. createViewFromTag() 見名知意,根據節點名稱建立View

  2. rInflateChildren() 載入該節點內子類

  3. parent.addView() 最後將該View新增進Parent佈局

第一階段 : createViewFromTag()

createViewFromTag()是根據name(節點名稱)來解析出View的一個方法,這裡直分析rInflate()的流程,而createViewFromTag()會在另一部分介紹,連結如下:

第二階段 :rInflateChildren()

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

這裡可以看到,這裡會將解析出來的View作為Root(父View),繼續進行子節點的解析,會繼續呼叫,直到無法解析。

這裡的無法解析是指:

  1. 當前解析的標識為XmlPullParser.END_TAG(節點結束的識別符號),並且深度不在父節點的標籤內。

  2. 或者type 為 XmlPullParser.END_DOCUMENT(文件結束的識別符號)。

第三階段 parent.addView()將View新增進父View中

viewGroup.addView(view, params);

這段話,不難理解,就是將解析出的View,新增到父View中。

流程圖

如果圖片比較大,請下載到本地,或者放大120%檢視。

這裡寫圖片描述