溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android?rinflate怎么使用

發(fā)布時間:2023-04-19 14:20:38 來源:億速云 閱讀:118 作者:iii 欄目:開發(fā)技術

這篇“Android rinflate怎么使用”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android rinflate怎么使用”文章吧。

rinflate源碼解析

這里詳細理一理rinflate方法,作用就是找到傳入的XmlPullParser當前層級所有的view并add到parent上:

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

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        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();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } 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 {
                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);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

很明顯rinflate是個遞歸方法,代碼很簡單,遞歸-判斷類型決定是否繼續(xù)遞歸-遞歸。

遞歸

我們知道,遞歸最重要的就是結束條件的選取,這里的結束條件有這么幾個:

  • type != XmlPullParser.END_TAG

  • parser.getDepth() > depth

  • type != XmlPullParser.END_DOCUMENT

其實1和3都是常規(guī)的結束條件,最重要的是2這個條件,這個結束條件保證了當前循環(huán)只讀取本層的view,我們結合一個例子來看一下。

下面是一個很簡單的XmlPullParser解析的例子:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/btn_1"
            android:layout_width="80dp"
            android:layout_height="45dp" />
    </LinearLayout>

    <Button
        android:id="@+id/btn_2"
        android:layout_width="match_parent"
        android:layout_height="60dp" />

</RelativeLayout>

