Konifar's WIP

親方!空からどらえもんが!

DroidKaigi2019で発表したAndroid Themeの話のスライド補足

DroidKaigi2019お疲れ様でした。日高さんはじめスタッフの皆さん、登壇者の皆さん、自分のセッションに来ていただいた皆さん、直接お話させていただいた皆さんありがとうございました。

英語での発表で、30分に収めるために削った部分も多く、スライドだけだと伝わりにくい部分もあると思うので日本語で補足しておこうと思います。

内容に関して、間違っている部分やわからない部分があれば、お手数ですが直接 @konifarまで連絡をもらえるとありがたいです。

speakerdeck.com


f:id:konifar:20190211093135p:plain

  • 今日は聞きにきていただいてありがとうございます。
  • AndroidのThemeについてお話します。

f:id:konifar:20190211093359p:plain

  • まず少しだけ自己紹介をさせてください。
  • 小西裕介と言います。konifarという名前でGitHubTwitterをやっています。
  • GitHubリポジトリでいくつかのツールやアプリを公開しています。

f:id:konifar:20190211093500p:plain

  • 普段はKyashという日本でFintechをやっている会社で働いています。

  • Kyashを使えば、Visaカードをアプリ上で簡単に発行して誰かに送金することができます。
  • もしこの発表がよかったと感じたら、インストールして自分に投げ銭してみてください。すみません冗談です。

f:id:konifar:20190211113900g:plain

  • 実は、今日の発表のためにサンプルアプリを作ってあります。
  • AppCompat、Material Componentsのテーマを適用したコンポーネントがどう見えるかを確認できます。また、テーマの動的な切り替えやNight modeの対応も入っています。
  • このアプリのテーマ自分の愛猫を元にしていて、毛の色をprimaryColorに、眼の色をsecondaryColorに設定しています。今日はこのサンプル猫アプリの実装を例に説明していきます。


f:id:konifar:20190211093854p:plain

  • では、本題に入りましょう。Android Themeについてです。
  • Android Themeは便利ですが少し複雑で、うまく使いこなせていないという方も多いのではないでしょうか。

f:id:konifar:20190211093858p:plain

  • まずAndroid Themeについて少しおさらいしましょう。
  • みなさんご存知のとおり、Android Themeをうまく使うことでアプリのデザインに統一感を持たせることができます。
  • Themeは技術的にはStyleを同じです。ざっくり言えば、Themeはグローバル、Styleはローカルの設定のようなものだと言えます。
  • Material Designが登場した2014年からは特に重要なものとなりました。

f:id:konifar:20190211093905p:plain

  • AppCompatを使っているのであれば、もっとも有名なのはこの3つの属性でしょう。colorPrimary、colorPrimaryDark、colorAccentです。

f:id:konifar:20190211093911p:plain

  • 画面で見るとこんな感じですね。NavigationBar、Toolbar、FloatingActionButtonなどに先ほど設定した色が適用されます。
  • テーマをうまく使えば統一感のあるアプリを作ることができます。

f:id:konifar:20190211093916p:plain

  • しかし実際には、うまくthemeを使えている人があまり多くないように思います。
  • 「テーマはなんだか難しい、複雑だ」と感じさせる問題が2つあると考えています。

f:id:konifar:20190211093921p:plain

  • 1つめは、親テーマが多すぎる問題です。
  • Android Studioでサジェストされる親テーマを見てみると、たくさんのテーマがありますね。
  • テーマを継承して作成する際に、どれを指定するのが正解なのか実はよくわかっておらず、なんとなく選んでいる人もいるのではないでしょうか。

f:id:konifar:20190211093927p:plain

  • 2つめは、属性が多すぎる問題です。
  • テーマでは、色だけではなく各コンポーネントのスタイルも色々と調整できます。本当にたくさんの属性があるので、おそらく有効活用しきれていないのではないでしょうか。
  • また、2018年にはMaterial Componentsがリリースされて色や形を指定するための属性が増えました。
  • どのような属性があるかわからないことで、本当はテーマで指定できるものを各Viewでいちいち指定していることはありませんか?
  • ほとんどのコンポーネントのStyleや色をテーマで設定できるので、どういうものをStyleで設定するべきなのかわからないという方もいるでしょう。

f:id:konifar:20190211093932p:plain

  • ということで、今日はうまくテーマを活用してアプリのデザインに統一感を持たせる方法についてお話しようと思います。
  • ほとんどのアプリはAppCompatとMaterial Componentsを使っていると思うので、今日はその2つを使うことを前提にお話しします。

