Compose でのテーマ設定

Jetpack Compose では、テーマを適用することで、簡単にアプリのデザインに一貫性を持たせることができます。Compose のマテリアル デザインの実装は、プロダクトのブランドに合わせてカスタマイズできます。ニーズに合わない場合は、Compose の公開 API を使用してカスタム デザイン システムを構築できます。

アプリ全体のテーマ設定

Jetpack Compose には、デジタル インターフェースを作成するための包括的なデザイン システムであるマテリアル デザインの実装が用意されています。マテリアル デザインのコンポーネント(ボタン、カード、スイッチなど)は、プロダクトのブランドをより良く反映するようにマテリアル デザインをカスタマイズする体系的な方法である、マテリアル テーマ設定に基づいて構築されています。マテリアル テーマは、タイポグラフィシェイプの属性で構成されています。これらの属性をカスタマイズすると、その変更内容は、アプリのビルドに使用するコンポーネントに自動的に反映されます。

Jetpack Compose は、MaterialTheme コンポーザブルを使用して、こうしたコンセプトを実装します。

MaterialTheme(
    colors = …,
    typography = …,
    shapes = …
) {
    // app content
}

MaterialTheme に渡すパラメータを構成して、アプリのテーマを設定します。

対照的な 2 つのスクリーンショット。1 つ目のスクリーンショットはデフォルトの MaterialTheme スタイリングを使用し、2 つ目のスクリーンショットは変更したスタイリングを使用しています。

図 1. 1 つ目のスクリーンショットは MaterialTheme を構成していないアプリを示しています。そのため、デフォルトのスタイリングが使用されています。2 つ目のスクリーンショットは、スタイリングをカスタマイズするために MaterialTheme にパラメータを渡すアプリを示しています。

Compose では、色はシンプルなデータ保持クラスである Color クラスによってモデル化されます。

val red = Color(0xffff0000)
val blue = Color(red = 0f, green = 0f, blue = 1f)

これらはどのように整理してもかまいませんが(最上位の定数として、シングルトン内で、またはインラインで定義)、テーマで色を指定し、そこから色を取得することを強くおすすめします。このアプローチにより、ダークテーマなど、複数のテーマをサポートできるようになります。

テーマのカラーパレットの例

Compose には、マテリアル カラーシステムをモデル化するための Colors クラスが用意されています。Colors は、明るい色または暗い色のセットを作成するためのビルダー関数を提供します。

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

Colors を定義したら、MaterialTheme に渡すことができます。

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

テーマ色の使用

MaterialTheme.colors を使用すると、MaterialTheme コンポーザブルに提供されている Colors を取得できます。

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

サーフェスとコンテンツの色

多くのコンポーネントは、色と「コンテンツ色」のペアを受け入れます。

