Custom Control – Extending Existing Views

Building on our previous example of how we can extend existing controls and customize them for our requirements, we improve the “CustomFontTextView”. In the last post, we saw that by extending a TextView and then setting it’s font in it’s constructor we can achieve a TextView with custom fonts. But you will soon see that it rather restricts our development. If we were dealing with a number of different fonts, we would have to replicate them in multiple classes and then use each of them.

What we need is a more generic approach to the problem. As a developer, one should always build his own toolkit of reusable components over time which speeds up development. We could make it more generic if there was a way in which we could specify the font type at the time of defining the xml. This is possible by declaring an attribute through styleable which could be accessed through the xml.

So the basic steps are as follows

  1. Declare an attribute in a styleable which will be used to specify the font type.
  2. Add your custom fonts in the assets/fonts folder.
  3. Use the specified font type in the constructor of your extended TextView to access the custom fonts.

Let’s start with the styleable. In the “res/values” folder, we add a resource file called attrs.xml. This is the file where all our attributes of our custom controls go. In it we add the following piece of code.

[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<resources>

<declare-styleable name="CustomFontTextView">
<attr name="fontName" format="enum">
<enum name="AirMole" value="0" />
<enum name="AirMoleAntique" value="1" />
<enum name="AirMoleShaded" value="2" />
<enum name="AirMoleStripe" value="3" />
</attr>
</declare-styleable>
</resources>
[/sourcecode]

We declare a styleable name “CustomFontTextView” and within it declare an attribute called “fontName” of type “enum”. In our example, we have four different font types which we have specified using name/value format.The attributes can be of different types such as integer, string, boolean, etc. We will discuss styleables in more details in another post.

The next thing we need to do is to access the attribute in our extended class. Check out the updated code of the CustomFontTextView below.

[sourcecode language="java"]
public class CustomFontTextView extends TextView {

public CustomFontTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if( !isInEditMode() ){
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomFontTextView,
defStyle, 0);

int fontId = a.getInteger(R.styleable.CustomFontTextView_fontName, -1);
if( fontId == -1 ){
throw new IllegalArgumentException("The font_name attribute is required and must refer "
+ "to a valid child.");
}
a.recycle();
initialize(fontId);
}

}

public CustomFontTextView(Context context, AttributeSet attrs) {
//call the constructor which has the complete definition
this(context, attrs, 0);
}

public CustomFontTextView(Context context) {
super(context);
//This fallbacks to the default TextView without applying any custom fonts
}

public void initialize(int fontId){

Typeface tf = null;
switch(fontId){
case 0:
tf = Typeface.createFromAsset(getContext().getAssets(), "fonts/airmole.ttf");
break;
case 1:
tf = Typeface.createFromAsset(getContext().getAssets(), "fonts/AirmoleAntique.ttf");
break;
case 2:
tf = Typeface.createFromAsset(getContext().getAssets(), "fonts/AirmoleShaded.ttf");
break;
case 3:
tf = Typeface.createFromAsset(getContext().getAssets(), "fonts/AirmoleStripe.ttf");
break;
}

setTypeface(tf);
}
}
[/sourcecode]

We declare a TypedArray which points to an array of values retrieved with obtainStyledAttributes(). We will look into it in more details at a later time. For now, we need to extract the fontName using the getInteger() of the TypedArray. We also pass a default value (-1 here) which is returned in case we don’t find a valid value in the array.

Note that it is important to call recycle() on the TypedArray so that the array can be cached for later use. A lot of people ask about the purpose of recycle() and why it is recommended. If you look at the code of TypedArray, you will realize that it is basically a caching mechanism which stores the currently retreived StyledAttributes for later re-use.

Once we retreive the fontId which is nothing but an enum value for the font name, we load the appropriate font type.

Finally, we need to use this in our xml files and specify the font. The code below does just that

[sourcecode language="xml"]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.iwannalearn.customcontrols"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<com.iwannalearn.customcontrols.CustomFontTextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:fontName="AirMole"
android:text="AirMole" android:textSize="22sp"/>

<com.iwannalearn.customcontrols.CustomFontTextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:fontName="AirMoleStripe"
android:text="AirMoleStripe" android:textSize="22sp"/>

</LinearLayout>
[/sourcecode]

The implementation is pretty straight forward. You need to add the xml namespace of your custom control.

[sourcecode language="xml"]
xmlns:app="http://schemas.android.com/apk/res/com.iwannalearn.customcontrols"
[/sourcecode]

