Material Design 2 in Compose

Jetpack Compose bietet eine Implementierung von Material Design, einem umfassenden Designsystem zum Erstellen digitaler Schnittstellen. Die Material Design-Komponenten (Schaltflächen, Karten, Schalter usw.) basieren auf Material Theming, einer systematischen Möglichkeit, Material Design so anzupassen, dass es die Marke Ihres Produkts besser widerspiegelt. Ein Materialdesign enthält die Attribute Farbe, Typografie und Form. Wenn Sie diese Attribute anpassen, werden Ihre Änderungen automatisch in den Komponenten widergespiegelt, die Sie zum Erstellen Ihrer Anwendung verwenden.

Jetpack Compose implementiert diese Konzepte mit der zusammensetzbaren Funktion MaterialTheme:

MaterialTheme(
    colors = // ...
    typography = // ...
    shapes = // ...
) {
    // app content
}

Konfigurieren Sie die Parameter, die Sie an MaterialTheme übergeben, um das Design Ihrer Anwendung zu bestimmen.

Zwei kontrastierende Screenshots. Im ersten Screenshot wird der Standard-MaterialTheme-Stil
verwendet, im zweiten mit modifizierten Stilen.

Abbildung 1: Der erste Screenshot zeigt eine App, in der MaterialTheme nicht konfiguriert wird und daher einen Standardstil verwendet. Der zweite Screenshot zeigt eine App, die Parameter an MaterialTheme übergibt, um den Stil anzupassen.

Farbe

Farben werden in Compose mit der Klasse Color modelliert, einer einfachen Datenspeicherklasse.

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

Sie können diese beliebig organisieren (als Konstanten auf oberster Ebene, in einem Singleton-Element oder als Inline-Definition). Wir empfehlen jedoch dringend, Farben in Ihrem Design anzugeben und die Farben von dort abzurufen. Auf diese Weise können das dunkle Design und verschachtelte Designs problemlos unterstützt werden.

Beispiel für die Farbpalette eines Designs

Abbildung 2: Das Material-Farbsystem.

Compose bietet die Klasse Colors zum Modellieren des Farbsystems „Material“. Colors bietet Builder-Funktionen zum Erstellen von hellen oder dunklen Farben:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

Nachdem Sie die Colors definiert haben, können Sie sie an eine MaterialTheme übergeben:

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

Designfarben verwenden

Sie können die Colors, die für die zusammensetzbare Funktion MaterialTheme bereitgestellt wurde, mit MaterialTheme.colors abrufen.

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

Oberfläche und Inhaltsfarbe

Viele Komponenten akzeptieren ein Farb- und ein Inhaltsfarbe-Paar:

Surface(
    color = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

Auf diese Weise können Sie nicht nur die Farbe einer zusammensetzbaren Funktion festlegen, sondern auch eine Standardfarbe für den Inhalt, also die darin enthaltenen zusammensetzbaren Funktionen, angeben. Bei vielen zusammensetzbaren Funktionen wird diese Inhaltsfarbe standardmäßig verwendet. Beispielsweise basiert die Farbe von Text auf der Inhaltsfarbe des übergeordneten Elements und Icon verwendet diese Farbe, um die Färbung festzulegen.

Zwei Beispiele für dasselbe Banner in unterschiedlichen Farben

Abbildung 3: Durch das Festlegen verschiedener Hintergrundfarben ergeben sich unterschiedliche Text- und Symbolfarben.

Die Methode contentColorFor() ruft die entsprechende "on"-Farbe für alle Designfarben ab. Wenn Sie beispielsweise eine Hintergrundfarbe primary für Surface festlegen, wird mit dieser Funktion onPrimary als Inhaltsfarbe festgelegt. Wenn Sie eine Hintergrundfarbe festlegen, die nicht zum Design gehört, sollten Sie auch eine entsprechende Inhaltsfarbe angeben. Verwenden Sie LocalContentColor, um die bevorzugte Inhaltsfarbe für den aktuellen Hintergrund an einer bestimmten Position in der Hierarchie abzurufen.

Alphaversion für Inhalt

Oftmals sollten Sie variieren, wie sehr Sie Inhalte betonen, um Wichtigkeit zu kommunizieren und eine visuelle Hierarchie zu schaffen. In den Empfehlungen zur Lesbarkeit von Material Design-Texten wird empfohlen, verschiedene Grad der Deckkraft festzulegen, um unterschiedliche Wichtigkeitsstufen zu vermitteln.

Jetpack Compose implementiert dies über LocalContentAlpha. Sie können einen Alphawert für Inhalte für eine Hierarchie angeben, indem Sie für diese CompositionLocal einen Wert angeben. Verschachtelte zusammensetzbare Funktionen können diesen Wert verwenden, um die Alpha-Behandlung auf ihren Inhalt anzuwenden. Beispiel: Für Text und Icon wird standardmäßig eine Kombination aus LocalContentColor verwendet, angepasst an LocalContentAlpha. Material gibt einige Standard-Alphawerte (high, medium, disabled) an, die vom Objekt ContentAlpha modelliert werden.

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(
        // ...
    )
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(
        // ...
    )
    Text(
        // ...
    )
}

