Material ist unser empfohlenes Designsystem und Jetpack Compose enthält eine Implementierung von Material. Sie müssen es aber nicht verwenden. Material Design basiert vollständig auf öffentlichen APIs. Daher ist es möglich, auf dieselbe Weise ein eigenes Designsystem zu erstellen.
Es gibt mehrere Möglichkeiten:
MaterialTheme
mit zusätzlichen Designwerten erweitern- Ein oder mehrere Material-Systeme –
Colors
,Typography
oderShapes
– durch benutzerdefinierte Implementierungen ersetzen, während die anderen beibehalten werden - Ein vollständig benutzerdefiniertes Designsystem implementieren, um
MaterialTheme
zu ersetzen
Sie können auch weiterhin Materialkomponenten mit einem benutzerdefinierten Designsystem verwenden. Das ist möglich, aber es gibt einige Dinge, die Sie beachten sollten, um den von Ihnen gewählten Ansatz zu unterstützen.
Weitere Informationen zu den Konstrukten und APIs der unteren Ebene, die von MaterialTheme
und benutzerdefinierten Designsystemen verwendet werden, finden Sie im Leitfaden Anatomy of a theme in Compose (Anatomie eines Themas in Compose).
Material Theme erweitern
Compose Material orientiert sich stark an den Material-Design-Themen, damit die Material-Design-Richtlinien einfach und typsicher eingehalten werden können. Es ist jedoch möglich, die Farb-, Typografie- und Formensätze um zusätzliche Werte zu 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 MaterialTheme
-Nutzungs-APIs. Ein Beispiel hierfür ist surfaceColorAtElevation
, das von Compose selbst definiert wird und die Oberflächenfarbe bestimmt, die je nach Höhe verwendet werden soll.
Eine weitere Möglichkeit 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 für Aktionen mit mittlerem Gefahrenpotenzial. Die vorhandenen Materialfarben sollen dabei 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 }
Das ist mit den APIs zur MaterialTheme
-Nutzung vergleichbar. Außerdem werden mehrere Themen unterstützt, da ExtendedTheme
s genauso wie MaterialTheme
verschachtelt werden können.
Materialkomponenten verwenden
Wenn Sie Material-Design-Themen erweitern, bleiben vorhandene MaterialTheme
-Werte erhalten und Material-Komponenten haben weiterhin angemessene Standardwerte.
Wenn Sie erweiterte Werte in Komponenten verwenden möchten, verpacken Sie sie in Ihre eigenen zusammensetzbaren Funktionen, legen Sie die Werte fest, die Sie ändern möchten, und geben Sie andere als Parameter für das enthaltene zusammensetzbare Element an:
@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 ) }
Ersetzen Sie dann gegebenenfalls die Verwendung von Button
durch ExtendedButton
.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Material-Subsysteme ersetzen
Anstatt 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 das Schriftbild- und das Formensystem ersetzen, das Farbsystem aber 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 }
Materialkomponenten verwenden
Wenn eines oder mehrere Systeme von MaterialTheme
ersetzt wurden, kann die Verwendung von Materialkomponenten in der Standardkonfiguration zu unerwünschten Werten für Materialfarbe, -typ oder -form führen.
Wenn Sie Ersatzwerte in Komponenten verwenden möchten, verpacken Sie sie in Ihre eigenen zusammensetzbaren Funktionen, legen Sie die Werte direkt für das entsprechende System fest und geben Sie andere als Parameter für das enthaltene zusammensetzbare Element an.
@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() } } ) }
Ersetzen Sie dann gegebenenfalls die Verwendung von Button
durch ReplacementButton
.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Ein vollständig benutzerdefiniertes Designsystem implementieren
Sie können Material Theming durch ein vollständig benutzerdefiniertes Designsystem ersetzen.
Angenommen, MaterialTheme
stellt die folgenden Systeme bereit:
Colors
,Typography
undShapes
: Material Theming-SystemeTextSelectionColors
: Farben, die für die Textauswahl mitText
undTextField
verwendet werdenRipple
undRippleTheme
: Materialimplementierung vonIndication
Wenn Sie Material-Komponenten weiterhin verwenden möchten, müssen Sie einige dieser Systeme in Ihrem benutzerdefinierten Design oder Ihren benutzerdefinierten Designs ersetzen oder die Systeme in Ihren Komponenten verwalten, um unerwünschtes Verhalten zu vermeiden.
Designsysteme sind jedoch nicht auf die Konzepte beschränkt, auf die Material Design basiert. Sie können vorhandene Systeme ändern und ganz 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 mit Farbverläufen (List<Color>
), ein Typsystem, ein neues Höhensystem und schließen andere von MaterialTheme
bereitgestellte Systeme aus:
@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 }
Materialkomponenten verwenden
Wenn kein MaterialTheme
vorhanden ist, führt die Verwendung von Materialkomponenten in der Standardeinstellung zu unerwünschten Materialfarben, -typen und -formwerten sowie zu unerwünschtem Anzeigeverhalten.
Wenn Sie benutzerdefinierte Werte in Komponenten verwenden möchten, verpacken Sie sie in Ihre eigenen composable-Funktionen, legen Sie die Werte direkt für das entsprechende System fest und geben Sie andere als Parameter für das enthaltene composable frei.
Wir empfehlen, auf Werte zuzugreifen, die Sie in Ihrem benutzerdefinierten Design festgelegt haben.
Wenn Ihr Design keine Color
-, TextStyle
-, Shape
- oder andere 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>
für Farbverläufe, ist es möglicherweise besser, Komponenten von Grund auf neu zu implementieren, anstatt sie zu umschließen. Sehen Sie sich zum Beispiel JetsnackButton
aus dem Jetsnack-Beispiel an.
Empfehlungen für dich
- 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 Themas in Compose