web-dev-qa-db-fra.com

Existe-t-il une API pour détecter le thème utilisé par le système d'exploitation - sombre ou clair (ou autre)?

Contexte

Sur les versions récentes de Android, depuis Android 8.1, le système d'exploitation a de plus en plus de support pour les thèmes. Plus précisément le thème sombre.

Le problème

Même s'il y a beaucoup de discussions sur le mode sombre du point de vue des utilisateurs, il n'y a presque rien d'écrit pour les développeurs.

Ce que j'ai trouvé

À partir de Android 8.1, Google a fourni une sorte de thème sombre. Si l'utilisateur choisit d'avoir un fond d'écran sombre, certains composants de l'interface utilisateur du système d'exploitation deviendraient noirs (article ( ici ).

De plus, si vous avez développé une application de fond d'écran en direct, vous pouvez dire au système d'exploitation de quelles couleurs il a (3 types de couleurs), ce qui affecte également les couleurs du système d'exploitation (au moins sur les ROM basées sur Vanilla et sur les appareils Google). C'est pourquoi j'ai même créé une application qui vous permet d'avoir n'importe quel fond d'écran tout en pouvant choisir les couleurs ( ici ). Cela se fait en appelant ( notifyColorsChanged puis en les fournissant en utilisant onComputeColors

À partir de Android 9.0, il est maintenant possible de choisir le thème à avoir: clair, foncé ou automatique (basé sur le papier peint):

enter image description here

Et maintenant, près de Android Q, il semble que cela soit allé plus loin, mais on ne sait toujours pas dans quelle mesure. D'une manière ou d'une autre, un lanceur appelé "Smart Launcher" est monté dessus, offrant d'utiliser le thème à droite sur lui-même (article ici ). Donc, si vous activez le mode sombre (manuellement, comme écrit ici ), vous obtenez l'écran des paramètres de l'application en tant que tel:

enter image description here

La seule chose que j'ai trouvée jusqu'à présent est les articles ci-dessus, et que je suis ce genre de sujet.

Je sais également comment demander au système d'exploitation de changer de couleur à l'aide du fond d'écran en direct, mais cela semble changer sur Android Q, au moins en fonction de ce que j'ai vu en l'essayant (je pense c'est plus basé sur le moment de la journée, mais pas sûr).

Questions

  1. Existe-t-il une API pour obtenir les couleurs que le système d'exploitation est configuré pour utiliser?

  2. Existe-t-il une sorte d'API pour obtenir le thème du système d'exploitation? De quelle version?

  3. La nouvelle API est-elle également liée au mode nuit? Comment fonctionnent-ils ensemble?

  4. Existe-t-il une belle API pour les applications pour gérer le thème choisi? Cela signifie que si le système d'exploitation est dans un certain thème, l'application actuelle le sera-t-elle?

11
android developer

OK, j'ai donc appris comment cela fonctionne généralement, sur les deux dernières versions de Android (Q) et avant.

Il semble que lorsque le système d'exploitation crée les WallpaperColors, il génère également des nuances de couleur. Dans la fonction WallpaperColors.fromBitmap, Il y a un appel à int hints = calculateDarkHints(bitmap);, et voici le code de calculateDarkHints:

/**
 * Checks if image is bright and clean enough to support light text.
 *
 * @param source What to read.
 * @return Whether image supports dark text or not.
 */
private static int calculateDarkHints(Bitmap source) {
    if (source == null) {
        return 0;
    }

    int[] pixels = new int[source.getWidth() * source.getHeight()];
    double totalLuminance = 0;
    final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA);
    int darkPixels = 0;
    source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */,
            source.getWidth(), source.getHeight());

    // This bitmap was already resized to fit the maximum allowed area.
    // Let's just loop through the pixels, no sweat!
    float[] tmpHsl = new float[3];
    for (int i = 0; i < pixels.length; i++) {
        ColorUtils.colorToHSL(pixels[i], tmpHsl);
        final float luminance = tmpHsl[2];
        final int alpha = Color.alpha(pixels[i]);
        // Make sure we don't have a dark pixel mass that will
        // make text illegible.
        if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) {
            darkPixels++;
        }
        totalLuminance += luminance;
    }

    int hints = 0;
    double meanLuminance = totalLuminance / pixels.length;
    if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels < maxDarkPixels) {
        hints |= HINT_SUPPORTS_DARK_TEXT;
    }
    if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) {
        hints |= HINT_SUPPORTS_DARK_THEME;
    }

    return hints;
}

Puis en recherchant getColorHints que possède le WallpaperColors.Java, J'ai trouvé la fonction updateTheme dans StatusBar.Java:

    WallpaperColors systemColors = mColorExtractor
            .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
    final boolean useDarkTheme = systemColors != null
            && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;

Cela ne fonctionnerait que sur Android 8.1, car le thème était basé uniquement sur les couleurs du fond d'écran. Sur Android 9.0, l'utilisateur peut le définir sans toute connexion au fond d'écran.

Voici ce que j'ai fait, selon ce que j'ai vu sur Android:

enum class DarkThemeCheckResult {
    DEFAULT_BEFORE_THEMES, LIGHT, DARK, PROBABLY_DARK, PROBABLY_LIGHT, USER_CHOSEN
}

