banner
jzman

jzman

Coding、思考、自觉。
github

Android JetpackコンポーネントのBindingAdapterについて

PS:過去は煙のようであり、生活を分析することを理解し、同じ場所で二度転んではいけません。残りの人生では、変わる必要があり、さらに積極的でなければなりません。進取の心を持ち、ぼんやりと一日を終わらせてはいけません。

上篇は主に DataBinding の基本的な使用法についてであり、Android Jetpack コンポーネントシリーズの記事は以下の通りです:

本記事では主に Binding adapters の使用方法について説明します。内容は以下の通りです:

  1. databinding メカニズム
  2. BindingMethods
  3. BindingAdapter
  4. BindingConversion

databinding メカニズム#

Binding adapters は特定の値を設定するためのフレームワークとして使用できます。databinding ライブラリは、関連する値の設定を行うために具体的なメソッドを指定することを許可します。このメソッド内でいくつかの処理ロジックを行うことができ、Binding adapters は最終的に望む結果を提供します。では、レイアウトファイルで databinding を使用してデータをバインドする際、どのように対応するプロパティメソッドを呼び出すのでしょうか?

 <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.name}" />

レイアウトファイルで特定のデータをバインドする際、例えば上記の TextView の text プロパティでは、バインディング時に自動的に互換性のある型の引数に対応するメソッド(setText (arg) など)を受け取ります。この時、databinding ライブラリは user.getName () の返り値の型に対応する user.setName (arg) メソッドを探します。もし user.getName () の返り値の型が文字列であれば、String 型の引数を持つ setName (arg) メソッドが呼び出され、逆に int 型であれば Int 型の引数を持つ setName (arg) メソッドが呼び出されます。したがって、データの正確性を保証するために、XML 内の式で返される値の正確性をできるだけ確保する必要があります。もちろん、実際のニーズに応じて型変換を行うこともできます。

上記の分析からわかるように、レイアウトファイルでプロパティを設定すると、databinding ライブラリは自動的に関連する setter メソッドを探して設定します。つまり、TextView を例にとると、特定の setter メソッドを見つけることができれば検証が可能です。TextView には setError (error) メソッドがあります:

@android.view.RemotableViewMethod
public void setError(CharSequence error) {
    if (error == null) {
        setError(null, null);
    } else {
        Drawable dr = getContext().getDrawable(
                com.android.internal.R.drawable.indicator_input_error);

        dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
        setError(error, dr);
    }
}

このメソッドは主にエラーメッセージを提示するために使用され、通常はコード内で使用しますが、ここではこのメソッドをレイアウトファイルに設定して使用します。参考は以下の通りです:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.name,default=name}"
    app:error="@{user.name}"/>

以下はテスト効果の画像です:

image

setError (String error) メソッドがあるため、user.name が String を返すので、ここでプロパティの形で設定することができます。

BindingMethods#

これは databinding ライブラリが提供する注釈で、View 内の特定のプロパティとそれに対応する setter メソッド名が一致しない場合にマッピングを行うために使用されます。例えば、TextView のプロパティ androidに対応するメソッドは setHintTextColor メソッドです。この時、プロパティ名と対応する setter メソッド名が一致しないため、BindingMethods 注釈を使用してこのプロパティと対応する setter メソッドをバインドする必要があります。これにより、databinding はプロパティ値に基づいて対応する setter メソッドを見つけることができます。databinding は、ネイティブ View 内のこのようなプロパティと setter メソッドの不一致の状況をすでに処理しています。以下は、TextView 内のこれらの不一致プロパティの処理を参照してください:

@BindingMethods({
        @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
        @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
        @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
        @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
        @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
        @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})
public class TextViewBindingAdapter {
    //...
}

したがって、Android フレームワーク内の View の一部のプロパティに関して、databinding ライブラリはすでに BindingMethods を使用してプロパティの自動検索とマッチングを行っています。特定のプロパティに対応する setter メソッドがない場合、databinding を使用する際にカスタム setter メソッドを作成する必要があります。この時、BindingAdapter を使用します。

BindingAdapter#

  • プロパティ設定の前処理

特定のプロパティにカスタム処理ロジックが必要な場合、BindingAdapter を使用できます。例えば、BindingAdapter を使用して TextView の setText メソッドを再定義し、入力された英語をすべて小文字に変換するカスタム TextViewAdapter は以下の通りです:

/**
 * 自定义BindingAdapters
 * Powered by jzman.
 * Created on 2018/12/6 0006.
 */
public class TextViewAdapter {

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        //省略特殊处理...
        String txt = text.toString().toLowerCase();
        view.setText(txt);
    }
}

この時、databinding を使用する際には、優先的に自分で定義した BindingAdapter が使用されます。なぜ認識できるのか疑問に思うかもしれませんが、コンパイル時に data-binding コンパイラは @BindingAdapter 注釈の付いたメソッドを探し、最終的にカスタム setter メソッドを対応する binding クラスに生成します。生成された部分コードは以下の通りです:

@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    // batch finished
    if ((dirtyFlags & 0x2L) != 0) {
        // api target 1
        //注意:ここは自定义のTextViewAdapter
        com.manu.databindsample.activity.bindingmethods.TextViewAdapter.setText(this.tvData, "これはTextViewです");
    }
}

