Android 10 で公式にサポートされた「ダークテーマ(ナイトモード)」ですが、スレッド式メモ帳アプリ『CBnotes』では、Android 10 未満の端末にインストールしても「ダークテーマ(ナイトモード)」の切り替え設定が可能になっています。
以下の公式ガイドに従って、アプリを DayNight テーマから継承すれば、簡単に「ダークテーマ(ナイトモード)」を実現できました。『CBnotes』では、「MaterialComponents のダークテーマ機能」を使用しています。
Dark theme | Android Developers
Dark Theme - Material Components for Android
実装の重要な注意点
ところが、公式ガイドには記載されていない重要なポイントがありました。
公式ガイドに従って「AppCompatDelegate.setDefaultNightMode()」を呼び出してダークテーマ(ナイトモード)を設定しますが、なんと実はこの設定値はシステムに保存されません。
「MODE_NIGHT_YES」を設定してダークテーマ(ナイトモード)に切り替えていても、次にアプリを起動すると、ライトテーマに戻ってしまいます。
ですので、やや面倒ですが、独自にアプリ内で設定を保存しておかなければならず、また、アプリの起動時にその設定を反映しなければなりません。
その設定の反映は、タイミングが非常に重要です。
公式ガイドに記載がある通り(以下)、設定の切り替えによってアクティビティが再生成されますから、アクティビティの onCreate で反映するとタイミングが遅すぎて、アクティビティが二度連続で起動するような挙動(見た目にチラツキが分かる)になってしまいます。
注:AppCompat v1.1.0 以降、開始されているアクティビティは setDefaultNightMode() により自動的に再作成されます。
アプリが起動するときの、最速の処理タイミングは、Application の onCreate です。「ダークテーマ(ナイトモード)」の反映は、このタイミングで対応する必要があります。
『CBnotes』における実装
Application の実装は以下の通りです。この onCreate のタイミングで実装すれば、反映はスムーズで、見た目(チラツキ)の問題も起こりません。設定の保存は小規模なので SharedPreferences で十分です。
package cutboss.cbnotes
import android.app.Application
import android.os.Build
import androidx.preference.PreferenceManager
import androidx.appcompat.app.AppCompatDelegate
class BossApplication : Application() {
companion object {
private const val TAG: String = "BossApplication"
private const val KEY_DARK_THEME = "dark_theme"
private const val KEY_MODE_NIGHT = "mode_night"
}
override fun onCreate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// Sets the default night mode.
setDefaultNightMode(getDarkTheme())
} else {
// Sets the default night mode.
setDefaultNightMode(getNightMode())
}
// Called when the application is starting, before any activity, service,
// or receiver objects (excluding content providers) have been created.
super.onCreate()
}
/**
* Sets the default night mode.
*
* @param isChecked The new checked state of buttonView.
*/
fun setDefaultNightMode(isChecked: Boolean) {
// Set a boolean value in the preferences.
putDarkTheme(isChecked)
// Sets the default night mode.
AppCompatDelegate.setDefaultNightMode(
if (isChecked) {
// Night mode which uses always uses a dark mode, enabling night qualified
// resources regardless of the time.
AppCompatDelegate.MODE_NIGHT_YES
} else {
// Night mode which uses always uses a light mode, enabling notnight qualified
// resources regardless of the time.
AppCompatDelegate.MODE_NIGHT_NO
}
)
}
/**
* Sets the default night mode.
*
* @param mode The night mode.
*/
fun setDefaultNightMode(@AppCompatDelegate.NightMode mode: Int) {
// Set an int value in the preferences.
putNightMode(mode)
// Sets the default night mode.
AppCompatDelegate.setDefaultNightMode(mode)
}
/**
* Retrieve a boolean value from the preferences.
*
* @return Returns the preference value if it exists, or defValue.
*/
fun getDarkTheme(): Boolean {
return PreferenceManager
.getDefaultSharedPreferences(this)
.getBoolean(KEY_DARK_THEME, false)
}
/**
* Set a boolean value in the preferences.
*
* @param value The new value for the preference.
*/
private fun putDarkTheme(value: Boolean) {
PreferenceManager
.getDefaultSharedPreferences(this)
.edit()
.putBoolean(KEY_DARK_THEME, value)
.apply()
}
/**
* Retrieve an int value from the preferences.
*
* @return Returns the preference value if it exists, or defValue.
*/
fun getNightMode(): Int {
return PreferenceManager
.getDefaultSharedPreferences(this)
.getInt(KEY_MODE_NIGHT, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
/**
* Set an int value in the preferences.
*
* @param value The new value for the preference.
*/
private fun putNightMode(value: Int) {
PreferenceManager
.getDefaultSharedPreferences(this)
.edit()
.putInt(KEY_MODE_NIGHT, value)
.apply()
}
}
元記事(note)
Android 10 未満でもダークテーマ(ナイトモード)に対応する方法|Kotlin|開発裏話
『CBnotes』ソースコード一式
以下 note では、実際に Google Play へ公開リリースしている『CBnotes』のソースコード一式を公開しております。
Android(Kotlin)アプリ開発を学習している方々には「参考教材」として、業務で動作実績のあるサンプルコード&開発環境一式が必要なプロの方々には「工数削減」として、大変にオススメです。
スレッド式メモ帳アプリ Android(Kotlin)ソースコード一式を公開
https://note.com/cbnotes/n/n0ee15a0562a2
参考記事
アプリにDayNightテーマを追加する
DayNight — Adding a dark theme to your app
AppCompatDelegate.setDefaultNightMode() picked up by main activity only the first time?
Androidアプリでダークテーマを実装する
DarkTheme対応
これからAndroidでダークモードを実装するエンジニア/デザイナ向け資料