Crear selector de tema para el modo oscuro en Android Kotlin


Apuntes para crear un cuadro de diálogo donde el usuario puede escoger el modo oscuro a aplicar en la app, se le deja escoger entre el tema claro, oscuro, definido por el sistema o bien por el ahorro de batería.

Prerequesitos

Definiendo el selector de tema

Crear los textos y valores para mostrar en el cuadro de diálogo para seleccionar el tema de modo oscuro

Archivo values/strings.xml

<string name="pref_interface_theme_title">Theme</string>
<string name="pref_interface_theme_dialog_title">Choose theme</string>
<string name="pref_interface_theme_light">Light</string>
<string name="pref_interface_theme_dark">Dark</string>
<string name="pref_interface_theme_default_system">System default</string>
<string name="pref_interface_theme_auto_battery">Set by battery saver</string>

Definir los arrays de valores archvios values/arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="pref_theme_entries">
        <item>@string/pref_interface_theme_light</item>
        <item>@string/pref_interface_theme_dark</item>
        <item>@string/pref_interface_theme_default_system</item>
        <item>@string/pref_interface_theme_auto_battery</item>
    </string-array>
    <string-array name="pref_theme_entries_values">
        <item>MODE_NIGHT_NO</item>
        <item>MODE_NIGHT_YES</item>
        <item>MODE_DEFAULT_SYSTEM</item>
        <item>MODE_NIGHT_AUTO_BATTERY</item>
    </string-array>
</resources>

pref_theme_entries contendrá las cadenas de textos a mostrar de cada selección:

pref_theme_entries_values el valor que se usará para controlar el modo a establecer

En el XML donde se define los elementos de la ventana de configuración añadir

<ListPreference
    android:defaultValue="MODE_DEFAULT_SYSTEM"
    android:dialogTitle="@string/pref_interface_theme_dialog_title"
    android:entries="@array/pref_theme_entries"
    android:entryValues="@array/pref_theme_entries_values"
    android:key="pref_dark_theme"
    android:summary="%s"
    android:title="@string/pref_interface_theme_title"
    app:iconSpaceReserved="false" />

defaultValue: el valor del modo oscuro por defecto en este caso MODE_DEFAULT_SYSTEM
el android:key se declara el identificador de la preferencia en este caso pref_dark_theme

Control del modo oscuro

En el inicio de la aplicación en MyApplication.kt es donde se debe comprobar que modo oscuro a establecer para la totalidad de la app

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        instance = this

        prefs = Prefs(applicationContext)

        when (prefs.darkTheme) {
            "MODE_NIGHT_NO" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            "MODE_NIGHT_YES" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            "MODE_DEFAULT_SYSTEM" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
            "MODE_NIGHT_AUTO_BATTERY" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
        }

    }

    companion object {
        lateinit var instance: MyApplication
            private set

        lateinit var prefs: Prefs
            private set

        init {
            AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
        }
    }
}

Para el controlador de preferencias Prefs.kt

class Prefs(val context: Context) {
    val PREFS_FILENAME = BuildConfig.APPLICATION_ID + ".prefs"
    val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, Context.MODE_PRIVATE)

    fun getDefaultSharedPreferencesName(): String? = context.packageName + "_preferences"

    private val sharedDefaultPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)

    val darkTheme: String?
        get() = sharedDefaultPreferences.getString("pref_dark_theme", "MODE_DEFAULT_SYSTEM")

}

Aplicar cambios al salir de configuración

En caso de querer aplicar los cambios sin la necesidad de que reinicie la aplicación el usuario, se puede interceptar el cambio de selección del cuadro de diálogo y al salir de configuración, restablecer la vista.

En el controlador de la vista de preferencias, podemos interceptar el cambio de selección con onPreferenceChangeListener

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val selectTheme: ListPreference? = findPreference("pref_dark_theme")
    selectTheme?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
        (activity as SettingsActivity).needRecreate = true
        true
    }

}

Al detectar la selección por parte del usuario, añadimos un controlador de bandera que ha habido cambio con needRecreate = true

En SettingsActivity.kt

var needRecreate: Boolean = false
...
override fun onSupportNavigateUp(): Boolean {
    if (supportFragmentManager.popBackStackImmediate()) {
        return true
    }
    if (needRecreate) triggerRebirth(this) else finish()
    return super.onSupportNavigateUp()
}

Para reinicar la actividad usaremos la función triggerRebirth()

private fun triggerRebirth(context: Context) {
    val packageManager: PackageManager = context.packageManager
    val intent = packageManager.getLaunchIntentForPackage(context.packageName)
    val componentName = intent!!.component
    val mainIntent = Intent.makeRestartActivityTask(componentName)
    context.startActivity(mainIntent)
    Runtime.getRuntime().exit(0)
}

Estilo para Settings

Ajustes para que la vetana de ajustes aparezca concorde con los estilos definidos en la app

<style name="AppTheme.DayNight.Settings" parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge">
    <item name="windowActionBar">true</item>
    <item name="windowNoTitle">false</item>
    <item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
    <!--<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>-->
    <item name="android:statusBarColor">?attr/colorPrimary</item>
    <item name="colorPrimary">@color/primaryColor</item>
    <item name="colorPrimaryDark">@color/primaryDarkColor</item>
    <item name="colorAccent">@color/secondaryColor</item>

    <item name="android:navigationBarColor">?android:colorBackground</item>

</style>
Anuncio publicitario

Publicado por Codelaby

Mobile DevDesigner

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

A %d blogueros les gusta esto: