はじめに
こちらでダークモード対応する際の見積もり手法という記事をあげましたが、本記事では短い準備期間でダークモード対応1画面にかかるコストをパターン化するためにどんな構成にしたのかを紹介します。
事前準備
NewsPicksのAndroidアプリは10年もののプロダクトであるがゆえに、いわゆるカラーパレットと呼ばれるようなものがアプリ内に複数セットありました。
ダークモード対応用のカラーパレットと変数名がかぶってしまう古いものもあったため、以下なルールで対処しました(いずれもIDEAのりファクタ機能を用いて作業しています)
- 使われていないリソースは削除、同一カラーを指定しているものは1個に可能な限りまとめる
- ダークモード対応用に新規追加するもの以外の変数名にoldというPrefixを付けてRename(例:blue_200 -> oldblue_200)
手順1:ダークモード対応用カラーパレット追加
UI層のres/values/colors.xmlにiOSと共用しているFigma記載のカラーパレットを追記(以下はblueに関するカラーパレット設定例)
手順2:デザイナ設計のセマンティックカラー追加
設計に従い、Body/Surface/Object/Border/Text/Overlay...etcといった区分でライトモード時セマンティックカラーをUI層のres/values/colors.xmlへ、ダークモード時セマンティックカラーをUI層のres/values-night/colors.xmlにカラーパレット内のグローバルカラーを使って定義していきます。
この時、変数名の命名方法がiOSと異なるルールになりました(iOSではBorderであれば Border/Accent/Primary
といったディレクトリ構造で管理をしている)。
AndroidではResource名は小文字プラス記号である必要がある及び、ディレクトリ構造で管理できないという制約のためBorderであれば以下例のように、階層はアンダースコアで表現しました。
例:ボーダー色(ライトモード) | 例:ボーダー色(ダークモードモード) |
---|---|
手順3: Figmaに従って各セマンティックカラーをXML等に反映
手順2までで準備は完了しているので、あとはひたすらFigma通りに値を反映する作業を行っていきます。
実際に何画面かダークモード対応をやってみることで見積もり時間をわりと正確に出すことができました。
手順4: ライトモード・ダークモードの切り替え対応
機能要件は ダーク/ライト/端末の設定を使用
の3パターンが設定画面で切り替えられることで、以下で判定します。
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)// 常にダークモードをONにする
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)// 常にダークモードをOFFにする
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); // アプリ全体に適用
ただ、端末の設定を使用したい場合の判定処理ではまりました。
具体的にはAppCompatDelegate.getDefaultNightMode()
で得た値と↑の3値の一致で判定するだけでは不十分な端末があり、最終的には以下な処理になりました
/**
* 表示を切り替えるために現在ダークモードかどうかを返す
*/
fun isDark(): Boolean {
val defaultNightMode = AppCompatDelegate.getDefaultNightMode()
if (defaultNightMode == AppCompatDelegate.MODE_NIGHT_YES) {
return true
}
if (defaultNightMode == AppCompatDelegate.MODE_NIGHT_NO) {
return false
}
// 端末の設定に従うケース
val currentNightMode: Int = (context.resources.configuration.uiMode
and Configuration.UI_MODE_NIGHT_MASK)
when (currentNightMode) {
Configuration.UI_MODE_NIGHT_NO -> return false
Configuration.UI_MODE_NIGHT_YES -> return true
Configuration.UI_MODE_NIGHT_UNDEFINED -> return false
}
return false
}
まとめ
事前準備期間をあまり多く設けず、見積もりもし易い構成が取れたかなと思います。
また、過去に何回もカラーパレットを作っては反映しなおし、ということを繰り返していたので、セマンティックカラー導入により同じことが起きずに安心して運用できる設計にしてくれたデザイナ陣に感謝!