Jetpack Compose には、デジタル インターフェースを作成するための包括的なデザイン システムであるマテリアル デザインの実装が用意されています。マテリアル デザインのコンポーネント(ボタン、カード、スイッチなど)は、マテリアル テーマ設定に基づいて構築されています。これは、マテリアル デザインをカスタマイズしてプロダクトのブランドを適切に反映するための体系的な方法です。マテリアル テーマは、色、タイポグラフィ、シェイプの属性で構成されています。これらの属性をカスタマイズすると、その変更内容はアプリのビルドに使用するコンポーネントに自動的に反映されます。
Jetpack Compose は、MaterialTheme
コンポーザブルを使用して、こうしたコンセプトを実装します。
MaterialTheme(
colors = …,
typography = …,
shapes = …
) {
// app content
}
MaterialTheme
に渡すパラメータを構成して、アプリのテーマを設定します。
図 1. 1 つ目のスクリーンショットは MaterialTheme
を構成していないアプリを示しています。そのため、デフォルトのスタイリングが使用されています。2 つ目のスクリーンショットは、スタイリングをカスタマイズするために MaterialTheme
にパラメータを渡すアプリを示しています。
色
Compose では、色はシンプルなデータ保持クラスである Color
クラスによってモデル化されます。
val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)
これらはどのように整理してもかまいませんが(最上位の定数として、シングルトン内で、またはインラインで定義)、テーマで色を指定し、そこから色を取得することを強くおすすめします。この方法により、ダークテーマやネストされたテーマを簡単にサポートできます。
図 2. マテリアル カラーシステム。
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 = MaterialTheme.colors.surface,
contentColor = contentColorFor(color),
// ...
TopAppBar(
backgroundColor = MaterialTheme.colors.primarySurface,
contentColor = contentColorFor(backgroundColor),
// ...
これにより、コンポーザブルの色を設定するだけでなく、コンテンツ(コンポーザブルの中に含まれるコンポーザブル)のデフォルト色も指定できます。多くのコンポーザブルは、デフォルトでこのコンテンツ色を使用します。たとえば、Text
の色は親のコンテンツ色に基づき、Icon
はその色を使用して色合いを設定します。
図 3. 異なる背景色を設定すると、テキストとアイコンが異なる色になります。
contentColorFor()
メソッドは、テーマカラーに適した「on」色を取得します。たとえば、Surface
で primary
の背景色を設定すると、この関数を使用して onPrimary
がコンテンツ色として設定されます。テーマ以外の背景色を設定する場合は、適切なコンテンツ色も指定する必要があります。現在の背景で優先するコンテンツ色を取得するには、階層内の特定の位置で LocalContentColor
を使用します。
コンテンツのアルファ版
重要度を伝え、視覚的な階層を提供するために、コンテンツを強調する度合いを変化させることがよくあります。マテリアル デザインのテキストの読みやすさに関する推奨事項では、異なるレベルの透明度を利用してさまざまな重要度レベルを示すことを推奨しています。
Jetpack Compose は LocalContentAlpha
を介して、これを実装します。この CompositionLocal
に値を指定することで、階層のコンテンツのアルファ版を指定できます。ネストされたコンポーザブルは、この値を使用してコンテンツにアルファ処理を適用できます。たとえば、Text
と Icon
は、LocalContentColor
を使用するように調整された LocalContentAlpha
の組み合わせをデフォルトで使用します。マテリアルは、ContentAlpha
オブジェクトによってモデル化される一部の標準的なアルファ値(high
、medium
、disabled
)を指定します。
// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(/*...*/)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(/*...*/)
Text(/*...*/)
}
CompositionLocal
の詳細については、CompositionLocal でローカルにスコープ設定されたデータについてのガイドをご覧ください。
図 4. テキストにさまざまなレベルの強調を適用して、情報の階層を視覚的に伝えます。テキストの最初の行はタイトルで、最も重要な情報があるため、ContentAlpha.high
を使用します。2 行目にはそれよりも重要度の低いメタデータが含まれているため、ContentAlpha.medium
を使用します。
ダークテーマ
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
Icon(
painterResource(
id = if (isLightTheme) {
R.drawable.ic_sun_24dp
} else {
R.drawable.ic_moon_24dp
}
),
contentDescription = "Theme"
)
エレベーション オーバーレイ
マテリアルでは、エレベーションの高いダークテーマのサーフェスは、背景を明るくするエレベーション オーバーレイを受け取ります。サーフェスのエレベーションが高くなるほど(暗黙的な光源に近づくほど)、サーフェスは明るくなります。
こうしたオーバーレイは、暗い色を使用している場合や、サーフェスを使用するその他のマテリアル コンポーザブルの場合、Surface
コンポーザブルによって自動的に実装されます。
Surface(
elevation = 2.dp,
color = MaterialTheme.colors.surface, // color will be adjusted for elevation
/*...*/
) { /*...*/ }
図 5. カードとボトム ナビゲーションはどちらも背景として surface
色が使用されています。カードとボトム ナビゲーションは背景よりレベルが高い異なるエレベーションに存在するため、色がわずかに異なります。カードは背景よりも明るく、ボトム ナビゲーションはカードよりも明るくなります。
Surface
を含まないカスタム シナリオの場合、Surface
コンポーネントによって使用される ElevationOverlay
を含む CompositionLocal
、LocalElevationOverlay
を使用します。
// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
color, elevation
)
エレベーション オーバーレイを無効にするには、コンポーザブル階層内の適切なポイントで null
を指定します。
MyTheme {
CompositionLocalProvider(LocalElevationOverlay provides null) {
// Content without elevation overlays
}
}
限定的なカラー アクセント
マテリアルでは、ほとんどの場面で primary
カラーよりも surface
カラーの使用を優先することで、ダークテーマに限定的なカラー アクセントを適用することをおすすめします。TopAppBar
や BottomNavigation
などのマテリアル コンポーザブルは、デフォルトでこの動作を実装しています。
図 6. 限定的なカラー アクセントを使用したマテリアル ダークテーマ。上部のアプリバーは、ライトテーマでプライマリ色を使用し、ダークテーマでサーフェス色を使用しています。
カスタム シナリオでは、primarySurface
拡張プロパティを使用します。
Surface(
// Switches between primary in light theme and surface in dark theme
color = MaterialTheme.colors.primarySurface,
/*...*/
) { /*...*/ }
タイポグラフィ
マテリアルはタイプシステムを定義し、意味的に名前をつけたスタイルを少数使用するよう推奨しています。
図 7. マテリアルのタイプシステム。
Compose は、Typography
、TextStyle
、フォント関連のクラスでタイプシステムを実装しています。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 parameter
を指定し、TextStyle
要素の fontFamily
を省略します。
val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)
テキスト スタイルの使用
TextStyle
へのアクセスには MaterialTheme.typography
が使用されます。次のように TextStyle
を取得します。
Text(
text = "Subtitle2 styled",
style = MaterialTheme.typography.subtitle2
)
図 8. 書体とスタイルを使い分けてブランドを表現します。
シェイプ
マテリアルはシェイプ システムを定義しており、大、中、小のコンポーネントのシェイプを定義できます。
図 9. マテリアルのシェイプ システム。
Compose は Shapes
クラスでシェイプ システムを実装しており、サイズカテゴリごとに CornerBasedShape
を指定できます。
val Shapes = Shapes(
small = RoundedCornerShape(percent = 50),
medium = RoundedCornerShape(0f),
large = CutCornerShape(
topStart = 16.dp,
topEnd = 0.dp,
bottomEnd = 0.dp,
bottomStart = 16.dp
)
)
MaterialTheme(shapes = Shapes, /*...*/)
多くのコンポーネントで、こうしたシェイプがデフォルトで使用されます。たとえば、Button
、TextField
、FloatingActionButton
のデフォルトは small、AlertDialog
のデフォルトは medium、ModalDrawer
のデフォルトは large です。マッピングの詳細については、シェイプ スキームのリファレンスをご覧ください。
シェイプの使用
Shape
へのアクセスには MaterialTheme.shapes
が使用されます。次のようなコードを使用して Shape
を取得します。
Surface(
shape = MaterialTheme.shapes.medium, /*...*/
) {
/*...*/
}
図 10. シェイプを使用してブランドや状態を表します。
デフォルト スタイル
Android View のデフォルト スタイルの Compose には、同等の概念はありません。マテリアル コンポーネントをラップする独自の「オーバーロード」したコンポーズ可能な関数を作成することで、同様の機能を実現できます。たとえば、ボタンのスタイルを作成するには、独自のコンポーズ可能な関数でボタンをラップし、変更するパラメータを直接設定します。他のパラメータは包含コンポーザブルにパラメータとして公開します。
@Composable
fun MyButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.secondary
),
onClick = onClick,
modifier = modifier,
content = content
)
}
テーマ オーバーレイ
MaterialTheme
コンポーザブルをネストすることで、Android View のテーマ オーバーレイと同等の機能を Compose で実現できます。MaterialTheme
では色、タイポグラフィ、シェイプが現在のテーマ値にデフォルトで設定されるため、テーマによってこれらのパラメータのうちの 1 つしか設定されていない場合、他のパラメータはデフォルト値を保持します。
さらに、View ベースの画面を Compose に移行する場合は、android:theme
属性の使い方に注意してください。Compose UI ツリーの該当部分に新しい MaterialTheme
が必要になる場合があります。
Owl サンプルでは、詳細画面にはほとんどの画面で PinkTheme
が使用され、関連するセクションには BlueTheme
が使用されています。以下のスクリーンショットとコードをご覧ください。
図 11. Owl サンプルのネストされたテーマ。
@Composable
fun DetailsScreen(/* ... */) {
PinkTheme {
// other content
RelatedSection()
}
}
@Composable
fun RelatedSection(/* ... */) {
BlueTheme {
// content
}
}
コンポーネントの状態
クリックや切り替えなど、操作可能なマテリアル コンポーネントには、さまざまな視覚状態が存在します。状態には「enabled」、「disabled」、「press」などがあります。
コンポーザブルには、多くの場合 enabled
パラメータが含まれます。false
に設定すると、操作ができなくなり、色やエレベーションなどのプロパティが変更され、コンポーネントの状態を視覚的に伝えることができます。
図 12. enabled = true
(左)と enabled = false
(右)を設定したボタン。
ほとんどの場合、色やエレベーションなどの値にはデフォルト値を使用できます。それぞれ異なる状態で使用される値を構成する場合は、クラスと便利な関数を利用できます。下のボタンの例をご覧ください。
Button(
onClick = { /* ... */ },
enabled = true,
// Custom colors for different states
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.secondary,
disabledBackgroundColor = MaterialTheme.colors.onBackground
.copy(alpha = 0.2f)
.compositeOver(MaterialTheme.colors.background)
// Also contentColor and disabledContentColor
),
// Custom elevation for different states
elevation = ButtonDefaults.elevation(
defaultElevation = 8.dp,
disabledElevation = 2.dp,
// Also pressedElevation
)
) { /* ... */ }
図 13. 色とエレベーションの値が調整された enabled = true
(左)と enabled = false
(右)を設定したボタン。
リップル
マテリアル コンポーネントでは、リップルを使用することで、自身が操作されていることを示すことができます。階層内で MaterialTheme
を使用している場合、修飾子内(clickable
や indication
など)で Ripple
がデフォルトの Indication
として使用されます。
ほとんどの場合、デフォルトの Ripple
を使用できます。リップルの外観を構成するには、RippleTheme
を使用して色やアルファなどのプロパティを変更します。
RippleTheme
を拡張することで、defaultRippleColor
と defaultRippleAlpha
ユーティリティ関数を使用できます。その後、LocalRippleTheme
を使用することで、階層内でカスタム リップルテーマを指定できます。
@Composable
fun MyApp() {
MaterialTheme {
CompositionLocalProvider(
LocalRippleTheme provides SecondaryRippleTheme
) {
// App content
}
}
}
@Immutable
private object SecondaryRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = RippleTheme.defaultRippleColor(
contentColor = MaterialTheme.colors.secondary,
lightTheme = MaterialTheme.colors.isLight
)
@Composable
override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
contentColor = MaterialTheme.colors.secondary,
lightTheme = MaterialTheme.colors.isLight
)
}
図 14. RippleTheme
で異なるリップル値を指定したボタン
詳細
Compose のマテリアル テーマ設定の詳細については、以下の参考情報をご覧ください。