我有篇文章你的自定義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,惟一區別就是
這裏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)