Android-ダークテーマを案件で導入してみて


はじめに

こんにちは、株式会社アドグローブのアプリ開発部 Androidグループ所属の太田です。
開発案件でダークテーマ対応を行った為、今回はその導入・所感を記載したいと思います。

ダークテーマとは?

端末の画面が黒基調な画面表示に切り替わるモードのことになります。
その反対に白基調な画面表示をライトテーマと言います。
Android 10 (API レベル 29) 以上でサポートされて、Androidのシステム設定でダークテーマの有効/無効を設定することが可能になりました。
ダークテーマのメリットとしては以下の通りです。

  • 電力消費を大幅に節約できます(端末の画面テクノロジーに応じて異なる)。
  • 視覚障がいのあるユーザーや明るい光に過敏な方にとっての見やすさが向上します。
  • すべての人にとって暗い場所で端末を使用するのが容易になります。

また、iOSでもiOS13以降で、Androidのダークテーマと同様のモード(ダークモード)が利用可能です。

図1:ダークテーマ時の画面例 (左図:ダークテーマ、右図:ライトテーマ)
※Android端末の設定において、ダークテーマの設定名称がダークモードになっている場合があり、表記に揺らぎがあります。

ダークテーマの導入に関して

「-night」を付与したフォルダー名を追加して、ファイル名、色の定義名を同じにすればシステムの設定に基づいたダークテーマへの適用は可能になります。

全てではありませんが、以下は導入例になります。

  • color.xmlでの記載

 ライトテーマ時に適用
  res/values/colors.xml

    <color name="common_text_color_1">#FF000000</color>
    <color name="common_background_color_1">#FFFFFFFF</color>

 ダークテーマ時に適用
  res/values-night/colors.xml

    <color name="common_text_color_1">#FFFFFFFF</color>
    <color name="common_background_color_1">#FF333333</color>
  • drawable(画像系等)でもファイル名を同じにすれば適用されます。

 ライトテーマ時に適用
  res/drawable/image1.png
 ダークテーマ時に適用
  res/drawable-night/image1.png

  • styleにおいても定義を同じにすれば適用されます。

 ライトテーマ時に適用
  res/values/themes.xml

  <style name="TextCommonStyle">
    <item name="android:textStyle">italic</item>
    <item name="android:background">@color/teal_200</item>
  </style>

 ダークテーマ時に適用
  res/values-night/themes.xml

  <style name="TextCommonStyle">
    <item name="android:textStyle">bold</item>
    <item name="android:background">@color/purple_200</item>
  </style>
  • バージョン分けを行っている場合でも、リソースを定義してあれば適応されます。

 res/drawable-v31/image1.png
  →端末のOSがAndroid 12(API レベル 31)以上で、設定がライトモードの場合に適用されます。
 res/drawable-night-v31/image1.png
  →端末のOSがAndroid 12(API レベル 31)以上で、設定がダークテーマの場合に適用されます。
 res/drawable/image1.png
  →端末のOSがAndroid 11(API レベル 30)以下で、設定がライトモードの場合に適用されます。
 res/drawable-night/image1.png
  →端末のOSがAndroid 11(API レベル 30)以下で、設定がダークテーマの場合に適用されます。

WebViewでのダークテーマ対応・導入

アプリ内のWebViewにおいても、ダークテーマに対応させることは可能になります。

以下、gradleファイルに追加を行いました。

implementation 'androidx.webkit:webkit:1.4.0'

以下、ソースの記載例になります。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding: WebviewActivityBinding =
        DataBindingUtil.setContentView(this, R.layout.webview_activity)
    binding.webview.webViewClient = mWebViewClient
    binding.webview.settings.javaScriptEnabled = true
    if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
        val nightMode =
            (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
        val forceDarkMode = if (nightMode) {
            WebSettingsCompat.FORCE_DARK_ON
        } else {
            WebSettingsCompat.FORCE_DARK_OFF
        }
        WebSettingsCompat.setForceDark(binding.webview.settings, forceDarkMode)
    }
    if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
        WebSettingsCompat.setForceDarkStrategy(
            binding.webview.settings,
            WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING
        )
    }
    binding.webview.loadUrl("https://twitter.com/")
}

端末がダークテーマの機能をサポートしているかを確認するために
WebViewFeature.isFeatureSupported
で判定を行っています。

端末内の設定がダークテーマか否かは、
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
で確認を行います。