Weitere Informationen zu CompositionLocal finden Sie im Leitfaden für lokal zugeordnete Daten im CompositionLocal-Leitfaden.

Screenshot eines Artikeltitels mit unterschiedlicher
Textbetonung.

Abbildung 4: Wenden Sie verschiedene Betonungsebenen auf Text an, um die Informationshierarchie visuell zu vermitteln. Die erste Textzeile ist der Titel und enthält die wichtigsten Informationen. Daher wird ContentAlpha.high verwendet. Die zweite Zeile enthält weniger wichtige Metadaten und verwendet daher ContentAlpha.medium.

Dunkles Design

In Compose implementieren Sie helle und dunkle Designs, indem Sie der zusammensetzbaren Funktion MaterialTheme verschiedene Colors-Sätze bereitstellen:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

In diesem Beispiel ist MaterialTheme in eine eigene zusammensetzbare Funktion eingebunden, die einen Parameter akzeptiert, der angibt, ob ein dunkles Design verwendet werden soll. In diesem Fall ruft die Funktion den Standardwert für darkTheme ab, indem sie die Einstellung des Gerätedesigns abfragt.

Mit einem Code wie diesem können Sie prüfen, ob die aktuellen Colors hell oder dunkel sind:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
            R.drawable.ic_sun_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

Höhen-Overlays

In Material erhalten Oberflächen in dunklen Designs mit höheren Erhebungen Elevation-Overlays, die den Hintergrund aufhellen. Je höher die Höhe einer Oberfläche (d. h., sie nähert sich an eine implizierte Lichtquelle), desto leichter wird sie.

Diese Overlays werden bei Verwendung dunkler Farben automatisch von der zusammensetzbaren Funktion Surface und allen anderen zusammensetzbaren Funktionen aus Material mit einer Oberfläche angewendet:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

Screenshot einer App, in der die leicht unterschiedlichen Farben für Elemente auf verschiedenen Höhenstufen zu sehen sind

Abbildung 5: Die Karten und die Navigation unten verwenden beide die Farbe surface als Hintergrund. Da sich die Karten und die untere Navigationsleiste auf verschiedenen Höhen über dem Hintergrund befinden, haben sie leicht unterschiedliche Farben: Die Karten sind heller als der Hintergrund und die untere Navigationsleiste ist heller als die Karten.

Verwende für benutzerdefinierte Szenarien, die keine Surface beinhalten, LocalElevationOverlay, ein CompositionLocal-Objekt, das die ElevationOverlay enthält, die von den Surface-Komponenten verwendet wird:

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

Zum Deaktivieren von Höhen-Overlays geben Sie null am gewünschten Punkt in einer zusammensetzbaren Hierarchie an:

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

Begrenzte Farbakzente

Material empfiehlt, eingeschränkte Farbakzente für dunkle Designs anzuwenden. In den meisten Fällen wird die Farbe surface gegenüber primary bevorzugt. Zusammensetzbare Funktionen aus Material wie TopAppBar und BottomNavigation implementieren dieses Verhalten standardmäßig.

Abbildung 6: Dunkles Design mit begrenzten Farbakzenten. Für die obere App-Leiste wird die Primärfarbe im hellen Design und die Oberflächenfarbe im dunklen Design verwendet.

Verwenden Sie für benutzerdefinierte Szenarien die Erweiterungseigenschaft primarySurface:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

Typografie

Material definiert ein Typsystem, das Ihnen nahelegt, eine kleine Anzahl semantisch benannter Stile zu verwenden.

Beispiel für verschiedene Schriftarten in verschiedenen Stilen

Abbildung 7: Materialtypsystem.

Compose implementiert das Typsystem mit den Klassen Typography, TextStyle und schriftart. Der Typography-Konstruktor bietet Standardwerte für jeden Stil, sodass Sie alle Stile weglassen können, die Sie nicht anpassen möchten:

val raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

Wenn Sie überall dieselbe Schriftart verwenden möchten, geben Sie defaultFontFamily parameter an und lassen Sie fontFamily aller TextStyle-Elemente weg:

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

Textstile verwenden

Der Zugriff auf TextStyles erfolgt über MaterialTheme.typography. Rufen Sie die TextStyles so ab:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

Screenshot, der eine Mischung verschiedener Schriftbilder für unterschiedliche Zwecke zeigt

Abbildung 8: Verwenden Sie verschiedene Schriftarten und Stile, um Ihre Marke auszudrücken.

Form

Material definiert ein Formsystem, in dem Formen für große, mittlere und kleine Komponenten definiert werden können.

Zeigt eine Vielzahl von Material Design-Formen

Abbildung 9: Das Material-Formsystem.