@JvmStatic
fun getIsOsDarkTheme(context: Context): DarkThemeCheckResult {
    when {
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.O -> return DarkThemeCheckResult.DEFAULT_BEFORE_THEMES
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> {
            val wallpaperManager = WallpaperManager.getInstance(context)
            val wallpaperColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
                    ?: return DarkThemeCheckResult.UNKNOWN
            val primaryColor = wallpaperColors.primaryColor.toArgb()
            val secondaryColor = wallpaperColors.secondaryColor?.toArgb() ?: primaryColor
            val tertiaryColor = wallpaperColors.tertiaryColor?.toArgb() ?: secondaryColor
            val bitmap = generateBitmapFromColors(primaryColor, secondaryColor, tertiaryColor)
            val darkHints = calculateDarkHints(bitmap)
            //taken from StatusBar.Java , in updateTheme :
            val HINT_SUPPORTS_DARK_THEME = 1 shl 1
            val useDarkTheme = darkHints and HINT_SUPPORTS_DARK_THEME != 0
            if (Build.VERSION.SDK_INT == VERSION_CODES.O_MR1)
                return if (useDarkTheme)
                    DarkThemeCheckResult.UNKNOWN_MAYBE_DARK
                else DarkThemeCheckResult.UNKNOWN_MAYBE_LIGHT
            return if (useDarkTheme)
                DarkThemeCheckResult.MOST_PROBABLY_DARK
            else DarkThemeCheckResult.MOST_PROBABLY_LIGHT
        }
        else -> {
            return when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
                Configuration.UI_MODE_NIGHT_YES -> DarkThemeCheckResult.DARK
                Configuration.UI_MODE_NIGHT_NO -> DarkThemeCheckResult.LIGHT
                else -> DarkThemeCheckResult.MOST_PROBABLY_LIGHT
            }
        }
    }
}

fun generateBitmapFromColors(@ColorInt primaryColor: Int, @ColorInt secondaryColor: Int, @ColorInt tertiaryColor: Int): Bitmap {
    val colors = intArrayOf(primaryColor, secondaryColor, tertiaryColor)
    val imageSize = 6
    val bitmap = Bitmap.createBitmap(imageSize, 1, Bitmap.Config.ARGB_8888)
    for (i in 0 until imageSize / 2)
        bitmap.setPixel(i, 0, colors[0])
    for (i in imageSize / 2 until imageSize / 2 + imageSize / 3)
        bitmap.setPixel(i, 0, colors[1])
    for (i in imageSize / 2 + imageSize / 3 until imageSize)
        bitmap.setPixel(i, 0, colors[2])
    return bitmap
}

J'ai défini les différentes valeurs possibles, car dans la plupart de ces cas, rien n'est garanti.

3

Google vient de publier la documentation sur le thème sombre à la fin des E/S 2019, ici .

Pour gérer le thème sombre, vous devez d'abord utiliser la dernière version de la bibliothèque de composants de matériaux: "com.google.Android.material:material:1.1.0-alpha06".

Modifiez le thème de l'application en fonction du thème du système

Pour que l'application passe au thème sombre selon le système, un seul thème est requis. Pour ce faire, le thème doit avoir Theme.MaterialComponents.DayNight en tant que parent.

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

Déterminez le thème système actuel

Pour savoir si le système est actuellement dans un thème sombre ou non, vous pouvez implémenter le code suivant:

switch (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) {
    case Configuration.UI_MODE_NIGHT_YES:
        …
        break;
    case Configuration.UI_MODE_NIGHT_NO:
        …
        break; 
}

Soyez averti d'un changement de thème

Je ne pense pas qu'il soit possible d'implémenter un rappel pour être averti chaque fois que le thème change, mais ce n'est pas un problème. En effet, lorsque le système change de thème, l'activité est automatiquement recréée. Placer le code précédent au début de l'activité est alors suffisant.

À partir de quelle version du SDK Android SDK cela fonctionne-t-il?

Je n'ai pas pu faire fonctionner cela sur Android Pie avec la version 28 du SDK Android SDK. Je suppose donc que cela ne fonctionne qu'à partir de la prochaine version du SDK, qui sera lancé avec Q, version 29.

Résultat

result

17
Charles Annic

Une approche Kotlin plus simple de la réponse de Charles Annic:

fun Context.isDarkThemeOn(): Boolean{
    return resources.configuration.uiMode and 
            Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
}
1
Vitor Hugo Schwaab

Je pense que Google se base sur le niveau de la batterie pour appliquer des thèmes sombres et clairs dans Android Q.

Peut-être thème DayNight ?

Vous devez ensuite activer la fonctionnalité dans votre application. Pour ce faire, vous appelez AppCompatDelegate.setDefaultNightMode (), qui prend l'une des valeurs suivantes:

  • MODE_NIGHT_NO. Utilisez toujours le thème du jour (léger).
  • MODE_NIGHT_YES. Utilisez toujours le thème de la nuit (sombre).
  • MODE_NIGHT_FOLLOW_SYSTEM (par défaut). Ce paramètre suit le paramètre du système, qui sur Android Pie et supérieur est un paramètre système (plus d'informations ci-dessous).
  • MODE_NIGHT_AUTO_BATTERY. Passe à sombre lorsque la fonction ‘Économiseur de batterie’ est activée sur l’appareil, sinon, s’allume. EwNouveau dans v1.1.0-alpha03.
  • MODE_NIGHT_AUTO_TIME & MODE_NIGHT_AUTO. Changements jour/nuit en fonction de l'heure de la journée.
1