Material Design 2 in Compose

Jetpack Compose bietet eine Implementierung von Material Design, einem umfassenden Designsystem zum Erstellen digitaler Oberflächen. Die Material Design-Komponenten (Schaltflächen, Karten, Schalter usw.) basieren auf Material Theming, einer systematischen Methode, um Material Design so anzupassen, dass es die Marke Ihres Produkts besser widerspiegelt. Ein Material Theme enthält die Attribute Farbe, Typografie und Form. Wenn Sie diese Attribute anpassen, werden Ihre Änderungen automatisch in den Komponenten übernommen, die Sie zum Erstellen Ihrer App 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 festzulegen.

Zwei Screenshots im Vergleich. Der erste Screenshot zeigt das Standard-MaterialTheme-Styling, der zweite Screenshot ein modifiziertes Styling.
Abbildung 1. Der erste Screenshot zeigt eine App, die „MaterialTheme“ nicht konfiguriert und daher das Standard-Styling 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)

Du kannst diese nach Belieben organisieren (als Konstanten auf oberster Ebene, innerhalb eines Singletons oder inline definiert). Wir empfehlen jedoch dringend, Farben in deinem Design anzugeben und die Farben von dort abzurufen. Dieser Ansatz ermöglicht die Unterstützung des dunklen Designs und verschachtelter Designs.

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

Compose stellt die Klasse Colors bereit, um das Material-Farbsystem zu modellieren. Colors bietet Builder-Funktionen zum Erstellen von Sätzen 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 Ihre 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 den Colors, der der zusammensetzbaren Funktion MaterialTheme 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 kannst du nicht nur die Farbe eines Composables festlegen, sondern auch eine Standardfarbe für den Inhalt,also die darin enthaltenen Composables. Viele Composables verwenden diese Inhaltsfarbe standardmäßig. Beispielsweise basiert die Farbe von Text 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 du verschiedene Hintergrundfarben festlegst, werden unterschiedliche Text- und Symbolfarben erzeugt.

Die Methode contentColorFor() ruft die entsprechende „On“-Farbe für alle Designfarben ab. Wenn Sie beispielsweise die 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. Mit LocalContentColor kannst du 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 die Wichtigkeit zu kommunizieren und eine visuelle Hierarchie bereitzustellen. In den Empfehlungen zur Lesbarkeit von Texten im Material Design wird empfohlen, verschiedene Deckkraftstufen zu verwenden, um unterschiedliche Wichtigkeitsstufen zu vermitteln.

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

Screenshot eines Artikeltitels mit verschiedenen Hervorhebungsstufen
Abbildung 4: Wenden Sie verschiedene Hervorhebungsebenen auf Text an, um die Informationshierarchie visuell zu kommunizieren. 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 dem MaterialTheme-Composable verschiedene Sätze von Colors zur Verfügung stellen:

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

In diesem Beispiel ist MaterialTheme in einer eigenen zusammensetzbaren Funktion verpackt, 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 Design-Einstellung des Geräts abfragt.

Mit folgendem 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 Höhen Höhen-Overlays, die ihren Hintergrund aufhellen. Je höher die Erhebung einer Oberfläche ist (je näher sie an einer implizierten Lichtquelle liegt), desto heller wird diese Oberfläche.

Die zusammensetzbare Funktion Surface wendet diese Overlays automatisch an, wenn dunkle Farben verwendet werden. Das gilt auch für alle anderen zusammensetzbaren Material-Funktionen, 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 Höhenstufen zu sehen sind
Abbildung 5. Die Karten und die untere Navigationsleiste verwenden beide die Farbe surface als Hintergrund. Da sich die Karten und die untere Navigationsleiste auf unterschiedlichen Höhenebenen ü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.

Verwenden Sie für benutzerdefinierte Szenarien, die keinen Surface beinhalten, LocalElevationOverlay, ein CompositionLocal, das die von Surface-Komponenten verwendete ElevationOverlay enthält:

// 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 am ausgewählten Punkt 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. In den meisten Fällen sollte die Farbe surface der Farbe primary vorgezogen werden. Material-Kompositionen wie TopAppBar und BottomNavigation implementieren dieses Verhalten standardmäßig.

Screenshot eines dunklen Material-Designs, bei dem die obere App-Leiste die Oberflächenfarbe anstelle der Hauptfarbe 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 von semantisch benannten Stilen zu verwenden.

