<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
這裡接上一篇LayoutInflater原始碼分析繼續分析。
這裡詳細理一理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
是個遞迴方法,程式碼很簡單,遞迴-判斷型別決定是否繼續遞迴-遞迴。
我們知道,遞迴最重要的就是結束條件的選取,這裡的結束條件有這麼幾個:
其實1和3都是常規的結束條件,最重要的是2
這個條件,這個結束條件保證了當前回圈唯讀取本層的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.開始解析 //獲取當前節點的事件型別 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,分別是LinearLayout
和Button2
,depth都是2,結合上面的rinflate
的程式碼可以理解,在View的遞迴樹上,XmlPullParser的depth保證了層級,只會處理當前層級的View。
方法體中做了型別的判斷,特殊判斷了幾種型別如下:
非容器控制元件標籤中放標籤,表示將當前控制元件設為焦點,可以放到標籤裡面,多個EditText的時候使用標籤首先獲得焦點。
標籤裡面都可以放,類似於程式碼中使用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()
,有引數無引數獲取的不是同一個屬性。
這裡還對標籤做了二次的判斷,保證標籤不會出現在非root元素的位置。 如果不是上述特殊的標籤,使用createViewFromTag
載入出來view,並用當前的attrs載入成LayoutParams設定給當前View,繼續向下遞迴的同時把view add到parent.
<include>
標籤可以實現在一個layout中參照另一個layout的佈局,這通常適合於介面佈局複雜、不同介面有共用佈局的APP中,比如一個APP的頂部佈局、側邊欄佈局、底部Tab欄佈局、ListView和GridView每一項的佈局等,將這些同一個APP中有多個介面用到的佈局抽取出來再通過<include>
標籤參照,既可以降低layout的複雜度,又可以做到佈局重用(佈局有改動時只需要修改一個地方就可以了)。
這些型別之外就類似於之前分析過的處理,先呼叫createViewFromTag
方法建立View,設定attrs屬性,再呼叫遞迴方法rInflateChildren
把view的子View add到view上,然後新增到parent上,直到層級遍歷結束。
下面重點看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屬性,如果有,就重新建立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裡面是否有layout的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 >標籤的屬性去建立params,判斷的標準是有沒有width/height屬性 // 如果沒有則使用view的屬性去建立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 />
標籤是否有layout屬性,並應用適合的theme
屬性<merge>
,不同的方式載入對應的view,替換對應的屬性<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();
這是方式②
和③
的區別,方式②
說明傳過來的context就有theme,方式③
表示能從attrs中找到theme屬性,所以hasThemeOverride=true
,如果需要覆蓋就用當前view的theme重新建立了ContextThemeWrapper
。這兩者有一即可。
// 檢視當前view的attrs裡面是否有layout的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/
下面找。
這段的程式碼其實看上面程式碼裡的註釋就好了,很清晰。載入替換的layout有兩種情況:
1.merge
標籤,我們知道 merge標籤
用於降低View樹的層次來優化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
的對應屬性
設定完上面的屬性之後,繼續呼叫rInflateChildren
去遞迴載入完所有的子view
。
其實這個邏輯很像剛inflate剛開始執行時候的邏輯,可以回憶一下之前的程式碼。
這裡有個小demo來看清這幾個的區別:
#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>
顯示效果圖如下:
大致覆蓋了上面說的幾種include的方式
<include />
設定了theme可以不設定layout屬性<merge />
標籤和非<merge />
標籤的區別<include />
標籤設定了width/height
和其他位置相關的屬性會使用外面設定的屬性覆蓋子View的屬性,include_1
沒有設定屬性所以使用的是include_test_viewgroup的屬性,include_2
設定了位置相關屬性所以使用了設定的屬性,從實際顯示效果能看得出來。android:background="?attr/colorPrimary"
可以看出來rinflate是遞迴方法,主要的遞迴判斷條件是XmlPullParser的depth
rinflate中判斷了多種型別,有requestFocus和tag這些特殊標籤的處理,View的建立還是會呼叫createViewFromTag來處理
如果是include標籤會使用parseInclude方法,因為<include />
標籤的特殊性,會有一些<include />
和真實標籤的屬性和theme的判斷和替換
<include />
設定theme就替換掉父佈局的theme,兩種方式設定layout屬性,標籤中直接設定layout或者使用theme中的layout。
<include />
標籤中設定了位置屬性會替換子View的屬性,<include />
設定了id和visibility一定會生效。
以上就是原始碼分析Android rinflate的使用的詳細內容,更多關於Android rinflate的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45