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

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

前言

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

導航

概述

本篇部落格,是作為Android中LayoutInflater(佈局載入器)原始碼篇的一個補充,至此LayoutInflater中幾個大模組在這個系列的博文中,已經分析完畢了。

本篇專門介紹解析《include》標籤的解析流程,具體分成以下幾部分:

  1. include標籤涉及到theme時的相關處理

  2. 獲取include標籤中的layout資源

  3. 處理include包裹的內容

parseInclude()是在哪裡使用的?

 void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
            //----------------省略部分程式碼--------------------//

            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0
) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } //----------------省略部分程式碼--------------------// }

從上來程式碼中,可以發現parseInclude()是在rInflate()中出現,作用是處理當前節點是Include標籤時的狀況。

而rInflater()這個方法的作用是,解析某個節點,根據節點的不同型別從而進行不同的處理,如果想深入瞭解可以參考這篇部落格:

parseInclude()原始碼解析

 //引數說明:
 // parser      解析佈局的解析器
 // context     當前載入佈局的上下文物件
 // parent      父容器
 // attrs       屬性集合(XML該節點的屬性集合)
 private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;

        // 判斷 Include標籤是否在 ViewGroup容器之內,因為 include 標籤只能存在於 ViewGroup 容器之內。

        if (parent instanceof ViewGroup) {

            //------------------<第一部分>-------------------//

            //當開發者設定 include 主題屬性時,可以覆蓋被 include 包裹View的主題屬性。
            //但是這種操作很少會使用。
            //所以如果被包裹 View 設定主題屬性,我們在設定就會出現覆蓋效果。
            //以 include 標籤的主題屬性為最終的主題屬性

            //提取出 include 的 thme 屬性,如果設定了 them 屬性,那麼include 包裹的View 設定的 theme 將會無效
            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();


            //------------------<第二部分>-------------------//

            //如果這個屬性是指向主題中的某個屬性,我們必須設法得到主題中layout 的資源識別符號
            //先獲取 layout 屬性(資源 id)是否設定
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
            if (layout == 0) {
            //如果沒直接設定佈局的資源 id,那麼就檢索?attr/name這一類的 layout 屬性
                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/name 這一類的屬性中,獲取佈局屬性  
                layout = context.getResources().getIdentifier(value.substring(1), null, null);
            }

            //這個佈局資源也許存在主題屬性中,所以需要去主題屬性中解析
            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.");
            } else {
                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.
                    }

                    if (type != XmlPullParser.START_TAG) {
                        throw new InflateException(childParser.getPositionDescription() +
                                ": No start tag found!");
                    }

                    final String childName = childParser.getName();

                    if (TAG_MERGE.equals(childName)) {
                        //解析 Meger 標籤
                        rInflate(childParser, parent, context, childAttrs, false);
                    } else {
                        //根據 name名稱來建立View
                        final View view = createViewFromTag(parent, childName,
                                context, childAttrs, hasThemeOverride);
                        final ViewGroup group = (ViewGroup) parent;


                        //獲取 View 的 id 和其 Visiable 屬性
                        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();

                        //需要將 Parent中的 LayoutParams 設定為其 Params 屬性。
                        //如果 Parent 沒有通用的 Params,那麼就會丟擲Runtime 異常

                        //然後會為其設定 include 包裹內容的通用 Params,

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

                        // 解析子標籤
                        rInflateChildren(childParser, view, childAttrs, true);

                        if (id != View.NO_ID) {
                            view.setId(id);
                        }

                        // 載入include內容時,需要直接設定其 可見性
                        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();
                }
            }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }

        LayoutInflater.consumeChildElements(parser);
    }

先把parseInclude()這個方法全景先看下,然後我們在進行分拆,一部分一部分分析。

parseInclude()引數解讀

parseInclude()中分別含義四個引數:

(1)解析器 -> XmlPullParser parser

用來解析XML檔案的解析器,通過解析器可以得到當前節點的相對應的AttributeSet(屬性集)

(2)上下文物件 - > Context context

當前載入該XML的上下文物件,並且這個Context與LayoutInflater屬於相互繫結關係(一一對應)

(3)父容器 - > View parent

包裹該節點的父容器,一般來說都是繼承ViewGroup實現的檢視組

