Android源码解析 --- LayoutParams以及Vi
在分析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对象的成员变量来存在。