解析代碼如下:

    public void readMainXml() {
        //1. 拿到資源文件
        InputStream is = getResources().openRawResource(R.raw.activity_main);
        //2. 拿到解析器對象
        XmlPullParser parser = Xml.newPullParser();
        final int depth = parser.getDepth();
        try {
            //3. 初始化xp對象
            parser.setInput(is, "utf-8");
            //4.開始解析
            //獲取當前節(jié)點的事件類型
            int type = parser.getEventType();
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                switch (type) {
                    case XmlPullParser.START_TAG:
                        int attrCount = parser.getAttributeCount();
                        LogUtil.d("depth:" + parser.getDepth() + " - " + parser.getName() + " 標簽開始");
                        for (int i = 0; i < attrCount; i++) {
                            String attrName = parser.getAttributeName(i);
                            String attrValue = parser.getAttributeValue(i);
                            //layout_width : match_parent
                            LogUtil.d("depth:" + parser.getDepth() + " - " + parser.getName() + "屬性: " + attrName + " : " + attrValue);
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        LogUtil.d("depth:" + parser.getDepth() + " - " + parser.getName() + "標簽結束");
                        break;
                    default:
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//    D: depth:1 - RelativeLayout 標簽開始
//    D: depth:1 - RelativeLayout屬性: layout_width : match_parent
//    D: depth:1 - RelativeLayout屬性: layout_height : match_parent
//    D: depth:2 - LinearLayout 標簽開始
//    D: depth:2 - LinearLayout屬性: layout_width : wrap_content
//    D: depth:2 - LinearLayout屬性: layout_height : wrap_content
//    D: depth:3 - Button 標簽開始
//    D: depth:3 - Button屬性: id : @+id/btn_1
//    D: depth:3 - Button屬性: layout_width : 80dp
//    D: depth:3 - Button屬性: layout_height : 45dp
//    D: depth:3 - Button標簽結束
//    D: depth:2 - LinearLayout標簽結束
//    D: depth:2 - Button 標簽開始
//    D: depth:2 - Button屬性: id : @+id/btn_2
//    D: depth:2 - Button屬性: layout_width : match_parent
//    D: depth:2 - Button屬性: layout_height : 60dp
//    D: depth:2 - Button標簽結束
//    D: depth:1 - RelativeLayout標簽結束

這里展示一個簡單的XmlPullParser的例子,可以看到RelativeLayout有兩個子View,分別是LinearLayoutButton2,depth都是2,結合上面的rinflate的代碼可以理解,在View的遞歸樹上,XmlPullParser的depth保證了層級,只會處理當前層級的View。

類型判斷

方法體中做了類型的判斷,特殊判斷了幾種類型如下:

TAG_REQUEST_FOCUS

非容器控件標簽中放標簽,表示將當前控件設為焦點,可以放到標簽里面,多個EditText的時候使用標簽首先獲得焦點。

TAG_TAG

標簽里面都可以放,類似于代碼中使用View.setTag:

    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);
        final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
        final CharSequence value = ta.getText(R.styleable.ViewTag_value);
        view.setTag(key, value);
        ta.recycle();

        consumeChildElements(parser);
    }

根據id獲取value,并把id當做key,設置parent的Tag??梢钥聪旅孢@個例子:

    <EditText
        android:id="@+id/et_1"
        android:layout_width="match_parent"
        android:layout_height="50dp">

        <tag
            android:id="@+id/tag1"
            android:value="tag_value" />

    </EditText>

可以使用findViewById(R.id.et_1).getTag(R.id.tag1),得到tag_value值,注意不可以使用getTag(),有參數無參數獲取的不是同一個屬性。

TAG_MERGE

這里還對標簽做了二次的判斷,保證標簽不會出現在非root元素的位置。 如果不是上述特殊的標簽,使用createViewFromTag加載出來view,并用當前的attrs加載成LayoutParams設置給當前View,繼續(xù)向下遞歸的同時把view add到parent.

TAG_INCLUDE

<include>標簽可以實現在一個layout中引用另一個layout的布局,這通常適合于界面布局復雜、不同界面有共用布局的APP中,比如一個APP的頂部布局、側邊欄布局、底部Tab欄布局、ListView和GridView每一項的布局等,將這些同一個APP中有多個界面用到的布局抽取出來再通過<include>標簽引用,既可以降低layout的復雜度,又可以做到布局重用(布局有改動時只需要修改一個地方就可以了)。

這些類型之外就類似于之前分析過的處理,先調用createViewFromTag方法創(chuàng)建View,設置attrs屬性,再調用遞歸方法rInflateChildren把view的子View add到view上,然后添加到parent上,直到層級遍歷結束。

下面重點看parseInclude的源碼分析:

parseInclude

private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;

//-------------------------------------第1部分-------------------------------------//
        if (!(parent instanceof ViewGroup)) {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }

        // 如果有theme屬性,從當前View的attrs里面查看是否有theme屬性,如果有,就重新創(chuàng)建ContextThemeWrapper,
        // 用當前View的theme替換之前ContextThemeWrapper里面的theme
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);//InflateActivityMergeTheme
        final boolean hasThemeOverride = themeResId != 0;
        if (hasThemeOverride) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();

        // 查看當前view的attrs里面是否有l(wèi)ayout的id,也就是'@layout/xxxx‘,如果沒有就返回0
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        if (layout == 0) {
            //找不到先找這個layout屬性的值'@layout/xxxx‘,看layout屬性的string是否為空,如果是空就直接拋異常,不為空才去找layoutId
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            if (value == null || value.length() <= 0) {
                throw new InflateException("You must specify a layout in the"
                    + " include tag: <include layout=\"@layout/layoutID\" />");
            }

            // 如果取不到,就嘗試去"?attr/"下面找對應的屬性。
            layout = context.getResources().getIdentifier(
                value.substring(1), "attr", context.getPackageName());

        }

        // The layout might be referencing a theme attribute.
        if (mTempValue == null) {
            mTempValue = new TypedValue();
        }
        if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
            layout = mTempValue.resourceId;
        }

        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                + "reference. The layout ID " + value + " is not valid.");
        }

