Android之 inflate() 方法总结

文章目录

    • 一、引言
    • 二、源码分析
      • 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(" 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
        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;
        }
      }

    } 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),我们只需要跟进这个方法便可。
Android之 inflate() 方法总结_第1张图片

从上图可以看出以上四个方法总体上可以分为两类:第一类的第一个参数为布局资源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(" 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(" cannot be the root element");
      }
      parseInclude(parser, context, parent, attrs);
    } else if (TAG_MERGE.equals(name)) {
      throw new InflateException(" 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 xml
  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;
  }
}

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

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的话,根据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方法总结

你可能感兴趣的:(Android)