保姆式级别详解Layout属性怎么不生效之LayoutInflater的inflate方法

“队友呢队友呢 救救我!我这边动态加载的view layout属性怎么失效了…”

“兄弟,你真的懂LayoutInflater. inflate()吗?”

”嗯?“

”且听我娓娓道来“

进入主题前先了解几个概念:

  • 在xml布局文件里面的 layout_witdh等都是 指 我这个view 在容器里面的大小, 如果前提要容器,而容器就相当于是我们的父view。比如:我这里的 textview的父view 就是我的Linearlayout,也就是我textview的容器
  • 根布局是布局文件的最外层布局,如下面代码的LinearLayout (id=“@+id/mroot”)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mroot"
   android:orientation="vertical"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
    <TextView
        android:id="@+id/sonview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    
</LinearLayout>

懂了这个概念我们再来看一下layoutinflater的inflate方法
在这里插入图片描述
inflate有四个重载方法,而常用的有俩个。
先来讲3个参数的,懂了这个就懂其他重载方法了
在这里插入图片描述
第一个参数:resource, 是我们要加载的布局文件
第二个参数:root,为第一个参数resource这个布局文件的根布局(即布局文件最外层的view)指定父view为root (是否指定还要看第三个参数)
第三个参数:attachToRoot,是否让root成为resource根布局的父布局(也就是作为根布局的容器)
先讲一下结论:后再根据源码分析
分为三种情况:

  1. root 为 null, 也就是我这个resource没有父布局(即没有容器可以放),那么我的resource布局文件的最外层的布局(根布局)所有属性都会失效。 (此时返回值为resource的根布局,即最外层的布局)
  2. root不为null,且attachToRoot为true,将root指定为resource的父布局,resource的根布局属性生效。(有了父容器) (此时返回值为root,即第二个参数)
  3. root不为null,且attachToRoot为false, 不将root指定为resource根布局的父布局,但此时resource的根布局属性生效。(这里因为root不为null,有root帮忙生成layoutparams也就是属性,然后设置给我这个resource的根布局) (此时返回值为resource的根布局,即最外层布局)

activity_main.xml

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

layout_text.xml

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mbt"
    android:text="按钮"
    android:layout_gravity="center"
    android:layout_height="399dp"
    android:layout_width="200dp"
    >

</Button>

注意看我的button的高度和宽度
第一种情况:root等于null (此时inflate返回值是最外层的布局,但最外层的布局属性值无效)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewGroup parentView = findViewById(R.id.mroot);
        View view = LayoutInflater.from(this).inflate(R.layout.layout_text, null, true);
        parentView.addView(view); 
    }

在这里不管我第三个参数是false,还是true,我这个button的属性的失效了
在这里插入图片描述
不管怎么调整大小,button都将是这个样子

第二种情况:root != null && attchToRoot == true

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewGroup parentView = findViewById(R.id.mroot);
        View view = LayoutInflater.from(this).inflate(R.layout.layout_text, parentView, true);
     //   parentView.addView(view);
    }

效果:
在这里插入图片描述
button属性值生效了。 (因为此时我的resource的根布局(在这里是button)有了容器)
此时细心的人会发现我把addview这一行给注释掉了。

那么为什么要注释掉呢?
因为我第三个参数为true时已经将root指定为resource的父布局了,此时返回值就是root,这里root就是我们传进来的parentView,因此此处的parentView和view是同一个实例。

如果我不注释掉这行代码会发生什么事呢?
在这里插入图片描述
看到了吧,它会报错。 原因是此时俩个实例是一样的。

第三种情况:root != null && attchToRoot == false

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewGroup parentView = findViewById(R.id.mroot);
        View view = LayoutInflater.from(this).inflate(R.layout.layout_text, parentView, false);
        parentView.addView(view);
    }

在这里插入图片描述
此时button的属性生效。

此时细心的同学又会发现我的addview又 没有注释了。
这里没有注释 是因为 第三个参数为false的时候 就没有把root指定为resource根布局的父布局,返回值就是我们resource这个布局文件的根布局,因此这里的linearlayout(activity_main的根布局)需要手动addview一下,否则inflate加载出来的resource(即button)就不会显示在linearlayout里面了。

再然后又有疑问?
我即然给出了root,为什么还要将attachToRoot弄为false呢? 直接传个root = null不就行了。

从源码分析并不能这样。当root = null就成了第一种情况了。 至于为什么这里为false的时候属性还生效呢,是因为 当 root != null && attchToRoot == false 的时候,root就会为resource的根布局生成属性参数,并且设置给它,因此此时的属性才可以生效。

