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

发布时间 - 2026-01-11 00:50:00    点击率:

前言

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

所以想在这里总结一下,避免以后继续掉坑。

先来看看inflate方法的注释:

/**
 * Inflate a new view hierarchy from the specified xml resource. Throws
 * {@link InflateException} if there is an error.
 * 
 * @param resource ID for an XML layout resource to load (e.g.,
 *  <code>R.layout.main_page</code>)
 * @param root Optional view to be the parent of the generated hierarchy (if
 *  <em>attachToRoot</em> is true), or else simply an object that
 *  provides a set of LayoutParams values for root of the returned
 *  hierarchy (if <em>attachToRoot</em> is false.)
 * @param attachToRoot Whether the inflated hierarchy should be attached to
 *  the root parameter? If false, root is only used to create the
 *  correct subclass of LayoutParams for the root view in the XML.
 * @return The root View of the inflated hierarchy. If root was supplied and
 *   attachToRoot is true, this is root; otherwise it is the root of
 *   the inflated XML file.
 */
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

首先需要了解的一点是,一个View的测量结果并不只是由它自己的layout_width和layout_height(即LayoutParams)所决定的,而是由父容器给它的约束(MeasureSpec)和它自身的LayoutParams共同决定的。

达成这个共识之后,我们再来看看它的参数。

  • root:给布局文件提供一个父容器。布局文件里面总有一个元素是没有父容器的(没错,就是根元素),所以需要给它提供一个父容器来帮助它完成测量工作。如果root为空的话,就会导致根元素中的layout_xxx全部失效,从而影响到整个布局。同时,如果root为空的话,那么attachToRoot也就没有意义了。
  • attachToRoot: 如果为true,创建出来的布局系统会帮我们添加到父容器中去。为false的话,就只是给它提供约束,好让这个布局顺利完成测量等工作而已,将布局添加到父容器中去需要我们后续根据需要去手动调用addView方法。
  • 返回值:如果root != null && attachToRoot,返回的View就是传进来的root,否则返回由布局文件所创建的View对象。

用几个例子来说明一下会比较好理解。Activity的布局是一个LinearLayout,要添加的布局如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/item_text"
  android:layout_width="200dp"
  android:layout_height="50dp"
  android:layout_marginLeft="16dp"
  android:layout_marginTop="16dp"
  android:background="@color/colorAccent"
  android:gravity="center"
  android:text="item: text"/>

正常的情况

// 第一种方法
View inflatedView = LayoutInflater.from(this).inflate(R.layout.item_text, mLinearLayout, true);
Log.d(TAG, "inflated view is " + inflatedView);

// 第二种方法
View inflatedView = LayoutInflater.from(this).inflate(R.layout.item_text, mLinearLayout, false);
Log.d(TAG, "inflated view is " + inflatedView);
mLinearLayout.addView(inflatedView);

视觉上的结果都是一样的


但是Log就有一点不一样了,这就是attachToRoot不同的值所导致的。

第一种方法的Log

D/MainActivity: inflated view is android.widget.LinearLayout{36e9aac V.E...... ......I. 0,0-0,0 #7f0c0051 app:id/linear}

第二种方法的Log

D/MainActivity: inflated view is android.support.v7.widget.AppCompatTextView{3c9d37b V.ED..... ......ID 0,0-0,0 #7f0c0054 app:id/item_text}

还有一个需要注意的地方是:如果在第一种方法的基础上再加上mLinearLayout.addView(inflatedView)就会造成报错
IllegalStateException: The specified child already has a parent....  。

而如果第二种方法没有这句话,界面上是看不到任何东西的。

root为null的情况

mLinearLayout = (LinearLayout) findViewById(R.id.linear);
View inflatedView = LayoutInflater.from(this).inflate(R.layout.item_text, null);
Log.d(TAG, "inflated view is " + inflatedView);
mLinearLayout.addView(inflatedView);

此时再看看它的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/item_text"
  android:layout_width="200dp"
  android:layout_height="50dp"
  android:layout_marginLeft="16dp"
  android:layout_marginTop="16dp"
  android:background="@color/colorAccent"
  android:gravity="center"
  android:text="item: text"/>

不难发现,所有layout_xxx的属性全都失效了。

RecyclerView中的Inflater

上面说了,在创建布局的时候,要把布局添加到root中去,并且有两种方法,但是我们在onCreateViewHolder中添加布局的时候却是这样写的:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
 View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text, parent, false);
 return new MyViewHolder(view);
}

如果第三个参数传了true还会报错,这又是为什么呢?

java.lang.IllegalStateException: The specified child already has a parent.

直观上来解释就是,子View的添加与删除是由RecyclerView来管理的,不需要我们来添加。但我们还是从RecyclerView的代码来理解一下会好一些。

