关于LayoutInflater方法,无论是在listview的适配器中,还是在动态添加view的时候,都会出现它的身影,最开始我在看《第一行代码》时,不知道这个方法实际的参数到底指的是什么意思,后来看了一些博客以及查看了源代码,对它参数的理解加深了不少。今天更大家分享一下它的常用写法以及容易出错的地方,再分析原因。

  先从最常见的listview中看看使用这个方法的情况,这是填充listview的item布局代码,待会用的上:

  这是MainActivity代码:

  

public class MainActivity extends AppCompatActivity {
private ListView listView;
private List<String> list = new ArrayList<>();
private MainActivity context = this;
private MyAdapter adapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list.add("张三");
list.add("李四");
list.add("王五");
listView = findViewById(R.id.listview);
adapter = new MyAdapter();
listView.setAdapter(adapter);
} class MyAdapter extends BaseAdapter{ @Override
public int getCount() {
return list.size();
} @Override
public Object getItem(int position) {
return list.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null){
//parent 指的是listview
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent,false);
}
TextView tv = convertView.findViewById(R.id.text);
String name = list.get(position);
tv.setText(name);
return convertView;
}
}
}

  在内部类MyAdaper中getView()方法中,LayoutInflater.inflate()方法传了3个参数,第一个参数是你想填充成view的xml文件id,第二个参数类型是ViewGroup,它的作用是在你第一个xml生成的view的根布局上再加上一个父布局。这句话很重要,等会看例子会更明白。第3个参数是布尔型,表示绿色的那句话是否执行;方法返回的是第一个xml生成的view的最顶层根布局。好解释完3个参数,先看看以上代码的运行结果截图:

  此时每个item的高度为80dp,也就是item_list中的layout_height属性生效.现在我们把 inflate(R.layout.list_item, parent,false);中的false改为true.重新运行程序,结果。。。。程序崩溃。

  log日志中出现这样的一行信息:addView(View, LayoutParams) is not supported in AdapterView

  原因是:listview继承自AdapterView,当第3个参数为true,表示将执行viewgroup.addView(第一个xml生成的view);这里的viewGroup实际指的是listview,而listview.addView()这个方法在父类AdapterView中已经声明了,调用它就会抛出异常。

  再次修改参数,inflate(R.layout.list_item,null,false);运行程序结果如图:

  程序没崩,谢天谢地!不过高度好像变短了啊!我设置的80dp,怎么变得只是包裹了内容。我把80dp改成给180dp,结果还是没变化。这是怎么搞的?原来当第二个参数为null时,此时最外层节点的layout属性全部失效了。就选你把false改为true也无济于事。我们在改改方法参数看看:inflate(R.layout.list_item,parent).这次只传两个参数,且第二个参数不为null,运行看看,结果程序再次addView(View, LayoutParams) is not supported in AdapterView     这种情况等同于inflate(R.layout.list_item, parent,true).

  再将参数改为 inflate(R.layout.list_item,null)结果等同于inflate(R.layout.list_item,null,false) or inflate(R.layout.list_item,null,true).天啊!参数不同又是崩溃又是layout属性失效,难怪这个inflate方法就是要3个都写对,才会出现预期的结果!看来前辈的写法让我们少走了很多弯路啊!为了理清这个方法不同参数带来的不同问题,只能从源代码中取寻找原因。

