Macadamian Blog
Android - Custom classes from XML layout
If you are relatively new to Android, I highly recommend you check out our guide: Your First Android Release - It Could Go Well (Or Really, Really Badly)
In Android, you can use a list view and define the layout for items in the list. You can even use different layouts based on item type, each with different heights and content.
For example, ListView uses a subclass of BaseAdapter to give it information about the number of items and to get the actual views for each item. In our case, what interests us is the adapter’s getView method. With a list with only one type of cell, we would have something that looks like this:
public View getView(int position, View view, ViewGroup vg)
{
if (view == null)
{
// context would be a variable set via the constructor and given
// by our owner (most likely a ListActivity)
LayoutInflater inflater = context.getLayoutInflater();
view = inflater.inflate(R.layout.custom_view, null);
}
// Here you would get the sub-views of your layout and fill them
TextView title = (TextView) view.findViewById(R.id.TitleView);
Title.setText(“My title”);
return myView;
}
As you can imagine, the more complex the layout, the uglier this function becomes. It gets worse when you have different cell types. What we want is to define your own view classes, which would be subclasses of TableLayout, RelativeLayout and so on.
The problem is that the LayoutInflater.inflate method only creates views of the Android SDK built-in classes, like TextView, TableLayout, ImageView, etc. Or so I thought…
Enter the LayoutInflater.Factory class.
The way we’ve been implementing our lists with relatively complex getView methods left me with a bad taste. When I stumbled upon the LayoutInflater.Factory class, I knew that’s exactly what I needed.
In a nutshell, in your activity (which is also a Context), you can add a factory for inflating XML layouts into objects.
The LayoutInflater.Factory interface defines the onCreateView method, which is called for every item in the XML layout. It gives us the name of the item (as in TableLayout, ImageView, etc), the context and an attribute set. This is where we would create custom UI objects to return to Android,which will return that same object to our “inflate”call. If we return null, then Android will create the object it normally does.
Even with that, there are several ways things could go wrong:
Wrong Way #1
When you define your layout you could replace the top-level XML item with a custom name. So instead of having something like this:
<TableLayout>
<!—Your layout content --?
</TableLayout>
You would have something like:
<MyCustomLayout>
<!—Your layout content --?
</MyCustomLayout >
Then, in onCreateView you could compare the name parameter with “MyCustomLayout” and construct your own object.
Why is this wrong? Well, it will work, but you cannot easily use the layout UI from the Eclipse plug-in. It will either show you a monolithic single item of your content, which you can only edit in the XML view of the layout, or it will give you a ClassCastException .
Wrong Way #2
This one is a very wrong tangent I took before I realized the errors of my ways. Basically, the AttributeSet of the onCreateView method is actually a XML pull parser, which tells us the depth of the current element.
We implemented our layouts in such a way that the main view is the only one at a depth of 1. Also, we had custom view only for top-level elements. Therefore, in our onCreateView method, we compared the depth of the current element to 1 and created our own custom object.
Why is this wrong? Well, again it worked but:
It does not permit you to inflate different types of layout if you have multiple list item formats.
It makes it hard to support custom objects INSIDE our main layout.
The Right Way (IMHO)
The best way to create the right custom object at the right time is to use the AttributeSet parameter of the onCreateView method. Whatever attributes you set in your layout XML, you can access through the AttributeSet parameter.
Even better, you can get an attribute as a resource identifier (an integer) to compare with the actual ID you expect. To do so, you can call getidAttributeResourceValue on the attribute set you get in onCreateView.
For example, if you have a custom layout defined as follows
<TableLayout
id=”@+id/MyCustomLayout”>
<!—Your layout content --?
</TableLayout>
Then in your layout inflater factory you can do something like this:
private class ClassInflater implements Factory
{
public View onCreateView(String name,
Context context,
AttributeSet attrs)
{
int resourceId = attrs.getIdAttributeResourceValue(0);
if (resourceId == R.id.MyCustomLayout)
{
Return new MyCustomLayout(context, attrs);
}
return null;
}
}
You might have noticed that the in the layout, I used “id” and not “android:id”. For some reason, when I used “android:id”, the call to getIdAttributeResourceValue always returned 0 instead of the expected value.
With that in mind, you can now create great looking layout, create classes that sub-class the base type of layout and pull those sub-classes from the layout inflater.
+ Comments
This is good stuff of reading. Pegasys is a leading software development company offering Offshore Software Development Services & solutions with their vast experience and expertise in Application Development, Web Development, E-Strategy Consulting, Ecommerce solutions, Web Application development, Multimedia and Design Solutions, Wireless Technologies and so on. Companies are becoming software dependent, developing & capitalizing on strong software capabilities. Thanking you. Custom Software Development
Jun 29, 2010
07:34
What about like this..
resource:
<com.foo.bar.MyLinearLayout>
android:blahblah
</com.foo.bar.MyLinearLayout>
java:
package com.foo.bar;
class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
or I believe also that you can attach an inflate to a compatible subclass by using the third parameter of inflate().
MyLinearLayout layout = new MyLinearLayout(context);
layout = (MyLinearLayout)inflate(id, layout, true);
Jul 07, 2010
04:28
Till this momrnt this part was inunerstandable for me:
class MyLinearLayout extends LinearLayout { public MyLinearLayout(Context context) { super(context);
But now it's ok, thanks to you I'm busy with my coursework writing and this article was like a treasure for me, thanks a lot.
Jul 08, 2010
03:25
The fundamental idea you present here is a good one - it's a way of allowing custom classes while still allowing the resource editor to show a preview of your view with some accuracy. However I would suggest not using the "id" attribute as that is supposed to be unique for a given element within an entire XML file. This is not ideal for this situation where you might have multiple elements of the same type that you want to instantiate.
Try the following instead. In the XML file, define a namespace (this can be more or less any old rubbish; it doesn't have to be a real schema or even a real URL) and some custom tags, e.g.:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:my="http://schemas.mydomain.com/res/android" ...>
...
<TextView my:subclass="MyTextView" ...></TextView>
<TextView my:subclass="MyTextView" ...></TextView>
...
</LinearLayout>
Then in your code you define your custom layout factory as:
public static final String NAMESPACE =
"http://schemas.mydomain.com/res/android";
public static final String SUBCLASS = "subclass";
public static final String MYTEXTVIEW = "MyTextView";
public View onCreateView( String name,
Context context, AttributeSet attrs )
{
String subClass = attrs.getAttributeValue(
NAMESPACE, SUBCLASS );
View newView;
if (MYTEXTVIEW.equals( subClass ))
newView = new MyTextView( context, attrs );
else
newView = null;
return newView;
}
Note that attrs gets passed to the constructor of your class, which means you can define any other arbitrary attributes you like on your tag and then read them in your class's constructor to control its behaviour. E.g. if MyTextView prints each letter in the view in an alternate colour, you could specify:
<TextView my:subclass="MyTextView'
android:textColor="@color/text"
my:alternateColor="@color/alternate" ...>
and then read the alternateColor attribute in your constructor.
Here's a preview of your comment:
Sep 28, 2009
07:22
Thanks a lot! Very informative blog, Seb!