今更遅れてDataBinding事始め(3)[include]
前回のお話
今回は単方向の拡張として includeレイアウトの話
inlcude bindingを使った 体感的な結論
include bindingするだけでビルド時間が増える
- InstantRun有効で、
- 変更なし5秒
- レイアウト変更1分超え
- CPU 200-300とかになる
- メモリどか食い
- InstantRun有効で、
レイアウトプレビューとかエラー必然でまともに見れない
- よく変換処理?で暴走する
自分は殺すシェルを用意して常に使ってたりします いまさら再導入 peco for mac (2) - exception think
- レイアウトを生成後、エディタ補完用のindexを作っているはずだが
- 結構な確率で コード補完/importショートカット が効かなくなる*1
- clean rebildをし直せば改善はする
- gralde sync だけだとファイル変更済みフラグが相変わらずイマイチなので効かないことも
- 結構な確率で コード補完/importショートカット が効かなくなる*1
まあ、data-binding layout自体
- layout.xml を変更しただけだと
- XXXBindingにid追加やid変更などの変更が適応されない
時点で、未だ実用レベルではないとは思うんだけどな。。
まあXCodeと比較されて揶揄される下記の状況に戻っただけでは有りますが。。
- InstantRunで爆速開発?
- => ビルド中にお茶が飲める開発
JPPとかのAPT利用だと、それほどストレスはたまらないので
毎回フル変換が必須な作りが駄目な気がする
対策的な案
- Fragmentとかつかってbindingレイアウトをシンプルにする
- include-binding は極力使わない
- テスト的に2−3段階 include-binding試してみたら、解釈が劇的に時間がかかる感じ
あたりが無難かなーと
よく記載されているbindingサンプル例
- app/layout_main.xml
<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.example.User"/> </data> <LinearLayout android:id="@+id/root_layout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include android:id="@+id/name_layout" layout="@layout/name" bind:user="@{user}"/> <include android:id="@+id/contact_layout" layout="@layout/contact" bind:user="@{user}"/> </LinearLayout> </layout>
- app/name.xml
<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.example.User"/> </data> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}"/> </layout>
- app/contact.xml
<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.example.User"/> </data> <TextView android:id="@+id/contact" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.contact}"/> </layout>
動かないらしいケース(以前はそうだったらしい)
- app/layout_main.xml
<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.example.User"/> </data> <!-- merge直下のincludeなのでこれはダメ --> <merge> <include android:id="@+id/name_layout" layout="@layout/name" bind:user="@{user}"/> <include android:id="@+id/contact_layout" layout="@layout/contact" bind:user="@{user}"/> </merge> </layout>
これ、多分次のidが認識しない件と同件なだけだと思う
で、現在は変換前チェックタスク辺りで
- rootタグにmergeタグは使えないよ
エラーになるので、記述自体ができないイメージ。
data-bindingの変換処理的な挙動について
data-bindingをつかったレイアウトをビルドした場合
- app/build/generated/source/apt/debug
- bind記述をしたレイアウトのid一覧、bind:user等の処理を抽出した 生成コード
- app/build/intermediates/data-binding-info
- bind:user等の処理を抽出したxml部分
- app/build/intermediates/data-binding-layout-out
- 上記を除いた通常のレイアウト
- bindingを使っていないレイアウトや画像とかも一切合切ここに放り込まれる
が作成されます*2
- app/layout_main.xml
<LinearLayout android:id="@+id/root_layout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <include android:id="@+id/name_layout" layout="@layout/name"/> <include android:id="@+id/contact_layout" layout="@layout/contact"/> </LinearLayout>
なレイアウトが生成されてapkに梱包されるわけです
でこれに対して
- data-binding処理を使わなければ、
- binding記述を除かれた上記レイアウトが利用される
- binding宣言された属性はレイアウト的に除去されるので、
- 必須属性に関して記述すると勿論レイアウトエラーになる
で通常のレイアウトとして成立すれば、上記のmergeタグとかも問題ないわけです。*3
data-bindingでふられるidに関して(1)
- root_layout => LayoutMainBinding.rootLayout
- name_layout => LayoutMainBinding.nameLayout.name
- contact_layout => LayoutMainBinding.contactLayout.contact
includeされるレイアウトで注意しなければいけないのは
- nameLayout は NameBinding
- contactLayout は ContactBinding
であり、includeされるレイアウトにidを振っておかないと実体参照が出来ないということです。
RootLayoutBinding binding = DataBindingUtil.setContentView(this, R.id.root_layout);
TextView name = binding.nameLayout.name;
TextView contact = binding.contactLayout.contact;
- 通常の findViewByIdだと
setContentView(R.id.root_layout); TextView name = (TextView)findViewById(R.id.nameLayout); TextView contact = (TextView)findViewById(R.id.contactLayout);
となるので混乱するかも
data-bindingでふられるidに関して(2)
- あと、これは現状の制限かもしれませんが、include先でふられるidに関して
- name ・・OK
- name_code ・・NG
の形で、2段階目の変数は大文字変換されてnameCodeと変換されないようです*4
DataBinding時に、includeタグのidが認識しない件
DataBindingの対象のレイアウトは、
idを振っていると自動的に認識される仕様らしい。*5
findViewByIdが要らなくなるのでButterKifeの代わりになると紹介されているサイトも有るのですが。。。
XXXBinding.ID と うまく認識しないケース
- app/layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="activity" type="com.example.Activity"/> </data> <LinearLayout android:id="@+id/root_layout" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include android:id="@+id/name_layout" layout="@layout/name"/> <include android:id="@+id/contact_layout" layout="@layout/contact"/> </LinearLayout> </layout>
動いたケース
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="activity" type="com.example.Activity"/> </data> <LinearLayout android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <include android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/name_layout" layout="@layout/name"/> <include android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/contact_layout" layout="@layout/contact"/> </LinearLayout> </layout>
ようは、
- android:layout_width/android:layout_height を書かないと 単純なincludeレイアウトの場合、id抽出対象のView として認識されない
- ただincludeレイアウトに 引数的な bind記述があると認識する。
- その場合、includeタグにidをふらないと、プログラム上からはアクセスが出来ない
Bindする変数をlayout上で指定するケース
http://wpdev.hatenablog.com/entry/2016/05/30/205434
にかかれているお話。
- app/layout_main.xml
<include layout="@layout/commmon_view" bind:iconRes="@drawable/icon" bind:textRes="@string/text" />
は、引数は実体のみなので出来ないよ。というお話
実際の一般的な?例
G様推奨のAppCompat系レイアウトでの利用例を書き出してみます
android:transitionName あたりの話は下記を参照してください
- app/layout_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:bind="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="hoge.fuga.example.R" /> </data> <android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!--//bind includeするtoolbar--> <include bind:trans_name="@{R.string.trans_main}" bind:title="@{R.string.title_main}" layout="@layout/toolbar" android:id="@+id/app_bar_timeline" android:layout_width="match_parent" android:layout_height="wrap_content"/> <!--//何らかのUIコンテンツ--> <!--//浮かんでるFabボタン--> <android.support.design.widget.FloatingActionButton android:id="@+id/first_fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_marginBottom="16dp" android:layout_marginRight="16dp" android:src="@drawable/ic_star_white_24dp"/> </android.support.design.widget.CoordinatorLayout> <!-- Navi--> <android.support.design.widget.NavigationView android:id="@+id/navi_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/naiv_header" app:menu="@menu/navi_body"/> </android.support.v4.widget.DrawerLayout> </layout>
- layout/toolbar.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="hoge.fuga.BindingUtils" /> <variable name="trans_name" type="int" /> <variable name="title" type="int" /> </data> <android.support.v7.widget.Toolbar android:transitionName="@{BindingUtils.string(trans_name)}" app:title="@{BindingUtils.string(title)}" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"> </layout>
- BindingUtils.java
public class BindingUtils { public static String string(int resId) { if(resId == 0 || resId == -1){ return ""; } HogeApplication instance = HogeApplication.getInstance(); return instance.getApplication() .getResources().getString(resourceId); } }
ポイント的な所
- include引数に対してレイアウト上で宣言する方法が現状ない
- main_layout で対象属性(bind:trans_name)指定なし
- 0が渡ってくる
- -1等のあえて動かないような値を宣言等も出来る
引数指定なし、引数に変数を入れる例
- app/layout_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:bind="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="hoge.fuga.example.R" /> </data> <android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <!--//中略--> <!--//bind includeするtoolbar--> <include bind:trans_name="@{-1}" layout="@layout/toolbar" android:id="@+id/app_bar_timeline" android:layout_width="match_parent" android:layout_height="wrap_content"/>
dataタグを使わない例
- app/layout_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:bind="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"> <android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <!--//中略--> <!--//bind includeするtoolbar--> <include bind:trans_name="@{hoge.fuga.example.R.string.trans_main}" bind:title="@{hoge.fuga.example.R.string.title_main}" layout="@layout/toolbar" android:id="@+id/app_bar_timeline" android:layout_width="match_parent" android:layout_height="wrap_content"/>
data-bindingを使わず、data-bindingで利用した子レイアウトを使う例
一応普通に使えてしまう
- app/layout_main.xml
<android.support.v4.widget.DrawerLayout android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <!--//中略--> <!--//bindしない includeするtoolbar--> <include layout="@layout/toolbar" android:id="@+id/app_bar_timeline" android:layout_width="match_parent" android:layout_height="wrap_content"/>
応用例っぽい話
- values/string.xml
<resources> <string name="list_date_format">Date discovered: %1$s</string> </resources>
- app/layout_main.xml
<TextView android:id="@+id/date_text_view" android:text="@{@string/list_date_format(date.toString() ?? `(no date)`)}" android:allCaps="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toLeftOf="@id/solved_check_box" android:layout_below="@id/title_text_view" tools:text="Crime Date" android:padding="@{@dimen/list_item_padding * 2}"/>
ようは
- 文字整形をリソースだけで処理できる(?? はnullだったらの省略二項演算子らしい)
- dimen定義をレイアウト上で調整できる
デメリットとしては
- 完全に文字列扱いになるため
- Ctrl+B で定義にジャンプできていたのができなくなる
- 補完が効かないので Gradle Syncのタイミングでしかエラー判別ができない
ココらへんの話は、下記のアノテーション書くときに補完が効かないのと似てるんだよな。(汗
@SuppressWarnings("deprecation") @SuppressWarnings("unused")
stringリソースだけでなく、Plurals リソースというのも有るらしい。
例は下記のQittaとかを参照すると良