Android 老生常谈LayoutInflater的新认知

现在看我文章的多数是一些老Android了,相信每个人使用起LayoutInflater都是家常便饭,信手拈来。

但即使是这样,我仍然觉得这个知识点有可以分析的地方,看完之后或许你对LayoutInflater又会有一些新的认识。

首先概括一下LayoutInflater是用来做什么的。

我们都知道,在开发Android应用程序的时候,编写布局基本都是通过xml文件来编写的。当然你也完全可以在代码中纯手写布局,但是写过的人都清楚,这样编写布局会非常麻烦。

那么通过xml编写的布局文件是如何转换成Android中的一个View对象从而显示在应用程序当中的呢?这就是LayoutInflater的作用了。

简单来说,LayoutInflater的工作就是将使用xml文件编写的布局转换成Android里的View对象,并且这也是Android中将xml布局转换成View的唯一方式。

可能有些朋友会说,不对啊,我平时也没怎么用过LayoutInflater,xml布局转换成View不是调用Activity里的setContentView()方法就可以了吗?

这是因为Android SDK在上层给我们做了一些很好的封装,让开发工作变得更加简单。如果你打开setContentView()方法的源码去了解一下,就会发现它的底层同样也是使用的LayoutInflater:

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

那么LayoutInflater又是如何将一个xml布局转换成一个View对象的呢?

这当然是一个非常复杂的过程,但是如果简要概括的话,最重要的无非就是两步:

  • 通过解析器来将xml文件中的内容解析出来。
  • 使用反射将解析出来的元素创建成View对象。

这里我不想在文章中带着大家一步步追源码,这样文章看起来可能会又累又枯燥,因此我就只贴出一些我认为比较关键的代码。

解析xml文件内容的代码片段:

public View inflate(@LayoutRes int resource,
                    @Nullable ViewGroup root,
                    boolean attachToRoot) {
    ...
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

可以看到,这里获取到了一个XmlResourceParser对象,用于对xml文件进行解析。由于具体的解析规则过于复杂,我们就不跟进去看了。

使用反射创建View对象的代码片段:

public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
    ...
    if (constructor == null) {
        // Class not found in the cache, see if it's real, and try to add it
        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                mContext.getClassLoader()).asSubclass(View.class);
        constructor = clazz.getConstructor(mConstructorSignature);
        constructor.setAccessible(true);
        sConstructorMap.put(name, constructor);
    }
    ...
    try {
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        return view;
    }
    ...
}

看到这里,我们就将LayoutInflater大体的工作原理基本了解了。

但是正如前面所说,本篇文章并不是要带着大家去读源码的,而是想要从用法层面对LayoutInflater有些新的理解。

那么LayoutInflater最常见的用法如下:

View view = LayoutInflater.from(context).inflate(resourceId, parent, false);

这段代码的意思是,首先调用LayoutInflater的from()方法去获取一个LayoutInflater的实例,然后再调用它的inflate()方法去解析并加载一个布局,从而转换成一个View对象并返回。

然而我认为这段代码对于新手来说却及其不友好,甚至对于很多的老手来说也是。

我们来看一下inflate()方法的参数定义:

public View inflate(int resource,
                    @Nullable ViewGroup root,
                    boolean attachToRoot) {
    ...
}

inflate()方法接收3个参数,第一个参数resource还比较好理解,就是我们要解析加载的xml文件的资源id。第二个参数root,和第三个参数attachToRoot是什么意思?可能即使不少做过多年Android开发的程序员也未必能解释得清楚。

而这段代码在我们使用RecyclerView,或者使用Fragment时都是一定会用到的。我在写《第一行代码》时由于在很早的章节就要讲RecyclerView的用法,但是却又感觉很难向初学者解释清楚LayoutInflater的相关内容,所以我一直都觉得这块内容没有讲好。只能先用死记硬背的方式,暂时就记着这部分代码必须这么写。

而今天,我希望能将LayoutInflater真正讲讲清楚。

我们知道,Android的布局结构是一种树状结构。每个布局都可以包含若干个子布局,每个子布局又可以继续包含子布局,以此构建出任意样式的View呈现给用户。

因此,我们大致可以明白,每个布局它都是要有一个父布局的。

这也是inflate()方法第二个参数root的作用,就是给当前要解析加载的xml布局指定一个父布局。