以LinearLayoutManager为例,RecyclerView在创建子View的时候会调用到LinearLayoutManager$layoutChunk方法:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
  LayoutState layoutState, LayoutChunkResult result) {
 // 在这里会调用到Adapter$onCreateViewHolder
 View view = layoutState.next(recycler);
 if (view == null) {
  if (DEBUG && layoutState.mScrapList == null) {
   throw new RuntimeException("received null view when unexpected");
  }
  // if we are laying out views in scrap, this may return null which means there is
  // no more items to layout.
  result.mFinished = true;
  return;
 }
 LayoutParams params = (LayoutParams) view.getLayoutParams();
 if (layoutState.mScrapList == null) {
  if (mShouldReverseLayout == (layoutState.mLayoutDirection
    == LayoutState.LAYOUT_START)) {
   addView(view);
  } else {
   addView(view, 0);
  }
 } else {
  if (mShouldReverseLayout == (layoutState.mLayoutDirection
    == LayoutState.LAYOUT_START)) {
   addDisappearingView(view);
  } else {
   addDisappearingView(view, 0);
  }
 }

 // 省略其它大部分代码
}

在初始化的时候,View view = layoutState.next(recycler)里面会调用到我们熟悉的onCreateViewHolder方法,然后我们在里面inflate的过程中第三个参数传了true,将子View添加到了RecyclerView中去了。然而,获得View之后,调用到了addView(因为是初始化,不可能调用addDisappearingView) ,这里又会去添加一次,所以报出了上面的IllegalStateException异常。

总结

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


# layoutinflater用法  # layoutinflater  # layoutinflater的详解  # Android LayoutInflater.inflate源码分析  # Android 老生常谈LayoutInflater的新认知  # Android布局加载之LayoutInflater示例详解  # Android LayoutInflater加载布局详解及实例代码  # Android LayoutInflater深入分析及应用  # Android开发中LayoutInflater用法详解  # 基于Android LayoutInflater的使用介绍  # Android用于加载xml的LayoutInflater源码超详细分析  # 种方法  # 中去  # 第三个  # 就会  # 在这里  # 出了  # 也就  # 是由  # 报错  # 提供一个  # 传了  # 给它  # 为空  # 过程中  # 自己的  # 是一个  # 这是  # 几个  # 不可能  # 去了 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel的辅助函数有哪些_Laravel常用Helpers函数提高开发效率  EditPlus中的正则表达式实战(6)  如何快速启动建站代理加盟业务?  jQuery validate插件功能与用法详解  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  简单实现jsp分页  如何在阿里云完成域名注册与建站?  香港服务器网站推广:SEO优化与外贸独立站搭建策略  百度浏览器网页无法复制文字怎么办 百度浏览器复制修复  Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  如何快速生成专业多端适配建站电话?  Laravel DB事务怎么使用_Laravel数据库事务回滚操作  微信小程序 wx.uploadFile无法上传解决办法  百度浏览器如何管理插件 百度浏览器插件管理方法  如何撰写建站申请书?关键要点有哪些?  Python函数文档自动校验_规范解析【教程】  详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  如何在搬瓦工VPS快速搭建网站?  Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】  BootStrap整体框架之基础布局组件  Laravel怎么进行数据库事务处理_Laravel DB Facade事务操作确保数据一致性  如何在IIS管理器中快速创建并配置网站?  Laravel怎么做缓存_Laravel Cache系统提升应用速度的策略与技巧  如何有效防御Web建站篡改攻击?  香港服务器建站指南:免备案优势与SEO优化技巧全解析  Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案  如何在万网主机上快速搭建网站?  Laravel怎么使用Intervention Image库处理图片上传和缩放  如何制作一个表白网站视频,关于勇敢表白的小标题?  如何在宝塔面板中修改默认建站目录?  Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析  Laravel中间件如何使用_Laravel自定义中间件实现权限控制  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  Laravel集合Collection怎么用_Laravel集合常用函数详解  php 三元运算符实例详细介绍  Laravel安装步骤详细教程_Laravel环境搭建指南  网站制作免费,什么网站能看正片电影?  js代码实现下拉菜单【推荐】  教你用AI将一段旋律扩展成一首完整的曲子  Laravel如何记录自定义日志?(Log频道配置)  如何在云服务器上快速搭建个人网站?  javascript日期怎么处理_如何格式化输出  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  Laravel怎么多语言本地化设置_Laravel语言包翻译与Locale动态切换【手册】  Laravel怎么发送邮件_Laravel Mail类SMTP配置教程  Laravel怎么判断请求类型_Laravel Request isMethod用法  HTML 中动态设置元素 name 属性的正确语法详解  如何做网站制作流程,*游戏网站怎么搭建?