Material Design 2 in Compose

Jetpack Compose bietet eine Implementierung von Material Design, einem umfassenden Designsystem zum Erstellen digitaler Benutzeroberflächen. Die Material Design-Komponenten (Schaltflächen, Karten, Schalter usw.) basieren auf Material Theming. Damit lässt sich Material Design systematisch an das Branding Ihres Produkts anpassen. Ein Material-Theme enthält die Attribute Farbe, Typografie und Form. Wenn Sie diese Attribute anpassen, werden Ihre Änderungen automatisch in den Komponenten übernommen, mit denen Sie Ihre App erstellen.

In Jetpack Compose werden diese Konzepte mit der MaterialTheme-Composable-Funktion implementiert:

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

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

Zwei Screenshots mit Kontrast. Auf dem ersten Screenshot wird der Standardstil von MaterialTheme verwendet, auf dem zweiten ein modifizierter Stil.
Abbildung 1. Der erste Screenshot zeigt eine App, in der „MaterialTheme“ nicht konfiguriert ist. Daher wird die Standardformatierung verwendet. Der zweite Screenshot zeigt eine App, die Parameter an `MaterialTheme` übergibt, um das Styling anzupassen.

Farbe

Farben werden in Compose mit der Klasse Color modelliert, einer Klasse, die Daten enthält.

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

Sie können diese beliebig organisieren (als Konstanten der obersten Ebene, in einem Singleton oder inline definiert). Wir empfehlen jedoch dringend, Farben in Ihrem Theme anzugeben und von dort abzurufen. Dieser Ansatz ermöglicht die Unterstützung von dunklem Design und verschachtelten Designs.

Beispiel für die Farbpalette eines Designs
Abbildung 2: Das Material-Farbsystem.

Compose bietet die Klasse Colors, um das Material-Farbsystem zu modellieren. Colors bietet Builder-Funktionen zum Erstellen von Sets mit 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 Ihre Colors definiert haben, können Sie sie an ein MaterialTheme übergeben:

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

Designfarben verwenden

Sie können den Colors-Wert, der der MaterialTheme-komponierbaren Funktion bereitgestellt wird, mit MaterialTheme.colors abrufen.

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

Oberflächen- und Inhaltsfarbe

Viele Komponenten akzeptieren ein Paar aus Farbe und Inhaltsfarbe:

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

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

So können Sie nicht nur die Farbe eines Composables festlegen, sondern auch eine Standardfarbe für den Inhalt, also die darin enthaltenen Composables, angeben. Viele Composables verwenden diese Inhaltsfarbe standardmäßig. Text basiert beispielsweise auf der Inhaltsfarbe des übergeordneten Elements und Icon verwendet diese Farbe, um die Tönung festzulegen.

Zwei Beispiele für dasselbe Banner mit unterschiedlichen Farben
Abbildung 3: Wenn Sie verschiedene Hintergrundfarben festlegen, werden auch verschiedene Text- und Symbolfarben verwendet.

Mit der Methode contentColorFor() wird die passende „on“-Farbe für alle Designfarben abgerufen. Wenn Sie beispielsweise eine primary-Hintergrundfarbe 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 geeignete Inhaltsfarbe angeben. Mit LocalContentColor können Sie die bevorzugte Inhaltsfarbe für den aktuellen Hintergrund an einer bestimmten Position in der Hierarchie abrufen.

Inhalts-Alpha

Oft möchten Sie die Betonung von Inhalten variieren, um ihre Wichtigkeit zu kommunizieren und eine visuelle Hierarchie zu schaffen. Die Empfehlungen zur Lesbarkeit von Text in Material Design raten dazu, unterschiedliche Deckkraftgrade zu verwenden, um verschiedene Wichtigkeitsstufen zu vermitteln.

In Jetpack Compose wird dies mit LocalContentAlpha implementiert. Sie können ein Inhalts-Alpha für eine Hierarchie angeben, indem Sie einen Wert für dieses CompositionLocal angeben. Verschachtelte Composables können diesen Wert verwenden, um die Alpha-Behandlung auf ihre Inhalte anzuwenden. Text und Icon verwenden standardmäßig die Kombination aus LocalContentColor, die für die Verwendung von LocalContentAlpha angepasst wurde. Das Material gibt einige Standard-Alphawerte (high, medium, disabled) an, die durch das ContentAlpha-Objekt 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 unter Lokal begrenzte Daten mit CompositionLocal.