f:id:konifar:20190211094013p:plain

  • まず最初に、テーマの適用について少し体系的に話します。
  • 親のテーマに何を選べばいいのか。AppCompatもMaterial Componentsもたくさんのテーマを提供してくれています。どれを選べばいいのでしょうか。
  • また、テーマをActivityやFragmentで動的に変更したり、Night Modeに切り替えたりするにはどのように実装すればよいのでしょうか。

f:id:konifar:20190211094018p:plain

  • 2つめは、テーマの属性について話します。おそらく、こちらの方が気になっている方も多いのではないでしょうか。
  • テーマには本当にたくさんの属性がありますよね。どんな属性があり、どこに影響するのかについてお話しします。
  • 1つずつ順番に説明するだけでは単調になってしまうので、今日は「指定できるテーマの属性をどのように調べればいいのか」という方法について話そうと思います。
  • このセッションが終わった時に、テーマについて前より少しでも詳しくなったと思ってもらえたら嬉しいです。

f:id:konifar:20190211094024p:plain

  • それでは、テーマの適用から見ていきましょう。

f:id:konifar:20190211094032p:plain

  • まずはAndroidManifestでのテーマの適用について少し復習しましょう。
  • Androidのテーマは、既存のテーマを継承してAndroidManifestのApplicationまたはActivityに指定します。
  • カメラ画面のような透明なActivityなど画面固有のテーマを適用したい場合には、Activityで指定することもあります。

f:id:konifar:20190211094039p:plain

  • この親のテーマに何を指定すればいいのかを説明していきます。
  • その前に、まずテーマの継承についてお話します。

f:id:konifar:20190211094043p:plain

  • テーマの継承の方法は、dot区切りとparentでの指定の2種類があります。


f:id:konifar:20190211094049p:plain

  • このdot区切りの例では、 Theme.AppCompat.Light を継承していくつかのActionBarに関する属性を指定し、 Theme.AppCompat.Light.NoActionBar というテーマを定義しています。

f:id:konifar:20190211094054p:plain

  • 一方、parentで指定して継承することもできます。
  • parentで指定した場合、dot区切りよりも優先されます。

f:id:konifar:20190211094102p:plain

  • この2つの継承方法を使い分けるために、どんな時にparentの指定を使うべきなのかを説明します。

f:id:konifar:20190211094106p:plain

  • 1つは、AppCompatやMaterial Componentsといったライブラリが提供するテーマを継承する時。
  • もう1つは、APIバージョンによる属性の差異に対応したい時です。

f:id:konifar:20190211094112p:plain

  • この Base.Theme.AppCompat.Lightを例に、APIバージョンの差異にどのように対応しているかを見てみましょう。
  • Androidのテーマは常に進化を続けているので、途中から追加された属性が色々あります。
  • Androidでは、それらを values-{version}というsuffixをつけたディレクトリの中で定義することで、バージョンに応じて適用するテーマを変更できます。

f:id:konifar:20190211094118p:plain

  • バージョンsuffixのないvaluesディレクトリの中を見ると、 Base.V7.Theme.AppCompat.Lightというテーマを継承しています。

  • 親は Platform.AppCompat.Lightです。属性はたくさん指定されています。

f:id:konifar:20190211094123p:plain

  • 一方、values-v21の中の同じテーマを見てみると、 Base.V21.Theme.AppCompat.Lightというテーマを継承しています。
  • この親は Material.Light.NoActionBarという先ほどとは違うテーマです。

f:id:konifar:20190211094127p:plain

  • values-v28では、また違うテーマを継承しています。
  • このように、APIバージョンに応じて適切なテーマと属性を設定し、それをparentで継承することで Base.Theme.AppCompat.Lightという同じ名前のテーマを使うことができます。
  • ここまでででわかると思いますが、AppCompatなどのライブラリが提供する Base~から始まるテーマはバージョン差異を吸収するためのものなので、アプリを作る上では継承することはありません。

f:id:konifar:20190211094132p:plain

  • では、何のテーマを継承すればよいのでしょうか。
  • 適切な親テーマを選ぶためには、ライブラリが提供するテーマの命名規則を知っておくとよいです。

f:id:konifar:20190211094137p:plain

  • コンポーネントごとのテーマはたくさんありますが、ActivityやFragmentにセットするテーマのルールは実はとてもシンプルです。


