LayoutInflator源码分析

获取LayoutInflator对象

获取LayoutInflator的方式有两种:

  1. 使用LayoutInflator.from(Context context)可以获取到LayoutInflator对象。
  2. (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

第一种本质也是调用第二种方式。

从本质上看context无论是以何种形式传入,最终都是利用binder获取的远程Service的能力

进行XML文件的填装工作。

LayoutInflator方法

LyaoutInflator含有的方法如下:

其中核心功能地一个系列就是inflate方法和rInflate方法

inflate方法 | 作用是把XML文件以View形式实例化到内存中

rInflate方法 | 作用是递归调用把XML中相应的嵌套布局结构也实例化出来并添加到XML的根布局中

inflate方法

inflate系列方法最终调用的都是:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)

方法

但是实际上最常用的是:

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

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)

因为XmlPullParser对象并不是普通的XML的来的。而是在编译器XML预编译出来的文件得到的。所以普通的XML在这里转换成

XmlPullParser对象并不允许。所以必须借助Resource得到布局id进行相关操作。

参数代表的含义:

  1. int resource | XML布局资源的id.(e.g. R.layout.main_activity)
  2. ViewGroup root | 父root对象,若attachTRoot==true, 则把resource代表的布局对象填充到root.

    若attachToRoot == false,则resource代表的布局只是利用root的LayoutParams即布局参数,但是不往root里添加。
  3. attachToRoot | true 则返回父布局root, false 则返回XML对应得视图对象(源码中为temp对象)

inflate方法的调用流程如下:

下面用代码做一些试验:

这个是activity_main.xml:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mainView"
tools:context="com.example.myapplication.MainActivity"
android:orientation="vertical"> </LinearLayout>

这个是一个textview 在layout文件夹命名为pink.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent"
android:text="red"> </TextView>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TextView view;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); ViewGroup parent = (ViewGroup) findViewById(R.id.mainView); // result: layout_height=wrap_content layout_width=match_parent
view = (TextView) LayoutInflater.from(this).inflate(R.layout.pink, null); parent.addView(view); // result: layout_height=100 layout_width=100
view = (TextView) LayoutInflater.from(this).inflate(R.layout.pink, null); parent.addView(view, 100, 100); // result: layout_height=25dp layout_width=25dp
// view=textView due to attachRoot=false
view = (TextView) LayoutInflater.from(this).inflate(R.layout.pink, parent, false); parent.addView(view); // result: layout_height=25dp layout_width=25dp
// parent.addView not necessary as this is already done by attachRoot=true
// view=root due to parent supplied as hierarchy root and attachRoot=true ViewGroup p = LayoutInflater.from(this).inflate(R.layout.pink, parent, true); }
}

在开发者眼里这个inflate有时候显得很诡异。下面就意义分析一下上面的结果的原因:

inflate(R.layout.pink, null) 父布局addView(view)

// result: layout_height=wrap_content layout_width=match_parent
view = (TextView) LayoutInflater.from(this).inflate(R.layout.pink, null); parent.addView(view);

这里的结果是宽match_parent 高wrap_content

刚开始肯定心里满是WTF,因为这里根本没有给pink布局文件指定一个父root.那pink在父布局中是怎么判定自己的布局参数的呢?

其实秘密就在addView方法中

/**

*

Adds a child view. If no layout parameters are already set on the child, the

* default parameters for this ViewGroup are set on the child.

*

*

Note: do not invoke this method from

* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},

* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.

*

* @param child the child view to add

*

* @see #generateDefaultLayoutParams()

*/

