美文网首页
Android LayoutInflater.inflate各个

Android LayoutInflater.inflate各个

作者: Heezier | 来源:发表于2021-05-13 07:43 被阅读0次

    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

    相关文章

      网友评论

          本文标题:Android LayoutInflater.inflate各个

          本文链接:https://www.haomeiwen.com/subject/dkktjltx.html