源码:

    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 {
                advanceToRootNode(parser);
                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                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 {
                    // 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) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    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);
                        }
                    }

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

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

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

从上面的源码可以看出,当root != null && attchToRoot == true的时候,会执行这一行代码:

                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

这时root会把resource解析出来的view add进去,此时的params为null, 由上面的源码可以看出。

而当root != null && attachToRoot = false的时候会执行这个:

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    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);
                        }
                    }

此时我resource根布局的属性值 由 root帮忙生成,并设置进去。因此button的属性值(resource根布局的属性值)生效了。 根据源码的注释也可以清晰的看出

然后又有人有疑问了,我Linearlayout根布局为什么会生效,
跟踪一下源码:

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

发现setContentViews 实际上调用的也是inflate这个方法,但此时是二位参数的,我们再跟踪一下,如下:

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

事实上不管是几个参数的inflate方法,最后都会调用inflate三个参数的方法。
此时更加清晰了。 这里setContentViews()其实执行的是上面的第二种情况。

那么问题来了,我这个linearlayout的最外层已经没有父布局了啊,按理说这里的linear应该不生效才对!

其实是这样子的。你看到的上面activity_main的linearlayout没有父布局其实是一种假象。
我们的页面中有一个顶级View叫做DecorView,DecorView中包含一个竖直方向的LinearLayout,LinearLayout由两部分组成,第一部分是标题栏,第二部分是内容栏,内容栏是一个FrameLayout,我们在Activity中调用setContentView就是将View添加到这个FrameLayout中
由上面的代码也可以看出

        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);

因此此时根布局的属性生效。

当然这个大家可以到sdk->tools->monitor.bat下查看布局就可以清晰的看到补局内容了。
在这里插入图片描述
最顶层的View这里现实了FrameLayout是因为DecorView是继承至FrameLayout
或者也可以去搜搜其他文章 写关于view布局相关的文章。

最后关于返回值可以通过下面源码分析和代码实践得出:

源码:
在这里插入图片描述
在这里插入图片描述
根据注释也可以知temp是resource的根布局。
在这里插入图片描述
从源码的注释也可以清晰的看的出,当满足一或三的条件是返回值就是resource的根布局(最外层布局),而满足第二个条件的时候,返回的是root,即第二个参数。(可能是因为已经执行了root.addView(temp, params)了,所以直接返回root更合理一点。)

代码:
son_layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/mSonParent"
    android:layout_height="50dp"
    android:layout_width="50dp"
    xmlns:android="http://schemas.android.com/apk/res/android">
   <Button android:layout_height="300dp"
    android:id="@+id/mson"
    android:text="按键"
    android:layout_width="300dp"
    android:layout_gravity="center"
    xmlns:android="http://schemas.android.com/apk/res/android">
</Button>
</LinearLayout>

parent_layout

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

</LinearLayout>
         setContentView(R.layout.activity_parent);
        ViewGroup mParent = (ViewGroup)findViewById(R.id.mparent);
        Log.d("jian", "onCreate:  " + "parent :  " + mParent.getId());
        View mSon = getLayoutInflater().inflate(R.layout.activity_mson, null, false);
        Log.d("jian", "onCreate: " + "mSon :  " + mSon.getId());
        View son = mSon.findViewById(R.id.mson);
        Log.d("jian", "onCreate: " + "son :  " + son.getId());
        mParent.addView(mSon);

这下诸如 什么layout属性不生效,但在外面嵌套了个父布局如linearlayout等就后属性又生效了的情况就知道了吧!!!

文章最后再来唠一唠 在自定义view 中的 onMeasure方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        LayoutParams layoutParams = getLayoutParams();
  • LayoutParams和MeasureSpec有什么关系?
    这里简要说一些:
  1. LayoutParams指的是xml布局文件里已layout开头的属性。
  2. 而MeasureSpec则是 一种 压缩 数据。 由Mode + size组成。 比如上面的widthMeasureSpec。 它是int类型,也就是32位。 这里做高俩位用来存放mode,后面30位用来存放size。
  • 而mode有三种:
  1. UNSPECIFIED:父容器不做限制,View要多大给多大,一般是系统内部使用。

  2. EXACTLY:父容器已经检测出View所需大小,具体值由SpeacSize决定。我们定义View的Layout_width,layout_height 为match_parent 或者具体数值时,就是EXACTLY。

  3. AT_MOST:父容器不知道View要多大,根据View实际情况取值,但是最大不超过父容器的宽高。即使用wrap_content属性时,view的内容不确定,可以根据内容改变宽高值,但是最多只能和父容器一样大。

至于这三种在自定义view中扮演什么样的角色,以及怎么用可以在网上搜搜相关文章,写的很详细,这里不做多介绍。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值