今更遅れてDataBinding事始め(2)
前回のお話
環境構築とかは下記のエントリを参照してください
やっと一個の画面をButterKnifeからDataBindingを使って書き換えた。
— さくら ポケモンGo TL.28 (@sakura_bird1) 2016年10月1日
と言ってもDataタグが必要ない箇所だけど。
本当に置き換えただけという段階です。
と呟かれてしまうぐらい大変。
とりあえずこのエントリでは単方向Bindの話をまとめます。
自分も
- 1画面が半日〜1日
かかってしまって白目向いている状態だったりも・・。
慣れれば効率が上がるんだろうか・・・(汗
スモールスタートが良いかもしれない。
でもちょっとの記述エラーで全く動かなくなるのはJavaScript道に通じるものがあるかも、、
自分の開発スタイル
- layout.xml を 戻しやすい形にして修正
- data-binding の形式のレイアウトに直す
- 動かなくなったら戻す *1
Gradle Sync / Build のみを行って正常に変換されるか確認する
生成される位置
app/build/generated/source/apt/debug XXXXBinding.java
APTの生成コードといえば
- app/build.gradle
android { sourceSets { main { java.srcDirs = ['src/main/java', 'build/generated/source/apt/debug'] } } }
等でパス通せば
- Gradle Sync でプロジェクト的には見えるようになる
- assembleDebug で二重エントリでビルドエラーになる
な状況なんだけど DataBinding絡みで APTを組み込む仕組みとか内部で取り込まれているがための挙動なんだろうな。。と
だからクラス候補には出るけどsouceJumpとか存在確認が何気に現在大変かと思う。開発環境的には退化した?
だからDataBindingのコードも別名生成できるようですが、それをしないほうが無難かと思われます
例)生成クラス名
- JPPの場合
- DataBindingの場合
実際の作業手順をメモってみる
layout.xml を 戻しやすい形にして修正
- layout/activity_main.xml(元)
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello Wold" /> </RelativeLayout>
- layout/activity_main.xml(変更後)
- 外側に mergeタグを追加して、xmlnsタグを移動する
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello Wold" /> </RelativeLayout> </merge>
data-binding の形式のレイアウトに直す
- layout/activity_main.xml(変更前)
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello Wold" /> </RelativeLayout> </merge>
- layout/activity_main.xml(変更後)
- エラーが出て戻す場合は以下の作業をする
- layout => merge タグへ
- dataタグ、利用箇所をコメント
- エラーが出て戻す場合は以下の作業をする
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="activity" type="hoge.fuga.MainActivity" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{activity.onClick}" android:text="@{activity.message}" /> </RelativeLayout> </layout>
で事足りてしまう。。
layout.xml 触っててハマった注意点
- importタグ はワイルドカードとかは使えません
- importタグ でクラスをimport後、variable のtype宣言 で省略して書くという書き方 が有りますが、
- 実はこれ誤動作することがあるらしく、基本的にtype宣言は フルパスで書いたほうが良いとのこと
レイアウト編集をする上での所感的な感想
- layout.xml に記述する判定式は、単純なboolean判定の処理程度にすべき。
- それ以上の場合はエスケープシーケンス地獄でレイアウトエラーの沼間にハマる
- 複雑な判定をしたい場合は、判定用のUtilクラスを作成して、importして使うほうが遥かに楽
<data> <import type="android.text.TextUtils" /> <variable name="activity" type="hoge.fuga.MainActivity" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{TextUtils.isEmpty(activity.message) ? "empty" : activity.message" /> </RelativeLayout>
Gradle Sync を行う
このタイミングでAPTの処理が走り、XXXXBinding のソースがでて、初めて利用できる形になる
- DataBindingの場合
なイメージ
ソース上で 単方向のBind処理を行う
双方向の話は次回以降で。
Activityの場合
DataBindingUtil.setContentView で bindする
- app/MainActivity
public class MainActivity extends AppCompatActivity { public String message = "Hello World!"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);//★ // DataBindingUtilクラスを使ってレイアウトを初期化する ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);//◎ // ここでデータをバインド binding.setActivity(this); } public void onClick(View view){ } }
★に関してですが、コメントしても、あっても動作します。
ネットでググると、★の箇所を消しているサンプルもあったり。
どっちが推奨記述なんでしょうね??*5
Fragmentの場合
XXXXBinding.bind を使う //★のタイミングで生成されたものを使う
- app/MainActivityFragment.java
public class MainActivityFragment extends Fragment { FragmentMainBinding fragmentMainBinding; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main, container, false);//★ } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); fragmentMainBinding = FragmentMainBinding.bind(view);//◎ fragmentMainBinding.setFragment(this); } }
Adapter の場合
DataBindingUtil.inflate を使う
@Override public View getView(int position, View convertView, ViewGroup parent) { HogeItemBinding binding; if (convertView == null) { // リストのアイテムにレイアウト適用と Data Binding を適用する binding = DataBindingUtil.inflate( LayoutInflater.from(getContext()), R.layout.hoge_item, parent, false );//◎ // レイアウトを適用したアイテムの View を取得 convertView = binding.getRoot(); } // 2 周目ではアイテムの View が作成されているので else { // Data Binding オブジェクトを View から取得 binding = DataBindingUtil.getBinding(convertView);//◎ } // Data Binding するために View と layout.xml上の item変数に 紐つける binding.setItem(getItem(position)); return super.getView(position, convertView, parent); }