マテリアル 3 を使用した Compose でのテーマ設定

1. はじめに

この Codelab では、マテリアル デザイン 3 を使用した Jetpack Compose でのアプリのテーマ設定について学習します。また、マテリアル デザイン 3 のカラーパターン、タイポグラフィ、シェイプの主な構成要素についても学習します。これにより、パーソナライズしたわかりやすい方法でアプリのテーマを設定できます。

また、さまざまな強調レベルでの動的なテーマ設定のサポートも探索します。

学習内容

この Codelab では、以下について学びます。

  • マテリアル 3 のテーマ設定の主な要素
  • マテリアル 3 のカラーパターンと、アプリのテーマを生成する方法
  • アプリの動的テーマ設定とライトモード / ダークモードをサポートする方法
  • アプリをカスタマイズするためのタイポグラフィとシェイプ
  • マテリアル 3 のコンポーネントと、アプリのスタイルのカスタマイズ

作成するアプリの概要

この Codelab では、Reply というメール クライアント アプリのテーマを設定します。ベースライン テーマを使用して、スタイルを適用していないアプリから開始し、学習した内容を活用してアプリのテーマを設定して、ダークモードをサポートします。

d15db3dc75a9d00f.png

ベースライン テーマにおけるアプリのデフォルトの開始点。

カラーパターン、タイポグラフィ、シェイプを使用してテーマを作成し、アプリのメールリストと詳細ページに適用します。また、動的テーマサポートをアプリに追加します。Codelab の終わりまでに、アプリのカラーテーマとダイナミック テーマの両方をサポートします。

1357cdbfaaa67721.png

ライトカラー テーマ設定とライト ダイナミック テーマ設定を使用するテーマ設定 Codelab のエンドポイント。

1357cdbfaaa67721.png

ダークカラー テーマ設定とダーク ダイナミック テーマ設定を使用したテーマ設定 Codelab のエンドポイント。

必要なもの

2. セットアップする

このステップでは、この Codelab でスタイルを設定する Reply アプリのコード全体をダウンロードします。

コードを取得する

この Codelab のコードは、GitHub リポジトリ android-compose-codelabs にあります。クローンを作成するには、次のコマンドを実行します。

$ git clone https://github.com/googlecodelabs/android-compose-codelabs

または、次の 2 つの zip ファイルをダウンロードします。

サンプルアプリを確認する

ダウンロードしたコードには、利用可能なすべての Compose Codelab のコードが含まれています。この Codelab を完了するには、Android Studio 内で ThemingCodelab プロジェクトを開きます。

main ブランチのコードから始め、ご自身のペースで Codelab を進めることをおすすめします。どちらのバージョンも、プロジェクトの git ブランチを変更することで Android Studio でいつでも実行できます。

開始コードを確認する

メインコードには UI パッケージが含まれており、このパッケージには、次の主なパッケージとファイルが含まれます。

  • MainActivity.kt - Reply アプリを起動するエントリ ポイント アクティビティ。
  • com.example.reply.ui.theme – このパッケージには、テーマ、タイポグラフィ、カラーパターンが含まれています。このパッケージでマテリアル テーマ設定を追加します。
  • com.example.reply.ui.components - リストアイテム、アプリバーなどのアプリのカスタム コンポーネントが含まれます。これらのコンポーネントにはテーマを適用します。
  • ReplyApp.kt - UI ツリーを開始するメインのコンポーズ可能な関数です。このファイルでは、トップレベルのテーマ設定を適用します。

この Codelab では、ui パッケージ ファイルを中心に説明します。

3. マテリアル 3 テーマ設定

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

マテリアル 3 のテーマは、アプリにテーマ設定を追加するための次のサブシステムで構成されています。カラーパターンタイポグラフィシェイプ。これらの値をカスタマイズすると、その変更は、アプリのビルドに使用する M3 コンポーネントに自動的に反映されます。では、各サブシステムを掘り下げて、サンプルアプリで実装してみましょう。

マテリアル デザインのサブシステム: 色、タイポグラフィ、シェイプ

カラー、タイポグラフィ、シェイプのマテリアル 3 サブシステム。

4. カラーパターン

カラーパターンの基盤は、マテリアル 3 コンポーネントで使用される 13 トーンの色調パレットに関連する 5 つの主要なカラーのセットです。

