Android LayoutInflater简介和使用

一、什么是LayoutInflater?

翻译源码中的解释:

实例化一个布局XML文件到他相应的View视图中。他从未被直接使用。相反,需要使用Activiy中的getLayoutInflater()或Context的getSystemService()来检索一个标准的LayoutInflater实例,该实例已经连接到当前的上下文并正确配置为您正在运行的设备;
要为您的视图创建一个新的LayoutInflater,需要使用额外的Factory,你可以使用 cloneInContext 克隆现有的ViewFactory,然后在其上调用 setFactory()来包含您的Factory。
出于性能远远,视图填充在很大程度上依赖于在构建时完成的XML文件的预处理。因此,目前不可能在运行时通过XmlPullParser在普通的XML文件上使 LayoutInflater,它只对编译后的资源(R.)返回的XmlPullParse有效

通过上面的翻译,我自认为的LayoutInflater简单理解如下:

  • LayoutInflater是一个视图填充器;
  • 获取LayoutInflater 需要通过 Activity.getLayoutInflater() 或 Context.getSystemService();
  • 可以自定义一个新的LayoutInflater(目前我还没有遇到过,不理解);
  • LayoutInflater只对构建的XML的布局编译后的R文件返回的XmlPullParse有效;

二、LayoutInflater的获取

第一种方式: 从给定的上下文中获取LayoutInflater:

LayoutInflater  inflater = LayoutInflater.from(context);

第二种方式:在Activity中获取LayoutInflater:

LayoutInflater inflater = getLayoutInflater();

第三种方式:通过context.getSystemService()获取:

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

源码调用分析

第一种 LayoutInflater.from(context);调用方式所用源码:

@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
 .....
 /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
.....
}

第二中Activity中 getLayoutInflater()调用源码分析:

Activity.class的源代码:
public class Activity extends .......  {
.........
 @NonNull
    public LayoutInflater getLayoutInflater() {
        return getWindow().getLayoutInflater();
    }
.........
 @NonNull
    public LayoutInflater getLayoutInflater() {
        return getWindow().getLayoutInflater();
    }
.........
   final void attach(.....){
          ......
           mWindow = new PhoneWindow(this, window, activityConfigCallback);
          .......
    }
.........
}

PhoneWindow源码:

   public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }

查看源码可以发现:第一种调用的是第三种实例,第二种调用的是第一种实例,所以可以得到结果是:最终获取LayoutInflater都是通过第三种方式获取滴;

三、获取View : View.inflate()和LayoutInflate.inflater()

在项目中,经常需要用到将一个.xml布局文件转为View,这里有两种方式:
1、使用View的静态方法inflate,不用获取LayoutInflater,其实是源码内部同样获取了LayoutInflater:

View中的源码:
  /**
  * 从xml资源中填充视图,这个方法封装了LayoutInflater
  * @param  context  您的Activity或Application上下文对象
  * @param  resouce 需要填充的资源ID
  * @param  root 一个视图组,他将是父视图组,用于正确填充layout的参数
  */
  public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }

2、使用LayoutInflater.inflater():

有四种调用方式:
mLayoutInflater.inflate(@LayoutRes int resource,@Nullable ViewGroup root);
mLayoutInflater.inflate(XmlPullParser parser,@Nullable ViewGroup root);
mLayoutInflater.inflate(@Layoutres int resourse,@Nullable ViewGroup root ,boolean attachToRoot);
mLayoutInflater.inflater(XmlPullParser parser,@Nullable ViewGroup root,boolean attachToToot) ;

查看上面四种方法调用的源码后,发现:最终调用的都是第四种方法;

在实际项目开放中,经常使用的是一下两种方法:

mLayoutInflater.inflate(@LayoutRes int resource,@Nullable ViewGroup root);
mLayoutInflater.inflate(@Layoutres int resourse,@Nullable ViewGroup root ,boolean attachToRoot);

可以看见,一个是两个参数,一个是三个参数,而两个参数的inflate,其实也是调用的三个参数的inflate,现在来看看三个参数代表的意义和使用需要注意的事项:

@LayoutRes int resource : 这个很明显,是需要传入的布局的资源ID;
@Nullable ViewGroup root : 需要附加到resource资源文件的父控件,即调用inflate方法会得到一个View对象,root参数就是接受该对象的容器;
boolean attachToRoot : 是否把inflate得到的View对象添加到root中,该参数为false时,表示不直接添加到root,true时,表示直接添加到root中;