where “app” is the name we have assigned here to represent the namespace.

It can either be added at the top level viewgroup affecting the complete xml or just within your custom control. To specify the CustomFontTextView, you must specify the complete qualifying name. In this example, it is com.iwannalearn.customcontrols. CustomFontTextView. See what happens when you just specify CustomFontTextView. Check out the error that occurs.

To specify the font name, you must use the namespace:attributeName format. Ex, app:fontName = “AirMole”.

And that’s it! You are done. Compile and run it. If you have done things correctly you will see a screen similar to the following, with fonts specified by you.

Custom Fonts ScreenShot

Note: If you want to make it more generic. You could use a “string” format instead of “enum” and pass the name of the font to the CustomFontTextView. You can then use this name to open the appropriate file. Another thing that you can do to make it more efficient, is to create a spearate file which inflates the font file from the assets folder and keeps a reference to it. In that case, you can save re-inflation time, specially if you are using the CustomFontTextView in numerous places in your application.

You can view and download the complete working code here.

Custom Controls – An Introduction

Introduction

Very often while developing for Android, you will come across situations where you need a control which is not readily available in the default set of Android controls. However, Android was developed keeping the developer in mind, so that the developer’s creativity is not inhibited by the platform. All you need to do is just find out what and how to do.

There are a number of approaches which you can take to make a custom control, the choice however will depend on what you want and its level of complexity. The general approaches that you can take are as follows,

  1. Extend an existing View type or a widget. Pretty simple job here. Check if you have some existing View in the framework, extend it, implement the extra functions you require and you are done.
  2. Extend an existing ViewGroup (any layout) and put the controls you require in it. Add the extra functionalities and you are ready to reuse it in other places.
  3. Extend the class View/ViewGroup and implement everything. This is a little advanced and you would require a little extra knowledge of the way Android draws and creates layouts but this is the father of all customizations. With this method you can make a control that is as unique as you.
  4. Implement extra interfaces to existing Views/ViewGroups such that it can handle more events and do more. This is similar to 1 and 2, with the exception of implementing interfaces.
  5. Reuse the Google code of an existing widget and change its behavior. This might sound like a hack but you have to agree that there are times where you have looked at a control and thought, “if only…”. Eg, If only I could interchange the position of the RadioButton and its text. In some cases, it can be done by extending it but at other times, the behavior is so in its core that you need to reimplement it. At such times, its better to reuse the code than reinvent the wheel. There are 2 positives in this.
    • You see some really good code and learn from it.
    • Once you learn to figure out the way google writes code, you can quickly change it and come up with just the right control in quarter of a time.

I’ll be covering each of the cases, in following posts but for now, here’s a small introduction into how custom controls can make your life easier.

First Custom Control

When I first came across the requirement of setting fonts to my Textviews I was shocked to see that I could not set it through xml. The only fonts you can set using xml are the default android fonts. I knew how to go about it programatically but can you imagine the pain of setting it for every TextView in every file in the project?

The most natural thing to do was to extend the TextView and set the font in the constructor.And that is simple.

First, make a folder called “fonts” in the assets folder and put your font in it. In the example I have put the airmole.ttf file.

Next, extend TextView and in it’s constructor load the font and set it using setTypeface() and you are done.

[sourcecode language="java"]
public class CustomFontTextView extends TextView {

public CustomFontTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}

public CustomFontTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}

public CustomFontTextView(Context context) {
super(context);
initialize();
}

public void initialize(){
if( !isInEditMode() ){
Typeface tf = Typeface.createFromAsset(getContext().getAssets(), "fonts/airmole.ttf");
setTypeface(tf);
}
}
}
[/sourcecode]

If you are wondering what the isInEditMode() does, then just comment the check and observe what happens to your graphical view of the xml in Eclipse. You will mostly see the following error,
The graphics preview in the layout editor may not be accurate: Typeface.createFromAsset() is not supported.

What the check basically does is that it avoids the code within it when you are viewing your xml and hence falls back to the default TextView. This helps in designing as you can actually see the content of the TextView.

Here is the xml file where the CustomFontTextView has been used.
[sourcecode language="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="vertical" >

<com.iwannalearn.customcontrols.CustomFontTextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" android:textSize="22sp"/>

</LinearLayout>
[/sourcecode]

Note that the CustomFontTextView requires it’s complete qualifying name. Just specifying CustomFontTextView> will not work.