既存アプリにData Bindingを導入してみて
こんにちは。Android Studio 2.0の正式版が待ち遠しくてたまらない @rooandqoo です。 最近、今関わっているAndroidアプリ「pixivマンガ」にData Bindingを導入したので、所感をお話します。
Data Bindingとは
Google I/O 2015で発表された機能で、XMLに書いたViewのプロパティをバインドしてくれるスグレモノです。
従来は、たとえばfirstName
と lastName
というプロパティを持つ User
というモデルを定義し、画面に2つのプロパティを表示する場合、レイアウトファイルとActivityのコードは以下のようになっていました。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/firstName" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/lastName" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
class MainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); ((TextView) findViewById(R.id.firstName)).setText("Test"); ((TextView) findViewById(R.id.lastName)).setText("User"); }
Data Bindingを使うと、以下のように記述することができます。
<?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:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <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>
class MainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); User user = new User("Test", "User"); binding.setUser(user); }
Activityでモデルを定義し、自動生成されるクラスに渡してやれば、あとはレイアウト側がよしなに表示までをやってくれる、という塩梅になっています。
公式ドキュメントを見るに様々なことができそうですが、一番インパクトが大きいなと思ったのは Android のコードにありがちなfindViewById()
やsetOnClickListener()
を一掃することができたり、ListView などを使う上でほぼ必須といえる ViewHolder の実装が不要になる点です。
今回の変更では ButterKnife というライブラリを使ってViewのバインドを行っていた箇所をData Bindingを使うように置き換えたので、実際に変更を加えた部分について簡単にご紹介していきます。
導入
build.gradle
に3行書き足すだけで使い始めることができます。
android { ... dataBinding { enabled = true } }
Viewをバインドしていた部分を置き換える
まず、レイアウトファイルのトップレベルを<layout></layout>
で囲みます。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> ... </LinearLayout>
↓
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> ... </LinearLayout> </layout>
また、クラスのonCreate()
内で、ButterKnifeを使ってバインドしていた部分は以下のように置き換えます。
public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_xxxx); ButterKnife.bind(this); }
↓
public void onCreate(Bundle savedInstanceState) { ActivityXXXXBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_xxxx); binding.setActivity(this); }
このbinding
に、レイアウトのIDから自動生成されたメンバが含まれます。
例えばtitle_text
というIDを降ったTextViewにテキストをセットしたい場合はbinding.titleText.setText("hoge")
としてやると良いです。
ButterKnifeにおいて@OnClick
などでイベントを取得していた場合、XMLファイルの各アイテムに以下のような行を追加します。
<data> <variable name="activity" type="(Activityのパッケージ)" /> </data> ... <Button> ... android:onClick="@{activity.onXXXClick}" ... </Button>
AdapterクラスにData Bindingを実装する
ListView
やGridView
を使う上で必要不可欠なAdapterクラスですが、ほとんどの場合 ViewHolder
を実装してビューの使い回しを実現していると思います。
Data Bindingを使うと、ViewHolderを作らずともビューの使い回しが実現できて便利です。
既存のAdapterクラス内 getView()
の実装はこのようになりました。
@Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false); holder = new ContentViewHolder(convertView); convertView.setTag(holder); } else { holder = (ContentViewHolder) convertView.getTag(); } holder.hogeText.setText("hoge"); } ... public static class ViewHolder { @Bind(R.id.hoge) public TextView hoge; public ViewHolder(View view) { ButterKnife.bind(this, view); } }
↓
@Override public View getView(final int position, View convertView, ViewGroup parent) { XXXBinding binding; if (convertView == null) { binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.list_item, parent, false); convertView = binding.getRoot(); convertView.setTag(holder); } else { binding = (XXXBinding) convertView.getTag(); } // ModelオブジェクトをXML内に持たせるだけで済む Model model = models.get(position); binding.setModel(model); }
ViewHolderに持たせたViewに、いちいちオブジェクトをセットしなくて済むのも良いところです。
@BindingAdapterを使ってみる
ListViewのアイテムの中で、アイテムが持つパラメータの状態に合わせてViewを切り替えるといったことを実現したい時に便利なアノテーションが@BindingAdapter
です。
以下のように、漫画を「読んだ」「読んでる途中」「まだ読んでいない」というステータスに合わせて、アイテムの背景色を変えるコードを実装してみます。
まず、Adapter内に@BindingAdapter
を付加したメソッドを実装します。(ViewHistory
は閲覧履歴を保存するためのモデルです)
@BindingAdapter("containerBackground") public static void setContainerBackground(View container, ViewHistory viewHistory) { if (viewHistory.hasRead()) { itemContainer.setBackgroundResource(R.drawable.bg_item_viewed); } else { itemContainer.setBackgroundResource(R.drawable.bg_item); } }
第一引数に対象のViewを、第二引数以降に、メソッド内で使用するパラメータを指定しているのがポイントです。
次に、XMLのアイテムに、アノテーションをつけたcontainerBackground
を追記します。
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="jp.pxv.android.manga.model.ViewHistory" /> <variable name="viewHistory" type="ViewHistory" /> </data> <LinearLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="wrap_content" ... app:containerBackground="@{viewHistory}"> ... </LinearLayout> </layout>
ViewHistory
を使うために<data></data>
内にインポートが必要となり、少しだけXMLが複雑になりますが、AdapterのgetView()
内で分岐を書くよりはいくらか綺麗になるのではないでしょうか。
導入のメリットとデメリット
Activityをはじめとしたクラス内にViewのメンバを用意する必要がなくなるため、Java側のコードがすっきりします。
特にAdapterを使うにあたってほぼ必須だったViewHolderクラスの生成が不要になったのは個人的に革命でした。
デメリットとしては、まず<layout></layout>
で囲む必要があるために必然的にXMLファイルのネストが1段階深くなることが挙げられます。
アノテーションをつけて定義したメソッドがどこからでも参照できたり、XML内に変数を色々定義できるのも便利なのですが、気をつけないとすぐにどこに何があるのかわからなくなってしまうのも難点です。
まとめ
既存プロジェクトへのData Binding導入について簡単に解説しました。 公式の機能ということもありますので、新規開発の際には積極的に使っていくと良いと思います。 複数人で触るプロジェクトへの導入には、どこからどこまでをData Bindingに任せるかしっかり線引きをしておくと良さそうです。