1、 题外话

相信大家对LayoutInflate都不陌生,特别在ListView的Adapter的getView方法中基本都会出现,使用inflate方法去加载一个布局,用于ListView的每个Item的布局。Inflate有三个参数,我在初学Android的时候这么理解的:

对于Inflate的三个参数(int resource, ViewGroup root, boolean attachToRoot)

如果inflate(layoutId, null )则layoutId的最外层的控件的宽高是没有效果的

如果inflate(layoutId, root, false ) 则认为和上面效果是一样的

如果inflate(layoutId, root, true ) 则认为这样的话layoutId的最外层控件的宽高才能正常显示

如果你也这么认为,那么你有就必要好好阅读这篇文章,因为这篇文章首先会验证上面的理解是错误的,然后从源码角度去解释,最后会从ViewGroup与View的角度去解释。

2、 实践是验证真理的唯一标准

下面我写一个特别常见的例子来验证上面的理解是错误的,一个特别简单的ListView,每个Item中放一个按钮:

Activity的布局文件:

  1. <ListView xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/id_listview"
  4. android:layout_width="fill_parent"
  5. android:layout_height="wrap_content" >
  6. </ListView>

ListView的Item的布局文件:

  1. <Button xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/id_btn"
  4. android:layout_width="120dp"
  5. android:layout_height="120dp" >
  6. </Button>

ListView的适配器:

  1. package com.example.zhy_layoutinflater;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.view.LayoutInflater;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import android.widget.BaseAdapter;
  8. import android.widget.Button;
  9. public class MyAdapter extends BaseAdapter
  10. {
  11. private LayoutInflater mInflater;
  12. private List<String> mDatas;
  13. public MyAdapter(Context context, List<String> datas)
  14. {
  15. mInflater = LayoutInflater.from(context);
  16. mDatas = datas;
  17. }
  18. @Override
  19. public int getCount()
  20. {
  21. return mDatas.size();
  22. }
  23. @Override
  24. public Object getItem(int position)
  25. {
  26. return mDatas.get(position);
  27. }
  28. @Override
  29. public long getItemId(int position)
  30. {
  31. return position;
  32. }
  33. @Override
  34. public View getView(int position, View convertView, ViewGroup parent)
  35. {
  36. ViewHolder holder = null;
  37. if (convertView == null)
  38. {
  39. holder = new ViewHolder();
  40. convertView = mInflater.inflate(R.layout.item, null);
  41. //          convertView = mInflater.inflate(R.layout.item, parent ,false);
  42. //          convertView = mInflater.inflate(R.layout.item, parent ,true);
  43. holder.mBtn = (Button) convertView.findViewById(R.id.id_btn);
  44. convertView.setTag(holder);
  45. } else
  46. {
  47. holder = (ViewHolder) convertView.getTag();
  48. }
  49. holder.mBtn.setText(mDatas.get(position));
  50. return convertView;
  51. }
  52. private final class ViewHolder
  53. {
  54. Button mBtn;
  55. }
  56. }

主Activity:

  1. package com.example.zhy_layoutinflater;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.widget.ListView;
  7. public class MainActivity extends Activity
  8. {
  9. private ListView mListView;
  10. private MyAdapter mAdapter;
  11. private List<String> mDatas = Arrays.asList("Hello", "Java", "Android");
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState)
  14. {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.activity_main);
  17. mListView = (ListView) findViewById(R.id.id_listview);
  18. mAdapter = new MyAdapter(this, mDatas);
  19. mListView.setAdapter(mAdapter);
  20. }
  21. }

好了,相信大家对这个例子都再熟悉不过了,没啥好说的,我们主要关注getView里面的inflate那行代码:下面我依次把getView里的写成:
1、convertView = mInflater.inflate(R.layout.item, null);
2、convertView = mInflater.inflate(R.layout.item, parent ,false);
3、convertView = mInflater.inflate(R.layout.item, parent ,true);
分别看效果图:

图1:

图2:

图3:

  1. FATAL EXCEPTION: main
  2. java.lang.UnsupportedOperationException:
  3. addView(View, LayoutParams) is not supported in AdapterView

嗯,没错没有图3,第三种写法会报错。

由上面三行代码的变化,产生3个不同的结果,可以看到

inflater(resId, null )的确不能正确处理宽高的值,但是inflater(resId,parent,false)并非和inflater(resId, null )效果一致,它可以看出完美的显示了宽和高。

而inflater(resId,parent,true)报错了(错误的原因在解析源码的时候说)。

由此可见:文章开始提出的理解是绝对错误的。

3、源码解析

下面我通过源码来解释,这三种写法真正的差异