Beispiel für mehrere verschiedene Schriftbilder in verschiedenen Stilen
Abbildung 7. Das Material-Typensystem.

Compose implementiert das Typsystem mit den Klassen Typography, TextStyle und schriftbezogenen Klassen. 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 den Parameter defaultFontFamily an und lassen Sie das fontFamily-Element aller TextStyle-Elemente weg:

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

Textstile verwenden

Auf TextStyle-Elemente wird mit MaterialTheme.typography zugegriffen. Rufen Sie die TextStyle-Elemente so 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 -stilen, um Ihre Marke zu präsentieren.

Form

Material definiert ein Formensystem, mit dem Sie Formen für große, mittlere und kleine Komponenten definieren können.

Zeigt verschiedene Material Design-Formen
Abbildung 9. Das Material-Formensystem.

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. Beispielsweise sind Button, TextField und FloatingActionButton standardmäßig klein, AlertDialog ist standardmäßig mittelgroß und ModalDrawer ist standardmäßig 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 folgendem Code ab:

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

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

Standardstile

In Compose gibt es kein Äquivalent zu den Standardstilen aus Android-Ansichten. Sie können eine ähnliche Funktionalität bereitstellen, indem Sie Ihre eigenen overload komponierbaren Funktionen erstellen, die Material-Komponenten umschließen. Wenn Sie beispielsweise einen bestimmten Schaltflächenstil erstellen möchten, können Sie eine Schaltfläche in Ihre eigene zusammensetzbare Funktion einbinden. Legen Sie dabei die Parameter, die Sie ändern möchten oder müssen, direkt fest und stellen Sie andere als Parameter für die enthaltende 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

In Compose können Sie das Äquivalent von Design-Overlays aus Android-Ansichten erreichen, indem Sie MaterialTheme-Kompositionen verschachteln. Da MaterialTheme die Farben, Typografie und Formen standardmäßig auf den aktuellen Designwert setzt, behalten alle anderen Parameter ihre Standardwerte, wenn ein Design nur einen dieser Parameter festlegt.

Achten Sie außerdem bei der Migration von ansichtsbasierten 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 auf dem Detailbildschirm für den Großteil des Bildschirms ein PinkTheme und für den Abschnitt „Ähnlich“ ein BlueTheme verwendet. Der folgende Screenshot und Code veranschaulichen dieses Konzept:

Screenshot einer App mit verschachtelten Designs, mit einem pinken Design für den Hauptbildschirm und einem blauen Design für einen zugehörigen Bereich
Abbildung 11. Verschachtelte Designs.

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

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

Komponentenzustände

Material-Komponenten, mit denen interagiert werden kann (durch Klicken, Ein-/Ausschalten usw.), können sich in verschiedenen visuellen Zuständen befinden. Mögliche Status sind „aktiviert“, „deaktiviert“, „gedrückt“ usw.

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

Screenshot von zwei Buttons: einer aktiviert, einer deaktiviert, mit ihren unterschiedlichen visuellen Zuständen
Abbildung 12. Taste mit enabled = true (links) und enabled = false (rechts).

In den meisten Fällen können Sie sich auf die Standardwerte für Werte wie Farbe und Höhe verlassen. Wenn Sie Werte konfigurieren müssen, die in verschiedenen Status verwendet werden, stehen Klassen und praktische Funktionen zur Verfügung. Betrachten Sie das folgende 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
    )
) { /* ... */ }

Screenshot von zwei Buttons mit angepasster Farbe und Elevation für aktivierte und deaktivierte Status
Abbildung 13. Button mit enabled = true (links) und enabled = false (rechts) mit angepassten Farb- und Höhenwerten

Kreise im Wasser

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

In den meisten Fällen können Sie sich auf den Standardwert Ripple verlassen. Wenn Sie das Erscheinungsbild konfigurieren möchten, können Sie mit RippleTheme Eigenschaften wie Farbe und Alpha ändern.

Sie können RippleTheme erweitern und die Dienstfunktionen defaultRippleColor und defaultRippleAlpha verwenden. Anschließend können Sie Ihr benutzerdefiniertes Ripple-Design mit LocalRippleTheme in Ihrer Hierarchie 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 verschiedenen Welleneffekten beim Antippen zeigt
Abbildung 14. Schaltflächen mit unterschiedlichen Ripple-Werten, die mit RippleConfiguration bereitgestellt werden.

Weitere Informationen

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

Codelabs

Videos