1. はじめに
この Codelab では、Jetpack Compose のテーマ設定 API を使用してアプリのスタイルを設定する方法を学びます。アプリ全体で一貫性を持つように色、シェイプ、タイポグラフィをカスタマイズし、ライトモードやダークモードなどの複数のテーマをサポートする方法について説明します。
学習内容
この Codelab では、以下について学びます。
- マテリアル デザインの基本と、ブランドに合わせてカスタマイズする方法
- Compose によるマテリアル デザイン システムの実装方法
- アプリ全体で色、タイポグラフィ、シェイプを定義して使用する方法
- コンポーネントのスタイルを設定する方法
- ライトモードとダークモードをサポートする方法
作成するアプリの概要
この Codelab では、ニュース リーダー アプリのスタイルを設定します。スタイル設定なしのアプリから始め、学んだことを活かしてアプリのテーマを設定し、ダークモードをサポートします。
変更前: スタイルを適用していないアプリ | 変更後: スタイルを適用したアプリ | 変更後: ダークモード |
前提条件
- ラムダを含む Kotlin 構文の使用経験。
- Compose に関する基礎知識。
- Compose のレイアウト(
Row
、Column
、Modifier
など)に関する基本的な知識。
2. 設定方法
このステップでは、スタイルを設定するシンプルなニュース リーダー アプリを構成するコードをダウンロードします。
必要なもの
コードをダウンロードする
git がインストールされている場合は、以下のコマンドをそのまま実行できますgit がインストールされているかどうかを確認するには、ターミナルまたはコマンドラインで「git --version
」と入力し、正しく実行されることを確認します。
git clone https://github.com/googlecodelabs/android-compose-codelabs.git cd android-compose-codelabs/ThemingCodelabM2
git がない場合は、次のボタンをクリックして、この Codelab のすべてのコードをダウンロードできます。
Android Studio でプロジェクトを開き、[File] > [Import Project] を選択して、ThemingCodelabM2
ディレクトリを参照します。
プロジェクトには次の 3 つの主要パッケージが含まれています。
com.codelab.theming.data
: モデルクラスとサンプルデータが含まれています。この Codelab では、このパッケージを編集する必要はありません。com.codelab.theming.ui.start
: Codelab の出発点であり、この Codelab で求められる変更はすべて、このパッケージで行う必要があります。com.codelab.theming.ui.finish
: この Codelab の最終状態(参考)
アプリをビルドして実行する
このアプリには、Codelab の開始状態と終了状態を反映した 2 つの実行構成があります。いずれかの構成を選択して実行ボタンを押すと、コードがデバイスまたはエミュレータにデプロイされます。
このアプリには Compose のレイアウト プレビューも含まれています。start
/ finish
パッケージの Home.kt
に移動し、デザインビューを開くと、UI コードを迅速に反復処理できるプレビューが複数表示されます。
3. マテリアル テーマ設定
Jetpack Compose には、デジタル インターフェースを作成するための包括的なデザイン システムであるマテリアル デザインの実装が用意されています。マテリアル デザインのコンポーネント(ボタン、カード、スイッチなど)は、プロダクトのブランドをより良く反映するようにマテリアル デザインをカスタマイズする体系的な方法である、マテリアル テーマ設定に基づいて構築されています。マテリアル テーマは、色、タイポグラフィ、シェイプの属性で構成されています。これらの属性をカスタマイズすると、その変更内容は、アプリのビルドに使用するコンポーネントに自動的に反映されます。
マテリアル テーマ設定について理解しておくと Jetpack Compose アプリのテーマ設定方法を理解しやすくなるため、ここではそのコンセプトについて簡単に説明します。すでにマテリアル テーマ設定についてよくご存じの場合は、読み飛ばしても構いません。
色
マテリアル デザインでは、アプリ全体で使用できる、意味的に名付けられた色が複数定義されています。
プライマリはメインのブランドカラーです。セカンダリはアクセントに使用します。コントラストが強い部分には、さらに暗いまたは明るいバリエーションを提供できます。背景色とサーフェス色は、アプリの「サーフェス」に理論上存在するコンポーネントを保持するコンテナに使用します。マテリアルでは「on」色(指定した色の上に配置されるコンテンツに使用する色)も定義します。たとえば「サーフェス」色のコンテナ内のテキストは、「サーフェス上」の色になります。マテリアル コンポーネントは、こうしたテーマの色を使用するように設定されます。たとえばデフォルトでは、フローティング アクション ボタンの色は secondary
、カードのデフォルトは surface
などです。
名前付きの色を定義すると、ライトモードとダークモードの両方など、代替のカラーパレットを提供できるようになります。
小さなカラーパレットを定義し、アプリ全体で一貫して使用することもおすすめします。マテリアル カラーツールを使用すると、色を選択してカラーパレットを作成でき、その組み合わせにもアクセスできるようになります。
タイポグラフィ
同様に、マテリアルでは、意味する内容に基づいて名付けられたタイプスタイルがいくつか定義されています。
テーマによってタイプスタイルを変えることはできませんが、タイプスケールを使用すると、アプリ内での一貫性が向上します。独自のフォントや他のタイプ カスタマイズを指定すると、アプリで使用するマテリアル コンポーネントに反映されます(アプリバーはデフォルトで h6
スタイルを使用し、ボタンは button
を使用するなど)。タイプスケールを作成するには、マテリアル タイプスケール生成ツールを使用すると便利です。
シェイプ
マテリアルでは、シェイプを使用して体系的にブランドを表現できます。コンポーネントについて小、中、大という 3 つのカテゴリを定義します。それぞれに使用するシェイプを定義でき、角のスタイル(切り落としまたは丸)とサイズをカスタマイズできます。
シェイプテーマのカスタマイズは多くのコンポーネントに反映されます(ボタンとテキスト フィールドは小、カードとダイアログは中、シートは大のシェイプテーマをデフォルトで使用するなど)。コンポーネントとシェイプテーマの対応関係について詳しくはこちらをご覧ください。シェイプテーマを生成するには、マテリアル シェイプ カスタマイズ ツールを使用すると便利です。
ベースライン
マテリアルのデフォルトは「ベースライン」テーマです。カラーパターンは紫色、タイプスケールは Roboto であり、シェイプは上の図のようにやや丸みを帯びています。テーマを指定しない場合、またはカスタマイズしない場合、コンポーネントはベースライン テーマを使用します。
4. テーマを定義する
MaterialTheme
Jetpack Compose でテーマ設定を実装するための主な要素は、MaterialTheme
コンポーザブルです。このコンポーザブルを Compose 階層に配置すると、その中にあるすべてのコンポーネントの色、タイプ、シェイプのカスタマイズを指定できます。このコンポーザブルは、ライブラリで次のように定義されています。
@Composable
fun MaterialTheme(
colors: Colors,
typography: Typography,
shapes: Shapes,
content: @Composable () -> Unit
) { ...
このコンポーザブルに渡されたパラメータは、colors
、typography
、shapes
のプロパティを公開する MaterialTheme
object
を使用して、後で取得できます。これらについては、後ほど詳しく説明します。
Home.kt
を開き、コンポーズ可能な関数 Home
(アプリのメイン エントリ ポイント)を探します。MaterialTheme
を宣言していますが、パラメータを指定していないため、デフォルトの「ベースライン」スタイルが適用されます。
@Composable
fun Home() {
...
MaterialTheme {
Scaffold(...
色、タイプ、シェイプのパラメータを作成して、アプリのテーマを実装しましょう。
テーマを作成する
スタイル設定を一元管理するために、MaterialTheme
をラップして設定する独自のコンポーザブルを作成することをおすすめします。これにより、テーマのカスタマイズを 1 か所で指定し、複数の画面や @Preview
など、さまざまな場所で簡単に再利用できるようになります。たとえば、アプリのセクションごとに異なるスタイルをサポートする場合など、必要に応じて複数のテーマ コンポーザブルを作成できます。
com.codelab.theming.ui.start.theme
パッケージで、Theme.kt
という新しいファイルを作成します。他のコンポーザブルをコンテンツとして受け入れて MaterialTheme
をラップする、JetnewsTheme
という新しいコンポーズ可能な関数を追加します。
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(content = content)
}
Home.kt
に戻り、MaterialTheme
を JetnewsTheme
に置き換えてインポートします。
- MaterialTheme {
+ JetnewsTheme {
...
この画面の @Preview
には、まだ変更が反映されていません。プレビューに新しいテーマが使用されるように、PostItemPreview
と FeaturedPostPreview
を更新して新しい JetnewsTheme
コンポーザブルでコンテンツをラップします。
@Preview("Featured Post")
@Composable
private fun FeaturedPostPreview() {
val post = remember { PostRepo.getFeaturedPost() }
+ JetnewsTheme {
FeaturedPost(post = post)
+ }
}
色
アプリに実装するカラーパレットは次のとおりです(まずはライトパレットのみ。ダークモードは後ほどサポートします)。
Compose では、Color
クラスを使用して色を定義します。コンストラクタが複数あり、ULong
として、または個別のカラー チャンネルで色を指定できます。
theme
パッケージに新しいファイル Color.kt
を作成します。このファイルに、最上位の公開プロパティとして次の色を追加します。
val Red700 = Color(0xffdd0d3c)
val Red800 = Color(0xffd00036)
val Red900 = Color(0xffc20029)
アプリの色を定義したので、今度はそれを MaterialTheme
で必要となる Colors
オブジェクトにまとめ、マテリアルの名前付きの色に具体的な色を割り当てます。Theme.kt
に戻り、次のコードを追加します。
private val LightColors = lightColors(
primary = Red700,
primaryVariant = Red900,
onPrimary = Color.White,
secondary = Red700,
secondaryVariant = Red900,
onSecondary = Color.White,
error = Red800
)
lightColors
関数を使用して Colors
を作成します。適切なデフォルト値が提供されるため、マテリアル カラーパレットの構成色をすべて指定する必要はありません。たとえば、background
色や「on」色の多くを指定していないため、デフォルトを使用します。
それでは、これらの色をアプリに使用しましょう。新しい Colors
を使用するように JetnewsTheme
コンポーザブルを更新します。
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
+ colors = LightColors,
content = content
)
}
Home.kt
を開いてプレビューを更新します。TopAppBar
などのコンポーネントに新しいカラーパターンが反映されます。
タイポグラフィ
アプリに実装するタイプスケールは次のとおりです。
Compose では、TextStyle
オブジェクトを定義して、あるテキストのスタイルを設定するために必要な情報を定義できます。その属性のサンプルを次に示します。
data class TextStyle(
val color: Color = Color.Unset,
val fontSize: TextUnit = TextUnit.Inherit,
val fontWeight: FontWeight? = null,
val fontStyle: FontStyle? = null,
val fontFamily: FontFamily? = null,
val letterSpacing: TextUnit = TextUnit.Inherit,
val background: Color = Color.Unset,
val textAlign: TextAlign? = null,
val textDirection: TextDirection? = null,
val lineHeight: TextUnit = TextUnit.Inherit,
...
)
目的のタイプスケールでは、タイトルに Montserrat を使用し、本文に Domine を使用します。関連するフォント ファイルは、プロジェクトの res/fonts
フォルダにすでに追加されています。
theme
パッケージに新しいファイル Typography.kt
を作成します。まず、FontFamily
(各 Font
のさまざまなウェイトの組み合わせ)を定義しましょう。
private val Montserrat = FontFamily(
Font(R.font.montserrat_regular),
Font(R.font.montserrat_medium, FontWeight.W500),
Font(R.font.montserrat_semibold, FontWeight.W600)
)
private val Domine = FontFamily(
Font(R.font.domine_regular),
Font(R.font.domine_bold, FontWeight.Bold)
)
次に、MaterialTheme
が受け入れる Typography
オブジェクトを作成し、スケールのセマンティック スタイルごとに TextStyle
を指定します。
val JetnewsTypography = Typography(
h4 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 30.sp
),
h5 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 24.sp
),
h6 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 20.sp
),
subtitle1 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W600,
fontSize = 16.sp
),
subtitle2 = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
body1 = TextStyle(
fontFamily = Domine,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
body2 = TextStyle(
fontFamily = Montserrat,
fontSize = 14.sp
),
button = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
caption = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
),
overline = TextStyle(
fontFamily = Montserrat,
fontWeight = FontWeight.W500,
fontSize = 12.sp
)
)
Theme.kt
を開き、新しい Typography
を使用するように JetnewsTheme
コンポーザブルを更新します。
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = LightColors,
+ typography = JetnewsTypography,
content = content
)
}
Home.kt
を開いてプレビューを更新し、新しいタイポグラフィが有効になることを確認します。
シェイプ
アプリでシェイプを使用してブランドを表現します。一部の要素には、角を切り落としたシェイプを使用します。
Compose には、シェイプテーマを定義するために使用できる RoundedCornerShape
クラスと CutCornerShape
クラスが用意されています。
theme
パッケージに新しいファイル Shape.kt
を作成し、次のコードを追加します。
val JetnewsShapes = Shapes(
small = CutCornerShape(topStart = 8.dp),
medium = CutCornerShape(topStart = 24.dp),
large = RoundedCornerShape(8.dp)
)
Theme.kt
を開き、これらの Shapes
を使用するように JetnewsTheme
コンポーザブルを更新します。
@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = LightColors,
typography = JetnewsTypography,
+ shapes = JetnewsShapes,
content = content
)
}
Home.kt
を開いてプレビューを更新し、注目の投稿を表示する Card
に、新しく適用したシェイプテーマがどのように反映されるかを確認します。
ダークモード
アプリでダークモードをサポートすると、ユーザーのデバイス(Android 10 以降のグローバルなダークモードの切り替えがある)にアプリを適切に統合できるだけでなく、電力使用量を削減したり、ユーザー補助ニーズに対応したりできます。マテリアルには、ダークモードの作成に関するデザイン ガイダンスが用意されています。ダークモードに実装する別のカラーパレットを次に示します。
Color.kt
を開き、次の色を追加します。
val Red200 = Color(0xfff297a2)
val Red300 = Color(0xffea6d7e)
Theme.kt
を開き、次のコードを追加します。
private val DarkColors = darkColors(
primary = Red300,
primaryVariant = Red700,
onPrimary = Color.Black,
secondary = Red300,
onSecondary = Color.Black,
error = Red200
)
JetnewsTheme
を更新します。
@Composable
fun JetnewsTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
+ colors = if (darkTheme) DarkColors else LightColors,
typography = JetnewsTypography,
shapes = JetnewsShapes,
content = content
)
}
ダークモードを使用するかどうかを指定する新しいパラメータが追加され、デフォルトでデバイスに全般設定をクエリするようになりました。これだけでも適切なデフォルトになりますが、特定の画面を常に暗くするかまったく暗くしないようにする、あるいはダークモードの @Preview
を作成する場合にも、簡単にオーバーライドできます。
Home.kt
を開き、ダークモードで表示する FeaturedPost
コンポーザブルの新しいプレビューを作成します。
@Preview("Featured Post • Dark")
@Composable
private fun FeaturedPostDarkPreview() {
val post = remember { PostRepo.getFeaturedPost() }
JetnewsTheme(darkTheme = true) {
FeaturedPost(post = post)
}
}
プレビュー ペインを更新すると、ダークモードのプレビューが表示されます。
5. 色の取り扱い
前のステップでは、独自のテーマを作成して、アプリの色、タイプスタイル、シェイプを設定する方法を確認しました。こうしたカスタマイズは、すべてのマテリアル コンポーネントですぐに使用できます。たとえば、FloatingActionButton
コンポーザブルはデフォルトでテーマの secondary
の色を使用しますが、このパラメータに別の値を指定することで代替色を設定できます。
@Composable
fun FloatingActionButton(
backgroundColor: Color = MaterialTheme.colors.secondary,
...
) {
常にデフォルトの設定を使用するとは限らないため、このセクションではアプリで色を扱う方法について説明します。
未加工の色
前述のとおり、Compose には Color
クラスが用意されています。これらはローカルで作成して object
などに保持できます。
Surface(color = Color.LightGray) {
Text(
text = "Hard coded colors don't respond to theme changes :(",
textColor = Color(0xffff00ff)
)
}
Color
には、copy
など、アルファ、赤、緑、青の値が異なる新しい色を作成できる便利なメソッドが複数用意されています。
テーマの色
さらに柔軟なアプローチとして、テーマから色を取得することが挙げられます。
Surface(color = MaterialTheme.colors.primary)
ここでは、MaterialTheme
コンポーザブルに設定された Colors
を colors
プロパティが返す MaterialTheme
object
を使用しています。つまり、テーマに異なる色のセットを指定するだけで、アプリコードを編集することなく、さまざまなデザインをサポートできます。たとえば、AppBar
は primary
の色を使用し、画面の背景は surface
です。テーマの色を変更すると、これらのコンポーザブルに反映されます。
テーマの各色は Color
インスタンスであるため、copy
メソッドを使用して簡単に色を導出できます。
val derivedColor = MaterialTheme.colors.onSurface.copy(alpha = 0.1f)
ここでは、onSurface
の色のコピーを不透明度 10% で作成しています。この方法は、静的な色をハードコードするのではなく、さまざまなテーマで色を機能させます。
サーフェスとコンテンツの色
多くのコンポーネントは、色と「コンテンツ色」のペアを受け入れます。
Surface(
color: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(color),
...
TopAppBar(
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
...
これにより、コンポーザブルの色を設定するだけでなく、「コンテンツ」(つまりコンポーザブルの中にあるコンポーザブル)にデフォルト色を指定できます。多くのコンポーザブルは、デフォルトでこのコンテンツ色を使用します(Text
の色や Icon
の色合いなど)。contentColorFor
メソッドは、テーマカラーに適した「on」色を取得します。たとえば primary
の背景を設定すると、コンテンツ色として onPrimary
が返されます。テーマ以外の背景色を設定する場合は、適切なコンテンツ色をご自身で指定する必要があります。
Surface(color = MaterialTheme.colors.primary) {
Text(...) // default text color is 'onPrimary'
}
Surface(color = MaterialTheme.colors.error) {
Icon(...) // default tint is 'onError'
}
LocalContentColor
CompositionLocal
を使用すると、現在の背景と対照的な色を取得できます。
BottomNavigationItem(
unselectedContentColor = LocalContentColor.current ...
要素の色を設定する際は、適切なコンテンツ色である CompositionLocal
値が設定されるため、Surface
を使用することをおすすめします。Modifier.background
を直接呼び出すと適切なコンテンツ色が設定されないため、注意が必要です。
-Row(Modifier.background(MaterialTheme.colors.primary)) {
+Surface(color = MaterialTheme.colors.primary) {
+ Row(
...
現在、Header
コンポーネントの背景は常に Color.LightGray
です。ライトモードでは問題ないようですが、ダークモードでは背景に対して高コントラストになります。また、特定のテキスト色が指定されていないため、現在のコンテンツ色が継承されますが、これは背景と対照的ではない可能性があります。
この問題を修正しましょう。Home.kt
の Header
コンポーザブルで、ハードコードされた色を指定している background
修飾子を削除します。代わりに、Text
をテーマ由来の色の Surface
でラップし、コンテンツを primary
の色にする必要があることを指定します。
+ Surface(
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
+ contentColor = MaterialTheme.colors.primary,
+ modifier = modifier
+ ) {
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
- .background(Color.LightGray)
.padding(horizontal = 16.dp, vertical = 8.dp)
)
+ }
コンテンツのアルファ
重要性を伝え、視覚的な階層を持たせるために、コンテンツを強調または抑制することはよくあります。マテリアル デザインでは、異なるレベルの透明度を利用してさまざまな重要度レベルを示すことを推奨しています。
Jetpack Compose は LocalContentAlpha
を介して、これを実装します。この CompositionLocal
に値を指定することで、階層のコンテンツのアルファを指定できます。子のコンポーザブルは、この値を使用できます。たとえば、Text
と Icon
は、LocalContentAlpha
を使用するように調整された LocalContentColor
の組み合わせをデフォルトで使用します。マテリアルは、ContentAlpha
オブジェクトによってモデル化される一部の標準的なアルファ値(high
、medium
、disabled
)を指定します。なお、MaterialTheme
では LocalContentAlpha
のデフォルトが ContentAlpha.high
に設定されます。
// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting a different content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(...)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(...)
Text(...)
}
こうして、コンポーネントの重要性を簡単かつ一貫した方法で伝えることができます。
コンテンツのアルファを使用して、注目の投稿の情報階層を明確にします。Home.kt
の PostMetadata
コンポーザブルで、メタデータ medium
を強調します。
+ CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = text,
modifier = modifier
)
+ }
ダークモード
前述のとおり、Compose でダークモードを実装するには、さまざまな色のセットを指定し、テーマを通じて色をクエリします。ただし、注目すべき例外がいくつかあります。
ライトモードで動作しているかどうかを確認できます。
val isLightTheme = MaterialTheme.colors.isLight
この値は、ビルダー関数 lightColors / darkColors によって設定されます。
マテリアルでは、ダークモードでエレベーションの高いサーフェスにエレベーション オーバーレイが適用されます(背景が明るくなります)。これは、暗いカラーパレットを使用する場合に自動的に実装されます。
Surface(
elevation = 2.dp,
color = MaterialTheme.colors.surface, // color will be adjusted for elevation
...
アプリのこの自動的な動作は、使用している TopAppBar
コンポーネントと Card
コンポーネントの両方で発生します。エレベーションはデフォルトで 4 dp と 1 dp に設定されているため、このエレベーションが伝わりやすくなるよう、ダークモードで背景が自動的に明るくなります。
マテリアル デザインは、ダークモードでは明るい色が大きな面積を占めないようにすることを提案しています。一般的なパターンでは、コンテナをライトモードでは primary
の色に、ダークモードでは surface
の色にします。アプリバーやボトム ナビゲーションなど、多くのコンポーネントがデフォルトでこの戦略を使用します。これを簡単に実装できるように、Colors
には、この動作を実現する primarySurface
の色が用意されています。これらのコンポーネントはデフォルトでこれを使用します。
このアプリは、アプリバーが現時点では primary
の色に設定されています。これを primarySurface
に切り替えるか、このデフォルトのパラメータを削除すると、ガイダンスに沿わせることができます。AppBar
コンポーザブルで、TopAppBar
の backgroundColor
パラメータを変更します。
@Composable
private fun AppBar() {
TopAppBar(
...
- backgroundColor = MaterialTheme.colors.primary
+ backgroundColor = MaterialTheme.colors.primarySurface
)
}
6. テキストの取り扱い
テキストを扱う場合、Text
コンポーザブルを使用してテキストを表示します。テキスト入力には TextField
と OutlinedTextField
を使用し、テキストに単一のスタイルを適用するには TextStyle
を使用します。AnnotatedString
を使用すると、テキストに複数のスタイルを適用できます。
色の場合と同様に、テキストを表示するマテリアル コンポーネントには、テーマのタイポグラフィのカスタマイズが反映されます。
Button(...) {
Text("This text will use MaterialTheme.typography.button style by default")
}
これを実現することは、色の場合のようにデフォルトのパラメータを使用するよりも、やや複雑です。コンポーネントは、それ自体ではテキストを表示しない傾向があり、Text
コンポーザブルを渡せるように「スロット API」を提供するためです。では、コンポーネントはどのようにしてテーマのタイポグラフィ スタイルを設定するのでしょうか。内部では、ProvideTextStyle
コンポーザブル(これ自体は CompositionLocal
を使用します)を使用して、「現在の」TextStyle
が設定されています。具体的な textStyle
パラメータを指定しない場合、Text
コンポーザブルはデフォルトでこの「現在の」スタイルをクエリします。
Compose の Button
クラスと Text
クラスの例を次に示します。
@Composable
fun Button(
// many other parameters
content: @Composable RowScope.() -> Unit
) {
...
ProvideTextStyle(MaterialTheme.typography.button) { //set the "current" text style
...
content()
}
}
@Composable
fun Text(
// many, many parameters
style: TextStyle = LocalTextStyle.current // get the value set by ProvideTextStyle
) { ...
テーマのテキスト スタイル
色と同様に、現在のテーマから TextStyle
を取得することをおすすめします。管理しやすくするために、一貫性のある小規模なスタイルセットを使用しましょう。MaterialTheme.typography
は、MaterialTheme
コンポーザブルに設定された Typography
インスタンスを取得し、定義したスタイルを使用できるようにします。
Text(
style = MaterialTheme.typography.subtitle2
)
TextStyle
をカスタマイズする必要がある場合は、copy
によってプロパティ(単なる data class
)をオーバーライドします。または Text
コンポーザブルが複数のスタイル設定パラメータを受け入れ、TextStyle
の上にオーバーレイさせます。
Text(
text = "Hello World",
style = MaterialTheme.typography.body1.copy(
background = MaterialTheme.colors.secondary
)
)
Text(
text = "Hello World",
style = MaterialTheme.typography.subtitle2,
fontSize = 22.sp // explicit size overrides the size in the style
)
アプリ内の多くの場所でテーマの TextStyle
が自動的に適用されます。たとえば、TopAppBar
では title
のスタイルが h6
に設定され、ListItem
ではメインテキストとセカンダリ テキストのスタイルがそれぞれ subtitle1
と body2
に設定されます。
テーマのタイポグラフィ スタイルをアプリの残りの部分に適用しましょう。subtitle2
を使用するように Header
を設定し、タイトルに h6
、著者とメタデータに body2
を使用するように FeaturedPost
のテキストを設定します。
@Composable
fun Header(...) {
...
Text(
text = text,
+ style = MaterialTheme.typography.subtitle2
複数のスタイル
あるテキストに複数のスタイルを適用する必要がある場合は、AnnotatedString
クラスを使用してマークアップを適用し、テキストの範囲に SpanStyle
を追加します。これらを動的に追加することも、DSL 構文を使用してコンテンツを作成することもできます。
val text = buildAnnotatedString {
append("This is some unstyled text\n")
withStyle(SpanStyle(color = Color.Red)) {
append("Red text\n")
}
withStyle(SpanStyle(fontSize = 24.sp)) {
append("Large text")
}
}
アプリ内の各投稿を記述するタグのスタイルを設定しましょう。現在は、他のメタデータと同じテキスト スタイルを使用しています。overline
テキスト スタイルと背景色を使用して区別します。PostMetadata
コンポーザブルを次のようにします。
+ val tagStyle = MaterialTheme.typography.overline.toSpanStyle().copy(
+ background = MaterialTheme.colors.primary.copy(alpha = 0.1f)
+ )
post.tags.forEachIndexed { index, tag ->
...
+ withStyle(tagStyle) {
append(" ${tag.toUpperCase()} ")
+ }
}
7. シェイプの取り扱い
色、タイポグラフィと同様に、シェイプのテーマを設定すると、マテリアル コンポーネントに反映されます。たとえば Button
には、小さなコンポーネントのシェイプセットが反映されます。
@Composable
fun Button( ...
shape: Shape = MaterialTheme.shapes.small
) {
色と同様に、マテリアル コンポーネントでもデフォルトのパラメータを使用するため、コンポーネントで使用するシェイプのカテゴリの確認や代替手段の提供を簡単に行えます。コンポーネントとシェイプ カテゴリのマッピングの一覧については、ドキュメントをご覧ください。
なお一部のコンポーネントでは、コンテキストに合わせて変更したテーマシェイプが使用されています。たとえばデフォルトで、TextField
は小さいシェイプテーマを使用しますが、下の角のサイズを 0 にします。
@Composable
fun FilledTextField(
// other parameters
shape: Shape = MaterialTheme.shapes.small.copy(
bottomStart = ZeroCornerSize, // overrides small theme style
bottomEnd = ZeroCornerSize // overrides small theme style
)
) {
テーマのシェイプ
コンポーザブル、またはシェイプを受け入れる Modifier
(Surface
、Modifier.clip
、Modifier.background
、Modifier.border
など)を使用して独自のコンポーネントを作成するときに、ご自身でシェイプを使用することはもちろん可能です。
@Composable
fun UserProfile(
...
shape: Shape = MaterialTheme.shapes.medium
) {
Surface(shape = shape) {
...
}
}
PostItem
に表示される画像にシェイプのテーマ設定を追加しましょう。clip
Modifier
でテーマの small
シェイプを適用して、左上の角を切り落とします。
@Composable
fun PostItem(...) {
...
Image(
painter = painterResource(post.imageThumbId),
+ modifier = Modifier.clip(shape = MaterialTheme.shapes.small)
)
8. コンポーネント「スタイル」
Compose には、Android View スタイルや CSS スタイルのような、コンポーネントのスタイル設定を抽出する明確な方法がありません。Compose コンポーネントはすべて Kotlin で作成されていることから、同じ目標を達成するには他の方法があります。代わりに、カスタマイズされたコンポーネントの独自のライブラリを作成し、アプリ全体で使用します。
これは、今回のアプリですでに行っています。
@Composable
fun Header(
text: String,
modifier: Modifier = Modifier
) {
Surface(
color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
contentColor = MaterialTheme.colors.primary,
modifier = modifier.semantics { heading() }
) {
Text(
text = text,
style = MaterialTheme.typography.subtitle2,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
基本的に Header
コンポーザブルはスタイル設定された Text
であり、アプリ全体で使用できます。
コンポーネントはすべて下位のビルディング ブロックから構成されていることを確認しましたが、同じビルディング ブロックを使用して、マテリアルのコンポーネントをカスタマイズできます。たとえば、Button
では ProvideTextStyle
コンポーザブルを使用して、渡されるコンテンツのデフォルト テキスト スタイルを設定していました。まったく同じメカニズムを使用して、独自のテキスト スタイルを設定できます。
@Composable
fun LoginButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonConstants.defaultButtonColors(
backgroundColor = MaterialTheme.colors.secondary
),
onClick = onClick,
modifier = modifier
) {
ProvideTextStyle(...) { // set our own text style
content()
}
}
}
この例では、標準の Button
クラスをラップして LoginButton
の独自の「スタイル」を作成し、別の backgroundColor
やテキスト スタイルのような特定のプロパティを指定しています。
また、デフォルトのスタイル設定という概念(つまりコンポーネント タイプのデフォルトの外観をカスタマイズする方法)もありません。この場合も、ライブラリ コンポーネントをラップしてカスタマイズする独自のコンポーネントを作成することで実現できます。たとえば、アプリ全体ですべての Button
のシェイプをカスタマイズするとします。ただし、他の(Button
以外の)コンポーネントに影響する小さなシェイプのテーマは変更しません。これを実現するには、独自のコンポーザブルを作成し、全体で使用します。
@Composable
fun AcmeButton(
// expose Button params consumers should be able to change
) {
val acmeButtonShape: Shape = ...
Button(
shape = acmeButtonShape,
// other params
)
}
9. 完了
これで、この Codelab は終了です。Jetpack Compose アプリのスタイルを設定できました。
マテリアル テーマを実装し、アプリ全体で使用する色、タイポグラフィ、シェイプをカスタマイズすることで、ブランドを表現し、一貫性を高めました。ライトモードとダークモードの両方のサポートを追加しました。
次のステップ
Compose パスウェイに関する他の Codelab をご確認ください。
参考資料
サンプルアプリ
- 複数のテーマのデモを行う Owl
- 動的テーマ設定のデモを行う Jetcaster
- カスタム デザイン システムの実装のデモを行う Jetsnack