WebSettingsCompatのsetForceDark (WebSettings settings, int forceDarkMode)を使用してWebViewのダークテーマのON/OFFを設定します。
※以下の表1において設定値を説明していますが、一見FORCE_DARK_AUTOを設定すればよさそうな感じはしますが、WebViewがダークテーマに切り替わらないため、直接ON/OFFを設定するように対応しています。
WebSettingsCompatのsetForceDarkStrategy (WebSettings settings, int forceDarkBehavior)
を使用してどのようにダークテーマを反映するかを設定します。

setForceDarkの設定値は以下になります。
表1 setForceDarkの設定値

意味
FORCE_DARK_AUTO 親Viewの状態に応じて、ダークテーマのON/OFFを自動で設定します。
FORCE_DARK_OFF ダークテーマを無効にします。WebViewのコンテンツをライトモードで表示するようにします。
FORCE_DARK_ON ダークテーマを有効にします。WebViewのコンテンツをダークテーマで表示するようにします。

setForceDarkStrategyの設定値は以下になります。
表2 setForceDarkStrategyの設定値

意味
DARK_STRATEGY_WEB_THEME_DARKENING_ONLY Webコンテンツがダークテーマに対応していればそのダークテーマで表示されます。
非ダークテーマ対応であれば、ライトテーマで表示されます。
DARK_STRATEGY_USER_AGENT_DARKENING_ONLY 「ダークモード」設定ONの時において、WebViewコンテンツがダークテーマに対応・非ダークテーマ対応に関わらず、強制ダークテーマで表示されます。
「ダークモード」設定OFF時では、ライトテーマで表示されます。
DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING 「ダークモード」設定ONの時において、WebViewコンテンツがダークテーマに対応していればそのダークテーマで表示される。非ダークテーマ対応であれば、強制ダークテーマで表示されます。
「ダークモード」設定OFF時では、ライトテーマで表示されます。

ダークテーマ切り替え時のライフサイクルについて

図1に示しているようなシステム設定の「ダークモード」で、ダークテーマ/ライトテーマを切り替えた時のアプリのライフサイクルは以下の通りです。

Activity#onDestroy()

Activity#onCreate(savedInstanceState: Bundle?)

Activity#onStart()

Activity#onResume()

アプリの再描画がかかりonCreateが起動しています。
Fragmentを設定している場合、多重にFragmentがcommitされ、アプリの表示がおかしくなるような事象が発生しました。

アプリ独自でダークテーマを設定

端末固有の設定ではなく、アプリ固有でダークテーマを設定することも可能です。
 AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
  →常時、ライトテーマで表示されます
 AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO)
  →常時、ダークテーマで表示されます。
 AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_FOLLOW_SYSTEM)
  →システム設定の「ダークモード」の設定状況に従います。

案件で導入してみての所感

  • 導入でも記載していますが、リソースのファイルの定義名・ファイル名を共通にすればダークテーマは適用可能な為、特別なことは必要なく導入は容易でした。
  • 自分自身での開発中においては、あまり情報は出回っていなく導入方法で不明瞭なところもありましたが、技術共有サイトQiita等で情報が多く出回ってきていることもあり導入は容易だと思います。
  • 致し方ないことですが、ダークテーマ用の画像リソース等の追加が必要な為、アプリ容量が増えました。
  • 開発中はデザイナーとのやり取りが多く発生しました。デザインツール上と実際の端末では色合いが微妙に異なり部品が背景色と同化してしまう等あり、調整が普段より多めに発生しました。
  • リソースファイルの定義において、色自身が定義されていると修正が大変。担当した案件ではこれが広範囲に及びリソースの再定義を行いました。(※1%でもダークテーマ対応の可能性がある場合、リソースの定義は色名の使用は避け、部品(パーツ)名等にした方が良いと思います。)

以下、例を記載しておきます。

例)再定義前
 res/values/colors.xml

 <color name="black">#000000</color>  ※000000は色の値としては黒色になります。

 res/values-night/colors.xml

 <color name="black">#FFFFFF</color>  ※FFFFFFは色の値としては白色になります。

 →values-nightのcolors.xmlにおいて、色の値はFFFFFFで白色にも関わらず、定義名がblackで定義名と値が一致していません。

例)再定義後
 res/values/colors.xml

 <color name="common_text_color_1">#000000</color>

 res/values-night/colors.xml

 <color name="common_text_color_1">#FFFFFF</color>

 →定義名をパーツ名にすれば、色の値と定義名で一致しないということはないです。