王志超-Android筑基——深入理解 LayoutInflater

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

  • ViewGroup root 参数的作用是什么,为什么有时候可以传 null,有时候却不可以?
  • boolean attachToRoot 参数什么时候传 true,什么时候传 false?为什么有时候传递 true 会崩溃?
  • 为什么有的时候 xml 中根节点设置的布局参数却不生效?

这篇文章主要是说明 inflate() 方法参数的含义,以及在具体场景的使用。

2. 正文

2.1 inflate() 方法分析

LayoutInflater 类中,有几个重载的 inflate() 方法:

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

大家在实际开发中,使用比较多的应该是前两个。

它们的调用关系(箭头指向表示调用方向)如下:

从图中可以看出前三个最终调用的都是最后一个:

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

那么现在,我们集中精力去看最后一个方法。把最后一个方法搞清楚就可以了,就可以搞懂另外三个了。

这个方法中的第一个参数 XmlPullParser parser,查看源码,可以看到:

final Resources res = getContext().getResources();
XmlResourceParser parser = res.getLayout(resource);

是由 xml 转换而来的,用来对 xml 进行解析的一个类。

好了,我们已经了解了第一个参数的含义,就是传递要转换的 xml 布局过来。

接着看后面的两个参数:@Nullable ViewGroup rootboolean attachToRoot。需要注意的是 ViewGroup root 前面有一个注解 @Nullable,表示 ViewGroup root 这个参数可以为 null

这两个参数的取值组合有几种呢?4 种。
| 取值组合 | ViewGroup root | boolean attachToRoot|
|–|–|–|–|
| 第一组| notNull | false |
| 第二组| notNull | true |
| 第三组| null | false |
| 第四组| null | true |

不同的取值组合,对于最后的返回值 View 有什么影响呢?

到这里,我们需要去查看一下 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 方法的源码:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
View result = root;
advanceToRootNode(parser);
// 获取根节点的名字,比如 LinearLayout, FrameLayout 等。
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
// 根节点的名字是 merge
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "

  • “ViewGroup root and attachToRoot=true”);
    }
    rInflate(parser, root, inflaterContext, attrs, false);
    } else {
    // Temp is the root view that was found in the xml
    // 获取 xml 布局的根 View 对象,比如 LinearLayout 对象,FrameLayout 对象等。
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    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);
    }
    }
    // Inflate all children under temp against its context.
    rInflateChildren(parser, temp, attrs, true);
    // 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);
    }
    // Decide whether to return the root that was passed in or the
    // top view found in xml.
    if (root == null || !attachToRoot) {
    result = temp;
    }
    }
    return result;
    }
    }

我们先不考虑根节点为 merge 的情况,因为这是比较特殊的根节点。先按照一般的情况来分析,有助于解决普遍的问题。

2.1.1 根节点不是 merge 时,第一组取值情况分析

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot
第一组notNullfalse
在第 5 行 View result = root;root 的值赋值给 View result,那么有 result 的值是 notNull

在第 21 行 if (root != null) 的判断语句判断为 true,不能进入 if 语句。

在第 23 行 params = root.generateLayoutParams(attrs);,通过 root 来获取根节点的布局参数 ViewGroup.LayoutParams 对象,也就是说,把 xml 中的根节点的 layout_ 开头的属性,如layout_widthlayout_height 对应的值转为布局参数对象中的字段值,如widthheight 值。对应的源码在 ViewGroup 中如下:

public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}

这个方法被 ViewGroup 的子类重写后,会解析 xml 中更多的布局参数,例如在 LinearLayout 中重写后,还会解析 layout_weightlayout_gravity 参数。

在第 24 行 if (!attachToRoot) 判断,因为这里的 attachToRoot 取值为 false,所以判断为 true,进入 if 分支,到达第 25 行 temp.setLayoutParams(params);,把布局参数设置给了根节点控件对象。

在第 34 行 if (root != null && attachToRoot) 判断,由于 attachToRootfalse,所以判断为 false,不会进入 if 语句,也就是说不会把根节点控件对象以及布局参数设置给 root

在第 39 行 if (root == null || !attachToRoot) 判断,由于 attachToRootfalse,所以判断为 true,进入 if 语句,到达第 40 行 result = temp;,也就是把根节点控件对象赋值给了 result 变量。

在第 43 行,return result; ,返回的就是根节点对象。

总结一下:

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot返回值
第一组notNullfalse返回的是 xml 布局的根节点 View 对象,并且对象上拥有根节点上的布局参数。

2.1.2 根节点不是 merge 时,第二组取值情况分析

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot
第二组notNulltrue

我们直接从第 24 行开始,因为之前的代码流程和第一组取值情况是一模一样的。

在第 24 行,if (!attachToRoot) 判断,由于 attachToRoot 的取值为 true,所以判断为 false,不会进入 if 分支,也就是说不会把布局参数设置给了根节点控件对象。

在第 34 行 if (root != null && attachToRoot) 判断,由于 root 不为 null 并且 attachToRoottrue,所以判断为 true,会进入 if 语句,第 35 行:root.addView(temp, params);,也就是说会把根节点控件对象以及布局参数设置给 root

在第 39 行 if (root == null || !attachToRoot) 判断,因为 root 不为 nullattachToRoot 不为 false,所以判断为 false,不会进入此分支。

