本文出自博客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》标签的解析流程,具体分成以下几部分:

  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(" 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属性,接下来看一下显示效果:

Android 中LayoutInflater(布局加载器)源码篇之parseInclude方法_第1张图片

从效果图可以发现,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 中LayoutInflater(布局加载器)源码篇之parseInclude方法_第2张图片

更多相关文章

  1. 68.android 简单的布局展示不全的问题,明明写的是android:layout_
  2. Android 中LayoutInflater(布局加载器)源码篇之rInflate方法
  3. Android 解决沉浸式状态栏下,输入法弹出,布局不会自动调整的BUG
  4. 【android】初学安卓,简单布局和activity切换
  5. 布局(一)
  6. [导入]2010-03-06 传智播客—Android(六)通知、样式、主题、HTML

随机推荐

  1. Android ADB的一些用法
  2. android扫一扫 二维码显示结果中文乱码
  3. android混淆proguard
  4. Android无需root实现apk的静默安装
  5. 反弹效果实现
  6. 基于 CentOS 使用 Jenkins 实现 Android(
  7. Android中自定义属性的格式详解
  8. 将war包部署到android服务器上
  9. Android —— Retrofit 请求注解分析
  10. Android killer和Apktool回编译错误No re