Android布局加载之LayoutInflater示例详解

前言

Activity 在界面创建时需要将 XML 布局文件中的内容加载进来,正如我们在 ListView 或者 RecyclerView 中需要将 Item 的布局加载进来一样,都是使用 LayoutInflater 来进行操作的。

LayoutInflater 实例的获取有多种方式,但最终是通过(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来得到的,也就是说加载布局的 LayoutInflater 是来自于系统服务的。

由于 Android 系统源码中关于 Content 部分采用的是装饰模式,Context 的具体功能都是由 ContextImpl 来实现的。通过在 ContextImpl 中找到getSystemService的代码,一路跟进,得知最后返回的实例是PhoneLayoutInflater。

  registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
    new CachedServiceFetcher<LayoutInflater>() {
   @Override
   public LayoutInflater createService(ContextImpl ctx) {
    return new PhoneLayoutInflater(ctx.getOuterContext());
   }});

LayoutInflater 只是一个抽象类,而 PhoneLayoutInflater 才是具体的实现类。

inflate 方法加载 View

使用 LayoutInflater 时常用方法就是inflate方法了,将一个布局文件 ID 传入并最后解析成一个 View 。

LayoutInflater 加载布局的 inflate 方法也有多种重载形式:

View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

而这两者的差别就在于是否要将 resource 布局文件加载到 root布局中去。

不过有点需要注意的地方,若 root为 null,则在 xml 布局中为 resource设置的属性会失效,只是单纯的加载布局。

     // temp 是 xml 布局中的顶层 View
     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
     ViewGroup.LayoutParams params = null;
     if (root != null) { // root
      // root 不为 null 才会生成 layoutParams
      params = root.generateLayoutParams(attrs);
      if (!attachToRoot) {
       // 如果不添加到 root 中,则直接把布局参数设置给 temp
       temp.setLayoutParams(params);
      }
     }
     // 加载子 View
     rInflateChildren(parser, temp, attrs, true);
     if (root != null && attachToRoot) {
      root.addView(temp, params);//添加到布局中,则布局参数用到 addView 中去
     }
     if (root == null || !attachToRoot) {
      result = temp;
     }

跟进createViewFromTag方法查看 View 是如何创建出来的。

   View view; // 最后要返回的 View
   if (mFactory2 != null) {
    view = mFactory2.onCreateView(parent, name, context, attrs); // 是否设置了 Factory2
   } else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs); // 是否设置了 Factory
   } else {
    view = null;
   }
   if (view == null && mPrivateFactory != null) { // 是否设置了 PrivateFactory
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
   }
   if (view == null) { // 如果的 Factory 都没有设置过,最后在生成 View
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
     if (-1 == name.indexOf('.')) { // 系统控件
      view = onCreateView(parent, name, attrs);
     } else { // 非系统控件,自定义的 View
      view = createView(name, null, attrs);
     }
    } finally {
     mConstructorArgs[0] = lastContext;
    }
   }

如果设置过 Factory 接口,那么将由 Factory 中的 onCreateView 方法来生成 View 。

关于 LayoutInflater.Factory 的作用,就是用来在加载布局时可以自行去创建 View,抢在系统创建 View 之前去创建。

关于 LayoutInflater.Factory 的使用场景,现在比较多的就是应用的换肤了。

若没有设置过 Factory 接口,则是判断是否为自定义控件或者系统控件,不管是 onCreateView 方法还是 createView 方法,内部最终都是调用到了 createView 方法,通过它来生成 View 。

// 通过反射生成 View 的参数,分别是 Context 和 AttributeSet 类
static final Class<?>[] mConstructorSignature = new Class[] {
   Context.class, AttributeSet.class};
