前面总结了 ViewModel、LiveData および Lifecycle アーキテクチャコンポーネントの使用について、以下の記事を先に読んで詳細を理解してください:
- Android Jetpack コンポーネントの Lifecycle 編
- Android Jetpack コンポーネントの LiveData 編
- Android Jetpack コンポーネントの ViewModel 編
本篇は主に dataBinding の基本的な使用に焦点を当てており、主な内容は以下の通りです:
- dataBinding のサポート
- レイアウトファイルの設定
- データのバインディング
- 特殊な式
- イベントバインディング
- カスタムバインディングクラス
- その他
dataBinding のサポート#
dataBinding を使用するには、app モジュールの build.gradle ファイルで設定を行う必要があります。具体的には以下の通りです:
// dataBindingをサポートする設定
dataBinding {
enabled = true
}
レイアウトファイルの設定#
Data Binding Library は、レイアウト内のビューとデータオブジェクトをバインドするために必要なクラスを自動生成します。Data Binding Library のレイアウトファイルは layout タグをルートタグとし、その後に具体的なデータ要素とビュー要素が続きます。このビュー要素はバインドされたレイアウトファイルの位置を示します。レイアウトファイルの例は以下の通りです:
<?xml version="1.0" encoding="utf-8"?>
<!--dataBindingは必ずlayoutをルートタグとする-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--データオブジェクト-->
<data>
<variable name="user" type="com.manu.databindsample.data.User"/>
</data>
<!--ビュー要素-->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--dataBindingで具体的な属性値の設定は"@{}"内で行います-->
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name,default=名前}"/>
</LinearLayout>
</layout>
データエンティティ#
"@{user.name}" の中の name 属性は最終的にデータオブジェクトの getter メソッドを呼び出します。つまり、getter メソッドです。もちろん、データオブジェクトに対応する name メソッドがある場合、対応する getter メソッドがないときは同名のメソッドが呼び出されます。両方が存在する場合は、対応する getter メソッドが優先的に呼び出されます。以下を参照してください:
/**
* データエンティティ
* Powered by jzman.
* Created on 2018/11/28 0028.
*/
public class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
//両方が存在する場合は優先的に呼び出される
public String getName() {
return name;
}
//getterメソッドが存在しない場合は呼び出される
public String name() {
return "name";
}
//...
}
データのバインディング#
dataBinding は各レイアウトファイルに対応するバインディングクラスを生成します。デフォルトでは、クラス名はレイアウトファイルの名前に基づいています。例えば、レイアウトファイル名が activity_main の場合、そのレイアウトファイルに対応するバインディングクラスは ActivityMainBinding です。このクラスはデータオブジェクトからレイアウトファイルへのすべてのバインディングを含みます。では、Activity でデータとビューをどのようにバインドするのでしょうか。以下のようにバインドします:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//バインディングクラスを生成
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
//ビューとデータをバインド
User user = new User("山田");
binding.setUser(user);
}
}
Fragment でのバインド方法は以下の通りです:
//inflateメソッド
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
FragmentOneBinding oneBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_one,container,false);
User user = new User("小明");
oneBinding.setUser(user);
return oneBinding.getRoot();
}
//bindメソッド
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
View view = inflater.inflate(R.layout.fragment_one,container,false);
FragmentOneBinding oneBinding = FragmentOneBinding.bind(view);
User user = new User("小明");
oneBinding.setUser(user);
return view;
}
他のレイアウトのバインディング方法は基本的に生成されたバインディングクラスの inflate メソッドと bind メソッドを使用することで完了します。
特殊な式#
- 三項演算子の簡略化
//完全な書き方
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
//簡略化
android:text="@{user.displayName ?? user.lastName}"
- NullPointerException の処理
生成されたバインディングクラスは null 値を自動的にチェックし、NullPointerException を回避します。式 @ {user.name} の中で、user が null の場合、user.name にデフォルト値 null が割り当てられます。user.age を参照する場合、age の型が int であれば、データバインディングはデフォルト値 0 を使用します。
- コレクション
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="java.util.Map" />
<import type="java.util.List" />
<!--Map-->
<variable
name="map"
type="Map<String,String>" />
<variable
name="key"
type="String" />
<!--List-->
<variable
name="list"
type="List<String>" />
<variable
name="index"
type="int" />
</data>
<!--MapとListの値の取得方法に注意-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map.key}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[0]}" />
</LinearLayout>
</layout>
Map 型のデータは式 @{} の中で map.key を使用して Map コレクション内の key に対応する value を取得できます。List 型のデータはインデックスを使用して値を取得します。また、variable タグ内で使用される < はエスケープする必要があり、< を使用して < の代わりにする必要があります。そうしないと、以下のようなエラーが発生します:
> Error: 要素タイプ "variable" に関連付けられた "type" 属性値は '<' 文字を含むことができません。
- @{} 式内で文字列を使用する
@{} 式内で文字列を文字列変数ではなく使用するには、2 つの方法があります。具体的には以下の通りです:
<!--シングルクォートを使用-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{map["key"]}' />
<!--バッククォートを使用-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`key`]}" />
<!--@{}内で文字列リソースを使用できます-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/app_name}"/>
イベントバインディング#
dataBinding を使用する際、メソッド参照またはリスナーをバインドする方法でイベントリスナーを設定できます。この 2 つの違いは、前者のイベントリスナーはデータバインディング時に作成され、後者はイベントが発生したときにバインドされる点です。
- メソッド参照
イベントはイベント処理メソッドに直接バインドできます。通常の android 属性と比較して、この設定方法はコンパイル時に関連処理が行われます。このメソッドが存在しない場合やメソッドのシグネチャが正しくない場合、コンパイル時エラーが発生します。まず、以下のようにイベント処理メソッドを作成します:
/**
* Powered by jzman.
* Created on 2018/11/30 0030.
*/
public class MyHandler {
/**
* @param view
*/
public void onClickEvent(View view){
Toast.makeText(view.getContext(), "クリックしてね", Toast.LENGTH_SHORT).show();
}
}
次に、対応するレイアウトファイルで具体的な onClick などのイベントを設定します。ここでは onClick イベントの例を示します:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="handler"
type="com.manu.databindsample.handler.MyHandler"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--第一の方法-->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="クリックしてね"
android:onClick="@{handler::onClickEvent}"/>
<!--第二の方法-->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="クリックしてね"
android:onClick="@{handler.onClickEvent}"/>
</LinearLayout>
</layout>
最後に、対応する Activity でデータオブジェクト handler を設定します。具体的には以下の通りです:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
binding.setHandler(new MyHandler());
}
これにより、メソッド参照イベントが成功裏にバインドされました。
- リスナーのバインディング
この方法は、イベントが発生したときにイベントリスナーを作成します。メソッド参照と比較して、カスタムパラメータをイベントコールバックに渡すことができます。まず、以下のようにイベントコールバックメソッドを作成します:
/**
* リスナーのバインディング
* Powered by jzman.
* Created on 2018/12/3 0003.
*/
public class MyPresenter {
private Context mContext;
public MyPresenter(Context mContext) {
this.mContext = mContext;
}
public void onClickEvent(User user) {
Toast.makeText(mContext, user.getName(), Toast.LENGTH_SHORT).show();
}
}
次に、対応するレイアウトファイルで具体的な onClick などのイベントを設定します。ここでは onClick イベントの例を示します:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.manu.databindsample.data.User" />
<variable
name="presenter"
type="com.manu.databindsample.handler.MyPresenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onClickEvent(user)}"
android:text="クリックしてね 3" />
</LinearLayout>
</layout>
最後に、対応する Activity でデータオブジェクト presenter を設定します。具体的には以下の通りです:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
binding.setUser(new User("android"));
binding.setPresenter(new MyPresenter(this));
}
これにより、イベントリスナーイベントが成功裏にバインドされました。上記の xml でイベントメソッドを呼び出す際、現在の View を設定できます。具体的には以下の通りです:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(view) -> presenter.onClickEvent(user)}"
android:text="クリックしてね 3" />
対応するイベントコールバックメソッドは以下の通りです:
public class MyPresenter {
public void onClickEvent(View view, User user){}
}
また、イベントバインディング時に三項演算子を使用することもでき、その場合は void を演算子として使用できます。使用方法は以下の通りです:
android:onClick="@{(v) -> v.isVisible() ? presenter.doSomething() : void}"
カスタムバインディングクラス#
前述の通り、デフォルトではバインディングクラス名はレイアウトファイル名によって決まりますが、カスタムバインディングクラスをどのように指定するかは、レイアウトファイルの data タグに class 属性を使用してカスタムバインディングクラス名を指定することで実現できます。もちろん、カスタムクラス名の前に完全なパッケージパスを追加することもできます。以下を参照してください:
<!--カスタムバインディングクラス-->
<data class="com.manu.test.CustomBinding">
<variable name="user" type="com.manu.databindsample.data.User"/>
</data>
その他#
dataBinding で import キーワードを使用して関連クラスをインポートします。java.lang.* の下にある関連クラスはデフォルトで自動的にインポートされます。同じ名前の View がある場合は、alias を使用して区別できます。以下を参照してください:
<import type="android.view.View"/>
<import type="com.manu.View"
alias="MView"/>
variable キーワードを使用して、xml レイアウト内で使用する変数を定義します。include レイアウトを使用する場合、bind を使用して include で含まれるレイアウトと主レイアウトで同じ変数をバインドする必要があります。include で含まれるレイアウト test_layout.xml ファイルを作成します。具体的には以下の通りです:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="userTest"
type="com.manu.databindsample.data.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`これはincludeコンテンツです...`+userTest.getName(),default=user}" />
</LinearLayout>
</layout>
次に、主レイアウトでこのレイアウトを参照します。具体的には以下の通りです:
<?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">
<data>
<variable
name="user"
type="com.manu.databindsample.data.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--bindで変数をバインド-->
<include
layout="@layout/test_layout"
bind:userTest="@{user}" />
</LinearLayout>
</layout>
これにより、bind を使用して 2 つのレイアウトで使用される User 型の変数がバインドされ、2 つのレイアウトで使用される変数が同じ変数になります。また、dataBinding は merge タグをサポートしていません。次回は Binding adapters の紹介を続けます。