Material 3のカラーテーマについて整理してみる

この記事をシェア

Material 3(以下、M3)のカラーテーマって複雑ですよね。自分の理解を進めるために、できるだけ整理してみます。もし間違っている部分があったら、Twitterなどで指摘していただけると助かります。

カラーテーマの構成

まずはM3のカラーテーマがどのような要素で構成されているのか、使われる用語の意味も確認しながら整理します。

テーマとは

そもそもテーマとは、アプリのUIに一貫性を持たせるために定義する属性のことです。

カラースキーム、タイプグラフィー、シェイプで構成されます。

カラースキーム

よく見るこれのことです。

https://material-foundation.github.io/material-theme-builder/#/custom

23種類のロールに割り当てた色の集合です。

ライトテーマとダークテーマで別々のカラースキームを設定するのが一般的です。

5つのキーカラーエラーカラーからそれぞれトーンパレットを生成し、各トーンパレットから3~4色の色をピックアップしてロールに割り当て、カラースキームを構成します。

ロール

Primary, OnPrimary, PrimaryContainer, OnPrimaryContainerなど、カラースキームを構成する23種類の色の名前です。

“On”がつくロールは、同名のロールの上に文字やアイコンを配置するときに使います。例えばOnPrimaryはPrimaryの上に配置する文字やアイコンの色として使います。

キーカラー

トーンパレットを作成する基準になり、カラースキームを特徴づける色です。

キーカラーのうちPrimarySecondaryTertiaryの3色を「アクセントカラー」と呼び、NeutralNeutral Variantの2色を「ニュートラルカラー」と呼びます。

Primary Color

UI全体を通して目立つ部分に使うキーカラーで、UIを特徴づける色となります。

Secondary Color

Primaryよりは目立たないが、色のバリエーションを広げるために使われます。

Tertiary Color

PrimaryとSecondaryのバランスをとったり、より高い注意を引かせたい部分に使ったりします。

Neutral Color

SurfaceやBackgroundとして、テキストやアイコンを目立たせたい部分で使います。

Neutral Variant Color

テキストやアイコンを控えめに見せたい部分のSurfaceに使います。

エラーカラー

エラー表示などに使うための色。通常は赤系の色になります。キーカラーと同じく、トーンパレットの基準になります。

トーンパレット

これです。

https://material-foundation.github.io/material-theme-builder/#/custom

各キーカラーの色相(Hue)と彩度(Chroma)は変化させずに、輝度(Luminance)を0%(黒)から100%(白)まで変化させて作成した13種類の色の集合です。それぞれの色は、輝度のパーセンテージを使って、「Primary40」などのように呼びます。

このトーンパレットの中から、カラースキームのロールに割り当てます。キーカラーを元にアルゴリズム的にパレットを作成するため、キーカラーで指定した色は必ずしも実際のカラースキームには使われません。

カラースキームを作成する

ここまで読んでいただいた方は分かったと思いますが、独自のカラーテーマの作成とは、カラースキームを作成する作業にほかなりません。ここからは、WebのTheme Builderを使ってカラースキームを作成し、Jetpack ComposeのUIに適用するまでの手順を確認していきます。

Theme Builder

テーマの作成には、Webツールの「Material Theme Builder」が便利ですが、このほかにもFigmaのプラグインなんかもあります。

Material Theme BuilderのURLを開き、上部の「CUSTOM」をクリックします。

「Primary」の丸い部分をクリックし、Primary Key Colorを指定します。

Primary Key Colorを指定すると、トーンパレットが作成され、Primary, On Primaryなどの色が設定されます。また、SecondaryやTertiary、Neutralも連動して変化します。

必要に応じて、Secondary, Tertiary, Neutralも指定します。これらの色を指定するときは、上から順に指定する必要があることに注意してください。Secondaryを指定した後でPrimaryを指定すると、Primaryに合わせてアルゴリズムがSecondaryの色を変更してしまいます。TertiaryやNeutralも同様です。

希望のカラースキームができたら、右上の「EXPORT」をクリックし、Jetpack Composeをクリックすると、zipファイルをダウンロードできます。

プロジェクトに組み込む

ダウンロードしたzipファイルには、Color.ktTheme.ktが含まれています。これらをAndroid Studioのプロジェクトに組み込めば、作成したカラースキームを適用できます。

プロジェクト作成時にEmpty Compose Activityのテンプレートを使った場合は、プロジェクトにすでにColor.ktTheme.ktが存在しています。ここに、ダウンロードしたファイルの内容を組み込みます。

