スタイルを使用してアプリを構築する方法はいくつかあります。どの方法を選択するかは、マテリアル デザインの採用状況に応じて異なります。
- マテリアル デザインを使用しないフルカスタム デザイン システム
- 推奨事項: テーマから値を使用するコンポーネント スタイルを定義し、デザイン システム コンポーネントでスタイル パラメータを公開します。
- マテリアル デザインを使用する
- 推奨事項: マテリアルが採用されるまで待ってから、スタイルと統合します。 可能な場合は、独自のコンポーネントでスタイルを使用します。
スタイル レイヤ
従来の Compose モデルでは、カスタマイズは多くの場合、MaterialTheme によって提供されるグローバル トークン(色とタイポグラフィ)のオーバーライドや、可能な場合はデザイン システムのコンポーザブルのプロパティのラッピングとオーバーライドに大きく依存しています。サブシステムやパラメータを介して公開されていないプロパティがマテリアル
レイヤ内に存在し、コンポーネント自体にハードコードされたデフォルト値になっている場合もあります。
Styles API には、サブシステムとコンポーネントの橋渡しとなる新しい抽象化レイヤ(スタイル )があります。
| レイヤ | 責任範囲 | 例 |
|---|---|---|
| サブシステムの値 | 名前付きの値 | val Primary = Color(0xFF34A85E) |
| アトミック スタイル | 1 つのプロパティ変更のみを行うスタイル | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| コンポーネント スタイル | コンポーネント固有の構成 | プライマリ背景と 16dp のパディングを持つボタン。val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| コンポーネント | スタイルを使用する機能的な UI 要素。 | Button(style = buttonStyle) { ... } |
アトミック スタイルとモノリシック スタイル
Styles API を使用すると、スタイルを個別の原子スタイルに分解できます。
baseButtonStyle
のような複雑なコンポーネント固有のスタイルを定義する代わりに、小規模で単一目的のユーティリティ
スタイルを作成することもできます。これらは「原子」として機能します。
// Define single-purpose "atomic" styles val paddingAtomic = Style { contentPadding(16.dp) } val roundedCornerShapeAtomic = Style { shape(RoundedCornerShape(8.dp)) } val primaryBackgroundAtomic = Style { background(Color.Blue) } val largeSizeAtomic = Style { size(100.dp, 40.dp) } val interactiveShadowAtomic = Style { hovered { animate { dropShadow( Shadow( offset = DpOffset( 0.dp, 0.dp ), radius = 2.dp, spread = 0.dp, color = Color.Blue, ) ) } } }
「then」を使用した構成
新しい Styles API の強力な機能の 1 つに then 演算子があります。この演算子を使用すると、複数の Style
オブジェクトをマージできます。これにより、アトミック ユーティリティ クラスを使用してコンポーネントを構築できます。
従来型(非アトミック):
// One large monolithic style val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
アトミック リファクタリング:
// Combine atoms to create the final appearance val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
デザイン システムにスタイルを採用する
デザイン システムにスタイルを採用する場合は、デザイン システムがスペクトラムのどの位置にあるかに応じて、次のオプションを検討してください。
スタイルを使用したカスタム デザイン システム
検討すべき場合: マテリアル デザインに基づいていない包括的なブランドガイドが提供されており、マテリアル デザインを使用する予定がない場合。
**戦略**: フルカスタム デザイン システムを実装し、スタイルをテーマの一部 として公開します。
マテリアルをメインのデザイン システム言語として使用しない場合は、カスタムパスを使用します。ビジュアル定義には MaterialTheme を完全にバイパスし、
独自のカスタム テーマをすでに作成しています。スタイルのコンテナとして機能する CompanyTheme
を構築します。
- 仕組み: システム内のすべてのコンポーネントの
Styleオブジェクト を保持するCompanyThemeオブジェクトを作成します。コンポーネント(マテリアル ロジックのラッパー、カスタムのBox実装、Layout実装)はこれらのスタイルを直接使用し、デザイン システムのコンシューマに対してStyleパラメータを公開します。 - スタイル レイヤ: スタイルはデザイン システム の主要な定義です。トークンは、これらのスタイルに渡される名前付き変数です。これにより、状態変化に対する一意のアニメーション(押下時のスケールと色の変化など)の定義など、詳細なカスタマイズが可能になります。
マテリアルを使用せずに独自のカスタムテーマを構築していて、 スタイルを採用する場合は、スタイルのリストをテーマに追加します。これにより、プロジェクト内のどこからでも基本スタイルにアクセスできます。
アプリケーション内のさまざまなスタイルを保存し、デフォルトを作成する
Stylesクラスを作成します。たとえば、Jetsnack アプリでは、クラス名はJetsnackStylesです。object JetsnackStyles{ val buttonStyle: Style = Style { shape(shapes.medium) background(colors.brand) contentColor(colors.textPrimary) contentPaddingVertical(8.dp) contentPaddingHorizontal(24.dp) textStyle(typography.labelLarge) disabled { animate { background(colors.brandSecondary) } } } val cardStyle: Style = Style { shape(shapes.medium) background(colors.uiBackground) contentColor(colors.textPrimary) } }
Stylesを全体的なテーマの一部として提供し、サブシステムにアクセスするためのヘルパー拡張関数をStyleScopeで公開します。@Immutable class JetsnackTheme( val colors: JetsnackColors = LightJetsnackColors, val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(), val shapes: Shapes = Shapes() ) { companion object { val colors: JetsnackColors @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.colors val typography: androidx.compose.material3.Typography @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.typography val shapes: Shapes @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.shapes val styles: JetsnackStyles = JetsnackStyles val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme> get() = LocalJetsnackThemeInstance } } val StyleScope.colors: JetsnackColors get() = LocalJetsnackTheme.currentValue.colors val StyleScope.typography: androidx.compose.material3.Typography get() = LocalJetsnackTheme.currentValue.typography val StyleScope.shapes: Shapes get() = LocalJetsnackTheme.currentValue.shapes internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors val theme = JetsnackTheme(colors = colors) CompositionLocalProvider( LocalJetsnackTheme provides theme, ) { MaterialTheme( typography = LocalJetsnackTheme.current.typography, shapes = LocalJetsnackTheme.current.shapes, content = content, ) } }
コンポーザブル内で
JetsnackStylesにアクセスします。@Composable fun CustomButton(modifier: Modifier, style: Style = Style, text: String) { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } // Apply style to top level container in combination with incoming style from parameter. Box(modifier = modifier .clickable( interactionSource = interactionSource, indication = null, enabled = true, role = Role.Button, onClick = { }, ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) { Text(text) } }
グローバル テーマの採用以外にも、アプリに Styles
を組み込むための代替戦略があります。特定の呼び出しサイトで Styles
をインラインで使用することも、完全なテーマ設定機能が不要な場合は静的定義を使用することもできます。
スタイル全体が根本的に異なる場合を除き、Styles
を条件付きで切り替えることは避けてください。個別のスタイル オブジェクトを切り替えるのではなく、ビジュアル定義内で動的トークンにアクセスすることをおすすめします。