M3 テーマ設定を作成するための 5 つのベースライン キーの色。

M3 テーマ設定を作成するための 5 つのベースライン キーの色。

それにより、アクセントの各色(プライマリ、セカンダリ、ターシャリ)が、ペア設定、強調の定義、視覚表現に必要な 4 つの対応する色で提供されます。

プライマリ、セカンダリ、ターシャリ ベースラインのアクセント カラーの 4 色。

プライマリ、セカンダリ、ターシャリ ベースラインのアクセント カラーの 4 色。

同様に、ニュートラル カラーも 4 つの対応するトーンに分けられ、サーフェスと背景に使用されます。また、これらは、サーフェスに置く際にテキスト アイコンを強調するためにも重要です。

ベースラインのニュートラル カラー(4 色)

ベースラインのニュートラル カラー(4 色)

詳しくは、カラーパターンとカラーロールをご覧ください。

カラーパターンの生成

カスタムの ColorScheme は手動で作成できますが、多くの場合、ブランドのソースカラーを使用して生成するほうが簡単です。マテリアル テーマビルダー ツールを使用すると、この処理を行い、必要に応じて Compose テーマ設定コードをエクスポートできます。

好みの色を選択することもできますが、このユースケースでは、デフォルトの Reply プライマリ カラー #825500 を使用します。左側の [Core colors] の [Primary] 色をクリックして、カラー選択ツールにコードを追加します。

294f73fc9d2a570e.png

マテリアル テーマビルダーでのプライマリ カラー コードの追加。

マテリアル テーマビルダーでプライマリ カラーを追加すると、右上に以下のテーマとエクスポート オプションが表示されます。この Codelab では、Jetpack Compose でテーマをエクスポートします。

エクスポートするオプションを備えたマテリアル テーマビルダー(右上)。

エクスポートするオプションを備えたマテリアル テーマビルダー(右上)。

プライマリ カラー #825500 は、アプリに追加する次のテーマを生成します。マテリアル 3 では、コンポーネントの状態、視認性の高さ、強調を柔軟に表現するために、さまざまなカラーロールが用意されています。

プライマリ カラーからエクスポートされたライトとダークのカラーパターン。

プライマリ カラーからエクスポートされたライトとダークのカラーパターン。

The Color.kt で生成されたファイルには、ライトモードとダークモードの色に対して定義されたすべてのロールを含むテーマの色が含まれます。

Color.kt

package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)


val seed = Color(0xFF825500)

The Theme.kt で生成されたファイルには、ライトとダークのカラーパターンとアプリのテーマの設定が含まれます。また、コンポーズ可能な主要な関数 AppTheme() も含まれています。

Theme.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable


private val LightColors = lightColorScheme(
   primary = md_theme_light_primary,
   onPrimary = md_theme_light_onPrimary,
   primaryContainer = md_theme_light_primaryContainer,
   onPrimaryContainer = md_theme_light_onPrimaryContainer,
   secondary = md_theme_light_secondary,
   onSecondary = md_theme_light_onSecondary,
   secondaryContainer = md_theme_light_secondaryContainer,
   onSecondaryContainer = md_theme_light_onSecondaryContainer,
   tertiary = md_theme_light_tertiary,
   onTertiary = md_theme_light_onTertiary,
   tertiaryContainer = md_theme_light_tertiaryContainer,
   onTertiaryContainer = md_theme_light_onTertiaryContainer,
   error = md_theme_light_error,
   errorContainer = md_theme_light_errorContainer,
   onError = md_theme_light_onError,
   onErrorContainer = md_theme_light_onErrorContainer,
   background = md_theme_light_background,
   onBackground = md_theme_light_onBackground,
   surface = md_theme_light_surface,
   onSurface = md_theme_light_onSurface,
   surfaceVariant = md_theme_light_surfaceVariant,
   onSurfaceVariant = md_theme_light_onSurfaceVariant,
   outline = md_theme_light_outline,
   inverseOnSurface = md_theme_light_inverseOnSurface,
   inverseSurface = md_theme_light_inverseSurface,
   inversePrimary = md_theme_light_inversePrimary,
   surfaceTint = md_theme_light_surfaceTint,
   outlineVariant = md_theme_light_outlineVariant,
   scrim = md_theme_light_scrim,
)