这三个方法,最终都会执行下面的代码:

  1. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. final AttributeSet attrs = Xml.asAttributeSet(parser);
  4. Context lastContext = (Context)mConstructorArgs[0];
  5. mConstructorArgs[0] = mContext;
  6. View result = root;
  7. try {
  8. // Look for the root node.
  9. int type;
  10. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  11. type != XmlPullParser.END_DOCUMENT) {
  12. // Empty
  13. }
  14. if (type != XmlPullParser.START_TAG) {
  15. throw new InflateException(parser.getPositionDescription()
  16. + ": No start tag found!");
  17. }
  18. final String name = parser.getName();
  19. if (DEBUG) {
  20. System.out.println("**************************");
  21. System.out.println("Creating root view: "
  22. + name);
  23. System.out.println("**************************");
  24. }
  25. if (TAG_MERGE.equals(name)) {
  26. if (root == null || !attachToRoot) {
  27. throw new InflateException("<merge /> can be used only with a valid "
  28. + "ViewGroup root and attachToRoot=true");
  29. }
  30. rInflate(parser, root, attrs, false);
  31. } else {
  32. // Temp is the root view that was found in the xml
  33. View temp;
  34. if (TAG_1995.equals(name)) {
  35. temp = new BlinkLayout(mContext, attrs);
  36. } else {
  37. temp = createViewFromTag(root, name, attrs);
  38. }
  39. ViewGroup.LayoutParams params = null;
  40. if (root != null) {
  41. if (DEBUG) {
  42. System.out.println("Creating params from root: " +
  43. root);
  44. }
  45. // Create layout params that match root, if supplied
  46. params = root.generateLayoutParams(attrs);
  47. if (!attachToRoot) {
  48. // Set the layout params for temp if we are not
  49. // attaching. (If we are, we use addView, below)
  50. temp.setLayoutParams(params);
  51. }
  52. }
  53. if (DEBUG) {
  54. System.out.println("-----> start inflating children");
  55. }
  56. // Inflate all children under temp
  57. rInflate(parser, temp, attrs, true);
  58. if (DEBUG) {
  59. System.out.println("-----> done inflating children");
  60. }
  61. // We are supposed to attach all the views we found (int temp)
  62. // to root. Do that now.
  63. if (root != null && attachToRoot) {
  64. root.addView(temp, params);
  65. }
  66. // Decide whether to return the root that was passed in or the
  67. // top view found in xml.
  68. if (root == null || !attachToRoot) {
  69. result = temp;
  70. }
  71. }
  72. } catch (XmlPullParserException e) {
  73. InflateException ex = new InflateException(e.getMessage());
  74. ex.initCause(e);
  75. throw ex;
  76. } catch (IOException e) {
  77. InflateException ex = new InflateException(
  78. parser.getPositionDescription()
  79. + ": " + e.getMessage());
  80. ex.initCause(e);
  81. throw ex;
  82. } finally {
  83. // Don't retain static reference on context.
  84. mConstructorArgs[0] = lastContext;
  85. mConstructorArgs[1] = null;
  86. }
  87. return result;
  88. }
  89. }

第6行:首先声明了View result = root ;//最终返回值为result

第43行执行了:temp = createViewFromTag(root, name, attrs);创建了View

然后直接看48-59:

  1. if(root!=null)
  2. {
  3. params = root.generateLayoutParams(attrs);
  4. if (!attachToRoot)
  5. {
  6. temp.setLayoutParams(params);
  7. }
  8. }

可以看到,当root不为null,attachToRoot为false时,为temp设置了LayoutParams.

继续往下,看73-75行:

  1. if (root != null && attachToRoot)
  2. {
  3. root.addView(temp, params);
  4. }

当root不为null,attachToRoot为true时,将tmp按照params添加到root中。

然后78-81行:

  1. if (root == null || !attachToRoot) {
  2. result = temp;
  3. }

如果root为null,或者attachToRoot为false则,将temp赋值给result。

最后返回result。

从上面的分析已经可以看出:

Inflate(resId , null ) 只创建temp ,返回temp

Inflate(resId , parent, false )创建temp,然后执行temp.setLayoutParams(params);返回temp

Inflate(resId , parent, true ) 创建temp,然后执行root.addView(temp, params);最后返回root

由上面已经能够解释:

Inflate(resId , null )不能正确处理宽和高是因为:layout_width,layout_height是相对了父级设置的,必须与父级的LayoutParams一致。而此temp的getLayoutParams为null

Inflate(resId , parent,false ) 可以正确处理,因为temp.setLayoutParams(params);这个params正是root.generateLayoutParams(attrs);得到的。

Inflate(resId , parent,true )不仅能够正确的处理,而且已经把resId这个view加入到了parent,并且返回的是parent,和以上两者返回值有绝对的区别,还记得文章前面的例子上,MyAdapter里面的getView报的错误:

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

