完全搞定LayoutInflater

前提回顾

我有篇文章你的自定义View是否真的支持Margin
讲到 子View的margin属性的支持须要在 自定义ViewGroup 经过generateLayoutParams设置,而子View的padding支持则是本身在onDraw中处理。node

generateLayoutParams大体以下:android

public class MyViewGroup extends ViewGroup {
 @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        // MyLayoutParams 继承自MarginLayoutParams
        return new MyLayoutParams(getContext(), attrs);
    }
}

其实MarginLayoutParams 不光设置子View的margin属性,还设置了子View的layout_width和layout_height属性。见下面的代码:dom

public abstract class ViewGroup extends View {
    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
         public MarginLayoutParams(Context c, AttributeSet attrs) {
            super();

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
            //************************** 设置layout_width和layout_height*********************
            setBaseAttributes(a,
                    R.styleable.ViewGroup_MarginLayout_layout_width,
                    R.styleable.ViewGroup_MarginLayout_layout_height);

            int margin = a.getDimensionPixelSize(
                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
              // ............ 略
            a.recycle();
        }
    }
}

这里之因此提一下这个知识点,是为了说明子View的LayoutParams不能脱离parent存在,不然没法获取该参数。ide


源码分析

咱们常常用到LayoutInflater下面的两个方法源码分析

public View inflate(int resource, ViewGroup root) 
public View inflate(int resource, ViewGroup root, boolean attachToRoot)

而方式一 只是间接调用了 方式二,因此只需分析和使用方式二便可。布局

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

根据xml的ID获取xml解析器,而后又调用了另一个最重要的重载方法code

public View inflate(int resource,ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        // 获取xml解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}

为了方便理解,我 删除 无关代码,并对某些部分稍有修改,其中createViewFromTag方法比较长,此方法仅仅是根据当前的xml标签经过反射生成View对象,代码并不难,可是注意该方法的一个parent参数,源码并未使用此参数,防止对本身产生误导,这里再也不贴出createViewFromTag的代码。xml

public View inflate(XmlPullParser parser, ViewGroup parent, boolean attachToRoot) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            View result = parent;

            // Look for the parent node.
            int type;
            type = parser.next();

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            final String name = parser.getName();
            // 根据当前的标签名(此处是xml的根节点)反射生成一个View对象,查看源码这个parent参数并没什么卵用,没用到。
            final View temp = createViewFromTag(parent, name, inflaterContext, attrs);

            ViewGroup.LayoutParams params = null;

            if (parent != null) {
                // 若是传参parent != null时候,才建立LayoutParams
                //************************* 这个地方是致使咱们经常犯错误的关键 *************************
                // generateLayoutParams -> { new LinearLayout.LayoutParams(attrs); } 
                // LinearLayout.LayoutParams <init> ->  { TypedArray a = context.obtainStyledAttributes; }
                params = parent.generateLayoutParams(attrs);
                if (!attachToRoot) {
                    // 1. 若是parent != null && attachToRoot = false,就给xml的顶布局设置LayoutParams参数。
                    temp.setLayoutParams(params);
                }
            }

            // 此方法为递归调用,inflate 全部temp的直接下级children并添加到temp(ViewGroup)中,child若是有children则继续递归知道遍历完整个dom树。
            rInflateChildren(parser, temp, attrs, true);

            // 2. 若是 parent != null && attachToRoot = true,则把temp(即整个xml视图)看成子view 添加到parent中
            if (parent != null && attachToRoot) {
                // 添加子view(temp) ,并给temp设置LayoutParams
                parent.addView(temp, params);
            }

            // 此处决定 返回的是传入的parent参数仍是xml中的顶级布局
            // 状况1: parent == null (attachToRoot不管true或false) 返回temp(temp无LayoutParams)
            // 状况2: parent != null && attachToRoot == false 返回 temp(temp有LayoutParams)
            // 状况3: parent != null && attachToRoot == true 返回 parent(temp有LayoutParams)
            if (parent == null || !attachToRoot) {
                result = temp;
            }
            return result;
    }

经过去除大量无关代码,分析起来就方便多了,注释说的很是明白了,这里再也不过多讲解。对象

实际开发Listview(RecycleView) 在Adapter 中使用inflater.inflate(R.layout.item, null); 设置
layout_width,layout_height无效的缘由就很是简单明了了。继承

而下面两种方式

inflater.inflate(R.layout.item, parent ,false);
inflater.inflate(R.layout.item, parent ,true);

都会使设置xml的顶级Dom的layout_width和layout_height,惟一区别就是

  1. 一个返回xml视图
  2. 一个返回parent.add(xml视图)

结论

这里temp指的是xml的根节点

1. parent == null (attachToRoot不管true或false) 返回temp(temp无LayoutParams)
2. parent != null && attachToRoot == false 返回 temp(temp有LayoutParams)
3. parent != null && attachToRoot == true 返回 parent(temp有LayoutParams)
相关文章
相关标签/搜索