铺垫了这么多,就是为了和大家一起分析源代码。底下的内容来源用一句歌词来概括:(我哪里 碍到你 硬说跟我 有默契      是编程 让我们 东拼西凑在一起)

  不管是3个参数,还是2个参数,都会走进上面这个方法,提一点:我们的xml已经在方法中转化成了解析器对象,也就是说这个parsr对象带有xml文件的全部信息,进入了下一个inflate方法,我们继续跟进:

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
//这里的result就是最终该方法要返回的view,初始时设为第二个参数root
View result = root; try {
// Look for the root node.
int type;
//在while中找到了parser的根节点。寻找方法既不是起始标志,也不是结束标志,它就是最开始的根节点。
//后面的节点都是它的子节点。这个节点对应的是xml布局中最外层的布局。
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
} if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
} final String name = parser.getName(); if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//你的布局最外层是否merge标签,不是的话,不用看里面的
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
} rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//这里返回的是xml布局根节点的对应的view。其中createViewFromTag()是通过name找到类加载器,然后利用反射
//创建name对应的view。如<TextView>就会创建TextView对象。
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null; if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
//这里的root经常让人理解为xml生成view的根节点,上面的temp才是那个根节点,这里的root你是传进来的第二个
//参数viewGroup,它更第一个参数没半毛钱关系。这里创建root的布局参数。
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//这里的情况对应的是第二个参数不为null,且第3个参数为false,这是getView里的最常用的写法。
//temp与root的区别解释上面说了,之所以为temp设置布局参数需要传root的params,你因为你想要temp的layout属性
//生效,就必须给他的外层加个容器,这里指root,只有存在这个容器,它的layout属性才有意义。 temp.setLayoutParams(params);
}
} if (DEBUG) {
System.out.println("-----> start inflating children");
} // Inflate all children under temp against its context.
//这个方法的任务就是将xml里的所有子节点全部填充成view,并调用addView添加到temp里面。
//实现的逻辑根据当前xml标签名生成对应的view和viewGroup,如果是view就直接添加到temp里,如果是viewGroup
//继续调用rInflateChildren,直至解析完它的子view。
rInflateChildren(parser, temp, attrs, true); if (DEBUG) {
System.out.println("-----> done inflating children");
} // We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//前面提到过 listview(也就是这里的root).addView会报错,当我们getView第二个参数不为null,且第3个参数为true,
//就是触发了这里的条件
if (root != null && attachToRoot) {
root.addView(temp, params);
} // Decide whether to return the root that was passed in or the
// top view found in xml.
//官方的注释说的很明白:决定是返回root还是我们xml的顶级view(temp)。
//如果满足以下条件,就返回顶级view--temp。在上面的例子中,只要root为null,不管第3个参数为true还是false,这个if都会成立,
//结果返回没有外层容器的顶级view。因此它的layout属性就失效了。
if (root == null || !attachToRoot) {
result = temp;
}
} } catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW);
} return result;
}
}

  理清楚参数是否为null,以及是否为true,false,都在注释中写的很明白了。不保证百分之百正确,但大体上不会错的很离谱。另外2个参数是怎么关联到3个参数的代码如下:

  从代码很清晰的看出,第二个参数是否为null,直接导致了第三个参数是否为true还是false。因此看懂了超长版,这一行代码简直不要再简单。

  走笔至此,关于这个方法还有一个很有趣的用法(从Toast的API中看到的),很巧妙。它的用处是主布局设置好的layout参数,要添加进来的子view的layout属性不管怎么设置都无效:先看看代码:

这是主界面:

  这是要将要添加的子界面布局:

  现在我们让子界面的layout不管设置什么参数,它的属性都不会生效:

  

  这个方法中,inflate()方法的第一个xml布局的顶级view temp,和第二个viewGroup对应的root可以说是同一个view。这个view没有父容器,所以不管子界面的layout属性怎么设置,最终都受到主界面layout的限制。这种写法的巧妙在于从xml中找到顶级节点的id,生成了两个一样的顶级view,而且第3个参数不管是true还是false,执行root.addView(temp)还是temp.setLayoutParams(root.params),同样也是无法影响先前的结果。

  

