Shapes und Elevation in Jetpack Compose
Lerne, wie du mit Shapes und Elevation in Jetpack Compose visuelle Hierarchien, Gruppierungen und Tiefe für moderne Android-UIs aufbaust.
In modernen Android-Apps strukturieren visuelle Elemente die Bedienoberfläche und führen das Auge des Nutzers sicher durch komplexe Workflows. Shapes (Formen) und Elevation (Erhebung oder Tiefe) sind die entscheidenden Werkzeuge, um eine klare visuelle Hierarchie zu etablieren. Sie helfen dabei, zusammengehörige Informationen zu gruppieren, Interaktionsflächen deutlich hervorzuheben und die räumliche Anordnung von Elementen auf dem Bildschirm verständlich zu machen. Wenn Nutzer instinktiv wissen, wo sie tippen können und welche Datenblöcke zusammengehören, steigt die Bedienbarkeit der Anwendung enorm. Jetpack Compose bietet dir ein deklaratives und stark typisiertes System, mit dem du diese Konzepte konsistent, plattformgerecht und präzise umsetzen kannst.
Was ist das?
Shapes definieren die geometrische Form und äußere Kontur von UI-Komponenten. In den meisten täglichen Anwendungsfällen handelt es sich dabei um abgerundete Ecken bei Karten, Buttons oder Dialogen, aber auch vollständige Kreise für Profilbilder oder asymmetrische Formen für spezielle Design-Akzente fallen direkt in diese Kategorie. Sie dienen in erster Linie dazu, die harten, technischen Kanten eines Bildschirms aufzubrechen und funktionale Blöcke optisch angenehm voneinander zu trennen. Die Formsprache einer App trägt maßgeblich zu ihrer Markenidentität bei; weiche Rundungen wirken oft nahbarer, während scharfe Kanten einen strengeren, formaleren Charakter vermitteln.
Elevation beschreibt hingegen den virtuellen Abstand eines Elements entlang der Z-Achse, also die rechnerische Entfernung zur Basis-Hintergrundebene des Geräts. Wenn eine Komponente eine höhere Elevation aufweist, wirft sie einen physisch simulierten Schatten auf die darunterliegenden Ebenen. Dies erzeugt eine greifbare räumliche Tiefe und signalisiert dem Nutzer unmissverständlich, welche Flächen über anderen liegen. In der Material Design-Sprache ist dieses Konzept tief verwurzelt und simuliert physikalisches Papier. Eine schwebende Aktionsschaltfläche (Floating Action Button) liegt beispielsweise immer deutlich höher als der normale Textinhalt der Seite, weil sie eine globale, primäre Aktion darstellt und nicht beim vertikalen Scrollen unter Inhalten verschwinden darf.
Zusammen bilden Shapes und Elevation ein kohärentes System für Oberflächen (Surfaces). Jede sichtbare Komponente existiert auf einer solchen Oberfläche, die wiederum eine spezifische Form und Höhe besitzt, um ihre relative Wichtigkeit, ihren Status und ihren Kontext im Gesamtlayout zu verdeutlichen.
Wie funktioniert es?
In Jetpack Compose wird die Kombination aus Hintergrundfarbe, Form und Erhebung zentral über das Surface-Composable oder entsprechende spezialisierte Modifier gesteuert. Das Surface-Composable ist der fundamentale Baustein für alle Material Design-Oberflächen. Es übernimmt nicht nur das Zeichnen des Hintergrunds, sondern trägt auch die Verantwortung dafür, dass Textfarben und Icon-Farben automatisch an die jeweilige Hintergrundfarbe angepasst werden (die sogenannte Inhaltsfarbe oder Content Color). Weiterhin stellt es sicher, dass Schatten oder Tonwertanpassungen korrekt nach den offiziellen Design-Richtlinien berechnet werden.
Die exakte Form einer Komponente übergibst du über den shape-Parameter. Compose stellt dir verschiedene vordefinierte Formen wie RoundedCornerShape, CircleShape oder CutCornerShape zur Verfügung. Gleichzeitig erlaubt die Architektur auch die Definition äußerst komplexer, eigener Geometrien über die abstrakte Shape-Schnittstelle, falls dein Designteam sehr individuelle Umrisse fordert.
Die Elevation wird in Compose in zwei wesentlichen Ausprägungen gehandhabt, was besonders dann relevant ist, wenn du das moderne Material 3 Designsystem verwendest: Shadow Elevation und Tonal Elevation. Shadow Elevation erzeugt den klassischen, berechneten Schattenwurf unterhalb und um ein Element herum, der von einer fiktiven Lichtquelle abhängig ist. Tonal Elevation hingegen verändert leicht den eigentlichen Farbton der Oberfläche, anstatt nur einen Schatten zu werfen. Im dunklen Modus (Dark Mode) ist ein klassischer schwarzer Schatten auf einem ohnehin schwarzen oder sehr dunklen Hintergrund kaum bis gar nicht sichtbar. Daher wird bei einer höheren Tonal Elevation die Hintergrundfarbe der Komponente minimal mit der primären Akzentfarbe aufgehellt oder gemischt, um die Erhebung visuell zu kommunizieren, ohne auf unsichtbare Schatten angewiesen zu sein. Beide Parameter – shadowElevation und tonalElevation – akzeptieren Werte in der Maßeinheit Dp (Density-independent Pixels).
Die visuelle Hierarchie wird dabei direkt durch den geschachtelten Komponentenbaum abgebildet. Eine verschachtelte Kind-Komponente erbt nicht automatisch die Elevation des Eltern-Elements, aber sie befindet sich räumlich absolut auf deren Oberfläche. Du musst als Entwickler sorgfältig planen und orchestrieren, welche Elemente durch eigene Oberflächen und eigene Erhebungen vom generischen Hintergrund abgehoben werden.
Zusätzlich verändern interaktive Komponenten ihre Elevation oft dynamisch, um direktes Feedback zu geben. Ein Button, der vom Nutzer aktiv gedrückt wird, senkt sich typischerweise physisch ab – seine Elevation verringert sich temporär, um das Hineindrücken in die Material-Oberfläche zu simulieren. Sobald der Finger gelöst wird, federt das Element auf seine ursprüngliche Höhe zurück. Dies erreichst du in Compose am elegantesten über Interaktionsquellen (InteractionSource), welche den Status kontinuierlich auswerten. In Kombination mit Funktionen wie animateDpAsState kannst du weiche, organische Übergänge für diese Höhenänderungen programmieren, was die wahrgenommene Wertigkeit der App spürbar steigert.
In der Praxis
Der typischste und am häufigsten anzutreffende Anwendungsfall für Shapes und Elevation ist die Strukturierung von Listen-Elementen oder das Design von Karten (Cards). Eine Karte fasst logisch zusammengehörige Daten zu einer visuellen Einheit zusammen und hebt diese klar vom generischen Hintergrund der Applikation ab.
Hier ist ein konkretes, praxisnahes Beispiel in Kotlin, das dir demonstriert, wie du eine Karte mit abgerundeten Ecken, dynamischem Inhalt und einer leichten Erhebung in Jetpack Compose definierst:
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@Composable
fun ArticleHighlightCard(title: String, summary: String, modifier: Modifier = Modifier) {
// Die Surface bildet die physische Grundlage und liefert Farbe, Form und Schatten.
Surface(
modifier = modifier.padding(8.dp).fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
shadowElevation = 4.dp,
tonalElevation = 2.dp,
color = MaterialTheme.colorScheme.surface
) {
// Innerhalb der Surface ordnen wir die Elemente vertikal an.
Column {
// Ein Bild im Kopf der Karte. Wenn das Bild den oberen Rand berührt,
// sollte es zur Sicherheit explizit beschnitten (clipped) werden.
Image(
painter = painterResource(id = android.R.drawable.ic_menu_gallery),
contentDescription = "Artikelbild",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp))
)
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = summary,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
Eine typische, in Code-Reviews oft anzutreffende Stolperfalle in der Praxis ist die inkonsistente und fehleranfällige Nutzung von Basis-Modifiern im direkten Vergleich zum höherwertigen Surface-Composable. Du kannst zwar rein technisch einen Modifier wie Modifier.shadow(elevation = 4.dp, shape = RoundedCornerShape(8.dp)) auf ein einfaches Box- oder Column-Element anwenden, um den gleichen optischen Schatten zu erzeugen. Wenn du jedoch ein standardisiertes Material Design einsetzt, fehlen dir bei diesem manuellen Ansatz gravierende Funktionalitäten: Die automatischen Berechnungen für Tonal Elevation im Dark Mode greifen nicht, und die korrekte Bestimmung der passenden Schriftfarbe über LocalContentColor findet nicht statt. Für interaktive oder strukturell hervorgehobene Container solltest du daher immer strikt bevorzugt das Surface- oder das spezifische Card-Composable verwenden.
Ein weiterer Fehler ist der völlig übermäßige Einsatz von Erhebungen. Wenn buchstäblich jedes Element auf dem Bildschirm einen Schatten wirft, geht die intendierte visuelle Hierarchie vollständig verloren. Das resultierende Layout wirkt chaotisch und unruhig. Reserviere hohe Elevation-Werte (wie 8.dp oder 16.dp) ausschließlich für zwingend überlagernde Elemente wie modale Dialoge, verschiebbare Bottom Sheets oder persistente schwebende Buttons. Flache, in das Layout eingebettete Container sollten in der Regel nur minimale Schatten erhalten. Nutze stattdessen subtile, abweichende Hintergrundfarben, großzügige Weißraum-Abstände oder sehr dezente Rahmen (Borders), um Gruppierungen elegant zu verdeutlichen, ohne die GPU durch komplexe Schattenberechnungen zu belasten.
Fazit
Shapes und Elevation strukturieren deine App-Architektur auf der Präsentationsebene und geben dem Endnutzer kontinuierlich unterbewusste Hinweise zur richtigen Bedienung. Du trennst komplexe Informationen durch sanfte Formen und signalisierst interaktive Wichtigkeit oder Überlagerungen präzise durch simulierte Tiefe. Prüfe deine implementierten Layouts regelmäßig auf echten physischen Geräten – wechsle dabei unbedingt wiederholt zwischen dem hellen und dunklen System-Modus, um absolut sicherzustellen, dass die Tonal Elevation korrekt greift und feine Schatten nicht im tiefen Schwarz des Displays unsichtbar werden. Überprüfe bei anstehenden Code-Reviews deines Teams rigoros, ob für UI-Container konsistent das Surface-Composable anstelle von isolierten Schatten-Modifiern verwendet wurde, um langfristig die Theme-Kompatibilität zu garantieren.