Mobile apps by TuneSoftware

Android Custom Components and Compound Controls

Android provides pretty much all the UI components you need to build any app. There are two types of these UI components – Views and ViewGroups. Views (also known as widgets) are the individual elements that a user interacts with on a screen – labels, edit texts, buttons, lists, etc. ViewGroups are the container views that are used for the layout of widgets, the most common being LinearLayout, RelativeLayout and FrameLayout.

There are times however, when the standard widgets don’t quite do what you want, or maybe you just want to work smarter and stop repeating the same groups of widgets over and over again in an app. That’s where Custom Components and Compound Controls come in handy.

Custom Components are new widgets that you create yourself by extending an existing view class. This could be simply adding a bit more functionality to an existing widget, such as extending TextView to allow you to specify the typeface to use. Or it could be building a completely new widget by extending View which is the base of all widgets, for example to create a widget that displays a hierarchical list of people in an organisation chart.

Compound Controls are a group of widgets that you put together into one class with a corresponding layout, to enable you to re-use them in many layouts and/or to simplify the interface to the group so they all act together. For example, your app might display a list of games the user has completed, and for each game it might display the result with a text field showing won/lost, the number of points earned, and an image to make the display more graphical. If you built a Compound Control with all these widgets in a layout, and a class for the control with all the behaviour in it, you could re-use it wherever you want in your app and define a nice clean interface for it so your activity and fragment code is kept clean.

Enough waffle, let’s do some stuff.

1. Custom Component

I want to display labels in my app using different fonts. The standard TextView widget uses the standard Android font, so I’ll create a custom component that builds on TextView to provide the required functionality. To do this I create a new class called FontTextView that extends TextView:

Whenever you extend a view class you need to provide the three standard constructors that simply call the super class constructors as shown above.

You could use this new widget in a layout file as it is. In order to do this you need to specify the full package name as well as the class name in the layout xml, like this:

1

2

3

4

<com.tunesoftware.example.FontTextView

android:id="@+id/text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

If you do this now with the class just created it will behave just as a normal TextView because there is no additional functionality in the FontTextView class.

To include your own custom font in an app you can embed the font file as a TTF or OTF font in the app’s assets folder. The assets folder in an Android app is a top level folder, not part of the res folder, so create /assets at the same level as the /src and /res folders in an eclipse project (or /java and /res in Android Studio), and copy your font file into that folder. For this example I have created a /fonts sub-folder under my assets folder and copied a font file named halibut.ttf into this folder.

To use this font in the custom component I could simply load the font in the constructor and set it as the typeface for the widget:

If you try this now it will work, you will have a TextView in your layout with the new font. But obviously it’s not ideal, it’s hardcoded as the halibut font, if we wanted to use a different font for another TextView we would have to create a new custom component.

To overcome this, and make the custom component more useful, I will create a new attribute for FontTextView that will enable the font name to be specified in the layout xml. In the /res/values folder create a new file attrs.xml, and add the following:

1

2

3

4

5

6

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

<resources>

<declare-styleable name="FontTextView">

<attr name="fontName"format="string"/>

</declare-styleable>

</resources>

This declares a custom attribute called fontName for our custom widget. It can now be used in the layout xml like this:

As you can see I’ve declared an xml namespace called custom (you can call it whatever you want) that points to the app’s resources. Then in the layout of the FontTextView widget I’ve used this namespace to include the new attribute ‘fontName’ and set it’s value to halibut.ttf.

All that’s needed now is to modify the FontTextView class itself so that it reads the fontName custom attribute and sets the font according to the value it finds.

And that’s it. You can include any font file in your assets folder and use it in your app by using the FontTextView instead of TextView and specifying the font to use in the layout xml.

There are a couple of potential issues with this solution however. One is that every time the new widget is used, the font file is read from the assets directory, which could be slow. The other is that there was a memory leak in earlier versions of Android resulting in the font not being released, so every time it was used memory was being consumed and not released (this was fixed in Ice Cream Sandwich I believe). To overcome these two issues we could cache a font when it is used for the first time and then use it from the cache on subsequent usages.

To do this I create a static class level map variable to hold a reference to the typefaces:

1

privatestaticMap<String,Typeface>mTypefaces;

Then instead of loading from the assets folder each time, I check if that font is in the variable first and use it from this cache. The new init method looks like this:

Well that’s it for this post. I was going to give an example of a compound control, but I think I’ve taken up enough of your time so I’ll leave that for a future post. Besides, I’ve run out of square Earl Grey teabags, I accidentally got round ones yesterday and they’re difficult to lift out of the mug with your fingers, so I’m off to Sainsbury’s to get some square ones.