//-------------------------------------第2部分-------------------------------------//

        final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
            (ViewGroup) parent, /*attachToRoot=*/true);
        if (precompiled == null) {
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
                final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                    // Empty.
                }

                final String childName = childParser.getName();

                if (TAG_MERGE.equals(childName)) {
                    // 如果是merge標簽,不支持屬性的設置,注意此處直接把parent作為父布局傳入,也就是加載出來的子View直接掛到parent上。
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    final View view = createViewFromTag(parent, childName,
                        context, childAttrs, hasThemeOverride);
                    final ViewGroup group = (ViewGroup) parent;

                    // 獲取include設置的id和visible。也就是說如果include設置了id和visible,會使用include設置的這兩個屬性
                    // 真正view設置的id和visible會不起作用
                    final TypedArray a = context.obtainStyledAttributes(
                        attrs, R.styleable.Include);
                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                    a.recycle();

                    // 先嘗試使用<include >標簽的屬性去創(chuàng)建params,判斷的標準是有沒有width/height屬性
                    // 如果沒有則使用view的屬性去創(chuàng)建params,然后調用view.setLayoutParams給View設置屬性
                    // 換言之,如果<include>設置了width/height屬性,會整體覆蓋view的屬性,反之則不會。
                    ViewGroup.LayoutParams params = null;
                    try {
                        params = group.generateLayoutParams(attrs);
                    } catch (RuntimeException e) {
                        // Ignore, just fail over to child attrs.
                    }
                    if (params == null) {
                        params = group.generateLayoutParams(childAttrs);
                    }
                    view.setLayoutParams(params);

                    // Inflate all children.
                    rInflateChildren(childParser, view, childAttrs, true);

                    // 如果<include>標簽設置了id和visibility屬性則一定會替換里面的id和visibility屬性
                    // 換言之,<include>標簽設置了id和visibility屬性,里面View的id和visibility會不起作用。
                    if (id != View.NO_ID) {
                        view.setId(id);
                    }

                    switch (visibility) {
                        case 0:
                            view.setVisibility(View.VISIBLE);
                            break;
                        case 1:
                            view.setVisibility(View.INVISIBLE);
                            break;
                        case 2:
                            view.setVisibility(View.GONE);
                            break;
                    }

                    group.addView(view);
                }
            } finally {
                childParser.close();
            }
        }
        LayoutInflater.consumeChildElements(parser);
    }

兩個部分:

  • 查找<include /> 標簽是否有l(wèi)ayout屬性,并應用適合的theme屬性

  • 判斷是否是<merge>,不同的方式加載對應的view,替換對應的屬性

第一部分:查找layout屬性

<include />最重要的就是用來做layout的替換,所以必須設置一個layout屬性,沒有設置layout屬性的<include />是沒有意義的,有兩種方式去設置這個layout屬性: 一種是直接設置:

    <include
        layout="@layout/include_test_viewgroup"/>

這種也是我們最常用的方式,這種方式我們稱作。

第二種方式是自定義一個reference,在attrs中定義,這樣也可以用來實現重用,比如:

//attrs.xml
    <declare-styleable name="TestInclude">
        <attr name="theme_layout" format="reference" />
    </declare-styleable>
//style.xml
    <style name="InflateActivityTheme" parent="AppTheme">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/red</item>
        <item name="theme_layout">@layout/include_test_merge</item>
    </style>

然后在layout中使用:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/InflateActivityTheme">
    <include layout="?attr/theme_layout"/>
</RelativeLayout>

上面這種方式我們稱作,或者下面這種我們稱作

<include
        layout="?attr/theme_layout"
        android:theme="@style/InflateActivityTheme" />

按照這幾種的介紹我們來走一遍上面查找layout的代碼:

        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        final boolean hasThemeOverride = themeResId != 0;
        if (hasThemeOverride) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();

這是方式的區(qū)別,方式說明傳過來的context就有theme,方式表示能從attrs中找到theme屬性,所以hasThemeOverride=true,如果需要覆蓋就用當前view的theme重新創(chuàng)建了ContextThemeWrapper。這兩者有一即可。

       // 查看當前view的attrs里面是否有l(wèi)ayout的id,也就是'@layout/xxxx‘,如果沒有就返回0
       int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
       if (layout == 0) {
           //找不到先找這個layout屬性的值'@layout/xxxx‘,看layout屬性的string是否為空,如果是空就直接拋異常,不為空才去找layoutId
           final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
           if (value == null || value.length() <= 0) {
               throw new InflateException("You must specify a layout in the"
                   + " include tag: <include layout=\"@layout/layoutID\" />");
           }

           // 如果取不到,就嘗試去"?attr/"下面找對應的屬性。
           layout = context.getResources().getIdentifier(
               value.substring(1), "attr", context.getPackageName());

       }