以下は事例を用いて BindingAdapter の使用を検証します。レイアウトファイルは以下の通りです:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--デフォルトTextView-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#a37c7c"
            android:text="これはTextViewです..."
            android:textSize="16sp" />
        <!--dataBindingを使用したTextView-->
        <TextView
            android:id="@+id/tvData"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:background="#a37c7c"
            android:text="@{`これはTextViewです...`}"
            android:textSize="16sp" />
    </LinearLayout>
</layout>

自分で定義した BindingAdapter の効果は以下の通りです:

image

自分で定義した TextViewAdapter が有効になり、必要に応じてデータを特別に処理することができます。これが BindingAdapter の役割です。

  • カスタムプロパティ設定

カスタムプロパティ設定では、単一のプロパティを定義することも、複数のプロパティを定義することもできます。まずは単一のプロパティを定義します。参考は以下の通りです:

/**
 * 自定义BindingAdapters
 * Powered by jzman.
 * Created on 2018/12/7 0007.
 */
public class ImageViewAdapter {
    /**
     * 単一のプロパティを定義
     * @param view
     * @param url
     */
    @BindingAdapter("imageUrl")
    public static void setImageUrl(ImageView view, String url) {
        Glide.with(view).load(url).into(view);
    }
}

この時、レイアウトファイル内でカスタムプロパティ imageUrl を使用できるようになります。使用例は以下の通りです:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        <!--単一のカスタムプロパティ-->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/>
    </LinearLayout>
</layout>

上記のコードのテスト効果は以下の通りです:

image

これにより、imageUrl プロパティを使用してネットワーク画像を簡単に読み込むことができます。ここではスレッド切り替えの問題を心配する必要はありません。databinding ライブラリが自動的にスレッド切り替えを行います。では、複数のプロパティをカスタマイズするにはどうすればよいでしょうか。

以下は複数のプロパティをカスタマイズする方法です。定義方法は以下の通りです:

/**
 * 自定义BindingAdapters
 * Powered by jzman.
 * Created on 2018/12/7 0007.
 */
public class ImageViewAdapter {
    /**
     * 複数のプロパティを定義
     * @param view
     * @param url
     * @param placeholder
     * @param error
     */
    @BindingAdapter(value = {"imageUrl", "placeholder", "error"})
    public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {
        RequestOptions options = new RequestOptions();
        options.placeholder(placeholder);
        options.error(error);
        Glide.with(view).load(url).apply(options).into(view);
    }
}

この時、レイアウトファイル内で上記の 3 つのプロパティを使用できるようになります。すなわち、imageUrl、placeholder、error です。使用方法は以下の通りです:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        <!--複数のカスタムプロパティ-->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginTop="10dp"
            app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"
            app:placeholder="@{@drawable/icon}"
            app:error="@{@drawable/error}"/>
    </LinearLayout>
</layout>

この時、3 つのプロパティをすべて使用することで BindingAdapter が正常に機能します。もしそのうちのいくつかのプロパティのみを使用した場合、正常にコンパイルされません。では、複数のプロパティをカスタマイズしながら、いくつかのプロパティのみを正常に使用するにはどうすればよいでしょうか@BindingAdapter 注釈にはもう 1 つのパラメータ requireAll があります。requireAll のデフォルト値は true で、すべてのプロパティを使用する必要があります。これを false に設定すると、部分的にプロパティを使用できるようになります。この時、複数のプロパティをカスタマイズする際には、@BindingAdapter の requireAll 属性を false に設定する必要があります。参考は以下の通りです:

// requireAll = false
@BindingAdapter(value = {"imageUrl", "placeholder", "error"},requireAll = false)
public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {
    RequestOptions options = new RequestOptions();
    options.placeholder(placeholder);
    options.error(error);
    Glide.with(view).load(url).apply(options).into(view);
}

この時、レイアウトファイルでは部分的にプロパティを使用できます。以下のレイアウトファイルでは imageUrl と placeholder のみを使用してもコンパイルエラーは発生しません:

 <ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginTop="10dp"
    app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"
    app:placeholder="@{@drawable/icon}"/>

BindingAdapter の紹介はここまでです。

BindingConversion#

特定の状況では、プロパティを設定する際に型間の変換が必要です。この時、@BindingConversion 注釈を使用して型間の変換を行うことができます。例えば、androidプロパティは Drawable を受け取りますが、databinding の式内で色値を設定した場合、@BindingConversion が必要です。レイアウトファイルは以下の通りです:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        <!--型変換-->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@{true ? @color/colorRed : @color/colorBlue}"/>
    </LinearLayout>
</layout>

@BindingConversion を使用して型変換を行います。参考は以下の通りです:

/**
 * 型変換
 * Powered by jzman.
 * Created on 2018/12/7 0007.
 */
public class ColorConversion {
    @BindingConversion
    public static ColorDrawable colorToDrawable(int color){
        return new ColorDrawable(color);
    }
}

上記のコードのテスト効果は以下の通りです:

image

@BindingConversion 注釈を使用する際は、同じ型を使用する必要があります。上記の androidプロパティを以下のように使用することはできません:

<!--型変換-->
<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@{true ? @color/colorRed : @drawable/drawableBlue}"/>

BindingAdapter や BindingConversion は最終的に関連コードを対応する binding クラスに生成し、その後指定された View に値を設定します。これで BindingMethods、BindingAdapter、BindingConversion に関する知識の紹介は終了です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。