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

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

程序员文章站 2023-11-17 19:00:16
前言 在平时的开发过程中,我们经常会用layoutinflater这个类,比如说在fragment$oncreateview和recyclerview.adapter$o...

前言

在平时的开发过程中,我们经常会用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);

视觉上的结果都是一样的

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

但是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);

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

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

<?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开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。