欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android LayoutInflater.inflate()详解及分析

程序员文章站 2024-02-14 16:09:34
android  layoutinflater.inflate()详解 深入理解layoutinflater.inflate() 由于我们很容易习惯公式化...

android  layoutinflater.inflate()详解

深入理解layoutinflater.inflate()

由于我们很容易习惯公式化的预置代码,有时我们会忽略很优雅的细节。layoutinflater以及它在fragment的oncreateview()中填充view的方式带给我的就是这样的感受。这个类用于将xml文件转换成相对应的viewgroup和控件widget。我尝试在google官方文档与网络上其他讨论中寻找有关的说明,而后发现许多人不但不清楚layoutinflater的inflate()方法的细节,而且甚至在误用它。

这里的困惑很大程度上是因为google上有关attachtoroot(也就是inflate()方法第三个参数)的文档太模糊:

被填充的层是否应该附在root参数内部?如果是false,root参数只适用于为xml根元素view创建正确的layoutparams的子类。

其实意思就是:如果attachtoroot是true的话,那第一个参数的layout文件就会被填充并附加在第二个参数所指定的viewgroup内。方法返回结合后的view,根元素是第二个参数viewgroup。如果是false的话,第一个参数所指定的layout文件会被填充并作为view返回。这个view的根元素就是layout文件的根元素。不管是true还是false,都需要viewgroup的layoutparams来正确的测量与放置layout文件所产生的view对象。

attachtoroot传入true代表layout文件填充的view会被直接添加进viewgroup,而传入false则代表创建的view会以其他方式被添加进viewgroup。

让我们就两种情况多举一些例子来更深入的理解。

attachtoroot是true

假设我们在xml layout文件中写了一个button并指定了宽高为match_parent。

<button xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/custom_button">
</button>

现在我们想动态地把这个按钮添加进fragment或activity的linearlayout中。如果这里linearlayout已经是一个成员变量mlinearlayout了,我们只需要通过如下代码达成目标:

inflater.inflate(r.layout.custom_button, mlinearlayout, true);

我们指定了用于填充button的layout资源文件,然后我们告诉layoutinflater我们想把button添加到mlinearlayout中。这里button的layoutparams种类为linearlayout.layoutparams。

下面的代码也有同样的效果。layoutinflater的两个参数的inflate()方法自动将attachtoroot设置为true。

inflater.inflate(r.layout.custom_button, mlinearlayout);

另一种在attachtoroot中传递true的情况是使用自定义view。我们看一个layout文件中根元素有标签的例子。标签标识着这个layout文件的根viewgroup可以有多种类型。

public class mycustomview extends linearlayout {
  ...
  private void init() {
  layoutinflater inflater = layoutinflater.from(getcontext());
  inflater.inflate(r.layout.view_with_merge_tag, this);
  }
}

这就是一个很好的使用attachtoroot的例子。这个例子中layout文件没有viewgroup作为根元素,所以我们指定我们自定义的linearlayout作为根元素。如果layout文件有一个framelayout作为根元素而不是,那么framelayout和它的子元素都可以正常填充,而后都会被添加到linearlayout中,linearlayout是根viewgroup,包含着framelayout和其子元素。

attachtoroot是false

我们看一下什么时候attachtoroot应该是false。在这种情况下,inflate()方法中的第一个参数所指定的view不会被添加到第二个参数所指定的viewgroup中。

回忆一下刚才的例子中的button,我们想通过layout文件添加自定义的button至mlinearlayout中。当attachtoroot为false时,我们仍可以将button添加到mlinearlayout中,但是这需要我们自己动手。

button button = (button) inflater.inflate(r.layout.custom_button, mlinearlayout, false);
mlinearlayout.addview(button);

这两行代码与刚才attachtoroot为true时的一行代码等效。通过传入false,我们告诉layoutinflater我们不暂时还想将view添加到根元素viewgroup中,意思是我们一会儿再添加。在这个例子中,一会儿再添加就是在inflate()后调用addview()方法。

