Another Android Blog

Insights into those hard to solve Android Development problems

Skip to: Content | Sidebar | Footer

Custom Composite Android Component

26 May, 2011 (20:58) | Components | By: Randall Mitchell

This example puts a couple of TextViews into a LinearLayout. The combination of components is then treated as one component with the added functionality of programmatically changing the text of the two TextViews. I pretty much modified this off of something I learned and forgot. It took me a good bit of digging to find it again. Check maohao.com for the original source/alternate version. I’m putting it here so I don’t loose it (again). I’m sure it’s somewhere on developer.google.com but I just couldn’t find a specific example not bogged down by other code (i.e., ListView). Also, I’m not even sure this works well for ListView. That said, it does have practical applications. For example, a list that will always be small. For further explanation, please visit maohao at:

http://maohao.wordpress.com/2009/08/27/building-mix-up-custom-component-android/

Here is a working example of my version. The first code below,two_text_components.xml, has root LinearLayout and two TextView components with id text_one and text_two. This is the XML layout of the the composite component we want to create.

two_text_components.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:orientation="horizontal"
	>
	<TextView
		android:id="@+id/text_one"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_weight="1"
		></TextView>
	<TextView
		android:id="@+id/text_two"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_weight="9"
		></TextView>
</LinearLayout>

We are going to programmatically set the text for text_one and text_two. In the Maohao post, he specifies a namespace and is able to insert variables directly into the xml of the composite component – definitely worth taking a look at. The most important aspect of this example is connecting the XML to the Java. That’s done on a single line below that point’s the Activity’s layout inflator to the appropriate xml file. Also make sure most of your work is done in onFinishInflate() or later; otherwise, you’ll end up with null pointer exceptions. Take a look at the Java code.

TwoTextWidget.java

package com.anotherandroidblog.snippets.CustomCompositeViewExample.customcomponents;
 
import com.anotherandroidblog.snippets.CustomCompositeViewExample.R;
 
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
 
public class TwoTextWidget extends LinearLayout {
	private TextView textOne;
	private TextView textTwo;
 
	public TwoTextWidget(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
 
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		((Activity)getContext()).getLayoutInflater().inflate(R.layout.two_text_component, this);
		setupViewItems();
	}
 
	private void setupViewItems() {
		textOne = (TextView) findViewById(R.id.text_one);
		textTwo = (TextView) findViewById(R.id.text_two);
	}
 
	public void setTextOne(String text) {
		textOne.setText(text);
	}
 
	public void setTextTwo(String text) {
		textTwo.setText(text);
	}
}

Now let’s see how we add the new composite widget into the Activity’s XML. You must use the full package/class name of the new composite widget. You can use any of the standard LinearLayout properties in our composite widget (which extends from LinearLayout).

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/title"
    />
    <com.anotherandroidblog.snippets.CustomCompositeViewExample.customcomponents.TwoTextWidget
    	android:id="@+id/list_item_one"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:orientation="horizontal"
    	/>
    <com.anotherandroidblog.snippets.CustomCompositeViewExample.customcomponents.TwoTextWidget
    	android:id="@+id/list_item_two"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:orientation="horizontal"
    	/>
</LinearLayout>

Here is our last file. It is just the Activity class that runs the application. I’ve called the two methods created in the composite component for each instance we use them.

Home.java

package com.anotherandroidblog.snippets.CustomCompositeViewExample;
 
import com.anotherandroidblog.snippets.CustomCompositeViewExample.customcomponents.TwoTextWidget;
 
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
 
public class Home extends Activity {
	private TwoTextWidget itemOne;
	private TwoTextWidget itemTwo;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setupViews();
    }
 
    private void setupViews() {
    	itemOne = (TwoTextWidget) findViewById(R.id.list_item_one);
    	itemOne.setTextOne("1");
    	itemOne.setTextTwo("Hello");
 
    	itemTwo = (TwoTextWidget) findViewById(R.id.list_item_two);
    	itemTwo.setTextOne("2");
    	itemTwo.setTextTwo("Goodbye");
    }
}

Project Files

Here is the project file.

Comments

Comment from Randall Mitchell
Time May 27, 2011 at 2:57 am

After writing this post, I was quickly faced with a problem when trying to programmatically CREATE and add my new composite component to a layout object. I went a few places for the answer and ended up finding this: http://stackoverflow.com/questions/3705144/how-to-pass-attributeset-to-custom-view

… which is to not use:

layout.addView(customViewClass)

Instead use:

LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.yourcustomviewxml, layout);

It’s good information (which I also found later in a post by Romain Guy in response to a similar but different situation). If you look at this code, you will notice that it doesn’t actually make use of the custom class that extends LinearLayout. Making a change to the xml layout file of the custom component fixes this. Instead of having LinearLayout as root, we can instead use the custom component with its complete package name. For example:

<com.anotherandroidblog.snippets.CustomCompositeViewExample.customcomponents.TwoTextWidget>
...
</com.anotherandroidblog.snippets.CustomCompositeViewExample.customcomponents.TwoTextWidget>

Using this method, I can then move the code that talks to the inflater out of the custom widget class and into the activity class where I want to dynamically create the custom widget:

LayoutInflater inflater = LayoutInflater.from(this);
TwoTextWidget customWidget =
                (TwoTextWidget) inflater.inflate(R.layout.two_text_component, layout, false);
customWidget.setTextOne("This thing");
customWidget.setTextTwo("is complicated");
layout.addView(customWidget);

The R.layout.two_text_component will point the inflater to the proper layout file. The layout file will point the inflater to the new class.

From my reading inflate takes up to three parameters. In this instance, I am using the first parameter to point to the custom layout xml file. The second parameter tells the inflater to inflate the custom layout as if it were inside layout which will be its parent in the view hierarchy. This means the AttributeSet is properly set and any custom styling will be properly handled. false at the end tells the inflater to not place the layout inside of the desired layout (setting this to true will place the custom view into the layout. As best as I could tell (didn’t actually look at the source) using false allows us to adjust the custom layout afterword.

Hope this helps out,
Randall

Comment from Mitja
Time December 30, 2011 at 4:34 pm

Nice post. I was looking for good custom components tutorials. This one looks really promising. Thanks for sharing the code!

Best regards and keep it up,
Mitja

Comment from Stephen
Time February 22, 2012 at 4:52 pm

You completely rock. This is a great example. Thank you for posting it.