f:id:konifar:20190211094142p:plain

  • 最初の部分は単なるPrefixです。基本的には、Themeで始まるものを使えばよいです。
  • ThemeOverlayから始まるものは、各Viewでオーバーライドする時に使うものです。

f:id:konifar:20190211094147p:plain

  • 2つめの部分はテーマの名前です。ライブラリ名といってもよいでしょう。
  • AppCompatMaterialComponentsを使うことが多いと思います。
  • Designというのは、MaterialComponentsが提供しているもので、AppCompatのエイリアスなので実態はAppCompatと同じです。

f:id:konifar:20190211094152p:plain

  • 3つめは、背景の明るさです。
  • 何も指定しなければ、黒い背景・明るい文字色になります。Lightを指定すると、明るい背景・暗い文字色になります。
  • DayNightを指定すると、その2つを時間帯や任意のタイミングで切り替えられるようになります
。

f:id:konifar:20190211094159p:plain

  • 最後のDarkActionBarの部分は、背景はLightだけどActionBarは暗い色にしたい時に指定します。
  • NoActionBarを指定すると、ActionBarは表示されません。ActionBarのいらない画面か、ToolBarと組み合わせて使う時に指定します。

f:id:konifar:20190211094204p:plain

  • まとめるとこんな感じです。
  • AndroidManifestでセットする全体のテーマは、実はこれらの掛け合わせだけで選べるということです。簡単ですね。

f:id:konifar:20190211094209p:plain


f:id:konifar:20190211094214p:plain

  • 何のテーマを選べばいいかわかったところで少し話を変えて、コードからテーマを変更する方法について見ていきましょう。


f:id:konifar:20190211094220p:plain

  • Activityはこんな感じです。 setTheme()メソッドを setContentView()よりも前に呼ぶだけです。
  • この setTheme()に渡すstyleをSharedPreferenceに保存したstyleにすれば、設定に応じてテーマを切り替えることもできます。

f:id:konifar:20190211094224p:plain

  • Fragmentの場合も簡単です。
  • onCreateView() でViewをinflateする前に contextThemeWrapperにstyleを渡し、それを使ってinflaterをcloneします。cloneしたinflaterを使ってinflateするだけです。

f:id:konifar:20190211094230p:plain

  • 同じように、DayNight modeをコードから切り替える方法について説明します。

f:id:konifar:20190211094234p:plain

  • まず、DayNight用のテーマを継承したテーマを作ります。
  • ここで、textColorとcolorBackgroundの色を指定しておきます。

f:id:konifar:20190211094239p:plain

  • valuesディレクトリには通常時に適用する色を、values-nightディレクトリにはNightモードの時に適用する色を指定しておきます。

f:id:konifar:20190211094245p:plain

  • 画面ではこんな感じで変更されます。背景とテキストの色が変わっていますね。

f:id:konifar:20190211094251p:plain

  • DayNight modeをコードから変更するのは簡単です。activity delegatesetLocalNightMode()メソッドを呼ぶだけです。
  • テーマの変更と違ってActivityを再起動する必要もありません。

f:id:konifar:20190211114119g:plain

  • こんな感じですぐに変更されます。
  • テーマと属性を適切に設定しておけば、かなり簡単にnightmodeに対応することができますね。
If you set the theme and attributes properly, it’s so easy to support night mode. テーマについて話したところで、次に属性を適切に設定する方法について見ていきましょう。
Then, on next part, I’ll talk how we can find and set proper theme attributes.

f:id:konifar:20190211094302p:plain

  • 1つ1つを順番に説明するのではなく、何の属性があってどこに影響するか、それをどう調べればいいかといった話を中心に話します。

f:id:konifar:20190211094306p:plain


f:id:konifar:20190211094311p:plain

  • ただ、xmlを読んで理解するのは大変ですよね。
  • ですので、どんな項目が定義できるのかを分類しながら整理しましょう。

f:id:konifar:20190211094316p:plain

  • テーマで設定できる属性はたくさんありますが、実は7種類のカテゴリに分けられます。

f:id:konifar:20190211094321p:plain

  • Colors、Drawables、Text appearances、Shape appearances、Widget styles、Themes、Window configurationsの7つです。
  • その中の全ての属性を覚えておく必要はありません。
  • それぞれのカテゴリがどういうイメージのものなのかをざっと説明します。

f:id:konifar:20190211094327p:plain

  • Material Designケーススタディの1つである、Owlというサービスを例に説明していきます。
  • Owlは、画面ごとに鮮やかなテーマが切り変わるアプリです。