(4)屬性集 -> AttributeSet attrs

該節點的屬性集,包括所有該節點的相關屬性

Include中的theme屬性

這裡大家先了解一個相關的問題,關於include標籤設定theme屬性的情況:

一般來說theme(主題)一般出現在Activtiy的AndroidManifest檔案下,來給Activity設定統一的佈局效果,而且可以使用如下的操作來進行主題屬性的使用。

//  ?attr這樣的形式,使用主題中的設定引數
android:background="?attr/colorPrimary"

如果Include標籤下設定了新的theme,那麼Include中的內容在使用主題屬性時,使用的theme主題就是(include)設定的內容,而不是Activity預設下的主題,形成了一種覆蓋效果。

也就是說Include標籤設定的主題可以覆蓋Activity設定的根主題,但是Include設定的主題只作用與Include內部。

舉個栗子:

style.xml

先定義好兩個基礎Theme,一個是作為App的基礎主題,另一個是include中的主題。

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- BaseApplication theme -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>


    <style name="IncludeTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Include Theme -->
        <item name="colorPrimary">@color/colorAccent</item>
        <item name="colorPrimaryDark">@color/colorAccent</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

AndroidManifest.xml

設定Activity的基礎主題為AppTheme

        <activity
            android:name="com.demo.MainActivity"
            android:theme="@style/AppTheme"></activity>

activity_main.xml

<?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:orientation="vertical">

    <!-- 這裡是使用基礎Theme的Toolbar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/activity_theme_tb"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="?attr/colorPrimary" />

    <!-- 這裡是自帶Theme Include的Toolbar -->
    <include
        layout="@layout/test_toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:theme="@style/IncludeTheme" />

</RelativeLayout>

接下來,我們在看一下Include包裹的佈局
test_toolbar.xml

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/include_toolbar"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="?attr/colorPrimary" />

</LinearLayout>

從上面的XML檔案我們可以看出兩個Toolbar呼叫的background都指向theme的colorPrimary屬性,接下來看一下顯示效果:

Toolbar對比圖

從效果圖可以發現,Include Toolbar顯示的顏色是粉色的,也就是Include額外設定的theme,這裡也是從正面證明了這個概念。

第一部分:Include Theme主題的設定

            //------------------<第一部分>-------------------//
            //提取出Theme屬性
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            final boolean hasThemeOverride = themeResId != 0;
            //如果存在Theme屬性,那麼Include包含的子標籤都會使用該主題
            if (hasThemeOverride) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();

通過上面的介紹,很明顯這段程式碼含義,就是檢測是否給Include標籤設定了Theme屬性,如果設定theme,就建立相應的ContextThemeWrapper,用於之後子標籤的解析時theme的使用。

第二部分:Include 內容佈局的設定

            //------------------<第二部分>-------------------//
            //先獲取 layout 屬性(資源 id)是否設定
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
            if (layout == 0) {
            //如果沒直接設定佈局的資源 id,那麼就檢索?attr/name這一類的 layout 屬性
                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/name 這一類的屬性中,獲取佈局屬性  
                layout = context.getResources().getIdentifier(value.substring(1), null, null);
            }

            //這個佈局資源也許存在主題屬性中,所以需要去主題屬性中解析
            if (mTempValue == null) {
                mTempValue = new TypedValue();
            }
            if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
                layout = mTempValue.resourceId;
            }

這部分的內容主要是提取Include的內容佈局的提取,Include的內容佈局的設定有兩種:

第一種 : 直接@layout 後面設定佈局的XML

        layout="@layout/test_toolbar"

第二種:通過引入theme的item設定的layout屬性

Include標籤下:

        layout="?attr/theme_layout"

包裹Include標籤的佈局Theme(注意:這裡不是Include設定的主題):

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        //重點在這裡!!!!!
        <item name="theme_layout">@layout/test_toolbar</item>
    </style>

而上面的程式碼的作用是檢索layout屬性,如果layout已經以第一種方式引入,就不需要在去theme中檢索,如果layout第一種方式檢索不到資源ID,那麼就會去以第二種方式進行檢索。

