LayoutInflater的参数 root 和 attachToRoot的作用分析

1,337 阅读9分钟

日常开发中很多地方都见到了LayoutInflater.from().inflate()方法去将一个布局文件的内容填充为一个View,特别是inflate()这个方法,这个方法的参数有布局文件id,root和attachToRoot,那么这个root和attachToRoot参数有什么作用呢?

本篇文章就是一篇详细分析 root和attachToRoot参数 对于填充的View有什么影响,当然最主要是为了我以后方便复(预)习的。

日常调用代码展示

日常开发过程中是通过类似下面的代码来将布局文件填充出来的,并且inflate()方法的返回值是一个View对象。

LayoutInflater.from(this).inflate(R.layout.layout_inflater, findViewById(R.id.ll), true);

点击inflate()方法,查看这个函数的root和attachToRoot参数对填充出来的View有什么影响。

查看inflate()函数源码

发现有两个return,

  • 通过调用tryInflatePrecompiled(resource, res, root, attachToRoot)方法 创建View并return;
  • 通过重载的inflate()方法创建View并return。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
              + Integer.toHexString(resource) + ")");
    }
    // 1、tryInflatePrecompiled()函数返回View对象
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
        // 2、通过重载函数inflate()返回View对象
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
  • 那么究竟是哪个方法创建了View对象呢?

    这里可以直接的说出结论,默认的情况下肯定是调用重载函数inflate()来创建出View对象的。

简单分析默认情况不调用tryInflatePrecompiled(resource, res, root, attachToRoot)创建View对象

在tryInflatePrecompiled(resource, res, root, attachToRoot)函数的代码块中,有一个判断,这是判断是这个方法是否工作的开关,默认情况下mUseCompiledView是false,所以默认的情况下,不会调用该方法创建View对象。

private @Nullable
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
    boolean attachToRoot) {
    if (!mUseCompiledView) {
        return null;
    }
    // 省略了好多的创建View的代码
    .......
}

那么创建出View的方法就是重载的inflate()方法了,

查看inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法

这里主要分析root和attachToRoot参数对创建出来的View的影响,对于View是如何创建出来这个问题,先放一放,等后面兴致来了再分析

下面的代码块是整个inflate()方法的简约版本,删除了很多的暂时不需要了解的代码,也方便看的时候,能够看到满满的全是重点,就很nice。

核心代码块展示

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    .... 省略了代码 ....
    // 1、声明返回的View对象 result,并赋予初始值为 root 
    View result = root;

    // 2、createViewFromTag(root, name, inflaterContext, attrs)并不是本文重点,只需要关心这个temp对象
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    /*
     *下面的代码进行分段,每一段有每一段的逻辑处理
     */
     
     // 第一段:设置temp的params,设置之后temp的最外层宽高才会生效
    ViewGroup.LayoutParams params = null;
    if (root != null) {
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }
    .... 省略了代码 ....

    // 第二段:是否需要把temp添加到root上
    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }

    // 第三段:result是否需要重新赋值
    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp;
    }
    
    .... 省略了代码 ....
    // 返回result对象
    return result;
}

排列组合root和attachToRoot的传参方式,并代入查看对生成的View影响

先按照排列组合的方式,列举出所有的传参方式,结果如下:

root取值attachToRoot取值
nullfalse
nulltrue
not nullfalse
not nulltrue

根据上面的组合方式,一组组的分析,这些参数对View的影响

当root = null , attachToRoot = false
  • 通过代码块+注释查看执行流程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    .... 省略了代码 ....
    // 1、声明返回的View对象 result,并赋予初始值为 root 
    View result = root;

    // 2、createViewFromTag(root, name, inflaterContext, attrs)并不是本文重点,只需要关心这个temp对象
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    /*
     *下面的代码进行分段,每一段有每一段的逻辑处理
     */
     
    // root = null , attachToRoot = false 所以不会执行第一段
    // 第一段:设置temp的params,设置之后temp的最外层宽高才会生效
    ViewGroup.LayoutParams params = null;
    if (root != null) {
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }
    .... 省略了代码 ....

    // root = null , attachToRoot = false 所以不会执行第二段
    // 第二段:是否需要把temp添加到root上
    // root = null,所以此段代码不执行
    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }

    // root = null , attachToRoot = false 所以会执行第三段,将temp赋值给result并返回该result
    // 第三段:result是否需要重新赋值
    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp;
    }
    
    .... 省略了代码 ....
    // 返回result对象
    return result;
}
  • 分析结果

    能够看到,最后创建出来的View没有设置params就使用,所以被创建出来的布局,最外层设置的宽高是无效的,并最终返回了创建出来的View对象。