Color.kt

Color.ktは単なる色の定義の羅列ですので、ダウンロードしたファイルに含まれている定義を単純にコピペでOKです。既存プロジェクトがM3の場合は、同じ名前が既に定義されているかもしれないので、上書きします。もともとがM2の場合は、単純に定義を追加する形で問題ないと思います。

val md_theme_light_primary = Color(0xFF166d21)
val md_theme_light_onPrimary = Color(0xFFffffff)
val md_theme_light_primaryContainer = Color(0xFFa0f799)
val md_theme_light_onPrimaryContainer = Color(0xFF002202)
val md_theme_light_secondary = Color(0xFF52634e)
val md_theme_light_onSecondary = Color(0xFFffffff)
val md_theme_light_secondaryContainer = Color(0xFFd5e8ce)
val md_theme_light_onSecondaryContainer = Color(0xFF101f0f)
val md_theme_light_tertiary = Color(0xFF38656a)
val md_theme_light_onTertiary = Color(0xFFffffff)
val md_theme_light_tertiaryContainer = Color(0xFFbcebf0)
val md_theme_light_onTertiaryContainer = Color(0xFF011f22)
val md_theme_light_error = Color(0xFFba1b1b)
val md_theme_light_errorContainer = Color(0xFFffdad4)
val md_theme_light_onError = Color(0xFFffffff)
val md_theme_light_onErrorContainer = Color(0xFF410001)
val md_theme_light_background = Color(0xFFfcfdf6)
val md_theme_light_onBackground = Color(0xFF1a1c19)
val md_theme_light_surface = Color(0xFFfcfdf6)
val md_theme_light_onSurface = Color(0xFF1a1c19)
val md_theme_light_surfaceVariant = Color(0xFFdee5d8)
val md_theme_light_onSurfaceVariant = Color(0xFF424840)
val md_theme_light_outline = Color(0xFF73796f)
val md_theme_light_inverseOnSurface = Color(0xFFf1f1eb)
val md_theme_light_inverseSurface = Color(0xFF2f312d)
val md_theme_light_inversePrimary = Color(0xFF85da80)
val md_theme_light_shadow = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFF85da80)
val md_theme_dark_onPrimary = Color(0xFF003907)
val md_theme_dark_primaryContainer = Color(0xFF00530d)
val md_theme_dark_onPrimaryContainer = Color(0xFFa0f799)
val md_theme_dark_secondary = Color(0xFFbaccb3)
val md_theme_dark_onSecondary = Color(0xFF253423)
val md_theme_dark_secondaryContainer = Color(0xFF3c4b39)
val md_theme_dark_onSecondaryContainer = Color(0xFFd5e8ce)
val md_theme_dark_tertiary = Color(0xFFa1cfd4)
val md_theme_dark_onTertiary = Color(0xFF00363b)
val md_theme_dark_tertiaryContainer = Color(0xFF1e4d52)
val md_theme_dark_onTertiaryContainer = Color(0xFFbcebf0)
val md_theme_dark_error = Color(0xFFffb4a9)
val md_theme_dark_errorContainer = Color(0xFF930006)
val md_theme_dark_onError = Color(0xFF680003)
val md_theme_dark_onErrorContainer = Color(0xFFffdad4)
val md_theme_dark_background = Color(0xFF1a1c19)
val md_theme_dark_onBackground = Color(0xFFe2e3dd)
val md_theme_dark_surface = Color(0xFF1a1c19)
val md_theme_dark_onSurface = Color(0xFFe2e3dd)
val md_theme_dark_surfaceVariant = Color(0xFF424840)
val md_theme_dark_onSurfaceVariant = Color(0xFFc2c8bd)
val md_theme_dark_outline = Color(0xFF8c9288)
val md_theme_dark_inverseOnSurface = Color(0xFF1a1c19)
val md_theme_dark_inverseSurface = Color(0xFFe2e3dd)
val md_theme_dark_inversePrimary = Color(0xFF166d21)
val md_theme_dark_shadow = Color(0xFF000000)

Theme.kt

ダウンロードしたTheme.ktには、ライトテーマ用とダークテーマ用のカラースキームの定義と、それらを端末の設定に合わせて切り替える処理が含まれています。

