何时将子视图从XML添加到Layout / ViewGroup

时间:2013-06-27 03:32:10

标签: android android-layout android-ui

我的问题是: 我想知道xLayout(或一般的ViewGroup)何时从XML添加子视图?并且当"当"我的意思是在什么时候代码,在"传递" "遍历" UI工具包? 我应该覆盖xLayout或ViewGroup的哪种方法?

我完成了我的作业:我在上一次Google I / O中观看了"Writing Custom Views For Android"(由Adam Powell和Romain Guy提供),我在此Google+ post上阅读了Adam Powell的评论。

2 个答案:

答案 0 :(得分:12)

  

在Android的源代码中寻找添加子项的确切位置。

我们可以看看setContentView(R.layout.some_id)正在做什么。

setContentView(int)来电PhoneWindow#setContentView(int) - PhoneWindow LinkWindow的具体实施:

@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。此处DecorViewgetWindow().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.viewandroid.widgetandroid.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

这将处理TextureViewandroid.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发现的内容根标签。您可以自己跟踪代码逻辑。一个有趣的观点是,在孩子被递归膨胀并添加到孩子之前,孩子不会被添加到其父母身上。

inflaterInflate使用的另一种有趣的方法是createViewFromTag。这可能依赖于可安装的LayoutInflater.Factory(或.Factory2对象)来创建视图,或者最终可能会调用createView。在那里你可以看到如何调用视图的双参数构造函数((Context context, AttributeSet attrs))。