热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android布局加载之LayoutInflater示例详解

这篇文章主要介绍了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() {
   @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<&#63;>[] mCOnstructorSignature= new Class[] {
   Context.class, AttributeSet.class};
public final View createView(String name, String prefix, AttributeSet attrs)
   throws ClassNotFoundException, InflateException {
  Constructor<&#63; extends View> cOnstructor= sConstructorMap.get(name);
  Class<&#63; extends View> clazz = null;
  if (cOnstructor== null) { // 从缓存中得到 View 的构造器,没有则调用 getConstructor
    clazz = mContext.getClassLoader().loadClass(
      prefix != null &#63; (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&#63;
     Boolean allowedState = mFilterMap.get(name);
     if (allowedState == null) {
      // New class -- remember whether it is allowed
      clazz = mContext.getClassLoader().loadClass(
        prefix != null &#63; (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(" cannot be the root element");
    }
    parseInclude(parser, context, parent, attrs);
   } else if (TAG_MERGE.equals(name)) {
    throw new InflateException(" 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开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 本文详细介绍如何使用arm-eabi-gdb调试Android平台上的C/C++程序。通过具体步骤和实用技巧,帮助开发者更高效地进行调试工作。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 几何画板展示电场线与等势面的交互关系
    几何画板是一款功能强大的物理教学软件,具备丰富的绘图和度量工具。它不仅能够模拟物理实验过程,还能通过定量分析揭示物理现象背后的规律,尤其适用于难以在实际实验中展示的内容。本文将介绍如何使用几何画板演示电场线与等势面之间的关系。 ... [详细]
  • 本文介绍如何通过Windows批处理脚本定期检查并重启Java应用程序,确保其持续稳定运行。脚本每30分钟检查一次,并在需要时重启Java程序。同时,它会将任务结果发送到Redis。 ... [详细]
author-avatar
蛋蛋小可爱的诱惑_360
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有