Material ist zwar das von uns empfohlene Designsystem und Jetpack Compose enthält eine Implementierung von Material, Sie sind jedoch nicht gezwungen, es zu verwenden. Material basiert vollständig auf öffentlichen APIs. Sie können also auf dieselbe Weise Ihr eigenes Designsystem erstellen.
Es gibt mehrere Ansätze, die Sie verfolgen können:
- Erweitern Sie
MaterialThememit zusätzlichen Designwerten. - Ersetzen Sie ein oder mehrere Material-Systeme (
ColorScheme,TypographyoderShapes) durch benutzerdefinierte Implementierungen und behalten Sie die anderen bei. - Implementieren Sie ein vollständig benutzerdefiniertes Designsystem, um
MaterialThemezu ersetzen.
Vielleicht möchten Sie auch weiterhin Material-Komponenten mit einem benutzerdefinierten Designsystem verwenden. Das ist möglich, aber Sie müssen je nach Ansatz einige Dinge beachten.
Weitere Informationen zu den untergeordneten Konstrukten und APIs, die von MaterialTheme und benutzerdefinierten Designsystemen verwendet werden, finden Sie im Leitfaden Anatomie eines Designs in Compose.
Material Theming erweitern
Compose Material orientiert sich eng an Material Theming, damit die Material-Richtlinien einfach und typsicher eingehalten werden können. Es ist jedoch möglich, die Farb-, Typografie- und Formsets um zusätzliche Werte zu erweitern. Am einfachsten ist es, Erweiterungsattribute 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 MaterialTheme-Nutzungs-APIs. Ein von Compose selbst definiertes Beispiel hierfür ist surfaceColorAtElevation, das die Oberflächenfarbe bestimmt, die je nach Höhe verwendet werden soll.
Ein anderer Ansatz besteht darin, ein erweitertes Design 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, die für Aktionen verwendet wird, die mäßig gefährlich sind, während die vorhandenen Material-Farben beibehalten werden:
@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 MaterialTheme-Nutzungs-APIs. Es werden auch mehrere Designs unterstützt, da Sie ExtendedThemes auf dieselbe Weise wie MaterialTheme verschachteln können.
Material-Komponenten verwenden
Wenn Sie Material Theming erweitern, werden vorhandene MaterialTheme-Werte beibehalten und Material-Komponenten haben weiterhin sinnvolle Standardwerte.
Wenn Sie erweiterte Werte in Komponenten verwenden möchten, verpacken Sie sie in Ihre eigenen zusammensetzbaren Funktionen. Legen Sie die Werte, die Sie ändern möchten, direkt fest und stellen Sie andere als Parameter für die enthaltende zusammensetzbare Funktion bereit:
@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, wo dies sinnvoll ist.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Material-Subsysteme ersetzen
Anstatt Material Theming zu erweitern, können Sie ein oder mehrere Systeme – ColorScheme, Typography oder Shapes – durch eine benutzerdefinierte Implementierung ersetzen und die anderen beibehalten.
Angenommen, Sie möchten die Typ- und Formsysteme 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, binden Sie sie in Ihre eigenen zusammensetzbaren Funktionen ein. Legen Sie die Werte für das entsprechende System direkt fest und stellen Sie andere als Parameter für die enthaltende zusammensetzbare Funktion bereit.
@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, wo dies sinnvoll 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:
ColorScheme,TypographyundShapes: Material Theming-SystemeTextSelectionColors: Farben, die für die Textauswahl vonTextundTextFieldverwendet werdenripple, bereitgestellt alsLocalIndication: Material-Implementierung vonIndication
Wenn Sie weiterhin Material-Komponenten verwenden möchten, müssen Sie einige dieser Systeme in Ihren benutzerdefinierten Designs 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 Designs kompatibel zu machen.
Im folgenden Code wird ein benutzerdefiniertes Farbsystem mit Farbverläufen (List<Color>) modelliert, ein Typsystem eingebunden, ein neues Erhöhungssystem eingeführt und andere von MaterialTheme bereitgestellte Systeme ausgeschlossen:
@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 Verwendung von Material-Komponenten im Ist-Zustand zu unerwünschten Werten für Farbe, Typ und Form sowie zu unerwünschtem Verhalten bei der Anzeige.
Wenn Sie benutzerdefinierte Werte in Komponenten verwenden möchten, verpacken Sie sie in Ihre eigenen zusammensetzbaren Funktionen. Legen Sie die Werte für das entsprechende System direkt fest und stellen Sie andere als Parameter für die enthaltende zusammensetzbare Funktion bereit.
Wir empfehlen, auf Werte zuzugreifen, die Sie in Ihrem benutzerdefinierten Design festgelegt haben.
Wenn Ihr Design keine Color-, TextStyle-, Shape- oder anderen Systeme bietet, können Sie sie auch hartcodieren.
@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 Klassentypen eingeführt haben, z. B. List<Color> zur Darstellung von Farbverläufen, 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 Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Material Design 3 in Compose
- In Compose von Material 2 zu Material 3 migrieren
- Aufbau eines Designs in Compose