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



我的问题是:我想知道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的评论来看,他似乎更感兴趣的是了解自定义视图是如何膨胀的。要知道这一点,我们必须了解LayoutInflaterLink的工作原理。

因此,Which method of xLayout or ViewGroup should I override ?的答案是ViewGroup#addView(View, LayoutParams)。请注意,在这一点上,所有常规/自定义视图的膨胀已经发生。

自定义视图的膨胀:

LayoutInflater中的以下方法是在父/根上调用addView(View, LayoutParams)的方法:

注意:PhoneWindow#setContentView(int)中的调用mLayoutInflater.inflate(layoutResID, mContentParent);链接到此。这里mContentParentDecorView:可以通过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.viewandroid.widgetandroid.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);
}

这将处理SurfaceViewTextureViewandroid.view包中定义的其他视图。如果您有兴趣了解如何处理TextView, Button etc.,请查看PhoneLayoutInflaterLink—它扩展了LayoutInflater并覆盖了onCreateView(...),以检查android.widgetandroid.webkit是否为预期的包名称。事实上,调用getLayoutInflater()会得到一个PhoneLayoutInflater的实例。这就是为什么如果您要对LayoutInflater进行子类化,您甚至无法扩展最简单的布局——因为LayoutInflater只能处理android.view包中的视图

不管怎样,我离题了。这个额外的位发生在常规视图中——它们的定义中没有period(.)。自定义视图的名称中有一个句点-com.my.package.CustomView这就是LayoutInflater区分两者的方式

因此,在常规视图(例如Button)的情况下,将传递prefix(如android.widget)作为第二个参数——对于自定义视图,这将是null。然后将prefixname一起使用以获得该特定视图的类的构造函数。自定义视图不需要这样做,因为它们的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()方法中的几个不同位置调用该子项。您可以自己通过代码逻辑进行跟踪。一个有趣的点是,直到它自己的子对象被递归地膨胀并添加到它,子对象才被添加到它的父对象中

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

最新更新