Android LayoutInflater.inflate各个参数作用

Android LayoutInflater.inflate各个参数作用

简介

LayoutInflater这个类相信大家都不陌生,当我们平时需要加载layout文件来转换成View的场景都会用到它,其中最常用的有如下两个加载方法:

View inflate(int resource, ViewGroup root)
View inflate(int resource, ViewGroup root, boolean attachToRoot)

可能一直在使用,却并未了解其中参数真正的奥义所在,让我们从源码中看下这几个参数究竟代表着什么。

源码分析

先看下两个参数的inflate,源码如下:

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

可以看到,其实它内部还是调用了三个参数的inflate,且只有root不为null时,attachToRoot这个参数才会传true。那我们可以直接看三个参数的inflate:

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

首先获取资源对象然后将其转换为XmlResourceParser对象,XmlResourceParser对象可以解析layout.xml文件中的具体属性和标签等信息,那就进而看inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean 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 {
                final String name = parser.getName();
                //标识一
                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, inflaterContext, attrs, false);
                } else {
                    //标识二,正常的非merge标签都走这里
                    // 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) {
                            // 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,将temp添加到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);
                    }

                    //标识五:root == null || !attachToRoot,将temp返回,否则返回root
                    // 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) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

标识一:异常兼容

你要加载的layout文件中根布局是merge就必须设置父布局的原因

标识二:temp=你传入的lauout布局view

createViewFromTag(root, name, inflaterContext, attrs)最终走到createView(String name, String prefix, AttributeSet attrs),和一般的Activity加载布局一样,解析XML标签并且使用反射将XML布局创建出来;

标识三:创建和root相关的params

root不为空时,使用root去创建LayoutParams参数,那么此时该参数如果设置给layout加载好的View,那么root将会约束到layout,如果没有设置将无效

params = root.generateLayoutParams(attrs);

接着判断attachToRoot,如果attachToRoot为false,则将params设置给temp(你将要加载的布局)

标识四:root != null && attachToRoot,将temp添加到root中,并且设置了params

标识五:root == null || !attachToRoot,将temp返回,否则返回root

现象

1.如果root为null或者attachToRoot为false时,则调用layout.xml中的根布局的属性并且将其作为一个View对象返回。

2.如果root不为null,但attachToRoot为false时,则先将layout.xml中的根布局转换为一个View对象,再调用传进来的root的布局属性设置给这个View,然后将它返回。

3.如果root不为null,且attachToRoot为true时,则先将layout.xml中的根布局转换为一个View对象,再将它add给root,最终再把root返回出去。(两个参数的inflate如果root不为null也是相当于这种情况)

Demo验证

四种情况:

通过以上的源码可知:

inflate(R.layout.layout2, this);
inflate(R.layout.layout3, this, true);

这两种为同一种类型

inflate(R.layout.layout1, null);
inflate(R.layout.layout2, this);
inflate(R.layout.layout3, this, true);
inflate(R.layout.layout4, this, false);

验证代码:

public class MyViewGroup extends LinearLayout {
    public MyViewGroup(Context context) {
        this(context, null);
    }

    public MyViewGroup(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
        setDividerDrawable(context.getResources().getDrawable(R.drawable.divider));
        setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);

        //第一种
        //加载resID的布局文件,并返回resID根布局类型RelativeLayout
        View view1 = LayoutInflater.from(context).inflate(R.layout.layout1, null);
        TextView tv1 =  view1.findViewById(R.id.tv1);
        tv1.setText(tv1.getText() + "第一种");
        //resID根布局RelativeLayout的参数将会失去作用
        addView(view1);
        Log.d("inflate" , "root == null :  " +  view1.getClass().getName());

        //第二种
        //加载resID的布局文件,并返回当前布局类型MyViewGroup
        View view2 = LayoutInflater.from(context).inflate(R.layout.layout2, this);
        TextView tv2 =  view2.findViewById(R.id.tv2);
        tv2.setText(tv2.getText() + "第二种");
        //addView(view2);此时不能添加View,否则将报错
        //MyViewGroup
        Log.d("inflate", "root == this, attachToRoot默认为 == true:  " + view2.getClass().getName());

        //第三种
        //加载resID的布局文件,并返回当前布局类型MyViewGroup
        View view3 = LayoutInflater.from(context).inflate(R.layout.layout3, this, true);
        TextView tv3 =  view3.findViewById(R.id.tv3);
        tv3.setText(tv3.getText() + "第三种");
        //addView(view3); view3此时不能添加View,否则将报错
        Log.d("inflate", "root == this, attachToRoot == true:  " + view3.getClass().getName());

        //第四种
        //加载resID的布局文件,并返回resID根布局类型RelativeLayout
        View view4 = LayoutInflater.from(context).inflate(R.layout.layout4, this, false);
        TextView tv4 =  view4.findViewById(R.id.tv4);
        tv4.setText(tv4.getText() + "第四种");
        addView(view4);
        Log.d("inflate", "root == this, attachToRoot == true:  " + view4.getClass().getName());

// TODO: 2021/5/13 注意
//        当root不为空,attachToRoot=true,inflate之后会将当前布局加载到当前根布局,并且返回当前root,
//        那么也就是说inflate的结果包含了当前根布局的内容,如果之前的加载的布局包含某个ID=tv的组件,
//        那么执行findViewById将会找到布局中的第一个ID=tv的组件,
//        执行修改的时候则只会修改之前加载的第一个布局中ID=tv的组件,而不会修改当前这次加载的layout中的组件
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

}