private val DarkColors = darkColorScheme(
   primary = md_theme_dark_primary,
   onPrimary = md_theme_dark_onPrimary,
   primaryContainer = md_theme_dark_primaryContainer,
   onPrimaryContainer = md_theme_dark_onPrimaryContainer,
   secondary = md_theme_dark_secondary,
   onSecondary = md_theme_dark_onSecondary,
   secondaryContainer = md_theme_dark_secondaryContainer,
   onSecondaryContainer = md_theme_dark_onSecondaryContainer,
   tertiary = md_theme_dark_tertiary,
   onTertiary = md_theme_dark_onTertiary,
   tertiaryContainer = md_theme_dark_tertiaryContainer,
   onTertiaryContainer = md_theme_dark_onTertiaryContainer,
   error = md_theme_dark_error,
   errorContainer = md_theme_dark_errorContainer,
   onError = md_theme_dark_onError,
   onErrorContainer = md_theme_dark_onErrorContainer,
   background = md_theme_dark_background,
   onBackground = md_theme_dark_onBackground,
   surface = md_theme_dark_surface,
   onSurface = md_theme_dark_onSurface,
   surfaceVariant = md_theme_dark_surfaceVariant,
   onSurfaceVariant = md_theme_dark_onSurfaceVariant,
   outline = md_theme_dark_outline,
   inverseOnSurface = md_theme_dark_inverseOnSurface,
   inverseSurface = md_theme_dark_inverseSurface,
   inversePrimary = md_theme_dark_inversePrimary,
   surfaceTint = md_theme_dark_surfaceTint,
   outlineVariant = md_theme_dark_outlineVariant,
   scrim = md_theme_dark_scrim,
)

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

Jetpack Compose でテーマ設定を実装するための主な要素は、MaterialTheme コンポーザブルです。

MaterialTheme() コンポーザブルを AppTheme() 関数でラップします。この関数は次の 2 つのパラメータを取ります。

  • useDarkTheme - このパラメータは関数 isSystemInDarkTheme() に関連付けられ、システムテーマ設定を監視し、ライトモードまたはダークモードを適用します。アプリをライトモードまたはダークモードに手動で保持する場合は、useDarkTheme にブール値を渡します。
  • content - テーマが適用されるコンテンツ。

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

アプリを実行してみると、同じように見えるはずです。新しいテーマ設定カラーを使用して新しいカラーパターンをインポートしても、Compose アプリにテーマが適用されていないため、ベースライン テーマが表示されます。

テーマが適用されていない場合にベースライン テーマ設定を使用するアプリ。

テーマが適用されていない場合にベースライン テーマ設定を使用するアプリ。

MainActivity.kt で新しいテーマを適用するには、メインのコンポーザブル ReplyApp をメインのテーマ設定関数 AppTheme() でラップします。

MainActivity.kt

setContent {
   val uiState by viewModel.uiState.collectAsStateWithLifecycle()

   AppTheme {
       ReplyApp(/*..*/)
   }
}

また、プレビュー関数を更新して、アプリのプレビューに適用されるテーマを確認します。ReplyAppPreview() 内の ReplyApp コンポーザブルを AppTheme でラップして、プレビューにテーマ設定を適用します。

プレビュー パラメータでライトとダークのシステムモードの両方が定義されているため、両方のプレビューが表示されます。

MainActivity.kt

@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_YES,
   name = "DefaultPreviewDark"
)
@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_NO,
   name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
   AppTheme {
       ReplyApp(
           replyHomeUIState = ReplyHomeUIState(
               emails = LocalEmailsDataProvider.allEmails
           )
       )
   }
}

アプリを実行すると、ベースライン テーマではなく、インポートされたテーマカラーでアプリのプレビューが表示されます。

fddf7b9cc99b1fe3.png be7a661b4553167b.png

ベースライン テーマを使用したアプリ(左)。

インポートしたカラーテーマを使用するアプリ(右)。

674cec6cc12db6a0.png

色テーマがインポートされたアプリのライトモードとダークモードのプレビュー。