这是因为源码中调用了root.addView(temp, params);而此时的root是我们的ListView,ListView为AdapterView的子类:
直接看AdapterView的源码:

  1. @Override
  2. public void addView(View child) {
  3. throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
  4. }

可以看到这个错误为啥产生了。

4、 进一步的解析

上面我根据源码得出的结论可能大家还是有一丝的迷惑,我再写个例子论证我们上面得出的结论:
主布局文件:

  1. <Button xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/id_btn"
  4. android:layout_width="120dp"
  5. android:layout_height="120dp"
  6. android:text="Button" >
  7. </Button>

主Activity:

  1. package com.example.zhy_layoutinflater;
  2. import android.app.ListActivity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. public class MainActivity extends ListActivity
  9. {
  10. private LayoutInflater mInflater;
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState)
  13. {
  14. super.onCreate(savedInstanceState);
  15. mInflater = LayoutInflater.from(this);
  16. View view1 = mInflater.inflate(R.layout.activity_main, null);
  17. View view2 = mInflater.inflate(R.layout.activity_main,
  18. (ViewGroup)findViewById(android.R.id.content), false);
  19. View view3 = mInflater.inflate(R.layout.activity_main,
  20. (ViewGroup)findViewById(android.R.id.content), true);
  21. Log.e("TAG", "view1 = " + view1  +" , view1.layoutParams = " + view1.getLayoutParams());
  22. Log.e("TAG", "view2 = " + view2  +" , view2.layoutParams = " + view2.getLayoutParams());
  23. Log.e("TAG", "view3 = " + view3  );
  24. }
  25. }

可以看到我们的主Activity并没有执行setContentView,仅仅执行了LayoutInflater的3个方法。
注:parent我们用的是Activity的内容区域:即android.R.id.content,是一个FrameLayout,我们在setContentView(resId)时,其实系统会自动为了包上一层FrameLayout(id=content)。

按照我们上面的说法:

view1的layoutParams 应该为null
view2的layoutParams 应该不为null,且为FrameLayout.LayoutParams
view3为FrameLayout,且将这个button添加到Activity的内容区域了(因为R.id.content代表Actvity内容区域)

下面看一下输出结果,和Activity的展示:

  1. 07-27 14:17:36.703: E/TAG(2911): view1 = android.widget.Button@429d1660 , view1.layoutParams = null
  2. 07-27 14:17:36.703: E/TAG(2911): view2 = android.widget.Button@42a0e120 , view2.layoutParams = android.widget.FrameLayout$LayoutParams@42a0e9a0
  3. 07-27 14:17:36.703: E/TAG(2911): view3 = android.widget.FrameLayout@42a0a240

效果图:

可见,虽然我们没有执行setContentView,但是依然可以看到绘制的控件,是因为

View view3 = mInflater.inflate(R.layout.activity_main,(ViewGroup)findViewById(android.R.id.content), true);

这个方法内部已经执行了root.addView(temp , params); 上面已经解析过了。

也可以看出:和我们的推测完全一致,到此已经完全说明了inflate3个重载的方法的区别。相信大家以后在使用时也能选择出最好的方式。不过下面准备从ViewGroup和View的角度来说一下,为啥layoutParams为null,就不能这确的处理。

5、从ViewGroup和View的角度来解析

如果大家对自定义ViewGroup和自定义View有一定的掌握,肯定不会对onMeasure方法陌生:
ViewGroup的onMeasure方法所做的是:

为childView设置测量模式和测量出来的值。

如何设置呢?就是根据LayoutParams。
如果childView的宽为:LayoutParams. MATCH_PARENT,则设置模式为MeasureSpec.EXACTLY,且为childView计算宽度。
如果childView的宽为:固定值(即大于0),则设置模式为MeasureSpec.EXACTLY,且将lp.width直接作为childView的宽度。
如果childView的宽为:LayoutParams. WRAP_CONTENT,则设置模式为:MeasureSpec.AT_MOST
高度与宽度类似。

View的onMeasure方法:
主要做的就是根据ViewGroup传入的测量模式和测量值,计算自己应该的宽和高:
一般是这样的流程:
如果宽的模式是AT_MOST:则自己计算宽的值。
如果宽的模式是EXACTLY:则直接使用MeasureSpec.getSize(widthMeasureSpec);

对于最后一块,如果不清楚,不要紧,以后我会在自定义ViewGroup和自定义View时详细讲解的。

大概就是这样的流程,真正的绘制过程肯定比这个要复杂,就是为了说明如果View的宽和高如果设置为准确值,则一定依赖于LayoutParams,所以我们的inflate(resId,null)才没能正确处理宽和高。