当root = null , attachToRoot = true
  • 通过代码块+注释查看执行流程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    .... 省略了代码 ....
    // 1、声明返回的View对象 result,并赋予初始值为 root 
    View result = root;

    // 2、createViewFromTag(root, name, inflaterContext, attrs)并不是本文重点,只需要关心这个temp对象
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    /*
     *下面的代码进行分段,每一段有每一段的逻辑处理
     */
     
    // root = null , attachToRoot = true不会执行第一段
    // 第一段:设置temp的params,设置之后temp的最外层宽高才会生效
    ViewGroup.LayoutParams params = null;
    if (root != null) {
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }
    .... 省略了代码 ....

    // root = null , attachToRoot = true 不会执行第二段
    // 第二段:是否需要把temp添加到root上
    // root = null,所以此段代码不执行
    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }

    // root = null , attachToRoot = true 会执行第三段,将temp赋值给result并返回该result
    // 第三段:result是否需要重新赋值
    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp;
    }
    
    .... 省略了代码 ....
    // 返回result对象
    return result;
}
  • 分析结果

    能够看到,最后创建出来的View没有设置params就使用,所以被创建出来的布局,最外层设置的宽高是无效的,并最终返回了创建出来的View对象。

当root != null , attachToRoot = false
  • 通过代码块+注释查看执行流程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    .... 省略了代码 ....
    // 1、声明返回的View对象 result,并赋予初始值为 root 
    View result = root;

    // 2、createViewFromTag(root, name, inflaterContext, attrs)并不是本文重点,只需要关心这个temp对象
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    /*
     *下面的代码进行分段,每一段有每一段的逻辑处理
     */
     
    // 当root != null , attachToRoot = false 会执行第一段,设置params,此时最外层的宽高设置生效
    // 第一段:设置temp的params,设置之后temp的最外层宽高才会生效
    ViewGroup.LayoutParams params = null;
    if (root != null) {
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }
    .... 省略了代码 ....

    // 当root != null , attachToRoot = false不会执行第二段
    // 第二段:是否需要把temp添加到root上
    // root = null,所以此段代码不执行
    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }

    // 当root != null , attachToRoot = false会执行第三段,将temp赋值给result并返回该result
    // 第三段:result是否需要重新赋值
    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp;
    }
    
    .... 省略了代码 ....
    // 返回result对象
    return result;
}
  • 分析结果

    能够看到,最后创建出来的View有设置params,所以被创建出来的布局,最外层设置的宽高是有效的,并最终返回了创建出来的View对象。

当root != null , attachToRoot = true
  • 通过代码块+注释查看执行流程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    .... 省略了代码 ....
    // 1、声明返回的View对象 result,并赋予初始值为 root 
    View result = root;

    // 2、createViewFromTag(root, name, inflaterContext, attrs)并不是本文重点,只需要关心这个temp对象
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    /*
     *下面的代码进行分段,每一段有每一段的逻辑处理
     */
     
    // 当root != null , attachToRoot = true 会执行第一段,设置params,此时最外层的宽高设置生效
    // 第一段:设置temp的params,设置之后temp的最外层宽高才会生效
    ViewGroup.LayoutParams params = null;
    if (root != null) {
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }
    .... 省略了代码 ....

    // 当root != null , attachToRoot = true会执行第二段,当前temp会被add到root上
    // 第二段:是否需要把temp添加到root上
    // root = null,所以此段代码不执行
    // We are supposed to attach all the views we found (int temp)
    // to root. Do that now.
    if (root != null && attachToRoot) {
        root.addView(temp, params);
    }

    // 当root != null , attachToRoot = true不会执行第三段,result是root
    // 第三段:result是否需要重新赋值
    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
        result = temp;
    }
    
    .... 省略了代码 ....
    // 返回result对象
    return result;
}
  • 分析结果

    能够看到,最后创建出来的View有设置params,所以被创建出来的布局,最外层设置的宽高是有效的,并最终返回了root对象。

总结root对View的影响

  • 当root为null时,被填充出来的 View对象最外层的宽高属性是无效的,
  • 当root不为null时,被填充出来的View对象最外层的宽高属性是有效的,

总结attachToRoot对View的影响

  • 当root为null时,被填充出来的View对象不会被添加到root上,且inflate()方法的返回值是被填充View本身,
  • 当root不为null且attachToRoot为true时,被填充出来的View对象会被添加到root上,且inflate()方法的返回值是root,
  • 当root不为null且attachToRoot为false时,被填充出来的View对象不会被添加到root上,且inflate()方法的返回值是被填充View本身。