マテリアル 3 は、ライトとダークのカラーパターンの両方をサポートしています。インポートされたテーマでのみアプリをラップしました。マテリアル 3 コンポーネントは、デフォルトのカラーロールを使用しています。

アプリに追加する前に、カラーロールとその使い方を学びましょう。

カラーロールとユーザー補助

各カラーロールは、コンポーネントの状態、視認性の高さ、強調に応じて、さまざまな場所で使用できます。

1f184a05ea57aa84.png

プライマリ カラー、セカンダリ カラー、ターシャリ カラーのカラーロール。

プライマリはベースカラーです。視認性の高いボタンやアクティブ状態などの主要コンポーネントに使用されます。

セカンダリ キーカラーは、UI の視認性の低いコンポーネント(フィルタラベルなど)に使用されます。

ターシャリ キーカラーは対照的なアクセントとして使用され、ニュートラル カラーはアプリの背景やサーフェスに使用されます。

マテリアルのカラーシステムは、アクセス可能なコントラスト比を満たすために使用できる標準のトーン値と測定値を提供します。プライマリでは on-primary、プライマリ コンテナでは on-primary-container を使用し、他のアクセント カラーやニュートラル カラーでも同様に、ユーザーにとってのコントラストを提供します。

詳しくは、カラーロールとユーザー補助をご覧ください。

色調とシャドウのエレベーション

マテリアル 3 では、主に色調カラー オーバーレイを使用してエレベーションを表現します。これは、コンテナとサーフェスを区別する新しい方法です。色調エレベーションを高くすると、シャドウに加えて、より目立つ色調が使用されます。

シャドウ エレベーションを含む色調エレベーション レベル 2 の色調エレベーション(プライマリ カラースロットから色を取得します)。

ダークモードのエレベーション オーバーレイも、マテリアル デザイン 3 の色調カラー オーバーレイに変更されました。オーバーレイの色は、プライマリ カラースロットで指定されます。

M3 サーフェス(ほとんどの M3 コンポーネントの背後にあるバッキング コンポーザブル)は、色調エレベーションとシャドウ エレベーションの両方をサポートします。

Surface(
   modifier = modifier,
   tonalElevation = {..}
   shadowElevation = {..}
) {
   Column(content = content)
}

アプリに色を追加する

アプリを実行すると、コンポーネントがデフォルトの色を使用している箇所に、エクスポートされた色が表示されます。色の役割と使用方法について理解したところで、正しい色の役割を使用してアプリをテーマ設定しましょう。

be7a661b4553167b.png

カラーテーマを使用するアプリとデフォルトのカラーロールを使用するコンポーネント。

サーフェスの色

ホーム画面では、まずメインアプリのコンポーザブルを Surface() でラップして、その上にアプリのコンテンツを配置するベースを提供します。MainActivity.kt を開き、ReplyApp() コンポーザブルを Surface でラップします。

また、サーフェスのプライマリ スロットの色調になるように、5.dp の色調エレベーションを指定して、リストアイテムとその上の検索バーとのコントラストをつけることができます。デフォルトでは、サーフェスの色調とシャドウのエレベーションは 0.dp です。

MainActivity.kt

AppTheme {
   Surface(tonalElevation = 5.dp) {
       ReplyApp(
           replyHomeUIState = uiState,
          // other parameters
         )
   }
}

アプリを実行してリストページと詳細ページの両方を表示すると、色調サーフェスがアプリ全体に適用されていることがわかります。

be7a661b4553167b.png e70d762495173610.png

サーフェスと色調の色のないアプリの背景(左)。

サーフェスと色調の色が適用されたアプリの背景(右)。

アプリバーの色

上部のカスタム検索バーは、デザイン リクエストのようにわかりやすい背景には対応していません。デフォルトでは、デフォルトのベース サーフェスにフォールバックします。明確に区別するための背景を用意できます。

5779fc399d8a8187.png

背景のないカスタム検索バー(左)。

背景付きのカスタム検索バー(右)。

ここでは、アプリバーを含む ui/components/ReplyAppBars.kt を編集します。MaterialTheme.colorScheme.backgroundRow コンポーザブルの Modifier に追加します。

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

色調サーフェスと背景色付きのアプリバーがはっきりと区別されるようになりました。

b1b374b801dadc06.png

