Android(安卓)中LayoutInflater(布局加载器)源码篇之parseInclude方法
本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
博客地址: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(" 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: "); } //从 ?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(" 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中的主题。
AndroidManifest.xml
设置Activity的基础主题为AppTheme
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
接下来,我们在看一下Include包裹的布局
test_toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
从上面的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: "); } //从?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设置的主题):
而上面的代码的作用是检索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(" 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 EditText设置不可写
- android“设置”里的版本号
- 在Fragment中设置控件点击方法,执行失败。
- Android(安卓)闹钟管理类的使用
- Android设置通知栏/状态栏透明改变通知栏颜色和app最上部分颜色
- android 设置中划线 下划线等
- Andorid Dialog 示例【慢慢更新】
- android图表ichartjs
- Android开发——Android搜索框架(二)