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

Android  LayoutInflater加载布局详解

对于有一定Android开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如

1.LayoutInflater为什么可以加载layout文件?
2.加载layout文件之后,又是怎么变成供我们使用的View的?
3.我们定义View的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢?

既然在这篇文章提出来,那说明这三个问题都是跟LayoutInflater脱不了干系的。在我们的分析过程中,会对这些问题一一进行解答。

我们一步一步来,首先当我们需要从layout中加载View的时候,会调用这个方法

LayoutInflater.from(context).inflater(R.layout.main_activity,null);

1.如何创建LayoutInflater?

这有什么值得说的?如果你打开了LayoutInflater.Java你自然就明白了,LayoutInflater是一个抽象类,而抽象类是不能直接被实例化的,也就是说我们创建的对象肯定是LayoutInflater的某一个实现类。

我们进入LayoutInflater.from方法中可以看到

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
      throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
  }

好吧,是获取的系统服务!是从context中获取,没吃过猪肉还没见过猪跑么,一说到context对象十有八九是说得ContextImpl对象,于是我们直接去到ContextImpl.java中,找到getSystemService方法

@Override
  public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
  }

额。。。又要去SystemServiceRegistry.java文件中

  /**
   * Gets a system service from a given context.
   */
  public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
  }

由代码可知,我们的Service是从SYSTEM_SERVICE_FETCHERS这个HashMap中获得的,而稍微看一下代码就会发现,这个HashMap是在static模块中赋值的,这里注册了很多的系统服务,什么ActivityService,什么AlarmService等等都是在这个HashMap中。从LayoutInflater.from方法中可以知道,我们找到是Context.LAYOUT_INFLATER_SERVICE对应的Service

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

好啦,主角终于登场了——PhoneLayoutInflater,我们获取的LayoutInflater就是这个类的对象。

那么,这一部分的成果就是我们找到了PhoneLayoutInflater,具体有什么作用,后面再说。

2.inflater方法分析

这个才是最重要的方法,因为就是这个方法把我们的layout转换成了View对象。这个方法直接就在LayoutInflater抽象类中定义

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
  }

传入的参数一个是layout的id,一个是是否指定ParentView,而真正的实现我们还得往下看

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
      Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
          + Integer.toHexString(resource) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
      return inflate(parser, root, attachToRoot);
    } finally {
      parser.close();
    }
  }

我们先从context中获取了Resources对象,然后通过res.getLayout(resource)方法获取一个xml文件解析器XmlResourceParser(关于在Android中的xml文件解析器这里就不详细讲了,免得扯得太远,不了解的同学可以在网上查找相关资料阅读),而这其实是把我们定义layout的xml文件给加载进来了。

然后,继续调用了另一个inflate方法

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
      final Context inflaterContext = mContext;
      //快看,View的构造函数中的attrs就是这个!!!
      final AttributeSet attrs = Xml.asAttributeSet(parser);

      //这个数组很重要,从名字就可以看出来,这是构造函数要用到的参数
      mConstructorArgs[0] = inflaterContext;
      View result = root;

      try {
        // 找到根节点,找到第一个START_TAG就跳出while循环,
        // 比如<TextView>是START_TAG,而</TextView>是END_TAG
        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
          // Empty
        }

        if (type != XmlPullParser.START_TAG) {
          throw new InflateException(parser.getPositionDescription()
              + ": No start tag found!");
        }
        //获取根节点的名称
        final String name = parser.getName();

        //判断是否用了merge标签
        if (TAG_MERGE.equals(name)) {
          if (root == null || !attachToRoot) {
            throw new InflateException("<merge /> can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
          }
          //解析
          rInflate(parser, root, inflaterContext, attrs, false);
        } else {
          // 这里需要调用到PhoneLayoutInflater中的方法,获取到根节点对应的View
          final View temp = createViewFromTag(root, name, inflaterContext, attrs);

          ViewGroup.LayoutParams params = null;
          //如果指定了parentView(root),则生成layoutParams,
          //并且在后面会将temp添加到root中
          if (root != null) {
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
              temp.setLayoutParams(params);
            }
          }

          // 上面解析了根节点,这里解析根节点下面的子节点
          rInflateChildren(parser, temp, attrs, true);

          if (root != null && attachToRoot) {
            root.addView(temp, params);
          }

          if (root == null || !attachToRoot) {
            result = temp;
          }
        }

      } catch (Exception e) {

      } finally {
        // Don't retain static reference on context.
        mConstructorArgs[0] = lastContext;
        mConstructorArgs[1] = null;
      }

      return result;
    }
  }

