Android: How to write your own custom composite component

I’ve only been writing code for android for a short time and already my ui requirements got to such proportions, that my layout xml files seemed to explode. I hate copy-pasting the same stuff all over the place and so I spent a day to find out how to “not copy paste” my xml layouts. Instead I wanted a re-usable parametrized tag of my own. What good is xml, if we cannot have our own tags?

The Problem

Imagine you have half a dozen input values for a bunch of measures taken. For each input value you want a label, an input field and a second label showing the unit that is to be used.

[sourcecode language=”xml” wraplines=”true”]
<TextView android:id="@+id/label"
style="@style/LabelFont"
android:text="@string/label_fnord" />
<EditText android:id="@+id/fnord" style="@style/InputBox" />
<TextView android:id="@+id/label_length1"
style="@style/UnitFont"
android:text="@string/metric_length" />
[/sourcecode]

So you got this nice xml which is already using styles to reduce some of the verbosity – six times. It is neither short, nor easy to read and so it is easy to overlook some tiny detail like mismatched labels and ids. It also leads to convoluted java code and more copy-pasting. This is not the solution you are looking for.

The Solution

To effectively create your own tags to describe your UI, you need a so-called composite component. A composite component consists of a few different parts that all work together to produce layouts as simple as this:

[sourcecode language=”xml” wraplines=”true”]
<de.delusions.measure.components.MeasureInput android:id="@+id/weight"
delusions:label="@string/label_weight"
delusions:unit="kg"/>
[/sourcecode]

Example Composite Component

The xml in the example defines a use of a custom component with several custom attributes. This is much easier to read and therefore less prone to errors. The android:id defined here is the id your activity will access to retrieve your custom component like this:

[sourcecode language=”java” light=”true”]MeasureInput input = (MeasureInput) activity.findViewById(this.viewId);[/sourcecode]

How to get there

To arrive at this you need 3 more pieces of code and xml:

  • a custom layout for the component
  • a resouce defining your custom attributes
  • a java class implementing the behaviour of the component

Custom Layout for your component:

The first thing you want is a custom layout for reusable component in your layout/ folder. I opted to do this in xml, because I find it easier and faster than doing it in java code. You can implement the layout as part of the java class.

[sourcecode language=”xml” wraplines=”true”]
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:id="@+id/label"
style="@style/LabelFont"
android:layout_width="100dp"
android:layout_height="wrap_content"/>

<EditText android:id="@+id/input"
style="@style/MyNumberBox"
android:layout_width="75dp"
android:layout_height="wrap_content"/>
</merge>
[/sourcecode]

Two important things to note in this xml snippet.

  1. the tag is important because this will be merged into another – surrounding – layout.
  2. the android:ids you define here are the ones that you will use in your custom layout java class to access the parts to make a whole.

Custom Attributes for your component:

If you want a component that can be parametrized through the xml definition you need some attribute definitions of your own. You can put these definitions of your custom attributes in your values/ folder in any xml resource file:

[sourcecode language=”xml” wraplines=”true”]
<resources>
<declare-styleable name="MeasureInput">
<attr name="label" format="string" />
<attr name="unit" format="string" />
</declare-styleable>
</resources>
[/sourcecode]

To access these in the layout for your activity, you need to introduce your own namespace. Just define it in the layout and you’re all set.

[sourcecode language=”xml” light=”true”]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:delusions= "http://schemas.android.com/apk/res/de.delusions.measure">
[/sourcecode]

You can now access your custom attributes with the namespace you defined:

[sourcecode language=”xml” light=”true”]delusions:unit="kg"[/sourcecode]

Custom Layout class for your component:

Last but not least you need the class that implements what your component does. Things to remember:

Extend one of the existing Layout classes: public class MeasureInput extends LinearLayout {

Inflate your xml layout (if you didn’t opt for the java version)

[sourcecode language=”java”]
final LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_input, this);
[/sourcecode]

You can access the parts of your component by the android:ids and findByViewId as usual:

[sourcecode language=”java” light=”true”] this.labelText = (TextView) findViewById(R.id.label);[/sourcecode]

Write some accessors or data retrieval methods for whoever wants to extract the information that was entered:

[sourcecode language=”java” light=”true”] public String getInput() { return this.input.getText().toString(); }[/sourcecode]

Voilà: your custom composite component is ready to go and be used over and over again!

Demo App ScreenshotUpdate: as requested I added a small demonstration app that shows an example of how to create such a component and can be run on your device or emulator. Download the demo here.

This entry was posted in android. Bookmark the permalink.

17 Responses to Android: How to write your own custom composite component

  1. Pingback: Creating Custom Composite Components | JavaZzzzz….