Integrating fonts in Android

Once you step into the wondrous world of developing Android applications, there will come a point at which you want to show a text view with a different font than one of the default ones (the Droid family in pre-ICS and the Roboto family in ICS+). Luckily, there are ample resources on the internet where you can figure out how to achieve your goal. I find, however, that most of these solutions are somewhat limited: they work fine if there is only one text view in your entire app that needs a custom font, but if you need app-wide styling of your text, a more manageable solution is needed. Here I will share with you how to achieve this.

A screenshot of an activity showing various lines of text with custom fonts

Fonts?

First of all, let's explain a little bit about fonts in general. A font is basically identified by its name and a style, where the style can be regular, bold, italic or bold-italic. You might have heard of other styles such as thin, light or condensed, but these are not actually different styles, but different fonts (e.g., Roboto Thin is known as sans-serif-light in regular style). Each style of a font lives inside its very own font file, e.g., Roboto-LightItalic.ttf. The suffix that indicates the style is just a convention, it will not be necessary to follow this strict format in your app, nor does Android use it in any special way if you do follow the convention.

Fonts!

Now, let's continue to a scalable and manageable solution to integrate our own fonts!

For starters, we have to define our own fonts in a neat way. For that, we're going to take a look at how Android itself defines its fonts and simply copy this format. Credits for this idea and the implementation of it below go to Sriram Ramani, who posted it on his blog on the 29th of November, 2012. Starting from API 14, the Android platform defines the default fonts in a file called system_fonts.xml, which you can find in your Android SDK in <android-sdk>/platforms/android-<api-level>/data/fonts
/system_fonts.xml
. If you open it, you'll find an XML file that contains something like this:

<familyset>  
    <family>
        <nameset>
            <name>sans-serif</name>
            <name>arial</name>
            <name>helvetica</name>
            <name>tahoma</name>
            <name>verdana</name>
        </nameset>
        <fileset>
            <file>Roboto-Regular.ttf</file>
            <file>Roboto-Bold.ttf</file>
            <file>Roboto-Italic.ttf</file>
            <file>Roboto-BoldItalic.ttf</file>
        </fileset>
    </family>
</familyset>  

In this file, a family is a single font with all its different styles. A family can be referenced by multiple names listed in the nameset, all of which identify the exact same font. The fileset specifies which font files contain the actual font data. Note that the order in which these files are defined is important! They are listed in the order of the styles which they support: regular, bold, italic and bold-italic. If less than four styles are listed, then the styles with no associated font file will be mapped to the other font files instead. It is important to note that Android will not automatically pick the right style of your font: if you use a custom font in a TextField whose text contains bold or italic HTML tags (e.g., "This is <b>bold</b> text"), Android applies its own algorithm to thicken and/or italicize that font instead of picking the actual bold or italic font.

OK, let's create our own XML file fonts.xml in the res/xml folder, listing all fonts that we want to use in our app. In this example, I used Aspergit and Bodoni FLF, both of which can be downloaded from www.fontspace.com. Our XML file looks as follows:

res/xml/fonts.xml:

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

    <!-- Font with all four styles defined -->
    <family>
        <nameset>
            <name>aspergit</name>
        </nameset>
        <fileset>
            <file>Aspergit.ttf</file>
            <file>Aspergit Bold.ttf</file>
            <file>Aspergit Italic.ttf</file>
            <file>Aspergit Bold Italic.ttf</file>
        </fileset>
    </family>

    <!-- Font with only regular and bold defined -->
    <family>
        <nameset>
            <name>bodoni</name>
        </nameset>
        <fileset>
            <file>BodoniFLF-Roman.ttf</file>
            <file>BodoniFLF-Bold.ttf</file>
        </fileset>
    </family>

</familyset>  

From asset to Typeface

Next we need a central place to manage all font related actions, such as parsing this XML file, handling requests for fonts with a certain style and caching of loaded fonts. For this task we will make a TypefaceManager. Since the XML file plays a central role in the management of our fonts, the TypefaceManager will be a singleton that is initialized with the resource identifier of that XML file. You'll probably want to initialize it in a single place, before any other parts of your app start running, so a good place to do this initialization would be in the onCreate() function of your Application class.