这个就稍微有点长了,我稍微去除了一些跟逻辑无关的代码,并且添加了注释,如果有耐心看的话应该是能看懂了。这里主要讲两个部分,首先是rInflateChildren这个方法,其实就是一层一层的把所有节点取出来,然后通过createViewFromTag方法将其转换成View对象。所以重点是在如何转换成View对象的。

3.createViewFromTag

我们一层层跟进代码,最后会到这里

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
      boolean ignoreThemeAttr) {
      ......
      ......
    try {
      ......

      if (view == null) {
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try {
          //不含“.” 说明是系统自带的控件
          if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
          } else {
            view = createView(name, null, attrs);
          }
        } finally {
          mConstructorArgs[0] = lastContext;
        }
      }

      return view;
    } catch (InflateException e) {
      throw e;
      ......
    }
  }

为了方便理解,将无关的代码去掉了,我们看到其实就是调用的createView方法来从xml节点转换成View的。如果name中不包含'.' 就调用onCreateView方法,否则直接调用createView方法。

在上面的PhoneLayoutInflater中就复写了onCreateView方法,而且不管是否重写,该方法最后都会调用createView。唯一的区别应该是系统的View的完整类名由onCreateView来提供,而如果是自定义控件在布局文件中本来就是用的完整类名。

4. createView方法

public final View createView(String name, String prefix, AttributeSet attrs)
      throws ClassNotFoundException, InflateException {
    //1.通过传入的类名,获取该类的构造器

    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {
      if (constructor == null) {

        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 (mFilter != null) {
          Boolean allowedState = mFilterMap.get(name);
          if (allowedState == null) {
            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);
          }
        }
      }

      //2.通过获得的构造器,创建View实例
      Object[] args = mConstructorArgs;
      args[1] = attrs;

      final View view = constructor.newInstance(args);
      if (view instanceof ViewStub) {
        final ViewStub viewStub = (ViewStub) view;
        viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
      }
      return view;

    } catch (NoSuchMethodException e) {
     ......
    }
  }

这段代码主要做了两件事情

第一,根据ClassName将类加载到内存,然后获取指定的构造器constructor。构造器是通过传入参数类型和数量来指定,这里传入的是mConstructorSignature

static final Class<?>[] mConstructorSignature = new Class[] {
      Context.class, AttributeSet.class};

即传入参数是Context和AttributeSet,是不是猛然醒悟了!!!这就是为什么我们在自定义View的时候,必须要重写View(Context context, AttributeSet attrs)则个构造方法,才能在layout中使用我们的View。

第二,使用获得的构造器constructor来创建一个View实例。

5.回答问题

还记得上面我们提到的三个问题吗?现在我们来一一解答:

1.LayoutInflater为什么可以加载layout文件?

因为LayoutInflater其实是通过xml解析器来加载xml文件,而layout文件的格式就是xml,所以可以读取。

2.加载layout文件之后,又是怎么变成供我们使用的View的?

LayoutInflater加载到xml文件中内容之后,通过反射将每一个标签的名字取出来,并生成对应的类名,然后通过反射获得该类的构造器函数,参数为Context和AttributeSet。然后通过构造器创建View对象。

3.我们定义View的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢?

因为LayoutInflater在解析xml文件的时候,会将xml中的内容转换成一个AttributeSet对象,该对象中包含了在xml文件设定的属性值。需要在构造函数中将这些属性值取出来,赋给该实例的属性。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

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

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

  • 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对象的方法总结

    本文实例讲述了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 getViewById和getLayoutInflater().inflate()的详解及比较

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

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

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

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

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

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

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

  • Canvas 制作动态进度加载水球详解及实例代码

    Canvas 动态进度加载水球 前言 之前看到一些球型的动态加载的效果,一直想自己动手做一个,正好这段时间重温了一个Canvas,所以就尝试了一下. 实现思路 关于水波的实现,使用了sin()函数,通过每一帧不断的移动sin()函数曲线,实现水波动态效果.然后,通过绘制圆形路径,进行clip(),实现球型效果. sin()函数相关 这里说一下sin()函数的相关基础,对于绘制水波的影响. 看一下图,回顾一下中学sin()函数的基础. 从图中可以看出,当函数为sin(x)时,值域为[-1, 1],

  • Android刷新加载框架详解

    本文实例为大家分享了Android刷新加载框架的具体代码,供大家参考,具体内容如下 1.定义一个接口控制下拉和上拉 public interface Pullable { /** * 是否可下拉 */ boolean canPullDown(); /** * 是否可上拉 */ boolean canPullUp(); } 2.定义一个刷新加载布局 public class PullToRefreshLayout extends RelativeLayout { /** * 头 */ privat

  • Android 活动条ActionBar的详解及实例代码

    Android 活动条ActionBar的详解 图一 图二 图三 图四 图五 ActionBar其提供的功能总结 图一使用ActionBar显示选项菜单项 menu_mainxml代码 启用程序图标导航 如何添加Action View 图二Activity代码区 menu_mainxml代码 clockxml 图三使用ActionBar实现Tab导航 创建ActionBar实现Tab导航步骤 MainActivity代码 DummyFragmentjava代码 图四Android 30之前的Fr

  • Android 欢迎全屏图片详解及实例代码

    Android 欢迎全屏图片详解 其实欢迎界面就是在主Activity之前再添加一个欢迎的Activity.在这个Activity中实现欢迎界面,和其他的Activity用法 是基本一样,只有细微的差别.     1.在Activity的onCreate方法中实现: @Override ic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /**全屏设置,隐藏窗口所有装饰**/ getW

  • Android Handler移除Message详解及实例代码

    Android Handler移除Message详解 问题: 1.removeMessage(what)函数是否只能移除对应what值的Message? 2.对于Delayed发送的Message,能否提前remove? 代码测试: package javine.k.testhandler; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Han

  • Android ListView 滚动条的设置详解及实例代码

    Android ListView 滚动条的设置详解 1.滚动条的属性 android:scrollbarAlwaysDrawHorizontalTrack 设置是否始终显示水平滚动条.这里用ScrollView.ListView测试均没有效果. android:scrollbarAlwaysDrawVerticalTrack 设置是否始终显示垂直滚动条.这里用ScrollView.ListView测试均没有效果. android:scrollbarDefaultDelayBeforeFade 设

  • Java 配置加载机制详解及实例

    前言 现如今几乎大多数Java应用,例如我们耳熟能详的tomcat, struts2, netty-等等数都数不过来的软件,要满足通用性,都会提供配置文件供使用者定制功能. 甚至有一些例如Netty这样的网络框架,几乎完全就是由配置驱动,这样的软件我们也通常称之为"微内核架构"的软件.你把它配置成什么,它就是什么. It is what you configure it to be. 最常见的配置文件格式是XML, Properties等等文件. 本文探讨加载配置中最通用也是最常见的场

  • Android Listview中显示不同的视图布局详解及实例代码

    Android Listview中显示不同的视图布局 1. 使用场景 在重写ListView的BaseAdapter时,我们常常在getView()方法中复用convertView,以提高性能.convertView在Item为单一的同种类型布局时,能够回收并重用,但是多个Item布局类型不同时,convertView的回收和重用会出现问题.比如有些行为纯文本,有些行则是图文混排,这里纯文本行为一类布局,图文混排的行为第二类布局.单一类型的ListView很简单,下面着重介绍一下ListView

随机推荐