public final View createView(String name, String prefix, AttributeSet attrs)
   throws ClassNotFoundException, InflateException {
  Constructor<? extends View> constructor = sConstructorMap.get(name);
  Class<? extends View> clazz = null;
  if (constructor == null) { // 从缓存中得到 View 的构造器,没有则调用 getConstructor
    clazz = mContext.getClassLoader().loadClass(
      prefix != null ? (prefix + name) : name).asSubclass(View.class);
    if (mFilter != null && clazz != null) {
     boolean allowed = mFilter.onLoadClass(clazz);
     if (!allowed) {
      failNotAllowed(name, prefix, attrs);
     }
    }
    constructor = clazz.getConstructor(mConstructorSignature);
    constructor.setAccessible(true);
    sConstructorMap.put(name, constructor);
   } else {
    // If we have a filter, apply it to cached constructor
    if (mFilter != null) { // 过滤,是否允许生成该 View
     // Have we seen this name before?
     Boolean allowedState = mFilterMap.get(name);
     if (allowedState == null) {
      // New class -- remember whether it is allowed
      clazz = mContext.getClassLoader().loadClass(
        prefix != null ? (prefix + name) :     name).asSubclass(View.class);
      boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
      mFilterMap.put(name, allowed);
      if (!allowed) {
       failNotAllowed(name, prefix, attrs);
      }
     } else if (allowedState.equals(Boolean.FALSE)) {
      failNotAllowed(name, prefix, attrs); // 不允许生成该 View
     }
    }
   }
  Object[] args = mConstructorArgs;
  args[1] = attrs;
  final View view = constructor.newInstance(args); // 通过反射生成 View
  return view;

在 createView 方法内部,首先从 View 的构造器缓存中查找是否有对应的缓存,若没有则生成构造器并且放到缓存中去,若有构造器则看能否通过过滤,是否允许该 View 生成。

最后都满足条件的则是通过 View 的构造器反射生成了 View 。

在生成 View 时采用 Constructor.newInstance调用构造函数,而参数所需要的变量就是mConstructorSignature变量所定义的,分别是 Context 和 AttributeSet。可以看到,在最后生成 View 时也传入了对应的参数。

采用 Constructor.newInstance的形式反射生成 View ,是为了解耦,只需要有了类名,就可以加载出来。

由此可见,LayoutInflater 加载布局仍然是需要传递 Context的,不光是为了得到 LayoutInflater ,在反射生成 View 时同样会用到。

深度遍历加载布局

如果需要加载的布局只有一个控件,那么 LayoutInflater 返回那个 View 工作也就结束了。

若布局文件中有多个需要加载的 View ,则通过rInflateChildren方法继续加载顶层 View 下的 View ,最后通过rInflate方法来加载。

void rInflate(XmlPullParser parser, View parent, Context context,
   AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
  final int depth = parser.getDepth();
  int type;
  // 若 while 条件不成立,则加载结束了
  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(); // 从 XmlPullParser 中得到 name 出来解析
   if (TAG_REQUEST_FOCUS.equals(name)) { // name 各种情况下的解析
    parseRequestFocus(parser, parent);
   } 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); // 顶层 View 添加 子 View
   }
  }
  if (finishInflate) { // 遍历解析
   parent.onFinishInflate();
  }
 }

rInflate方法首先判断是否解析结束了,若没有,则从 XmlPullParser 中加载出下一个 View 进行处理,中间还会对不同的类型进行处理,比如TAG_REQUEST_FOCUS、TAG_TAG、TAG_INCLUDE、TAG_MERGE等等。

最后仍然还是通过createViewFromTag来生成 View ,并以这个生成的 View 为父节点,开始深度遍历,继续调用rInflateChildren方法加载布局,并把这个 View 加入到它的父 View 中去。

至于为什么生成 View 的方法名字createViewFromTag从字面上来看是来自于 Tag标签,想必是和 XmlPullParser解析布局生成的内容有关。

总结