那么一个布局可不可以没有父布局呢?当然也是可以的,这也是为什么root参数被标为@Nullable的原因。

但是如果我们inflate出来了一个没有父布局的布局,又该如何去展示它呢?那自然是没有办法去展示的,所以只能后面再用addView的方式将它添加到某个现有的布局下面。又或者你inflate出来的布局就是个顶层布局,所以它不需要有父布局。但是这些场景都比较少见,因此大多数情况下,我们在使用LayoutInflater的inflate()方法时都是要指定父布局的。

另外,如果不为inflate出来的布局指定父布局,还会出现另外一种问题,我们通过一个例子来讲解一下。

这里我们定义一个button_layout.xml布局文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button" />

这个布局文件非常简单,里面只有一个按钮。

接下来我们使用LayoutInflater来加载这个布局文件,并将它添加到一个现有的布局当中:

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		LinearLayout mainLayout = (LinearLayout) findViewById(R.id.main_layout);
		View buttonLayout = LayoutInflater.from(this).inflate(R.layout.button_layout, null);
		mainLayout.addView(buttonLayout);
	}

}

可以看到,这里我们并没有给button_layout指定父布局,而是传入了一个null。当第二个参数传入null时,第三个参数就没有意义了,因此可以不用指定。

但是前面也说了,一个布局如果没有父布局的话没办法显示出来呀,所以我们又使用了addView()方法将它添加到了一个现有布局当中。

代码就是这么简单,现在我们可以运行一下程序,效果如下图所示:

看上去好像没啥问题,按钮已经可以正常显示出来了,说明button_layout.xml这个布局确实成功加载出来并且添加到现有的布局当中了。

但是如果你尝试去调整一下按钮的大小,你会发现不管你如何调整,按钮的大小都是不会变的:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="300dp"
    android:layout_height="100dp"
    android:text="Button" />

这里我们将按钮的宽高指定成了300dp,高度指定成了100dp,重新运行程序界面毫无变化。

为什么会出现这样的情况呢?

其实这里不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。

而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中才行。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。

而我们因为在使用LayoutInflater加载button_layout.xml这个布局时并没有为它指定父布局,因此这里layout_width和layout_height属性就都失去了作用。更准确点来讲,所有以layout_开头的属性都会失去作用。

现在我们将代码进行如下修改:

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		LinearLayout mainLayout = (LinearLayout) findViewById(R.id.main_layout);
		View buttonLayout = LayoutInflater.from(this).inflate(R.layout.button_layout, mainLayout, false);
		mainLayout.addView(buttonLayout);
	}

}

可以看到,这里将inflate()方法的第二个参数指定成了mainLayout。也就是说,我们为button_layout.xml这个布局指定了一个父布局。这样的话,layout_width和layout_height属性就可以生效了。

重新运行程序,效果如下图所示:

到这里为止,我们就将inflate()方法的第二个参数root的作用解释得非常清楚了。那么还有一个问题就是,第三个参数attachToRoot又是什么意思呢?

注意观察上述代码,我们将第二个参数指定成mainLayout的同时,将第三个参数指定成了false。如果你尝试将第三个参数指定成true,然后重新运行代码,程序将会直接崩溃。崩溃信息如下:

这个崩溃信息是在说,我们正在添加一个子View,但是这个子View已经有父布局了,需要让父布局先调用removeView()移除子View后才能添加。

为什么修改第三个参数之后会出现这样的错误呢?我们现在就来分析一下。

首先关注一下第三个参数的名字是什么,attachToRoot。从字面意思上看,是在问我们是否要添加到root上面。那么root是什么呢?再次观察inflate()方法的定义,你会发现第二个参数不就是root吗?

public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    ...
}

也就是说,attachToRoot的意思,就是在问我们要不要将当前加载的xml布局添加到第二个参数传入的父布局上面。如果传入true,那么就意味着会添加,传入false就表示不会添加。

所以在刚才的代码当中,我们一开始在inflate()方法的第三个参数中传入false,那么button_layout.xml布局是不会被添加到mainLayout当中的,我们后面就可以手动调用addView()方法将它添加到mainLayout当中。

而如果将第三个参数改成true,就表示button_layout.xml布局已经自动被添加到mainLayout当中了,此时再去调用一遍addView()方法,发现button_layout.xml已经有父布局了,自然就会抛出上面的异常。