Android LayoutInflater深度解析的更多相关文章

  1. Android LayoutInflater深度解析 给你带来全新的认识

      转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38171465 , 本文出自:http://blog.csdn.net/lmj ...

  2. Android Fragment 深度解析

    1.Fragment的产生与介绍 Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视.针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套app,然后拷贝一份,修改布局以适应 ...

  3. android dp深度解析(转)

    我转载地方的连接:http://zhangkun716717-126-com.iteye.com/blog/1772696  当笔记记录一下 dip: device independent pixel ...

  4. Unity加载模块深度解析(Shader)

    作者:张鑫链接:https://zhuanlan.zhihu.com/p/21949663来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 接上一篇 加载模块深度解析(二 ...

  5. Unity加载模块深度解析(网格篇)

    在上一篇 加载模块深度解析(一)中,我们重点讨论了纹理资源的加载性能.这次,我们再来为你揭开其他主流资源的加载效率. 这是侑虎科技第53篇原创文章,欢迎转发分享,未经作者授权请勿转载.同时如果您有任何 ...

  6. android源码解析(十七)-->Activity布局加载流程

    版权声明:本文为博主原创文章,未经博主允许不得转载. 好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与andr ...

  7. Android LayoutInflater和findViewById 源码详解

    LayoutInflater大家很熟悉,简单点说就是布局文件XML解析器,setContentView函数也是调用了LayoutInflater 用法: View view = LayoutInfla ...

  8. 汇顶指纹传感器GF919深度解析

    前言: 随着指纹识别技术的日益普遍,其在手机上的应用也得到了广泛关注.作为全球第一款Android正面按压指纹识别手机,魅族MX4 Pro所搭载的国产指纹识别系统可谓是赚足了眼球,这就是由汇顶科技提供 ...

  9. Android ActionBar完全解析,使用官方推荐的最佳导航栏(下) .

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/25466665 本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工 ...

随机推荐

  1. 手动在Windows上创建kafka环境

    一 主要内容请移步 参考文章 二 一点小问题 安装上面的参考文章完成配置,并且尝试传输消息后,我尝试去查看kafka的消息数据,目录是{logdir}/test-0/00000000000000.lo ...

  2. 【IdentityServer4文档】- 支持和咨询选项

    支持和咨询选项 我们为 IdentityServer 提供多个免费和商业支持及咨询选项. 免费支持 免费支持是基于社区的,而且使用的是公共论坛 StackOverflow 有越来越多的使用 Ident ...

  3. TCP系列06—连接管理—5、TCP fastopen(TFO)

    一.TFO背景 当前web和web-like应用中一般都是在三次握手后开始数据传输,相比于UDP,多了一个RTT的时延,即使当前很多应用使用长连接来处理这种情况,但是仍然由一定比例的短连接,这额外多出 ...

  4. Hadoop出现错误:WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable,解决方案

    安装Hadoop的时候直接用的bin版本,根据教程安装好之后运行的时候发现出现了:WARN util.NativeCodeLoader: Unable to load native-hadoop li ...

  5. AndroidStudio3.0 注解报错Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor.

    把Androidstudio2.2的项目放到3.0里面去了,然后开始报错了. 体验最新版AndroidStudio3.0 Canary 8的时候,发现之前项目的butter knife报错,用到注解的 ...

  6. 什么情况下需要检测预装Win8/8.1电脑内置激活密钥(即Win8/8.1 OEM key)?

    1.针对预装在品牌机中的Win8操作系统,因使用了某些软件或自己操作不当导致系统激活Key被替换而激活失效时,你需要找到内置在电脑中的OEM key,重新输入OEM key来激活系统. 2.针对预装W ...

  7. 【bzoj2259】[Oibh]新型计算机 堆优化Dijkstra

    题目描述 Tim正在摆弄着他设计的“计算机”,他认为这台计算机原理很独特,因此利用它可以解决许多难题. 但是,有一个难题他却解决不了,是这台计算机的输入问题.新型计算机的输入也很独特,假设输入序列中有 ...

  8. Python 嵌套函数和闭包

    Python 嵌套函数和闭包 1.函数嵌套 如果在一个函数内部定义了另一个函数,我们称外部的函数为外函数,内部的函数为内函数,如下代码: def out_func(): def inner_func1 ...

  9. 封装一个jquery库

    现在Javascript库海量,流行的也多,比如jQuery,YUI等,虽然功能强大,但也是不万能的,功能不可能涉及方方面面,自己写一个的JS库是对这些的补充,很多也比较实用,把应用到项目中中去也比较 ...

  10. 【Luogu】P2901牛慢跑(K短路模板)

    题目链接 K短路居然用A*……奇妙. 先建反图从终点(1)跑一遍最短路,再A*,用堆存当前点到终点距离+从起点到当前点距离. 每次取出终点都可以视为发现了一个新的最短路. #include<cs ...