f:id:konifar:20190211094333p:plain

  • テーマの色は異なりますが、いくつか共通部分はあるのでAppThemeというテーマを作ります。

f:id:konifar:20190211094338p:plain

  • 例えば、TextAppearanceで定義する文字サイズやフォント、行間のサイズなどは共通で定義しておいた方がよさそうです。

f:id:konifar:20190211094343p:plain

  • CardViewの形を見ると、ShapeAppearanceも共通で定義しておいた方がよさそうですね。

f:id:konifar:20190211094349p:plain

  • 一方、色は明らかに違うのでそれぞれAppThemeを継承してテーマを作ります。

f:id:konifar:20190211094356p:plain

  • colorPrimary、colorPrimaryVariant、colorSecondaryをそれぞれデザインに合わせて定義します。

f:id:konifar:20190211094402p:plain

  • colorBackgroundやtextColorも同じように各テーマに定義しておけそうですね。

f:id:konifar:20190211094409p:plain

  • 真ん中のBrowse画面のBottomNavigationは、bottomNavigationStyleで定義しておくとよいかもしれません。

f:id:konifar:20190211094415p:plain

  • 右のLearn画面のStatusBarは透明なので、APIバージョンごとにWindow configurationの属性を定義しておく必要があります。

f:id:konifar:20190211094420p:plain

  • こんな感じでテーマに必要な属性を定義します。
  • テーマはどうしても属性が多くなってしまうので、カテゴリごとコメントで区切っておくと整理しやすくオススメです。

f:id:konifar:20190211094425p:plain

  • 今日は全てを説明する時間はないので、Colors、Widget styles、Shape appearancesの3つに絞って説明します。

f:id:konifar:20190211094430p:plain


f:id:konifar:20190211094435p:plain

  • こんな感じで、何の属性があってどこに定義されているかを一覧で確認できます。

f:id:konifar:20190211094440p:plain

  • 属性をタップすると、リファレンスで説明を確認できます。
  • カテゴリごとにまとめてあるので、どんな属性があるかをざっと確認したい時は参考にしてみてください。

f:id:konifar:20190211094446p:plain

  • それでは、colorsから見ていきましょう。
  • AppCompatとMaterial Componentsの違いを中心にお話します。

f:id:konifar:20190211094451p:plain

  • AppCompatで定義されていた色の属性が、Material Componentsで少し変わります。
  • 例えば、StatusBarのcolorPrimaryDarkがcolorPrimaryVariantに変わってますね。

f:id:konifar:20190211094457p:plain

  • Material Componentsでは、colorPrimaryやcolorSecondaryの上に表示される色を colorOn から始まる属性で指定できるようになりました。

f:id:konifar:20190211094503p:plain

  • colorOnSurfaceに設定した色は、こんな感じで反映されます。
  • EditTextのborderや、TabLayoutのデフォルトのタブ色などに反映されていますね。

f:id:konifar:20190211094509p:plain

  • colorOnPrimaryは、primaryカラーのボタンのテキスト色に反映されます。
  • colorOnSecondaryはFloatingActionButtonのアイコンの色に反映されます。

f:id:konifar:20190211094514p:plain

  • 他のテキストの色については、AppCompatとMaterialComponentで大きな違いはありません。

f:id:konifar:20190211094520p:plain

  • コンポーネントの色も細かく指定できます。
  • SwitchやButtonなど何かアクションがあるコンポーネントの色は controlから始まる属性の色が適用されることが多いです。

f:id:konifar:20190211094525p:plain


f:id:konifar:20190211094531p:plain

  • 基本的には、Colorsだけでもかなりカスタマイズできます。
  • けれども、コンポーネントごとにスタイルを指定することでより柔軟に見た目を変更することができます。

f:id:konifar:20190211094535p:plain

  • 例えばTabのスタイルを例に見ていきましょう。
  • 何も指定しないと、特に色はつきません。

f:id:konifar:20190211094542p:plain

  • tabStyleという属性にTabLayoutのスタイルを指定します。
  • TabLayoutは Widget.Design.TabLayoutを継承していて、elevationやtextColorなどを設定しています。

f:id:konifar:20190211094547p:plain

  • では、どういうWidget styleがあって何の属性を設定できるかをどのように調べればよいでしょうか。自分が普段やっている調べ方を説明します。

  • 基本的には、AndroidStudioのSuggestionが優秀なのでそれを使って調べています。
たったの3ステップです。