public void addView(View child) {

addView(child, -1);

}

  /**
* Adds a child view. If no layout parameters are already set on the child, the
* default parameters for this ViewGroup are set on the child.
*
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
* @param index the position at which to add the child
*/
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();//这里是关键,不通的ViewGroup会重新此方法
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
} @Override//此处我们用的LinearLayout 这里是他的重新实现
protected LayoutParams generateDefaultLayoutParams() {
if (mOrientation == HORIZONTAL) {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} else if (mOrientation == VERTICAL) {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
return null;
}

通过源码可以看出addView对于要添加的View的默认布局参数是依据不同的ViewGroup来的。ViewGroup默认宽高都是wrap_content

。而LinearLayout对子View要求的默认布局参数是根据布局方向定的:

  1. 水平布局 | 宽高均为wrap_content
  2. 垂直布局 | 宽是match_parent 高是warp_content

所以就可以解释为什么在指定root为null的时候addView得到的是宽是match_parent 高是warp_content了。因为我么用了垂直布局

inflate(R.layout.pink, null) 父布局parent.addView(view, 100, 100)

这里依旧没有为pink指定其在父布局中的布局参数。但是父布局调用了parent.addView(view, 100, 100)。

/**
* Adds a child view with this ViewGroup's default layout parameters and the
* specified width and height.
*
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
*/
public void addView(View child, int width, int height) {
final LayoutParams params = generateDefaultLayoutParams();
params.width = width;
params.height = height;
addView(child, -1, params);
}

源码也很明确,虽然布局参数依旧是父布局默认的。但是为布局参数重新指定了宽高,覆盖了默认值。所以这样指定可以改掉

pink在父布局中的布局参数。

inflate(R.layout.pink, parent, false)

这里为pink指定了其在父布局中应当使用的布局参数是parent的布局参数。但是并不把pink布局加载到parent视图中。

返回的依旧是pink对应的布局。

这个过程其实就是上面的流程图中的过程。

这里不再分析。

inflate(R.layout.pink, parent, true)

这里同样是上述的流程图过程。pink的布局参数用parent的布局参数。并且把pink布局视图放到parent中。返回的是

parent。

这里需要注意,因为这里是传入的parent是mian_view对应的视图,所以返回的也是parent.但是如果这里传入的是

另外的ViewGroup,那么返回的就是那个ViewGroup了。

最具有迷惑性的属性是root 和attachToRoot

root相对来好解释一点:

root就仅仅是为xml文件提供一个布局参数LayoutParams。用于限定大小和位置。不论attachToRoot true或者false

这个布局参数都会实实在在的应用在xml对应的视图对象上。

attachToRoot | true xml布局对应的对象利用root的布局参数限定来填充到root对象中。返回的是root对象。

attachToRoot | false xml布局对应的对象利用root的布局参数但是返回的是xml对应对象的根布局对象。要想把返回的View

加到父布局中就要用其他的办法。

但是什么时候用true 什么时候用false呢?

用true的场景:

  1. 假如父布局是一个LinearLayout,在父布局上加一个button,那么直接用true即可

    下面这两种方式是等价的

    inflater.inflate(R.layout.custom_button, mLinearLayout, true);

    inflater.inflate(R.layout.custom_button, mLinearLayout);

    在root不空时,attachToRoot是true

  2. 自定义View

    自定义View初始化的时候,目的就是为了给自定义的View加一个自己的布局上去。

    private void init() {

    LayoutInflater inflater = LayoutInflater.from(getContext());

    inflater.inflate(R.layout.view_with_merge_tag, this);

    }

总结: 总结一下就是,在我们只是需要往root上加布局对象。而不需要的到返回的XML布局对象时。使用true即可

需要false的场景:

  1. 比如有个button的布局文件。想在加到父布局之前做一些定制。那么这是肯定需要先拿到符合父布局布局参数的button

    的。做完定制以后再添加到父布局中

    Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);

    。。。。。

    mLinearLayout.addView(button);

  2. RecyclerView 子类中onCreateViewHolder方法中需要使用false

    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负责呈现和填充XML布局的时机

  1. Fragment 中onCreateView方法要使用false

    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就是fragment中onCreateView方法第二个参数的视图对象。

    //而fragment_layout填充附加到parentViewGroup的过程是FragmentManager 来做的

    //所以你一定不能为同一个parentViewGroup添加两次,所以下面要传false

    public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);



    return view;

    }

当然root参数也可以传null。但是可能会带来不符合预期的结果。因为root为null的情况要有ViewGroup及其子类自己决定。

而你在XML布局中指定的参数就会失效。所以为了保证XML中的布局参数就是我们想要XML展示的。那就需要传入root.

例外情况:AlertDialog。inflater.inflate(R.layout.custom_alert_dialog, null);这是合理的。

因为AlertDialog会为所有的布局都采取match_parent

总结

为了保证结果是符合预期的。需要尽量按照下面的方案来操作:

  1. 只要有父布局,就一定穿进去父布局当root.
  2. 避免给root传null因为不同的viewGroup实现的generateDefaultLayoutParams不同
  3. 只要我们不负责把xml附加到root上,attachToRoot 参数传false.
  4. 同一个XML不要为同一个ViewGroup传两次true,否则報异常
  5. 自定义View attachToRoot传true

参考:

https://www.bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/