经过这样的解释之后,你是否就对inflate()方法中的每一个参数的作用都理解清楚了呢?

其实理解到了这里,我们可以回过头来再去看一看过去写的代码。比如说大家肯定都用过Fragment,在Fragment中加载一个布局我们通常都会这么写:

public class MyFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_layout, container, false);
    }
}

不知道你过去有没有想过,为什么这里inflate()方法的最后一个参数一定要传入false?

那么现在可以想一想了。观察一下Fragment的相关源码,你会发现它会将我们在onCreateView()方法中返回的View添加到一个Container当中:

void addViewToContainer() {
    // Ensure that our new Fragment is placed in the right index
    // based on its relative position to Fragments already in the
    // same container
    int index = mFragmentStore.findFragmentIndexInContainer(mFragment);
    mFragment.mContainer.addView(mFragment.mView, index);
}

这个情况和我们刚才的例子非常类似,也就是说,后续Fragment自己会有一个addView的操作,如果我们将inflate()方法的第三个参数传入true,那么就会直接将inflate出来的布局添加到父布局当中。这样后面再次addView的时候就会发现它已经有一个父布局了,从而抛出与上面同样的崩溃信息。

不信的话你可以自己动手试一试。

除了Fragment之外,RecyclerView中对于LayoutInflater的用法也是基于一模一样的原因,这里就不再展开讨论了。

希望通过阅读本文之后,你对LayoutInflater又能有一些新的认识。

