误用 LayoutInflater 的 inflate() 方法已经不是什么稀罕事儿了……

做 Android 开发做久了,一定会或多或少地对布局的渲染有一些懵逼:

1.View.inflate() 和 LayoutInflator.from().inflate() 有啥区别?
2.调用 inflate() 方法的时候有时候传 null,有时候传 parent 是为啥?
3.用 LayoutInflater 有时候还可能传个 attachToRoot ,这又是个啥?

接下来我们就从源码的角度来寻找一下这几个问题的答案,后面再用几个示例来验证我们的猜想。

话不多说,Let’s go !

基本介绍

先来看一下这个方法具体做了什么:

/*** Inflate a view from an XML resource.  This convenience method wraps the {@link* LayoutInflater} class, which provides a full range of options for view inflation.*/
public static View inflate(Context context, int resource, ViewGroup root) {LayoutInflater factory = LayoutInflater.from(context);return factory.inflate(resource, root);
}

当我们查看源码,就会发现,这个方法的内部实际上就是调用了 LayoutInflater 的 inflate 方法。正如此方法的注释所言,这是一个方便开发者调用的 LayoutInflater 的包装方法,而 LayoutInflater 本身则为 View 的渲染提供了更多的选择。

那么我们现在的问题就变成了, LayoutInflater 又做了什么?

继续追踪代码,我们会发现, LayoutInflator.from().inflate() 是这个样子的:

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

啥?重载?

// LayoutInflator#inflate(int, ViewGroup, boolean)
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();final XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}
}

这里我们看到,通过层层调用,最终会调用到 LayoutInflator#inflate(int, ViewGroup, boolean) 方法,很明显,这个方法会将我们传入的布局 id 转换为 XmlResourceParser,然后进行另一次,也是最后一次重载。

这个方法就厉害了,这里基本上包括了我们所有问题的答案,我们继续往下看。

源码分析

话不多说,上代码。接下来我们来逐段分析下这个 inflate 方法:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);// 默认返回结果为传入的根布局View result = root;// 通过 createViewFromTag() 方法找到传入的 layoutId 的根布局,并赋值给 tempfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;// 如果传入的父布局不为空if (root != null) {// 为这个 root 生成一套合适的 LayoutParamsparams = root.generateLayoutParams(attrs);if (!attachToRoot) {// 如果没有 attachToRoot,那为根布局设置 layoutparamstemp.setLayoutParams(params);}}// 如果传入的父布局不为空,且想要 attachToRootif (root != null && attachToRoot) {// 那就将传入的布局以及 layoutparams 通过 addView 方法添加到父布局中 root.addView(temp, params);}// 如果传入的根布局为空,或者不想 attachToRoot,则返回要加载的 layoutIdif (root == null || !attachToRoot) {result = temp;}return result;
}

代码也分析完了,我再来总结一下:

  • View#inflate 只是个简易的包装方法,实际上还是调用的 LayoutInflater#inflate ;
  • LayoutInflater#inflate 由于可以自己选择 root 和 attachToRoot
    的搭配(后面有解释),使用起来更加灵活;
  • 实际上的区别只是在于 root 是否传空,以及 attachToRoot 真假与否;
  • 当 root 传空时,会直接返回要加载的 layoutId,返回的 View 没有父布局且没有 LayoutParams;
  • 当 root 不传空时,又分为 attachToRoot 为真或者为假:
    • attachToRoot = true 会为传入的 layoutId 直接设置参数,并将其添加到 root 中,然后将传入的 root
      返回;
    • attachToRoot = false 会为传入的 layoutId 设置参数,但是不会添加到 root ,然后返回 layoutId
      对应的 View;

这里需要注意的是,虽然不马上将 View 添加到 parent 中,但是这里最好也传上 parent,而不是粗暴的传入 null;因为子
View 的 LayoutParams 需要由 parent 来确定;否则会在手动 addView 时调用
generateDefaultLayoutParams() 为子 View 生成一个宽高都为包裹内容的
LayoutParams,而这并不一定是我们想要的。

测试 & 检验

单说起来可能有些抽象,下面使用代码来进行具体的测试与检验。

View.inflate(context, layoutId, null)

如之前所说,这实际上调用的是 getLayoutInflater().inflate(layoutId, null) ,结合之前的源码来看:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {View result = root;final View temp = createViewFromTag(root, name, inflaterContext, attrs);if (root == null || !attachToRoot) {result = temp;}return result;
}

很明显,传入的 root 为空,则会直接将加载好的 xml 布局返回,而这种情况下返回的这个 View 没有参数,也没有父布局。

protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_test);View inflateView = View.inflate(this, R.layout.layout_basic_use_item, null);Log.e("Test", "LayoutParams -> " + inflateView.getLayoutParams());Log.e("Test", "Parent -> " + inflateView.getParent());
}


如图所示,正如我们想的,root 传 null 时,参数以及父布局返回结果均为 null。

View.inflate(context, layoutId, mParent)
按之前分析过的,此方法实际调用的是 getLayoutInflater().inflate(layoutId, root, true) ,再来看源码:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);View result = root; final View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {params = root.generateLayoutParams(attrs);}if (root != null && attachToRoot) {root.addView(temp, params);}return result;
}

如源码所示,返回的 result 会在最开始就被赋值为入参的 root,root 不为空,同时 attachToRoot 为 true,就会将加载好的布局直接通过 addView 方法添加到 root 布局中,然后将 root 返回。

protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_test);LinearLayout mParent = findViewById(R.id.ll_root);View inflateView = View.inflate(this, R.layout.layout_basic_use_item, mParent);Log.e("Test", "LayoutParams -> " + inflateView.getLayoutParams());Log.e("Test", "Parent -> " + inflateView.getParent());Log.e("Test", "inflateView -> " + inflateView);
}


如图示,返回的 View 正是我们传入的 mParent,对应的 id 是 ll_root,参数也不再为空。

getLayoutInflater().inflate(layoutId, root, false)

也许会有人问了,现在要么是 root 传空,返回 layoutId 对应的布局;要么是 root 不传空,返回传入的 root 布局。那我要是想 root 不传空,但是还是返回 layoutId 对应的布局呢?

这就是 View#inflate 的局限了,由于它是包装方法,因此 attachToRoot 并不能因需定制。这时候我们完全可以自己调用 getLayoutInflater().inflate(layoutId, root, false) 方法,手动的将第三个参数传为 false,同时为这个方法传入目标根布局。这样,我们就可以得到一个有 LayoutParams,但是没有 parentView 的 layoutId 布局了。

protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_test);LinearLayout mParent = findViewById(R.id.ll_root);View inflateView = getLayoutInflater().inflate(R.layout.main, mParent, false);Log.e("Test", "LayoutParams -> " + inflateView.getLayoutParams());Log.e("Test", "Parent -> " + inflateView.getParent());
}

与我们分析的一致,有参数,但是没有父布局,且返回的就是我们加载的布局 id。我们在之后可以通过 addView 方法手动将这个布局加入父布局中。

这里还有个要注意的点,那就是 params = root.generateLayoutParams(attrs); 这句代码,我们会发现,为 layoutId 设置的 params 参数,实际上是通过 root 来生成的。这也就告诉我们,虽然不马上添加到 parent 中,但是这里最好也传上 parent,而不是粗暴的传入 null,因为子 View 的 LayoutParams 需要由 parent 来确定;当然,传入 null 也不会有问题,因为在执行 addView() 方法的时候,如果当前 childView 没有参数,会调用 generateDefaultLayoutParams() 生成一个宽高都包裹的 LayoutParams 赋值给 childView,而这并不一定是我们想要的。

attachToRoot 必须为 false!

代码写多了,大家有时候会发现这个 attachToRoot 也不是想怎样就怎样的,有时候它还就必须是 false,不能为 true。下面我们就来看看这些情况。

  • RecylerView#onCreateViewHolder()

在为 RecyclerView 创建 ViewHolder 时,由于 View 复用的问题,是 RecyclerView 来决定什么时候展示它的子View,这个完全不由我们决定,这种情况下,attachToRoot 必须为 false:

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  LayoutInflater inflater = LayoutInflater.from(getActivity());  View view = inflater.inflate(R.layout.item, parent, false);  return new ViewHolder(view);
}
  • Fragment#onCreateView()

由于 Fragment 需要依赖于 Activity 展示,一般在 Activity 中也会有容器布局来盛放 Fragment:

Fragment fragment = new Fragment();
getSupportFragmentManager().beginTransaction().add(R.id.root_container, fragment).commit();

上述代码中的 R.id.root_container 便为容器,这个 View 会作为参数传递给 Fragment#onCreateView() :

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

它也是你在 inflate() 方法中传入的 ViewGroup,FragmentManager 会将 Fragment 的 View 添加到 ViewGroup 中,言外之意就是,Fragment 对应的布局展示或者说添加进 ViewGroup 时也不是我们来控制的,而是 FragmentManager 来控制的。

总结一下就是,当我们不为子 View 的展示负责时,attachToRoot 必须为 false;否则就会出现对应的负责人,比如上面说的 Rv 或者 FragmentManager,已经把布局 id 添加到 ViewGroup 了,我们还继续设置 attachToRoot 为 true,想要手动 addView,那必然会发生 child already has parent 的错误。

