Android 中LayoutInflater(佈局載入器)原始碼篇之parseInclude方法
前言
如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。
導航
概述
本篇部落格,是作為Android中LayoutInflater(佈局載入器)原始碼篇的一個補充,至此LayoutInflater中幾個大模組在這個系列的博文中,已經分析完畢了。
本篇專門介紹解析《include》標籤的解析流程,具體分成以下幾部分:
include標籤涉及到theme時的相關處理
獲取include標籤中的layout資源
處理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屬性,接下來看一下顯示效果:
從效果圖可以發現,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的分析就到此結束。
流程圖
相關推薦
Android 中LayoutInflater(佈局載入器)原始碼篇之parseInclude方法
前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是作為Android中LayoutInflater(佈局載入器)原
Android 中LayoutInflater(佈局載入器)原始碼篇之rInflate方法
前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是屬於Android 中LayoutInflater(佈局載入器)原始
Android 中LayoutInflater(佈局載入器)之原始碼篇
前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 (1)Activity 的 getSystemService的實現過程 (2
Android 中LayoutInflater(佈局載入器)之實戰篇
前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 效果 可以看出在滑動時,會出現視覺差效果。 可以看出在滑動時,物品會飄出去。
Android 中LayoutInflater(佈局載入器)之介紹篇
前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 本篇作為Android 中LayoutInflater(佈局載入器)系列的介紹篇,該篇內容知識內容比較基礎,建議先看一些概述,如果感覺
PyQt5(2)——調整佈局(佈局管理器)第一個程式
我們拖拽一個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
Java中GUI簡介、AWT概述、以及佈局管理器(流式佈局管理器、邊界佈局管理器、網格佈局管理器、網格包佈局管理器、卡片佈局管理器)
1 GUI簡介 GUI的全稱是Graphical User Interface,即圖形使用者介面。顧名思義,就是應用程式提供給使用者操作的圖形介面,包括視窗、選單、按鈕、工具欄和其他各種使用者介面元素。Java中針對GUI設計提供了豐富的類庫,這些類分別位
Android中ListView上拉載入(分頁)功能
思路 1新增頁尾,並隱藏 2監聽滑動事件,判斷當滑到低部時,顯示頁尾,並載入資料(介面回撥到activity中載入) 3資料新增完成之後隱藏頁尾 效果圖: 專案結構: 自定義listView類LoadListView package com.zhh.android;
Android中ListView錯位佈局實現(無聊向)
由於某些原因,需要個錯位的頁面,在網上找不到好的例子,試著動手寫了寫。 不考慮配色的完成圖如下: 首先考慮的是,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
Android中Listview(七)--排序ListView
ListView的A-Z字母排序和過濾搜尋功能並且實現漢字轉成拼音的功能,我們知道一般我們對聯絡人,城市列表等實現A-Z的排序,因為聯絡人和城市列表我們可以直接從資料庫中獲取他的漢字拼音,而對於一般的資料,我們怎麼實現A-Z的排序,我們需要將漢字轉換成拼音就行
Android屬性動畫(三) TimeInterpolator(插值器)
OK,繼續學習屬性動畫,本篇文章是屬性動畫系列的第三篇文章了,今天來學習一下屬性動畫中的TimeInterpolator,如果你對屬性動畫還不太熟悉,可以點選下面的連結學習一下前兩篇文章的知識: 1.介紹 先說說Interpolator,在And