完全搞定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)
相關文章
相關標籤/搜索