注意 本稿は、2015-06-23 現在の Data Binding のドキュメントをもとに書いてあります。仕様や挙動は将来変更される可能性があるので気をつけてください。
Data Binding
Google I/O 2015 で M Preview が発表され、他にも Android の新機能が発表されました。そのうちの1つが Data Binding です。
この Data Binding は、名前の通り、メモリ上のデータを XML で表されている View に結び付けるための仕組みです。
これまで、たとえばリストを表示するには、プログラム上で各 View とデータを結び付けるという作業が必要でした。今回の機能を利用することで、そのような処理を XML 内で宣言的に書けるようになります。
参考文献
- Data Binding Guide | Android Developers
- Data BindingとMultidexの兼ね合いの問題を大体倒したので実用段階待ったなし - visible true
- Android DataBinding導入の第一歩: ButterKnife+ViewHolderパターンを置き換える - Islands in the byte stream
インストール
Data Binding を利用するためには build.gradle
に下記を記述します。
dependencies {
classpath "com.android.tools.build:gradle:1.3.0-beta1"
classpath "com.android.databinding:dataBinder:1.0-rc0"
}
また jcenter がレポジトリに追加されていることも確認しておきましょう。
allprojects {
repositories {
jcenter()
}
}
各モジュールの build.gradle
では以下の記述が必要です。
apply plugin: ‘com.android.application'
apply plugin: 'com.android.databinding'
前提
gradle プラグインのバージョンは 1.3.0-beta1 以上である必要があります。また Android Studio 1.3 以上でないとコード補完やレイアウト表示がサポートされていません。
上記のサポートライブラリがコンパイル時に入っていれば、ランタイム時の Android のバージョンに制限はありません。
基本的な使い方
ここでは、POJO の User
型オブジェクトを表示する例を考えます。まず、レイアウトファイルを記述します。
レイアウトファイル
layout
要素をルートにして、直下に data
要素と通常のビューのルート要素を記述します。data
要素には使用するデータオブジェクトとその型を記述します。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
通常ならリソース ID やテキストを記述する箇所で @{user.firstName}
のように User
型オブジェクトのフィールドを指定していることに注意してください。これによって、このビューにバインドした User
オブジェクトの firstName
フィールドの値が TextView に表示されます。
バインド
さて、次にデータオブジェクトを View にバインドします。バインドするには以下のコードを実行します。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}
以上で終わりです。MainActivity を起動してみれば、表示を確認できるでしょう。
レイアウトファイル内の式
レイアウトファイル内では、@{...}
という形式でバインドされるデータの値を記述しますが、ここには式を記述することが可能です。
使用可能な演算子
- 数値演算子
+ - / * %
- 文字列連結
+
- 論理演算子
&& ||
- ビット演算子
& | ^
- 単項演算子
+ - ! ~
- シフト演算子
>> >>> <<
- 比較演算子
== > < >= <=
instanceof
- グルーピング
()
- リテラル: character, String, numeric, null
- キャスト
- メソッド呼び出し
- フィールドアクセス
- 配列アクセス
[]
- 三項演算子
?:
例
いくつか例を示します。式内でダブルクォート "
を使うときは属性値をシングルクォート '
で囲むか、ダブルクォートを "
でエスケープするか、ダブルクォートの代わりにバッククォート `
を使うかします。
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
android:text="@{"First: " + user.firstName + `, Last: ` + user.lastName}"
Null 合体演算子 (Null Coalescing Operator)
Java では使えない Null 合体演算子 (Null Coalescing Operator)も利用可能です。
android:text="@{user.displayName ?? user.lastName}"
上記の例では、左の user.displayName
が null
なら、右の user.lastName
が使われます。
NullPointerException の回避
式内では NullPointerException
が起きないようになっています。たとえば、下記の例で user
が null
の場合は、式全体の user.name
も null
になります。また、int
型の user.age
はデフォルト値、すなわち 0
になります。
android:text="@{user.name}"
android:text="@{user.age}"
リスト、配列およびマップのアクセス
リスト、配列およびマップにアクセスするには [ ]
演算子を用います。
<data>
<variable name="list" type="java.util.List<String>"/>
<variable name="sparse" type="android.util.SparseArray<String>"/>
<variable name="map" type="java.util.Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"
リソースの使用
@{...}
式内では、リソース ID を使用して、リソースにアクセスすることも可能です。また String フォーマットや Plurals リソースを使うこともできます。
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
なお、通常の Plurals リソースを使う場合と同様に、Plurals リソース内に String フォーマットがある場合は、引数を二つ渡さなければなりません。
<plurals name="orange">
<item quantity="one">Have an orange.</item>
<item quantity="other">Have %d oranges.</item>
</plurals>
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
Observable オブジェクト
実はデータバインディングでは、データオブジェクトを Observable にすることで、動的に View の更新をすることができます(ここで言う Observable は Rx の Observable とはまったくの無関係です)。
上記に示した手順では、たとえば、User
オブジェクトの setFirstName(String)
メソッドを呼んだときにバインドされている TextView 側に変更は通知されません。User
クラスを Observable にすることで、その通知が可能になります。
任意のデータクラスを Observable にするには3つの方法があります。いずれかの方法を使うことで、データオブジェクトを更新したときに動的に View も更新されるようになります。
1. BaseObservable
を継承させる
データクラスが BaseObservable
を継承し、Setter に @Bindable
アノテーションを、Getter 内で notifyPropertyChanged(View)
を呼ぶことで、そのデータオブジェクトの変更が View に通知されるようになります。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getFirstName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
2. ObservableField
を利用する
上記の方法はちょっと手間です。もっと簡単に行いたい場合は、ObservableField
を使う方法もあります。ただし、この場合は変更を監視したいフィールドのクラスがすべて ObservableField
になってしまいます。
private static class User extends BaseObservable {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
3. ObservableMap
を利用する
もっと簡単に行いたい、マップで十分、という場合は ObservableMap
を使うこともできます。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
View へのアクセス
Data Binding を使ってうれしいもう1つのところは、コード内での View へのアクセスが容易になるところです。以下のように、いままで findViewById(int)
で View へのアクセスを得ていたところを、以下のようにできます。
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
binding.firstName.setText(... // <- TextView
これを使うと、カスタム Adapter 内で ViewHolder の代わりに使うことができます。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
final ItemArticleBinding binding = ItemArticleBinding.inflate(LayoutInflater.from(this.getContext()));
convertView = binding.getRoot();
convertView.setTag(binding);
}
final ItemArticleBinding binding = (ItemArticleBinding) convertView.getTag();
Article article = this.getItem(position);
binding.setArticle(article);
Picasso.with(this.getContext()).load(article.getUser().getProfileImageUrl()).into(binding.userIcon);
return convertView;
}
さらに深く利用するために
Data Binding では、さらに様々な機能が用意されています。たとえば、生成されるクラスやメソッド名を変更したり、カスタムバインディングを定義することで、データオブジェクトの画像 URL を XML 内で指定したら自動的に画像をロードするといったこともできます。
また、実際の利用については、すでにいくつか罠があることが知られています。それらについては、sys1yagi さんのブログに詳しくまとめられています。
参考: Data Binding カテゴリーの記事一覧 - visible true
Tips
- Data Binding のエラーは分かりにくいので、もしエラーを見ても分からない場合は
--debug
オプションを付けましょう。
まとめ
- コードを生成するだけなので、ランタイム時に Android のバージョンに依存しないのは素晴らしい
- とりあえず、ButterKnife の代わりにはなりそう