Google I/O 2015!! Data Bindingのサポート出ましたね。とりあえずアレコレ触ってみた雑感を書きます。Data Bindingの使い方や機能全般に関する説明はData Binding Guide | Android Developersを参照してください。
※Android Data Binding LibraryはまだPreview版です振る舞いやシンタックスが今後変わる可能性があるので本エントリの情報と差が出てくる可能性があります。ご注意ください。
Android SDK Platform-Toolsを更新しないと動かないぽいので注意
Data Binding Guide | Android Developers通りにbuild.gradleを設定するだけではダメだった。Android SDK Platform-Toolsも更新しないと〜
Kotlinワンチャンあるで
Keynoteとかではーミリも登場しなかったKotlin。時代はC++かと思われましたが、Data Bindingを使うためにbuild.gradleにapply plugin: 'com.android.databinding'
と書くと以下の様に色々dependenciesが追加されます。そこには元気に走り回るKotlinの姿が!
apply plugin
するとdependenciesのprovidedにcom.android.databinding:compiler:1.0-rc0
が追加され、そいつがKotlinに依存している様です。com.android.databinding:compiler
はソース生成用のdependenciesなのでアプリ側へは影響を与えません。
provided - Classpath for only compiling the main sources. \--- com.android.databinding:compiler:1.0-rc0 +--- com.google.guava:guava:17.0 +--- org.apache.commons:commons-lang3:3.3.2 +--- commons-codec:commons-codec:1.10 +--- com.android.databinding:baseLibrary:1.0-rc0 +--- org.jetbrains.kotlin:kotlin-stdlib:0.11.91 | \--- org.jetbrains.kotlin:kotlin-runtime:0.11.91 +--- com.tunnelvisionlabs:antlr4:4.4 | +--- com.tunnelvisionlabs:antlr4-runtime:4.4 | | +--- org.abego.treelayout:org.abego.treelayout.core:1.0.1 | | \--- com.tunnelvisionlabs:antlr4-annotations:4.4 | +--- com.tunnelvisionlabs:antlr4-annotations:4.4 | +--- org.antlr:antlr-runtime:3.5.2 | \--- org.antlr:ST4:4.0.8 | \--- org.antlr:antlr-runtime:3.5.2 \--- commons-io:commons-io:2.4
公式ライブラリに使われるだなんてKotlinワンチャンあるで!
ソースが生成されて良い
で、com.android.databinding:compiler
はコンパイル時にBindingが定義されたLayout XMLからBinding用のソースを吐き出します。という事は実行時のSDK Versionはそんなに気にしなくてよさそうです。以下の様な単純なBindをするXMLを書くと、
activity_simple.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.sys1yagi.databindingsample.models.User"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/text" android:text="@{user.name}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/description" android:text='@{"description:"+user.description}' android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </layout>
こんなクラスが吐かれます。
ActivitySimpleBinding.java
package com.sys1yagi.databindingsample.databinding; import com.sys1yagi.databindingsample.R; import com.sys1yagi.databindingsample.BR; import android.view.View; public class ActivitySimpleBinding extends android.databinding.ViewDataBinding { //略 private final android.widget.LinearLayout mboundView0; private final android.widget.TextView mboundView1; private final android.widget.TextView mboundView2; // variables private com.sys1yagi.databindingsample.models.User mUser; public ActivitySimpleBinding(View root) { super(root, 1); final Object[] bindings = mapBindings(root, 3, sIncludes, sViewsWithIds); setRootTag(root); this.mboundView0 = (android.widget.LinearLayout) bindings[0]; this.mboundView0.setTag(null); this.mboundView1 = (android.widget.TextView) bindings[1]; this.mboundView1.setTag(null); this.mboundView2 = (android.widget.TextView) bindings[2]; this.mboundView2.setTag(null); invalidateAll(); } //略 public void setUser(com.sys1yagi.databindingsample.models.User user) { updateRegistration(0, user); this.mUser = user; synchronized(this) { mDirtyFlags |= 0b1L; } super.requestRebind(); } public com.sys1yagi.databindingsample.models.User getUser() { return mUser; } //略 }
ソースが見えるのはいいですね。軽く読むとbind対象のViewとModelをfieldに持ち、setUser(User)
が生えていて、いい感じにbindしてくれそうな気配を感じます。
Layout XMLにidを振るとそのViewがBindingクラスに生えるので良い
Layout XMLにidを振ると...
activity_views_with_ids.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/description" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:id="@+id/ok_button" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </layout>
こんな感じで生える。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityViewsWithIdsBinding binding = DataBindingUtil .setContentView(this, R.layout.activity_views_with_ids); binding.name.setText("jiro"); binding.description.setText("I'm the second son."); binding.okButton.setText("ok"); }
もうfindViewByIdは要りません。これだけでも価値がある気がしますね。
ModelでBaseObservableを継承すれば、auto updateができる
通常のBindingはXXXBinding.setHoge()
しないと反映されないですが、Model側を更新したらbindしたViewに即時反映させるといった動作も実現できます。
こんな感じ。
public class User extends BaseObservable { long id; public String name; public String description; public long getId() { return id; } public void setId(long id) { this.id = id; } @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
ModelでBaseObservableを継承し*1、auto updateしたいgetterに@Bindable
を付けて、setterでnotifyPropertyChanged(BR.name);
という風に呼べばOK。BR.name
はR.java
のBinding版みたいなもの。Layout XML定義したvariableや、@Bindable
の対象がBR
に生えます。
あとは使うだけ!
ActivityAutoUpdateBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_auto_update); User user = new User(); user.setName("Saburo"); user.setDescription("hehehe"); binding.setUser(user); binding.textInput.setText(user.getName()); binding.textInput.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { //ここでbindしたViewの方にも変更が反映される user.setName(s.toString()); } //略 });
Modelを変更、というかnotifyPropertyChanged()
を実行するsetterを呼び出すとbindしているView側にも変更が反映されます。楽ちんやで。
コンパイルエラーは結構謎いので気をつける必要がある
Layout XML側でbindするfield名が間違っていてもその場では教えてもらえません。
コンパイルすると以下のようなエラーが。
うーん情報に乏しい。ActivitySimpleBinding
のエラーなどが含まれているという事からData Bindingのエラーだろうという事がわかります。しかしどこが死んでるのかはわかりません。全体的に死ぬっぽいので数が増えると特定が難しくなるかもしれませんね。
なんでも大体bindingもできる
コンパイル時にコード生成するので、結構力技っぽい事ができるっぽいです。android:onItemClickListener="@{listener}"
という風に本来のattributeに存在しないものも書けます。
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="listener" type="android.widget.AdapterView.OnItemClickListener"/> </data> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:onItemClickListener="@{listener}" /> </layout>
で、こう
binding.setListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //do something } });
なんかこう、色々publicなsetterがあるやつは大体bind対象にできる気配があります。なんかこうこれに関しては微妙な気配を感じます。何が嬉しいんだろう、Bindingにレイアウトの持つプロパティを書けるぜみたいな感じなんだろうか。
サンプルコード
コチラにまとめてます。
https://github.com/sys1yagi/data-binding-sample
まとめ
まだ触ってない機能一杯ありますが、とりあえずサイコーでは。正式版はよ
*1:BaseObservableは実装を容易にする為に用意されたものです。継承が厳しい場合はObservable interfaceを実装すればOK