Custom Fonts in Android Widgets

Last Update: 15 March 2012

Tags: android android-font android-custom-widget

Find further code and implementation here: https://github.com/browep/AndroidCustomFontWidgets

There is no standard way of adding custom fonts to Android UI widgets using XML. You can add them programmatically at create time but that mixes the "view" code and the "controller" and is not transportable. Using custom UI attributes you can use the XML to setup the fonts using one line.

First we need to declare custom attributes that are going to be used in the XML.

/values/attrs.xml

<resources>

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

    <declare-styleable name="com.github.browep.customfonts.view.FontableTextView">
        <attr name="font" />
    </declare-styleable>
    <declare-styleable name="com.github.browep.customfonts.view.FontableButton">
        <attr name="font" />
    </declare-styleable>

</resources>

The name attributes point to custom widgets that will use that attribute set to use the custom fonts.

FontableTextView:

package com.github.browep.customfonts.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import com.github.browep.customfonts.R;
import com.github.browep.customfonts.util.UiUtil;

public class FontableTextView extends TextView {

    public FontableTextView(Context context) {
        super(context);
    }

    public FontableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        UiUtil.setCustomFont(this,context,attrs,
                R.styleable.com_github_browep_customfonts_view_FontableTextView,
                R.styleable.com_github_browep_customfonts_view_FontableTextView_font);
    }

    public FontableTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        UiUtil.setCustomFont(this,context,attrs,
                R.styleable.com_github_browep_customfonts_view_FontableTextView,
                R.styleable.com_github_browep_customfonts_view_FontableTextView_font);
    }
}

The TextView uses the a util class, UiUtil. It pulls from the attribute set a name of a font and looks for a font file by that name in the "/assets/fonts" folder:

package com.github.browep.customfonts.util;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Hashtable;

public class UiUtil {

    public static final String TAG = "UiUtil";

    public static void setCustomFont(View textViewOrButton, Context ctx, AttributeSet attrs, int[] attributeSet, int fontId) {
        TypedArray a = ctx.obtainStyledAttributes(attrs, attributeSet);
        String customFont = a.getString(fontId);
        setCustomFont(textViewOrButton, ctx, customFont);
        a.recycle();
    }

    private static boolean setCustomFont(View textViewOrButton, Context ctx, String asset) {
        if (TextUtils.isEmpty(asset))
            return false;
        Typeface tf = null;
        try {
            tf = getFont(ctx, asset);
            if (textViewOrButton instanceof TextView) {
                ((TextView) textViewOrButton).setTypeface(tf);
            } else {
                ((Button) textViewOrButton).setTypeface(tf);
            }
        } catch (Exception e) {
            Log.e(TAG, "Could not get typeface: " + asset, e);
            return false;
        }

        return true;
    }

    private static final Hashtable<String, SoftReference<Typeface>> fontCache = new Hashtable<String, SoftReference<Typeface>>();

    public static Typeface getFont(Context c, String name) {
        synchronized (fontCache) {
            if (fontCache.get(name) != null) {
                SoftReference<Typeface> ref = fontCache.get(name);
                if (ref.get() != null) {
                    return ref.get();
                }
            }

            Typeface typeface = Typeface.createFromAsset(
                    c.getAssets(),
                    "fonts/" + name
            );
            fontCache.put(name, new SoftReference<Typeface>(typeface));

            return typeface;
        }
    }

}

We are using a soft ref map to cache typefaces so we dont make identical objects for each TextView ( there could easily be many in a decent size application ) as the OS will create a new object for each one. The soft ref will allow the garbage collector to clean up any stale Typeface objects if memory becomes too high. More details about the issue here

Once this is wired up we can point to a font file when using our widgets in a layout file:

      
   <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res/com.github.browep.customfonts"

              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
        >
    <com.github.browep.customfonts.view.FontableTextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:textSize="24sp"

            android:text="Hello World, Main"
            app:font="TechnoHideo.ttf"

            />

</LinearLayout>

Notice the namespacing I have added: xmlns:app="http://schemas.android.com/apk/res/com.github.browep.customfonts". It contains the package name for the app. This is required in order for the XML to understand the custom attributes. Of course the package name for your app will be different.

In the linked github repo there is full example that also has a custom Button that will also set a custom font.

Comments (10)


Simply awesome tutorial. One small suggestion... you may want to call out the fact that you need to reference the namespace xmlns:app="http://schemas.android.com/apk/res/com.github.browep.customfonts in your layout file for the code to work.

@itruemper , thanks. Tutorial updated to reflect.

Hi! A really nice tutorial! Two questions: 1) What was the rationale behind synchronizing on the fontCache? Isn't the code that creates the layout only supposed to run from a single thread, the main UI thread, anyways? 2) I've implemented "my" UiUtil as a ContextSingleton and have taken over the SoftReference improvement. This works all fine as is on Android >= 4.0, but on older versions (2.2, 2.3) the typeface is simply not applied and LogCat keeps saying nothing. It silently falls back to the default font. If I, however, load and set the font for each TextView anew, then the font is applied (and I have a big memory leak, of course): Have you experienced such a behaviour as well? Thanks! Thomas. PS: It would be cool if this site would denote that a registration is needed before a comment can be posted, because some not-so-cautious people could loose their written text when they're not logged in... PPS: nl2br on the comment would also be nice... :)

Nevermind my second question, it was a very stupid mistake. I forgot to set the custom font attribute in a couple of places...

Beautiful, elegant, clear, transparent, awesome... I can't find words to describe these pieces of code that worked as a charma at the first try. You helped me a lot.

WTF! This solution does not work for widgets. As in widgets you set on your home screens. The title is misleading and it gave me hope.

Fantastic!!! Thanks a lot! Is there a way to get the xml preview to work with custom fonts? I guess not, Android should come with the tagline - "Why make it easy!"

Can i set font to whole app in android?

will this font style effect the complete phone...or just application..

Will this custom font work within a homescreen widget?