以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android getViewById和getLayoutInflater().inflate()的详解及比较

    Android getViewById和getLayoutInflater().inflate()的详解及比较                由于本人刚刚学习Android 对于getViewById和getLayoutInflater().inflate()的方法该如何使用不知如何分别,这里就上网查下资料整理下,大家可以看下. LayoutInflater 要明白这个问题首先要知道什么是LayoutInflater.根据Android的官方API解释: Instantiates a layou

  • Android开发之获取LayoutInflater对象的方法总结

    本文实例讲述了Android开发之获取LayoutInflater对象的方法.分享给大家供大家参考,具体如下: 在写Android程序时,有时候会编写自定义的View,使用Inflater对象来将布局文件解析成一个View.本文主要目的是总结获取LayoutInflater对象的方法. 1.若能获取context对象,可以有以下几种方法: LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYO

  • 基于Android LayoutInflater的使用介绍

    在android中,LayoutInflater有点类似于Activity的findViewById(id),不同的是LayoutInflater是用来找layout下的xml布局文件,并且实例化!而findViewById()是找具体xml下的具体 widget控件(如:Button,TextView等). 下面通过一个例子进行详细说明: 1.在res/layout文件夹下,添加一个xml文件dialog.xml 复制代码 代码如下: <LinearLayout xmlns:android=&qu

  • Android LayoutInflater加载布局详解及实例代码

    Android  LayoutInflater加载布局详解 对于有一定Android开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如 1.LayoutInflater为什么可以加载layout文件? 2.加载layout文件之后,又是怎么变成供我们使用的View的? 3.我们定义View的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢? 既然在这篇文章提出来,那说明这三

  • Android中使用LayoutInflater要注意的一些坑

    前言 在平时的开发过程中,我们经常会用LayoutInflater这个类,比如说在Fragment$onCreateView和RecyclerView.Adapter$onCreateViewHolder中都会用到.它的用法也无非就是LayoutInflater.inflate(resourceId, root, attachToRoot),第一个参数没什么好说的,但第二个和第三个参数结合起来会带来一定的迷惑性.之前有时候会发现界面布局上出了一些问题,查了很久之后偶然的改动了这两个参数,发现问题

  • Android 中LayoutInflater.inflate()方法的介绍

    Android 中LayoutInflater.inflate()方法的介绍 最近一直想弄明白LayoutInflater对象的inflate方法的用法,今天做了实例. <LinearLayout android:id="@+id/ll_item_Group" android:layout_width="match_parent" android:layout_height="200dp" android:background="

  • Android布局加载之LayoutInflater示例详解

    前言 Activity 在界面创建时需要将 XML 布局文件中的内容加载进来,正如我们在 ListView 或者 RecyclerView 中需要将 Item 的布局加载进来一样,都是使用 LayoutInflater 来进行操作的. LayoutInflater 实例的获取有多种方式,但最终是通过(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来得到的,也就是说加载布局的 LayoutInflat

  • Android 动态加载 so实现示例详解

    目录 背景 so动态加载介绍 从一个例子出发 so库检索与删除 动态加载so 结束了吗? ELF文件 扩展 总结 背景 对于一个普通的android应用来说,so库的占比通常都是巨高不下的,因为我们无可避免的在开发中遇到各种各样需要用到native的需求,所以so库的动态化可以减少极大的包体积,自从2020腾讯的bugly团队发部关于动态化so的相关文章后,已经过去两年了,相关文章,经过两年的考验,实际上so动态加载也是非常成熟的一项技术了. 但是很遗憾,许多公司都还没有这方面的涉略又或者说不知

  • 详解polyfills如何按需加载及场景示例详解

    目录 前言 青铜时代 火枪时代 webpack添加babel-loader @babel/preset-env @babel/polyfill 配置 useBuiltIns 加入 @babel/plugin-transform-runtime 前言 青铜时代 最使前端头痛的问题,莫过于浏览器兼容性,无论是js,还是css都要考虑浏览器兼容性问题,在webpack出来之前,这无非是一个非常头疼的问题,查到一个兼容性问题,查找很多资料,解决一下,再出来一个问题又要花很长时间解决一下,这无疑要花费很长

  • ECharts图表使用及异步加载的特性示例详解

    目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展.开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互.性能.数据处理等方面有更高的要求. chart.setOption({ color: [ '#c23531', '#2f4554', '#61a0a8', '#d48265', '#91c7ae', '#749f83', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3' ], // ...

  • 详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,PhoneWindow包裹DecorView.接下来我们首先看一下三者分别是如何创建的. 二.Activity是如何创建的 首先看到入口类ActivityThread的performLaunchActivity方法: private Activity performLaunchActivity(Activi

  • Android 应用程序的启动流程示例详解

    目录 应用进程的启动流程 1.ActivityStackSupervisor.startSpecificActivity 2.ATMS.startProcessAsync 3.LocalService.startProcess 4.startProcessLocked函数 5.ProcessList.startProcessLocked 6.ProcessList.startProcessLocked重载 7.ProcessList.startProcess 8.ZygoteState.star

  • linecache模块加载和缓存文件内容详解

    linecache模块 接触到linecache这个模块是因为前两天读attrs源码的时候看到内部代码引用了这个模块来模拟一个假文件,带着一脸疑问顺便读了一下这个模块的源码,发现其实也就那么回事儿,代码不多,在这总结一下. linecache模块可以读取文件并将文件内容缓存起来,方便后面多次读取.这个模块原本被设计用来读取Python模块的源代码,所以当一个文件名不在指定路径下的时候,模块会通过搜索路径(search path)来尝试读取文件. 接口 linecache模块的__all__参数其

  • vue 项目常用加载器及配置详解

    本文介绍了vue 项目常用加载器及配置详解,分享给大家,具体如下: 1.安装sass: 1.1 由于sass-loader依赖于node-sass,所以在安装sass-loader的同时还需安装node-sass npm install --save-dev node-sass npm install --save-dev sass-loader 1.2 安装完成后修改 <style>标签: <style lang="scss"></style> 2

  • Glide4 高效加载图片的配置详解

    本文介绍了Glide4 高效加载图片的配置详解,分享给大家,具体如下: 在build.gradle中添加glide依赖 // glide 依赖 compile 'com.github.bumptech.glide:glide:4.6.1' // glide 相关注解,生成GlideApp代码 annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' // Glide网络库配置成okhttp3 compile ('com.gith

  • python从内存地址上加载python对象过程详解

    这篇文章主要介绍了python从内存地址上加载pythn对象过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在python中我们可以通过id函数来获取某个python对象的内存地址,或者可以通过调用对象的__repr__魔术函数来获取对象的详细信息 def tt(): print(111) print(tt.__repr__()) print(id(tt)) 但是不知大家是否想过,其实这个内存地址可以直接加载python对象的.有两种方

随机推荐