Android笔记--LayoutInflator源码和使用分析的更多相关文章

  1. SLAM学习笔记 - ORB_SLAM2源码运行及分析

    参考资料: DBow2的理解 单目跑TUM数据集的运行和函数调用过程 跑数据集不需要ros和相机标定,进入ORB_SLAM目录,执行以下命令: ./Examples/Monocluar/mono_tu ...

  2. 从源码的角度分析ViewGruop的事件分发

    从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...

  3. Android 开源项目源码解析(第二期)

    Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations ...

  4. 安卓图表引擎AChartEngine(二) - 示例源码概述和分析

    首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...

  5. android仿漫画源码、抽奖转盘、Google相册、动画源码等

    Android精选源码 android实现仿今日头条的开源项目 波浪效果,实现流量的动态显示 美妆领域的app, 集成了摄像头取色, 朋友圈, 滤镜等 android仿漫画源码 android一个视差 ...

  6. android狼人杀源码,桌面源码,猎豹快切源码

    Android精选源码 android实现狼人杀app源码 android实现精心打造的Android基础框架源码 android热门电影的客户端源码 android 实现桌面的Launcher源码 ...

  7. Android 视频 教程 源码 电子书 网址

    资源名称 资源地址 下载量 好评率8天快速掌握Android视频教程67集(附源码)http://down.51cto.com/zt/2197 32157Android开发入门之实战技巧和源码 htt ...

  8. 第一部分:开发前的准备-第八章 Android SDK与源码下载

    第8章 Android SDK与源码下载 如果你是新下载的SDK,请阅读一下步骤了解如何设置SDK.如果你已经下载使用过SDK,那么你应该使用AVD Manager,来更新即可. 下面是构建Andro ...

  9. Hadoop学习笔记(9) ——源码初窥

    Hadoop学习笔记(9) ——源码初窥 之前我们把Hadoop算是入了门,下载的源码,写了HelloWorld,简要分析了其编程要点,然后也编了个较复杂的示例.接下来其实就有两条路可走了,一条是继续 ...

随机推荐

  1. border-radius实现圆弧阴影效果

    1 原理 利用border-radius实现一个圆弧边的矩形,并添加box-shadow,然后放在目标元素的下方 demo: html <div class="demo1"& ...

  2. c/c++语言实现tesseract ocr引擎编程实例

    编译下面的程序操作系统必须在安装了tesseract库和leptonica库才可以 Basic example c++ code: #include <tesseract/baseapi.h&g ...

  3. linux下删除3分钟之前指定文件夹下的指定类型文件

    如果想要修改crontab,一般做以下的几步就可以了 将crontab 推到一个自定义的文件上 crontab -l>;tmp 编辑这个文件,做需要的修改 vi tmp 推回crontab cr ...

  4. solr--搭建全文搜索服务器

    1.下载http://www-eu.apache.org/dist/lucene/solr/6.5.0/ 2.新建: 云模式 3.打开浏览器: 简单的中文分词,暂时是一个一个的分词:

  5. AnimationCurve

    http://blog.sina.com.cn/s/blog_471132920101f8nv.html 说明:关于animationCurve的使用总结1 创建,可以新建脚本,创建animation ...

  6. springboot 启动

    1. 新建一个java 类,名为Application,代码内容: @ServletComponentScan@SpringBootApplicationpublic class Applicatio ...

  7. Delphi调用C# 编写dll动态库

    Delphi调用C# 编写dll动态库 编写C#dll的方法都一样,首先在vs2005中创建一个“类库”项目WZPayDll, using System.Runtime.InteropServices ...

  8. 如何使用WPS从正文开始页码为1,而不是从目录开始?

    在插入目录前,在最前页插入一个空白页,在这个空白页里面生成目录,双击正文的页脚,点一下出现的与上一节相同的按钮,关闭页眉页脚的同前节,发现与上一节相同这几个字消失后,把目录中的页码删除,不会在影响正文 ...

  9. 【VueJS】windows环境安装vue-cli及webpack并创建VueJS项目

    1. 安装node.js, Node.js安装包及源码下载地址为:https://nodejs.org/en/download/. 这次node.js不是主角,默认已安装好了,通过npm –v查看no ...

  10. SpringBoot---核心---基本配置

    1.[入口类和@SpringBootApplication注解] 2.[关闭特定的配置] 3.[定制Banner] 1.1 修改Banner 1.2 关闭Banner 4.SpringBoot配置文件