三个参数都明白意思了,那现在再把使用的几种参数情况理解一下:

既然两个参数最终都是调用的三个参数方法,那先分析三个参数的inflate方法:

现在定义出来需要用到的布局文件:
Activity布局文件,Activity的xml布局文件activity_main:里面只有一个空了Linearlayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/ll_root">
</LinearLayout>

再定义一个用来填充的布局文件inflatelayout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:orientation="vertical"
    android:background="@color/colorPrimary"
    android:gravity="center">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试按钮"/>
</LinearLayout>

1、root不为空,attachToRoot为true:
在代码中inflatelayout.xml添加到我的Activity布局中:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout ll_root = (LinearLayout)findViewById(R.id.ll_root) ;      //Activity的根布局
        getLayoutInflater().inflate(R.layout.inflatelayout,ll_root,true);            //进行布局填充
    }

查看项目运行结果:

可以看见,已经将填充的布局添加到activty中了,这是因为第三个参数为true,表示将第一个参数所指的的布局添加到第二个参数的View中

2、root不为空,attachToRoot为false:

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout ll_root = (LinearLayout)findViewById(R.id.ll_root) ;
        View view = getLayoutInflater().inflate(R.layout.inflatelayout,ll_root,false);
        ll_root.addView(view);
    }

上面代码实现了与attachToRoot为true一样的运行效果,现在分析一下,为false的情况:

attachToRoot为false,表示不将第一个参数所指定的布局文件添加到第二个参数的View中;在这里会有一个疑问:既然不将布局文件添加到指定的root中,那为什么不直接将第二个参数给null?这里就涉及另外一个问题:我们给控件所指定的layout_weight和layout_height到底是什么意思?该属性表示一个控件在容器中大小,就是说这个控件必须在容器中,这个属性才有意义,否则无意义!那由此我们可以等到这个结论:如果我直接将inflatelayout.xml填充而不给他指定一个父布局,则inflatelayout.xml的根节点的高宽会失效,如果想让其有效,就必须为其指定一个父容器。现在我们能明白root和attachToRoot在这里器到的作用:设置root不为空,是为了让inflatelayout.xml的根布局宽高有效,attachToRoot就是不自动将inflateLayout.xml填充到父容器中。

3、root为空:
其实滴二种情况也将root为空的给解释一下。这里在重复一下:root不为空,则填充的xml文件的根布局的宽高设置有效,attachToRoot决定是否将填充布局自动添加到root中; root为空,即不为填充控件指定父布局,这个时候attachToRoot无论是true还是false都是没有意义的!
看下面代码的例子:

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout ll_root = (LinearLayout)findViewById(R.id.ll_root) ;
        View view = getLayoutInflater().inflate(R.layout.inflatelayout,null,false);
  //    View view = getLayoutInflater().inflate(R.layout.inflatelayout,null,true);
        ll_root.addView(view);
    }

root为空,attachToRoot无论为true还是false,运行结果都为下图:

因为inflate时没有指定容器,所以他个宽高失效了,但如果我设置button的宽高,则会有效,因为他是处于一个容器中的;

现在分析两个参数的情况
我们先看两个参数的调用源码:

  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }

可以很明显看出,两个参数的inflate实际上调用的也是三个参数的inflate方法,attchToRoot根据root来判断值,所以最终的结果也就分为以下两种情况:

  • root为null,等同于三个参数的:root为空,attachToRoot为false;
  • root不为null,等同于三个参数的:root不为空,attachToRoot为true;

上面就是LayoutInflater的简单理解和inflate方法的使用介绍;

这里再记录分析一个问题:为什么Activity布局的根节点的宽高属性会生效?

大部分Activity页面都由两部分组成(Android版本号和应用主题会影响到Activity页面的组成,这里以常见页面为例),我们的Activity页面中有一个顶级View叫DecorView,DecorView中包含一个Vertical的LinearLayout,Linearlayout由两部分组成,第一部分为标题栏,第二部分为内容栏,内容栏是一个FrameLayout,我们在Activity中调用setContent就是将View添加到这个FrameLayout中;

看Activity中的setContent源码:

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

 @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

可以看见再设置的时候,获取了android.R.id.content的View,并将其做为了父控件后使用的 LayoutInflater.from(mContext).inflate(resId, contentParent);所以我们设置的布局的宽高会生效;

本文参考链接:三个案例带你看懂LayoutInflater中inflate方法两个参数和三个参数的区别

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容