layoutInflater参数解析与源码分析的更多相关文章

  1. ReentrantLock解析及源码分析

    本文结构 Tips:说明一部分概念及阅读源码需要的基础内容 ReentrantLock简介 公平机制:对于公平机制和非公平机制进行介绍,包含对比 实现:Sync源码解析额,公平和非公平模式的加锁.解锁 ...

  2. Django(49)drf解析模块源码分析

    前言 上一篇分析了请求模块的源码,如下: def initialize_request(self, request, *args, **kwargs): """ Retu ...

  3. 自定义HandlerMethodArgumentResolver参数解析器和源码分析

    在初学springmvc框架时,我就一直有一个疑问,为什么controller方法上竟然可以放这么多的参数,而且都能得到想要的对象,比如HttpServletRequest或HttpServletRe ...

  4. Django 之 restframework 解析器源码分析

    解析器分类: 1. JSONPaser ----> 解析 JSON-serialized data (解析JSON序列化的数据) 2.FormParser ---->解析form 表单中 ...

  5. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  6. Retrofit源码分析(一)

    1.基本用法 创建接口 public interface GitHubService { @GET("users/{user}/repos") Observable<List ...

  7. tomcat 源码分析

    Tomcat源码分析——Session管理分析(下)    Tomcat源码分析——Session管理分析(上)     Tomcat源码分析——请求原理分析(下)     Tomcat源码分析——请 ...

  8. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  9. ABP源码分析十六:DTO的设计

    IDTO:空接口,用于标注Dto对象. ComboboxItemDto:用于combobox/list中Item的DTO NameValueDto<T>/NameValueDto:用于na ...

随机推荐

  1. Spinner 用法

    </Spinner> <TextView android:layout_width="wrap_content" android:layout_height=&q ...

  2. 渗透测试-http协议简介

    HTTP协议 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议.所有的WWW文件都 必须遵守这个标准.设计HTTP最初的目的是为了 ...

  3. 决战Leetcode: easy part(1-50)

    本博客是个人原创的针对leetcode上的problem的解法,所有solution都基本通过了leetcode的官方Judging,个别未通过的例外情况会在相应部分作特别说明. 欢迎互相交流! em ...

  4. OpenCV-Python 轮廓属性 | 二十三

    目标 在这里,我们将学习提取一些常用的物体属性,如坚实度,等效直径,掩模图像,平均强度等.更多的功能可以在Matlab regionprops文档中找到. (注:质心.面积.周长等也属于这一类,但我们 ...

  5. html-css:浮动_清除浮动

    1.浮动 清除浮动之前我们首先需要了解为什么要清除浮动 1. 假设我们有一个父盒子,不设置高度,其高度有内部子盒子的大小自动撑开,这样是完全可行的,因为有时候我们并不想直接固定死父盒子的大小,而是根据 ...

  6. 【mysql】索引 回表 覆盖索引 索引下推

    索引类型 索引类型分为主键索引和非主键索引.(一定要牢记,是怎么存储数据的) 主键索引的叶子节点存的是整行数据.在 InnoDB 里,主键索引也被称为聚簇索引(clustered index). 非主 ...

  7. 加密解密 Python

    常见加密方式和Python实现 1. 前言 我们所说的加密方式,都是对二进制编码的格式进行加密的,对应到Python中,则是我们的Bytes. 所以当我们在Python中进行加密操作的时候,要确保我们 ...

  8. Effective Java要点笔记

    第一章: 创建和销毁对象 类可以通过静态工厂方法来提供客户端,而不是通过构造器 优点: 自定义工厂名称,提高可读性 可以工厂里搞单例 控制实例类是哪种子类 总之是更加灵活,可读性更高 缺点: 有可能会 ...

  9. NodeMCU入坑指南-低成本打造零舍友闻风丧胆WiFi断网神器

    前言 最近对IoT方面比较感兴趣,所以在某宝上入手了一块NodeMCU的开发板,至于为什么我选择这块开发板呢?嘿嘿,当然是因为便宜啊

  10. springBoot配置文件属性注入

    以一个微信公众号开发为例,注入微信appid 1.在application.properites文件中定义属性 #===================微信相关=============#公众号wxp ...