Android Coden
Android 8 min lesen

Window Size Classes in Jetpack Compose: Layouts für jedes Display

Lerne, wie du mit Window Size Classes responsive UIs in Jetpack Compose baust, die sich an jede Bildschirmgröße anpassen.

Die Vielfalt an Android-Geräten wächst stetig und stellt Entwickler vor neue Aufgaben. Vom schmalen Smartphone über faltbare Bildschirme bis hin zum großen Tablet oder Chromebook – deine App muss auf all diesen Formfaktoren eine gute Figur machen und eine exzellente Benutzererfahrung bieten. Um diese Herausforderung systematisch zu lösen, stellt Google das Konzept der Window Size Classes bereit. Damit verlässt du den Pfad starrer Pixel-Abfragen und nutzt stattdessen logische Haltepunkte, um deine Layouts strukturiert an die verfügbare Bildschirmfläche anzupassen. Dieses System bildet das Fundament für moderne, adaptive Benutzeroberflächen in Jetpack Compose und ermöglicht es dir, flexiblen und wartbaren Code zu schreiben.

Was ist das?

Window Size Classes sind ein standardisiertes System, um die Bildschirmbreite und -höhe eines Android-Geräts in diskrete, leicht handhabbare Kategorien einzuteilen. Anstatt in deinem Code hunderte möglicher Auflösungen und Pixeldichten abzufangen, arbeitest du mit drei klaren, vordefinierten Breakpoints: Compact, Medium und Expanded. Diese Klassifizierung abstrahiert die reine Hardware und konzentriert sich auf das, was für deine UI wirklich relevant ist: den tatsächlich nutzbaren Raum für deine Applikation.

Diese Einteilung hilft dir dabei, fundamentale architektonische Entscheidungen für deine Benutzeroberfläche zu treffen. Die Klassen repräsentieren nicht zwingend den physischen Gerätetyp. Wenn ein Nutzer deine App auf einem Tablet im Split-Screen-Modus ausführt oder das Fenster auf einem Chromebook verkleinert, ändert sich die verfügbare Breite. Die Window Size Class passt sich in Echtzeit an. Deine App reagiert darauf und kann nahtlos von einem zweispaltigen Tablet-Layout zu einem einspaltigen Smartphone-Layout wechseln, ohne dass du den Status des Betriebssystems im Detail kennen musst.

Das primäre Ziel dieses Systems ist es, den kognitiven Aufwand für responsives Design drastisch zu reduzieren. Du denkst bei der Entwicklung nicht mehr in fixen dp-Werten (density-independent pixels) oder berechnest mühsam Display-Ratios. Stattdessen planst deine Jetpack Compose Layouts explizit für diese drei Szenarien. Das schafft zudem einen einheitlichen mentalen Rahmen für das gesamte Entwicklungsteam und die Designer. Wenn alle im Team von Compact, Medium und Expanded sprechen, gibt es keine Missverständnisse mehr darüber, wie eine Ansicht auf einem bestimmten Endgerät aussehen soll.

Darüber hinaus integrieren sich diese Klassen hervorragend in die offizielle Material Design 3 Spezifikation. Die Richtlinien von Google bauen direkt auf diesen Konzepten auf und geben dir klare Vorgaben, wann du welche Navigationsmuster oder Raster-Layouts verwenden solltest. Du erhältst also nicht nur ein technisches Werkzeug, sondern auch einen Design-Leitfaden, der dir viele Entscheidungen abnimmt.

Wie funktioniert es?

Das Konzept teilt sowohl die Breite (Width) als auch die Höhe (Height) des App-Fensters in die drei genannten Kategorien ein. In den meisten Anwendungsfällen ist die Breite der entscheidende Faktor für Layout-Entscheidungen, da wir auf mobilen Geräten typischerweise vertikal scrollen und der horizontale Platz stark limitiert ist. Die Höhe spielt eher bei speziellen Anforderungen eine Rolle, etwa wenn die Tastatur eingeblendet wird oder das Gerät im extremen Breitbildformat genutzt wird.