private val LightThemeColors = lightColorScheme(
	primary = md_theme_light_primary,
	onPrimary = md_theme_light_onPrimary,
	primaryContainer = md_theme_light_primaryContainer,
	onPrimaryContainer = md_theme_light_onPrimaryContainer,
	secondary = md_theme_light_secondary,
	onSecondary = md_theme_light_onSecondary,
	secondaryContainer = md_theme_light_secondaryContainer,
	onSecondaryContainer = md_theme_light_onSecondaryContainer,
	tertiary = md_theme_light_tertiary,
	onTertiary = md_theme_light_onTertiary,
	tertiaryContainer = md_theme_light_tertiaryContainer,
	onTertiaryContainer = md_theme_light_onTertiaryContainer,
	error = md_theme_light_error,
	errorContainer = md_theme_light_errorContainer,
	onError = md_theme_light_onError,
	onErrorContainer = md_theme_light_onErrorContainer,
	background = md_theme_light_background,
	onBackground = md_theme_light_onBackground,
	surface = md_theme_light_surface,
	onSurface = md_theme_light_onSurface,
	surfaceVariant = md_theme_light_surfaceVariant,
	onSurfaceVariant = md_theme_light_onSurfaceVariant,
	outline = md_theme_light_outline,
	inverseOnSurface = md_theme_light_inverseOnSurface,
	inverseSurface = md_theme_light_inverseSurface,
	inversePrimary = md_theme_light_inversePrimary,
)

private val DarkThemeColors = darkColorScheme(
	primary = md_theme_dark_primary,
	onPrimary = md_theme_dark_onPrimary,
	primaryContainer = md_theme_dark_primaryContainer,
	onPrimaryContainer = md_theme_dark_onPrimaryContainer,
	secondary = md_theme_dark_secondary,
	onSecondary = md_theme_dark_onSecondary,
	secondaryContainer = md_theme_dark_secondaryContainer,
	onSecondaryContainer = md_theme_dark_onSecondaryContainer,
	tertiary = md_theme_dark_tertiary,
	onTertiary = md_theme_dark_onTertiary,
	tertiaryContainer = md_theme_dark_tertiaryContainer,
	onTertiaryContainer = md_theme_dark_onTertiaryContainer,
	error = md_theme_dark_error,
	errorContainer = md_theme_dark_errorContainer,
	onError = md_theme_dark_onError,
	onErrorContainer = md_theme_dark_onErrorContainer,
	background = md_theme_dark_background,
	onBackground = md_theme_dark_onBackground,
	surface = md_theme_dark_surface,
	onSurface = md_theme_dark_onSurface,
	surfaceVariant = md_theme_dark_surfaceVariant,
	onSurfaceVariant = md_theme_dark_onSurfaceVariant,
	outline = md_theme_dark_outline,
	inverseOnSurface = md_theme_dark_inverseOnSurface,
	inverseSurface = md_theme_dark_inverseSurface,
	inversePrimary = md_theme_dark_inversePrimary,
)

@Composable
fun AppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable() () -> Unit
) {
    val colors = if (!useDarkTheme) {
        LightThemeColors
    } else {
        DarkThemeColors
    }

    MaterialTheme(
        colorScheme = colors,
        typography = AppTypography,
        content = content
    )
}

Jetpack Composeプロジェクトの場合、XxxTheme()(Xxxはプロジェクト名)という関数がすでに用意されているはずですので、その中身をダウンロードしたAppTheme()に置き換えます。

M2のプロジェクトをM3に移行する場合は、依存関係の変更などいくつか対応が必要になります。そのあたりは「Jetpack ComposeアプリをMaterial Design 3へ移行する」にまとめてありますので、参考にしてください。

また、ダウンロードしたTheme.ktには、カスタムカラーについての定義も含まれています。カスタムカラーを使用する場合はこれらもコピーします。

補足

Material Designはあくまでガイドラインであって、絶対に守らなければならないものではないです。Theme Builderで作成したテーマが気に入らないので、一部の色だけ変更したい、ということもあると思います。個人的には、指定したKey Colorが実際のアプリのUIには使われない(Key Colorを元に生成したカラーパレットから、決められた輝度の色が使われるため)点が微妙だなあと思います。

そういった場合には、Theme Builderで作成してダウンロードしたソースコードの一部を変更して使うことになると思います。その時は、コンテナとコンテンツの関係になって(入れ子になって)使われる可能性のあるロール同士のコントラストが確保されるよう注意します。そういったロールの組み合わせはMaterial Design 3のColor Themingページに記載されています。

参考

この記事を書くにあたって、下記のサイトを参考にしました。

この記事をシェア