LayoutInflater源码分析

本文详细分析了Android文档中关于LayoutInflater类的使用,重点介绍了如何将XML文件转换为View树,并通过源码解析了Activity内部setContentView方法及自加载View的流程。深入探讨了(LayoutInflater inflate(int resource, ViewGroup root, boolean attachToRoot)方法的实现,包括如何处理Merge节点、BlinkLayout节点,以及如何递归加载XML布局中的子View。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 介绍
这里写图片描述
这是Android文档对于LayoutInflater这个类的说明,这个类主要功能是把一个xml文件转换为有一定层次关系的View树。 LayoutInflater类的作用十分大,整体上说是由它解析xml并构建了整个DecorView的View层次结构,同时由于这个类对于Factory的设计,促成了我们比较常用的Fragment。本篇主要是对LayoutInflater类加载View的过程进行分析。

2. 源码分析
LayoutInflater类主要的使用场景是Activity内部setContentView函数和我们自己加载一个View的时候。

  • Activity使用场景

    首先先看下Activity内部的使用流程,

 public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initActionBar();
    }

Activity的setContentView方法内部会调用Window的setContentView,手机上默认是PhoneWindow,再看下PhoneWindow的setContentView内部的逻辑

 @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

其中第3-7行:主要是创建一个DecorView,然后根据主题的配置加载不同的xml并添加到这个DecorView上面,这些xml有一个共同的特征是内部都包含一个id=content的FrameLayout,我们在IDE编辑的xml布局其实就是添加到这个FrameLayout上面,本篇主要是介绍LayoutInflater类的功能,此处的具体细节不作过多描述!
第8行:调用LayoutInflater类的inflate方法将我们在IDE中编辑的xml布局添加到上面介绍的ID=content的这个FrameLayout上面

  • 自主加载View

加载布局的时候通常会调用两个方法,一个是直接使用LayoutInflater类的
inflate(int resource, ViewGroup root, boolean attachToRoot)方法

另外一个会使用View类的inflate方法

 public static View inflate(Context context, int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }
  • LayoutInflater类inflate方法分析

上面的Activity类的setContentView调用还是自己去加载View,最后都是调用到了LayoutInflater类的inflate方法,LayoutInflater内部重载了好几种参数类型的inflate方法,最后都是调用到这个函数

 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;
            View result = root;

            try {
                // 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 (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                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, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp;
                    if (TAG_1995.equals(name)) {
                        temp = new BlinkLayout(mContext, attrs);
                    } else {
                        temp = createViewFromTag(root, name, attrs);
                    }

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

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                    // Inflate all children under temp
                    rInflate(parser, temp, attrs, 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.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) {
                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;
            }

            return result;
        }
    }

其中30-36行:处理Merge节点逻辑,由于我们在IDE编辑的xml布局最终会添加到id=Content的FrameLayout上面,所以当编辑的xml根布局为FrameLayout的时候就可以使用merge进行布局优化,在这里36行直接以root为参数调用rInflate(parser, root, attrs, false)方法进行递归添加子View,这个方法在后面会进行详细分析。

其中39-44行:其中40行判断当前是否是blink节点,这个节点和其对应的BlinkLayout在Sdk中并没有开放,真心是不知道干嘛的,此处略过。其中43行会解析我们在IDE编辑的xml根节点并通过createViewFromTag方法通过反射生成一个View类的temp实例。

其中48-60行:假如参数attachToRoot为false,会把root生成的LayoutParam设置到temp实例上。

其中66行:还是以temp为参数调用rInflate(parser, temp, attrs, true)方法递归生成View

其中73-75行:根据root是否为null和attachToRoot来判断是否将加载的View树添加root上面

这样View树加载流程大体上就介绍完了,再看下其他几个在加载过程中使用的方法。

 View createViewFromTag(View parent, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

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

        try {
            View view;
            if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
            else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
            else view = null;

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
            }

            if (view == null) {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            }

            if (DEBUG) System.out.println("Created view is: " + view);
            return view;

        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;

        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
    }

该函数的具体功能是通过反射指定节点生成一个View实例
第2-4行:处理的是普通View节点
第9-16行:通过Factory的相关接口大大拓展了布局xml节点的范围,不仅可以加载View还可以加载Fragment等节点。
第18-24行:通过判断节点中 “.”分割符位置来判断是一个系统控件还是一个自定义控件,系统控件我们只会编写名称,系统会给我们自动添加”android.view.”前缀,具体细节在onCreateView方法内定义

 void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        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)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else if (TAG_1995.equals(name)) {
                final View view = new BlinkLayout(mContext, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs, true);
                viewGroup.addView(view, params);                
            } else {
                final View view = createViewFromTag(parent, name, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) parent.onFinishInflate();
    }

该方法的主要作用是递归遍历xml全部节点已经View 的过程;
其中17行:处理request-focus节点
其中19-22行:处理include节点的过程,这个节点平时开发用的比较多
其中24行:处理merge节点。这里抛出了一个异常,可以看出只有根节点才可以使用merge
其中26-30行:又是关于对BlinkLayout的处理
其中32-36行:这个是普通情况了,先生成该节点的实例,然后将该实例作为parent参与递归调用!

3. 总结
通过上面的分析了LayoutInflater解析xml节点生成View树的过程,主要还是attachToRoot变量的使用
这里写图片描述

为true是加载的View树会被添加到root上面,为false的时候用来为xml根节点对应的View生成对应的LayoutParams!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值