Custom Fonts on Android — Dynamic Font Selection via XML

This is the fourth post in our series of custom fonts on Android. In the last few weeks, we've seen how to use custom fonts and apply different styles to a TextView. This week, we'll show an advanced solution on adding a font parameter to the TextView XML in order to set the font dynamically — without any code!

If you haven't read the previous blog posts, you should do so. It might help your understanding since they're all based on each other.

Also, feel free to take a look at the entire example project on Github.

Custom Fonts on Android Series Overview

Different Fonts

Generally, we like using other beautiful fonts besides Roboto as a way to let the app stand out, be different and rememberable to the user. While we explain in this post an easy way to use multiple fonts, proceed with caution. Any UI with too many fonts just gets messy and distracting!

Of course, you might have your reason for adding more than one custom font. For example, we love using FontAwesome for well-designed icons, customizable in size and color. It's very convenient to set the font directly via XML without an additional line of Java code. Before we get to that, we'll need to do some small preparations.

Preparing a Custom XML Property

Since we want to set the font via XML and Android has no appropriate TextView property we can hijack (similar like we did in our previous blog post), we've to add a custom property we call font. The first step is to add a attrs.xml in your /values/ folder. The content should look like this:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <declare-styleable name="CustomFontTextView">
        <attr name="font" format="string"/>
    </declare-styleable>
</resources>  

All this does is let the system know we've a custom property with the name font belonging to the class CustomFontTextView. From now on we can access that property in code and in XML.

Let's go one step further and add the name of the fonts we want to use as a String resource in /values/strings.xml:

<resources>  
    <string name="font_name_fontawesome">fontawesome</string>
    <string name="font_name_source_sans_pro">SourceSansPro</string>
</resources>  

This will make some things easier for us down the road. Our preparations are done, onto the important parts ...

Using the font-Property

Let's look at the XML to learn how to use the newly added property font. First thing you've to do is add this line

xmlns:app="http://schemas.android.com/apk/res-auto"  

to your highest view hierarchy element. For example, if your view is nested in a LinearLayout, it'd look like this:

<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
...>

xml

That one line allows you to access the font parameter by prefacing it with app:. Of couse, you can freely change the app: to anything you like.

As already mentioned, you now can set your custom XML property via app:font, for example:

        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="http://futurestud.io/blog/"
        android:textSize="18sp"
        android:textStyle="italic"
        app:font="@string/font_name_source_sans_pro"/>

xml

The code above would display the link to our blog in Source Sans Pro. Our other font, FontAwesome, is rather for single character use. We looked at the FontAwesome CheatSheet and used the code for the rocket icon (&#xf135;) five times:

        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="&#xf135; &#xf135; &#xf135; &#xf135; &#xf135;"
        android:textSize="18sp"
        app:font="@string/font_name_fontawesome"/>

This should display the rocket item fives times, since the FontAwesome font will be applied. However, before that works, we've to go one last step.

Implementation of CustomFontTextView

We've seen in the previous blog post how to obtain the value of an XML property. However, since we use a custom property this time, the code is a little more complex. First, we've to get an array with all properties with calling obtainStyledAttributes():

TypedArray attributeArray = context.obtainStyledAttributes(  
    attrs,
    R.styleable.CustomFontTextView
);

The last parameter is a reference to the attrs.xml file we created as the first step! Now we just need to use the getString() function on the attributeArray to get the specified font name:

String fontName = attributeArray.getString(R.styleable.CustomFontTextView_font);  

Lastly, we've to extend our logic in our CustomFontTextView to set the font correctly. The important changes happened in applyCustomFont() and selectTypeface(). The entire class now looks like this:

public class CustomFontTextView extends TextView {

    public static final String ANDROID_SCHEMA = "http://schemas.android.com/apk/res/android";

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

        applyCustomFont(context, attrs);
    }

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

        applyCustomFont(context, attrs);
    }

    private void applyCustomFont(Context context, AttributeSet attrs) {
        TypedArray attributeArray = context.obtainStyledAttributes(
                  attrs,
                  R.styleable.CustomFontTextView);

        String fontName = attributeArray.getString(R.styleable.CustomFontTextView_font);
        int textStyle = attrs.getAttributeIntValue(ANDROID_SCHEMA, "textStyle", Typeface.NORMAL);

        Typeface customFont = selectTypeface(context, fontName, textStyle);
        setTypeface(customFont);

        attributeArray.recycle();
    }

    private Typeface selectTypeface(Context context, String fontName, int textStyle) {
          if (fontName.contentEquals(context.getString(R.string.font_name_fontawesome))) {
              return FontCache.getTypeface("fontawesome.ttf", context);
          }
          else if (fontName.contentEquals(context.getString(R.string.font_name_source_sans_pro))) {
              /*
              information about the TextView textStyle:
              http://developer.android.com/reference/android/R.styleable.html#TextView_textStyle
              */
              switch (textStyle) {
                  case Typeface.BOLD: // bold
                      return FontCache.getTypeface("SourceSansPro-Bold.ttf", context);

                  case Typeface.ITALIC: // italic
                      return FontCache.getTypeface("SourceSansPro-Italic.ttf", context);

                case Typeface.BOLD_ITALIC: // bold italic
                    return FontCache.getTypeface("SourceSansPro-BoldItalic.ttf", context);

                  case Typeface.NORMAL: // regular
                  default:
                      return FontCache.getTypeface("SourceSansPro-Regular.ttf", context);
              }
          }
          else {
              // no matching font found
              // return null so Android just uses the standard font (Roboto)
              return null;
          }
      }

If the function cannot find an appropriate font, we'll return null so Android just uses Roboto as a default.

Reviewing the Results

As always, we'll demonstrate the functionality in our example activity, now with another line for FontAwesome:

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="http://futurestud.io/blog/"
        android:textSize="18sp"/>

    <io.futurestud.tutorials.customfont.CustomFontTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="http://futurestud.io/blog/"
        android:textSize="18sp"
        app:font="@string/font_name_source_sans_pro"/>

    <io.futurestud.tutorials.customfont.CustomFontTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="http://futurestud.io/blog/"
        android:textSize="18sp"
        android:textStyle="bold"
        app:font="@string/font_name_source_sans_pro"/>

    <io.futurestud.tutorials.customfont.CustomFontTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="http://futurestud.io/blog/"
        android:textSize="18sp"
        android:textStyle="italic"
        app:font="@string/font_name_source_sans_pro"/>

    <io.futurestud.tutorials.customfont.CustomFontTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="&#xf135;&#xf135;&#xf135;&#xf135;&#xf135;"
        android:textSize="18sp"
        app:font="@string/font_name_fontawesome"/>

</LinearLayout>  

The view of the activity would look like this:

The fonts from top to bottom: Roboto, Source Sans Pro, Source Sans Pro Bold, Source Sans Pro Italic, FontAwesome

Next Steps

This is everything you've to do to specify custom fonts via XML! Feel free to add or change fonts according to your needs. At this point, we're technically done with the series. However, we'll add two more blog posts on how to change the standard font for each build variant and how to apply the custom fonts to other standard views (for example Button).

Let us know if everything works well for you in the comments or on twitter @futurestud_io.


Resources

Explore the Library

Find interesting tutorials and solutions for your problems.