JohnTube
JohnTube

Reputation: 1812

When are child views added to Layout/ViewGroup from XML

My question is : I want to know when does a xLayout (or ViewGroup in general) add a child view from XML ? And by "when" I mean at what point of code, in what "pass" of the "traversal" of the UI toolkit ? Which method of xLayout or ViewGroup should I override ?

I have done my homework : I have watched the "Writing Custom Views For Android" presented (by Adam Powell and Romain Guy) in the last Google I/O and I have read Adam Powell comments on this Google+ post.

Upvotes: 14

Views: 5621

Answers (2)

Vikram
Vikram

Reputation: 51581

Looking for the exact point in Android's source code where children are added.

We can look at what setContentView(R.layout.some_id) is doing under the hood.

setContentView(int) calls PhoneWindow#setContentView(int) - PhoneWindowLink is a concrete inplementation of 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();
    }
}

The method LayoutInflater#inflate(layoutResID, mContentParent) eventually calls ViewGroup#addView(View, LayoutParams) on mContentParent. In between, child views

I want to know what happens exactly after I set content view to an XML file that contains a custom view. Afer the constructor there has to be a part in the code where the custom view "parse/read/inflate/convert" XML-declared child views to actual views ! (comment by JohnTube)

Ambiquity: From JohnTube's comment, it seems he is more interested in knowing how a custom view is inflated. To know this, we will have to look at the workings of LayoutInflaterLink.

So, the answer to Which method of xLayout or ViewGroup should I override ? is ViewGroup#addView(View, LayoutParams). Note that, at this point, the inflation of all regular/custom Views has already taken place.

Inflation of custom views:

The following method in LayoutInflater is where the addView(View, LayoutParams) is called on the parent/root:

Note: The call mLayoutInflater.inflate(layoutResID, mContentParent); in PhoneWindow#setContentView(int) chains to this. Here mContentParent is the DecorView: the view that's accessible through 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

The call of interest in this method(and in the recursive rInflate(XmlPullParser, View, AttributeSet, boolean)) is:

temp = createViewFromTag(root, name, attrs);

Let's see what createViewFromTag(...) is doing:

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);
        }
    }
    ....
}

The period(.) decides whether onCreateView(...) or createView(...) is called.

Why this check? Because a View defined in android.view, android.widget or android.webkit package is accessed through its class name. For example:

android.widget: Button, TextView etc.

android.view: ViewStub. SurfaceView, TextureView etc.

android.webkit: WebView

When these views are encountered, onCreateView(parent, name, attrs) is called. This method actually chains to createView(...):

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

This would deal with SurfaceView, TextureView and other views defined in android.view package. If you are interested in knowing how TextView, Button etc. are dealt with, look at PhoneLayoutInflaterLink - it extends LayoutInflater and overrides onCreateView(...) to check if android.widget and android.webkit are the intended package names. In fact, the call getLayoutInflater() gets you an instance of PhoneLayoutInflater. This is why if you were to subclass LayoutInflater, you couldn't even inflate the simplest of layouts - because LayoutInflater can only deal with views from android.view package.

Anyway, I digress. This extra bit happens for regular Views - which don't have a period(.) in their definition. Custom views do have a period in their names - com.my.package.CustomView. This is how the LayoutInflater distinguishes between the two.

So, in case of a regular view(say, Button), a prefix such as android.widget will be passed as the second argument - for custom views, this will be null. The prefix is then used along with the name to obtain the constructor for that particular view's class. Custom views don't need this because their name is already fully qualified. I guess this has been done for convenience. Else, you would have been defining your layouts in this way:

<android.widget.LinearLayout
    ...
    ... />  

(Its legal though...)

Also, this is why views coming from a support library (eg. <android.support.v4.widget.DrawerLayout.../>) have to use fully qualified names.

By the way, if you did want to write your layouts as:

<MyCustomView ../>

all you have to do is to extend LayoutInflater and add your package name com.my.package. to the list of strings that are checked during inflation. Check PhoneLayoutInflater for help with this.

Let's see what happens in the final stage for both custom and regular views - 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`.
}

... a View is born.

Upvotes: 14

Ted Hopp
Ted Hopp

Reputation: 234847

If you're talking about a ViewGroup defined in XML, it's children are added when the view is inflated. This can be when you inflate explicitly with a LayoutInflater or when you set the content view of an activity. (There are probably a few other times as well, particularly if you are using stub views.)

If you want to add the children yourself to a ViewGroup that is not inflated, you can do that in the view's constructor.

EDIT: If you want to see how the children are added when a view is inflated, this occurs in the call to LayoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot). The source for android.view.LayoutInflater is included in the Android SDK distributions; on-line versions can be found in many places (here at GrepCode, for instance). This method ends up being called when, for instance, you call setContentView(int) for an Activity or when you explicitly inflate a layout resource.

The children are actually added in the call to rInflate(parser, root, attrs, false); ("recursive inflate"), which might be called from a couple of different places in the inflate() method, depending on what the inflater found as the root tag. You can trace through the code logic yourself. An interesting point is that a child is not added to its parent until its own children have been recursively inflated and added to it.

The other interesting method, used by both inflate and rInflate, is createViewFromTag. This might rely on an installable LayoutInflater.Factory (or .Factory2 object) to create the view, or may end up calling createView. There you can see how the call to the view's two-argument constructor ((Context context, AttributeSet attrs)) is made.

Upvotes: 8

Related Questions