色調サーフェスの上に設定した背景色付きの検索バー。

フローティング アクション ボタンの色

70ceac87233fe466.png

テーマ設定を適用していない大規模な FAB(左)。

ターシャリ カラー付きのテーマ別の大型 FAB(右)。

ホーム画面で、フローティング アクション ボタン(FAB)の視認性を高め、行動を促すフレーズのボタンとして目立たせることができます。これを実装するには、ターシャリ アクセント カラーを適用します。

ReplyListContent.kt ファイルで、ユーザー補助と色のコントラストを保つために、FAB の containerColortertiaryContainer 色に更新し、コンテンツ色を onTertiaryContainer に更新します。

ReplyListContent.kt

ReplyInboxScreen(/*..*/) {
// Email list content
  LargeFloatingActionButton(
    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
    contentColor = MaterialTheme.colorScheme.onTertiaryContainer
  ){
   /*..*/   
  }
}

アプリを実行すると、FAB のテーマが表示されます。この Codelab では LargeFloatingActionButton を使用します。

カードの色

ホーム画面のメールリストでカード コンポーネントが使用されています。デフォルトでは、塗りつぶしカードは、コンテナの色としてサーフェス バリアントの色を使用して、サーフェスとカードの色を明確に区別します。Compose では、ElevatedCardOutlinedCard の実装も行えます。

セカンダリ カラートーンを指定することで、重要な項目をさらに強調できます。ui/components/ReplyEmailListItem.kt を変更するには、重要なメールの CardDefaults.cardColors() を使用してカードのコンテナの色を更新します。

ReplyEmailListItem.kt

Card(
   modifier =  modifier
       .padding(horizontal = 16.dp, vertical = 4.dp)
       .semantics { selected = isSelected }
       .clickable { navigateToDetail(email.id) },
   colors = CardDefaults.cardColors(
       containerColor = if (email.isImportant)
           MaterialTheme.colorScheme.secondaryContainer
       else MaterialTheme.colorScheme.surfaceVariant
   )
){
  /*..*/   
}

5818200be0b01583.png 9367d40023db371d.png

色調サーフェスでセカンダリ コンテナ色を使用してリスト項目をハイライト表示します。

詳細リストアイテムの色

これで、ホーム画面のテーマが設定されました。メーリング リストの項目のいずれかをクリックして、詳細ページを確認します。

7a9ea7cf3e91e9c7.png 79b3874aeca4cd1.png

リスト項目がテーマ設定されていないデフォルトの詳細ページ(左)。

バックグラウンド テーマ設定が適用された詳細リストアイテム(右)。

リストアイテムには色が適用されていないため、デフォルトの色調サーフェス色にフォールバックします。リストアイテムに背景色を適用して区切りを作り、背景の周囲に余白を持たせます。

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background)
           .padding(20.dp)
    ) {
      // List item content
    }
}

背景を表示するだけで、色調サーフェスとリストアイテムを明確に区別できることがわかります。

ホームページと詳細ページの両方について、適切な色の役割と使用方法を適用できるようになりました。アプリでダイナミック カラーを活用し、よりパーソナライズされ一貫性のあるエクスペリエンスを実現しましょう。

5. アプリにダイナミック カラーを追加する

ダイナミック カラーは Material 3 の重要な要素です。アルゴリズムはそれによってユーザーの壁紙からカスタムカラーを導出し、それらがアプリとシステム UI に適用されるようにします。

動的テーマ設定を使用すると、アプリをさらにパーソナライズできます。また、システムのテーマによって統一感のあるシームレスなエクスペリエンスをユーザーに提供できます。

ダイナミック カラーは Android 12 以降で使用できます。ダイナミック カラーを使用できる場合は、dynamicDarkColorScheme() または dynamicLightColorScheme() を使用してダイナミック カラーパターンを設定できます。使用できない場合は、代わりにデフォルトのライトまたはダーク ColorScheme を使用してください。

Theme.kt ファイルの AppTheme 関数のコードを次のコードに置き換えます。

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val context = LocalContext.current
   val colors = when {
       (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
           if (useDarkTheme) dynamicDarkColorScheme(context)
           else dynamicLightColorScheme(context)
       }
       useDarkTheme -> DarkColors
       else -> LightColors
   }
   
      MaterialTheme(
       colorScheme = colors,
       content = content
     )
}