在将attachtoroot设置为false的例子中,由于要手动添加view进viewgroup导致代码变多了。将button添加到linearlayout中还是用一行代码直接将attachtoroot设置为true简便一些。下面我们看一下什么情况下attachtoroot必须传入false。

每一个recyclerview的子元素都要在attachtoroot设置为false的情况下填充。这里子view在oncreateviewholder()中填充。

public viewholder oncreateviewholder(viewgroup parent, int viewtype) {
  layoutinflater inflater = layoutinflater.from(getactivity());
  view view = inflater.inflate(android.r.layout.list_item_recyclerview, parent, false);
  return new viewholder(view);
}

recyclerview负责决定什么时候展示它的子view,这个不由我们决定。在任何我们不负责将view添加进viewgroup的情况下都应该将attachtoroot设置为false。

当在fragment的oncreateview()方法中填充并返回view时,要将attachtoroot设为false。如果传入true,会抛出illegalstateexception,因为指定的子view已经有父view了。你需要指定在哪里将fragment的view放进activity里,而添加、移除或替换fragment则是fragmentmanager的事情。

fragmentmanager fragmentmanager = getsupportfragmentmanager();
fragment fragment = fragmentmanager.findfragmentbyid(r.id.root_viewgroup);

if (fragment == null) {
  fragment = new mainfragment();
  fragmentmanager.begintransaction().add(r.id.root_viewgroup, fragment).commit();
}

上面代码中root_viewgroup就是activity中用于放置fragment的容器,它会作为inflate()方法中的第二个参数被传入oncreateview()中。它也是你在inflate()方法中传入的viewgroup。fragmentmanager会将fragment的view添加到viewgroup中,你可不想添加两次。

public view oncreateview(layoutinflater inflater, viewgroup parentviewgroup, bundle savedinstancestate) {
  view view = inflater.inflate(r.layout.fragment_layout, parentviewgroup, false);
  …
  return view;
}

问题是:如果我们不需在oncreateview()中将view添加进viewgroup,为什么还要传入viewgroup呢?为什么inflate()方法必须要传入根viewgroup?

原因是及时不需要马上将新填充的view添加进viewgroup,我们还是需要这个父元素的layoutparams来在将来添加时决定view的size和position。

你在网上一定会遇到一些不正确的建议。有些人会建议你如果将attachtoroot设置为false的话直接将根viewgroup传入null。但是,如果有父元素的话,还是应该传入的。

Android  LayoutInflater.inflate()详解及分析

lint会警告你不要讲null作为root传入。你的app不会挂掉,但是可能会表现异常。当你的子view没有正确的layoutparams时,它会自己通过generatedefaultlayoutparams)计算。

你可能并不想要这些默认的layoutparams。你在xml指定的layoutparams会被忽略。我们可能已经指定了子view要填充父元素的宽度,但父view又wrap_content导致最终的view小很多。

下面是一种没有viewgroup作为root传入inflate()方法的情况。当为alertdialog创建自定义view时,还无法访问父元素。

alertdialog.builder dialogbuilder = new alertdialog.builder(mcontext);
view customview = inflater.inflate(r.layout.custom_alert_dialog, null);
...
dialogbuilder.setview(customview);
dialogbuilder.show();

在这种情况下,可以将null作为root viewgroup传入。后来我发现alertdialog还是会重写layoutparams并设置各项参数为match_parent。但是,规则还是在有viewgroup可以传入时传入它。

避开崩溃、异常表现与误解

希望这篇文章可以帮助你在使用layoutinflater时避开崩溃、异常表现与误解。下面整理了文章的要点:

  • 如果可以传入viewgroup作为根元素,那就传入它。
  • 避免将null作为根viewgroup传入。
  • 当我们不负责将layout文件的view添加进viewgroup时设置attachtoroot参数为false。
  • 不要在view已经被添加进viewgroup时传入true。
  • 自定义view时很适合将attachtoroot设置为true。

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