相关文章推荐
月球上的镜子  ·  PSSession 简介 - ...·  6 月前    · 
逃课的黑框眼镜  ·  nodejs http request ...·  1 年前    · 
伤情的红豆  ·  Python ...·  1 年前    · 
LayoutInfalter的使用与源码解析

LayoutInfalter的使用与源码解析

1、LayoutInfalter的作用

查看源码注释,可以知道其作用为将布局XML文件实例化为其相应的View对象。

Instantiates a layout XML file into its corresponding View objects.

并且可以使用以下三种方式获取布局解析服务,其实最后都是使用的第三种:

Activity.getLayoutInflater();
LayoutInflater.from(context);
Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

2、LayoutInfalter的使用

ViewDrawActivity.java

public class ViewDrawActivity extends AppCompatActivity {
    private LinearLayout mLinearLayoutMain;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_draw);
        mLinearLayoutMain = findViewById(R.id.ll_main);
        //获取XML对应的View对象
        View viewRootNull = LayoutInflater.from(this).inflate(R.layout.layout_button, null);
        //将View对象添加到其他布局中
        mLinearLayoutMain.addView(viewRootNull);

activity_view_draw.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
</LinearLayout>

layout_button.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/app_name">
</Button>

运行结果:


3、LayoutInfalter的源码解析

3.1、尝试获取已经提前编译好的View对象

LayoutInfalter.java

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
	//使用反射尝试获取已经提前编译好的View对象
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    //根据资源ID获取布局的XML资源解析器
    XmlResourceParser parser = res.getLayout(resource);
    try {
    	//使用XML资源解析器对XML进行解析
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
}

先直接使用反射尝试获取已经提前编译好的View对象,如果获取到直接返回View对象,如果获取不到再根据资源ID获取布局的XML资源解析器,然使用XML资源解析器对XML进行解析。

3.2、将XML解析并实例化后的View对象进行添加返回操作

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            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)) {
                //如果布局根标签为merge,那么要求传入的root不能为空,且attachToRoot需为true
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid ViewGroup root and attachToRoot=true");
                    //继续解析XML
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                 //根据XML中的根标签生成根View对象
                    // 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的布局参数
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                        //如果attachToRoot==false,则当使用addView的时候再设置布局参数
                            // 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);
                    //如果root!=null,且attachToRoot==true,则自动将布局添加到root中,并设置布局参数
                    // 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.
                    //当root==null或者attachToRoot==false,则直接返回View对象
                    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;
            return result;

1、布局根标签为merge

首先判断根标签是否是merge标签,如果布局根标签为merge,那么要求传入的root不能为空,且attachToRoot需为true,然后继续解析XML,也就是rInflate()方法,否则会报以下异常:

Caused by: android.view.InflateException: Binary XML file line #2: <merge /> can be used only with a valid ViewGroup root and attachToRoot=true

如果布局根标签不是merge,首先根据XML中的根标签生成根View对象,也就是createViewFromTag()方法,然后解析子布局,也就是rInflateChildren()方法。然后分以下几种情况进行View对象的添加操作:

2、root==null

直接返回生成的View对象,需要使用addView添加到其他布局中,添加的时候会忽略布局最外层的参数。

也就是说,修改了Button布局为固定大小,最后的加载结果还是不变: layout_button.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="360dp"
    android:layout_height="360dp"
    android:text="@string/app_name">
</Button>

3、attachToRoot==true

如果root!=null,默认情况下attachToRoot==true,那么给加载的布局文件指定root为父布局,并且直接添加到root布局中,添加时不忽略布局最外层的参数,然后返回View对象。

layout_width和layout_height是用于设置View在布局中的大小的,但如果还没有依附在父布局中,也就是在Root为null的情况下,这两个的设置将会无效。平时在Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的,layout_width和layout_height都是有作用的,这主要是因为,在setContentView()方法中,Android是使用root!=null和attachToRoot==true的方式进行添加布局的,自动将布局添加到FrameLayout布局contentParent中,所以layout_width和layout_height属性才会有效果。

AppCompatDelegateImpl.java

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

我们修改添加Button的方式为root!=null,attachToRoot==true,就可以使得修改Button布局为固定大小产生效果: ViewDrawActivity.java

public class ViewDrawActivity extends AppCompatActivity {
    private LinearLayout mLinearLayoutMain;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_draw);
        mLinearLayoutMain = findViewById(R.id.ll_main);
        //解析获取View对象的同时,添加View到root布局中
        LayoutInflater.from(this).inflate(R.layout.layout_button, mLinearLayoutMain);

4、attachToRoot==false

如果root!=null,且如果attachToRoot==false,那么给加载的布局文件指定root为父布局,但无法直接添加到root布局中,需要使用addView添加到其他布局中,但不一定非要添加到所传的root布局中,添加时不忽略布局最外层的参数,然后返回View对象。

3.3、使用XML资源解析器对XML进行解析

在3.2中想要获取XML解析并实例化的View对象,就必须先进行XML解析得到AttributeSet, 也就是rInflate()、rInflateChildren()等方法;

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)) {
				//request_foucs
                pendingRequestFocus = true;
                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("<include /> cannot be the root element");
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
            	//merge
                throw new InflateException("<merge /> 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);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        if (finishInflate) {
            parent.onFinishInflate();
    }

然后使用反射根据tag对解析后的AttributeSet进行实例化得到View对象,也就是createViewFromTag()、createView()等方法。

@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    Class<? extends View> clazz = null;
    try {
     //创建构造器
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, 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 = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class);
                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, viewContext, attrs);
        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        try {
         //使用反射创建View的实例对象
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
             //如果是ViewStub类型,当稍后解析ViewStub时使用同个上下文解析
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            return view;
        } finally {
            mConstructorArgs[0] = lastContext;
    } catch (NoSuchMethodException e) {
        final InflateException ie = new InflateException(
                getParserStateDescription(viewContext, attrs)
                + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    } catch (ClassCastException e) {
        // If loaded class is not a View subclass
        final InflateException ie = new InflateException(
                getParserStateDescription(viewContext, attrs)
                + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    } catch (ClassNotFoundException e) {
        // If loadClass fails, we should propagate the exception.
        throw e;
    } catch (Exception e) {
        final InflateException ie = new InflateException(
                getParserStateDescription(viewContext, attrs) + ": Error inflating class "
                        + (clazz == null ? "<unknown>" : clazz.getName()), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);