美文网首页
Android Inflate 时在做些什么

Android Inflate 时在做些什么

作者: 你可记得叫安可 | 来源:发表于2019-09-27 19:39 被阅读0次

    问题

    我们在代码中向一个 ViewGroup 动态添加一个 layout 时,经常会写如下代码:

    View view = LayoutInflater.from(this).inflate(R.layout.test_layout, null);
    ViewGroup rootView = findViewById(R.id.root_view);
    rootView.addView(view);
    

    注意第一句的 inflate 方法实际上有 2 个重载方法:

    View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
    View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
    

    当我们将第二个参数传 null 时,Android Studio 会提示我们:

    Android Studio Warning
    意思就是当我们传 null 时,所有的 layout_ 参数都会被忽略。这个背后的意思是什么呢?哪些会被忽略呢?

    分析

    Inflate

    跟踪源码最后我们到 LayoutInflaterinflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 方法中

    final AttributeSet attrs = Xml.asAttributeSet(parser);
    ...
    // Look for the root node.
    int type;
    while ((type = parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
        // Empty
    }
    ...
    // 当 xml 的根标签是 <merge> 时
    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 {
        // 当 xml 的根标签不是 <merge> 时,把根 view 实例化出来,解析了 xml 属性中非 layout_ 前缀的属性
        // Temp is the root view that was found in the 
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
        
        ViewGroup.LayoutParams params = null;
        
        if (root != null) { // 如果我们传了 root 进来
            // 这里会解析 layout_ 为前缀的属性,并生成 LayoutParams
            // 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);
            }
            ...
        }
        // 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;
        }
    }
    
    
    1. 解析根标签

    通过 parser.next() 取出根标签,并且将 parser 指向二级标签

    2. 当根标签是 <merge>

    此时 inflate() 调用一定要有 root。接着就调用 rInflate(parser, root, inflaterContext, attrs, false);。**根据 1 中分析,此时 parser 已经指向二级标签,因此 rInflate() 解析的是二级标签。因此代码逻辑直接跳过了根标签<merge>中的所有属性(layout_view自身属性等)。** 根据上面的源码我们可以看到,AttributeSet里保存了这个 xml 中的所有属性,包括根布局 tag 中的 layout 属性(layout_width,layout_margin等)和根 view 自身的属性(LinearLayout就是orientation` 等)。

    3. 当根标签是具体某个 view

    代码的逻辑主要通过调用 createViewFromTag 来生成 xml 的根 view 的实例。生成根 view 实例只是通过反射调用了 view 的构造方法,并没有设置 view 的 LayoutParameters
    View 的构造方法中,通过解析 AttributeSet 设置了自己的属性。比如 LinearLayout 的构造方法就从 AttributeSet 中解析了 orientation, gravity, weightSum, showDividers, dividerPadding 等属性。但是并没有带 layout_ 前缀的 xml 属性。

    LinearLyaout 构造方法

    接下来,如果函数传进了 root,则再调用 generateLayoutParamsAttributeSet 中生成 LayoutParams。如果 attachToRoot == true,则还会调用 addView 方法将自己加进 ViewTree 中。

    如果在 inflate 方法中不传 rootView,则不会解析根节点的带 layout_ 前缀的属性。如果设置了,则会解析带 layout_ 前缀的属性,并将结果赋值给根节点 view 的 LayoutParams

    addView

    那么在上一节中 inflate 时没有传 root 进去,那么生成的 view 就没有 LayoutParams。那么在 inflate 完之后,开发者调用 addView 加入到父 view 时是怎样的 layoutParams 呢?
    跟进 addView 源码,

    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    

    如果 params 为空,则会调用该 ViewGroupgenerateDefaultLayoutParams() 生成一个。不同的 ViewGroup 实现了自己的逻辑。比如 FrameLayout 就是 match_parent

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }
    

    LinearLayout 则是看 orientation 分两种情况

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }
    

    所以如果通过调用 View inflate(@LayoutRes int resource, @Nullable ViewGroup root = null, boolean attachToRoot = false) 生成的 view 是没有 layoutParams 的,开发者再通过调用 addView 来加入父布局时,viewlayoutParams 是通过父布局的 generateDefaultLayoutParams() 来生成的,不同的父布局会生成不同的 layoutParams。这就是为什么有时有 wrap_content 有时是 match_parent

    相关文章

      网友评论

          本文标题:Android Inflate 时在做些什么

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