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
- Declare an attribute in a styleable which will be used to specify the font type.
- Add your custom fonts in the assets/fonts folder.
- 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.
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.
