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




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



    

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方法两个参数和三个参数的区别

你可能感兴趣的:(Android LayoutInflater简介和使用)