The implementation of the TypefaceManager comprises about 400 lines which is a little too much to include here, but you are encouraged to browse through the code for a bit. Two things should be said about it though:

  • First, every time a font is loaded, it is cached for two reasons. Loading a font can potentially be time-consuming, so caching it seems like a good idea in general, but more importantly, there is a bug in pre-ICS Android versions that prevents the font from being garbage collected when it is no longer used. While caching doesn't fix this issue, we can at least ensure we leak as little memory as possible by loading every font at most once.

  • Second, the way missing styles are mapped to existing styles should be mentioned: in this implementation, every style defines a list of fallback styles in order of precedence. For example, if bold-italic is not present for some font, it will be mapped to the bold style font. If that is not present either, the italic style is checked. If that is also absent, the last resort is the regular style. If none of these fallback styles exist, the missing style cannot be mapped, but if that's the case the font is useless anyway.

  • Third, applyFont(TextView, AttributeSet, int) is a static method that takes any TextView, searches the AttributeSet for a declared font and applies that font. The next section will reveal why this is very useful.

"And so we apply, our fonts in UI"

With this framework in place, we can add the UI elements. Of course, since we're adding custom behaviour to our UI, we need to make our own view class that will apply this custom behaviour. Since it is good practice to keep layout related things out of your code, we will define the font as an attribute of this view. Better practice, however, dictates that it should be possible to define the font in our styles, so we will also take that into account. But even better yet would be a solution where we can define the font in a text appearance style, so it can be applied throughout our app via the theme. All these goals are relatively easy to fulfil once you've learned how Android achieves these things itself.

Our view will be a subclass of TextView, called FontTextView, so it behaves like any other text view, with the exception of the fonts. We start by defining the XML attributes that we want to use in this FontTextView, by declaring so called 'styleables' in res/values/attrs.xml. In our FontTextView, it should be possible to define both the font and the style. For the font, we introduce our own attribute, but for the style we will reuse Android's native style attribute to increase consistency. Instead of creating a font attribute, we could've also 'borrowed' Android's native fontFamily attribute, but there's no knowing how Android will respond to invalid typefaces (although it should fall back to its fallback fonts), so we better be safe than sorry. Here's how attrs.xml looks like:

res/values/attrs.xml:

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

    <declare-styleable name="Fonts">
        <attr name="font" format="string" />
        <attr name="android:textStyle" />
    </declare-styleable>

</resources>  

Simple huh? It states that there is a group of related XML attributes called Fonts, consisting of two attributes with the given names. This enables us to neatly look them up during initialization of the FontTextView. Note that when we want to use our own XML attribute, we have to include the app namespace in the XML file (xmlns:app="http://schemas.android.com/apk/res-auto") and prefix the attribute with that namespace (e.g., app:font="aspergit").

Now, remember the applyFont method from the TypefaceManager? When called, it will find the font and style attributes and apply the proper version of that font to the targeted TextView. It was designed especially so it requires only a single extra call from any TextView subclass, resulting in the following code for our custom FontTextView:

src/com/innovattic/view/FontTextView.java:

public class FontTextView extends TextView  
{

    public FontTextView(Context context)
    {
        this(context, null);
    }

    public FontTextView(Context context, AttributeSet attrs)
    {
        this(context, attrs, android.R.attr.textViewStyle);
    }

    public FontTextView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        applyFont(this, attrs, defStyle);
    }

}

In fact, making a font-enabled version of almost any subclass of TextView (such as RadioButton or AutoCompleteTextView) will require only that single extra line of code, because they can all be passed directly as a target to the applyFont method! For your convenience, I have included font-enabled versions of most subclasses of TextView, e.g., a FontButton that extends a Button.

