View ベースの MVP アーキテクチャを採用しているアプリが画面ごとにテーマを割り当てる方法

  • 10
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

概要

最近、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#L134FlowContextWrappter.java#L47LayoutInflaterを取り出している様子が見えます)。この時、LayoutInflaterfrom()メソッドで取り出していますが、続くcloneInContext()メソッドの引数に渡すContextContextThemeWrapperにすることで、このLayoutInflaterで展開するViewに割り当てるテーマを指定することが出来ます。


ContextThemeWrappter wrappedContext = new ContextThemeWrapper(baseContext, R.style.AppTheme_Something);
LayoutInflater inflater = LayoutInflater.from(baseContext).cloneInContext(wrappedContext);

よって、画面のライフサイクルの最初期に、その画面のViewを展開するContextContextThemeWrappterにくるんでフレームワークに渡すことができれば、画面ごとにテーマの切り替えができます。

android:theme を使う方法

こちらは XML で記述します。やり方は非常に簡単で、テーマを指定したい画面の一番親にあたるViewandroid:theme属性を書くだけです。


<!-- LinearLayout 配下のすべての View にテーマを指定するよ -->
<LinearLayout
  ...
  android:theme="@style/AppTheme.Something"/>
  <Button .../>
  <ListView .../>
</LinearLayout>

どうしてこんな長々と書いたのか

android:theme使え」だけじゃ素っ気なさすぎるじゃないですか!

というのが本音だったり建前だったりしますが、XML でandroid:themeを指定したら結局 Android がよしなにContextThemeWrapper使ってテーマを割り当ててくれるので(このへん)
、勉強も兼ねてと言うお話でした。決して、android:themeViewにも使えることを知らずに MVP フレームワークにContextThemeWrapperを渡してViewを展開できるように必死こいてコードを読み書きした挙句android:themeで一発じゃん…と絶望した腹いせに書いたわけではありません。