Menüs in Jetpack Compose: Overflow und Kontext-Aktionen
Lerne, wie du Menüs in Compose erstellst, um sekundäre Aktionen zugänglich zu machen, ohne die Benutzeroberfläche zu überladen.
Eine gute Benutzeroberfläche zeichnet sich dadurch aus, dass sie dem Nutzer genau die Optionen bietet, die er im aktuellen Kontext benötigt, ohne ihn mit Elementen zu erdrücken. Menüs spielen hierbei eine zentrale Rolle, da sie sekundäre Handlungen intelligent verbergen und bei Bedarf sofort verfügbar machen. In der modernen Android-Entwicklung mit Jetpack Compose strukturierst du solche kontextuellen Aktionen nahtlos und zustandsgesteuert. Das Ziel ist stets die richtige Balance zwischen aufgeräumtem Design und guter Auffindbarkeit der Funktionen.
Was ist das?
Menüs sind temporäre Oberflächenelemente, die eine Liste von Optionen oder Aktionen anzeigen. In der Android-Entwicklung unterscheiden wir primär zwischen zwei Konzepten: dem Overflow-Menü und kontextuellen Aktionen. Das Overflow-Menü befindet sich meistens in der oberen App-Leiste (TopAppBar) und sammelt jene Funktionen, die für den globalen Bildschirm wichtig sind, aber nicht permanent sichtbar sein müssen. Beispiele hierfür sind Einstellungen, Hilfe-Seiten oder Sortierfunktionen. Kontextuelle Aktionen beziehen sich hingegen auf spezifische Elemente innerhalb deiner Ansicht, wie etwa ein einzelnes Listenelement, das du löschen, bearbeiten oder teilen möchtest.
Der Einsatz von Menüs löst ein klassisches Platzproblem auf mobilen Bildschirmen. Würdest du jede mögliche Aktion als eigenen Button auf dem Display platzieren, entstünde ein visuelles Chaos. Der Nutzer wäre überfordert und wüsste nicht, wo er zuerst klicken soll. Menüs bündeln diese Interaktionsmöglichkeiten an definierten, erwartbaren Orten.
Ein entscheidender Faktor bei der Arbeit mit Menüs ist die sogenannte Discoverability, also die Auffindbarkeit von Funktionen. Wenn du eine Funktion in einem Menü versteckst, machst du den Bildschirm zwar sauberer, aber du erhöhst gleichzeitig die kognitive Last für den Nutzer. Er muss wissen oder erahnen, dass sich hinter dem Drei-Punkte-Icon weitere Optionen verbergen. Daher gilt als goldene Regel: Primäre Kernaktionen, die der Nutzer ständig braucht, gehören direkt auf den Bildschirm. Sekundäre, seltener genutzte Aktionen wandern in das Overflow-Menü. Durch diese bewusste Trennung schaffst du eine klare visuelle Hierarchie, die deine App intuitiv bedienbar macht.
Wie funktioniert es?
In der klassischen View-basierten Entwicklung von Android wurdest du oft mit XML-Ressourcen für Menüs, Inflatern und überschriebenen Activity-Methoden konfrontiert. Jetpack Compose macht diesen Prozess deutlich direkter und deklarativer. Da UI-Komponenten in Compose Funktionen sind, die auf Zustandsänderungen reagieren, steuerst du auch ein Menü über einfache State-Variablen.
Das mentale Modell für ein Menü in Compose besteht aus drei Komponenten: einem Anker-Element, einem Zustand und dem eigentlichen Menü-Inhalt. Das Anker-Element ist der Button oder das Icon, auf das der Nutzer tippt, um das Menü zu öffnen. Der Zustand ist meist ein einfacher Boolean-Wert, der angibt, ob das Menü aktuell sichtbar (expanded) ist oder nicht. Der Menü-Inhalt definiert sich durch das Composable DropdownMenu sowie dessen Einträge in Form von DropdownMenuItem.
Sobald der Nutzer auf den Anker tippt, änderst du den Zustand auf true. Compose registriert diese Zustandsänderung, löst eine Recomposition aus und rendert das DropdownMenu. Ein großer Vorteil von Compose ist, dass sich das DropdownMenu automatisch relativ zu seinem Parent-Element (dem Anker) positioniert. Du musst keine komplexen Berechnungen für X- und Y-Koordinaten anstellen.
Darüber hinaus integrieren sich Menüs hervorragend in die asynchrone Welt von Kotlin. Wenn sich die Optionen deines Menüs dynamisch ändern sollen – beispielsweise weil bestimmte Aktionen nur für bestimmte Benutzerrollen sichtbar sind oder Daten erst aus dem Netzwerk geladen werden müssen –, kannst du diese Logik elegant über Kotlin Flow und Coroutines aus deinem ViewModel steuern. Das Menü beobachtet den StateFlow und passt seine Einträge automatisch an, sobald der Flow neue Daten emittiert. So bleibt deine Benutzeroberfläche stets synchron mit der zugrundeliegenden Geschäftslogik, ohne dass du manuelle UI-Updates schreiben musst.
In der Praxis
Schauen wir uns an, wie du ein klassisches Overflow-Menü in einer oberen App-Leiste implementierst. Wir nutzen dafür eine Kombination aus State-Management und den Standard-Komponenten von Jetpack Compose. Gleichzeitig binden wir eine Coroutine ein, um eine asynchrone Aktion zu simulieren, die nach dem Klick auf ein Menüelement ausgeführt wird.
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScreenWithMenu() {
// Zustand für die Sichtbarkeit des Menüs
var expanded by remember { mutableStateOf(false) }
// CoroutineScope für asynchrone Aktionen aus dem UI-Thread
val coroutineScope = rememberCoroutineScope()
var statusText by remember { mutableStateOf("Warte auf Aktion...") }
Scaffold(
topBar = {
TopAppBar(
title = { Text("Meine App") },
actions = {
// Die Box dient als Ankerpunkt für das DropdownMenu
Box {
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Zusätzliche Optionen öffnen"
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text("Daten synchronisieren") },
onClick = {
// 1. Menü schließen
expanded = false
// 2. Aktion starten
coroutineScope.launch {
statusText = "Synchronisiere..."
delay(2000) // Simuliere asynchronen Vorgang
statusText = "Synchronisation abgeschlossen"
}
}
)
DropdownMenuItem(
text = { Text("Einstellungen") },
onClick = {
expanded = false
statusText = "Navigiere zu Einstellungen"
}
)
}
}
}
)
}
) { paddingValues ->
Text(
text = statusText,
modifier = Modifier.padding(paddingValues)
)
}
}
In diesem Praxisbeispiel gibt es einige kritische Punkte, auf die du als Entwickler achten musst. Ein häufiger Fehler ist das Vergessen der Zustandsänderung beim Klick auf ein Menüelement. Wenn du expanded = false im onClick-Lambda weglässt, bleibt das Menü nach der Aktion geöffnet, was den Nutzer frustriert. Das Lambda onDismissRequest kümmert sich nur um Fälle, in denen der Nutzer neben das Menü tippt oder die Zurück-Taste drückt.
Eine weitere typische Stolperfalle ist die Platzierung des Menüs. Das DropdownMenu positioniert sich immer relativ zum direkten Parent-Container. Würdest du die Box weglassen und das Menü direkt auf Root-Ebene der actions definieren, könnte die Positionierung je nach Layout-Kontext unerwartet ausfallen. Die umschließende Box garantiert, dass sich das Menü exakt am Icon verankert.
Zudem solltest du bei der Barrierefreiheit sorgfältig arbeiten. Das contentDescription-Attribut im IconButton ist unerlässlich für Screenreader. Ohne diesen Text weiß ein blinder Nutzer nicht, was sich hinter dem Icon verbirgt und wird das Menü folglich nicht finden können. Achte stets darauf, dass du wesentliche Interaktionselemente korrekt auszeichnest.
Fazit
Menüs in Jetpack Compose sind ein unverzichtbares Werkzeug, um komplexe Benutzeroberflächen übersichtlich zu strukturieren und sekundäre Aktionen sinnvoll zu bündeln. Durch den deklarativen Ansatz und die direkte Kontrolle über den expanded-Zustand lassen sie sich nahtlos in moderne, reaktive Architekturen mit Flow und Coroutines integrieren. Um dein Verständnis zu festigen, experimentiere im Layout Inspector von Android Studio: Beobachte, wie sich das DropdownMenu beim Umschalten des Zustands in der UI-Hierarchie verhält. Schreibe einen einfachen UI-Test, der prüft, ob sich das Menü nach einem Klick auf ein Item tatsächlich schließt. So stellst du sicher, dass deine App nicht nur visuell ansprechend ist, sondern sich auch absolut robust und erwartungskonform bedienen lässt.