我真服了 这Layout属性怎么不生效之细讲LayoutInflater的inflate方法

发布于:2022-10-21 ⋅ 阅读:(451) ⋅ 点赞:(0)

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

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

”嗯?“

”且听我娓娓道来“

进入主题前先了解几个概念:
在xml布局文件里面的 layout_witdh等都是 指 我这个view 在容器里面的大小, 如果前提要容器,而容器就相当于是我们的父view。比如:我这里的 textview的父view 就是我Linearlayout,也就是我textview的容器

<?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这个布局文件的根布局(即l布局文件最外层的view)指定父view–root (是否指定还要看第三个参数)
第三个参数:attachToRoot,是否让root成为resource的父布局(也就是容器)
先讲一下结论:
分为三种情况:

  1. root 为 null, 也就是我这个resource没有父布局(即没有容器可以放),那么我的resource所有属性都会失效。
  2. root不为null,且attachToRoot为true,将root指定为resource的父布局,resource的根布局属性生效。(有了父容器)
  3. root不为null,且attachToRoot为false, 不将root指定为resource的父布局,resource的根布局属性生效。(这里因为root不为null,有它帮忙生成layoutparams也就是属性,然后设置到我这个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

    @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的父布局了,因此这个button已经在linearlayout里面了,因此不需要给linearlayout再addview。

如果我不注释掉这行代码会发生什么事呢?
在这里插入图片描述
看到了吧,它会报错。 原因是在view这个继承树里面,只能有一个父布局。而我们第三个参数为true已经为resource指定了父布局root了,再addview就会报错了。

第三种情况: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根布局的父布局,因此这里的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的属性值生效了。

然后又有人有疑问了,我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中
因此此时根布局的属性生效。

当然这个大家可以到sdk->tools->monitor.bat下查看布局就可以清晰的看到补局内容了。
或者也可以去搜搜其他文章 写关于view布局相关的文章。

这下诸如 什么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有三种:
UNSPECIFIED:父容器不做限制,View要多大给多大,一般是系统内部使用。

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到