This gives us the freedom to use the font attribute in three different places: the layout xml, the style xml or the textAppearance style. Note that, even though our solution will always check the textAppearance, Android's UI elements don't all use the textAppearance: the Button for example simply never looks for a defined textAppearance. That means that while you can still define a textAppearance with a font in a FontButton, the font is the only attribute that will be applied (attributes like textColor won't, contrary to what you would expect).

So this is the proper way

Now we have everything we need and we can play around for a bit! But first, let me summarize the steps we have taken to reach this point:

  1. Define our fonts in res/xml/fonts.xml
  2. Include a TypefaceManager and initialize it in your Application class
  3. Define styleables in res/values/attrs.xml
  4. Subclass the UI element to which you want to apply fonts and call TypefaceManager.applyFont(...) from its constructor
  5. Use our own UI element in the layout
  6. Use the font attribute in the layout xml, in your style or in your textAppearance

Below I have defined a simple style that sets a default font via the text appearance and some other styles that override this font.

res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>  
<resources xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Application theme -->
    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
        <item name="android:textViewStyle">@style/MyTextViewStyle</item>
    </style>

    <!-- Style to use for ALL text views (including FontTextView) -->
    <style name="MyTextViewStyle"
           parent="@android:style/Widget.Holo.Light.TextView">
        <item name="android:textAppearance">@style/MyTextAppearance</item>
    </style>

    <!-- Text appearance to use for ALL text views (including FontTextView) -->
    <style name="MyTextAppearance" parent="@android:style/TextAppearance.Holo">
        <item name="android:textSize">20sp</item>
        <item name="android:textColor">#FF002DB3</item>
        <item name="font">aspergit</item>
    </style>

    <!-- Some attributes we need in all example text views -->
    <style name="Default">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_marginTop">5dp</item>
        <item name="android:layout_marginBottom">5dp</item>
    </style>

    <style name="Bodoni" parent="Default">
        <item name="font">bodoni</item>
    </style>

    <style name="Bodoni.Bold">
        <item name="android:textStyle">bold</item>
    </style>

    <style name="Bodoni.Italic">
        <item name="android:textStyle">italic</item>
    </style>

    <style name="ButtonStyle" parent="Default">
        <item name="android:textAppearance">@style/MyTextAppearance</item>
    </style>

</resources>  

And I defined some strings that we will use in the example
res/values/strings.xml:

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

    <string name="app_name">Font Example</string>

    <string name="txt_no_style">A FontTextView without style</string>
    <string name="txt_style_in_layout_xml">Applying a style in
        the layout XML</string>
    <string name="txt_font_in_layout_xml">Applying a font in
        the layout XML</string>
    <string name="txt_style_bodoni">A FontTextView with style Bodoni</string>
    <string name="txt_style_bodoni_bold">A FontTextView with
        style Bodoni.Bold</string>
    <string name="txt_style_bodoni_italic">A FontTextView with
        style Bodoni.Italic</string>
    <string name="txt_italics"><i>Italics</i> when no <i>italic</i> font
        is present!</string>
    <string name="txt_bold_italics">Then what about
        <b><i>Bold-Italic</i></b>?</string>
    <string name="txt_button">Click me!</string>

</resources>  

And an activity that uses these styles
res/layout/activity.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    tools:context=".ShowFontsActivity" >

    <com.innovattic.view.FontTextView style="@style/Default"
        android:text="@string/txt_no_style" />

    <com.innovattic.view.FontTextView style="@style/Default"
        android:textStyle="bold|italic"
        android:text="@string/txt_style_in_layout_xml" />

    <com.innovattic.view.FontTextView style="@style/Default"
        app:font="bodoni"
        android:text="@string/txt_font_in_layout_xml" />

    <com.innovattic.view.FontTextView style="@style/Bodoni"
        android:text="@string/txt_style_bodoni" />

    <com.innovattic.view.FontTextView style="@style/Bodoni.Bold"
        android:text="@string/txt_style_bodoni_bold" />

    <com.innovattic.view.FontTextView style="@style/Bodoni.Italic"
        android:text="@string/txt_style_bodoni_italic" />

    <com.innovattic.view.FontTextView style="@style/Bodoni"
        android:text="@string/txt_italics" />

    <com.innovattic.view.FontTextView style="@style/Bodoni"
        android:text="@string/txt_bold_italics" />

    <com.innovattic.view.FontButton style="@style/ButtonStyle"
        android:text="@string/txt_button" />

</LinearLayout>  

So now you know how to integrate fonts in your application, the proper way ;)
Please download the example from our GitHub and include it in your next project so you don't have to reinvent the wheel.

Jelle

comments powered by Disqus