第三部分: Include標籤的View處理

            //------------------<第三部分>-------------------//
            //如果此時還找不到layout,那麼必然異常~,會報找不到資源ID的layout異常
            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.");
            } else {
            //生成子解析器
                final XmlResourceParser childParser = context.getResources().getLayout(layout);

                try {
                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
                    //----------------省略了XML一些規則的判斷----------------//
                    //獲取子節點的名稱
                    final String childName = childParser.getName();
                    if (TAG_MERGE.equals(childName)) {
                        //解析 Meger 標籤
                        rInflate(childParser, parent, context, childAttrs, false);
                    } else {
                        //根據 name名稱來建立View
                        final View view = createViewFromTag(parent, childName,
                                context, childAttrs, hasThemeOverride);
                        final ViewGroup group = (ViewGroup) parent;
                        //獲取 View 的 id 和其 Visiable 屬性
                        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();

                        //需要將 Parent中的 LayoutParams 設定為其 Params 屬性。
                        //如果 Parent 沒有通用的 Params,那麼就會丟擲Runtime 異常

                        //然後會為其設定 include 包裹內容的通用 Params,

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

                        // 解析子標籤
                        rInflateChildren(childParser, view, childAttrs, true);

                        if (id != View.NO_ID) {
                            view.setId(id);
                        }

                        // 載入include內容時,需要直接設定其 可見性
                        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();
                }
            }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }

這部分主要的作用是解析Include包裹layout的根標籤:

(1)先特別處理Merge標籤 :

如果子節點是Merge標籤,那麼直接進行內容的解析,呼叫rInflater()方法。

而rInflater()這個方法的作用是,解析某個節點,根據節點的不同型別從而進行不同的處理,如果想深入瞭解可以參考這篇部落格:

(2)解析Include的內容:

在這之前先通過createViewFromTag()方法,根據名稱來生成相對應的View,具體的解析請參考這篇部落格:

這裡分成兩塊內容,第一塊是設定LayoutParams:

                        ViewGroup.LayoutParams params = null;
                        try {
                            //載入Include的父ViewGroup的LayoutParams
                            params = group.generateLayoutParams(attrs);
                        } catch (RuntimeException e) {
                            // Ignore, just fail over to child attrs.
                        }
                        if (params == null) {
                            //載入Include的子ViewGroup的LayoutParams
                            params = group.generateLayoutParams(childAttrs);
                        }
                        view.setLayoutParams(params);

這段的作用是為Include的包裹的根View設定LayoutParams,使用的LayoutParams預設是Include外層的ViewGroup。

如果此時Params載入失敗,那就會使用Include包裹的ViewGroup的LayoutParams,反正怎麼都得設定一個。

第二塊是在這裡設定子ViewGroup的顯隱性:

                        // 載入include內容時,需要直接設定其 可見性
                        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);
                    }

設定ViewGroup的顯隱性,之後就將其新增至父View中,至此parseInclude的分析就到此結束。

流程圖

parseInclude的流程圖

相關推薦

Android LayoutInflater佈局載入原始碼parseInclude方法

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是作為Android中LayoutInflater(佈局載入器)原

Android LayoutInflater佈局載入原始碼rInflate方法

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是屬於Android 中LayoutInflater(佈局載入器)原始

