Android LayoutInflater.inflate各个

2021-05-13  本文已影响0人  Heezier

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

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

上一篇下一篇

猜你喜欢

热点阅读