DataBindingチュートリアル第3回 – BindingAdapter(バインディングアダプター)

第1回でデータをレイアウトにバインディングして中身を表示する方法を、第2回ではイベントのハンドリングを説明しました。 この回ではBindingAdapterという仕組みを使ってレイアウトにより高度なアクションを”Adapt(適用)”する方法について説明したいと思います。

BindingAdapter

BindingAdapterの使い方はクリエイティビティをもっとも発揮しやすい箇所ではあるのですが、逆に言えば最初は何のために使って良いかわからないものでもあります。 ここではAndroidアプリを作る時にかなりの確率で実装することになる 画像のダウンロード -> ImageViewへの表示 というタスクをBindingAdapterを使って便利に実装する方法について説明します。

第1回第2回で使ったプロジェクトにUserの画像を表示するようにしてみます。

モデルクラスの準備

まずUserクラスに新たにprofileImageUrlというメンバーを足してここに画像のUrlを格納できるようにします。

public class User {
    String name;
    String profileImageUrl;

    public User(String name, String profileImageUrl){
        this.name = name;
        this.profileImageUrl = profileImageUrl;
    }

    public String getName(){
        return name;
    }

    public String getProfileImageUrl() {
        return profileImageUrl;
    }

この画像をactivity_main.xmlでImageViewに表示させるようにします。

従来のやり方

通常ですとMainActivityでUserのイメージをダウンロードして、ダウンロードが終わったらactivity_mainのImageViewfindViewByIdで取得してそこにセットします。

BindingAdapterを使ったやり方

今回は下のようにbind:imageUrlというタグを足すだけでイメージが勝手にダウンロードされてダウンロードが終わったら表示させるようにします。

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

bind:imageUrlというタグは見たことがないと思いますが、それはこのタグがBindingAdapterを使って独自に定義したタグだからです。

それではこのタグの定義を見ていきます。

public class User {
    String name;
    String profileImageUrl;

    public User(String name, String profileImageUrl){
        this.name = name;
        this.profileImageUrl = profileImageUrl;
    }

    public String getName(){
        return name;
    }

    public String getProfileImageUrl() {
        return profileImageUrl;
    }

    @BindingAdapter({"bind:imageUrl"})
    public static void loadImage(ImageView view, String profileImageUrl) {
        Picasso.with(view.getContext()).load(profileImageUrl).into(view);
    }

}

このタグは上のようにUserクラスの中に定義されています。 @BindingAdapter({"bind:imageUrl"})というアトリビュートをメソッドの前に書いて、あとはスタティックメソッドを定義します。このメソッドの名前はなんでも構わないですが、パラメータの1番目にこのタグが定義されたImageView、2番目にタグに渡されたUrl文字列が来るためパラメータの型と順番には注意してください。
メソッドの中ではPicassoライブラリを使って画像をダウンロードしてImageViewに表示しています。Picassoを使うためにappモジュールのgradleのdependenciesに以下のようにPicassoを足してください。

dependencies {
    ---
    compile 'com.squareup.picasso:picasso:2.5.2'

公式ドキュメントの例では@BindingAdapter({"bind:imageUrl"})という風にbindというネームスペースをつけてタグを定義しているのですが、これだとコンパイル時にWarningが出てしまいます。そのため、bindというネームスペースはつけずに定義しています。    (activity_main.xmlにxmlns:bindという定義を足してもWarningは消えませんでしたが、何かやりようがあるのかもしれません)

必要なコードはこれだけです!もう一度レイアウトXMLに戻って見てみますと、このタグにUserオブジェクトのprofileImageUrlを渡すだけでイメージのロードが行われます。

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

1つハマリポインがあり、bind:imageUrlタグを使うためにトップレベルのlayoutタグの中に下のようにbindというネームスペースを足さなければいけません。xmlns:appの前にxmlns:bindというネームスペースを足してください。

<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">

BindingAdapterを作ることでImageViewが表示されるときにプロフィール画像のダウンロードが始まりダウンロードが完了するとImageViewに画像が表示されます。

あとはMainActivityでUserを初期化するときに下のようにユーザーのプロフィール画像のリンクを渡すようにします。

User user = new User("Benjamin", "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/BenFranklinDuplessis.jpg/1280px-BenFranklinDuplessis.jpg");
        binding.setUser(user);

最終的なレイアウト

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

<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}"/>

        <Button
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:text="SEND"
            android:onClick="@{handler::onClickSend}"
            />

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

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

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


    </LinearLayout>
</layout>

実行結果

起動するようと下のようにベンジャミンフランクリンの写真がロードされると思いますので是非実行して見てください。

スクリーンショット

まとめ

サンプルプロジェクトはこちらからダウンロードできます。

ニュースやコミュニケーションアプリを作ると画像のロードと表示をする箇所がたくさん出てきてすぐにコード量が増えてしまいますがBindingAdapterを使うとこのようにスッキリと書くことができます。 今回Userクラスの中にBindingAdapterを定義しましたが、タグの定義はグルーバルに有効なので実はどのクラスに書いてもbind:imageUrlというタグが使えます。User以外にも画像ダウンロードと表示が起きるようなアプリではユーティリティークラスのようなところにタグの定義を書いておいてもいいかもしれません。  

今回はBindingAdapterを”イメージのロード”という頻出するタスクのために使いましたが、BindingAdapterはもっと多くの使い道があります。筆者が公開しているオープンソースライブラリInAppTranslation for Androidでは、BindingAdapterの中でUI中のテキストをユーザの言語に翻訳して表示するということをしています。興味のある方はソースも非常にシンプルですので読んで見てください。
次回以降、BindingAdapterの色々な使い方を紹介していこうと思います。  

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

NEXT

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