Paul Brower bio photo

Paul Brower

Android development for fun and profit.

Email Twitter Github Stackoverflow Codementor

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.