LayoutInflater学习笔记

一次在做ListView渲染一个列表的时候,希望可以让每个列表都以指定的高度来渲染,可是发现设置自定义布局高度竟然不起作用,于是在网上搜索了一下,发现原来Android系统中控件本身并没有设置高宽的属性,所以我们在xml布局的时候都是用的android:layout_width或者android:layout_height来渲染控件的高宽,而不是android:width或android: height,它们的宽高都是有父布局统一分配的,先暂时这么理解控件本身的宽高。

LayoutInflater

在Android开发中会经常遇到这个LayoutInflater,多数是在自定义控件或布局中,inflate(int resource, ViewGroup root)或者inflate(int resource, ViewGroup root, boolean attachToRoot)。可能有时为了方面起见,直接使用View类下面的inflate(Context context, int resource, ViewGroup root)方法。查看一下源码,我们发现View类中的方法仍然是调用的LayoutInflater中的方法。

LayoutInflater就是将xml中layout布局文件实例化为View对象的,直译就是布局填充器,Android系统给我们提供了三种方式来获取一个LayoutInflater实例:

  • public static LayoutInflater from(Context context)

    LayoutInflater inflater =LayoutInflater.from(context);

  • public LayoutInflater getLayoutInflater()

    LayoutInflater inflater =context.getLayoutInflater();

  • public Object getSystemService(String name)

    LayoutInflater inflater =(LayoutInflater)context. getSystemService(Context.LAYOUT_INFLATER_SERVICE);

三种方式最终都归为一种,都是采用方式3来创建一个LayoutInflater实例的。

实例探讨

方式一

inflate01-02

inflate01-01

我们发现按钮定义的宽高都没有起作用,按钮本身已经被添加到相应的布局当中,在这种方式中如果我们将inflater.inflate(R.layout.button, null)中的null改为linearLayout,这时候会抛出下面异常:

Caused by: java.lang.IllegalStateException: The specified child already has a parent.You must call removeView() on the child’s parent first.

这里为什么会抛出异常,暂时先不解释,我们继续往下探讨,已经将null该为linearLayout,现在再将linearLayout.addView(view)这一行代码注释掉,运行结果如下图:

inflate02-02

inflate01-01

此时按钮不但已经被加入到目标布局中,而且按钮的宽高已经起了作用。继续走,inflater.inflate(R.layout.button, linearLayout),把linearLayout设置为空,把最后一行代码仍然注释掉,这时候我们发现按钮已经没有再被填充到布局中。为什么会出现上述情况呢,我们查看一下源代码:

到这里已经愈渐清晰了,两个参数的方法就是调用的三个参数的方法,三个参数就是接下来要探讨的方式二。

方式二

inflate02-02

inflate01-01

刚一上来发现所有的执行效果都是我们所要的理想效果,按钮已经按照指定宽高填充进父布局。感觉天下太平,一片祥和,可以笙歌艳舞了。接着往下走,inflater.inflate(R.layout.button, linearLayout, false),将false该为true,这时又抛出了同样的异常:

Caused by: java.lang.IllegalStateException: The specified child already has a parent.You must call removeView() on the child’s parent first.

这时候再把linearLayout.addView(view)这一行代码注释掉,又跟刚才效果一模一样了,一切恢复正常了。继续走,把inflater.inflate(R.layout.button, linearLayout, false)中的linearLayout设置为null,这时候不管我们最后一个参数设置为true或者false都不会让按钮按照指定的宽高显示,结果都是如下图:

inflate01-02

还剩下最后一种情况,就是inflater.inflate(R.layout.button, null, false),linearLayout.addView(view)这一行代码仍然注释掉,这时布局中已经没有了按钮的影子,按钮并没有被加到布局中去。

源码解析

上面两种方式我们实验了各种情况,出现了各种不同的结果,知其然知其所以然,还是回归源码吧,重点就在下面这个方法:

LayoutInflater中inflate(int resource, ViewGroup root, boolean attachToRoot)方法中,我们前面所有情况归根到底都是对后面两个参数不同情况的探讨。

public View inflate (int resource, ViewGroup root, boolean attachToRoot)

  • resource:将要加载的xml布局文件的ID
  • root:如果attachToRoot为true,返回生成视图的父,也就是root,如果attachToRoot为false,root仅仅返回的是包含已经生成视图的宽高参数的对象。
  • attachToRoot:如果为true,则返回填充视图的root,否则root仅仅被当做父布局创建子布局xml的布局参数。

方法返回值是一个已经渲染的层级视图,如果最后一个参数attachToRoot为true,返回的是root,如果设置为false,则仅仅返回resource布局文件设置的视图View。

该方法又调用了下面的方法:

先看下面几行代码:

先看temp对象,temp对象是我们resource的xml布局文件返回的根视图,如果root不为空,首先创建布局参数params,如果attachToRoot为false,temp这个View临时对象才会设置布局参数params。

接下来分析下面代码:

如果root不为null并且attachToRoot为true,此时会将填充视图的布局参数params和布局视图添加进入root中去。

如果root为null并且attachToRoot为true,此时仅仅是将子视图返回,而且视图无布局参数,因为上面一个代码片段我们已经看到只有root为null是才创建布局参数params。

分析完这些,我们就可以很简单的推断出上面的各种情况了。

结论

  1. 如果root不为null,attachToRoot设置为true,如果在执行linearLayout.addView(view)就会抛出如下异常:

    Caused by: java.lang.IllegalStateException: The specified child already has a parent.You must call removeView() on the child’s parent first.

    因为这时候infalte已经将我们子视图填充进了父布局中,而且ViewGroup中已经判断,如果子布局已经填充进了一个父布局,一个孩子只有一个亲爹嘛,再次填充就会抛出异常。

  2. View的inflate(Context context, int resource, ViewGroup root)方法填充布局时,如果root不为null,则这时候都不需要在是使用linearLayout.addView(view)方法再次填充了,该方法调用了LayoutInflater中的方法,只要root不为空,attachToRoot一定是true。
  3. View的inflate(Context context, int resource, ViewGroup root)方法填充布局时,如果root不为null,此时在布局文件中指定的宽高都无意义了,因为只有root不为null是才会生成子布局的布局参数params。

这里就不做更多的总结了。

其它

文章开头说的ListView中每一个条目高度固定,包括在GridView中,虽然这中情况在开发中很少见,一般都要根据屏幕适配。在public View getView(int position, View convertView, ViewGroup parent)中采用自定义布局时,要注意一点,就是不管用那种方式 最后一定要返回自定义布局的视图,也就是绝对不可以返回root,否则会抛出下面异常:

java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView

先不看源码,简单分析一下,这里抛出异常也是可以理解的,我们做适配器的目的就是为了返回一个自定义布局视图,然而这个时候如果我们将它放入了root中去,那么这个root是哪个root呢,我们一个ListView或GridView会有多个条目,从上面的分析中我们也知道一个子View只能有一个父亲,而这里我们自定义布局就一个ID,多个条目岂不是要多次添加,如果就这样也会在addView时也会抛出异常。当然了,我们还是看一下源码吧,ListView的继承了AdapterView,如果我们的root不为空并且attachToRoot为true的话,通过上面的分析,我们知道这里会执行ViewGroup中的addView方法,而AdapterView实现了ViewGroup的部分方法,其中就包括了addView方法。

到这里就明白了,只要执行addView系统就给抛出异常。

本文地址www.sunnyang.com/255.html

One comment

  • I’ll be excited for the cover but I really want the first chapter. I need something to get my love back since the SVM & I are in a bad place after TB4. (And I know it’s not SVM’s fault but it is what it is.p &nbs); 1 likes

发表评论