Benutzerdefinierte Designsysteme in Compose

Während Material als Designsystem empfohlen wird, liefert Jetpack Compose eine von Material implementiert ist, sind Sie nicht gezwungen, es zu nutzen. Das Material wird entwickelt vollständig auf öffentlichen APIs basieren, sodass Sie Ihr eigenes Designsystem in auf die gleiche Weise.

Dafür gibt es mehrere Ansätze:

Sie können auch weiterhin Material-Komponenten mit einem benutzerdefinierten Design verwenden. System. Das ist möglich, aber es gibt einige Dinge zu beachten, Ihren Ansatz.

Weitere Informationen zu den untergeordneten Konstrukten und APIs, die von MaterialTheme verwendet werden und benutzerdefinierte Designsysteme erhalten, finden Sie im Leitfaden Aufbau eines Designs im Editor.

Material-Design erweitern

Stark modelliertes Material zusammenstellen Material-Design damit es einfach und typsicher ist. Es ist jedoch Farben, Typografie und Form mit weiteren Werte.

Der einfachste Ansatz besteht darin, Erweiterungseigenschaften hinzuzufügen:

// Use with MaterialTheme.colors.snackbarAction
val Colors.snackbarAction: Color
    get() = if (isLight) 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: Compose-Datei definiert, primarySurface, der als Proxy zwischen primary und surface fungiert, Colors.isLight

Ein anderer Ansatz besteht darin, ein erweitertes Thema zu definieren, das MaterialTheme und ihre Werte.

Angenommen, Sie möchten die beiden zusätzlichen Farben tertiary und onTertiary hinzufügen. und die vorhandenen Material-Farben beibehalten:

@Immutable
data class ExtendedColors(
    val tertiary: Color,
    val onTertiary: Color
)

val LocalExtendedColors = staticCompositionLocalOf {
    ExtendedColors(
        tertiary = Color.Unspecified,
        onTertiary = Color.Unspecified
    )
}

@Composable
fun ExtendedTheme(
    /* ... */
    content: @Composable () -> Unit
) {
    val extendedColors = ExtendedColors(
        tertiary = Color(0xFFA8EFF0),
        onTertiary = Color(0xFF002021)
    )
    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {
        MaterialTheme(
            /* colors = ..., typography = ..., shapes = ... */
            content = content
        )
    }
}

// Use with eg. ExtendedTheme.colors.tertiary
object ExtendedTheme {
    val colors: ExtendedColors
        @Composable
        get() = LocalExtendedColors.current
}

Dies ähnelt den Nutzungs-APIs für MaterialTheme. Es werden auch mehrere Designs unterstützt. da du ExtendedThemes auf dieselbe Weise verschachteln kannst wie MaterialTheme.

Material-Komponenten verwenden

Beim Erweitern von Material Theming werden vorhandene MaterialTheme-Werte beibehalten. und Material-Komponenten haben noch angemessene Standardeinstellungen.

Wenn Sie erweiterte Werte in Komponenten verwenden möchten, schließen Sie sie in Ihre eigenen ein. zusammensetzbare Funktionen verwenden, die zu ändernden Werte direkt festlegen und andere als Parameter an die enthaltene zusammensetzbare Funktion übergeben:

@Composable
fun ExtendedButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = ExtendedTheme.colors.tertiary,
            contentColor = ExtendedTheme.colors.onTertiary
            /* Other colors use values from MaterialTheme */
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Dann würden Sie die Verwendungen von Button durch ExtendedButton ersetzen, wobei angemessen sein.

@Composable
fun ExtendedApp() {
    ExtendedTheme {
        /*...*/
        ExtendedButton(onClick = { /* ... */ }) {
            /* ... */
        }
    }
}

Materialsysteme ersetzen

Anstatt Material Theming zu erweitern, können Sie ein oder mehrere Colors, Typography oder Shapes mit einer benutzerdefinierten Implementierung, und die anderen beibehalten.

Angenommen, Sie möchten das Schrift- und Formsystem ersetzen, die Farbe System:

@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 eines oder mehrere Systeme von MaterialTheme mit Material ersetzt wurden Komponenten in unveränderter Form zu unerwünschten Farb-, Typ- oder Formwerten für Material führen.

Wenn Sie Ersatzwerte in Komponenten verwenden möchten, schließen Sie sie in Ihre eigenen ein. zusammensetzbaren Funktionen verwenden, die Werte direkt für das jeweilige System festlegen und andere als Parameter an die enthaltene zusammensetzbare Funktion übergeben.

@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()
            }
        }
    )
}

Dann würden Sie die Verwendungen von Button durch ReplacementButton ersetzen, wobei angemessen sein.

@Composable
fun ReplacementApp() {
    ReplacementTheme {
        /*...*/
        ReplacementButton(onClick = { /* ... */ }) {
            /* ... */
        }
    }
}

Vollständig kundenspezifisches Designsystem implementieren

Sie können Material Theming durch ein vollständig benutzerdefiniertes Designsystem ersetzen. MaterialTheme bietet die folgenden Systeme:

  • Colors, Typography und Shapes: Material Theming-Systeme
  • ContentAlpha: Deckkraft zur Hervorhebung in Text und Icon
  • TextSelectionColors: Farben für die Textauswahl von Text und TextField
  • Ripple und RippleTheme: Materialisierte Implementierung von Indication

Wenn Sie weiterhin Material-Komponenten verwenden möchten, benutzerdefinierten Designs erstellen oder die Systeme in Ihrem um unerwünschtes Verhalten zu vermeiden.

Designsysteme sind jedoch nicht auf die Konzepte beschränkt, auf denen das Material basiert. Ich bestehende Systeme modifizieren und ganz neue einführen – mit neuen Klassen und Typen hinzufügen, um andere Konzepte mit Themen kompatibel zu machen.

Im folgenden Code wird ein benutzerdefiniertes Farbsystem mit Farbverläufen modelliert (List<Color>), enthalten ein Schriftsystem, führen ein neues Höhensystem ein, und andere von MaterialTheme bereitgestellte Systeme ausschließen:

@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 der unveränderten Material-Komponenten zur Folge. in unerwünschtem Material-Farb-, Schrift- und Formwerte sowie in Anzeigeverhalten angezeigt.

Wenn Sie benutzerdefinierte Werte in Komponenten verwenden möchten, schließen Sie diese in Ihre eigene zusammensetzbare Funktion ein. die Werte für das relevante System direkt festlegen und andere Parameter als Parameter für die zusammensetzbare Funktion verwenden, in der sie enthalten ist.

Wir empfehlen, auf Werte zuzugreifen, die Sie in Ihrem benutzerdefinierten Design festgelegt haben. Wenn Sie alternativ Ihr Design unterstützt Color, TextStyle, Shape oder andere Systeme nicht. Sie hartcodieren können.

@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 = ContentAlpha.disabled)
        ),
        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, die Komponenten von Grund auf neu zu implementieren, anstatt sie zu umschließen. Sehen Sie sich zum Beispiel JetsnackButton aus dem Jetsnack-Sample.