關于方式,代碼里其實寫清楚了,先找@layout/xxx這樣的,如果找不到就到?attr/下面找。

第二部分:加載對應的View并替換

這段的代碼其實看上面代碼里的注釋就好了,很清晰。加載替換的layout有兩種情況:

1.merge標簽,我們知道 merge標簽用于降低View樹的層次來優(yōu)化Android的布局,所以merge標簽并不是一層View結構,可以理解成一個占位,遇到merge標簽就直接調用rinflate方法,找到所有的子view掛到parent上就好了,所以給設置什么屬性,其實沒什么作用。

2.非merge標簽的其他ViewGroup,createViewFromTag加載進來對應的ViewGroup后

2.1. 嘗試使用<include />的屬性,如果標簽沒有設置width/height這樣的基礎屬性就使用加載進來的layout的屬性。

2.2. <include />標簽總是起作用的屬性有兩個,一個是id,一個是visibility,如果<include />設置了這兩個屬性,總是會替換加載的layout的對應屬性

設置完上面的屬性之后,繼續(xù)調用rInflateChildren去遞歸加載完所有的子view。

其實這個邏輯很像剛inflate剛開始執(zhí)行時候的邏輯,可以回憶一下之前的代碼。

這里有個小demo來看清這幾個的區(qū)別:

#styles.xml
    <style name="InflateActivityTheme" parent="AppTheme">
        <item name="colorPrimary">@color/red</item>
        <item name="theme_layout">@layout/include_test_merge</item>
    </style>

    <style name="InflateActivityMergeTheme" parent="AppTheme">
        <item name="colorPrimary">@color/green</item>
    </style>

    <style name="InflateActivityIncludeTheme" parent="AppTheme">
        <item name="colorPrimary">@color/blue</item>
    </style>

總的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/colorPrimary"
    android:theme="@style/InflateActivityTheme">

    <include
        layout="?attr/theme_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:theme="@style/InflateActivityMergeTheme" />

    <include
        layout="@layout/include_test_viewgroup"
        android:id="@+id/include_1"
        android:theme="@style/InflateActivityIncludeTheme" />

    <include
        layout="@layout/include_test_viewgroup"
        android:id="@+id/include_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:theme="@style/InflateActivityIncludeTheme" />
</RelativeLayout>

兩個子View的布局文件如下:

#include_test_merge.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_centerInParent="true"
        android:background="?attr/colorPrimary"
        android:gravity="center"
        android:text="include merge"
        android:textColor="#fff"
        android:textSize="12sp" />

</merge>

#include_test_viewgroup.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_playcontroller"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="?attr/colorPrimary"
    android:gravity="center">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="include LinearLayout"
        android:textColor="#fff"
        android:textSize="15sp" />

</LinearLayout>

顯示效果圖如下:

Android?rinflate怎么使用

大致覆蓋了上面說的幾種include的方式

  • theme中設置layout,<include />設置了theme可以不設置layout屬性

  • include子view是<merge />標簽和非<merge />標簽的區(qū)別

  • <include />標簽設置了width/height和其他位置相關的屬性會使用外面設置的屬性覆蓋子View的屬性,include_1沒有設置屬性所以使用的是include_test_viewgroup的屬性,include_2設置了位置相關屬性所以使用了設置的屬性,從實際顯示效果能看得出來。

  • 關于theme的覆蓋,如果子View設置了theme,會使用子View設置的theme替換context(父布局RelativeLayout)的theme,根據android:background="?attr/colorPrimary"可以看出來

以上就是關于“Android rinflate怎么使用”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI