概要
最近、View ベースの MVP アーキテクチャが MVVM と並んで流行しているようです。Square 社製のライブラリである「Flow+Mortar」や、他にも Scoop など、View をベースにした MVP アーキテクチャのためのフレームワークを提供するライブラリはいろいろあるようです。
ところで、レイアウト上の UI パーツ(View
)に割り当てる属性値のまとまりに名前をつけることができます。これはスタイルと呼ばれ、Web で言うところの CSS に相当するようなものです。アプリを作っていく中で、どのレイアウトでも共通する属性値を持つ UI パーツが出てくることも多いでしょう。この時愚直に、同じ属性値をあちこちのレイアウト上でコピペしまくっていると、後々その共通部分を修正するときにひたすら面倒な作業をあちこちに適用するはめになり、作業量も差分も大きくなってしまいます。そこでスタイルと呼ばれる、レイアウトとは独立したリソースを用意してやり、この UI パーツはこのスタイルを割り当てるということを宣言してさえおけば、どのレイアウトでも共通の属性値を持った UI パーツを作れます。
さて、Anddroid でいうところのスタイルには、実は 2 種類あります。ひとつはスタイル、もうひとつはテーマです。どちらも<style>
タグを使って宣言するので紛らわしいのですが、ざっくり言ってしまうと、スタイルのまとまりのことをテーマと呼びます。テーマでは、どの種類の UI パーツにはどのスタイルを割り当てますよ、という宣言をします。こうすることで、レイアウトの中でいちいち「この UI パーツにはこのスタイルを割り当てる」ということを書かなくても、テーマを一発バチコンと当てるだけで指定した UI パーツに対応するスタイルが割り当てられていきます(もちろん、すべての場合でテーマ一発で解決するとは限りません)。
一般的に、と言うのが適切かはわかりませんが、多くの場合、テーマはAndroidManifest
の<application>
や<activity>
の属性として割り当てていきます。テーマはその性質上、画面単位での見た目を決定するものなので、アプリ内の全画面で共通のテーマを使うなら<application>
に、画面ごとに個別にテーマを割り当てたいときには<activity>
にandroid:theme
属性をつけます。
問題
これで何が困るかというと、View ベースの MVP アーキテクチャの場合、基本的に画面遷移は View のアニメーションで実現されるため、極端な話 Activity はひとつ用意しておけばよい作りになります(実際には複数用意することが多いかもしれませんが)。そうなると、この画面ではちょっとテーマを変えたい……という時に、どこでそのテーマの宣言をすればよいかが難しくなります。
対処法はふたつ
おおまかに 2 種類の対処法があります。ひとつは、MVP アーキテクチャを実現するフレームワークで ContextThemeWrapper
を使う方法、もうひとつはandroid:theme
属性をレイアウトで指定する方法です。おそらく後者の方が圧倒的に楽なはずです。
ContextThemeWrapper を使う方法
MVP アーキテクチャを実現するフレームワークのうち、View をベースとしたものは必ずどこかでLayoutInflater
を使ってView
を展開するはずです(Scoop.java#L134やFlowContextWrappter.java#L47でLayoutInflater
を取り出している様子が見えます)。この時、LayoutInflater
はfrom()
メソッドで取り出していますが、続くcloneInContext()
メソッドの引数に渡すContext
をContextThemeWrapper
にすることで、このLayoutInflater
で展開するView
に割り当てるテーマを指定することが出来ます。
ContextThemeWrappter wrappedContext = new ContextThemeWrapper(baseContext, R.style.AppTheme_Something);
LayoutInflater inflater = LayoutInflater.from(baseContext).cloneInContext(wrappedContext);
よって、画面のライフサイクルの最初期に、その画面のView
を展開するContext
をContextThemeWrappter
にくるんでフレームワークに渡すことができれば、画面ごとにテーマの切り替えができます。
android:theme を使う方法
こちらは XML で記述します。やり方は非常に簡単で、テーマを指定したい画面の一番親にあたるView
にandroid:theme
属性を書くだけです。
<!-- LinearLayout 配下のすべての View にテーマを指定するよ -->
<LinearLayout
...
android:theme="@style/AppTheme.Something"/>
<Button .../>
<ListView .../>
</LinearLayout>
どうしてこんな長々と書いたのか
「android:theme
使え」だけじゃ素っ気なさすぎるじゃないですか!
というのが本音だったり建前だったりしますが、XML でandroid:theme
を指定したら結局 Android がよしなにContextThemeWrapper
使ってテーマを割り当ててくれるので(このへん)
、勉強も兼ねてと言うお話でした。決して、android:theme
がView
にも使えることを知らずに MVP フレームワークにContextThemeWrapper
を渡してView
を展開できるように必死こいてコードを読み書きした挙句android:theme
で一発じゃん…と絶望した腹いせに書いたわけではありません。