f:id:konifar:20190211094552p:plain

  • まず、Toolbarなど変更したいコンポーネントの名前を打ってみましょう。
  • toolbarStyle、buttonStyleといった感じで xxxStyleという名前で出てくるはずです。

f:id:konifar:20190211094556p:plain

  • 属性が見つかったら、デフォルトのstyleを探します。
  • Widget.Toolbarのように打つと、それっぽい名前が出てくるはずです。
  • 例えばAppCompatテーマの場合には Widget.AppCompat.Toolbarですね。

f:id:konifar:20190211094602p:plain

  • 最後に、デフォルトのスタイルを見つければどんな属性が設定できるか確認できます。
  • 属性の名前を見れば、だいたいどのようなカスタマイズができるのかわかるはずです。
  • 自分はGitHubリポジトリから検索して探すことが多いです。

f:id:konifar:20190211094609p:plain


f:id:konifar:20190211094614p:plain

  • 最後に、ShapeAppearanceの説明をします。
  • Material Componentsで追加された、CardViewやEditTextなどのコンポーネントの形を指定できる属性です。

f:id:konifar:20190211094619p:plain

  • ShapeAppearanceはWidget styleの中で個別で指定することもできますが、テーマ全体で指定できるのはこの3つの属性です。
  • Small、Medium、Largeの3つのコンポーネントの大きさに応じて適用されます。
  • 例えば、CardViewに適用されるのはshapeAppearanceMediumComponentです。

f:id:konifar:20190211094624p:plain

  • 属性はcornerFamilyとcornerSizeの2つだけです。
  • alpha8の時点では2つだけですが、今後増えていくと思います。

f:id:konifar:20190211094629p:plain

  • cornerFamilyはcutとroundedの2つを指定できます。
  • この2つをそれぞれ指定すると、表のように形を変化させることができます。

f:id:konifar:20190211094635p:plain

  • ShapeAppearanceはchipStyleやbuttonStyleの中でも設定することができます。

f:id:konifar:20190211094640p:plain

  • FloatingActionButtonをcutにする時には、floatingActionButtonStyleを指定します。
  • 注意点としては、FloatingActionButtonのstyleにはshapeAppearanceではなくshapeAppearanceOverlayを指定する必要があります。

f:id:konifar:20190211094645p:plain

  • カテゴリをざっと見たところで、最後にテーマで定義するかスタイルで定義するかという多くの人が悩みがちなところを少しだけ話します。

f:id:konifar:20190211094651p:plain

  • 元も子もないことを言うと、デザインによります。
  • テーマを使えば、ほとんどのコンポーネントはカスタマイズ可能です。
  • もしアプリ全体で共通のデザインならば、テーマで定義した方がアプリ全体でデザインを統一できてよいでしょう。
  • 逆に、あるコンポーネントだけ特別なフォントやShapeならば、スタイルに定義してレイアウトの中で指定した方がよいです。

f:id:konifar:20190211094656p:plain

  • 例として、Material DesignケーススタディFortnightlyというサービスを見てみましょう。
  • FornightlyのガイドラインにはTypographyのセクションがあり、テキストのサイズや色に関してはアプリ全体で適用できるのでtextAppearanceをテーマに定義しておくのがよいでしょう。

f:id:konifar:20190211094702p:plain

  • 一方、この検索画面のEditTextは、検索のためだけのものならばスタイルとして定義した方がよいでしょう。
  • chipはアプリ全体で同じなので、テーマとして定義するべきです。
  • つまり、色やコンポーネントのスタイルをどの程度共通化するべきかで、テーマにするべきかスタイルにするべきかは変わるということです。
  • チームにデザイナーがいるのであればデザイナーと相談して一緒に考えていくのがよいでしょう。

f:id:konifar:20190211094707p:plain

  • 最後に今日のセッションについてまとめます。

f:id:konifar:20190211094712p:plain

  • テーマにはたくさんの属性がありますが、たった7つのカテゴリに分類することができます。

f:id:konifar:20190211094717p:plain

  • 指定したい属性の見つけ方を知っておくことが重要です。

f:id:konifar:20190211094723p:plain

  • ColorやWidget styleをテーマに定義するべきかスタイルに定義するべきかはデザインによります。
  • デザイナーと相談し、どの程度共通化するべきなのかを考えましょう。

f:id:konifar:20190211094728p:plain


f:id:konifar:20190211094732p:plain

  • Androidのテーマをうまく使って楽しく開発しましょう。
  • ありがとうございました。