Android LayoutInflater佈局載入原始碼

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 (1)Activity 的 getSystemService的實現過程 (2

Android LayoutInflater佈局載入實戰

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 效果 可以看出在滑動時,會出現視覺差效果。 可以看出在滑動時,物品會飄出去。

Android LayoutInflater佈局載入介紹

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 本篇作為Android 中LayoutInflater(佈局載入器)系列的介紹篇,該篇內容知識內容比較基礎,建議先看一些概述,如果感覺

PyQt52——調整佈局佈局管理第一個程式

我們拖拽一個UI檔案,轉為PY檔案後生成一個類Ui_MainWindow 此時,我們新建一個檔案,用來控制業務邏輯(繼承介面中的類),跟介面分開,這樣我們就完成了介面和邏輯相分離(這段程式碼使用率基本100%,牢牢記住)。 1 __author__ = "WSX" 2 import sys 3

Scrapy爬蟲入門教程七 Item Loaders專案載入

目錄 專案載入器 巢狀裝載器 開發環境: Python 3.6.0 版本 (當前最新) Scrapy 1.3.2 版本 (當前最新) 專案載入器 專案載入器提

salesforce零基礎學習一百零二Limitation CPU Limit

本篇參考: https://help.salesforce.com/articleView?id=000339361&type=1&mode=1 https://developer.salesforce.com/wiki/apex_code_best_practices https://dev

JavaGUI簡介、AWT概述、以及佈局管理流式佈局管理、邊界佈局管理、網格佈局管理、網格包佈局管理、卡片佈局管理

1 GUI簡介   GUI的全稱是Graphical User Interface,即圖形使用者介面。顧名思義,就是應用程式提供給使用者操作的圖形介面,包括視窗、選單、按鈕、工具欄和其他各種使用者介面元素。Java中針對GUI設計提供了豐富的類庫,這些類分別位

AndroidListView上拉載入分頁功能

思路 1新增頁尾,並隱藏 2監聽滑動事件,判斷當滑到低部時,顯示頁尾,並載入資料(介面回撥到activity中載入) 3資料新增完成之後隱藏頁尾 效果圖: 專案結構: 自定義listView類LoadListView package com.zhh.android;

AndroidListView錯位佈局實現無聊向

由於某些原因,需要個錯位的頁面,在網上找不到好的例子,試著動手寫了寫。 不考慮配色的完成圖如下: 首先考慮的是,listview每一行左右都有可能縮排。 先假設一行的佈局就是ImageView,TextView,ImageView,程式碼如下: 1 <Line

WinForm,每隔一段時間(參數)調用一次函數使用定時

pre tick break switch 時間 器) chan pri args 1      System.Windows.Forms.Timer setTimer; //定義一個定時器 2 int flg = 0;

_030_Android_ Android開發SmsManager簡訊管理詳解

轉自https://blog.csdn.net/qq_37443229/article/details/80039836,感謝作者的無私分享。 Android開發之SmsManager(簡訊管理器)詳解         SmsManager是

反射Constructor、Field、Method、類載入

一:什麼是反射 在認識反射之前,首先回顧下什麼是正向處理。如果我們需要例項化一個物件,首先需要匯入這個類的包,在這個包中找這個類: package CODE.反射; import java.util.Date; public class Fan { public static

1600802047 android 第三次作業音樂播放

一、實現的功能 播放、暫停、上一首、下一首    顯示列表 二、UI介面截圖   第一首歌   第二首歌   第三首歌   第四首歌 list列表   點選播放音樂時圖片旋轉,點選上一首切換上一

一起Talk Android第一百回:Android使用自定義控制元件

各位看官們,大家好,上一回中咱們說的是Android中使用自定義佈局的例子,這一回說的例子是Android中使用自定義控制元件。閒話休提,言歸正轉。讓我們一起Talk Android吧! 看官們,我們在上一回中通過自定義佈局巧妙地實現了分隔線,不過這個分隔線中看

Android開發筆記一百六十休眠模式下的定時控制

定時器AlarmManager常常用於需要週期性處理的場合,比如鬧鐘提醒、任務輪詢等等。並且定時器來源於系統服務,即使App已經不在運行了,也能收到定時器發出的廣播而被喚醒。似此迴光返照的神技,便遭到開發者的濫用,造成使用者手機充斥著各種殺不光程序,就算通過手機安全工具一再地

Android自定義DataTimePicker日期選擇

package com.wwj.datetimepicker; import java.text.SimpleDateFormat; import java.util.Calendar; import android.app.Activity; import android.app.AlertDialog

AndroidListview--排序ListView

 ListView的A-Z字母排序和過濾搜尋功能並且實現漢字轉成拼音的功能,我們知道一般我們對聯絡人,城市列表等實現A-Z的排序,因為聯絡人和城市列表我們可以直接從資料庫中獲取他的漢字拼音,而對於一般的資料,我們怎麼實現A-Z的排序,我們需要將漢字轉換成拼音就行

Android屬性動畫 TimeInterpolator插值

OK,繼續學習屬性動畫,本篇文章是屬性動畫系列的第三篇文章了,今天來學習一下屬性動畫中的TimeInterpolator,如果你對屬性動畫還不太熟悉,可以點選下面的連結學習一下前兩篇文章的知識: 1.介紹 先說說Interpolator,在And