Die Unterteilung basiert auf etablierten Breakpoints, die sich in der Praxis bewährt haben:

  • Compact: Diese Klasse steht zumeist für Smartphones im typischen Hochformat (Portrait) oder für sehr schmale Fenster im Split-Screen-Modus. Die verfügbare Breite liegt hier unter 600dp. In diesem Szenario nutzt du typischerweise einspaltige Layouts. Die Navigation erfolgt bevorzugt über eine Bottom Navigation Bar am unteren Bildschirmrand, da diese mit dem Daumen gut erreichbar ist. Modal Bottom Sheets und bildschirmfüllende Dialoge sind hier ebenfalls das Mittel der Wahl.
  • Medium: Dieser Bereich greift bei einer Breite zwischen 600dp und 839dp. Dies umfasst viele Tablets im Hochformat oder große faltbare Geräte (Foldables) im aufgeklappten inneren Zustand. Ein solches Layout bietet oft genügend Platz, um eine Navigation Rail am linken Bildschirmrand zu platzieren, anstelle einer Leiste am unteren Rand. Dadurch gewinnst du vertikalen Raum für deine Inhalte. Du kannst hier auch beginnen, erste kleinere Nebeninformationen oder ein erweitertes Raster für Karten-Ansichten einzublenden.
  • Expanded: Diese Klasse kommt bei 840dp und mehr zum Einsatz. Das sind klassische Tablets im Querformat (Landscape), große Foldables im Landscape-Modus oder Desktop-Umgebungen wie ChromeOS. Hier entfalten mehrspaltige Layouts, wie etwa eine klassische List-Detail-Ansicht, ihr volles Potenzial. Eine persistente Navigation Drawer bietet sich an, um komplexe Hierarchien abzubilden. Der großzügige Platz erlaubt es, zusätzliche Kontextinformationen permanent anzuzeigen, ohne den Nutzer zu überladen.

In Jetpack Compose ist die Anwendung dieses Konzepts besonders elegant. Du kannst die aktuelle Window Size Class direkt in deiner Activity oder an der Wurzel deines Compose-Baums ermitteln. Compose liefert dir ein Datenobjekt zurück, das du als normalen State durch deine UI-Hierarchie nach unten reichen kannst. Wenn der Nutzer nun das Gerät dreht oder die Fenstergröße auf einem Tablet ändert, berechnet das System die Klasse neu und löst automatisch eine Recomposition aus. Deine UI zeichnet sich dann basierend auf der neuen Klasse völlig selbstständig neu, ohne dass du manuelle Listener für Konfigurationsänderungen registrieren oder abmelden musst.

Das API-Design fördert eine saubere Architektur und zwingt dich quasi dazu, deinen UI-Code in kleine, spezialisierte und wiederverwendbare Composables zu zerlegen. Du programmierst eine Komponente, die für den kompakten Zustand optimiert ist, und eine völlig eigenständige Komponente für den erweiterten Zustand. Ein übergeordnetes, steuerndes Composable fungiert dann als Router. Dieser Router prüft lediglich den Zustand der Window Size Class und ruft daraufhin die jeweils passende Unterkomponente auf. So bleibt deine Logik übersichtlich und testbar.

In der Praxis

Die Implementierung in Jetpack Compose ist unkompliziert, erfordert jedoch zunächst, dass du die entsprechende Bibliothek in dein Projekt einbindest. Füge dazu die Abhängigkeit androidx.compose.material3:material3-window-size-class in deine build.gradle.kts-Datei auf App-Ebene ein. Diese Bibliothek stellt die notwendigen Funktionen und Datenstrukturen bereit.

Sobald das Setup abgeschlossen ist, beginnst du in der Regel in deiner MainActivity. Hier nutzt du die Funktion calculateWindowSizeClass(), um den aktuellen Zustand des Fensters zu erfassen. Das folgende Beispiel demonstriert, wie du diese Information an deine Haupt-Applikationslogik übergibst und darauf basierend unterschiedliche Layouts renderst.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Berechnet die Größe bei jedem Resize oder jeder Drehung neu
            val windowSizeClass = calculateWindowSizeClass(this)

            // Reiche nur die relevante Breiten-Klasse weiter
            MyApp(widthSizeClass = windowSizeClass.widthSizeClass)
        }
    }
}