fecc63b4c6034236.png

Android 13 の壁紙から生成された動的テーマ。

アプリを実行すると、デフォルトの Android 13 の壁紙を使用して動的テーマ設定が適用されていることを確認できます。

また、アプリのテーマ設定に使用するカラーパターンに応じて、ステータスバーを動的にスタイル設定することもできます。

1095e2b2c1ffdc14.png

ステータスバーの色が適用されていないアプリ(左)。

ステータスバーの色を適用したアプリ(右)。

テーマのプライマリ カラーに応じてステータスバーの色を更新するには、AppTheme コンポーザブルのカラーパターン選択の後にステータスバーの色を追加します。

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
 
 // color scheme selection code

 // Add primary status bar color from chosen color scheme.
 val view = LocalView.current
 if (!view.isInEditMode) {
    SideEffect {
        val window = (view.context as Activity).window
        window.statusBarColor = colors.primary.toArgb()
        WindowCompat
            .getInsetsController(window, view)
            .isAppearanceLightStatusBars = useDarkTheme
    }
 }
   
  MaterialTheme(
    colorScheme = colors,
     content = content
   )
}

アプリを実行すると、プライマリ カラーテーマ設定を表すステータスバーが表示されます。システムのダークモードを変更して、ライトモードとダークモードの動的なテーマ設定の両方を試すこともできます。

69093b5bce31fd43.png

動的なライト(左)とダーク(右)のモードを Android 13 のデフォルトの壁紙に適用。

ここまでは、アプリの外観を強化する色をアプリに適用してきました。アプリのテキストがすべて同じサイズであることがわかったため、アプリにタイポグラフィを追加できるようになりました。

6. タイポグラフィ

マテリアル デザイン 3 では、タイプスケールが定義されています。名前とグループ化は、表示、見出し、タイトル、本文、ラベルに簡略化され、それぞれに大、中、小のサイズがあります。

999a161dcd9b0ec4.png

マテリアル 3 タイプスケール。

タイポグラフィの定義

Compose には、既存の TextStyle クラスおよび font-related クラスとともに、マテリアル 3 のタイプスケールをモデル化するための M3 Typography クラスが用意されています。

タイポグラフィ コンストラクタにはスタイルごとにデフォルトが用意されているため、カスタマイズしないパラメータは省略できます。詳しくは、タイポグラフィのスタイルとデフォルト値をご覧ください。

アプリでは、headlineSmalltitleLargebodyLargebodyMediumlabelMedium の 5 種類のタイポグラフィ スタイルを使用します。このスタイルは、ホーム画面と詳細画面の両方に対応します。

タイトル、ラベル、body スタイルのタイポグラフィの使用方法を示す画面。

タイトル、ラベル、body スタイルのタイポグラフィの使用方法を示す画面。

次に、ui/theme パッケージに移動して、Type.kt を開きます。以下のコードを追加して、デフォルト値の代わりに、いくつかのテキスト スタイルを独自に実装します。

Type.kt

val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
   titleLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 18.sp,
       lineHeight = 28.sp,
       letterSpacing = 0.sp
   ),
   bodyLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.15.sp
   ),
   bodyMedium = TextStyle(
       fontWeight = FontWeight.Medium,
       fontSize = 14.sp,
       lineHeight = 20.sp,
       letterSpacing = 0.25.sp
   ),
   labelMedium = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 12.sp,
       lineHeight = 16.sp,
       letterSpacing = 0.5.sp
   )
)

タイポグラフィが定義されました。テーマに追加するには、AppTheme 内の MaterialTheme() コンポーザブルに渡します。

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

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

タイポグラフィを使用する

色と同様に、MaterialTheme.typography を使用して現在のテーマのタイポグラフィ スタイルにアクセスできます。これにより、Type.kt で定義されたすべてのタイポグラフィを使用するためのタイポグラフィ インスタンスが得られます。

Text(
   text = "Hello M3 theming",
   style = MaterialTheme.typography.titleLarge
)

Text(
   text = "you are learning typography",
   style = MaterialTheme.typography.bodyMedium
)

