Reusing Custom Views in The Android API

Have you ever wanted to turn a long list of Android user-interface elements into a single XML tag, ready to drop into any other layout? If you find yourself reusing the same Views over and over in your Android layouts, then consider creating a custom View with these elements, and then defining a custom XML tag for it, so you can just drop it wherever you need. Android does provide a way to do this. It is possible to add custom Views to your layouts in XML as a single tag. You can also write your own custom XML attributes for them, so that the configuration of the View is obvious from the XML, and you can even see them visually while you work in the Eclipse Android tools’ “Graphical Layout” XML editor.

The IconListItemView

IconListItemView

The IconListItemView

A user interface element that I often find myself reusing is the IconListItemView in the image above. The IconListItemView has a small ImageView icon to the left, a large text title to the right of that, a smaller text below the title, and a horizontal rule at the bottom. Normally, that would be a RelativeLayout View with those four Views inside it. Since I use this often, many Activities would have their layouts littered with this, and each of these Activities would also be stuck with the code that implements the functionality of them. Wouldn’t it be better to have all of that end up as just one tag in XML, like this?

<com.company.views.IconListItemView
                        android:id="@+id/edit_facebook"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="10dp"
                        app:title="Publish to Facebook"
                        app:text="Publish this to Facebook."
                        app:icon="@drawable/facebook_icon"
                        />

 

Since these parts are so often used, it is much better to have them all in this single tag that has the attributes for the View right there for you to see in XML. I can even place these into other custom Views, creating a hierarchy of custom Views, saving myself time and making cleaner code. I have custom tags in my apps for “titlebar” Views, “footer” Views, and even for special “menu” Views, which contain many of these custom IconListItemView Views. Each time I need a custom View, such as the IconListItemView, I only use this custom tag, making my projects much more maintainable. This also separates the concerns of the IconListItemView (such as setting the title, text, and icon) from the concerns of whichever Activity holds it, greatly cleaning up the Activity code. Imagine making a custom View that can have whatever options you can think of set through XML attributes, and not through messy code in the Activity.

The “Merge” Element

The layout of the View should be defined in XML, for maintainability, but since this XML defines a View and not an Activity, it would be best not to have a root-level view, such as a RelativeLayout or a LinearLayout. The View will later infer a root-level View based on whichever View it will inherit in the Java implementation, and then if there’s a root-level View in the XML layout, it will put that View from the XML into the other root View. Android will go through these steps, and render these Views, even though they’re empty. It’s wasteful.

To avoid nesting unnecessary Views, there will instead be no root-level view in the layout. Instead, the “merge” tag is used as the Views’ containing element. So, to define this custom View, create a layout XML titled “icon_list_view.xml” that has this definition:

<merge
    xmlns:android="http://schemas.android.com/apk/res/android">

               
        <ImageView
                android:id="@+id/icon"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_margin="2dp"
                android:scaleType="fitXY"
                android:layout_alignParentLeft="true" />

       
        <TextView
                android:id="@+id/li_title"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Title"
                android:paddingLeft="15dp"
                android:paddingTop="5dp"
                android:layout_toRightOf="@+id/icon" />

               
        <TextView
                android:id="@+id/li_text"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text="Text"
                android:paddingLeft="15dp"
                android:paddingBottom="5dp"
                android:layout_below="@+id/li_title"
                android:layout_toRightOf="@+id/icon"  />

       
        <View
                android:layout_width="fill_parent"
                android:layout_height="1dp"
                android:padding="1dp"
                android:background="#888888"
                android:layout_below="@+id/li_text" />

</merge>
 

 

Within the “merge” tag, we have the four Views of the IconListItemView: an ImageView, two TextViews, and finally a View that acts as a horizontal rule (in a later article, I will explain how to use Android’s vector graphics capability to draw a horizontal rule by defining a raw Drawable). The ImageView has no image set, and the TextViews have only a generic “Title” and “Text” strings set in them as defaults.

If you’re using Eclipse with the Android tools, your “Graphical Layout”, will look as though the Views are all bunched up in the corner. This is because it has no root-level view, so it can’t know how to place these. This is fine for now. When you place the View into other Activities later, it will show up correctly. Save the file.

Java Implementation

Create this View’s Java class: “IconListItemView” in whichever package you have set aside for these Views. For this article, we’ll use “com.company.views”. You may have noticed RelativeLayout attributes in the layout’s XML. This is because this custom View will inherit from RelativeLayout, and the superclass’ implementation will provide the layout functionality that the “Graphical Layout” lacked before.

