你需要知道的inlcude标签属性
有了解可以通过<include>
标签导入其他布局xml以实现服用的效果。但,一直不明白到底是怎么实现的。这篇就是对怎么实现的探索。
官网说明:
如果要使用
<include>
标记来替换布局属性,您必须同时替换android:layout_height
和android:layout_width
才能让其他布局属性生效。
为什么官网会这么说明,又是怎么实现呢?
入口
不管是从Activity还是Fragment设置布局,最后都会调用到LayoutInflater#inflate
方法。
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@linkInflateException} if there is an error.
*
* @paramresourceID for an XML layout resource to load (e.g.,
*<code>R.layout.main_page</code>)
* @paramrootOptional view to be the parent of the generated hierarchy (if
*<em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if<em>attachToRoot</em> is false.)
* @paramattachToRootWhether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
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) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
共有两处调用其他方法,分别是tryInflatePrecompiled
和inflate
。
tryInflatePrecompiled
方法中变量mUseCompiledView
为false时会return null。找到调用处只有一处可能为true,根据注释说明是用于单元测试,那么实际的方法就是inflate
了。
inflate内部先判断name
的值是否和TAG_NAME
相同:
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 {
...
}
找到TAG_MERGE的定义处,可以看到除了TAG_MERGE
还有*TAG_INCLUDE
,且其中抛出的异常是说<merge />
,大胆推测TAG_INCLUDE
*就是我们要找的<include>
的解析。
顺着可以找到用到*TAG_INCLUDE
*的位置:rInflate
方法。
...
else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
}
...
抛出异常的情况也是说<include />
标签,进一步证实,这就是我们要找的。那么parseInclude
方法肯定就是具体的解析方法了。
...
final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
(ViewGroup) parent, /*attachToRoot=*/true);
if (precompiled == null) {
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(getParserStateDescription(context, childAttrs)
+ ": No start tag found!");
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)) {
// The <merge> tag doesn't support android:theme, so
// nothing special to do here.
rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
// We try to load the layout params set in the <include /> tag.
// If the parent can't generate layout params (ex. missing width
// or height for the framework ViewGroups, though this is not
// necessarily true of all ViewGroups) then we expect it to throw
// a runtime exception.
// We catch this exception and set localParams accordingly: true
// means we successfully loaded layout params from the <include>
// tag, false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// Inflate all children.
rInflateChildren(childParser, view, childAttrs, true);
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
}
} finally {
childParser.close();
}
}
...
方法的参数有四个,分别是解析相关的parser
,Context
对象context
,父View
View
还有熟悉的AttributeSet
对象。只要有创建View的地方就有AttributeSet
对象存在,其作用是只有xml布局中的属性。
再看方法内部,开始首先调用tryInflatePrecompiled
,我们前面已经知道这是单元测试用到的,这里返回的肯定是null
,再往下看。
if (TAG_MERGE.equals(childName)) {
// The <merge> tag doesn't support android:theme, so
// nothing special to do here.
rInflate(childParser, parent, context, childAttrs, false);
} else {
...
}
这里还是*TAG_MERGE
,不管它。*
// else分支
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
// We try to load the layout params set in the <include /> tag.
// If the parent can't generate layout params (ex. missing width
// or height for the framework ViewGroups, though this is not
// necessarily true of all ViewGroups) then we expect it to throw
// a runtime exception.
// We catch this exception and set localParams accordingly: true
// means we successfully loaded layout params from the <include>
// tag, false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// Inflate all children.
rInflateChildren(childParser, view, childAttrs, true);
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
else分支上来就创建了个View,还把parent
转成了ViewGroup
,把它添加进去了,不用头想也知道这个就是include
标签中的布局了。
我们看到有个AttributeSet
参数childAttrs
,传入的要么是include标签的要么是被include的布局的。手写个demo,在被加载布局的构造方法打个断点,看到attrs
中的属性分明就是被include布局的。方法的参数attrs
肯定就是include
标签的属性了。
这里有个疑问,怎么实现include
标签中有layout_width
和layout_height
时,属性是inlcude
标签上的呢?
属性无非就两种来源,不是在xml布局中的设置的就是创建好View后设置的。
// We try to load the layout params set in the <include /> tag.
// If the parent can't generate layout params (ex. missing width
// or height for the framework ViewGroups, though this is not
// necessarily true of all ViewGroups) then we expect it to throw
// a runtime exception.
// We catch this exception and set localParams accordingly: true
// means we successfully loaded layout params from the <include>
// tag, false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
除了看到setLayoutParams
,注释也说的很清楚了。肯定就是我们所说的“创建好View后设置”的方法了。
/**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @paramattrsthe attributes to build the layout parameters from
*
* @return an instance of {@linkandroid.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
两个方法都是new一个新的LayoutParams
对象。子类重写方法,返回不同的类型。后面rInflateChildren
则是加载include
中的子View
和include
无关了。