Surface(
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    …

TopAppBar(
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    …

これにより、コンポーザブルの色を設定するだけでなく、コンテンツ(コンポーザブルの中に含まれるコンポーザブル)のデフォルト色も指定できます。多くのコンポーザブルは、デフォルトでこのコンテンツ色を使用します。たとえば、Text の色は親のコンテンツ色に基づき、Icon はその色を使用して色合いを設定します。

同じバナーで色が異なる 2 つの例

図 2. 異なる背景色を設定すると、テキストとアイコンが異なる色になります。

contentColorFor() メソッドは、テーマカラーに適した「on」色を取得します。たとえば、primary 背景を設定すると、コンテンツ色として onPrimary が設定されます。テーマ以外の背景色を設定する場合は、適切なコンテンツ色も指定する必要があります。LocalContentColor を使用して、現在の背景と対照的な現在のコンテンツ色を取得します。

コンテンツのアルファ版

重要度を伝え、視覚的な階層を作るために、コンテンツを強調する度合いを変化させたいことがよくあります。マテリアル デザインでは、異なるレベルの透明度を利用してさまざまな重要度レベルを示すことを推奨しています

Jetpack Compose は LocalContentAlpha を介して、これを実装します。この CompositionLocal に値を指定することで、階層のコンテンツのアルファ版を指定できます。子のコンポーザブルは、この値を使用できます。たとえば、TextIcon は、LocalContentAlpha を使用するように調整された LocalContentColor の組み合わせをデフォルトで使用します。Material は、ContentAlpha オブジェクトによってモデル化される一部の標準的なアルファ値(highmediumdisabled)を指定します。MaterialTheme では、LocalContentAlpha のデフォルトが ContentAlpha.high に設定されるので注意してください。

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
Providers(LocalContentAlpha provides ContentAlpha.medium) {
    Text(/*...*/)
}
Providers(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(/*...*/)
    Text(/*...*/)
}

さまざまなレベルのテキスト強調を示す、記事タイトルのスクリーンショット

図 3. テキストにさまざまなレベルの強調を適用して、情報の階層を視覚的に伝えます。

ダークテーマ

Compose では、MaterialTheme コンポーザブルにさまざまな Colors のセットを指定し、テーマを通じて色を使用することで、ライトテーマとダークテーマを実装します。

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

この例では、MaterialTheme が独自のコンポーズ可能な関数でラップされています。この関数は、ダークテーマを使用するかどうかを指定するパラメータを受け入れます。この場合、関数はデバイスのテーマ設定をクエリすることで、darkTheme のデフォルト値を取得します。

ダークテーマを実装する際、現在の Colors が明るいか暗いかを確認できます。

val isLightTheme = MaterialTheme.colors.isLight

この値は、ビルダー関数 lightColors()darkColors() によって設定されます。

マテリアルでは、エレベーションの高いダークテーマのサーフェスは、背景を明るくするエレベーション オーバーレイを受け取ります。こうしたオーバーレイは、暗い色を使用している場合、Surface コンポーザブルによって自動的に実装されます。

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

エレベーション レベルの異なる要素に使用される、微妙に異なる色を示すアプリのスクリーンショット

図 4. カードとボトム ナビゲーションは背景と同じように surface 色が付けられていますが、エレベーションが高いため、色がやや明るくなっています。

マテリアル カラーの拡張

Compose は、マテリアルのカラーテーマ設定を綿密にモデル化して、マテリアル ガイドラインに沿った、シンプルかつタイプセーフなものにします。カラーセットを拡張する必要がある場合は、下記のように独自のカラーシステムを実装するか、拡張機能を追加します。

@Composable
val Colors.snackbarAction: Color
    get() = if (isLight) Red300 else Red700

タイポグラフィ

マテリアルはタイプシステムを定義し、意味的に名前をつけたスタイルを少数使用するよう推奨しています。

さまざまなスタイルの各種書体の例

Compose は、TypographyTextStyleフォント関連のクラスでタイプシステムを実装しています。Typography コンストラクタは各スタイルのデフォルトを提供するため、カスタマイズしないものは省略できます。

val Rubik = FontFamily(
    Font(R.font.rubik_regular),
    Font(R.font.rubik_medium, FontWeight.W500),
    Font(R.font.rubik_bold, FontWeight.Bold)
)

val MyTypography = Typography(
    h1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = MyTypography, /*...*/)

全体を通して同じフォントを使用する場合は、defaultFontFamily パラメータを指定し、TextStyle 要素の fontFamily を省略します。

val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)

テキスト スタイルの使用

次の例に示すように、テーマから TextStyle を取得します。

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

目的が異なるさまざまな書体の混在を示すスクリーンショット

図 5. 書体とスタイルを使い分けてブランドを表現します。

マテリアルはシェイプ システムを定義しており、大、中、小のコンポーネントのシェイプを定義できます。

さまざまなマテリアル デザイン シェイプを示しています

Compose は Shapes クラスでシェイプ システムを実装しており、カテゴリごとに CornerBasedShape を指定できます。

val Shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomStart = 16.dp,
        bottomEnd = 0.dp
    )
)

MaterialTheme(shapes = Shapes, /*...*/)

多くのコンポーネントで、こうしたシェイプがデフォルトで使用されます。たとえば、ButtonTextFieldFloatingActionButton のデフォルトは small、AlertDialog のデフォルトは medium、ModalDrawerLayout のデフォルトは large です。マッピングの詳細については、シェイプ スキームのリファレンスをご覧ください。

シェイプの使用

テーマからシェイプを取得します。

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

マテリアル シェイプを使用して要素の状態を伝えるアプリのスクリーンショット

図 6. シェイプを使用してブランドや状態を表します。

コンポーネント スタイル

Compose にはコンポーネント スタイルの明確な概念はありません。独自のコンポーザブルを作成することでこの機能を提供します。たとえば、ボタンのスタイルを作成するには、独自のコンポーズ可能な関数でボタンをラップし、変更するパラメータを直接設定します。他のパラメータは包含コンポーザブルにパラメータとして公開します。

@Composable
fun LoginButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonConstants.defaultButtonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

カスタム デザイン システム

マテリアルは Google が推奨するデザイン システムであり、Jetpack Compose にはマテリアルの実装が同梱されていますが、これを使用するように制限されているわけではありません。独自のデザイン システムを同じように作成することも十分に可能です。マテリアルは完全に、デザイン システムの作成に使用できる公開 API に基づいています。

カスタム デザイン システムの構築方法に関する詳細な説明は、このドキュメントの対象外ですが、次のリソースを参照してください。

詳細

詳細については、Jetpack Compose テーマ設定の Codelab をご覧ください。