View绘制体系(二)——View的inflate详解

View绘制体系(二)——View的inflate详解


前言

上一篇博客讲到setContentView最后会调用mLayoutInflater.inflate来创建了自定义xml中的布局视图,添加到mContentParent中,这里我们就来学习下inflate的具体实现以及它的基本使用方法。

inflate的基本使用

首先我们需要明确的是,inflate方法是讲xml文件反射成一个View,但是并不执行View的绘制。

inflate常用的重载有两种方式:

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

所以我们只需要知道第二种重载的参数意义就OK了:

  • resource:需要反射的xml文件的资源id
  • root:关于root我们需要注意的是,它并不代表将创建好的布局加入到root中,而是表示将root作为父容器来创建指定的View

将root作为父容器创建View,其含义是让root协助View的根节点生成布局参数,如果没有父容器的话,View的根节点的宽高属性(match_parent,wrap_parent等)将没任何的意义,即不会产生任何的效果

  • attachToRoot:是否将创建好的View添加到root

通常我们使用inflate有以下三种方式:

  • LayoutInflater.inflate:在获取到LayoutInflater实例后,可以通过如下代码加载布局:
 LayoutInflater inflater = LayoutInflater.from(this);
 View view1 = inflater.inflate(R.layout.view1, null);
 View view2 = inflater.inflate(R.layout.view2, null, false);
  • View.inflate:只有一种形式如下:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}

其实质是通过LayoutInflater来创建View的

  • Context.getSystemService:通过Context.getSystemService来获得LayoutInflater,实质也是通过LayoutInflater来创建View
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view1 = inflater.inflate(R.layout.view1, null);

inflate源码解析

现在我们就来详细的分析下inflate的整个流程,源码如下:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    //省略debug相关代码
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

上面代码中关键的地方在于res.getLayout(resource)返回了一个XmlResourceParserXmlResourceParser是继承了XmlPullParserAttributeSet两个接口的接口,其实现类是Parser
(关于AttributeSet和XmlPullParser的内容详见View绘制体系(三)——AttributeSet与TypedArray详解)

Resources类中提供了getLayoutgetXmlgetAnim等方法解析对应资源id的xml文件,返回对应的解析器Parser。

接下来我们来看下inflate(parser, root, attachToRoot)的源码:

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 {
            // Look for the root node.
            int type;
            // 找到第一个tag
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

			// 找不到tag,抛出异常
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            
            final String name = parser.getName();

			// merge标签的处理
            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) {
                    //如果root不为空,获取它的布局参数
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                    	//如果不添加到root中,则调用setLayoutParams给temp设置布局参数
                    	//否则,使用addView(View, LayoutParmas)来设置布局参数(在下面)
                        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) {
            //省略异常捕获的代码
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        return result;
    }
}

上述代码,首先是attrs = Xml.asAttributeSet(parser),这是Xml类提供的根据XmlPullParser生成对应的AttributeSet的方法。

然后就是一些xml解析的判断与循环,并且获取根节点即xml文件第一个tag位的属性:

  • 如果为merge标签,那么必须将该view放入一个父容器之中,即root不为空且attachToRoot为true。然后调用rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)方法,它会递归地遍历xml的层次结构并创建对应的View,创建完后根据传入的最后一个参数finishInflate决定是否在创建完成后调用parent.onFinishInflate方法。
    创建好之后直接返回root

  • 如果不为merge标签,那么调用createViewFromTag创建根节点的视图temp,如果root不为空,且attachToRoot为false,那么就将root的LayoutParams传给temp。接下来就是调用rInflateChildren以temp为根节点递归地去创建视图。
    创建好了之后,如果attachToRoot为true,则调用addView将创建好的temp添加到root中,返回root,否则不添加,直接返回temp

rInflate与rInflateChildren源码解析

inflate中会调用rInflaterInflateChildren来递归的创建视图,我们来看下他们的源码:

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    //获取当前元素在View树中的深度,根节点深度为1
    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;

    //1.如果没有遇到结束标志(/>),则继续循环下去
    //2.如果遇到结束标志,但没有到文档末尾,且当前深度大于起始节点深度,则继续循环下去
    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();
        //根据name设置对应属性
        if (TAG_REQUEST_FOCUS.equals(name)) {
            //如果遇到requestFocus标签,那么使当前元素获得焦点
            pendingRequestFocus = true;
            //消费掉requestFocus标签下的所有子标签
            //该方法内部是一个空循环
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            //处理tag标签
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            //处理include标签
            if (parser.getDepth() == 0) {
                throw new InflateException(" cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            //如果遇见merge标签则抛出异常
            throw new InflateException(" must be the root element");
        } else {
            //创建对应的View
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            //递归创建view下面的所有子view
            rInflateChildren(parser, view, attrs, true);
            //将创建好的View添加到parent中
            viewGroup.addView(view, params);
        }
    }

    if (pendingRequestFocus) {
        //给包含requestFocus标签的父元素设置焦点
        parent.restoreDefaultFocus();
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

从上述源码可以看出,rInflateChildren实质就是调用了rInflate方法,个人认为区别可能在于处理merge标签是传入的Context参数,并且rInflateChildren只用于创建parent下面的子view,不用来创建根节点view。

rInflate方法的主要就是一个循环,判断是否已经处理完parent view下面的所有子View(通过深度和eventType来判断的),如果没有,则根据获取的标签类型依次处理。在处理一般标签(非requestFocus,merge,include,tag)标签时,通过createViewFromTag来创建view,并添加到parent上

以上就是LayoutInflater的inflate创建View机制的解析了,后续会继续介绍View绘制的相关知识,敬请关注!

你可能感兴趣的:(Android,View绘制)