マテリアル デザイン タイプのスケールの 15 個のデフォルト スタイルがすべて、プロダクトで必要になるとは限りません。この Codelab では 5 つのサイズを選択し、残りは省略します。

Text( コンポーザブルにタイポグラフィを適用していないため、すべてのテキストはデフォルトで Typography.bodyLarge にフォールバックします。

ホームリストのタイポグラフィ

次に、ui/components/ReplyEmailListItem.ktReplyEmailListItem 関数にタイポグラフィを適用して、タイトルとラベルを区別します。

ReplyEmailListItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.titleLarge,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   overflow = TextOverflow.Ellipsis
)

90645c0765167bb7.png 6c4af2f412c18bfb.png

タイポグラフィを適用していないホーム画面(左)。

タイポグラフィが適用されたホーム画面(右)。

詳細リストのタイポグラフィ

同様に、ui/components/ReplyEmailThreadItem.ktReplyEmailThreadItem のテキスト コンポーザブルをすべて更新して、詳細画面にタイポグラフィを追加します。

ReplyEmailThreadItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = stringResource(id = R.string.twenty_mins_ago),
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.bodyMedium,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

543ac09e43d8761.png 3412771e95a45f36.png

タイポグラフィが適用されていない詳細画面(左)。

タイポグラフィが適用された詳細画面(右)。

タイポグラフィのカスタマイズ

Compose を使用すると、テキスト スタイルのカスタマイズやカスタム フォントの提供を簡単に行うことができます。TextStyle を変更すると、フォントタイプ、フォント ファミリー、文字間隔などをカスタマイズできます。

theme/Type.kt ファイルのテキスト スタイルを変更すると、それを使用するすべてのコンポーネントに反映されます。

リストアイテム内のサブジェクトに使用される titleLarge について、fontWeightSemiBoldlineHeight32.sp に更新します。主題をより強調し、明確に区別できるようにします。

Type.kt

...
titleLarge = TextStyle(
   fontWeight = FontWeight.SemiBold,
   fontSize = 18.sp,
   lineHeight = 32.sp,
   letterSpacing = 0.0.sp
),
...

f8d2212819eb0b61.png

カスタム タイポグラフィを件名に適用する。

7. シェイプ

マテリアル サーフェスはさまざまな形状で表示できます。シェイプはユーザーの注意を引き、コンポーネントを明確に示し、状態を伝え、ブランドを表現するために役立ちます。

シェイプの定義

Compose には、新しい M3 シェイプを実装するための拡張パラメータを備えた Shapes クラスがあります。M3 のシェイプ スケールは、タイプスケールと同様に、UI 全体でシェイプの表現範囲を実現します。

シェイプのスケールには、さまざまなサイズのシェイプがあります。

  • 特小
  • 特大

デフォルトでは、各シェイプに上書きできるデフォルト値があります。アプリの場合、中程度のシェイプを使用してリストアイテムを変更しますが、他のシェイプを宣言することもできます。ui/theme パッケージで Shape.kt という新しいファイルを作成し、シェイプのコードを追加します。

Shape.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val shapes = Shapes(
   extraSmall = RoundedCornerShape(4.dp),
   small = RoundedCornerShape(8.dp),
   medium = RoundedCornerShape(16.dp),
   large = RoundedCornerShape(24.dp),
   extraLarge = RoundedCornerShape(32.dp)
)

shapes を定義したので、色とタイポグラフィと同様に、これを M3 MaterialTheme に渡します。

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       shapes = shapes
       content = content
   )
}

シェイプの取り扱い

色やタイポグラフィと同様に、MaterialTheme.shape を使用してマテリアル コンポーネントにシェイプを適用できます。これにより、Shape インスタンスを介してマテリアル シェイプにアクセスできます。

多くのマテリアル コンポーネントにすでにデフォルトのシェイプが適用されていますが、使用可能なスロットを介して、独自のシェイプを提供してコンポーネントに適用できます。

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}

すべてのマテリアル 3 コンポーネントのデフォルトのシェイプ値。さまざまなタイプのシェイプを使用したマテリアル コンポーネントのマッピング

シェイプのマッピングについては、シェイプに関するドキュメントをご確認ください。

他には、Compose の一部である RectangleShapeCircleShape という 2 つのシェイプを使用できます。長方形は枠線の半径がなく、円の輪郭は完全な丸いエッジを示しています。

