我的问题是:我想知道xLayout(或ViewGroup)什么时候从XML添加子视图?我所说的"何时"是指在代码的哪个点,在UI工具包的"遍历"的哪个"过程"中?我应该覆盖xLayout或ViewGroup的哪种方法?
我已经做了功课:我在上一次谷歌I/O中观看了Adam Powell和Romain Guy提出的"为Android编写自定义视图",我也阅读了Adam鲍威尔在这篇Google+帖子上的评论。
在Android的源代码中查找添加儿童的确切位置。
我们可以看看setContentView(R.layout.some_id)
在幕后做什么。
setContentView(int)
调用PhoneWindow#setContentView(int)
-PhoneWindow
链路是Window
:的具体实现
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
方法LayoutInflater#inflate(layoutResID, mContentParent)
最终在mContentParent
上调用ViewGroup#addView(View, LayoutParams)
。在此期间,子级查看
我想知道在我将内容视图设置为包含自定义视图的XML文件之后会发生什么。在构造函数之后,代码中必须有一部分是自定义视图"解析/读取/膨胀/转换"XML声明的子视图到实际视图的!(JohnTube评论)
模棱两可:从JohnTube的评论来看,他似乎更感兴趣的是了解自定义视图是如何膨胀的。要知道这一点,我们必须了解LayoutInflater
Link的工作原理。
因此,Which method of xLayout or ViewGroup should I override ?
的答案是ViewGroup#addView(View, LayoutParams)
。请注意,在这一点上,所有常规/自定义视图的膨胀已经发生。
自定义视图的膨胀:
LayoutInflater
中的以下方法是在父/根上调用addView(View, LayoutParams)
的方法:
注意:PhoneWindow#setContentView(int)
中的调用mLayoutInflater.inflate(layoutResID, mContentParent);
链接到此。这里mContentParent
是DecorView
:可以通过getWindow().getDecorView()
访问的视图。
// Inflate a new view hierarchy from the specified XML node.
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
// Recursive method used to descend down the xml hierarchy and instantiate views,
// instantiate their children, and then call onFinishInflate().
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException
该方法(以及递归rInflate(XmlPullParser, View, AttributeSet, boolean)
)中感兴趣的调用是:
temp = createViewFromTag(root, name, attrs);
让我们看看createViewFromTag(...)
在做什么:
View createViewFromTag(View parent, String name, AttributeSet attrs) {
....
....
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
}
....
}
period(.)
决定调用onCreateView(...)
还是createView(...)
为什么要检查?因为在android.view
、android.widget
或android.webkit
包中定义的View
是通过其类名访问的。例如:
android.widget: Button, TextView etc.
android.view: ViewStub. SurfaceView, TextureView etc.
android.webkit: WebView
当遇到这些视图时,会调用onCreateView(parent, name, attrs)
。此方法实际上链接到createView(...)
:
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
这将处理SurfaceView
、TextureView
和android.view
包中定义的其他视图。如果您有兴趣了解如何处理TextView, Button etc.
,请查看PhoneLayoutInflater
Link—它扩展了LayoutInflater
并覆盖了onCreateView(...)
,以检查android.widget
和android.webkit
是否为预期的包名称。事实上,调用getLayoutInflater()
会得到一个PhoneLayoutInflater
的实例。这就是为什么如果您要对LayoutInflater
进行子类化,您甚至无法扩展最简单的布局——因为LayoutInflater
只能处理android.view
包中的视图。
不管怎样,我离题了。这个额外的位发生在常规视图中——它们的定义中没有period(.)
。自定义视图的名称中有一个句点-com.my.package.CustomView
这就是LayoutInflater
区分两者的方式。
因此,在常规视图(例如Button)的情况下,将传递prefix
(如android.widget
)作为第二个参数——对于自定义视图,这将是null
。然后将prefix
与name
一起使用以获得该特定视图的类的构造函数。自定义视图不需要这样做,因为它们的name
已经完全限定。我想这样做是为了方便。否则,您将以这种方式定义布局:
<android.widget.LinearLayout
...
... />
(虽然是合法的…)
此外,这就是为什么来自支持库(例如<android.support.v4.widget.DrawerLayout…/>)的视图必须使用完全限定的名称。
顺便说一句,如果你真的想把你的布局写为:
<MyCustomView ../>
您所要做的就是扩展Layout充气器,并将您的包名称com.my.package.
添加到充气期间检查的字符串列表中。有关此方面的帮助,请查看PhoneLayoutInflater
。
让我们看看定制视图和常规视图的最后阶段会发生什么-createView(...)
:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// Try looking for the constructor in cache
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
....
// Get constructor
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor);
} else {
....
}
Object[] args = mConstructorArgs;
args[1] = attrs;
// Obtain an instance
final View view = constructor.newInstance(args);
....
// We finally have a view!
return view;
}
// A bunch of catch blocks:
- if the only constructor defined is `CustomView(Context)` - NoSuchMethodException
- if `com.my.package.CustomView` doesn't extend View - ClassCastException
- if `com.my.package.CustomView` is not found - ClassNotFoundException
// All these catch blocks throw the often seen `InflateException`.
}
一个CCD_ 56诞生了。
如果您谈论的是用XML定义的ViewGroup,则在视图膨胀时会添加它的子级。这可能是在使用LayoutInflater
显式膨胀时,也可能是在设置活动的内容视图时。(可能还有其他一些时间,特别是如果您使用存根视图。)
如果您想自己将子项添加到未膨胀的ViewGroup
中,可以在视图的构造函数中执行此操作。
编辑:如果您想查看在视图膨胀时如何添加子项,则会在对LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
的调用中出现这种情况。android.view.LayoutInflater
的源代码包含在Android SDK发行版中;在线版本可以在很多地方找到(例如,在GrepCode)。例如,当为Activity
调用setContentView(int)
时,或者当显式扩展布局资源时,会调用此方法。
实际上,在对rInflate(parser, root, attrs, false);
("递归膨胀")的调用中添加了子项,根据膨胀程序作为根标记找到的内容,可能会从inflate()
方法中的几个不同位置调用该子项。您可以自己通过代码逻辑进行跟踪。一个有趣的点是,直到它自己的子对象被递归地膨胀并添加到它,子对象才被添加到它的父对象中
inflate
和rInflate
使用的另一种有趣的方法是createViewFromTag
。这可能依赖于可安装的LayoutInflater.Factory
(或.Factory2
对象)来创建视图,或者可能最终调用createView
。在那里,您可以看到如何调用视图的双参数构造函数((Context context, AttributeSet attrs)
)。