[置顶] Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
2017-10-08 17:29
656 查看
本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址:http://blog.csdn.net/l540675759/article/details/78176074
Android中LayoutInflater(布局加载器)系列博文说明
Android 中LayoutInflater(布局加载器)系列之介绍篇
Android 中LayoutInflater(布局加载器)系列之源码篇
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
Android 中LayoutInflater(布局加载器)之实战篇
本篇专门介绍解析《include》标签的解析流程,具体分成以下几部分:
include标签涉及到theme时的相关处理
获取include标签中的layout资源
处理include包裹的内容
从上来代码中,可以发现parseInclude()是在rInflate()中出现,作用是处理当前节点是Include标签时的状况。
而rInflater()这个方法的作用是,解析某个节点,根据节点的不同类型从而进行不同的处理,如果想深入了解可以参考这篇博客:
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
先把parseInclude()这个方法全景先看下,然后我们在进行分拆,一部分一部分分析。
(1)解析器 -> XmlPullParser parser
用来解析XML文件的解析器,通过解析器可以得到当前节点的相对应的AttributeSet(属性集)
(2)上下文对象 - > Context context
当前加载该XML的上下文对象,并且这个Context与LayoutInflater属于相互绑定关系(一一对应)
(3)父容器 - > View parent
包裹该节点的父容器,一般来说都是继承ViewGroup实现的视图组
(4)属性集 -> AttributeSet attrs
该节点的属性集,包括所有该节点的相关属性
一般来说theme(主题)一般出现在Activtiy的AndroidManifest文件下,来给Activity设置统一的布局效果,而且可以使用如下的操作来进行主题属性的使用。
如果Include标签下设置了新的theme,那么Include中的内容在使用主题属性时,使用的theme主题就是(include)设置的内容,而不是Activity默认下的主题,形成了一种覆盖效果。
也就是说Include标签设置的主题可以覆盖Activity设置的根主题,但是Include设置的主题只作用与Include内部。
举个栗子:
style.xml
先定义好两个基础Theme,一个是作为App的基础主题,另一个是include中的主题。
AndroidManifest.xml
设置Activity的基础主题为AppTheme
activity_main.xml
接下来,我们在看一下Include包裹的布局
test_toolbar.xml
从上面的XML文件我们可以看出两个Toolbar调用的background都指向theme的colorPrimary属性,接下来看一下显示效果:
从效果图可以发现,Include Toolbar显示的颜色是粉色的,也就是Include额外设置的theme,这里也是从正面证明了这个概念。
通过上面的介绍,很明显这段代码含义,就是检测是否给Include标签设置了Theme属性,如果设置theme,就创建相应的ContextThemeWrapper,用于之后子标签的解析时theme的使用。
这部分的内容主要是提取Include的内容布局的提取,Include的内容布局的设置有两种:
第一种 : 直接@layout 后面设置布局的XML
第二种:通过引入theme的item设置的layout属性
Include标签下:
包裹Include标签的布局Theme(注意:这里不是Include设置的主题):
而上面的代码的作用是检索layout属性,如果layout已经以第一种方式引入,就不需要在去theme中检索,如果layout第一种方式检索不到资源ID,那么就会去以第二种方式进行检索。
这部分主要的作用是解析Include包裹layout的根标签:
(1)先特别处理Merge标签 :
如果子节点是Merge标签,那么直接进行内容的解析,调用rInflater()方法。
而rInflater()这个方法的作用是,解析某个节点,根据节点的不同类型从而进行不同的处理,如果想深入了解可以参考这篇博客:
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
(2)解析Include的内容:
在这之前先通过createViewFromTag()方法,根据名称来生成相对应的View,具体的解析请参考这篇博客:
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
这里分成两块内容,第一块是设置LayoutParams:
这段的作用是为Include的包裹的根View设置LayoutParams,使用的LayoutParams默认是Include外层的ViewGroup。
如果此时Params加载失败,那就会使用Include包裹的ViewGroup的LayoutParams,反正怎么都得设置一个。
第二块是在这里设置子ViewGroup的显隐性:
设置ViewGroup的显隐性,之后就将其添加至父View中,至此parseInclude的分析就到此结束。
博客地址:http://blog.csdn.net/l540675759/article/details/78176074
前言
如果读者没有阅读过该系列博客,建议先阅读下博文说明,这样会对后续的阅读博客思路上会有一个清晰的认识。Android中LayoutInflater(布局加载器)系列博文说明
导航
Android 中LayoutInflater(布局加载器)系列博文说明Android 中LayoutInflater(布局加载器)系列之介绍篇
Android 中LayoutInflater(布局加载器)系列之源码篇
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法
Android 中LayoutInflater(布局加载器)之实战篇
概述
本篇博客,是作为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()这个方法的作用是,解析某个节点,根据节点的不同类型从而进行不同的处理,如果想深入了解可以参考这篇博客:
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
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()这个方法的作用是,解析某个节点,根据节点的不同类型从而进行不同的处理,如果想深入了解可以参考这篇博客:
Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
(2)解析Include的内容:
在这之前先通过createViewFromTag()方法,根据名称来生成相对应的View,具体的解析请参考这篇博客:
Android 中LayoutInflater(布局加载器)源码篇之createViewFromTag方法
这里分成两块内容,第一块是设置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(布局加载器)源码篇之createViewFromTag方法
- [置顶] Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
- [置顶] Android 中LayoutInflater(布局加载器)之源码篇
- [置顶] Android 中LayoutInflater(布局加载器)之实战篇
- [置顶] Android 中LayoutInflater(布局加载器)系列博文说明
- [置顶] Android 中LayoutInflater(布局加载器)之介绍篇
- android三种布局优化的方法:include,merge,ViewStub
- Android 中LayoutInflater(布局加载器)系列博文说明
- android布局中如何把include布局隐藏掉的方法
- Android开发中自定义string、color、style、drawable,title,布局页面(include)的资源使用方法.txt
- [置顶] android 布局之滑动探究 scrollTo 和 scrollBy 方法使用说明
- 【android开发】之【android动态布局方法总结】
- android界面xml文件中导入另一个xml文件的方法include
- Android布局整合include界面控件(重用布局)
- Android布局优化几个重要标签的使用include、merge、ViewStub、requestFocus
- [置顶] Android开发12:Preference布局结构小议
- Android布局整合include界面控件
- Android中View绘制优化二一---- 使用<include />标签复用布局文件
- 【Android动态布局】之【使用addView方法时,如何保持已有动态控件位置不被改变】
- 【Android Training - Performance】提高显示布局文件的性能[Lesson 2 - 使用include标签重用Layout]