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.
The ViewGroup is a subclass of View and provides invisible container that hold other Views or other ViewGroups and define their layout properties.
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.
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.
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.
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.
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)
- PhoneWindow
Link 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 LayoutInflater
Link.
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 PhoneLayoutInflater
Link - 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With