当前位置: 代码迷 >> 综合 >> Android之 inflate() 方法总结
  详细解决方案

Android之 inflate() 方法总结

热度:66   发布时间:2024-01-31 20:20:48.0

文章目录

    • 一、引言
    • 二、源码分析
      • 2.1 源码跟踪
      • 2.2 结论

一、引言

inflater.inflate(R.layout.layout_inflate_test,null);
inflater.inflate(R.layout.layout_inflate_test, root,false);
inflater.inflate(R.layout.layout_inflate_test, root,true);

Android这么久,经常会看到上面三个方法,只知道这是通过布局资源id解析xml文件并返回View用的,但具体什么时候该用哪种参数的方法,还是懵懵懂懂。

以下从源码角度分析一下inflate()方法。

二、源码分析

2.1 源码跟踪

先看一下inflate()涉及的相关源码(以Android API 29为例):

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);
}public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();//...View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}
}public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];mConstructorArgs[0] = inflaterContext;View result = root;try {advanceToRootNode(parser);final String name = parser.getName();//...if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> 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 xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {//...// Create layout params that match root, if suppliedparams = 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;}}} catch (XmlPullParserException e) {final InflateException ie = new InflateException(e.getMessage(), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(getParserStateDescription(inflaterContext, attrs)+ ": " + e.getMessage(), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} finally {// Don't retain static reference on context.mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;Trace.traceEnd(Trace.TRACE_TAG_VIEW);}return result;}
}

以上虽然有四个重载方法,但仔细观察,发现万剑归宗,最后的调用都指向一个方法:inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot),我们只需要跟进这个方法便可。
在这里插入图片描述

从上图可以看出以上四个方法总体上可以分为两类:第一类的第一个参数为布局资源id;第二类第一个参数为xml文件解析器XmlPullParser
第一类调用第二类的关键代码如下:

XmlResourceParser parser = res.getLayout(resource);
try {return inflate(parser, root, attachToRoot);
} finally {parser.close();
}

跟进inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法:

final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;

这段代码主要通过XmlPuulPaser获取xml的属性集,以及初始化一些数据,把root参数(可能为null)赋值给返回结果。继续分析:

advanceToRootNode(parser); 
// ↓(实现)
private void advanceToRootNode(XmlPullParser parser)throws InflateException, IOException, XmlPullParserException {// Look for the root node.int type;while ((type = parser.next()) != XmlPullParser.START_TAG &&type != XmlPullParser.END_DOCUMENT) {// Empty}if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}
}

这里的代码含义是遍历寻找根节点,继续分析:

final String name = parser.getName();
if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, inflaterContext, attrs, false);
} 

当根节点的标签是merge时,如果rootnull或者attachToRootfalse会直接抛异常,也就是当根标签为merge的时候必须使用inflater.inflate(R.layout.layout_inflate_test, root,true);这种形式,不然会报错,你可以自己试验一下。实验结果:
在这里插入图片描述

然后看rInflate(parser, root, inflaterContext, attrs, false);方法的实现:

void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth();int type;boolean pendingRequestFocus = false;while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}final String name = parser.getName();if (TAG_REQUEST_FOCUS.equals(name)) {pendingRequestFocus = true;consumeChildElements(parser);} else if (TAG_TAG.equals(name)) {parseViewTag(parser, parent, attrs);} else if (TAG_INCLUDE.equals(name)) {if (parser.getDepth() == 0) {throw new InflateException("<include /> cannot be the root element");}parseInclude(parser, context, parent, attrs);} else if (TAG_MERGE.equals(name)) {throw new InflateException("<merge /> must be the root element");} else {final View view = createViewFromTag(parent, name, context, attrs);final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflateChildren(parser, view, attrs, true);viewGroup.addView(view, params);}}if (pendingRequestFocus) {parent.restoreDefaultFocus();}if (finishInflate) {//个人理解:这里的应该是控制递归调用结束的一个非常关键的因素(即非`ViewGroup`类型的view不会再被递归)parent.onFinishInflate();}
}

该方法递归遍历xml文件,根据标签Tag的名称通过createViewFromTag(parent, name, context, attrs);方法并利用反射创建子View,并添加到ViewGroup parent上。然后继续分析:

else {// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {//...// Create layout params that match root, if suppliedparams = 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;}
}

之前的merge是特殊情况,常见的是else中的情况,首先会通过createViewFromTag(root, name, inflaterContext, attrs);方法生成一个叫temproot view,下面的代码是关键:

if (root != null) {//...// Create layout params that match root, if suppliedparams = 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的话,根据root创建相应的LayoutParams params;如果attachToRootfalse的话,就把这个布局参数设置给temp,再继续分析:

// 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;
}

首先是递归解析子布局并生成相应的View,如果root != null && attachToRoot的话,就把temproot的布局参数params添加到root布局中;如果root == null 或者 attachToRootfalse,则忽略root的布局参数信息,直接将temp赋值给result并最终返回该结果。

2.2 结论

引用郭神的结论:

resourceId Root attachToRoot 效果
存在 null 设置无意义 attachToRoot失去意义
存在 !=null true 会给加载的布局文件指定一个父布局root
存在 !=null false 则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效
存在 !=null 未设置(true) 会给加载的布局文件指定一个父布局root

补充说明:

  1. 如果root不为null,布局文件最外层的layout关于LayoutParams设置的属性和其他属性都会被保留下来,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,我们不需要自己再addView,否则会报错;
  2. attachToRoot设为false,需要我们自己addViewrootnull时,被加载的布局LayoutParams的属性会被改变,但是其它属性例如背景颜色什么的会被保留。

本文参考:Android inflate方法总结

  相关解决方案