Android原始碼解析 --- LayoutParams以及ViewTree的生成
在分析ViewTree的生成之前,我們先來看下LayoutParams。LayoutParams翻譯為“佈局引數”,一般情況下,我們在程式碼中動態設定View的寬高或者Margin的時候會用到它,如下所示:
TextView tv_test = findViewById(R.id.tv_test); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tv_test.getLayoutParams(); params.height = height; tv_test.setLayoutParams(params);
我們都知道,如果View在佈局檔案中直接對應的父佈局為LinearLayout,那麼我們在程式碼中通過該View的getLayoutParams方法得到的佈局引數需要強轉成LinearLayout.LayoutParams,why?大家思考過原因嗎?我接著再問一句,LayoutParams可以動態設定View的寬高或者Margin,如果我想通過程式碼來動態設定View的Padding值,需要怎麼做呢?你也許會說,這還不簡單嗎,然後刷刷刷將程式碼寫了出來:
TextView tv_test = findViewById(R.id.tv_test); tv_test.setPadding(left,top,right,bottom);
沒錯,程式碼的確是這樣子的,不知道你有沒有想過,為什麼LayoutParams可以設定View的寬高或者Margin,卻不能設定View的Padding?LayoutParams和View的Padding之間到底有什麼關係呢?下面我們就一點一點來解決這些疑惑。
首先我先簡單說下LayoutParams的繼承結構,本來應該畫張圖簡潔明瞭一點貼出來的,無奈筆者畫圖太醜了,在這裡直接語言簡單描述下吧。在ViewGroup類中定義了一個內部類LayoutParams,這個LayoutParam類中定義了兩個成員變數width、height,分別用來儲存View的寬高資訊,我們在佈局檔案中通過layout_width和layout_height屬性配置的寬高資訊就是儲存在這兩個成員變數中的。然後呢,ViewGroup類中還定義了一個內部類MarginLayoutParams,MarginLayoutParams繼承自ViewGroup中的LayoutParams類,MarginLayoutParams對ViewGroup中的LayoutParams類進行了擴充套件,新增了leftMargin、topMargin、rightMargin、bottomMargin屬性,分別對應View佈局檔案中的layout_marginLeft、layout_marginTop、layout_marginRight、layout_marginBottom。每個具體的ViewGroup針對各自的職責又分別定製了一個LayoutParams類,繼承自ViewGroup中的MarginLayoutParams類,例如LinearLayout類中定義了一個內部類LayoutParams,對layout_weight進行了支援,RelativeLayout類中定義了一個內部類LayoutParams,對layout_centerInParent、layout_alignParentRight等進行了支援。
首先看下ViewGroup中LayoutParams類中的原始碼:
public static class LayoutParams { /** * Special value for the height or width requested by a View. * FILL_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. This value is deprecated * starting in API Level 8 and replaced by {@link #MATCH_PARENT}. */ @SuppressWarnings({"UnusedDeclaration"}) @Deprecated public static final int FILL_PARENT = -1; /** * Special value for the height or width requested by a View. * MATCH_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. Introduced in API Level 8. */ public static final int MATCH_PARENT = -1; /** * Special value for the height or width requested by a View. * WRAP_CONTENT means that the view wants to be just large enough to fit * its own internal content, taking its own padding into account. */ public static final int WRAP_CONTENT = -2; public int width; public int height; public LayoutParams(Context c, AttributeSet attrs) { TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout); setBaseAttributes(a, R.styleable.ViewGroup_Layout_layout_width, R.styleable.ViewGroup_Layout_layout_height); a.recycle(); } public LayoutParams(int width, int height) { this.width = width; this.height = height; } protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { width = a.getLayoutDimension(widthAttr, "layout_width"); height = a.getLayoutDimension(heightAttr, "layout_height"); }
接著看下ViewGroup中的MarginLayoutParams原始碼:
public static class MarginLayoutParams extends ViewGroup.LayoutParams { /** * The left margin in pixels of the child. Margin values should be positive. * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value * to this field. */ @ViewDebug.ExportedProperty(category = "layout") public int leftMargin; /** * The top margin in pixels of the child. Margin values should be positive. * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value * to this field. */ @ViewDebug.ExportedProperty(category = "layout") public int topMargin; /** * The right margin in pixels of the child. Margin values should be positive. * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value * to this field. */ @ViewDebug.ExportedProperty(category = "layout") public int rightMargin; /** * The bottom margin in pixels of the child. Margin values should be positive. * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value * to this field. */ @ViewDebug.ExportedProperty(category = "layout") public int bottomMargin; /** * Creates a new set of layout parameters. The values are extracted from * the supplied attributes set and context. * * @param c the application environment * @param attrs the set of attributes from which to extract the layout *parameters' values */ public MarginLayoutParams(Context c, AttributeSet attrs) { super(); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 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); if (margin >= 0) { leftMargin = margin; topMargin = margin; rightMargin= margin; bottomMargin = margin; } else { int horizontalMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1); int verticalMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1); if (horizontalMargin >= 0) { leftMargin = horizontalMargin; rightMargin = horizontalMargin; } else { leftMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginLeft, UNDEFINED_MARGIN); if (leftMargin == UNDEFINED_MARGIN) { mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK; leftMargin = DEFAULT_MARGIN_RESOLVED; } rightMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginRight, UNDEFINED_MARGIN); if (rightMargin == UNDEFINED_MARGIN) { mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK; rightMargin = DEFAULT_MARGIN_RESOLVED; } } startMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginStart, DEFAULT_MARGIN_RELATIVE); endMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_MARGIN_RELATIVE); if (verticalMargin >= 0) { topMargin = verticalMargin; bottomMargin = verticalMargin; } else { topMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginTop, DEFAULT_MARGIN_RESOLVED); bottomMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginBottom, DEFAULT_MARGIN_RESOLVED); } if (isMarginRelative()) { mMarginFlags |= NEED_RESOLUTION_MASK; } } final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport(); final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion; if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) { mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK; } // Layout direction is LTR by default mMarginFlags |= LAYOUT_DIRECTION_LTR; a.recycle(); }
在這裡我們簡單分析下MarginLayoutParams兩個引數的構造方法,可以看到構造方法中首先呼叫到setBaseAttributes方法,對width和height進行處理,然後呼叫a.getDimensionPixelSize方法,傳遞的第一個引數為com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin,簡單說就是獲取佈局檔案中View對應的layout_margin的值,如果我們的View在佈局檔案中設定了layout_margin的話,獲取到的值應該大於等於0,如果我們的View在佈局檔案中沒有設定layout_margin,則預設為-1。通過後續程式碼可以看出,如果我們在佈局檔案中設定了layout_margin,則以該margin值為準,否則分別獲取到R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal和R.styleable.ViewGroup_MarginLayout_layout_marginVertical,也就是View在佈局檔案中對應的layout_marginHorizontal、layout_marginVertical,如果在佈局檔案中設定了layout_marginHorizontal或者layout_marginVertical的話,則以該值為準,否則分別獲取到對應方向的margin值。一句話來說就是layout_margin的許可權>layout_marginHorizontal或者layout_marginVertical>對應方向的margin。到底是不是這個樣子呢?大家可以自己驗證下,絕對是這個樣子哈哈。
接著我們看下LinearLayout中的LayoutParams:
public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * Indicates how much of the extra space in the LinearLayout will be * allocated to the view associated with these LayoutParams. Specify * 0 if the view should not be stretched. Otherwise the extra pixels * will be pro-rated among all views whose weight is greater than 0. */ @ViewDebug.ExportedProperty(category = "layout") public float weight; /** * Gravity for the view associated with these LayoutParams. * * @see android.view.Gravity */ @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from =-1,to = "NONE"), @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,to = "NONE"), @ViewDebug.IntToString(from = Gravity.TOP,to = "TOP"), @ViewDebug.IntToString(from = Gravity.BOTTOM,to = "BOTTOM"), @ViewDebug.IntToString(from = Gravity.LEFT,to = "LEFT"), @ViewDebug.IntToString(from = Gravity.RIGHT,to = "RIGHT"), @ViewDebug.IntToString(from = Gravity.START,to = "START"), @ViewDebug.IntToString(from = Gravity.END,to = "END"), @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL,to = "CENTER_VERTICAL"), @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL,to = "FILL_VERTICAL"), @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL,to = "FILL_HORIZONTAL"), @ViewDebug.IntToString(from = Gravity.CENTER,to = "CENTER"), @ViewDebug.IntToString(from = Gravity.FILL,to = "FILL") }) public int gravity = -1; /** * {@inheritDoc} */ public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout); weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0); gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1); a.recycle(); }
最後我們再看下RelativeLayout中的LayoutParams:
public static class LayoutParams extends ViewGroup.MarginLayoutParams { @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { @ViewDebug.IntToString(from = ABOVE,to = "above"), @ViewDebug.IntToString(from = ALIGN_BASELINE,to = "alignBaseline"), @ViewDebug.IntToString(from = ALIGN_BOTTOM,to = "alignBottom"), @ViewDebug.IntToString(from = ALIGN_LEFT,to = "alignLeft"), @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"), @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,to = "alignParentLeft"), @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,to = "alignParentRight"), @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,to = "alignParentTop"), @ViewDebug.IntToString(from = ALIGN_RIGHT,to = "alignRight"), @ViewDebug.IntToString(from = ALIGN_TOP,to = "alignTop"), @ViewDebug.IntToString(from = BELOW,to = "below"), @ViewDebug.IntToString(from = CENTER_HORIZONTAL,to = "centerHorizontal"), @ViewDebug.IntToString(from = CENTER_IN_PARENT,to = "center"), @ViewDebug.IntToString(from = CENTER_VERTICAL,to = "centerVertical"), @ViewDebug.IntToString(from = LEFT_OF,to = "leftOf"), @ViewDebug.IntToString(from = RIGHT_OF,to = "rightOf"), @ViewDebug.IntToString(from = ALIGN_START,to = "alignStart"), @ViewDebug.IntToString(from = ALIGN_END,to = "alignEnd"), @ViewDebug.IntToString(from = ALIGN_PARENT_START,to = "alignParentStart"), @ViewDebug.IntToString(from = ALIGN_PARENT_END,to = "alignParentEnd"), @ViewDebug.IntToString(from = START_OF,to = "startOf"), @ViewDebug.IntToString(from = END_OF,to = "endOf") }, mapping = { @ViewDebug.IntToString(from = TRUE, to = "true"), @ViewDebug.IntToString(from = 0,to = "false/NO_ID") }) private int[] mRules = new int[VERB_COUNT]; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.RelativeLayout_Layout); final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion; mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 || !c.getApplicationInfo().hasRtlSupport()); final int[] rules = mRules; //noinspection MismatchedReadAndWriteOfArray final int[] initialRules = mInitialRules; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing: alignWithParent = a.getBoolean(attr, false); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: rules[LEFT_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf: rules[RIGHT_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above: rules[ABOVE] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below: rules[BELOW] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline: rules[ALIGN_BASELINE] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft: rules[ALIGN_LEFT] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop: rules[ALIGN_TOP] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight: rules[ALIGN_RIGHT] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom: rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft: rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop: rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight: rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom: rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent: rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal: rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical: rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf: rules[START_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf: rules[END_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart: rules[ALIGN_START] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd: rules[ALIGN_END] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart: rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd: rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0; break; } } mRulesChanged = true; System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT); a.recycle(); }
可以看到RelativeLayout.LayoutParams中將特有屬性值存放到了mRules陣列中,這點略有不同,應該是考慮到特有屬性數量過多的原因吧。
好了,我們接著從原始碼的角度分析下ViewTree的生成。通常,佈局檔案的載入我們這麼寫:
View view = LayoutInflater.from(this).inflate(R.layout.layout_test,null);
首先呼叫LayoutInflater.from(this),獲取到LayoutInflater物件,本質為PhoneLayoutInflater例項,具體怎麼獲取的,在這裡我就不贅述了,大家感興趣的可以翻看下原始碼。好了我們接著看下inflate方法,跟進去:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
接著跟:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { //重點 return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
接著跟:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); //判斷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 { //1.建立根view // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // 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); } } if (DEBUG) { System.out.println("-----> start inflating children"); } //2.載入根view下的所有子view,構建ViewTree // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } //這裡由於我們傳進來的root為null,attachToRoot為false,跳過該語句塊 if (root != null && attachToRoot) { root.addView(temp, params); } //將佈局檔案根view賦值給result if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } //最後將佈局檔案對應的根view return掉 return result; } }
我們首先來到第一步,看下根view是怎麼建立的,跟進去createViewFromTag方法:
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { return createViewFromTag(parent, name, context, attrs, false); } ... //最終呼叫到createView方法 public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Class<? extends View> clazz = null; try { if (constructor == null) { // 通過類載入器載入對應的class clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { ... } //通過反射建立view物件 final View view = constructor.newInstance(args); return view; } ... }
接著我們來到 2 處看下rInflateChildren方法,這才是ViewTree生成的關鍵:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }
接著跟進去:
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { //重點 //1.呼叫createViewFromTag方法,最終通過反射建立子view物件 final View view = createViewFromTag(parent, name, context, attrs); //2.parent為直接包裹子view的ViewGroup,例如LinearLayout、RelativeLayout等,將parent向上轉型為ViewGroup final ViewGroup viewGroup = (ViewGroup) parent; //3.呼叫viewGroup的generateLayoutParams方法,獲取到對應的LayoutParams物件 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //4.遞迴呼叫rInflateChildren方法,若當前子view同為ViewGroup,為其新增子View rInflateChildren(parser, view, attrs, true); //5.將子View新增到當前ViewGroup中 viewGroup.addView(view, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } if (finishInflate) { parent.onFinishInflate(); } }
上述程式碼中的重點部分我已經做了相應的標註,假設我們當前的ViewGroup為LinearLayout,1處子view物件建立完畢後代碼執行到2處,將LinearLayout向上轉型為ViewGroup,接著3處呼叫到viewGroup.generateLayoutParams方法,實質呼叫到LinearLayout的generateLayoutParams方法,我們跟進去看下:
#LinearLayout @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LinearLayout.LayoutParams(getContext(), attrs); }
可以看到LinearLayout的generateLayoutParams方法中建立了LinearLayout對應的LayoutParams物件。同樣我們可以分析出,每一個View物件的建立都伴隨著一個具體LayoutParams物件的建立。
最後我們看下5處,呼叫viewGroup.addView方法,將子View新增到當前ViewGroup中。由於在這裡我們的viewGroup為LinearLayout,所以呼叫到LinearLayout的addView方法,我們跟進去看下:
#ViewGroup @Override public void addView(View child, LayoutParams params) { addView(child, -1, params); }
接著跟進去:
public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this + " addView"); } if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child's request // will be blocked at our level requestLayout(); invalidate(true); //重點 addViewInner(child, index, params, false); }
可以看到上述方法最終呼叫到addViewInner方法,我們跟進去看下:
private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { if (mTransition != null) { // Don't prevent other add transitions from completing, but cancel remove // transitions to let them complete the process before we add to the container mTransition.cancel(LayoutTransition.DISAPPEARING); } //若child.getParent() != null,直接丟擲異常 if (child.getParent() != null) { throw new IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first."); } if (mTransition != null) { mTransition.addChild(this, child); } //檢查 params是否為null,若params為null則呼叫generateLayoutParams方法建立具體LayoutParams物件 if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } //在這裡我們傳進來的preventRequestLayout為false,執行else語句 if (preventRequestLayout) { child.mLayoutParams = params; } else { //1.呼叫當前子View的setLayoutParams方法,設定LayoutParams child.setLayoutParams(params); } //mChildrenCount為ViewGroup中的一個成員變數,用來記錄當前ViewGroup中子View的個數。在這裡我們傳入的index為-1 if (index < 0) { index = mChildrenCount; } //2.重點,將子View新增到mChildren陣列中, //此時當前ViewGroup持有子View物件的引用,ViewTree中由ViewGroup指向子View構建完畢 addInArray(child, index); //在這裡傳入的preventRequestLayout為false,執行else語句,將當前ViewGroup物件的引用賦值給子View的mParent成員變數, //此時子View持有當前ViewGroup的引用,ViewTree中由子View指向ViewGroup構建完畢 if (preventRequestLayout) { child.assignParent(this); } else { child.mParent = this; } ... }
上述程式碼中重點部分我已經做了標註,至此ViewTree就構建完畢了。
相信大家看到這裡,對於我文章開始提到的幾個問題就可以有自己的理解了,對於問題1,因為ViewTree在構建的過程中,每一個View物件的建立都伴隨著一個具體LayoutParams物件的建立,而這個具體的LayoutParams物件正是View對應ViewGroup中的LayoutParams,最後呼叫view物件的setLayoutParams方法,為當前View物件的mLayoutParams成員變數賦值,而我們在程式碼中呼叫到view的getLayoutParams方法,該方法返回的正是mLayoutParams的值,所以自然需要強轉成當前View物件對應ViewGroup中的LayoutParams物件啦。對於問題2和3,因為LayoutParams是用來描述子View和對應ViewGroup之間的關係,並沒有提供Padding變數,所以通過LayoutParams當然不能設定View的Padding了。LayoutParams和Padding之間的關係可以簡單理解為“並列”關係,二者都是作為View物件的成員變數來存在。