我的问题是: 我想知道xLayout(或一般的ViewGroup)何时从XML添加子视图?并且当"当"我的意思是在什么时候代码,在"传递" "遍历" UI工具包? 我应该覆盖xLayout或ViewGroup的哪种方法?
我完成了我的作业:我在上一次Google I / O中观看了"Writing Custom Views For Android"(由Adam Powell和Romain Guy提供),我在此Google+ post上阅读了Adam Powell的评论。
答案 0 :(得分:12)
在Android的源代码中寻找添加子项的确切位置。
我们可以看看setContentView(R.layout.some_id)
正在做什么。
setContentView(int)
来电PhoneWindow#setContentView(int)
- PhoneWindow
Link是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)
最终会在ViewGroup#addView(View, LayoutParams)
上调用mContentParent
。在两者之间,儿童观点
我想知道在将内容视图设置为包含自定义视图的XML文件后会发生什么。在构造函数之后,必须在代码中有一个部分,其中自定义视图“解析/读取/膨胀/转换”XML声明的子视图到实际视图! (JohnTube评论)
Ambiquity:从JohnTube的评论来看,他似乎更想知道自定义视图是如何膨胀的。要了解这一点,我们必须查看LayoutInflater
Link的工作原理。
因此,Which method of xLayout or ViewGroup should I override ?
的答案是ViewGroup#addView(View, LayoutParams)
。请注意,此时,所有常规/自定义视图的膨胀已经发生。
自定义视图的通胀:
LayoutInflater
中的以下方法是在父/ root上调用addView(View, LayoutParams)
的地方:
注意:mLayoutInflater.inflate(layoutResID, mContentParent);
中的PhoneWindow#setContentView(int)
来电话链接到此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(...)
或View
。
为什么要检查?因为android.view
,android.widget
或android.webkit
包中定义的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
的方式,请查看LayoutInflater
Link - 它会onCreateView(...)
延伸并覆盖android.widget
以检查android.webkit
是否getLayoutInflater()
}和PhoneLayoutInflater
是预期的包名称。实际上,通话LayoutInflater
会为您提供LayoutInflater
的实例。这就是为什么如果你是android.view
的子类,你甚至无法夸大最简单的布局 - 因为period(.)
只能处理com.my.package.CustomView
包中的视图
无论如何,我离题了。这个额外的位发生在常规视图中 - 它们的定义中没有LayoutInflater
。自定义视图执行的名称中包含句点 - prefix
。 这是android.widget
区分两个的方式。
因此,如果是常规视图(例如,按钮),null
(例如prefix
)将作为第二个参数传递 - 对于自定义视图,这将是name
。然后name
与<android.widget.LinearLayout
...
... />
一起使用以获取该特定视图类的构造函数。自定义视图不需要这样,因为他们的<MyCustomView ../>
已经完全限定。我想这是为了方便起见。否则,您将以这种方式定义布局:
com.my.package.
(虽然合法......)
此外,这就是来自支持库(例如&lt; android.support.v4.widget.DrawerLayout ... /&gt;)的视图必须使用完全限定名称的原因。
顺便说一下,如果你确实想把你的布局写成:
PhoneLayoutInflater
您所要做的就是扩展LayoutInflater并将包名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`.
}
以获取相关帮助。
让我们看看自定义视图和常规视图在最后阶段会发生什么 - View
:
{{1}}
...... {{1}}诞生了。
答案 1 :(得分:7)
如果您正在讨论以XML格式定义的ViewGroup,则会在视图膨胀时添加子项。当您使用LayoutInflater
明确夸大或设置活动的内容视图时,可以使用此功能。 (可能还有其他一些时间,特别是如果您使用存根视图。)
如果您想自己将孩子添加到未充气的ViewGroup
,您可以在视图的构造函数中执行此操作。
编辑:如果您想查看视图膨胀时如何添加子项,则会在调用LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
时发生这种情况。 android.view.LayoutInflater
的来源包含在Android SDK发行版中;可以在许多地方找到在线版本(例如here at GrepCode)。例如,当您为setContentView(int)
调用Activity
或明确夸大布局资源时,最终会调用此方法。
这些孩子实际上是在rInflate(parser, root, attrs, false);
(“递归膨胀”)的调用中添加的,可能会在inflate()
方法的几个不同位置调用,具体取决于inflater发现的内容根标签。您可以自己跟踪代码逻辑。一个有趣的观点是,在孩子被递归膨胀并添加到孩子之前,孩子不会被添加到其父母身上。
inflate
和rInflate
使用的另一种有趣的方法是createViewFromTag
。这可能依赖于可安装的LayoutInflater.Factory
(或.Factory2
对象)来创建视图,或者最终可能会调用createView
。在那里你可以看到如何调用视图的双参数构造函数((Context context, AttributeSet attrs)
)。