执行结果:

root == null :  android.widget.RelativeLayout
root == this, attachToRoot默认为 == true:  com.example.flowlayout.MyViewGroup
root == this, attachToRoot == true:  com.example.flowlayout.MyViewGroup
root == this, attachToRoot == true:  android.widget.RelativeLayout

界面效果:

image-20210513070641699

表现结果

inflate(R.layout.layout1, null);

返回resID根布局类型,并且需要执行addView动作才能将layout添加到父容器中,且resID根布局的布局参数失效;

inflate(R.layout.layout2, this);

返回传入的root布局类型,并且inflate已经将resID的布局加到root布局中,resID根布局的布局参数有效;

inflate(R.layout.layout3, this, true);

返回传入的root布局类型,并且inflate已经将resID的布局加到root布局中,resID根布局的布局参数有效;

inflate(R.layout.layout4, this, false);

返回resID根布局类型,并且需要执行addView动作才能将layout添加到父容器中,resID根布局的布局参数有效;

总结

root:用来可以用来创建params并约束你要加载的temp,并且条件成立时将temp add到root中

attachToRoot:用来决定你要是否将temp放到root中

addView报错

当给一个ViewGroup添加view的时候都会调用addView,最终调用addViewInner

  private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }

        //标识
        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }
        //...
 }

可以看到if (child.getParent() != null) 时会抛出异常,因为Android中的View为树形结构,每一个View被添加到ViewGroup时,改View就持有父节点,重复添加将会造成一个View有多个Parent,显然不符合规范,所以在上述实例中,inflate已经添加过你传入的view时,如果你再次执行addView将会报错。

当root不为空,attachToRoot=true的特殊情况

当root不为空,attachToRoot=true,inflate之后会将当前布局加载到当前根布局,并且返回当前root,那么也就是说inflate的结果包含了当前根布局的内容,如果之前的加载的布局包含某个ID=tv的组件,那么执行findViewById将会找到布局中的第一个ID=tv的组件,执行修改的时候则只会修改之前加载的第一个布局中ID=tv的组件,而不会修改当前这次加载的layout中的组件

具体效果如下:

public class MyViewGroup extends LinearLayout {
    public MyViewGroup(Context context) {
        this(context, null);
    }

    public MyViewGroup(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
        setDividerDrawable(context.getResources().getDrawable(R.drawable.divider));
        setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);

        //第一种
        //加载resID的布局文件,并返回resID根布局类型RelativeLayout
        View view1 = LayoutInflater.from(context).inflate(R.layout.layout1, null);
        TextView tv1 =  view1.findViewById(R.id.tv1);
        tv1.setText(tv1.getText() + "第一种");
        //resID根布局RelativeLayout的参数将会失去作用
        addView(view1);
        Log.d("inflate" , "root == null :  " +  view1.getClass().getName());

        //第二种
        //加载resID的布局文件,并返回当前布局类型MyViewGroup
        View view2 = LayoutInflater.from(context).inflate(R.layout.layout1, this);
        TextView tv2 =  view2.findViewById(R.id.tv1);
        tv2.setText(tv2.getText() + "第二种");
        //addView(view2);此时不能添加View,否则将报错
        //MyViewGroup
        Log.d("inflate", "root == this, attachToRoot默认为 == true:  " + view2.getClass().getName());

        //第三种
        //加载resID的布局文件,并返回当前布局类型MyViewGroup
        View view3 = LayoutInflater.from(context).inflate(R.layout.layout1, this, true);
        TextView tv3 =  view3.findViewById(R.id.tv1);
        tv3.setText(tv3.getText() + "第三种");
        //addView(view3); view3此时不能添加View,否则将报错
        Log.d("inflate", "root == this, attachToRoot == true:  " + view3.getClass().getName());

        //第四种
        //加载resID的布局文件,并返回resID根布局类型RelativeLayout
        View view4 = LayoutInflater.from(context).inflate(R.layout.layout4, this, false);
        TextView tv4 =  view4.findViewById(R.id.tv4);
        tv4.setText(tv4.getText() + "第四种");
        addView(view4);
        Log.d("inflate", "root == this, attachToRoot == true:  " + view4.getClass().getName());

// TODO: 2021/5/13 注意
//        当root不为空,attachToRoot=true,inflate之后会将当前布局加载到当前根布局,并且返回当前root,
//        那么也就是说inflate的结果包含了当前根布局的内容,如果之前的加载的布局包含某个ID=tv的组件,
//        那么执行findViewById将会找到布局中的第一个ID=tv的组件,
//        执行修改的时候则只会修改之前加载的第一个布局中ID=tv的组件,而不会修改当前这次加载的layout中的组件
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

}

执行结果:

root == null :  android.widget.RelativeLayout
root == this, attachToRoot默认为 == true:  com.example.flowlayout.MyViewGroup
root == this, attachToRoot == true:  com.example.flowlayout.MyViewGroup
root == this, attachToRoot == true:  android.widget.RelativeLayout

界面效果图:

image-20210513065847660

参考文章://www.greatytc.com/p/3f871d95489c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容