次のコマンドを使用して、コンポーネントにシェイプを適用することもできます。Modifier.clip のようなシェイプを取る ModifiersModifier.background、および Modifier.border

アプリバーの形状

アプリバーの背景の隅を丸くします。

f873392abe535494.png

TopAppBar は背景色とともに Row を使用しています。角の丸い背景を実現するには、背景修飾子に CircleShape を渡して背景のシェイプを定義します。

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(
               MaterialTheme.colorScheme.background,
               CircleShape
           ),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

f873392abe535494.png

詳細リストのアイテムのシェイプ

ホーム画面では、デフォルトで Shape.Medium を使用するカードを使用しています。ただし、詳細ページでは、代わりに背景色付きの Column を使用しました。リストの外観を統一するには、そのリストに中程度のシェイプを適用します。

3412771e95a45f36.png 80ee881c41a98c2a.png

リストアイテムにシェイプのない詳細リストアイテム列(左)とリスト上の中程度のシェイプ(右)。

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(8.dp)
           .background(
               MaterialTheme.colorScheme.background,
               MaterialTheme.shapes.medium
           )
           .padding(16.dp)

   ) {
      // List item content
      
   }
}

アプリを実行すると、medium にシェイプ設定された詳細な画面リストアイテムが表示されます。

8. 強調

UI で強調を使用すると、タイトルと字幕を区別したい場合など、一部のコンテンツを他のコンテンツより強調できます。M3 での強調では、さまざまなバリエーションの色と、on-color の組み合わせを使用します。強調を追加する方法は 2 つあります。

  1. 拡張 M3 カラーシステムの on-surface および on-surface-variants の色に加えて、サーフェス、surface-variant、背景を使用します。

たとえば、サーフェスは、on-surface-variant とともに使用できます。また、surface-variant を on-surface と併用すると、異なる強調レベルを指定できます。

また、サーフェス バリアントは、on-accent カラーよりも強調を弱くするためにアクセント カラーと組み合わせて使用することもできますが、それでも引き続きアクセスしてコントラスト比に従うことができます。

サーフェス、背景、サーフェス バリアント カラーのロール。

サーフェス、背景、サーフェス バリアント カラーのロール。

  1. テキストでは異なるフォントの太さを使用する。タイポグラフィのセクションで説明したように、タイプスケールにカスタム ウェイトを設定して、異なる強調を指定できます。

次に、ReplyEmailListItem.kt を更新し、サーフェス バリアントを使用して強調を指定します。デフォルトでは、カードのコンテンツは背景に応じてデフォルトのコンテンツ色になります。

時刻のテキストと本文のテキストにおけるコンポーザブルの色を onSurfaceVariant に更新します。これにより、デフォルトで件名とタイトルのテキスト コンポーザブルに適用される onContainerColors よりも強調が軽減されます。

2c9b7f2bd016edb8.png 6850ff391f21e4ba.png

件名やタイトルとの比較で、同じように強調されている時刻と本文(左)。

件名およびタイトルと比較して強調度が弱い時刻と本文(右)。

ReplyEmailListItem.kt

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
   overflow = TextOverflow.Ellipsis
)

背景が secondaryContainer の重要なメールカードの場合、デフォルトですべてのテキストの色が onSecondaryContainer の色になります。それ以外のメールでは、背景は surfaceVariant, になるため、すべてのテキストはデフォルトで onSurfaceVariant の色になります。

9. 完了

これで完了です。この Codelab を完了しました。Compose でマテリアル テーマ設定を実装しました。色、タイポグラフィ、シェイプを使用するとともに、ダイナミックな色でアプリのテーマを設定し、パーソナライズされたエクスペリエンスを実現しました。

2d8fcabf15ac5202.png 5a4d31db0185dca6.png ce009e4ce560834d.png

ダイナミック カラーとカラーテーマを使用したテーマ設定の結果。

次のステップ

Compose パスウェイに関する他の Codelab をご確認ください。

参考資料

サンプルアプリ

  • マテリアル 3 のフルテーマ設定を使用した Reply サンプルアプリ
  • 動的なテーマ設定のデモを行う JetChat

リファレンス ドキュメント