Compose implementiert das Formsystem mit der Klasse Shapes, mit der Sie für jede Größenkategorie eine CornerBasedShape angeben können:

val shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = shapes, /*...*/) {
    /*...*/
}

Viele Komponenten verwenden diese Formen standardmäßig. Beispiel: Button, TextField und FloatingActionButton sind standardmäßig auf „klein“, AlertDialog auf „mittel“ und ModalDrawer auf „groß“. Die vollständige Zuordnung finden Sie in der Referenz zum Formschema.

Formen verwenden

Der Zugriff auf Shapes erfolgt über MaterialTheme.shapes. Rufen Sie die Shapes mit folgendem Code ab:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

Screenshot einer App, in der mithilfe von Material-Formen der aktuelle Zustand eines Elements dargestellt wird

Abbildung 10: Verwenden Sie Formen, um Marke oder Zustand auszudrücken.

Standardstile

Es gibt kein äquivalentes Konzept bei der Erstellung von Standardformaten aus Android Views. Sie können eine ähnliche Funktionalität bereitstellen, indem Sie Ihre eigenen zusammensetzbaren Überlastfunktionen erstellen, die Material-Komponenten umschließen. Wenn Sie beispielsweise einen Schaltflächenstil erstellen möchten, fügen Sie eine Schaltfläche in Ihre eigene zusammensetzbare Funktion ein, legen Sie die Parameter, die Sie ändern möchten, direkt fest und stellen Sie andere als Parameter für die enthaltene zusammensetzbare Funktion bereit.

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Design-Overlays

Sie können das Äquivalent von Design-Overlays aus Android-Ansichten in der Funktion „Compose“ erreichen, indem Sie zusammensetzbare MaterialTheme-Objekte verschachteln. Da mit MaterialTheme die Farben, Typografie und Formen auf den aktuellen Designwert gesetzt werden, behalten die anderen Parameter die Standardwerte bei, wenn bei einem Design nur einer dieser Parameter festgelegt wird.

Achten Sie außerdem bei der Migration von auf Ansichten basierenden Bildschirmen auf die Verwendung des Attributs android:theme. Sie benötigen wahrscheinlich ein neues MaterialTheme in diesem Teil des UI-Baums zum Schreiben.

Im Owl-Beispiel wird im Detailbildschirm ein PinkTheme für den Großteil des Bildschirms und dann ein BlueTheme für den entsprechenden Abschnitt verwendet. Siehe Screenshot und Code unten.

Abbildung 11: Verschachtelte Designs im Owl-Beispiel.

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

Komponentenstatus

Materialkomponenten, mit denen interagiert werden kann (angeklickt, ein-/ausgeblendet usw.), können verschiedene visuelle Status haben. Zu den Status gehören „aktiviert“, „deaktiviert“, „betätigt“ usw.

Zusammensetzbare Funktionen haben oft einen enabled-Parameter. Wenn sie auf false gesetzt ist, werden Interaktionen verhindert und Eigenschaften wie Farbe und Höhe geändert, um den Komponentenstatus visuell zu vermitteln.

Abbildung 12: Schaltfläche mit enabled = true (links) und enabled = false (rechts).

In den meisten Fällen können Sie sich auf Standardwerte wie Werte wie Farbe und Höhe verlassen. Wenn Sie Werte konfigurieren möchten, die in verschiedenen Status verwendet werden, stehen Klassen und praktischen Funktionen zur Verfügung. Hier ein Beispiel für eine Schaltfläche:

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

Abbildung 13: Schaltfläche mit enabled = true (links) und enabled = false (rechts) mit angepassten Farb- und Höhenwerten.

Kleine Wellen

Materialbauteile verwenden Rippchen, um anzuzeigen, mit ihnen zu interagieren. Wenn Sie in Ihrer Hierarchie MaterialTheme verwenden, wird Ripple als Standard-Indication in Modifikatoren wie clickable und indication verwendet.

In den meisten Fällen können Sie den standardmäßigen Ripple verwenden. Wenn Sie die Darstellung konfigurieren möchten, können Sie mit RippleTheme Eigenschaften wie Farbe und Alpha ändern.

Sie können RippleTheme erweitern und die Dienstprogrammfunktionen defaultRippleColor und defaultRippleAlpha nutzen. Anschließend können Sie mit LocalRippleTheme ein benutzerdefiniertes Wellenthema in Ihrer Hierarchie angeben:

@Composable
fun MyApp() {
    MaterialTheme {
        CompositionLocalProvider(
            LocalRippleTheme provides SecondaryRippleTheme
        ) {
            // App content
        }
    }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor() = RippleTheme.defaultRippleColor(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )

    @Composable
    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )
}

Alt-Text

Abbildung 14: Schaltflächen mit unterschiedlichen Wellenwerten über RippleTheme.

Weitere Informationen

Weitere Informationen zu Material Design in Compose finden Sie in den folgenden zusätzlichen Ressourcen.

Codelabs

Videos