public class IconListItemView extends RelativeLayout {

        public IconListItemView(Context context, AttributeSet attrs) {
                super(context, attrs);

                //Set this view to the layout defined in the xml layout
                LayoutInflater layoutInflater = (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                layoutInflater.inflate(R.layout.icon_list_item_view, this);

        }
}

In the constructor, we get the LayoutInflator from the Context, and use it to inflate the icon_list_item_view.xml. The View is now a type of RelativeLayout, and RelativeLayout will now be its root-level View, and the layout attributes in the child Views will now make sense.

We can now use and see this custom View in other layouts, by simply doing this:

<com.company.views.IconListItemView
                        android:id="@+id/edit_facebook"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="10dp"
                        />

 

Though, the icon will be blank, and the title and the text will show up as “Title” and “Text”, since we haven’t implemented a way to specify these.

Custom Attributes

There are two simple steps to creating custom attributes: adding their definitions to the attr.xml file inside the “res/values” folder, and then pointing to your own app as a DTD within any XML layouts that will use these attributes.

Defining the Attributes

Navigate to the res/values folder, and if attr.xml doesn’t yet exist, create it:

<?xml version="1.0" encoding="utf-8"?>

<resources>
        <declare-styleable name="IconListItemView">
                <attr name="icon" format="reference" />
                <attr name="title" format="string" />
                <attr name="text" format="string" />
        </declare-styleable>
</resources>
 

Elements within the “declare-styleable” element will be used to specify options for your custom View. Each custom attribute has a name and a format. Here, the “reference” format for the icon will accept a value such as “@drawable/some_drawable”, just like an ImageView does, and the “string” format accepts literal strings. Save this file, and then return to your Java class.

In the IconListItemView class, add this block to the constructor, after the LayoutInflator is used:

if(attrs != null){
        TypedArray a=getContext().obtainStyledAttributes(attrs,R.styleable.IconListItemView);

        ImageView icon = (ImageView)this.findViewById(R.id.icon);
        TextView title  = (TextView)this.findViewById(R.id.li_title);
        TextView text   = (TextView)this.findViewById(R.id.li_text);

        title.setText(a.getString(R.styleable.IconListItemView_title));
        text.setText(a.getString(R.styleable.IconListItemView_text));

        if(a.hasValue(R.styleable.IconListItemView_icon)){
                icon.setImageDrawable(a.getDrawable(R.styleable.IconListItemView_icon));
        }
}
 

First, we make sure that attributes were passed to the constructor, and if so, we get them in a TypedArray from the Context. Then, we fetch and hold the three important Views: icon, title, and text from the layout that we previously inflated, so we can set these based on the attributes we have. We set the title and the text to be the raw values of the title and text stylables by referring to the auto-generated constant values R.styleable.IconListItemView_title and R.styleable.IconListItemView_text. If they weren’t set in the tag’s attributes, they’ll just show up as empty strings, and that’s fine. After that, we first make sure that an icon was specified, and if so, we set the icon’s Drawable to the specified value.

Now, we can use these custom attributes in XML! All we have to do is specify that we’re using our own DTD at the top of any layout that uses them. For instance, if we are using this custom View in an activity that has a RelativeLayout as its root-level View, we put this into the root element:

xmlns:app="http://schemas.android.com/apk/res/com.company.app"
 

Where “com.company.app” is the package name of your app in your Manifest. This is what it might look like:

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.company.app"
       
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

 

Now, in the custom View’s element, use your custom attributes, but prefix them with “app:” instead of “android:”, like this:

<com.company.views.IconListItemView
                        android:id="@+id/edit_facebook"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="10dp"
                        app:title="Publish to Facebook"
                        app:text="Publish this to Facebook."
                        app:icon="@drawable/facebook_icon"
                        />

 

You should be able to immediately see the custom View in the “Graphical Layout” section of Eclipse. The Activity instantiates the IconListItemView when its layout is inflated, which then inflates the icon_list_item_view.xml file as its layout. From there, it checks for the stylables that you defined in attr.xml for the custom attributes, and uses those values to specify the title, text, and icon image.

Using this method, you can write a custom View once, and then use it as many times as you like, rather than writing this functionality into every single Activity for each instance of this group of elements. This method can save you time, but most importantly, it can simplify your code, making it more maintainable, easier to read, and friendlier to your team members.

5 thoughts on “Reusing Custom Views in The Android API