在第 43 行 return result;result 是在第 5 行被赋值为 root,没有被重新赋值,所以返回的是 root

小结一下:

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot返回值
第二组notNulltrue返回的是添加了根节点 View 对象以及布局参数的 root 对象

2.1.3 根节点不是 merge 时,第三组取值情况分析

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot
第三组nullfalse

在第 5 行 View result = root;root 的值赋值给 View result,那么有 result 的值是 null

在第 21 行 if (root != null) 判断,因为 rootnull,所以判断为 false,不会进入 if 分支,也就是说 ViewGroup.LayoutParams params 的值仍然是 null,没有发生变化。

在第 34 行 if (root != null && attachToRoot) 判断,因为 rootnull,所以判断为 false,不会进入 if 分支,也就是说,不会把根节点控件对象以及布局参数设置给 root

在第 39 行 if (root == null || !attachToRoot) 判断,因为 rootnull,所以判断为 true,进入 if 分支,到达第 40 行,result = temp;,把根节点控件对象 temp 赋值给了 View result 变量。

在第 43 行 return result;,返回的是谁呢?返回的是没有布局参数的根节点控件对象。

小结一下:

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot返回值
第三组nullfalse返回的是没有布局参数信息的根节点 View 对象

2.1.4 根节点不是 merge 时,第四组取值情况分析

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot
第四组nulltrue

我们直接从第 34 行开始,因为之前的代码流程和第三组是一模一样的。

在第 34 行,if (root != null && attachToRoot) 判断,因为 rootnull,所以判断为 false,不会进入 if 分支,也就是说,不会把根节点控件对象以及布局参数设置给 root

在第 39 行 if (root == null || !attachToRoot) 判断,因为 rootnull,所以判断为 true,进入 if 分支,到达第 40 行,result = temp;,把根节点控件对象 temp 赋值给了 View result 变量。

在第 43 行 return result;,返回的是谁呢?返回的是没有布局参数的根节点控件对象。

第四组取值情况和第三组的返回值是一样的。

2.1.5 根节点为 merge 时情况分析

在第 9 行 if (TAG_MERGE.equals(name)) 判断,是 merge 根节点,进入 if 分支;

在第 11 行 if (root == null || !attachToRoot) 判断,若 rootnull,或者 attachToRootfalse,判断都会成立,进入 if 语句后抛出异常。

throw new InflateException(" can be used only with a valid "

  • “ViewGroup root and attachToRoot=true”);

这就是提醒我们,当根节点是 merge 时,root 必须不为 null 而且 attachToRoot 必须为 true

在第 43 行 return result;,而 result 在第 5 行 View result = root; 被赋值为 root

总结一下取值情况:

根节点是否是 merge取值组合ViewGroup rootboolean attachToRoot返回值
第一组notNullfalse返回的是 xml 布局的根节点 View 对象,并且对象上拥有根节点上的布局参数。
第二组notNulltrue返回的是添加了根节点 View 对象以及布局参数的 root 对象。
第三组nullfalse返回的是没有布局参数信息的根节点 View 对象。
第四组nulltrue返回的是没有布局参数信息的根节点 View 对象。
notNull(必须)true(必须)返回的是 root 对象。

2.2 实际应用

2.2.1 自定义控件填充布局

需要填充的布局 custom_view_layout.xml如下:

<?xml version="1.0" encoding="utf-8"?>



CustomView 类如下:

public class CustomView extends LinearLayout {
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.custom_view_layout, this);
}
}

这里的 inflate() 方法是 View 类的静态方法:

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}

内部调用的是 LayoutInflater 的第一个 inflate() 方法:

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

ViewGroup root 不为 nullboolean attachToRoottrue,根节点不是 merge 标签,所以对应的是表格里的第二组情况,返回的是添加了根节点 View 对象以及布局参数的 root 对象,也就是说根节点 View 对象已经添加进入了 root 对象里面。

这里,我们使用 Android Studio 的 Layout Inspector 工具(在 Tools -> Layout Inspector 开启)来查看一下布局:
在这里插入图片描述
可以看到出现了重复布局。我们知道,merge 标签可以用于优化重复布局。

现在我们修改布局文件为 custom_merge_view_layout.xml

<?xml version="1.0" encoding="utf-8"?>



代码中填充修改后的布局:

public class CustomMergeView extends LinearLayout {
public CustomMergeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.custom_merge_view_layout, this);
}
}

再次使用布局查看器查看布局:
在这里插入图片描述
可以看到使用 merge 标签消除了重复布局。

2.2.2 Fragment 填充布局

新建一个 FragmentInflateActivity.java 文件:

public class FragmentInflateActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_inflate_activity);
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_container, MyFragment.newInstance())
.commit();
}
public static void start(Context context) {
Intent starter = new Intent(context, FragmentInflateActivity.class);
context.startActivity(starter);
}
}

对应的 fragment_inflate_activity.xml

<?xml version="1.0" encoding="utf-8"?>

MyFragment.java 如下:

public class MyFragment extends Fragment {
private static final String TAG = “MyFragment”;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

image

image

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

“https://i-blog.csdnimg.cn/blog_migrate/2ccc2b71fa4d116de5b1974f107168df.jpeg” />

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

[外链图片转存中…(img-kyW1JI9s-1713686254758)]

[外链图片转存中…(img-0ewJIrYE-1713686254759)]

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值