2019]、初日は@hohohorisが書かせていただきます。Studio)でAndroidエンジニアとしてインターンをさせていただきました。詳しくはインターン参加記を書いたので是非。概要この記事ではAndroid開発で避けては通れないLayout周りの話をします。Firestoreを使おうとしていたのでその話をしたかったのですが、諸々間に合わず初日からニッチ目な記事を書きます...。タイトルにある通り、Androidで動的にレイアウトを生成する際に自分がつまづいたのでそのときに得られた知見を共有したいと思います。}って書いてるだけの人、いると思います。このinflateの方法、全てに通用すると思っていませんか?意味、わかってますか?その辺りを書いていきたいと思います。基本的にLayoutの生成全般に関わることですが、よく使われているDataBindingにフォーカスしていきます。Android初心者/Androidを書いたことがない読者にもわかるようDataBindingとは、から説明します。DataBindingDataBindingは、MVVMと相性の良い技術で、レイアウトのViewとオブジェクトのデータを紐づける機能です。MVVMやその他アーキテクチャの共通している思想として、「責務の分離」があります。UIの表示をするレイヤーと、ロジックを記述するレイヤーを分けることにより、テストがしやすく、それぞれの責務が明確なため運用もしやすくなるメリットがあります。その際、データソースとUIとの紐づけを完結に記述できるライブラリがDataBindingなのです。beforeとafterでサンプルコードを見てみるとわかりやすいと思います。}コードは端折っていますが、ViewModelの何かしらのメソッドによりLiveDataなuserに更新があったらxmlのtextを変更するというシンプルなコードです。}xmlにデータソースとなるオブジェクトをvariableとして持たせ、インスタンスはActivity/Fragmentで渡してあげます。xmlに渡したデータソースに変更があれば、@{}で囲んだ箇所が動的に更新されるという素敵なことができます。コード量が少ないと恩恵がわかりづらいですが、setTextをする必要がないため、コードが綺麗になります。他にも、BindingAdapterや双方向バインディングなど便利な機能が備わっているのですが、この記事のスコープ外なので気になるひとは調べてみてください。上記で、DataBindingによりlayoutを生成したのですが、これにはいくつか種類があります。R.layout.hoge)//2と`2は同義。false)//3と`3は同義。container))ハマりどころActivityなら1、Fragmentなら2を使うことが多いのかなーという印象で、なかなか、これらの生成方法の違いを意識して記述することはないかもしれません。RecyclerViewのitemでDataBindingを使いたい時、DialogでDataBindingを使いたい時、CustomViewでDataBindingを使いたい時、これらの違いを意識していないで雰囲気でコードを書くことで落とし穴にはまってしまうかもしれません。コンパイルエラーが生じたり、ランタイムでViewが表示されないことがありました。ポイントとなるのは、レイアウトの生成と表示は別問題であることです。inflateやbindメソッドでよしなに生成と表示をしてくれることもありますが、本質的には別問題です。Viewやレイアウトの生成時に引数として渡すparentやattachToRootに注目しながら、実際にcommand+Bで内部実装を軽くみてみましょう。DataBindingUtil#inflate我々がDataBindingを利用する際によく使うこのinflateメソッドからみていきます。説明の都合上、一旦FragmentでのDataBindingにフォーカスします。基本的にはFragmentではonCreateViewメソッド内で2のようにDataBindingの初期化を行いますが、ここで第3引数にViewGroupであるcontainerを渡し、第4引数にfalseを渡しています。ここで内部実装にコメントで記載がある引数の説明を読んでみます。XML.ざっくり翻訳すると、attachToParentがtrueであるとき、第3引数で渡したparentは生成されたView(layout)の親になるattachToParentがfalseであるとき、第3引数で渡したparentは生成されたViewのLayoutParamsの決定のみに使用されるという感じです。公式リファレンスでも記載があるように、FragmentはView自体をattachしないほうが良いので、第4引数にfalseを渡しています。Fragmentの場合はLayoutをinflateした後に、onCreateViewの戻り値としてViewGroupを返すことでLayoutを表示させます。これが、「レイアウトの生成と表示は別問題」と記述したところに繋がっており、戻り値にnullを指定するとFragmentは表示されません。DataBindingUtil#bindこのメソッドを直接呼ぶことは少ないかもしれません。使えてしまうので、違いを知った上で安全に運用することはDataBindingに寄らずLayout生成周りで役に立つはずです。この場合、引数にはrootとなるViewを渡してあげる必要があります。その際にView#inflateを使用したのですが、こちらは第3引数にparentを渡しますが、DataBindingUtil#inflateにあったattachToParentがありません。これは、同じく内部実装の引数説明を読んでみると詳細がわかります。parameters.こちらは、問答無用に引数で渡したparentがLayoutの親になってしまうようです。そのため、3に記載したbindingの初期化方法は間違いとわかります。FragmentはLayout自体をparentに追加するのは正しくないからです。FragmentFragmentの場合は上述した通りです。Adapter前述はFragmentでのDataBindingにおけるLayout生成にフォーカスしましたが、AdapterのitemをDataBindingで生成するにはどうすれば良いでしょうか。これもLayoutのを生成したあと「どのようにして表示するか」で考えれば良さそうです。それによってparentを渡すかどうか、渡したparentにattachするかどうかが変わります。Adapterの場合はinflateしたViewをViewHolderのコンストラクタの引数に渡してあげることで表示ができます。そのため、AdapterでDataBindingを使う時もparentに直接attachする必要はなく、2の方法でinflateができます。AdapterViewなるExceptionに遭遇しました。そもそもgetViewで渡されてくるparentは多くの場合ListViewなのですが、ListViewはそもそもViewGroupを継承していないためaddViewができないためです。itemをDataBindingで生成したあとにはparentにattachせずに、getViewの戻り値としてbinding.rootを返すことで表示しないといけないのです。RecyclerViewはViewGroupを継承してはいるものの、表示方法としてはparentにattachするのではなくRecyclerView.ViewHolderにbinding.rootを渡してあげるほうが良いでしょう。CustomViewこれまで、Layoutを生成した後に表示する方法が別に用意されていたため、attachToParentはfalseにしていたのですが、CustomViewの場合には注意が必要です。CustomViewの場合は、よしなに表示してくれる機構が備わっていないため、これまでのようにattachToParentをfalseにしてしまうと、Layoutは生成されているが、宙ぶらりんな状態になってしまいます。}}こんな感じで書いてあげます。parentにthisを渡していますが、これはMyCustomViewが継承しているFrameLayoutです。今まではfalseにしていたattachToParentをtrueにしてあげることで生成したLayoutをViewGroupのヒエラルキーにいれてあげるのです。まとめ普段あまり意識しないようなLayoutの生成に関して、内部実装を元に違いをみてみました。「こういう機構で動いていたのか」というのを意識するようにしています。Calendar1日目からマニアックめなテーマで記事を書きましたが、これを機に内部実装ちょこちょこ見るようにするとワンランクレベルアップできるかもしれないので是非!ほりすでした。upLoginユーザーは見つかりませんでした。