【Jetpack Compose/Android】MaterialThemeの使い方!Material3

【Jetpack Compose/Android】MaterialThemeの使い方!Material3

この記事からわかること

  • Kotlin/Android Jetpack Compose使い方
  • MaterialTheme指定方法と使い方
  • Material3の指定方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Jetpack Compose自体の基本的な使用方法に関しては以下の記事を参考にしてください。

MaterialTheme

Jetpack ComposeのMaterialThemeアプリ内のデザインルール(「色」・「文字」・「形」)を一元管理するための仕組みです。Googleが推奨しているUIデザインガイドラインの「Material Design」を踏襲する形でJetpack ComposeでもThemeComposable(UIパーツ)を使用することでデザインを統一できるようになっています。

カスタムでUIを構築しなくても良さそうなアプリであればMaterialThemeである程度定義しておくだけで、UIの実装コードを押さえて開発することができるようになります。

実装方法としてはMaterialThemeクラスの引数に「色」・「文字」・「形」の定義を渡してコンテンツをラップします。これでラップされたComposableで指定したテーマが反映されるような仕組みになっています。

MaterialTheme(
    // 色
    colorScheme = LightColorScheme,
    // 文字スタイル
    typography = Typography, 
    // 形
    shapes = Shapes
) {
  // UI Composable
}

MaterialThemeの使い方

新規でプロジェクトを立ち上げるとデフォルトのUI構築がAndroid View(XMLレイアウト)ではなく、Jetpack Composeになっています。そのため最初にプロジェクト名ThemeというComposableがすでにui.theme.Theme.ktに用意されています。関連ファイルとしてui.theme.Color.ktui.theme.Typo.ktも定義されています。

├── ui.theme
│ ├── Color.kt
│ ├── Theme.kt
│ └── Type.kt

ui.theme.Theme.ktの中身を見てみるとcolorSchemetypographyが指定されている状態です。


private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40
)

@Composable
fun ComposeTestAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

内容を1つずつ見ていきます。

ColorScheme

アプリ内のテーマカラーはColorScheme型で指定します。ColorSchemeには各役割にあった色を保持するためのプロパティが定義されており、そのプロパティに対して自身のアプリに沿った色を当てはめていく形になります。定義されている項目はMaterial2からMaterial3へ移行する際に大きく増加しています。

参照:Compose でマテリアル 2 からマテリアル 3 に移行する


@Immutable
class ColorScheme(
    // メインカラー。主要なボタンや強調テキストに使用
    val primary: Color,
    // primary 上に表示する文字やアイコンの色
    val onPrimary: Color,
    // primary の背景として使うコンテナ色(カードやチップなど)
    val primaryContainer: Color,
    // primaryContainer 上の文字色
    val onPrimaryContainer: Color,
    // 反転テーマ時の primary 色
    val inversePrimary: Color,

    // 補助カラー。サブボタンやタグなどに使用
    val secondary: Color,
    // secondary 上の文字やアイコンの色
    val onSecondary: Color,
    // secondary の背景として使うコンテナ色
    val secondaryContainer: Color,
    // secondaryContainer 上の文字色
    val onSecondaryContainer: Color,

    // 第3のアクセント色
    val tertiary: Color,
    // tertiary 上の文字色
    val onTertiary: Color,
    // tertiary の背景として使うコンテナ色
    val tertiaryContainer: Color,
    // tertiaryContainer 上の文字色
    val onTertiaryContainer: Color,

    // アプリ全体の背景色
    val background: Color,
    // background 上の文字色
    val onBackground: Color,

    // サーフェス背景(カード、ダイアログ、シート)
    val surface: Color,
    // surface 上の文字色
    val onSurface: Color,
    // サーフェスバリアント色(区切りや控えめな面)
    val surfaceVariant: Color,
    // surfaceVariant 上の文字色
    val onSurfaceVariant: Color,
    // サーフェスにティントをかける色(elevation に応じて)
    val surfaceTint: Color,
    // 反転サーフェス
    val inverseSurface: Color,
    // inverseSurface 上の文字色
    val inverseOnSurface: Color,

    // エラー色
    val error: Color,
    // error 上の文字色
    val onError: Color,
    // エラー用コンテナ背景
    val errorContainer: Color,
    // errorContainer 上の文字色
    val onErrorContainer: Color,

    // 標準枠線色
    val outline: Color,
    // 薄い境界線色
    val outlineVariant: Color,
    // 半透明オーバーレイ色(スクラム)
    val scrim: Color,

    // 明るいサーフェス
    val surfaceBright: Color,
    // 暗いサーフェス
    val surfaceDim: Color,
    // 中立的なサーフェス
    val surfaceContainer: Color,
    // 高コントラスト面
    val surfaceContainerHigh: Color,
    // 最も高いレベルの面
    val surfaceContainerHighest: Color,
    // やや低コントラスト面
    val surfaceContainerLow: Color,
    // 最も低いレベルの面
    val surfaceContainerLowest: Color,
)