Screenshot eines Artikeltitels mit verschiedenen Textformatierungen
Abbildung 4: Wenden Sie verschiedene Hervorhebungsstufen 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 MaterialTheme-Composable unterschiedliche Sets von Colors bereitstellen:

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

In diesem Beispiel wird MaterialTheme in einer eigenen zusammensetzbaren Funktion umschlossen, die einen Parameter akzeptiert, der angibt, ob ein dunkles Design verwendet werden soll oder nicht. In diesem Fall ruft die Funktion den Standardwert für darkTheme ab, indem sie die Gerätedesign-Einstellung abfragt.

Mit diesem Code 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 Erhebungs-Overlays, die ihren Hintergrund aufhellen. Je höher die Erhebung einer Oberfläche ist (wodurch sie sich einer impliziten Lichtquelle nähert), desto heller wird sie.

Das Surface-Composable wendet diese Overlays automatisch an, wenn dunkle Farben verwendet werden. Das gilt auch für alle anderen Material-Composables, die eine Oberfläche verwenden:

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

Screenshot einer App, auf dem die leicht unterschiedlichen Farben für Elemente auf verschiedenen Ebenen zu sehen sind
Abbildung 5. Die Karten und die untere Navigation verwenden beide die Farbe surface als Hintergrund. Da sich die Karten und die untere Navigation auf unterschiedlichen Ebenen über dem Hintergrund befinden, haben sie leicht unterschiedliche Farben. Die Karten sind heller als der Hintergrund und die untere Navigation ist heller als die Karten.

Für benutzerdefinierte Szenarien, die kein Surface-Element enthalten, verwenden Sie LocalElevationOverlay, ein CompositionLocal-Element, das das ElevationOverlay-Element enthält, das von 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
)

Wenn Sie Höhen-Overlays deaktivieren möchten, geben Sie null an der gewünschten Stelle in einer zusammensetzbaren Hierarchie an:

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

Begrenzte Farbakzente

Material empfiehlt, für dunkle Designs begrenzte Farbakzente zu verwenden und in den meisten Fällen die Farbe surface der Farbe primary vorzuziehen. Material-Composables wie TopAppBar und BottomNavigation implementieren dieses Verhalten standardmäßig.

Screenshot eines dunklen Material-Designs, in dem die obere App-Leiste die Oberflächenfarbe anstelle der Primärfarbe für begrenzte Farbakzente verwendet
Abbildung 6: Dunkles Material-Design mit begrenzten Farbakzenten. In der oberen App-Leiste wird im hellen Design die Primärfarbe und im dunklen Design die Oberflächenfarbe verwendet.

Verwenden Sie für benutzerdefinierte Szenarien das Erweiterungsattribut primarySurface:

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

Typografie

Material definiert ein Typsystem, das Sie dazu anregt, eine kleine Anzahl semantisch benannter Stile zu verwenden.

Beispiel für mehrere verschiedene Schriftarten in verschiedenen Stilen
Abbildung 7. Das System für Materialtypen.

In Compose wird das Typsystem mit den Klassen Typography, TextStyle und font-related implementiert. Der Typography-Konstruktor bietet Standardwerte für jeden Stil, sodass Sie alle 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 durchgehend dieselbe Schriftart verwenden möchten, geben Sie den Parameter defaultFontFamily an und lassen Sie das fontFamily aller TextStyle-Elemente weg:

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

Textstile verwenden

Auf TextStyle-Elemente wird mit MaterialTheme.typography zugegriffen. So rufen Sie die TextStyle-Elemente ab:

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

Screenshot mit einer Mischung aus verschiedenen Schriftarten für unterschiedliche Zwecke
Abbildung 8. Verwenden Sie eine Auswahl an Schriftarten und ‑formatierungen, um Ihre Marke zu präsentieren.

Form

Mit „Material“ wird ein Formsystem definiert, mit dem Sie Formen für große, mittelgroße und kleine Komponenten festlegen können.

Hier sind verschiedene Material Design-Formen zu sehen.
Abbildung 9. Das Material-Form-System.

In Compose wird das Formsystem mit der Klasse Shapes implementiert. Damit können Sie für jede Größenkategorie ein CornerBasedShape angeben:

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. Beispielsweise sind Button, TextField und FloatingActionButton standardmäßig „small“ (klein), AlertDialog standardmäßig „medium“ (mittel) und ModalDrawer standardmäßig „large“ (groß). Die vollständige Zuordnung finden Sie in der Referenz zum Formschema.

