Android 14以降の色のコントラスト機能をサポートする

Android 14 以降では OS での設定として色のコントラストを 3 段階に設定することができ、Android 14 では 開発者向けオプションでのみ設定可能だったものが、Android 15 以降では正式な機能になります。
この機能は各アプリで対応する必要があるものとなっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/d5895862-c6a8-75d1-b9dc-0c11b0030df6.png" width="200px" />


ここではその対応をどのように実装するのかについて書いてます。

<table>
  <caption>色のコントラストごとの見え方の違い</caption>
  <thead>
    <tr>
      <th>デフォルト</th> <th>中</th> <th>高</th>
    </tr>
  </thead>
  <tr>
    <td> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/c521428b-8de7-ef92-9a2f-c3a3af183147.png" width="200px" /> </td> <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/33ab0afd-2551-1a41-5315-26056c316660.png" width="200px" /></td>　<td> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/b449c566-7d5d-e8d0-8293-d3d5393b2bad.png" width="200px"/> </td>
  </tr>
</table>




## 前提
Material Design 3 を使用している必要があります。
個別に色をコントラストチェッカーにかけて定義していくことも可能ですが、ものすごい工数がかかるので基本的には [Material Theme Builder](https://material-foundation.github.io/material-theme-builder/) で生成した Color Theming を使うことをおすすめします。




## Jetpack Compose の場合

`UiModeManager.contrast` で現在 OS 側で設定されているコントラストの値を取得できるので、その値を元に Theming を分岐させます。
ここで使用している Theming の定義は Material Theme Builder を使って生成されるコードに含まれるものとなります。

```kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
    when (uiModeManager.contrast) {
        in 0.0f..0.33f -> if (isDarkTheme) darkScheme else lightScheme
        in 0.34f..0.66f -> if (isDarkTheme) mediumContrastDarkColorScheme else mediumContrastLightColorScheme
        in 0.67f..1.0f -> if (isDarkTheme) highContrastDarkColorScheme else highContrastLightColorScheme
        else -> if (isDarkTheme) darkScheme else lightScheme
    }
}
```

Dynamic Color の設定なども含めると、Theming の設定するコードはこのようになります。

```kotlin
@Composable
fun AppTheme(
    isDarkTheme: Boolean,
    isDynamicColor: Boolean,
    content: @Composable () -> Unit
) {
    val context = LocalContext.current
    val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
    val colorScheme =
        if (!isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            when (uiModeManager.contrast) {
                in 0.0f..0.33f -> if (isDarkTheme) darkScheme else lightScheme
                in 0.34f..0.66f -> if (isDarkTheme) mediumContrastDarkColorScheme else mediumContrastLightColorScheme
                in 0.67f..1.0f -> if (isDarkTheme) highContrastDarkColorScheme else highContrastLightColorScheme
                else -> if (isDarkTheme) darkScheme else lightScheme
            }
        } else if (isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (isDarkTheme) {
                dynamicDarkColorScheme(context)
            } else {
                dynamicLightColorScheme(context)
            }
        } else {
            if (isDarkTheme) darkScheme else lightScheme
        }

    MaterialTheme(
        colorScheme = colorScheme,
        ...
        content = content,
    )
}
```


## material-components(AndroidView) の場合

material-components に `ColorContrast` の Util クラスが用意されているので、Material Theme Builder で生成されたコントラスト用の ThemeOverlay の style を設定すれば Android 14 以降で Color Contrast が使用可能な場合に適用してくれます。

```kotlin
ColorContrast.applyToActivitiesIfAvailable(
    application,
    ColorContrastOptions.Builder()
        .setMediumContrastThemeOverlay(R.style.ThemeOverlay_AppTheme_MediumContrast)
        .setHighContrastThemeOverlay(R.style.ThemeOverlay_AppTheme_HighContrast)
        .build()
)
```

## Dynamic Color 使用時


Dynamic Color を使っているときは Dynamic color 側でコントラストの設定に合わせて色が調整されるので実装側で特別な対応は不要です。


## リンク

https://youtu.be/XQ_Hvu-s7JY?si=HbVUx5nrMd17x4oI

https://github.com/material-components/material-components-android/blob/e4f00fda11f152aa618ad19d36e914d05b484e14/docs/theming/Color.md#use-contrast-control---non-dynamic