LayoutInflater机制原理

通过实例引出问题

在开始之前我们先来做一个测试,我们平时最常见的就是ListView的Adapter中使用LayoutInflater加载xml的item布局文件,所以咱们就以ListView为例,如下:

省略掉Activity代码等,首先给出Activity的布局文件,如下:



    


给出两种不同的ListView的item布局文件。

textview_layout.xml文件:



textview_layout_parent.xml文件:




    


ListView的自定义Adapter文件:

public class InflateAdapter extends BaseAdapter {
    private LayoutInflater mInflater = null;

    public InflateAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //说明:这里是测试inflate方法参数代码,不再考虑性能优化等TAG处理
        return getXmlToView(convertView, position, parent);
    }

    private View getXmlToView(View convertView, int position, ViewGroup parent) {
        View[] viewList = {
                mInflater.inflate(R.layout.textview_layout, null),
//                mInflater.inflate(R.layout.textview_layout, parent),
                mInflater.inflate(R.layout.textview_layout, parent, false),
//                mInflater.inflate(R.layout.textview_layout, parent, true),
                mInflater.inflate(R.layout.textview_layout, null, true),
                mInflater.inflate(R.layout.textview_layout, null, false),

                mInflater.inflate(R.layout.textview_layout_parent, null),
//                mInflater.inflate(R.layout.textview_layout_parent, parent),
                mInflater.inflate(R.layout.textview_layout_parent, parent, false),
//                mInflater.inflate(R.layout.textview_layout_parent, parent, true),
                mInflater.inflate(R.layout.textview_layout_parent, null, true),
                mInflater.inflate(R.layout.textview_layout_parent, null, false),
        };

        convertView = viewList[position];

        return convertView;
    }
}

当前代码运行结果:


layoutinflater_listview_show_result.png

PS:当打开上面viewList数组中任意一行注释都会抛出异常(java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView)。

从LayoutInflater源码实例化说起

我们先看一下源码中LayoutInflater实例化获取的方法:

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

看见没有?是否很熟悉?我们平时写应用获取LayoutInflater实例时不也就两种写法吗,如下:

LayoutInflater lif = LayoutInflater.from(Context context);

LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

可以看见from方法仅仅是对getSystemService的一个安全封装而已。

LayoutInflater源码的View inflate(…)方法族剖析

得到LayoutInflater对象之后我们就是传递xml然后解析得到View,如下方法:

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

继续看inflate(int resource, ViewGroup root, boolean attachToRoot)方法,如下:

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
      final Resources res = getContext().getResources();
      if (DEBUG) {
          Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
      }

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

会发现无论哪个inflate重载方法最后都调运了inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法;

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
      synchronized (mConstructorArgs) {
          Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

          final AttributeSet attrs = Xml.asAttributeSet(parser);
          Context lastContext = (Context)mConstructorArgs[0];
          mConstructorArgs[0] = mContext;
          //定义返回值,初始化为传入的形参root
          View result = root;

          try {
              // Look for the root node.
              int type;
              while ((type = parser.next()) != XmlPullParser.START_TAG &&
                      type != XmlPullParser.END_DOCUMENT) {
                  // Empty
              }
              //如果一开始就是END_DOCUMENT,那说明xml文件有问题
              if (type != XmlPullParser.START_TAG) {
                  throw new InflateException(parser.getPositionDescription()
                          + ": No start tag found!");
              }
              //有了上面判断说明这里type一定是START_TAG,也就是xml文件里的root node
              final String name = parser.getName();

              if (DEBUG) {
                  System.out.println("**************************");
                  System.out.println("Creating root view: "
                          + name);
                  System.out.println("**************************");
              }

              if (TAG_MERGE.equals(name)) {
              //处理merge tag的情况(merge,你懂的,APP的xml性能优化)
                  //root必须非空且attachToRoot为true,否则抛异常结束(APP使用merge时要注意的地方,
                  //因为merge的xml并不代表某个具体的view,只是将它包起来的其他xml的内容加到某个上层
                  //ViewGroup中。)
                  if (root == null || !attachToRoot) {
                      throw new InflateException(" can be used only with a valid "
                              + "ViewGroup root and attachToRoot=true");
                  }
                  //递归inflate方法调运
                  rInflate(parser, root, attrs, false, false);
              } else {
                  // Temp is the root view that was found in the xml
                  //xml文件中的root view,根据tag节点创建view对象
                  final View temp = createViewFromTag(root, name, attrs, false);

                  ViewGroup.LayoutParams params = null;

                  if (root != null) {
                      if (DEBUG) {
                          System.out.println("Creating params from root: " +
                                  root);
                      }
                      // Create layout params that match root, if supplied
                      //根据root生成合适的LayoutParams实例
                      params = root.generateLayoutParams(attrs);
                      if (!attachToRoot) {
                          // Set the layout params for temp if we are not
                          // attaching. (If we are, we use addView, below)
                          //如果attachToRoot=false就调用view的setLayoutParams方法
                          temp.setLayoutParams(params);
                      }
                  }

                  if (DEBUG) {
                      System.out.println("-----> start inflating children");
                  }
                  // Inflate all children under temp
                  //递归inflate剩下的children
                  rInflate(parser, temp, attrs, true, true);
                  if (DEBUG) {
                      System.out.println("-----> done inflating children");
                  }

                  // We are supposed to attach all the views we found (int temp)
                  // to root. Do that now.
                  if (root != null && attachToRoot) {
                      //root非空且attachToRoot=true则将xml文件的root view加到形参提供的root里
                      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) {
                      //返回xml里解析的root view
                      result = temp;
                  }
              }

          } catch (XmlPullParserException e) {
              InflateException ex = new InflateException(e.getMessage());
              ex.initCause(e);
              throw ex;
          } catch (IOException e) {
              InflateException ex = new InflateException(
                      parser.getPositionDescription()
                      + ": " + e.getMessage());
              ex.initCause(e);
              throw ex;
          } finally {
              // Don't retain static reference on context.
              mConstructorArgs[0] = lastContext;
              mConstructorArgs[1] = null;
          }

          Trace.traceEnd(Trace.TRACE_TAG_VIEW);
          //返回参数root或xml文件里的root view
          return result;
      }
  }

