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

Vander發表於2018-11-20
本文出自部落格Vander丶CSDN部落格,如需轉載請標明出處,尊重原創謝謝
部落格地址:blog.csdn.net/l540675759/…

前言

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

Android 中LayoutInflater(佈局載入器)系列博文說明


導航

Android 中LayoutInflater(佈局載入器)系列博文說明

Android 中LayoutInflater(佈局載入器)系列之介紹篇

Android 中LayoutInflater(佈局載入器)系列之原始碼篇

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

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

Android 中LayoutInflater(佈局載入器)系列之實戰篇


概述

本篇部落格,是屬於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的特性,對其layout屬性內的佈局進行解析,這段內容和解析內容重複較高,暫時先了解到這裡,後續可能單獨開一篇文章做分析。


(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()會在另一部分介紹,連結如下:

Android 中LayoutInflater(佈局載入器)原始碼篇之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%檢視。

這裡寫圖片描述
這裡寫圖片描述

相關文章