DataBindingチュートリアル第4回 – カスタムフォント

これまでカスタムフォントをUIで表示するのはコード上で全てのTextViewにフォントを設定したり、TextViewのサブクラスを作ったりと非常に手間のかかる作業でした。
Using Custom Font
データバインディングを使うと下のようにカスタムフォントをレイアウト上で指定できるようになります。

<TextView android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.name}"
    bind:font="@{`AppleGaramond-BoldItalic.ttf`}"
    />

bind:fontタグにフォントのファイル(ttfやotfファイル)の名前を指定するだけです。 これでActivityクラスのOnCreateの中で1つ1つTextViewにフォントを設定したり、フォントの種類ごとにTextViewのサブクラスを作っていた今までのやり方から解放されます。

第1回〜第3回のDataBindingチュートリアルを見た人はもうピンときたかもしれません。それではどのようにこのbind:fontタグを実現しているか説明します。

BindingAdapterの作成

下のようなBindingAdapterを作ります。作る場所はどのクラス(MainActivity.javaやUser.java)でもいいのですが、今回は新たにUtility.javaというクラスを作ってそこに置きました。

@BindingAdapter({"bind:font"})
public static void setTypeface(TextView textView, String fontName) {
    Typeface typeface;
    if(typefaceCache.containsKey(fontName)){
        typeface = typefaceCache.get(fontName);
    }else{
        typeface = Typeface.createFromAsset(textView.getContext().getAssets(), "fonts/" + fontName);
    }

    textView.setTypeface(typeface);
}

このメソッドの中でtypefaceCacheというHashMapを使っていますが、これはUtilityクラスの中に下のように宣言します。

private static Map<String, Typeface> typefaceCache = new HashMap<>();

これで完了です。

実行結果

実行するとこのようにフォントがカスタムフォントに入れ替わっているのがわかると思います。Bejaminの部分はAppleというフォントを、ボタンのところにRoboto-Boldというフォントを指定しています。

スクリーンショット

setTypefaceメソッドの詳細

もう一度見て見ますとBindingAdapterの実装は下のようになります。

public class Utility {

    //Typefaceオブジェクトを保持するキャッシュ
    private static Map<String, Typeface> typefaceCache = new HashMap<>();

    @BindingAdapter({"bind:font"})
    public static void setTypeface(TextView textView, String fontName) {
        Typeface typeface;
        if(typefaceCache.containsKey(fontName)){
            typeface = typefaceCache.get(fontName);
        }else{
            typeface = Typeface.createFromAsset(textView.getContext().getAssets(), "fonts/" + fontName);
        }

        textView.setTypeface(typeface);
    }
    ...

タイプフェースのファイルからTypefaceクラスのオブジェクトを作成し、それをTextViewにセットしています。一度作ったTypefaceオブジェクトはtypefaceCacheというHashMapに保存して2回目からそれを使うようにしています。これで1フォントファイルあたり1つのTypefaceオブジェクトしか作られないようしまう。このようにキャッシュしない場合TextViewやButtonの数だけTypefaceオブジェクトが作られてしまうので注意してください。
(補足ですが、このsetTypefaceメソッドは第1引数にTextViewオブジェクトを取るようになっていますが、bind:fontタグはButtonやImageButtonなどにも使えます。これはButtonなどのこれらのクラスがTextViewの子クラスだからです。)

最終的なレイアウト

確認までにactivity_main.xmlの最終的な形は下のようになります。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="user"
            type="com.goldrushcomputing.databindingbasicsamples.model.User"/>

        <variable
            name="handler"
            type="com.goldrushcomputing.databindingbasicsamples.MainActivity"/>
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp">

        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            bind:imageUrl="@{user.profileImageUrl}"
            />

        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"
            android:textSize="20sp"
            bind:font="@{`AppleGaramond-BoldItalic.ttf`}"
            />

        <Button
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="SEND"
            android:onClick="@{handler::onClickSend}"
            bind:font="@{`Roboto-Bold.ttf`}"
            />

        <Button
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:text="INVITE"
            android:onClick="@{() -> handler.onClickInvite(user)}"
            bind:font="@{`Roboto-Bold.ttf`}"
            />

        <Button
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:text="LIKE"
            android:onClick="@{(view) -> handler.onClickLike(user)}"
            bind:font="@{`Roboto-Bold.ttf`}"
            />

        <Button
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:text="SHARE"
            android:onClick="@{(view) -> handler.onClickShare(view, user)}"
            bind:font="@{`Roboto-Bold.ttf`}"
            />


    </LinearLayout>
</layout>

サンプル

サンプルプロジェクトはこちらからダウンロードできます。第2回で作成したbind:imageUrlタグのBindingAdapterもUtilityクラスに移動してあります。

まとめ

DataBidningを使うことでonCreateの中で1つ1つTextViewにフォントを設定したり、フォントの種類ごとにTextViewのサブクラスを作ることなくシンプルなタグだけでカスタムフォントの設定ができるようになりました。これでデザイナーさんがプロジェクトの途中にタイプフェースをどんどん変えていっても心を乱すことなく対応ができるようになるのではないでしょうか.

Android Oからはxmlにフォントを定義して使えるような新しい仕組みが導入されるようですので、将来的にはDataBindingを使わなくてもカスタムフォントの管理や設定が楽になると思います。
Android O - Working With Fonts  

チュートリアルの内容を自分のプロジェクトに反映させる時にわからないことや問題にぶつかった時のために、Fourhandsではオンラインのメンタリングサービスを提供しています。
ビデオチャットやスクリーン共有をつかて実際にコードをメンターに見てもらいながら問題の解決や、考え方のアドバイスがもらえるサービスですので、是非活用して見てください。
fourhands.com

NEXT

DataBindingチュートリアル第5回 – データに表現させる