【Jetpack Compose/Android】MaterialThemeの使い方!Material3
この記事からわかること
- Kotlin/Android Jetpack Composeの使い方
- MaterialThemeの指定方法と使い方
- Material3の指定方法
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Meerkat
- Kotlin:2.0.21
- Material3
- AGP:8.9.2
- Gradle:8.11.1
- Mac M1:Sequoia 15.4
Jetpack Compose自体の基本的な使用方法に関しては以下の記事を参考にしてください。
MaterialTheme
Jetpack ComposeのMaterialThemeはアプリ内のデザインルール(「色」・「文字」・「形」)を一元管理するための仕組みです。Googleが推奨しているUIデザインガイドラインの「Material Design」を踏襲する形でJetpack ComposeでもThemeとComposable(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.kt・ui.theme.Typo.ktも定義されています。
├── ui.theme
│ ├── Color.kt
│ ├── Theme.kt
│ └── Type.kt
ui.theme.Theme.ktの中身を見てみるとcolorScheme、typographyが指定されている状態です。
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型の用意はライトモード・ダークモードスキームを使用して用意します。lightColorScheme・darkColorSchemeメソッドで各スキームのデフォルト色が定義されているので、アプリテーマに応じて変えたい部分だけ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)
)
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。