@Composable
fun MyApp(widthSizeClass: WindowWidthSizeClass) {
    // Der Router entscheidet basierend auf der Window Size Class
    when (widthSizeClass) {
        WindowWidthSizeClass.Compact -> {
            CompactLayout()
        }
        WindowWidthSizeClass.Medium -> {
            MediumLayout()
        }
        WindowWidthSizeClass.Expanded -> {
            ExpandedLayout()
        }
        else -> {
            // Ein Fallback ist wichtig, falls unerwartete Zustände auftreten
            CompactLayout()
        }
    }
}

@Composable
fun CompactLayout() {
    // Hier implementierst du ein Design für Smartphones,
    // z.B. mit einer NavigationBar am unteren Rand und Listen in einer Spalte.
}

@Composable
fun MediumLayout() {
    // Hier nutzt du den Platz für ein zweispaltiges Grid
    // und eine NavigationRail am Rand.
}

@Composable
fun ExpandedLayout() {
    // Der maximale Platz wird für eine persistente NavigationDrawer
    // und eine List-Detail-Ansicht (Master-Detail) genutzt.
}

Wenn du dieses Muster anwendest, wirst du schnell auf eine typische Stolperfalle stoßen: Viele Anfänger versuchen, die Window Size Class tief in den unteren Schichten der UI-Hierarchie, also in kleinen Button- oder Text-Composables, abzufragen. Das führt zu stark gekoppeltem Code, der extrem schwer zu warten und noch schwerer zu testen ist. Wenn ein Button selbst entscheidet, wie groß er aufgrund des Bildschirms sein soll, verlierst du die Kontrolle über das Gesamtlayout.

Die Lösung hierfür nennt sich State Hoisting. Reiche die Strukturierungsentscheidungen von oben nach unten. Die tiefen, atomaren Composables sollten die Window Size Class überhaupt nicht kennen. Sie sollten nur Daten und Callbacks als Parameter erhalten und sich so groß zeichnen, wie es der Container, in den sie platziert werden, zulässt.

Ein weiterer Fehler ist die irrtümliche Annahme, dass das Gerät zwangsläufig im Querformat gehalten wird, wenn die Klasse Expanded erreicht ist. Die Window Size Class beschreibt ausschließlich die verfügbare Fläche, nicht die physische Lage des Geräts. Ein Foldable im Hochformat kann ebenfalls Expanded sein. Verlasse dich daher immer auf den berechneten logischen Platz, um deine Layout-Entscheidungen zu fällen, und ignoriere die Hardware-Ausrichtung weitestgehend.

Beim Testen deiner Implementierung ist es zwingend erforderlich, dass du nicht nur auf einem statischen Smartphone-Emulator prüfst. Aktiviere in den Entwickleroptionen des Emulators die freie Fensteranpassung (Freeform Windows) oder nutze den Resizable Emulator in Android Studio. Verändere die Fenstergröße dynamisch zur Laufzeit. Beobachte genau, ob das Layout an den definierten Breakpoints reibungslos umschaltet, ob Zustände wie Scroll-Positionen verloren gehen und ob die Navigationselemente korrekt ausgetauscht werden. Ein gründliches Code-Review sollte immer hinterfragen, ob die Trennung zwischen kompakten und erweiterten Layouts sauber vollzogen wurde und keine unnötigen Abhängigkeiten bestehen.

Fazit

Window Size Classes strukturieren die Gestaltung responsiver Layouts unter Android, indem sie die komplexe Fragmentierung der Geräte in die handlichen Kategorien Compact, Medium und Expanded übersetzen. Durch diese Standardisierung reduzierst du fehleranfällige Pixel-Berechnungen und kannst dich voll darauf konzentrieren, eine benutzbare Oberfläche für jede Platzsituation zu entwerfen. Um dieses Wissen zu festigen, solltest du nun dein bestehendes Compose-Projekt öffnen, die Window Size Class in der Activity ermitteln und im Android Studio Emulator testen, wie präzise sich deine UI durch Verkleinern des Fensters vom Expanded- in den Compact-Zustand überführen lässt.

Quellen (1)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.