LayoutInflater源码解析

深入探讨LayoutInflater的工作原理,解析如何通过LayoutInflater从XML资源中加载视图,并详细分析inflate方法内部流程,包括如何处理不同类型的XML标签,以及如何创建和初始化View。

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

早些时候在自定义侧滑组件,遇到一个问题,就是添加布局文件的时候想获取布局文件自己定义的size来填充到新控件里去,试了一下午只能用LayoutInflater.from(getContext()).inflate(R.layout.resId, parent)把parent带上,但是这样很奇怪的问题是原来布局文件定义的透明度背景显示不了,只能显示纯色,但是parent=null获取到的View透明背景会在。所以最好的方式还是解决获取一个布局文件或者是一个ViewGroup的size问题。网上大多资料都是说自定义控件在构造函数里拿到AttributeSet获取对应的属性值,这个是可以,但是我的并不是自定义控件,而且我想用构建器模型。
期间留意到添加布局文件的方法LayoutInflater.from(getContext()).inflate(R.layout.resId, parent);很值得看一下 。

LayoutInflater

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) {
        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(parser, root, attachToRoot)是要解析XML Layout文件将对应的属性值解析出来然后更新LayoutParams的参数再将其attach到parent布局上。
继续之前先看一下解析类:XmlPullParser.java
next()和next***()都是返回下一条语句(next event)返回值和getEventType()一样当前parse的所在阶段状态。
常用的有五个状态:int START_DOCUMENT = 0;int END_DOCUMENT = 1;int START_TAG = 2;int END_TAG = 3;int TEXT = 4;
看下使用的例子:

XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
          factory.setNamespaceAware(true);
          XmlPullParser xpp = factory.newPullParser();

          xpp.<a href="#setInput">setInput</a>( new StringReader ( "&lt;foo>Hello World!&lt;/foo>" ) );
          int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
           if(eventType == XmlPullParser.START_DOCUMENT) {
               System.out.println("Start document");
           } else if(eventType == XmlPullParser.START_TAG) {
               System.out.println("Start tag "+xpp.<a href="#getName()">getName()</a>);
           } else if(eventType == XmlPullParser.END_TAG) {
               System.out.println("End tag "+xpp.getName());
           } else if(eventType == XmlPullParser.TEXT) {
               System.out.println("Text "+xpp.<a href="#getText()">getText()</a>);
           }
           eventType = xpp.next();
          }

输出结果为:

  • Start document
  • Start tag foo
  • Text Hello World!
  • End tag foo
  • End document

除了tag中间的text也可以获取name(tag-element的name)、depath(层数-tag的嵌套层数)、namespace(命名空间)等属性详见链接

/**
* 如果是merge标签的话直接遍历xml实例化子控件然后attach to root
* 如果不是merge就会创建一个“父布局”(parentView)根据attr创建LayoutParams(等着最后attach to root用),然后实例化他的child views(rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate)),将他们attach to parentView,最后attach to root。
**/
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;
                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("**************************");
                }
                //如果是merge页签
                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");
                    }
                    //TODO
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    //TODO
                    final View temp = createViewFromTag(root, name, inflaterContext, 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
                        //根据attr和parent来获取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)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    //TODO
                    rInflateChildren(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 (Exception 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);

            return result;
        }
    }

其中最接近我当时问题目标的是generateLayoutParams函数,根据parentVIew和自己的AttributeSet来获取对应的LayoutParams:

public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }
//拿到layout_width,layout_height的值创建一个ViewGroup.LayoutParams
public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }

而且都拿到AttributeSet了只要取到赋值就好了啊。这么我当时的问题就解决了,但是实例化View到底会进行到哪一步呢?想继续看看

有个很常用也很核心的函数是rInflate,包括rInflateChildren后面也是调用的rInflate方法

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

这是个递归方法用来往下遍历xml树和实例化views,实例他们的childrenViews,然后调用onFinishInflate()方法。

void rInflate(XmlPullParser parser, View parent, Context context,
            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)) {
                //TODO
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                //TODO
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                //TODO
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                //根据属性值和tag创建View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                //初始化LayoutParams
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //实例化子view
                rInflateChildren(parser, view, attrs, true);
                //parentView添加childView
                viewGroup.addView(view, params);
            }
        }

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

遍历XML文档遇到特定的标签执行特定的解析方法,其他情况执行他(ViewGroup) parent的generateLayoutParams(attrs)获取LayoutParams之后添加到parent。
其中有个关于创建View的方法createViewFromTag如下:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            //tag等于view的,有种引用控件的方法是<view class = "FrameLayout"
            //                                ...... />
            //现在用的人少
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        //如果需要该标签与主题相关,需要对context进行包装,将主题信息加入context包装类ContextWrapper
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }
        //BlinkLayout是一种闪烁的FrameLayout,它包裹的内容会一直闪烁,类似提示消息那种。
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            //如果定义了factory或者factory2或者privateFactory来构造View那就调用他们的对应的方法实例化View
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

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


            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                //如果name中包含.即为自定义View,否则为原生的View控件,他们最后都是调用public final View createView(String name, String prefix, AttributeSet attrs)方法
                    if (-1 == name.indexOf('.')) {
                //后面也是调用createView(String name, String prefix, AttributeSet attrs)
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

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

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

构建器先从缓存中取,缓存没有的话检查是否是View的子类,过滤掉不需要加载的类然后通过clazz.getConstructor来获取构建器,缓存有相关的构建器就直接newInstance(args)来创建对象返回。关于反射获取构建器来实例化对象请参考

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;

        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

构建器构建没啥疑问的,但是factory、factory2或者privateFactory可以用来创建View就比较有意思了。那如果想要定制自己createView方法的也是可以通过这个方法操作的。

 /**
     * Create a new LayoutInflater instance that is a copy of an existing
     * LayoutInflater, optionally with its Context changed.  For use in
     * implementing {@link #cloneInContext}.
     * 
     * @param original The original LayoutInflater to copy.
     * @param newContext The new Context to use.
     */
protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    }
public interface Factory {
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }

public void setFactory(Factory factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = factory;
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }
public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }
public void setPrivateFactory(Factory2 factory) {
        if (mPrivateFactory == null) {
            mPrivateFactory = factory;
        } else {
            mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
        }
    }

可以看到set方法是public的,但是变量mFactorySet限制住了调用次数,只要调用了一个setFactory方法就不能再调用setXXX方法了,所以如果之前定义了factory就不能再用setFactory方法了,会抛出异常。这种定制化创建View的方法很可能已经被系统先定义了,找找看:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
......
@Nullable
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return onCreateView(name, context, attrs);
        }

        return mFragments.onCreateView(parent, name, context, attrs);
    }
......
}

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ......
        }

果然这方法系统已经调用过了,那我们怎么才能使用自己的方法来创建View呢?
如下:

LayoutInflate  inflater = LayoutInflater.cloneInContext(originInflater,context);
inflater.setFactory(new FactoryX());

这样就可以自己定义怎么构建view了。


总结:

inflate流程图

注意点:
- 这个LayoutInflater是系统服务
- infalte view很依赖编译时对XML文件做的一些处理工作,所以我们不能在运行态使用LayoutInfalter来处理“扁平”/“一般 ”的XML文件,只能处理编译之后的xml文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值