Android --- View.inflate()的详细介绍相关推荐

  1. Android之Fragment的详细介绍和使用方法

    一.Fragment的基础知识介绍 1.1概述 1.1.1 特性 Fragment是activity的界面中的一部分或一种行为.可以把多个Fragment组合到一个activity中来创建一个多界面 ...

  2. android webview详情,Android中的WebView详细介绍

    Android中WebView的详细解释: 1. 概念: WebView(网络视图)能加载显示网页,可以将其视为一个浏览器.它使用了WebKit渲染引擎加载显示网页. 2. 使用方法: (1).实例化 ...

  3. caused by: android.view.inflate,安卓5.1报android.view.InflateException异常怎么解决?

    我利用View来作为一条分割线,在安卓7.0以上版本测试程序正常,但在安卓5.1程序却奔溃. android:id="@+id/tool_bar_shadow" android:l ...

  4. Android 之 Project Butter 详细介绍

    现在我们已经很少能够听到关于 Android UI 卡顿的话题了,这得益于 Google 长期以来对 Android 渲染性能的重视,基本每次 Google I/O 都会花很多篇幅讲这一块.随着时间的 ...

  5. 面向开发者的 Android 8.0 Oreo 详细介绍

    无需原生开发基础,也能完美呈现京东商城.<混合开发京东商城系统,提前布局大前端>课程融合vue.Android.IOS等目前流行的前端和移动端技术,混合开发经典电商APP--京东.课程将各 ...

  6. Android常见界面布局(详细介绍)

    一.View视图 所有的UI元素都是通过View与ViewGroup构建的,对于一个Android应用的用户界面来说,ViewGroup作为容器盛装界面中的控件,它可以包含普通的View控件,也可以包 ...

  7. Android QQ 登录接入详细介绍

    /   今日科技快讯   / 近日,百度地图发布2022春节出行大数据.迁徙大数据显示,2022年春运迁徙规模较去年农历同期有明显上升.春节期间全国人口迁徙规模日均值为去年农历同期的近两倍.春节前的迁 ...

  8. Android --- AndroidManifest.xml文件内容详细介绍

    文章目录 1.android:label="@string/app_name" 2. android:icon="@mipmap/ic_launcher"与an ...

  9. h5 intent 调起_Android与H5互调详细介绍

    Android与H5互调详细介绍 微信,微博,微商,QQ空间,大量的软件使用内嵌了H5,这个时候就需要了解Android如何更H5交互的了:有些外包公司,为了节约成本,采用Android内嵌H5模式开 ...

最新文章

  1. Linux那些事儿 之 戏说USB(22)设备的生命线(五)
  2. Spring+SpringMVC+Mybatics配置文件解析
  3. 【 NLS 】Newton – Raphson Iteration Procedure of TOA - Based Positioning
  4. document.forms用法示例介绍
  5. mysql 修改字符集
  6. WIndows下AppAche服务中调试php页面出现警告:Call to undefined function mysql_connect()
  7. 今日arXiv精选 | ICCV 2021/CIKM 2021/ACM MM 2021
  8. LeetCode 之 JavaScript 解答第141题 —— 环形链表 I(Linked List Cycle I)
  9. koa --- nunjucks
  10. JAVA中带有数字签名的XML安全性
  11. Linux以及各大发行版介绍
  12. pix2pix, CycleGAN和pix2pixHD(没有公式,容易理解)
  13. HTML DOM addEventListener() 方法
  14. Jenkins系列之-—07 集成JIRA
  15. ORACLE数据库DDL审计触发器与隐藏参数_system_trig_enabled
  16. 数据库触发器调用python_从mysql触发器调用python脚本
  17. 页面之间传输大量数据
  18. xp系统打开计算机硬盘分区,如何在xp系统对硬盘进行分区
  19. PostgreSQL导入导出CSV
  20. 实现远程开机(电脑)的各种方法总结

热门文章

  1. 高考题(可作为试讲资料)
  2. Windows Phone 实用开发技巧(9):自定义Windows Phone 页面切换动画
  3. 千家BBS系列-技术宝典(免费下载软件)
  4. 一种基于超体素结合粒子群与模糊聚类实现点云分割的优化算法
  5. boost bind使用指南
  6. linux环境变量显示、添加、删除
  7. 高速缓存系统之memcache c++使用实例
  8. caffe学习(三):caffe开发环境安装(Ubuntu)
  9. 【机器学习入门笔记4:OpenCV图片的写入和不同图片质量保存】20190203
  10. objective c 语法