Material ist zwar unser empfohlenes Designsystem und Jetpack Compose enthält eine Implementierung von Material, Sie sind aber nicht gezwungen, es zu verwenden. Material basiert vollständig auf öffentlichen APIs. Sie können also auf dieselbe Weise Ihr eigenes Designsystem erstellen.
Dafür gibt es mehrere Ansätze:
MaterialTheme
mit zusätzlichen Designwerten erweitern- Ersetzen eines oder mehrerer Material-Systeme –
Colors
,Typography
oderShapes
– durch benutzerdefinierte Implementierungen, während die anderen beibehalten werden - Implementierung eines vollständig benutzerdefinierten Designsystems als Ersatz für
MaterialTheme
Möglicherweise möchten Sie auch weiterhin Material-Komponenten mit einem benutzerdefinierten Designsystem verwenden. Das ist möglich, aber es gibt einige Dinge zu beachten, die zu Ihrem Ansatz passen.
Weitere Informationen zu den von MaterialTheme
und benutzerdefinierten Designsystemen verwendeten Low-Level-Konstrukten und APIs finden Sie im Leitfaden zur Struktur eines Themes in Compose.
Material Theme erweitern
Compose Material orientiert sich eng am Material-Theming, um die Einhaltung der Material-Richtlinien einfach und typsicher zu gestalten. Sie können die Farb-, Typografie- und Formsets jedoch mit zusätzlichen Werten erweitern.
Am einfachsten ist es, Erweiterungseigenschaften hinzuzufügen:
// Use with MaterialTheme.colorScheme.snackbarAction val ColorScheme.snackbarAction: Color @Composable get() = if (isSystemInDarkTheme()) Red300 else Red700 // Use with MaterialTheme.typography.textFieldInput val Typography.textFieldInput: TextStyle get() = TextStyle(/* ... */) // Use with MaterialTheme.shapes.card val Shapes.card: Shape get() = RoundedCornerShape(size = 20.dp)
Dies sorgt für Konsistenz mit den APIs zur MaterialTheme
-Nutzung. Ein Beispiel dafür, das von Compose selbst definiert wird, ist surfaceColorAtElevation
. Damit wird die Oberflächenfarbe bestimmt, die je nach Erhebung verwendet werden soll.
Eine weitere Möglichkeit besteht darin, ein erweitertes Theme zu definieren, das MaterialTheme
und seine Werte „umschließt“.
Angenommen, Sie möchten zwei zusätzliche Farben hinzufügen – caution
und onCaution
, eine gelbe Farbe für Aktionen, die nur teilweise gefährlich sind – und die vorhandenen Material-Farben beibehalten:
@Immutable data class ExtendedColors( val caution: Color, val onCaution: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( caution = Color.Unspecified, onCaution = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( caution = Color(0xFFFFCC02), onCaution = Color(0xFF2C2D30) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.caution object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
Dies ähnelt den APIs zur MaterialTheme
-Nutzung. Es werden auch mehrere Themen unterstützt, da Sie ExtendedTheme
auf dieselbe Weise wie MaterialTheme
verschachteln können.
Material-Komponenten verwenden
Wenn Sie Material Theming erweitern, bleiben vorhandene MaterialTheme
-Werte erhalten und Material-Komponenten haben weiterhin sinnvolle Standardwerte.
Wenn Sie erweiterte Werte in Komponenten verwenden möchten, umschließen Sie sie mit eigenen zusammensetzbaren Funktionen, legen Sie die Werte, die Sie ändern möchten, direkt fest und machen Sie andere als Parameter für die enthaltende zusammensetzbare Funktion verfügbar:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.caution, contentColor = ExtendedTheme.colors.onCaution /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
Anschließend ersetzen Sie die Verwendungen von Button
durch ExtendedButton
, sofern dies angemessen ist.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Material-Subsysteme ersetzen
Anstatt das Material-Theming zu erweitern, können Sie ein oder mehrere Systeme – Colors
, Typography
oder Shapes
– durch eine benutzerdefinierte Implementierung ersetzen und die anderen beibehalten.
Angenommen, Sie möchten die Systeme für Typografie und Form ersetzen, aber das Farbsystem beibehalten:
@Immutable data class ReplacementTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class ReplacementShapes( val component: Shape, val surface: Shape ) val LocalReplacementTypography = staticCompositionLocalOf { ReplacementTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalReplacementShapes = staticCompositionLocalOf { ReplacementShapes( component = RoundedCornerShape(ZeroCornerSize), surface = RoundedCornerShape(ZeroCornerSize) ) } @Composable fun ReplacementTheme( /* ... */ content: @Composable () -> Unit ) { val replacementTypography = ReplacementTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val replacementShapes = ReplacementShapes( component = RoundedCornerShape(percent = 50), surface = RoundedCornerShape(size = 40.dp) ) CompositionLocalProvider( LocalReplacementTypography provides replacementTypography, LocalReplacementShapes provides replacementShapes ) { MaterialTheme( /* colors = ... */ content = content ) } } // Use with eg. ReplacementTheme.typography.body object ReplacementTheme { val typography: ReplacementTypography @Composable get() = LocalReplacementTypography.current val shapes: ReplacementShapes @Composable get() = LocalReplacementShapes.current }
Material-Komponenten verwenden
Wenn ein oder mehrere Systeme von MaterialTheme
ersetzt wurden, kann die unveränderte Verwendung von Material-Komponenten zu unerwünschten Material-Farb-, ‑Typ- oder ‑Formwerten führen.
Wenn Sie Ersatzwerte in Komponenten verwenden möchten, müssen Sie sie in eigene zusammensetzbare Funktionen einfügen, die Werte für das entsprechende System direkt festlegen und andere als Parameter für die enthaltende zusammensetzbare Funktion verfügbar machen.
@Composable fun ReplacementButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( shape = ReplacementTheme.shapes.component, onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = ReplacementTheme.typography.body ) { content() } } ) }
Anschließend ersetzen Sie die Verwendungen von Button
durch ReplacementButton
, sofern dies angemessen ist.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Vollständig benutzerdefiniertes Designsystem implementieren
Möglicherweise möchten Sie Material Theming durch ein vollständig benutzerdefiniertes Designsystem ersetzen.
MaterialTheme
bietet die folgenden Systeme:
Colors
,Typography
undShapes
: Material-Theming-SystemeTextSelectionColors
: Farben, die für die Textauswahl durchText
undTextField
verwendet werdenRipple
undRippleTheme
: Materialimplementierung vonIndication
Wenn Sie Material-Komponenten weiterhin verwenden möchten, müssen Sie einige dieser Systeme in Ihrem benutzerdefinierten Theme oder Ihren benutzerdefinierten Themes ersetzen oder die Systeme in Ihren Komponenten verarbeiten, um unerwünschtes Verhalten zu vermeiden.
Designsysteme sind jedoch nicht auf die Konzepte beschränkt, auf denen Material basiert. Sie können vorhandene Systeme ändern und völlig neue Systeme mit neuen Klassen und Typen einführen, um andere Konzepte mit Themen kompatibel zu machen.
Im folgenden Code modellieren wir ein benutzerdefiniertes Farbsystem, das Verläufe (List<Color>
) enthält, ein Typsystem und ein neues Höhensystem einführt und andere von MaterialTheme
bereitgestellte Systeme ausschließt:
@Immutable data class CustomColors( val content: Color, val component: Color, val background: List<Color> ) @Immutable data class CustomTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class CustomElevation( val default: Dp, val pressed: Dp ) val LocalCustomColors = staticCompositionLocalOf { CustomColors( content = Color.Unspecified, component = Color.Unspecified, background = emptyList() ) } val LocalCustomTypography = staticCompositionLocalOf { CustomTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalCustomElevation = staticCompositionLocalOf { CustomElevation( default = Dp.Unspecified, pressed = Dp.Unspecified ) } @Composable fun CustomTheme( /* ... */ content: @Composable () -> Unit ) { val customColors = CustomColors( content = Color(0xFFDD0D3C), component = Color(0xFFC20029), background = listOf(Color.White, Color(0xFFF8BBD0)) ) val customTypography = CustomTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val customElevation = CustomElevation( default = 4.dp, pressed = 8.dp ) CompositionLocalProvider( LocalCustomColors provides customColors, LocalCustomTypography provides customTypography, LocalCustomElevation provides customElevation, content = content ) } // Use with eg. CustomTheme.elevation.small object CustomTheme { val colors: CustomColors @Composable get() = LocalCustomColors.current val typography: CustomTypography @Composable get() = LocalCustomTypography.current val elevation: CustomElevation @Composable get() = LocalCustomElevation.current }
Material-Komponenten verwenden
Wenn kein MaterialTheme
vorhanden ist, führt die unveränderte Verwendung von Material-Komponenten zu unerwünschten Materialfarb-, ‑typ- und ‑formwerten sowie zu unerwünschtem Indikationsverhalten.
Wenn Sie benutzerdefinierte Werte in Komponenten verwenden möchten, müssen Sie sie in eigene zusammensetzbare Funktionen einfügen, die Werte für das entsprechende System direkt festlegen und andere als Parameter für die enthaltende zusammensetzbare Funktion verfügbar machen.
Wir empfehlen, dass Sie über Ihr benutzerdefiniertes Design auf die von Ihnen festgelegten Werte zugreifen.
Alternativ können Sie die Systeme auch fest codieren, wenn Ihr Theme keine Color
-, TextStyle
-, Shape
- oder andere Systeme bietet.
@Composable fun CustomButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = CustomTheme.colors.component, contentColor = CustomTheme.colors.content, disabledContainerColor = CustomTheme.colors.content .copy(alpha = 0.12f) .compositeOver(CustomTheme.colors.component), disabledContentColor = CustomTheme.colors.content .copy(alpha = 0.38f) ), shape = ButtonShape, elevation = ButtonDefaults.elevatedButtonElevation( defaultElevation = CustomTheme.elevation.default, pressedElevation = CustomTheme.elevation.pressed /* disabledElevation = 0.dp */ ), onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = CustomTheme.typography.body ) { content() } } ) } val ButtonShape = RoundedCornerShape(percent = 50)
Wenn Sie neue Klassen eingeführt haben, z. B. List<Color>
für Verläufe, ist es möglicherweise besser, Komponenten von Grund auf neu zu implementieren, anstatt sie zu umschließen. Ein Beispiel finden Sie unter JetsnackButton
im Jetsnack-Beispiel.
Empfehlungen für dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Material Design 3 in Compose
- Von Material 2 zu Material 3 in Compose migrieren
- Aufbau eines Designs in Compose