ダークモードを実装する

Compose を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でテーマ設定を操作する方法を学びます。

図 1. ダークモード。

ダークテーマは、Android 10(API レベル 29)以上で利用可能です。これには次の利点があります。

  • デバイスの画面技術に応じて消費電力を大幅に削減します。
  • 視力の低いユーザーや明るい光に敏感なユーザーにとって、画面の見やすさが向上します。
  • 暗い場所でデバイスを使用しやすくなります。

ダークモードは、Android システム UI とデバイスで実行されるアプリに適用されます。

Android 10 以降でダークモードを有効にするには、次の 3 つの方法があります。

  • システム設定を使用して [設定] > [ディスプレイ] > [テーマ] に移動し、ダークモードを有効にします。
  • 通知トレイからテーマを切り替えるには、クイック設定タイルを使用します。
  • Google Pixel デバイスでは、バッテリー セーバー モードを有効にすると、ダークモードも同時に有効になります。他のデバイスではサポートされていない場合があります。

WebView コンポーネントを使用してウェブベースのコンテンツにダークテーマを適用する手順については、WebView のウェブ コンテンツをダークテーマ化するをご覧ください。

アプリでダークモードをサポートする

ダークモードをサポートするには、DayNight テーマを継承するようにアプリのテーマ(通常は res/values/styles.xml にあります)を設定します。

<style name="AppTheme" parent="Theme.AppCompat.DayNight">

マテリアル コンポーネントのダークテーマを使用することもできます。

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

これにより、アプリのメインテーマがシステム制御の夜間モードのフラグに関連付けられ、有効になっている場合はアプリにデフォルトのダークモードが設定されます。

テーマとスタイル

ライトモードで使用するためにハードコードされた色やアイコンは使用しないでください。代わりに、テーマ属性または夜間専用のリソースを使用してください。

ダークモードでは、次の 2 つのテーマ属性が特に重要です。

  • ?android:attr/textColorPrimary: 汎用のテキストの色。ライトモードでは黒に近い色、ダークモードでは白に近い色になります。この属性には「無効」状態もあります。
  • ?attr/colorControlNormal: 汎用のアイコンの色。この属性には「無効」状態もあります。

マテリアル デザイン コンポーネントの使用をおすすめします。カラーテーマ設定システム(テーマ属性 ?attr/colorSurface?attr/colorOnSurface など)では適切な色に簡単にアクセスできます。これらの属性は、テーマでカスタマイズできます。

アプリ内でテーマを変更する

アプリの実行中にユーザーがアプリのテーマを変更できるようにすることができます。推奨されるオプションは次のとおりです。

  • 軽度
  • ダーク
  • システムのデフォルト(推奨のデフォルト オプション)

これらのオプションは AppCompat.DayNight モードに直接マッピングされています。

テーマを切り替えるには、次の操作を行います。

  • API レベル 31 以降では、UiModeManager#setApplicationNightMode を使用して、アプリで実行されるテーマをシステムに通知します。これにより、スプラッシュ画面中にシステムがテーマを一致させることができます。

  • API レベル 30 以下では、AppCompatDelegate.setDefaultNightMode() を使用してテーマを切り替えます。

強制ダーク

Android 10 には、デベロッパーが DayNight テーマを明示的に設定しなくてもダークモードをすばやく実装できる、フォースダークの機能が用意されています。

フォースダークは、ライトモードのアプリの各ビューを分析し、画面に描画される前にダークモードを自動的に適用します。フォースダークとネイティブの実装を組み合わせることで、ダークテーマの実装に必要な時間を短縮できます。

アプリは、アクティビティのテーマで android:forceDarkAllowed="true" を設定してフォースダークにオプトインする必要があります。この属性は、システムおよび AndroidX が提供するすべてのライトテーマ(Theme.Material.Light など)に設定されます。フォースダークを使用する場合は、アプリを徹底的にテストし、必要に応じてビューを除外します。

アプリでダークモード(Theme.Material など)を使用している場合、フォースダークは適用されません。同様に、アプリのテーマが DayNight テーマから継承されている場合、テーマの自動切り替えによりフォースダークは適用されません。

ビューで強制ダークを無効にする

android:forceDarkAllowed レイアウト属性または setForceDarkAllowed() を使用して、特定のビューでフォースダークを制御できます。

ウェブ コンテンツ

ウェブベースのコンテンツでダークテーマを使用する方法については、WebView のウェブ コンテンツにダークテーマを適用するをご覧ください。WebView に適用されるダークモードの例については、GitHub の WebView のデモをご覧ください。

おすすめの方法

以下のセクションでは、ダークテーマの実装に関するおすすめの方法を紹介します。

通知とウィジェット

デバイスに表示されるが直接制御はしない UI サーフェスの場合、使用するビューにホストアプリのテーマが反映されていることを確認します。その 2 つの例として、通知ウィジェットとランチャー ウィジェットがあります。

通知

システム提供の通知テンプレート(MessagingStyle など)を使用します。つまり、正しいビュー スタイル設定を適用する役割はシステムが担います。

ウィジェットとカスタム通知ビュー

ランチャー ウィジェットの場合、またはアプリがカスタム通知コンテンツ ビューを使用している場合は、ライトモードとダークモードの両方でコンテンツをテストします。

注意すべき一般的な注意点は次のとおりです。

  • 背景色は常に明るいと仮定します。
  • テキストの色をハードコードする。
  • デフォルトのテキスト色を使用しながら、ハードコードされた背景色を設定する。
  • 静的な色のドローアブル アイコンを使用する。

このような場合は、ハードコードされた色ではなく、適切なテーマ属性を使用します。

起動画面

アプリにカスタムの起動画面がある場合は、選択したテーマを反映するように修正が必要になることがあります。

プログラムで白に設定された背景色など、ハードコードされた色を削除します。代わりに ?android:attr/colorBackground テーマ属性を使用してください。

構成の変更

システム設定または AppCompat のいずれかによってアプリのテーマが変更されると、uiMode 構成の変更がトリガーされます。この場合、アクティビティは自動的に再作成されます。

場合によっては、アプリで構成の変更を処理したいことがあります。たとえば、動画の再生中に構成の変更を遅延させる場合などです。

アプリは、各 ActivityuiMode 構成の変更を処理できることを宣言することで、ダークモードの実装を処理できます。

<activity
    android:name=".MyActivity"
    android:configChanges="uiMode" />

Activity が構成の変更を処理することを宣言すると、テーマが変更されると onConfigurationChanged() メソッドが呼び出されます。

アプリで現在のテーマを確認するには、次のようなコードを実行します。

Kotlin

val currentNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (currentNightMode) {
    Configuration.UI_MODE_NIGHT_NO -> {} // Night mode is not active, we're using the light theme.
    Configuration.UI_MODE_NIGHT_YES -> {} // Night mode is active, we're using dark theme.
}

Java

int currentNightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
    case Configuration.UI_MODE_NIGHT_NO:
        // Night mode is not active, we're using the light theme
        break;
    case Configuration.UI_MODE_NIGHT_YES:
        // Night mode is active, we're using dark theme
        break;
}