ColorScheme型の用意はライトモード・ダークモードスキームを使用して用意します。lightColorSchemedarkColorSchemeメソッドで各スキームのデフォルト色が定義されているので、アプリテーマに応じて変えたい部分だけoverrideする形で更新します。

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40
)

デフォルトではPurple80などはui.theme.Color.ktに定義されています。


val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

isSystemInDarkTheme()現在のシステムテーマがダークモードかどうかを判定することができるのでこの値に応じて定義したライトモード・ダークモードを切り替わるようにしておきます。

darkTheme: Boolean = isSystemInDarkTheme(),

DynamicColor

Android12(API31)以降から「ダイナミックカラー(Material You)」というのが出たみたいです。ユーザーの壁紙やテーマ色に応じてアプリの色を自動的に調整できる機能でそれに対応したい場合はdynamicDarkColorScheme/dynamicLightColorSchemeを使用するようです。

正直あまりわかっていないので公式リンクだけ貼っておきます。

val colorScheme = when {
    dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
        val context = LocalContext.current
        if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
    }

    darkTheme -> DarkColorScheme
    else -> LightColorScheme
}

Typography

Typography型で文字のスタイルやサイズを定義します。Typographyも役割に応じたプロパティを保持しているので必要に応じてoverrideする形で任意のスタイルやサイズを指定します。


class Typography(
    // Display系: 最も大きく、視覚的インパクトを与える文字サイズ
    val displayLarge: TextStyle = TypographyTokens.DisplayLarge,
    val displayMedium: TextStyle = TypographyTokens.DisplayMedium,
    val displaySmall: TextStyle = TypographyTokens.DisplaySmall,

    // Headline系: コンテンツやセクションの見出しに使う文字サイズ
    val headlineLarge: TextStyle = TypographyTokens.HeadlineLarge,
    val headlineMedium: TextStyle = TypographyTokens.HeadlineMedium,
    val headlineSmall: TextStyle = TypographyTokens.HeadlineSmall,

    // Title系: コンポーネントやカード、アプリバーなどのタイトルに使用
    val titleLarge: TextStyle = TypographyTokens.TitleLarge,
    val titleMedium: TextStyle = TypographyTokens.TitleMedium,
    val titleSmall: TextStyle = TypographyTokens.TitleSmall,

    // Body系: メインテキストや説明文など本文部分に使用
    val bodyLarge: TextStyle = TypographyTokens.BodyLarge,
    val bodyMedium: TextStyle = TypographyTokens.BodyMedium,
    val bodySmall: TextStyle = TypographyTokens.BodySmall,

    // Label系: ボタンやタグ、ラベルなど短いテキストに使用
    val labelLarge: TextStyle = TypographyTokens.LabelLarge,
    val labelMedium: TextStyle = TypographyTokens.LabelMedium,
    val labelSmall: TextStyle = TypographyTokens.LabelSmall,
)

デフォルトではbodyLargeだけ上書きされて定義されています。


val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
)

Shapes

Shapes型でMaterialコンポーネントの形を指定します。Shapesも役割に応じたプロパティを保持しているので必要に応じてoverrideする形で任意の形を指定します。


class Shapes(
    // extraSmall: 主にTextField, Chip などの小さなUI部品
    val extraSmall: Shape = ShapeTokens.CornerExtraSmall,

    // small: 主にButton系コンポーネント(Button, ElevatedButton, FilledButton など)
    val small: Shape = ShapeTokens.CornerSmall,

    // medium: 一部の中型コンポーネント(例: SearchBar, Menu など)
    val medium: Shape = ShapeTokens.CornerMedium,

    // large: 主にCard 系コンポーネント(Card, ElevatedCard, OutlinedCard など)
    val large: Shape = ShapeTokens.CornerLarge,

    // extraLarge: 主にDialog, BottomSheet, Navigation Drawer など大きめのUI部品
    val extraLarge: Shape = ShapeTokens.CornerExtraLarge
)

Shapes型はデフォルトで定義されていないので例えば以下のように定義して使用します。MaterialThemeに渡すのも忘れないようにしてください。

val CustomShapes = Shapes(
    extraSmall = RoundedCornerShape(2.dp),
    small = RoundedCornerShape(4.dp),
    medium = RoundedCornerShape(8.dp),
    large = RoundedCornerShape(16.dp),
    extraLarge = RoundedCornerShape(28.dp)
)

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index