Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 370
JohnTube Avatar asked Jun 27 '13 03:06

JohnTube


People also ask

Which class implements the layout properties of child views and holds them in an invisible container?

The ViewGroup is a subclass of View and provides invisible container that hold other Views or other ViewGroups and define their layout properties.

Why layouts are created using XML file?

Using Android's XML vocabulary, you can quickly design UI layouts and the screen elements they contain, in the same way you create web pages in HTML — with a series of nested elements. Each layout file must contain exactly one root element, which must be a View or ViewGroup object.

What is ViewGroup How are they different from views?

View is a simple rectangle box that responds to the user's actions. ViewGroup is a collection of Views(TextView, EditText, ListView, etc..), somewhat like a container. A View object is a component of the user interface (UI) like a button or a text box, and it's also called a widget.

What is the type of ViewGroup that helps you arrange the views inside of it in a flexible way?

The ViewGroup is the base class for Layouts in android, like LinearLayout , RelativeLayout , FrameLayout etc. In other words, ViewGroup is generally used to define the layout in which views(widgets) will be set/arranged/listed on the android screen.


2 Answers

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.

like image 174
Ted Hopp Avatar answered Nov 06 '22 19:11

Ted Hopp


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.

like image 22
Vikram Avatar answered Nov 06 '22 19:11

Vikram