web-dev-qa-db-ja.com

OSが使用しているテーマを検出するためのAPIはありますか-暗いまたは明るい(またはその他)?

バックグラウンド

最近のAndroidバージョン、Android 8.1以降のバージョン)では、OSはより多くのテーマをサポートしています。より具体的には、暗いテーマです。

問題

ユーザーの視点でダークモードについて多くの話があるにもかかわらず、開発者向けに書かれたものはほとんどありません。

私が見つけたもの

Android 8.1以降、Googleはある種の暗いテーマを提供しました。ユーザーが暗い壁紙を選択すると、OSの一部のUIコンポーネントが黒くなります(記事 ここ )。

さらに、ライブ壁紙アプリを開発した場合、OSにどの色(3種類の色)があるかを通知でき、OSの色にも影響を与えました(少なくともバニラベースのROMとGoogleデバイスでは)。そのため、色を選択しながら壁紙を作成できるアプリを作成しました( こちら )。これは、 notifyColorsChanged を呼び出し、次に onComputeColorsを使用して提供します

Android 9.0以降、テーマを選択できるようになりました:明るい、暗い、または自動(壁紙に基づく):

enter image description here

そして今Android Q、それはさらに進んだようですが、それでもまだどの程度かは不明です。どういうわけか「スマートランチャー」と呼ばれるランチャーがそれに乗っていて、テーマを正しく使用することを申し出ていますそれ自体(記事 here )なので、ダークモードを有効にすると(手動で、書いたように here )、アプリの設定画面が表示されます:

enter image description here

これまでに見つけた唯一のことは、上記の記事であり、私はこの種のトピックに従っています。

ライブ壁紙を使用して色を変更するようにOSに要求する方法も知っていますが、これはAndroid Q、少なくとも私が試したときに見たものに従って)変化しているようです(時刻に基づいていますが、確実ではありません)。

質問

  1. OSが使用するように設定されている色を取得するAPIはありますか?

  2. OSのテーマを取得するためのAPIはありますか?どのバージョンから?

  3. 新しいAPIはなんとなくナイトモードに関連していますか?それらはどのように連携しますか?

  4. アプリが選択したテーマを処理するための素晴らしいAPIはありますか?つまり、OSが特定のテーマの場合、現在のアプリはそうでしょうか?

11

わかりましたので、Android(Q)以前のバージョンの両方で、これが通常どのように機能するかを知るようになりました。

OSがWallpaperColorsを作成すると、色のヒントも生成されるようです。関数WallpaperColors.fromBitmapにはint hints = calculateDarkHints(bitmap);への呼び出しがあり、これは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;
}

次に、WallpaperColors.JavaにあるgetColorHintsを検索すると、StatusBar.JavaupdateTheme関数が見つかりました。

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

これはAndroid 8.1でのみ機能します。テーマは壁紙の色のみに基づいているためです。Android 9.0では、ユーザーは壁紙への接続。

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
}

ほとんどの場合、何も保証されないため、さまざまな値を設定しました。

3

GoogleはI/O 2019の終わりにダークテーマに関するドキュメントを公開しました here

ダークテーマを管理するには、最初に最新バージョンのMaterial Componentsライブラリを使用する必要があります:"com.google.Android.material:material:1.1.0-alpha06"

システムのテーマに応じてアプリケーションのテーマを変更します

システムに応じてアプリケーションがダークテーマに切り替わる場合、必要なテーマは1つだけです。これを行うには、テーマに親としてTheme.MaterialComponents.DayNightが必要です。

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

現在のシステムテーマを決定します

システムが現在ダークテーマになっているかどうかを確認するには、次のコードを実装できます。

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

テーマの変更が通知されます

テーマが変更されるたびに通知されるコールバックを実装することは可能ではないと思いますが、それは問題ではありません。実際、システムがテーマを変更すると、アクティビティは自動的に再作成されます。その場合、アクティビティの最初に前のコードを配置するだけで十分です。

Android SDKのどのバージョンから機能しますか?

Android Pie =Android SDKのバージョン28で動作します。これは、次のバージョンのSDKでのみ機能すると想定しています。 SDK、Q、バージョン29でリリースされます。

結果

result

17
Charles Annic

Charles Annicの答えに対するより単純なKotlinアプローチ:

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

Googleでは、Android Q.

たぶん DayNightテーマ

次に、アプリで機能を有効にする必要があります。そのためには、次のいずれかの値を取るAppCompatDelegate.setDefaultNightMode()を呼び出します。

  • MODE_NIGHT_NO。常に日(光)テーマを使用します。
  • MODE_NIGHT_YES。常に夜(暗い)テーマを使用します。
  • MODE_NIGHT_FOLLOW_SYSTEM(デフォルト)。この設定はシステムの設定に従います。Android Pie以上はシステム設定です(これについては以下で詳しく説明します)。
  • MODE_NIGHT_AUTO_BATTERY。デバイスの「バッテリーセーバー」機能が有効になっている場合は暗くなり、それ以外の場合は明るくなります。 vv1.1.0-alpha03の新機能。
  • MODE_NIGHT_AUTO_TIMEおよびMODE_NIGHT_AUTO。時刻に基づいて、昼と夜の間に変化します。