到此这篇关于Android 老生常谈LayoutInflater的新认知的文章就介绍到这了,更多相关Android LayoutInflater内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 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深入分析及应用

    LayoutInflater解析 前言: 在Android中,如果是初级玩家,很可能对LayoutInflater不太熟悉,或许只是在Fragment的onCreateView()中模式化的使用过而已.但如果稍微有些工作经验的人就知道,这个类有多么重要,它是连接布局XMl和Java代码的桥梁,我们常常疑惑,为什么Android支持在XML书写布局? 我们想到的必然是Android内部帮我们解析xml文件,LayoutInflater就是帮我们做了这个工作. 首先LayoutInflater是一个

  • Android中LayoutInflater.inflater()的正确打开方式

    前言 LayoutInflater在开发中使用频率很高,但是一直没有太知道LayoutInflater.from(context).inflate()的真正用法,今天就看看源码的流程. 首先来看from()的源码: /** * Obtains the LayoutInflater from the given context. */ public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater

  • Android开发实现自定义Toast、LayoutInflater使用其他布局示例

    本文实例讲述了Android开发实现自定义Toast.LayoutInflater使用其他布局.分享给大家供大家参考,具体如下: 内容: 1.自定义样式toast 2.再活动中添加其他布局 实现效果: 步骤: 一.自定义View 引用zidingyixml文件 生成一个布局对象 二.采用Toast 的addView() 方法将该对象添加到Toast对象中 三.显示:Toast.show() 具体实现方法: public class MainActivity extends Activity {

  • 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示例详解

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

  • Android LayoutInflater.inflate()详解及分析

    Android  LayoutInflater.inflate()详解 深入理解LayoutInflater.inflate() 由于我们很容易习惯公式化的预置代码,有时我们会忽略很优雅的细节.LayoutInflater以及它在Fragment的onCreateView()中填充View的方式带给我的就是这样的感受.这个类用于将XML文件转换成相对应的ViewGroup和控件Widget.我尝试在Google官方文档与网络上其他讨论中寻找有关的说明,而后发现许多人不但不清楚LayoutInfl

  • Android 老生常谈LayoutInflater的新认知

    现在看我文章的多数是一些老Android了,相信每个人使用起LayoutInflater都是家常便饭,信手拈来. 但即使是这样,我仍然觉得这个知识点有可以分析的地方,看完之后或许你对LayoutInflater又会有一些新的认识. 首先概括一下LayoutInflater是用来做什么的. 我们都知道,在开发Android应用程序的时候,编写布局基本都是通过xml文件来编写的.当然你也完全可以在代码中纯手写布局,但是写过的人都清楚,这样编写布局会非常麻烦. 那么通过xml编写的布局文件是如何转换成

  • Android Studio 3.0 新功能全面解析和旧项目适配问题

    简介: Android Studio是Android的官方IDE.它是专为Android而打造,可以加快您的开发速度,帮助您为每款Android设备构建最优应用. 它提供专为Android开发者量身定制的工具,其中包括丰富的代码编辑.调试.测试和性能分析工具. 上周四,Google 终于在经历大半年的打磨锤炼之后正式发布 Android Studio 3.0 版本,给广大安卓开发人员一份满意的答卷.如往常一样,每次新版开发工具的发布,很多谨慎点的朋友仍担心稳定性.是否存在坑等问题,选择隔岸观火,

  • Android编程实现获取新浪天气预报数据的方法

    本文实例讲述了Android编程实现获取新浪天气预报数据的方法.分享给大家供大家参考,具体如下: 新浪天气预报地址: http://php.weather.sina.com.cn/xml.php?city=武汉&password=DJOYnieT8234jlsK&day=0 其中,city后的城市可用java.net.URLEncoder.encode("武汉"," gb2312");也可以直接写"武汉",但不能用"wu

  • 神经网络API、Kotlin支持,那些你必须知道的Android 8.1预览版和Android Studio 3.0新特性

    谷歌2017发布会更新了挺多内容的,而且也发布了AndroidStudio3.0预览版,一些功能先睹为快. 过去的五个月里, Kotlin一直是我们反复谈论的重点.现在要告诉大家的是,Android Studio 3.0可以将Kotlin添加到您的项目中了.最新版本的Android Studio在支持Java 8语言功能上得到了改进,另外一个亮点是,有了用于Gradle 3.0.0的Android插件. 好,下面步入正文. 曾仅用 55 秒发布会的 Android 8.0 Oreo 在时隔两个月

  • Android Studio 3.6 新特性一览(推荐)

    设计 设计编辑器 设计编辑器(比如布局编辑器和导航编辑器)现在提供了一个拆分视图模式,能够同时查看 UI 界面的 Design 视图和 Code 视图.拆分视图取代并改进了早期的预览窗口,并且可以对每个文件进行设置,并且可以保存上下文信息,比如:缩放比例和设计视图选项等.想要开启拆分视图,单击编辑器窗口右上角的拆分图标即可.相关文档:https://medium.com/androiddevelopers/android-studio-design-tools-ux-changes-split-

  • Android Studio 4.0新特性及升级异常问题的解决方案

    一.升级问题 1. dataBinding开启配置修改 升级到AS 4.0以后,出现如下的预警,对于我这种有代码洁癖的人是不能忍的,必须解决 DSL element 'android.dataBinding.enabled' is obsolete and has been replaced with 'android.buildFeatures.dataBinding' 解决方法: dataBinding { enabled = true } 这是原有的DataBinding开启方式,在升级后

  • Android Studio 4.0 新功能中的Live Layout Inspector详解

    最近 Android Studio 4.0 稳定版本正式发布,其中一个重要升级就是新版的Layout Inspector 旧版的Layout Inspector 4.0 之前我们通过Tools -> Android -> Layout Inspector 可以对当前进程现实中画面进行分析,获取视图的Hierarchy以及Property信息 Live Layout Inspector 4.0 通过同样的菜单可以打开新版的 Layout Inspector 运行APP后,选择当前进程,可以看到当

  • Android中的RecyclerView新组件初步上手指南

    介绍 RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,但是直接把viewholder的实现封装起来,用户只要实现自己的viewholder就可以了,该组件会自动帮你回收复用每一个item. 它不但变得更精简,也变得更加容易使用,而且更容易组合设计出自己需要的滑动布局. RecyclerView与ListView原理是类似的:都是仅仅维护少量的View并且可以展示大量的数据集.RecyclerView用

  • Android仿QQ、新浪相册的实现

    在移动应用中,很多时候都会用到图片选择.图片裁剪等功能.最近我也在准备一个开源的相册项目,以方便以后开发应用的时候使用,也尽可能的方便需要的人.一个完整的相册,应该包含相册列表.图片列表.图片的单选和多选.图片的裁剪.拍照.多选图片的大图预览等功能.这也是我这个项目将要包含的功能.在本篇博客中,将会讲述下我在这个项目中相册列表和图片列表的大致实现. 实现效果 结合几个常用的APP中的相册效果,当前项目中已经实现了一些基本的功能和UI,在后续完善的过程中还会有所变动.项目在Github上开源,欢迎

随机推荐