Formen verwenden

Auf Shape-Elemente wird mit MaterialTheme.shapes zugegriffen. Rufen Sie die Shape-Elemente mit Code wie diesem ab:

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

Screenshot einer App, in der Material-Formen verwendet werden, um den Status eines Elements zu vermitteln
Abbildung 10. Verwenden Sie Formen, um die Marke oder den Status auszudrücken.

Standardstile

In Compose gibt es kein Äquivalent zu Standardformatierungen aus Android-Ansichten. Sie können ähnliche Funktionen bereitstellen, indem Sie eigene zusammensetzbare overload-Funktionen erstellen, die Material-Komponenten umschließen. Wenn Sie beispielsweise einen Buttonstil erstellen möchten, umschließen Sie einen Button mit einer eigenen zusammensetzbaren Funktion, legen Sie die Parameter, die Sie ändern möchten, direkt fest und machen Sie andere als Parameter für die enthaltende zusammensetzbare Funktion verfügbar.

@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 Themen-Overlays aus Android-Ansichten in Compose erreichen, indem Sie MaterialTheme-Composables verschachteln. Da mit MaterialTheme die Farben, Typografie und Formen auf den aktuellen Designwert festgelegt werden, behalten alle anderen Parameter ihre Standardwerte bei, wenn in einem Design nur einer dieser Parameter festgelegt wird.

Achten Sie außerdem bei der Migration von View-basierten Bildschirmen zu Compose auf die Verwendung des Attributs android:theme. Wahrscheinlich benötigen Sie in diesem Teil des Compose-UI-Baums ein neues MaterialTheme.

In diesem Beispiel wird für den Großteil des Bildschirms ein PinkTheme und für den Abschnitt „Ähnliche Inhalte“ ein BlueTheme verwendet. Der folgende Screenshot und Code veranschaulichen dieses Konzept:

Screenshot einer App mit verschachtelten Designs: Das Hauptmenü hat ein pinkes Design, ein zugehöriger Bereich ein blaues Design.
Abbildung 11. Verschachtelte Themen.

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

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

Komponentenzustände

Material-Komponenten, mit denen interagiert werden kann (z. B. durch Klicken oder Umschalten), können sich in verschiedenen visuellen Zuständen befinden. Mögliche Status sind „Aktiviert“, „Deaktiviert“, „Gedrückt“ usw.

Composables haben oft einen enabled-Parameter. Wenn Sie den Wert auf false festlegen, wird die Interaktion verhindert und Eigenschaften wie Farbe und Höhe werden geändert, um den Komponentenstatus visuell darzustellen.

Screenshot von zwei Schaltflächen: eine aktiviert, eine deaktiviert, mit unterschiedlichen visuellen Status
Abbildung 12. Schaltfläche mit enabled = true (links) und enabled = false (rechts)

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

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
    )
) { /* ... */ }

Screenshot von zwei Schaltflächen mit angepasster Farbe und Erhebung für aktivierte und deaktivierte Status
Abbildung 13. Schaltfläche mit enabled = true (links) und enabled = false (rechts) mit angepassten Farb- und Erhebungswerten.

Kreise im Wasser

Material-Komponenten verwenden Welleneffekte, um anzuzeigen, dass mit ihnen interagiert wird. Wenn Sie MaterialTheme in Ihrer Hierarchie verwenden, wird Ripple als Standard-Indication in Modifikatoren wie clickable und indication verwendet.

In den meisten Fällen können Sie sich auf die Standardeinstellung Ripple verlassen. Wenn Sie das Aussehen der Markierungen konfigurieren müssen, können Sie mit RippleTheme Eigenschaften wie Farbe und Alpha ändern.

Sie können RippleTheme erweitern und die Hilfsfunktionen defaultRippleColor und defaultRippleAlpha verwenden. Anschließend können Sie Ihr benutzerdefiniertes Ripple-Design in Ihrer Hierarchie mit LocalRippleTheme bereitstellen:

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

Animiertes GIF, das Schaltflächen mit unterschiedlichen Welleneffekten beim Tippen zeigt
Abbildung 14. Schaltflächen mit unterschiedlichen Ripple-Werten, die mit RippleConfiguration
bereitgestellt werden.

Weitere Informationen

Weitere Informationen zum Material-Theming in Compose finden Sie in den folgenden zusätzlichen Ressourcen.

Codelabs

Videos