从上面的源码分析我们可以看出inflate方法的参数含义:

  • inflate(xmlId, null); 只创建temp的View,然后直接返回temp。
  • inflate(xmlId, parent); 创建temp的View,然后执行root.addView(temp, params);最后返回root。
  • inflate(xmlId, parent, true); 创建temp的View,然后执行root.addView(temp, params);最后返回root。
  • inflate(xmlId, parent, false); 创建temp的View,然后执行temp.setLayoutParams(params);然后再返回temp。
  • inflate(xmlId, null, false); 只创建temp的View,然后直接返回temp。
  • inflate(xmlId, null, true); 只创建temp的View,然后直接返回temp。

在此先强调一个Android的概念

我们经常使用View的layout_width和layout_height来设置View的大小,而且一般都可以正常工作,所以有人时常认为这两个属性就是设置View的真实大小一样;然而实际上这些属性是用于设置View在ViewGroup布局中的大小的;这就是为什么Google的工程师在变量命名上将这种属性叫作layout_width和layout_height,而不是width和height的原因了。

到此其实已经可以说明我们上面示例部分执行效果差异的原因了

详细解释如下:

  • mInflater.inflate(R.layout.textview_layout, null)不能正确处理我们设置的宽和高是因为layout_width,layout_height是相对了父级设置的,而此temp的getLayoutParams为null。
  • mInflater.inflate(R.layout.textview_layout, parent)能正确显示我们设置的宽高是因为我们的View在设置setLayoutParams时params = root.generateLayoutParams(attrs)不为空。
    Inflate(resId , parent,false ) 可以正确处理,因为temp.setLayoutParams(params);这个params正是root.generateLayoutParams(attrs);得到的。
  • mInflater.inflate(R.layout.textview_layout, null, true)与mInflater.inflate(R.layout.textview_layout, null, false)不能正确处理我们设置的宽和高是因为layout_width,layout_height是相对了父级设置的,而此temp的getLayoutParams为null。
  • textview_layout_parent.xml作为item可以正确显示的原因是因为TextView具备上级ViewGroup,上级ViewGroup的layout_width,layout_height会失效,当前的TextView会有效而已。

上面例子中说放开那些注释运行会报错java.lang.UnsupportedOperationException:
addView(View, LayoutParams) is not supported

  • 因为AdapterView源码中调用了root.addView(temp, params);而此时的root是我们的ListView,ListView为AdapterView的子类,所以我们看下AdapterView抽象类中addView源码即可明白为啥了,如下:
/**
    * This method is not supported and throws an UnsupportedOperationException when called.
    *
    * @param child Ignored.
    *
    * @throws UnsupportedOperationException Every time this method is invoked.
    */
   @Override
   public void addView(View child) {
       throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
   }

我们在写App时Activity中指定布局文件的时候,xml布局文件或者我们用java编写的View最外层的那个布局是可以指定大小的啊?他们最外层的layout_width和layout_height都是有作用的啊?

是这样的,还记得我们上面的分析吗?我们自己的xml布局通过setContentView()方法放置到哪去了呢?记不记得id为content的FrameLayout呢?所以我们xml或者java的View的最外层布局的layout_width和layout_height属性才会有效果,就是这么回事而已。

总结

  • View的layout_width和layout_height来设置View在ViewGroup布局中的大小的;这就是为什么Google的工程师在变量命名上将这种属性叫作layout_width和layout_height,而不是width和height的原因了。

  • LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 这是最底层的获取方法

  • inflate重载方法最后都调运了inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法

  • inflate 方法参数含义

  • inflate(xmlId, null); 只创建temp的View,然后直接返回temp。

  • inflate(xmlId, parent); 创建temp的View,然后执行root.addView(temp, params);最后返回root。

  • inflate(xmlId, parent, true); 创建temp的View,然后执行root.addView(temp, params);最后返回root。

  • inflate(xmlId, parent, false); 创建temp的View,然后执行temp.setLayoutParams(params);然后再返回temp。

  • inflate(xmlId, null, false); 只创建temp的View,然后直接返回temp。

  • inflate(xmlId, null, true); 只创建temp的View,然后直接返回temp。

  • mInflater.inflate(R.layout.textview_layout, parent) 程序奔溃,因为root是我们的ListView,ListView为AdapterView的子类,AdapterView的addView抛出了异常,不支持addView;

  • activity的布局支持设置大小因为最外面有个content的framelayout

你可能感兴趣的:(LayoutInflater机制原理)