Android Inflate 时在做些什么

问题

我们在代码中向一个 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 的根标签是  时
if (TAG_MERGE.equals(name)) {
    if (root == null || !attachToRoot) {
        throw new InflateException(" can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
    }
    // rInflate 应该是 recurseInflate 的意思,表示这是一个递归的过程
    rInflate(parser, root, inflaterContext, attrs, false);
} else {
    // 当 xml 的根标签不是  时,把根 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. 当根标签是

此时 inflate() 调用一定要有 root。接着就调用 rInflate(parser, root, inflaterContext, attrs, false);根据 1 中分析,此时 parser 已经指向二级标签,因此 rInflate() 解析的是二级标签。因此代码逻辑直接跳过了根标签 中的所有属性(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 时在做些什么)